diff options
163 files changed, 4228 insertions, 1464 deletions
diff --git a/apex/jobscheduler/service/java/com/android/server/DeviceIdleController.java b/apex/jobscheduler/service/java/com/android/server/DeviceIdleController.java index 45588e831cb9..9eb7bb7149ef 100644 --- a/apex/jobscheduler/service/java/com/android/server/DeviceIdleController.java +++ b/apex/jobscheduler/service/java/com/android/server/DeviceIdleController.java @@ -3941,6 +3941,10 @@ public class DeviceIdleController extends SystemService if (idleUntil) { mAlarmManager.setIdleUntil(AlarmManager.ELAPSED_REALTIME_WAKEUP, mNextAlarmTime, "DeviceIdleController.deep", mDeepAlarmListener, mHandler); + } else if (mState == STATE_LOCATING) { + // Use setExact so we don't keep the GPS active for too long. + mAlarmManager.setExact(AlarmManager.ELAPSED_REALTIME_WAKEUP, + mNextAlarmTime, "DeviceIdleController.deep", mDeepAlarmListener, mHandler); } else { if (mConstants.USE_WINDOW_ALARMS) { mAlarmManager.setWindow(AlarmManager.ELAPSED_REALTIME_WAKEUP, diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java index bfff27a82f5b..3b0a5f3e9bdd 100644 --- a/core/java/android/app/ActivityThread.java +++ b/core/java/android/app/ActivityThread.java @@ -4906,7 +4906,8 @@ public final class ActivityThread extends ClientTransactionHandler Slog.w(TAG, "Activity top position already set to onTop=" + onTop); return; } - throw new IllegalStateException("Activity top position already set to onTop=" + onTop); + // TODO(b/197484331): Remove this short-term workaround while fixing the binder failure. + Slog.e(TAG, "Activity top position already set to onTop=" + onTop); } r.isTopResumedActivity = onTop; diff --git a/core/java/android/appwidget/AppWidgetProviderInfo.java b/core/java/android/appwidget/AppWidgetProviderInfo.java index 063ba1174cdc..2e94dd1a47c4 100644 --- a/core/java/android/appwidget/AppWidgetProviderInfo.java +++ b/core/java/android/appwidget/AppWidgetProviderInfo.java @@ -143,7 +143,7 @@ public class AppWidgetProviderInfo implements Parcelable { public ComponentName provider; /** - * The default height of the widget when added to a host, in dp. The widget will get + * The default height of the widget when added to a host, in px. The widget will get * at least this width, and will often be given more, depending on the host. * * <p>This field corresponds to the <code>android:minWidth</code> attribute in @@ -152,7 +152,7 @@ public class AppWidgetProviderInfo implements Parcelable { public int minWidth; /** - * The default height of the widget when added to a host, in dp. The widget will get + * The default height of the widget when added to a host, in px. The widget will get * at least this height, and will often be given more, depending on the host. * * <p>This field corresponds to the <code>android:minHeight</code> attribute in @@ -161,7 +161,7 @@ public class AppWidgetProviderInfo implements Parcelable { public int minHeight; /** - * Minimum width (in dp) which the widget can be resized to. This field has no effect if it + * Minimum width (in px) which the widget can be resized to. This field has no effect if it * is greater than minWidth or if horizontal resizing isn't enabled (see {@link #resizeMode}). * * <p>This field corresponds to the <code>android:minResizeWidth</code> attribute in @@ -170,7 +170,7 @@ public class AppWidgetProviderInfo implements Parcelable { public int minResizeWidth; /** - * Minimum height (in dp) which the widget can be resized to. This field has no effect if it + * Minimum height (in px) which the widget can be resized to. This field has no effect if it * is greater than minHeight or if vertical resizing isn't enabled (see {@link #resizeMode}). * * <p>This field corresponds to the <code>android:minResizeHeight</code> attribute in @@ -179,7 +179,7 @@ public class AppWidgetProviderInfo implements Parcelable { public int minResizeHeight; /** - * Maximum width (in dp) which the widget can be resized to. This field has no effect if it is + * Maximum width (in px) which the widget can be resized to. This field has no effect if it is * smaller than minWidth or if horizontal resizing isn't enabled (see {@link #resizeMode}). * * <p>This field corresponds to the <code>android:maxResizeWidth</code> attribute in the @@ -189,7 +189,7 @@ public class AppWidgetProviderInfo implements Parcelable { public int maxResizeWidth; /** - * Maximum height (in dp) which the widget can be resized to. This field has no effect if it is + * Maximum height (in px) which the widget can be resized to. This field has no effect if it is * smaller than minHeight or if vertical resizing isn't enabled (see {@link #resizeMode}). * * <p>This field corresponds to the <code>android:maxResizeHeight</code> attribute in the diff --git a/core/java/android/content/Context.java b/core/java/android/content/Context.java index 6885c10b4889..d9b261fa31f9 100644 --- a/core/java/android/content/Context.java +++ b/core/java/android/content/Context.java @@ -3286,7 +3286,7 @@ public abstract class Context { * apps targeting SDK Version {@link android.os.Build.VERSION_CODES#O} * or higher are not allowed to start background services from the background. * See - * <a href="{@docRoot}/about/versions/oreo/background"> + * <a href="/about/versions/oreo/background"> * Background Execution Limits</a> * for more details. * @@ -3295,7 +3295,7 @@ public abstract class Context { * apps targeting SDK Version {@link android.os.Build.VERSION_CODES#S} * or higher are not allowed to start foreground services from the background. * See - * <a href="{@docRoot}/about/versions/12/behavior-changes-12"> + * <a href="/about/versions/12/behavior-changes-12"> * Behavior changes: Apps targeting Android 12 * </a> * for more details. @@ -3349,7 +3349,7 @@ public abstract class Context { * apps targeting SDK Version {@link android.os.Build.VERSION_CODES#S} * or higher are not allowed to start foreground services from the background. * See - * <a href="{@docRoot}/about/versions/12/behavior-changes-12"> + * <a href="/about/versions/12/behavior-changes-12"> * Behavior changes: Apps targeting Android 12 * </a> * for more details. diff --git a/core/java/android/hardware/display/DisplayManager.java b/core/java/android/hardware/display/DisplayManager.java index e13a7b6eac65..fc8337ac3155 100644 --- a/core/java/android/hardware/display/DisplayManager.java +++ b/core/java/android/hardware/display/DisplayManager.java @@ -1257,6 +1257,23 @@ public final class DisplayManager { */ String KEY_FIXED_REFRESH_RATE_HIGH_AMBIENT_BRIGHTNESS_THRESHOLDS = "fixed_refresh_rate_high_ambient_brightness_thresholds"; + + /** + * Key for refresh rate when the device is in high brightness mode for sunlight visility. + * + * @see android.provider.DeviceConfig#NAMESPACE_DISPLAY_MANAGER + * @see android.R.integer#config_defaultRefreshRateInHbmSunlight + */ + String KEY_REFRESH_RATE_IN_HBM_SUNLIGHT = "refresh_rate_in_hbm_sunlight"; + + /** + * Key for refresh rate when the device is in high brightness mode for HDR. + * + * @see android.provider.DeviceConfig#NAMESPACE_DISPLAY_MANAGER + * @see android.R.integer#config_defaultRefreshRateInHbmHdr + */ + String KEY_REFRESH_RATE_IN_HBM_HDR = "refresh_rate_in_hbm_hdr"; + /** * Key for default peak refresh rate * diff --git a/core/java/android/service/translation/ITranslationService.aidl b/core/java/android/service/translation/ITranslationService.aidl index e9dd2c3bd70b..4cc732ab5a30 100644 --- a/core/java/android/service/translation/ITranslationService.aidl +++ b/core/java/android/service/translation/ITranslationService.aidl @@ -24,7 +24,7 @@ import com.android.internal.os.IResultReceiver; /** * System-wide on-device translation service. * - * <p>Services requests to translate text between different languages. The primary use case for this + * <p>Services requests to translate data between different languages. The primary use case for this * service is automatic translation of text and web views, when the auto Translate feature is * enabled. * diff --git a/core/java/android/service/translation/TranslationService.java b/core/java/android/service/translation/TranslationService.java index 93c006aff435..d454c393a7ce 100644 --- a/core/java/android/service/translation/TranslationService.java +++ b/core/java/android/service/translation/TranslationService.java @@ -48,6 +48,7 @@ import android.view.translation.TranslationManager; import android.view.translation.TranslationRequest; import android.view.translation.TranslationResponse; import android.view.translation.TranslationSpec; +import android.view.translation.Translator; import com.android.internal.os.IResultReceiver; @@ -81,7 +82,10 @@ public abstract class TranslationService extends Service { * android.R.styleable#TranslationService translation-service}></code> tag. * * <p>Here's an example of how to use it on {@code AndroidManifest.xml}: - * TODO: fill in doc example (check CCService/AFService). + * <pre> <translation-service + * android:settingsActivity="foo.bar.SettingsActivity" + * . . . + * /></pre> */ public static final String SERVICE_META_DATA = "android.translation_service"; @@ -148,7 +152,6 @@ public abstract class TranslationService extends Service { void onTranslationSuccess(@NonNull TranslationResponse response); /** - * TODO: implement javadoc * @removed use {@link #onTranslationSuccess} with an error response instead. */ @Deprecated @@ -225,7 +228,7 @@ public abstract class TranslationService extends Service { * should call back with {@code false}.</p> * * @param translationContext the {@link TranslationContext} of the session being created. - * @param sessionId the int id of the session. + * @param sessionId the id of the session. * @param callback {@link Consumer} to notify whether the session was successfully created. */ // TODO(b/176464808): the session id won't be unique cross client/server process. Need to find @@ -234,8 +237,6 @@ public abstract class TranslationService extends Service { int sessionId, @NonNull Consumer<Boolean> callback); /** - * TODO: fill in javadoc. - * * @removed use {@link #onCreateTranslationSession(TranslationContext, int, Consumer)} * instead. */ @@ -246,19 +247,16 @@ public abstract class TranslationService extends Service { } /** - * TODO: fill in javadoc. + * Called when a translation session is finished. + * + * <p>The translation session is finished when the client calls {@link Translator#destroy()} on + * the corresponding translator. * - * @param sessionId + * @param sessionId id of the session that finished. */ public abstract void onFinishTranslationSession(int sessionId); /** - * TODO: fill in javadoc. - * - * @param request - * @param sessionId - * @param callback - * @param cancellationSignal * @removed use * {@link #onTranslationRequest(TranslationRequest, int, CancellationSignal, Consumer)} instead. */ @@ -276,23 +274,29 @@ public abstract class TranslationService extends Service { * {@link TranslationRequest#FLAG_PARTIAL_RESPONSES} was set, the service may call * {@code callback.accept()} multiple times with partial responses.</p> * - * @param request - * @param sessionId - * @param callback - * @param cancellationSignal + * @param request The translation request containing the data to be translated. + * @param sessionId id of the session that sent the translation request. + * @param cancellationSignal A {@link CancellationSignal} that notifies when a client has + * cancelled the operation in progress. + * @param callback {@link Consumer} to pass back the translation response. */ public abstract void onTranslationRequest(@NonNull TranslationRequest request, int sessionId, @Nullable CancellationSignal cancellationSignal, @NonNull Consumer<TranslationResponse> callback); /** - * TODO: fill in javadoc + * Called to request a set of {@link TranslationCapability}s that are supported by the service. + * + * <p>The set of translation capabilities are limited to those supporting the source and target + * {@link TranslationSpec.DataFormat}. e.g. Calling this with + * {@link TranslationSpec#DATA_FORMAT_TEXT} as source and target returns only capabilities that + * translates text to text.</p> * * <p>Must call {@code callback.accept} to pass back the set of translation capabilities.</p> * - * @param sourceFormat - * @param targetFormat - * @param callback + * @param sourceFormat data format restriction of the translation source spec. + * @param targetFormat data format restriction of the translation target spec. + * @param callback {@link Consumer} to pass back the set of translation capabilities. */ public abstract void onTranslationCapabilitiesRequest( @TranslationSpec.DataFormat int sourceFormat, diff --git a/core/java/android/text/TextLine.java b/core/java/android/text/TextLine.java index 1f11d10052fe..1a7ec7f99c95 100644 --- a/core/java/android/text/TextLine.java +++ b/core/java/android/text/TextLine.java @@ -782,7 +782,7 @@ public class TextLine { int spanStart = runStart; int spanLimit; - if (mSpanned == null) { + if (mSpanned == null || runStart == runLimit) { spanLimit = runLimit; } else { int target = after ? offset + 1 : offset; diff --git a/core/java/android/view/Surface.java b/core/java/android/view/Surface.java index ff2d2eb3d334..fa7330fb84eb 100644 --- a/core/java/android/view/Surface.java +++ b/core/java/android/view/Surface.java @@ -98,6 +98,7 @@ public class Surface implements Parcelable { private static native int nativeSetFrameRate( long nativeObject, float frameRate, int compatibility, int changeFrameRateStrategy); + private static native void nativeDestroy(long nativeObject); public static final @android.annotation.NonNull Parcelable.Creator<Surface> CREATOR = new Parcelable.Creator<Surface>() { @@ -339,6 +340,9 @@ public class Surface implements Parcelable { */ @UnsupportedAppUsage public void destroy() { + if (mNativeObject != 0) { + nativeDestroy(mNativeObject); + } release(); } diff --git a/core/java/android/view/SurfaceView.java b/core/java/android/view/SurfaceView.java index c1956e45653b..4b8b607de089 100644 --- a/core/java/android/view/SurfaceView.java +++ b/core/java/android/view/SurfaceView.java @@ -903,7 +903,7 @@ public class SurfaceView extends View implements ViewRootImpl.SurfaceChangedCall mSurfaceAlpha = 1f; synchronized (mSurfaceControlLock) { - mSurface.release(); + mSurface.destroy(); if (mBlastBufferQueue != null) { mBlastBufferQueue.destroy(); mBlastBufferQueue = null; diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java index 3550a31f9038..93138e593646 100644 --- a/core/java/android/view/ViewRootImpl.java +++ b/core/java/android/view/ViewRootImpl.java @@ -2836,8 +2836,13 @@ public final class ViewRootImpl implements ViewParent, } } + final boolean surfaceControlChanged = + (relayoutResult & RELAYOUT_RES_SURFACE_CHANGED) + == RELAYOUT_RES_SURFACE_CHANGED; + if (mSurfaceControl.isValid()) { - updateOpacity(mWindowAttributes, dragResizing); + updateOpacity(mWindowAttributes, dragResizing, + surfaceControlChanged /*forceUpdate */); } if (DEBUG_LAYOUT) Log.v(mTag, "relayout: frame=" + frame.toShortString() @@ -2872,9 +2877,7 @@ public final class ViewRootImpl implements ViewParent, // RELAYOUT_RES_SURFACE_CHANGED since it should indicate that WMS created a new // SurfaceControl. surfaceReplaced = (surfaceGenerationId != mSurface.getGenerationId() - || (relayoutResult & RELAYOUT_RES_SURFACE_CHANGED) - == RELAYOUT_RES_SURFACE_CHANGED) - && mSurface.isValid(); + || surfaceControlChanged) && mSurface.isValid(); if (surfaceReplaced) { mSurfaceSequenceId++; } @@ -7824,7 +7827,8 @@ public final class ViewRootImpl implements ViewParent, return relayoutResult; } - private void updateOpacity(WindowManager.LayoutParams params, boolean dragResizing) { + private void updateOpacity(WindowManager.LayoutParams params, boolean dragResizing, + boolean forceUpdate) { boolean opaque = false; if (!PixelFormat.formatHasAlpha(params.format) @@ -7840,7 +7844,7 @@ public final class ViewRootImpl implements ViewParent, opaque = true; } - if (mIsSurfaceOpaque == opaque) { + if (!forceUpdate && mIsSurfaceOpaque == opaque) { return; } diff --git a/core/java/android/view/translation/TranslationCapability.java b/core/java/android/view/translation/TranslationCapability.java index 65b749add1b2..b7e13dda9ff6 100644 --- a/core/java/android/view/translation/TranslationCapability.java +++ b/core/java/android/view/translation/TranslationCapability.java @@ -40,15 +40,18 @@ import java.util.function.Consumer; public final class TranslationCapability implements Parcelable { /** - * TODO: fill in javadoc + * The translation service supports translation between the source and target specs, and it is + * ready to be downloaded onto the device. */ public static final @ModelState int STATE_AVAILABLE_TO_DOWNLOAD = 1; /** - * TODO: fill in javadoc + * The translation service supports translation between the source and target specs, and it is + * being downloaded onto the device currently. */ public static final @ModelState int STATE_DOWNLOADING = 2; /** - * TODO: fill in javadoc + * The translation service supports translation between the source and target specs, and it is + * downloaded and ready to use on device. */ public static final @ModelState int STATE_ON_DEVICE = 3; /** @@ -305,7 +308,7 @@ public final class TranslationCapability implements Parcelable { }; @DataClass.Generated( - time = 1624307114468L, + time = 1629158466039L, codegenVersion = "1.0.23", sourceFile = "frameworks/base/core/java/android/view/translation/TranslationCapability.java", inputSignatures = "public static final @android.view.translation.TranslationCapability.ModelState int STATE_AVAILABLE_TO_DOWNLOAD\npublic static final @android.view.translation.TranslationCapability.ModelState int STATE_DOWNLOADING\npublic static final @android.view.translation.TranslationCapability.ModelState int STATE_ON_DEVICE\npublic static final @android.view.translation.TranslationCapability.ModelState int STATE_NOT_AVAILABLE\npublic static final @android.view.translation.TranslationCapability.ModelState int STATE_REMOVED_AND_AVAILABLE\nprivate final @android.view.translation.TranslationCapability.ModelState int mState\nprivate final @android.annotation.NonNull android.view.translation.TranslationSpec mSourceSpec\nprivate final @android.annotation.NonNull android.view.translation.TranslationSpec mTargetSpec\nprivate final boolean mUiTranslationEnabled\nprivate final @android.view.translation.TranslationContext.TranslationFlag int mSupportedTranslationFlags\nclass TranslationCapability extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genHiddenConstDefs=true, genToString=true, genConstructor=false)") diff --git a/core/java/android/view/translation/TranslationRequest.java b/core/java/android/view/translation/TranslationRequest.java index df4836ebde46..0d41851ca704 100644 --- a/core/java/android/view/translation/TranslationRequest.java +++ b/core/java/android/view/translation/TranslationRequest.java @@ -39,12 +39,16 @@ public final class TranslationRequest implements Parcelable { public static final @RequestFlags int FLAG_TRANSLATION_RESULT = 0x1; /** * Indicates this request wants to receive the dictionary result. - * TODO: describe the structure of the result. + * + * <p>See {@link TranslationResponseValue#EXTRA_DEFINITIONS} for more detail on the structure + * of the returned data. */ public static final @RequestFlags int FLAG_DICTIONARY_RESULT = 0x2; /** * Indicates this request wants to receive the transliteration result. - * TODO: describe the structure of the result. + * + * <p>This returns a CharSequence representation of the transliteration of the translated text. + * See {@link TranslationResponseValue#getTransliteration()}. */ public static final @RequestFlags int FLAG_TRANSLITERATION_RESULT = 0x4; /** @@ -327,7 +331,8 @@ public final class TranslationRequest implements Parcelable { return this; } - /** @see #setTranslationRequestValues + /** + * @see #setTranslationRequestValues * @removed */ @DataClass.Generated.Member @@ -352,7 +357,8 @@ public final class TranslationRequest implements Parcelable { return this; } - /** @see #setViewTranslationRequests + /** + * @see #setViewTranslationRequests * @removed */ @DataClass.Generated.Member @@ -394,7 +400,7 @@ public final class TranslationRequest implements Parcelable { } @DataClass.Generated( - time = 1620429997487L, + time = 1629159107226L, codegenVersion = "1.0.23", sourceFile = "frameworks/base/core/java/android/view/translation/TranslationRequest.java", inputSignatures = "public static final @android.view.translation.TranslationRequest.RequestFlags int FLAG_TRANSLATION_RESULT\npublic static final @android.view.translation.TranslationRequest.RequestFlags int FLAG_DICTIONARY_RESULT\npublic static final @android.view.translation.TranslationRequest.RequestFlags int FLAG_TRANSLITERATION_RESULT\npublic static final @android.view.translation.TranslationRequest.RequestFlags int FLAG_PARTIAL_RESPONSES\nprivate final @android.view.translation.TranslationRequest.RequestFlags int mFlags\nprivate final @android.annotation.NonNull @com.android.internal.util.DataClass.PluralOf(\"translationRequestValue\") java.util.List<android.view.translation.TranslationRequestValue> mTranslationRequestValues\nprivate final @android.annotation.NonNull @com.android.internal.util.DataClass.PluralOf(\"viewTranslationRequest\") java.util.List<android.view.translation.ViewTranslationRequest> mViewTranslationRequests\nprivate static int defaultFlags()\nprivate static java.util.List<android.view.translation.TranslationRequestValue> defaultTranslationRequestValues()\nprivate static java.util.List<android.view.translation.ViewTranslationRequest> defaultViewTranslationRequests()\nclass TranslationRequest extends java.lang.Object implements [android.os.Parcelable]\npublic abstract @java.lang.Deprecated android.view.translation.TranslationRequest.Builder addTranslationRequestValue(android.view.translation.TranslationRequestValue)\npublic abstract @java.lang.Deprecated android.view.translation.TranslationRequest.Builder addViewTranslationRequest(android.view.translation.ViewTranslationRequest)\nclass BaseBuilder extends java.lang.Object implements []\n@com.android.internal.util.DataClass(genToString=true, genHiddenConstDefs=true, genBuilder=true)\npublic abstract @java.lang.Deprecated android.view.translation.TranslationRequest.Builder addTranslationRequestValue(android.view.translation.TranslationRequestValue)\npublic abstract @java.lang.Deprecated android.view.translation.TranslationRequest.Builder addViewTranslationRequest(android.view.translation.ViewTranslationRequest)\nclass BaseBuilder extends java.lang.Object implements []") diff --git a/core/java/android/view/translation/TranslationResponseValue.java b/core/java/android/view/translation/TranslationResponseValue.java index a24dbc3ae4f8..9dff2d56322b 100644 --- a/core/java/android/view/translation/TranslationResponseValue.java +++ b/core/java/android/view/translation/TranslationResponseValue.java @@ -93,9 +93,11 @@ public final class TranslationResponseValue implements Parcelable { @NonNull private final Bundle mExtras; + // TODO: Add example of transliteration. /** * The transliteration result of the translated text. - * TODO: Describe the result structure. + * + * <p>This returns a CharSequence representation of the transliteration of the translated text. */ @Nullable private final CharSequence mTransliteration; @@ -223,7 +225,8 @@ public final class TranslationResponseValue implements Parcelable { /** * The transliteration result of the translated text. - * TODO: Describe the result structure. + * + * <p>This returns a CharSequence representation of the transliteration of the translated text. */ @DataClass.Generated.Member public @Nullable CharSequence getTransliteration() { @@ -407,7 +410,8 @@ public final class TranslationResponseValue implements Parcelable { /** * The transliteration result of the translated text. - * TODO: Describe the result structure. + * + * <p>This returns a CharSequence representation of the transliteration of the translated text. */ @DataClass.Generated.Member public @NonNull Builder setTransliteration(@NonNull CharSequence value) { @@ -448,7 +452,7 @@ public final class TranslationResponseValue implements Parcelable { } @DataClass.Generated( - time = 1622133051937L, + time = 1631057245846L, codegenVersion = "1.0.23", sourceFile = "frameworks/base/core/java/android/view/translation/TranslationResponseValue.java", inputSignatures = "public static final int STATUS_SUCCESS\npublic static final int STATUS_ERROR\npublic static final java.lang.String EXTRA_DEFINITIONS\nprivate final @android.view.translation.TranslationResponseValue.Status int mStatusCode\nprivate final @android.annotation.Nullable java.lang.CharSequence mText\nprivate final @android.annotation.NonNull android.os.Bundle mExtras\nprivate final @android.annotation.Nullable java.lang.CharSequence mTransliteration\npublic static @android.annotation.NonNull android.view.translation.TranslationResponseValue forError()\nprivate static java.lang.CharSequence defaultText()\nprivate static android.os.Bundle defaultExtras()\nprivate boolean extrasEquals(android.os.Bundle)\nprivate static java.lang.CharSequence defaultTransliteration()\nclass TranslationResponseValue extends java.lang.Object implements [android.os.Parcelable]\nclass BaseBuilder extends java.lang.Object implements []\n@com.android.internal.util.DataClass(genBuilder=true, genToString=true, genEqualsHashCode=true, genHiddenConstDefs=true)\nclass BaseBuilder extends java.lang.Object implements []") diff --git a/core/java/android/view/translation/UiTranslationController.java b/core/java/android/view/translation/UiTranslationController.java index a8335918cc84..442d099f0678 100644 --- a/core/java/android/view/translation/UiTranslationController.java +++ b/core/java/android/view/translation/UiTranslationController.java @@ -356,7 +356,11 @@ public class UiTranslationController { } for (int i = 0; i < translatedResult.size(); i++) { final AutofillId autofillId = new AutofillId(translatedResult.keyAt(i)); - final View view = mViews.get(autofillId).get(); + final WeakReference<View> viewRef = mViews.get(autofillId); + if (viewRef == null) { + continue; + } + final View view = viewRef.get(); if (view == null) { Log.w(TAG, "onTranslationCompleted: the view for autofill id " + autofillId + " may be gone."); @@ -416,7 +420,11 @@ public class UiTranslationController { Log.w(TAG, "No AutofillId is set in ViewTranslationResponse"); continue; } - final View view = mViews.get(autofillId).get(); + final WeakReference<View> viewRef = mViews.get(autofillId); + if (viewRef == null) { + continue; + } + final View view = viewRef.get(); if (view == null) { Log.w(TAG, "onTranslationCompleted: the view for autofill id " + autofillId + " may be gone."); diff --git a/core/java/android/view/translation/UiTranslationManager.java b/core/java/android/view/translation/UiTranslationManager.java index b9ed32ce248b..3012e9344a1b 100644 --- a/core/java/android/view/translation/UiTranslationManager.java +++ b/core/java/android/view/translation/UiTranslationManager.java @@ -33,6 +33,7 @@ import android.util.ArrayMap; import android.util.Log; import android.view.View; import android.view.autofill.AutofillId; +import android.widget.TextView; import com.android.internal.annotations.GuardedBy; @@ -42,11 +43,50 @@ import java.util.List; import java.util.Map; import java.util.Objects; import java.util.concurrent.Executor; +import java.util.function.Consumer; -// TODO(b/178044703): Describe what UI Translation is. /** - * The {@link UiTranslationManager} class provides ways for apps to use the ui translation + * <p>The {@link UiTranslationManager} class provides ways for apps to use the ui translation * function in framework. + * + * <p> The UI translation provides ways for apps to support inline translation for the views. For + * example the system supports text translation for {@link TextView}. To support UI translation for + * your views, you should override the following methods to provide the content to be translated + * and deal with the translated result. Here is an example for {@link TextView}-like views: + * + * <pre><code> + * public class MyTextView extends View { + * public MyTextView(...) { + * // implements how to show the translated result in your View in + * // ViewTranslationCallback and set it by setViewTranslationCallback() + * setViewTranslationCallback(new MyViewTranslationCallback()); + * } + * + * public void onCreateViewTranslationRequest(int[] supportedFormats, + * Consumer<ViewTranslationRequest> requestsCollector) { + * // collect the information that needs to be translated + * ViewTranslationRequest.Builder requestBuilder = + * new ViewTranslationRequest.Builder(getAutofillId()); + * requestBuilder.setValue(ViewTranslationRequest.ID_TEXT, + * TranslationRequestValue.forText(etText())); + * requestsCollector.accept(requestBuilder.build()); + * } + * + * public void onProvideContentCaptureStructure( + * ViewStructure structure, int flags) { + * // set ViewTranslationResponse + * super.onViewTranslationResponse(response); + * } + * } + * </code></pre> + * + * <p>If your view provides its own virtual hierarchy (for example, if it's a browser that draws the + * HTML using {@link android.graphics.Canvas} or native libraries in a different render process), + * you must override {@link View#onCreateVirtualViewTranslationRequests(long[], int[], Consumer)} to + * provide the content to be translated and implement + * {@link View#onVirtualViewTranslationResponses(android.util.LongSparseArray)} for the translated + * result. You also need to implement {@link android.view.translation.ViewTranslationCallback} to + * handle the translated information show or hide in your {@link View}. */ public final class UiTranslationManager { @@ -248,14 +288,14 @@ public final class UiTranslationManager { } } - // TODO(b/178044703): Fix the View API link when it becomes public. /** * Register for notifications of UI Translation state changes on the foreground activity. This * is available to the owning application itself and also the current input method. * <p> * The application whose UI is being translated can use this to customize the UI Translation * behavior in ways that aren't made easy by methods like - * View#onCreateTranslationRequest(). + * {@link View#onCreateViewTranslationRequest(int[], Consumer)}. + * * <p> * Input methods can use this to offer complementary features to UI Translation; for example, * enabling outgoing message translation when the system is translating incoming messages in a diff --git a/core/java/android/view/translation/ViewTranslationCallback.java b/core/java/android/view/translation/ViewTranslationCallback.java index 6efd621c4caa..a0756622ca69 100644 --- a/core/java/android/view/translation/ViewTranslationCallback.java +++ b/core/java/android/view/translation/ViewTranslationCallback.java @@ -19,9 +19,17 @@ package android.view.translation; import android.annotation.NonNull; import android.annotation.UiThread; import android.view.View; +import android.view.contentcapture.ContentCaptureSession; /** - * Callback for handling the translated information show or hide in the {@link View}. + * <p> Callback for handling the translated information show or hide in the {@link View}. + * + * <p> When the platform intelligence starts translation of an app's ui, the system will call + * {@link View#dispatchCreateViewTranslationRequest} to collect the {@link ViewTranslationRequest}s + * for translation purpose by traversing the hierarchy then send to translation service. After + * receiving the {@link ViewTranslationResponse}, the system will call + * {@link ViewTranslationCallback#onShowTranslation(View)} to show the translated information for + * the {@link View}. */ @UiThread public interface ViewTranslationCallback { @@ -33,13 +41,19 @@ public interface ViewTranslationCallback { * method will not be called before {@link View#onViewTranslationResponse} or * {@link View#onVirtualViewTranslationResponses}. * + * <p> NOTE: For TextView implementation, {@link ContentCaptureSession#notifyViewTextChanged} + * shouldn't be called with the translated text, simply calling setText() here will trigger the + * method. You should either override {@code View#onProvideContentCaptureStructure()} to report + * the original text instead of the translated text or use a different approach to display the + * translated text. + * * See {@link View#onViewTranslationResponse} for how to get the translated information. * * @return {@code true} if the View handles showing the translation. */ boolean onShowTranslation(@NonNull View view); /** - * Called when the user wants to show the original text instead of the translated text. This + * Called when user wants to view the original content instead of the translated content. This * method will not be called before {@link View#onViewTranslationResponse} or * {@link View#onViewTranslationResponse}. * @@ -47,7 +61,8 @@ public interface ViewTranslationCallback { */ boolean onHideTranslation(@NonNull View view); /** - * Called when the user finish the Ui translation and no longer to show the translated text. + * Called when the translation state is no longer needed. It should restore the original content + * and clear all saved states. * * @return {@code true} if the View handles clearing the translation. */ diff --git a/core/java/android/window/ITaskOrganizer.aidl b/core/java/android/window/ITaskOrganizer.aidl index 8b8dba89ea67..69bc1b5f7763 100644 --- a/core/java/android/window/ITaskOrganizer.aidl +++ b/core/java/android/window/ITaskOrganizer.aidl @@ -88,4 +88,9 @@ oneway interface ITaskOrganizer { * user has pressed back on the root activity of a task controlled by the task organizer. */ void onBackPressedOnTaskRoot(in ActivityManager.RunningTaskInfo taskInfo); + + /** + * Called when the IME has drawn on the organized task. + */ + void onImeDrawnOnTask(int taskId); } diff --git a/core/java/android/window/SplashScreen.java b/core/java/android/window/SplashScreen.java index 3e0075857402..0c4d273f6ce8 100644 --- a/core/java/android/window/SplashScreen.java +++ b/core/java/android/window/SplashScreen.java @@ -92,6 +92,9 @@ public interface SplashScreen { * overrides and persists the theme used for the {@link SplashScreen} of this application. * <p> * To reset to the default theme, set this the themeId to {@link Resources#ID_NULL}. + * <p> + * <b>Note:</b> The theme name must be stable across versions, otherwise it won't be found + * after your application is updated. */ void setSplashScreenTheme(@StyleRes int themeId); diff --git a/core/java/android/window/TaskOrganizer.java b/core/java/android/window/TaskOrganizer.java index c7c91cdd0941..d8723a821a22 100644 --- a/core/java/android/window/TaskOrganizer.java +++ b/core/java/android/window/TaskOrganizer.java @@ -144,6 +144,10 @@ public class TaskOrganizer extends WindowOrganizer { @BinderThread public void onBackPressedOnTaskRoot(@NonNull ActivityManager.RunningTaskInfo taskInfo) {} + /** @hide */ + @BinderThread + public void onImeDrawnOnTask(int taskId) {} + /** * Creates a persistent root task in WM for a particular windowing-mode. * @param displayId The display to create the root task on. @@ -287,6 +291,11 @@ public class TaskOrganizer extends WindowOrganizer { public void onBackPressedOnTaskRoot(ActivityManager.RunningTaskInfo info) { mExecutor.execute(() -> TaskOrganizer.this.onBackPressedOnTaskRoot(info)); } + + @Override + public void onImeDrawnOnTask(int taskId) { + mExecutor.execute(() -> TaskOrganizer.this.onImeDrawnOnTask(taskId)); + } }; private ITaskOrganizerController getController() { diff --git a/core/java/com/android/internal/infra/OWNERS b/core/java/com/android/internal/infra/OWNERS new file mode 100644 index 000000000000..45503582b2c5 --- /dev/null +++ b/core/java/com/android/internal/infra/OWNERS @@ -0,0 +1,6 @@ +per-file AndroidFuture.java = eugenesusla@google.com +per-file RemoteStream.java = eugenesusla@google.com +per-file PerUser.java = eugenesusla@google.com +per-file ServiceConnector.java = eugenesusla@google.com +per-file AndroidFuture.aidl = eugenesusla@google.com +per-file IAndroidFuture.aidl = eugenesusla@google.com
\ No newline at end of file diff --git a/core/java/com/android/internal/jank/FrameTracker.java b/core/java/com/android/internal/jank/FrameTracker.java index 8e7fae7aa061..d12c870d2591 100644 --- a/core/java/com/android/internal/jank/FrameTracker.java +++ b/core/java/com/android/internal/jank/FrameTracker.java @@ -50,6 +50,7 @@ import com.android.internal.util.FrameworkStatsLog; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; +import java.util.concurrent.TimeUnit; /** * A class that allows the app to get the frame metrics from HardwareRendererObserver. @@ -103,6 +104,7 @@ public class FrameTracker extends SurfaceControl.OnJankDataListener private boolean mCancelled = false; private FrameTrackerListener mListener; private boolean mTracingStarted = false; + private Runnable mWaitForFinishTimedOut; private static class JankInfo { long frameVsyncId; @@ -263,10 +265,16 @@ public class FrameTracker extends SurfaceControl.OnJankDataListener if (mListener != null) { mListener.onCujEvents(mSession, ACTION_SESSION_END); } + // We don't remove observer here, + // will remove it when all the frame metrics in this duration are called back. + // See onFrameMetricsAvailable for the logic of removing the observer. + // Waiting at most 10 seconds for all callbacks to finish. + mWaitForFinishTimedOut = () -> { + Log.e(TAG, "force finish cuj because of time out:" + mSession.getName()); + finish(mJankInfos.size() - 1); + }; + mHandler.postDelayed(mWaitForFinishTimedOut, TimeUnit.SECONDS.toMillis(10)); } - // We don't remove observer here, - // will remove it when all the frame metrics in this duration are called back. - // See onFrameMetricsAvailable for the logic of removing the observer. } /** @@ -396,7 +404,8 @@ public class FrameTracker extends SurfaceControl.OnJankDataListener } private void finish(int indexOnOrAfterEnd) { - + mHandler.removeCallbacks(mWaitForFinishTimedOut); + mWaitForFinishTimedOut = null; mMetricsFinalized = true; // The tracing has been ended, remove the observer, see if need to trigger perfetto. @@ -481,7 +490,7 @@ public class FrameTracker extends SurfaceControl.OnJankDataListener } } if (DEBUG) { - Log.i(TAG, "FrameTracker: CUJ=" + mSession.getName() + Log.i(TAG, "finish: CUJ=" + mSession.getName() + " (" + mBeginVsyncId + "," + mEndVsyncId + ")" + " totalFrames=" + totalFramesCount + " missedAppFrames=" + missedAppFramesCount diff --git a/core/java/com/android/internal/jank/InteractionJankMonitor.java b/core/java/com/android/internal/jank/InteractionJankMonitor.java index aabcd7f82ac7..610cd7339001 100644 --- a/core/java/com/android/internal/jank/InteractionJankMonitor.java +++ b/core/java/com/android/internal/jank/InteractionJankMonitor.java @@ -103,7 +103,7 @@ public class InteractionJankMonitor { private static final String ACTION_PREFIX = InteractionJankMonitor.class.getCanonicalName(); private static final String DEFAULT_WORKER_NAME = TAG + "-Worker"; - private static final long DEFAULT_TIMEOUT_MS = TimeUnit.SECONDS.toMillis(5L); + private static final long DEFAULT_TIMEOUT_MS = TimeUnit.SECONDS.toMillis(2L); private static final String SETTINGS_ENABLED_KEY = "enabled"; private static final String SETTINGS_SAMPLING_INTERVAL_KEY = "sampling_interval"; private static final String SETTINGS_THRESHOLD_MISSED_FRAMES_KEY = diff --git a/core/java/com/android/internal/util/function/pooled/OWNERS b/core/java/com/android/internal/util/function/pooled/OWNERS new file mode 100644 index 000000000000..da723b3b67da --- /dev/null +++ b/core/java/com/android/internal/util/function/pooled/OWNERS @@ -0,0 +1 @@ +eugenesusla@google.com
\ No newline at end of file diff --git a/core/jni/OWNERS b/core/jni/OWNERS index 78787e52dbaa..da896636aaa8 100644 --- a/core/jni/OWNERS +++ b/core/jni/OWNERS @@ -1,6 +1,6 @@ # Camera per-file *Camera*,*camera* = cychen@google.com, epeev@google.com, etalvala@google.com -per-file *Camera*,*camera* = shuzhenwang@google.com, zhijunhe@google.com +per-file *Camera*,*camera* = shuzhenwang@google.com, yinchiayeh@google.com, zhijunhe@google.com # Connectivity per-file android_net_* = codewiz@google.com, jchalard@google.com, lorenzo@google.com, reminv@google.com, satk@google.com diff --git a/core/jni/android_view_Surface.cpp b/core/jni/android_view_Surface.cpp index 0957067de603..869b53df2837 100644 --- a/core/jni/android_view_Surface.cpp +++ b/core/jni/android_view_Surface.cpp @@ -449,6 +449,11 @@ static jint nativeSetFrameRate(JNIEnv* env, jclass clazz, jlong nativeObject, jf int(changeFrameRateStrategy)); } +static void nativeDestroy(JNIEnv* env, jclass clazz, jlong nativeObject) { + sp<Surface> surface(reinterpret_cast<Surface*>(nativeObject)); + surface->destroy(); +} + // ---------------------------------------------------------------------------- static const JNINativeMethod gSurfaceMethods[] = { @@ -477,6 +482,7 @@ static const JNINativeMethod gSurfaceMethods[] = { {"nativeSetAutoRefreshEnabled", "(JZ)I", (void*)nativeSetAutoRefreshEnabled}, {"nativeSetFrameRate", "(JFII)I", (void*)nativeSetFrameRate}, {"nativeGetFromBlastBufferQueue", "(JJ)J", (void*)nativeGetFromBlastBufferQueue}, + {"nativeDestroy", "(J)V", (void*)nativeDestroy}, }; int register_android_view_Surface(JNIEnv* env) diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml index 8496a478af97..3fdcbe760df6 100644 --- a/core/res/res/values/config.xml +++ b/core/res/res/values/config.xml @@ -4497,6 +4497,13 @@ If non-positive, then the refresh rate is unchanged even if thresholds are configured. --> <integer name="config_fixedRefreshRateInHighZone">0</integer> + <!-- Default refresh rate while the device has high brightness mode enabled for Sunlight. + This value overrides values from DisplayDeviceConfig --> + <integer name="config_defaultRefreshRateInHbmSunlight">0</integer> + + <!-- Default refresh rate while the device has high brightness mode enabled for HDR. --> + <integer name="config_defaultRefreshRateInHbmHdr">0</integer> + <!-- The type of the light sensor to be used by the display framework for things like auto-brightness. If unset, then it just gets the default sensor of type TYPE_LIGHT. --> <string name="config_displayLightSensorType" translatable="false" /> diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml index d06dafe9b3e7..ebb42c46e7d0 100644 --- a/core/res/res/values/symbols.xml +++ b/core/res/res/values/symbols.xml @@ -3949,6 +3949,8 @@ <java-symbol type="integer" name="config_defaultRefreshRateInZone" /> <java-symbol type="array" name="config_brightnessThresholdsOfPeakRefreshRate" /> <java-symbol type="array" name="config_ambientThresholdsOfPeakRefreshRate" /> + <java-symbol type="integer" name="config_defaultRefreshRateInHbmSunlight" /> + <java-symbol type="integer" name="config_defaultRefreshRateInHbmHdr" /> <!-- For fixed refresh rate displays in high brightness--> <java-symbol type="integer" name="config_fixedRefreshRateInHighZone" /> diff --git a/graphics/java/android/graphics/ImageFormat.java b/graphics/java/android/graphics/ImageFormat.java index 5b79d76dd6b9..884d27f8d887 100644 --- a/graphics/java/android/graphics/ImageFormat.java +++ b/graphics/java/android/graphics/ImageFormat.java @@ -178,22 +178,8 @@ public class ImageFormat { * <p>Android YUV P010 format.</p> * * P010 is a 4:2:0 YCbCr semiplanar format comprised of a WxH Y plane - * followed immediately by a Wx(H/2) CbCr plane. Each sample is - * represented by a 16-bit little-endian value, with the lower 6 bits set - * to zero. - * - * <p>This format assumes - * <ul> - * <li>an even height</li> - * <li>a vertical stride equal to the height</li> - * </ul> - * </p> - * - * <pre> stride_in_bytes = stride * 2 </pre> - * <pre> y_size = stride_in_bytes * height </pre> - * <pre> cbcr_size = stride_in_bytes * (height / 2) </pre> - * <pre> cb_offset = y_size </pre> - * <pre> cr_offset = cb_offset + 2 </pre> + * followed by a Wx(H/2) CbCr plane. Each sample is represented by a 16-bit + * little-endian value, with the lower 6 bits set to zero. * * <p>For example, the {@link android.media.Image} object can provide data * in this format from a {@link android.hardware.camera2.CameraDevice} diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/ShellTaskOrganizer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/ShellTaskOrganizer.java index ba0ab6db1003..656bdff0c782 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/ShellTaskOrganizer.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/ShellTaskOrganizer.java @@ -336,6 +336,13 @@ public class ShellTaskOrganizer extends TaskOrganizer implements } @Override + public void onImeDrawnOnTask(int taskId) { + if (mStartingWindow != null) { + mStartingWindow.onImeDrawnOnTask(taskId); + } + } + + @Override public void onTaskAppeared(RunningTaskInfo taskInfo, SurfaceControl leash) { synchronized (mLock) { onTaskAppeared(new TaskAppearedInfo(taskInfo, leash)); diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/StartingSurfaceDrawer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/StartingSurfaceDrawer.java index fc7c86d669cb..147f5e30f9d6 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/StartingSurfaceDrawer.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/StartingSurfaceDrawer.java @@ -545,6 +545,15 @@ public class StartingSurfaceDrawer { removeWindowSynced(taskId, null, null, false); } + void onImeDrawnOnTask(int taskId) { + final StartingWindowRecord record = mStartingWindowRecords.get(taskId); + if (record != null && record.mTaskSnapshotWindow != null + && record.mTaskSnapshotWindow.hasImeSurface()) { + record.mTaskSnapshotWindow.removeImmediately(); + } + mStartingWindowRecords.remove(taskId); + } + protected void removeWindowSynced(int taskId, SurfaceControl leash, Rect frame, boolean playRevealAnimation) { final StartingWindowRecord record = mStartingWindowRecords.get(taskId); @@ -572,14 +581,15 @@ public class StartingSurfaceDrawer { Slog.e(TAG, "Found empty splash screen, remove!"); removeWindowInner(record.mDecorView, false); } + mStartingWindowRecords.remove(taskId); } if (record.mTaskSnapshotWindow != null) { if (DEBUG_TASK_SNAPSHOT) { Slog.v(TAG, "Removing task snapshot window for " + taskId); } - record.mTaskSnapshotWindow.remove(); + record.mTaskSnapshotWindow.scheduleRemove( + () -> mStartingWindowRecords.remove(taskId)); } - mStartingWindowRecords.remove(taskId); } } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/StartingWindowController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/StartingWindowController.java index e84d498a9258..dee21b093dce 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/StartingWindowController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/StartingWindowController.java @@ -177,6 +177,13 @@ public class StartingWindowController implements RemoteCallable<StartingWindowCo } /** + * Called when the IME has drawn on the organized task. + */ + public void onImeDrawnOnTask(int taskId) { + mSplashScreenExecutor.execute(() -> mStartingSurfaceDrawer.onImeDrawnOnTask(taskId)); + } + + /** * Called when the content of a task is ready to show, starting window can be removed. */ public void removeStartingWindow(int taskId, SurfaceControl leash, Rect frame, diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/TaskSnapshotWindow.java b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/TaskSnapshotWindow.java index 6052d3dee891..dfb1ae3ef2a0 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/TaskSnapshotWindow.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/TaskSnapshotWindow.java @@ -64,7 +64,6 @@ import android.graphics.RectF; import android.hardware.HardwareBuffer; import android.os.IBinder; import android.os.RemoteException; -import android.os.SystemClock; import android.os.Trace; import android.util.MergedConfiguration; import android.util.Slog; @@ -119,7 +118,12 @@ public class TaskSnapshotWindow { private static final String TITLE_FORMAT = "SnapshotStartingWindow for taskId=%s"; private static final long DELAY_REMOVAL_TIME_GENERAL = 100; - private static final long DELAY_REMOVAL_TIME_IME_VISIBLE = 350; + /** + * The max delay time in milliseconds for removing the task snapshot window with IME visible. + * Ideally the delay time will be shorter when receiving + * {@link StartingSurfaceDrawer#onImeDrawnOnTask(int)}. + */ + private static final long MAX_DELAY_REMOVAL_TIME_IME_VISIBLE = 600; //tmp vars for unused relayout params private static final Point TMP_SURFACE_SIZE = new Point(); @@ -138,7 +142,6 @@ public class TaskSnapshotWindow { private final RectF mTmpDstFrame = new RectF(); private final CharSequence mTitle; private boolean mHasDrawn; - private long mShownTime; private boolean mSizeMismatch; private final Paint mBackgroundPaint = new Paint(); private final int mActivityType; @@ -148,6 +151,8 @@ public class TaskSnapshotWindow { private final SurfaceControl.Transaction mTransaction; private final Matrix mSnapshotMatrix = new Matrix(); private final float[] mTmpFloat9 = new float[9]; + private Runnable mScheduledRunnable; + private final boolean mHasImeSurface; static TaskSnapshotWindow create(StartingWindowInfo info, IBinder appToken, TaskSnapshot snapshot, ShellExecutor splashScreenExecutor, @@ -216,7 +221,7 @@ public class TaskSnapshotWindow { taskDescription.setBackgroundColor(WHITE); } - final long delayRemovalTime = snapshot.hasImeSurface() ? DELAY_REMOVAL_TIME_IME_VISIBLE + final long delayRemovalTime = snapshot.hasImeSurface() ? MAX_DELAY_REMOVAL_TIME_IME_VISIBLE : DELAY_REMOVAL_TIME_GENERAL; final TaskSnapshotWindow snapshotSurface = new TaskSnapshotWindow( @@ -281,12 +286,17 @@ public class TaskSnapshotWindow { mDelayRemovalTime = delayRemovalTime; mTransaction = new SurfaceControl.Transaction(); mClearWindowHandler = clearWindowHandler; + mHasImeSurface = snapshot.hasImeSurface(); } int getBackgroundColor() { return mBackgroundPaint.getColor(); } + boolean hasImeSurface() { + return mHasImeSurface; + } + /** * Ask system bar background painter to draw status bar background. * @hide @@ -304,21 +314,32 @@ public class TaskSnapshotWindow { mSystemBarBackgroundPainter.drawNavigationBarBackground(c); } - void remove() { - final long now = SystemClock.uptimeMillis(); - if ((now - mShownTime < mDelayRemovalTime) - // Show the latest content as soon as possible for unlocking to home. - && mActivityType != ACTIVITY_TYPE_HOME) { - final long delayTime = mShownTime + mDelayRemovalTime - now; - mSplashScreenExecutor.executeDelayed(() -> remove(), delayTime); - if (DEBUG) { - Slog.d(TAG, "Defer removing snapshot surface in " + delayTime); - } + void scheduleRemove(Runnable onRemove) { + // Show the latest content as soon as possible for unlocking to home. + if (mActivityType == ACTIVITY_TYPE_HOME) { + removeImmediately(); + onRemove.run(); return; } + if (mScheduledRunnable != null) { + mSplashScreenExecutor.removeCallbacks(mScheduledRunnable); + mScheduledRunnable = null; + } + mScheduledRunnable = () -> { + TaskSnapshotWindow.this.removeImmediately(); + onRemove.run(); + }; + mSplashScreenExecutor.executeDelayed(mScheduledRunnable, mDelayRemovalTime); + if (DEBUG) { + Slog.d(TAG, "Defer removing snapshot surface in " + mDelayRemovalTime); + } + } + + void removeImmediately() { + mSplashScreenExecutor.removeCallbacks(mScheduledRunnable); try { if (DEBUG) { - Slog.d(TAG, "Removing snapshot surface, mHasDrawn: " + mHasDrawn); + Slog.d(TAG, "Removing taskSnapshot surface, mHasDrawn: " + mHasDrawn); } mSession.remove(mWindow); } catch (RemoteException e) { @@ -356,7 +377,6 @@ public class TaskSnapshotWindow { } else { drawSizeMatchSnapshot(); } - mShownTime = SystemClock.uptimeMillis(); mHasDrawn = true; reportDrawn(); diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/startingsurface/StartingSurfaceDrawerTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/startingsurface/StartingSurfaceDrawerTests.java index d536adb9f8ae..eef0d9bb268f 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/startingsurface/StartingSurfaceDrawerTests.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/startingsurface/StartingSurfaceDrawerTests.java @@ -15,38 +15,53 @@ */ package com.android.wm.shell.startingsurface; +import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN; +import static android.content.res.Configuration.ORIENTATION_PORTRAIT; import static android.window.StartingWindowInfo.STARTING_WINDOW_TYPE_SPLASH_SCREEN; import static com.android.dx.mockito.inline.extended.ExtendedMockito.doNothing; import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn; +import static com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession; import static com.android.dx.mockito.inline.extended.ExtendedMockito.spy; +import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotEquals; import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; import android.app.ActivityManager; import android.content.BroadcastReceiver; +import android.content.ComponentName; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; import android.content.pm.ActivityInfo; import android.content.pm.ApplicationInfo; import android.content.pm.PackageManager; +import android.graphics.ColorSpace; +import android.graphics.Point; import android.graphics.Rect; +import android.hardware.HardwareBuffer; import android.net.Uri; import android.os.Handler; import android.os.IBinder; import android.os.Looper; import android.os.UserHandle; import android.testing.TestableContext; +import android.view.IWindowSession; +import android.view.InsetsState; +import android.view.Surface; import android.view.SurfaceControl; import android.view.View; import android.view.WindowManager; +import android.view.WindowManagerGlobal; import android.view.WindowMetrics; import android.window.StartingWindowInfo; +import android.window.TaskSnapshot; import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.filters.SmallTest; @@ -61,6 +76,7 @@ import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.Mock; import org.mockito.MockitoAnnotations; +import org.mockito.MockitoSession; import java.util.function.IntSupplier; @@ -78,6 +94,7 @@ public class StartingSurfaceDrawerTests { private TransactionPool mTransactionPool; private final Handler mTestHandler = new Handler(Looper.getMainLooper()); + private ShellExecutor mTestExecutor; private final TestableContext mTestContext = new TestContext( InstrumentationRegistry.getInstrumentation().getTargetContext()); TestStartingSurfaceDrawer mStartingSurfaceDrawer; @@ -138,9 +155,9 @@ public class StartingSurfaceDrawerTests { doReturn(metrics).when(mMockWindowManager).getMaximumWindowMetrics(); doNothing().when(mMockWindowManager).addView(any(), any()); - - mStartingSurfaceDrawer = spy(new TestStartingSurfaceDrawer(mTestContext, - new HandlerExecutor(mTestHandler), mTransactionPool)); + mTestExecutor = new HandlerExecutor(mTestHandler); + mStartingSurfaceDrawer = spy( + new TestStartingSurfaceDrawer(mTestContext, mTestExecutor, mTransactionPool)); } @Test @@ -205,6 +222,48 @@ public class StartingSurfaceDrawerTests { assertEquals(0, windowColor3.mReuseCount); } + @Test + public void testRemoveTaskSnapshotWithImeSurfaceWhenOnImeDrawn() throws Exception { + final int taskId = 1; + final StartingWindowInfo windowInfo = + createWindowInfo(taskId, android.R.style.Theme); + TaskSnapshot snapshot = createTaskSnapshot(100, 100, new Point(100, 100), + new Rect(0, 0, 0, 50), true /* hasImeSurface */); + final IWindowSession session = WindowManagerGlobal.getWindowSession(); + spyOn(session); + doReturn(WindowManagerGlobal.ADD_OKAY).when(session).addToDisplay( + any() /* window */, any() /* attrs */, + anyInt() /* viewVisibility */, anyInt() /* displayId */, + any() /* requestedVisibility */, any() /* outInputChannel */, + any() /* outInsetsState */, any() /* outActiveControls */); + TaskSnapshotWindow mockSnapshotWindow = TaskSnapshotWindow.create(windowInfo, + mBinder, + snapshot, mTestExecutor, () -> { + }); + spyOn(mockSnapshotWindow); + try (AutoCloseable mockTaskSnapshotSession = new AutoCloseable() { + MockitoSession mockSession = mockitoSession() + .initMocks(this) + .mockStatic(TaskSnapshotWindow.class) + .startMocking(); + @Override + public void close() { + mockSession.finishMocking(); + } + }) { + when(TaskSnapshotWindow.create(eq(windowInfo), eq(mBinder), eq(snapshot), any(), + any())).thenReturn(mockSnapshotWindow); + // Simulate a task snapshot window created with IME snapshot shown. + mStartingSurfaceDrawer.makeTaskSnapshotWindow(windowInfo, mBinder, snapshot); + waitHandlerIdle(mTestHandler); + + // Verify the task snapshot with IME snapshot will be removed when received the real IME + // drawn callback. + mStartingSurfaceDrawer.onImeDrawnOnTask(1); + verify(mockSnapshotWindow).removeImmediately(); + } + } + private StartingWindowInfo createWindowInfo(int taskId, int themeResId) { StartingWindowInfo windowInfo = new StartingWindowInfo(); final ActivityInfo info = new ActivityInfo(); @@ -216,10 +275,27 @@ public class StartingSurfaceDrawerTests { taskInfo.taskId = taskId; windowInfo.targetActivityInfo = info; windowInfo.taskInfo = taskInfo; + windowInfo.topOpaqueWindowInsetsState = new InsetsState(); + windowInfo.mainWindowLayoutParams = new WindowManager.LayoutParams(); + windowInfo.topOpaqueWindowLayoutParams = new WindowManager.LayoutParams(); return windowInfo; } private static void waitHandlerIdle(Handler handler) { handler.runWithScissors(() -> { }, 0 /* timeout */); } + + private TaskSnapshot createTaskSnapshot(int width, int height, Point taskSize, + Rect contentInsets, boolean hasImeSurface) { + final HardwareBuffer buffer = HardwareBuffer.create(width, height, HardwareBuffer.RGBA_8888, + 1, HardwareBuffer.USAGE_CPU_READ_RARELY); + return new TaskSnapshot( + System.currentTimeMillis(), + new ComponentName("", ""), buffer, + ColorSpace.get(ColorSpace.Named.SRGB), ORIENTATION_PORTRAIT, + Surface.ROTATION_0, taskSize, contentInsets, false, + true /* isRealSnapshot */, WINDOWING_MODE_FULLSCREEN, + 0 /* systemUiVisibility */, false /* isTranslucent */, + hasImeSurface /* hasImeSurface */); + } } diff --git a/libs/hwui/Properties.cpp b/libs/hwui/Properties.cpp index c804418e8380..513ad9aab9ff 100644 --- a/libs/hwui/Properties.cpp +++ b/libs/hwui/Properties.cpp @@ -50,6 +50,7 @@ bool Properties::showDirtyRegions = false; bool Properties::skipEmptyFrames = true; bool Properties::useBufferAge = true; bool Properties::enablePartialUpdates = true; +bool Properties::enableRenderEffectCache = false; DebugLevel Properties::debugLevel = kDebugDisabled; OverdrawColorSet Properties::overdrawColorSet = OverdrawColorSet::Default; @@ -84,7 +85,7 @@ float Properties::defaultSdrWhitePoint = 200.f; bool Properties::useHintManager = true; int Properties::targetCpuTimePercentage = 70; -bool Properties::enableWebViewOverlays = false; +bool Properties::enableWebViewOverlays = true; StretchEffectBehavior Properties::stretchEffectBehavior = StretchEffectBehavior::ShaderHWUI; @@ -141,7 +142,7 @@ bool Properties::load() { targetCpuTimePercentage = base::GetIntProperty(PROPERTY_TARGET_CPU_TIME_PERCENTAGE, 70); if (targetCpuTimePercentage <= 0 || targetCpuTimePercentage > 100) targetCpuTimePercentage = 70; - enableWebViewOverlays = base::GetBoolProperty(PROPERTY_WEBVIEW_OVERLAYS_ENABLED, false); + enableWebViewOverlays = base::GetBoolProperty(PROPERTY_WEBVIEW_OVERLAYS_ENABLED, true); // call isDrawingEnabled to force loading of the property isDrawingEnabled(); diff --git a/libs/hwui/Properties.h b/libs/hwui/Properties.h index 7f9782bf8d20..2f8c67903a8b 100644 --- a/libs/hwui/Properties.h +++ b/libs/hwui/Properties.h @@ -232,6 +232,7 @@ public: static bool skipEmptyFrames; static bool useBufferAge; static bool enablePartialUpdates; + static bool enableRenderEffectCache; // TODO: Move somewhere else? static constexpr float textGamma = 1.45f; diff --git a/libs/hwui/WebViewFunctorManager.cpp b/libs/hwui/WebViewFunctorManager.cpp index df4101109a18..5aad821ad59f 100644 --- a/libs/hwui/WebViewFunctorManager.cpp +++ b/libs/hwui/WebViewFunctorManager.cpp @@ -100,6 +100,9 @@ WebViewFunctor::~WebViewFunctor() { destroyContext(); ATRACE_NAME("WebViewFunctor::onDestroy"); + if (mSurfaceControl) { + removeOverlays(); + } mCallbacks.onDestroyed(mFunctor, mData); } diff --git a/libs/hwui/pipeline/skia/RenderNodeDrawable.cpp b/libs/hwui/pipeline/skia/RenderNodeDrawable.cpp index 7556af918170..48145d2331ee 100644 --- a/libs/hwui/pipeline/skia/RenderNodeDrawable.cpp +++ b/libs/hwui/pipeline/skia/RenderNodeDrawable.cpp @@ -231,14 +231,33 @@ void RenderNodeDrawable::drawContent(SkCanvas* canvas) const { SkASSERT(properties.effectiveLayerType() == LayerType::RenderLayer); SkPaint paint; layerNeedsPaint(layerProperties, alphaMultiplier, &paint); - const auto snapshotResult = renderNode->updateSnapshotIfRequired( - canvas->recordingContext(), - layerProperties.getImageFilter(), - clipBounds.roundOut() - ); - sk_sp<SkImage> snapshotImage = snapshotResult->snapshot; - srcBounds = snapshotResult->outSubset; - offset = snapshotResult->outOffset; + sk_sp<SkImage> snapshotImage; + auto* imageFilter = layerProperties.getImageFilter(); + auto recordingContext = canvas->recordingContext(); + // On some GL vendor implementations, caching the result of + // getLayerSurface->makeImageSnapshot() causes a call to + // Fence::waitForever without a corresponding signal. This would + // lead to ANRs throughout the system. + // Instead only cache the SkImage created with the SkImageFilter + // for supported devices. Otherwise just create a new SkImage with + // the corresponding SkImageFilter each time. + // See b/193145089 and b/197263715 + if (!Properties::enableRenderEffectCache) { + snapshotImage = renderNode->getLayerSurface()->makeImageSnapshot(); + if (imageFilter) { + auto subset = SkIRect::MakeWH(srcBounds.width(), srcBounds.height()); + snapshotImage = snapshotImage->makeWithFilter(recordingContext, imageFilter, + subset, clipBounds.roundOut(), + &srcBounds, &offset); + } + } else { + const auto snapshotResult = renderNode->updateSnapshotIfRequired( + recordingContext, layerProperties.getImageFilter(), clipBounds.roundOut()); + snapshotImage = snapshotResult->snapshot; + srcBounds = snapshotResult->outSubset; + offset = snapshotResult->outOffset; + } + const auto dstBounds = SkIRect::MakeXYWH(offset.x(), offset.y(), srcBounds.width(), diff --git a/libs/hwui/renderthread/EglManager.cpp b/libs/hwui/renderthread/EglManager.cpp index a11678189bad..383c79b27918 100644 --- a/libs/hwui/renderthread/EglManager.cpp +++ b/libs/hwui/renderthread/EglManager.cpp @@ -146,6 +146,9 @@ void EglManager::initialize() { LOG_ALWAYS_FATAL("Unsupported wide color space."); } mHasWideColorGamutSupport = EglExtensions.glColorSpace && hasWideColorSpaceExtension; + + auto* vendor = reinterpret_cast<const char*>(glGetString(GL_VENDOR)); + Properties::enableRenderEffectCache = (strcmp(vendor, "Qualcomm") != 0); } EGLConfig EglManager::load8BitsConfig(EGLDisplay display, EglManager::SwapBehavior swapBehavior) { diff --git a/location/java/android/location/GpsNavigationMessage.java b/location/java/android/location/GpsNavigationMessage.java index dc1e99fd6a4f..2b978f759a13 100644 --- a/location/java/android/location/GpsNavigationMessage.java +++ b/location/java/android/location/GpsNavigationMessage.java @@ -262,12 +262,8 @@ public class GpsNavigationMessage implements Parcelable { parcel.readByteArray(data); navigationMessage.setData(data); - if (parcel.dataAvail() >= Integer.SIZE) { - int status = parcel.readInt(); - navigationMessage.setStatus((short) status); - } else { - navigationMessage.setStatus(STATUS_UNKNOWN); - } + int status = parcel.readInt(); + navigationMessage.setStatus((short) status); return navigationMessage; } diff --git a/packages/PrintRecommendationService/src/com/android/printservice/recommendation/plugin/mdnsFilter/MDNSFilterPlugin.java b/packages/PrintRecommendationService/src/com/android/printservice/recommendation/plugin/mdnsFilter/MDNSFilterPlugin.java index 5f107d662b75..34e7e3d1cd6b 100644 --- a/packages/PrintRecommendationService/src/com/android/printservice/recommendation/plugin/mdnsFilter/MDNSFilterPlugin.java +++ b/packages/PrintRecommendationService/src/com/android/printservice/recommendation/plugin/mdnsFilter/MDNSFilterPlugin.java @@ -84,8 +84,7 @@ public class MDNSFilterPlugin implements PrintServicePlugin { */ public MDNSFilterPlugin(@NonNull Context context, @NonNull String name, @NonNull CharSequence packageName, @NonNull List<String> mDNSNames) { - mName = context.getResources().getIdentifier(name, null, - "com.android.printservice.recommendation"); + mName = context.getResources().getIdentifier(name, null, context.getPackageName()); mPackageName = packageName; mMDNSFilteredDiscovery = new MDNSFilteredDiscovery(context, PRINTER_SERVICE_TYPES, new VendorNameFilter(new HashSet<>(mDNSNames))); diff --git a/packages/SettingsLib/BannerMessagePreference/res/values-v31/styles.xml b/packages/SettingsLib/BannerMessagePreference/res/values-v31/styles.xml index e74ac44ec8a4..fede44feb090 100644 --- a/packages/SettingsLib/BannerMessagePreference/res/values-v31/styles.xml +++ b/packages/SettingsLib/BannerMessagePreference/res/values-v31/styles.xml @@ -33,18 +33,18 @@ <style name="Banner.Title.SettingsLib" parent="@android:style/TextAppearance.Material.Subhead"> <item name="android:textSize">20sp</item> - <item name="android:fontFamily">@*android:string/config_headlineFontFamily</item> + <item name="android:fontFamily">@string/settingslib_config_headlineFontFamily</item> <item name="android:textColor">?android:attr/textColorPrimary</item> </style> <style name="Banner.Subtitle.SettingsLib" - parent="@*android:style/TextAppearance.DeviceDefault.Body1"> + parent="@android:style/TextAppearance.DeviceDefault"> <item name="android:textColor">?android:attr/textColorSecondary</item> <item name="android:textSize">14sp</item> </style> <style name="Banner.Summary.SettingsLib" - parent="@*android:style/TextAppearance.DeviceDefault.Body1"> + parent="@android:style/TextAppearance.DeviceDefault"> <item name="android:textColor">?android:attr/textColorSecondary</item> <item name="android:textSize">14sp</item> </style> @@ -58,4 +58,4 @@ parent="android:Widget.DeviceDefault.Button.Borderless.Colored"> <item name="android:textColor">?android:attr/colorAccent</item> </style> -</resources>
\ No newline at end of file +</resources> diff --git a/packages/SettingsLib/BannerMessagePreference/res/values/styles.xml b/packages/SettingsLib/BannerMessagePreference/res/values/styles.xml index df47c642e402..4c6ed58f4a58 100644 --- a/packages/SettingsLib/BannerMessagePreference/res/values/styles.xml +++ b/packages/SettingsLib/BannerMessagePreference/res/values/styles.xml @@ -17,14 +17,13 @@ <resources> <style name="Banner.Text.Title" - parent="@android:style/TextAppearance.Material.Subhead"> + parent="@android:style/TextAppearance.DeviceDefault.WindowTitle"> <item name="android:textSize">16sp</item> - <item name="android:fontFamily">@*android:string/config_headlineFontFamilyMedium</item> <item name="android:textColor">?android:attr/textColorPrimary</item> </style> <style name="Banner.Text.Summary" - parent="@*android:style/TextAppearance.DeviceDefault.Body1"> + parent="@android:style/TextAppearance.DeviceDefault"> <item name="android:textColor">?android:attr/textColorSecondary</item> <item name="android:textSize">14sp</item> </style> diff --git a/packages/SettingsLib/BarChartPreference/res/values/styles.xml b/packages/SettingsLib/BarChartPreference/res/values/styles.xml index 92514ad58b77..1c4420717188 100644 --- a/packages/SettingsLib/BarChartPreference/res/values/styles.xml +++ b/packages/SettingsLib/BarChartPreference/res/values/styles.xml @@ -87,9 +87,9 @@ </style> <style name="BarChart.Text" - parent="@android:style/TextAppearance.Material.Subhead"> - <item name="android:fontFamily">@*android:string/config_headlineFontFamilyMedium</item> + parent="@android:style/TextAppearance.DeviceDefault.WindowTitle"> <item name="android:textColor">?android:attr/textColorPrimary</item> + <item name="android:textSize">16sp</item> </style> <style name="BarChart.Text.HeaderTitle"> @@ -101,7 +101,7 @@ </style> <style name="BarChart.Text.Summary" - parent="@*android:style/TextAppearance.DeviceDefault.Body1"> + parent="@android:style/TextAppearance.DeviceDefault"> <item name="android:textColor">?android:attr/textColorSecondary</item> <item name="android:textSize">12sp</item> </style> diff --git a/packages/SettingsLib/CollapsingToolbarBaseActivity/Android.bp b/packages/SettingsLib/CollapsingToolbarBaseActivity/Android.bp index 2f911c4e6546..238e65ec9a3c 100644 --- a/packages/SettingsLib/CollapsingToolbarBaseActivity/Android.bp +++ b/packages/SettingsLib/CollapsingToolbarBaseActivity/Android.bp @@ -19,6 +19,7 @@ android_library { "com.google.android.material_material", "SettingsLibSettingsTransition", "SettingsLibUtils", + "SettingsLibSettingsTheme", ], sdk_version: "system_current", min_sdk_version: "29", diff --git a/packages/SettingsLib/CollapsingToolbarBaseActivity/res/layout-v31/collapsing_toolbar_base_layout.xml b/packages/SettingsLib/CollapsingToolbarBaseActivity/res/layout-v31/collapsing_toolbar_base_layout.xml index 59506564400b..907863e19972 100644 --- a/packages/SettingsLib/CollapsingToolbarBaseActivity/res/layout-v31/collapsing_toolbar_base_layout.xml +++ b/packages/SettingsLib/CollapsingToolbarBaseActivity/res/layout-v31/collapsing_toolbar_base_layout.xml @@ -16,7 +16,6 @@ --> <androidx.coordinatorlayout.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android" - xmlns:androidprv="http://schemas.android.com/apk/prv/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" android:id="@+id/content_parent" android:layout_width="match_parent" @@ -40,7 +39,7 @@ android:clipToPadding="false" app:forceApplySystemWindowInsetTop="true" app:extraMultilineHeightEnabled="true" - app:contentScrim="?androidprv:attr/colorSurfaceHeader" + app:contentScrim="@color/settingslib_colorSurfaceHeader" app:maxLines="3" app:layout_scrollFlags="scroll|exitUntilCollapsed|snap" app:scrimAnimationDuration="50" diff --git a/packages/SettingsLib/CollapsingToolbarBaseActivity/res/values-night/themes.xml b/packages/SettingsLib/CollapsingToolbarBaseActivity/res/values-night-v31/themes.xml index 878275a08752..c20beaf9bf93 100644 --- a/packages/SettingsLib/CollapsingToolbarBaseActivity/res/values-night/themes.xml +++ b/packages/SettingsLib/CollapsingToolbarBaseActivity/res/values-night-v31/themes.xml @@ -18,7 +18,7 @@ <style name="Theme.CollapsingToolbar.Settings" parent="@style/Theme.MaterialComponents.DayNight"> <item name="elevationOverlayEnabled">true</item> <item name="elevationOverlayColor">?attr/colorPrimary</item> - <item name="colorPrimary">@*android:color/primary_dark_device_default_settings</item> - <item name="colorAccent">@*android:color/accent_device_default_dark</item> + <item name="colorPrimary">@color/settingslib_primary_dark_device_default_settings</item> + <item name="colorAccent">@color/settingslib_accent_device_default_dark</item> </style> </resources>
\ No newline at end of file diff --git a/packages/SettingsLib/CollapsingToolbarBaseActivity/res/values/dimens.xml b/packages/SettingsLib/CollapsingToolbarBaseActivity/res/values-v31/dimens.xml index 15c1abbf97ba..15c1abbf97ba 100644 --- a/packages/SettingsLib/CollapsingToolbarBaseActivity/res/values/dimens.xml +++ b/packages/SettingsLib/CollapsingToolbarBaseActivity/res/values-v31/dimens.xml diff --git a/packages/SettingsLib/CollapsingToolbarBaseActivity/res/values/styles.xml b/packages/SettingsLib/CollapsingToolbarBaseActivity/res/values-v31/styles.xml index 63d397c69353..d0b6c4d54bb1 100644 --- a/packages/SettingsLib/CollapsingToolbarBaseActivity/res/values/styles.xml +++ b/packages/SettingsLib/CollapsingToolbarBaseActivity/res/values-v31/styles.xml @@ -16,11 +16,13 @@ --> <resources> <style name="CollapsingToolbarTitle.Collapsed" parent="@android:style/TextAppearance.DeviceDefault.Widget.ActionBar.Title"> - <item name="android:fontFamily">@*android:string/config_headlineFontFamily</item> + <item name="android:fontFamily">@string/settingslib_config_headlineFontFamily</item> <item name="android:textSize">20dp</item> + <item name="android:textColor">@color/settingslib_text_color_primary_device_default</item> </style> <style name="CollapsingToolbarTitle.Expanded" parent="CollapsingToolbarTitle.Collapsed"> <item name="android:textSize">36dp</item> + <item name="android:textColor">@color/settingslib_text_color_primary_device_default</item> </style> </resources>
\ No newline at end of file diff --git a/packages/SettingsLib/CollapsingToolbarBaseActivity/res/values/themes.xml b/packages/SettingsLib/CollapsingToolbarBaseActivity/res/values-v31/themes.xml index 2e7a6a9181fe..9ecc297c6d36 100644 --- a/packages/SettingsLib/CollapsingToolbarBaseActivity/res/values/themes.xml +++ b/packages/SettingsLib/CollapsingToolbarBaseActivity/res/values-v31/themes.xml @@ -18,7 +18,7 @@ <style name="Theme.CollapsingToolbar.Settings" parent="@style/Theme.MaterialComponents.DayNight"> <item name="elevationOverlayEnabled">true</item> <item name="elevationOverlayColor">?attr/colorPrimary</item> - <item name="colorPrimary">@*android:color/primary_device_default_settings_light</item> - <item name="colorAccent">@*android:color/accent_device_default_light</item> + <item name="colorPrimary">@color/settingslib_primary_device_default_settings_light</item> + <item name="colorAccent">@color/settingslib_accent_device_default_light</item> </style> </resources>
\ No newline at end of file diff --git a/packages/SettingsLib/FooterPreference/res/values/styles.xml b/packages/SettingsLib/FooterPreference/res/values/styles.xml index 08dd35991f69..5a3bada3e594 100644 --- a/packages/SettingsLib/FooterPreference/res/values/styles.xml +++ b/packages/SettingsLib/FooterPreference/res/values/styles.xml @@ -17,9 +17,8 @@ <resources> <style name="TextAppearance.Footer.Title.SettingsLib" - parent="@android:style/TextAppearance.DeviceDefault.Medium"> + parent="@android:style/TextAppearance.DeviceDefault.WindowTitle"> <item name="android:textSize">14sp</item> - <item name="android:fontFamily">@*android:string/config_headlineFontFamilyMedium</item> <item name="android:textColor">?android:attr/colorAccent</item> </style> </resources>
\ No newline at end of file diff --git a/packages/SettingsLib/LayoutPreference/res/values/styles.xml b/packages/SettingsLib/LayoutPreference/res/values/styles.xml index 4a99e845a5fc..2ffe6d91651b 100644 --- a/packages/SettingsLib/LayoutPreference/res/values/styles.xml +++ b/packages/SettingsLib/LayoutPreference/res/values/styles.xml @@ -24,14 +24,13 @@ </style> <style name="TextAppearance.EntityHeaderTitle" - parent="@android:style/TextAppearance.Material.Subhead"> - <item name="android:fontFamily">@*android:string/config_headlineFontFamilyMedium</item> + parent="@android:style/TextAppearance.DeviceDefault.WindowTitle"> <item name="android:textColor">?android:attr/textColorPrimary</item> <item name="android:textSize">20sp</item> </style> <style name="TextAppearance.EntityHeaderSummary" - parent="@*android:style/TextAppearance.DeviceDefault.Body1"> + parent="@android:style/TextAppearance.DeviceDefault"> <item name="android:textAlignment">viewStart</item> <item name="android:textColor">?android:attr/textColorSecondary</item> <item name="android:singleLine">true</item> diff --git a/packages/SettingsLib/MainSwitchPreference/res/drawable/settingslib_switch_bar_bg_disabled.xml b/packages/SettingsLib/MainSwitchPreference/res/drawable-v31/settingslib_switch_bar_bg_disabled.xml index 088e82bb4260..088e82bb4260 100644 --- a/packages/SettingsLib/MainSwitchPreference/res/drawable/settingslib_switch_bar_bg_disabled.xml +++ b/packages/SettingsLib/MainSwitchPreference/res/drawable-v31/settingslib_switch_bar_bg_disabled.xml diff --git a/packages/SettingsLib/MainSwitchPreference/res/drawable/settingslib_switch_bar_bg_off.xml b/packages/SettingsLib/MainSwitchPreference/res/drawable-v31/settingslib_switch_bar_bg_off.xml index 088e82bb4260..088e82bb4260 100644 --- a/packages/SettingsLib/MainSwitchPreference/res/drawable/settingslib_switch_bar_bg_off.xml +++ b/packages/SettingsLib/MainSwitchPreference/res/drawable-v31/settingslib_switch_bar_bg_off.xml diff --git a/packages/SettingsLib/MainSwitchPreference/res/drawable/settingslib_switch_bar_bg_on.xml b/packages/SettingsLib/MainSwitchPreference/res/drawable-v31/settingslib_switch_bar_bg_on.xml index 250188b892f4..250188b892f4 100644 --- a/packages/SettingsLib/MainSwitchPreference/res/drawable/settingslib_switch_bar_bg_on.xml +++ b/packages/SettingsLib/MainSwitchPreference/res/drawable-v31/settingslib_switch_bar_bg_on.xml diff --git a/packages/SettingsLib/MainSwitchPreference/res/layout-v31/settingslib_main_switch_bar.xml b/packages/SettingsLib/MainSwitchPreference/res/layout-v31/settingslib_main_switch_bar.xml index 6e5911cbf0a0..30748e6244cb 100644 --- a/packages/SettingsLib/MainSwitchPreference/res/layout-v31/settingslib_main_switch_bar.xml +++ b/packages/SettingsLib/MainSwitchPreference/res/layout-v31/settingslib_main_switch_bar.xml @@ -50,7 +50,7 @@ android:tint="?android:attr/colorAccent" android:layout_gravity="center_vertical" android:layout_marginEnd="@dimen/settingslib_restricted_icon_margin_end" - android:src="@*android:drawable/ic_info" + android:src="@android:drawable/ic_info" android:visibility="gone" /> <Switch diff --git a/packages/SettingsLib/MainSwitchPreference/res/layout/settingslib_main_switch_bar.xml b/packages/SettingsLib/MainSwitchPreference/res/layout/settingslib_main_switch_bar.xml index 306145a3e689..d0c2d0b5937d 100644 --- a/packages/SettingsLib/MainSwitchPreference/res/layout/settingslib_main_switch_bar.xml +++ b/packages/SettingsLib/MainSwitchPreference/res/layout/settingslib_main_switch_bar.xml @@ -28,7 +28,7 @@ android:layout_gravity="center_vertical" android:maxLines="2" android:ellipsize="end" - android:textAppearance="@*android:style/TextAppearance.DeviceDefault.Widget.ActionBar.Title" + android:textAppearance="@android:style/TextAppearance.DeviceDefault.Widget.ActionBar.Title" android:textSize="16sp" android:textColor="?android:attr/textColorPrimaryInverse" android:layout_marginStart="@dimen/settingslib_switchbar_subsettings_margin_start" @@ -42,7 +42,7 @@ android:theme="@android:style/Theme.Material" android:layout_gravity="center_vertical" android:layout_marginEnd="@dimen/settingslib_restricted_icon_margin_end" - android:src="@*android:drawable/ic_info" + android:src="@android:drawable/ic_info" android:visibility="gone"/> <Switch diff --git a/packages/SettingsLib/MainSwitchPreference/res/values-v31/dimens.xml b/packages/SettingsLib/MainSwitchPreference/res/values-v31/dimens.xml new file mode 100644 index 000000000000..2272a375fb83 --- /dev/null +++ b/packages/SettingsLib/MainSwitchPreference/res/values-v31/dimens.xml @@ -0,0 +1,37 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + Copyright (C) 2021 The Android Open Source Project + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + --> + +<resources> + + <!-- Size of layout margin --> + <dimen name="settingslib_switchbar_margin">16dp</dimen> + + <!-- Size of layout margin left --> + <dimen name="settingslib_switchbar_padding_left">24dp</dimen> + + <!-- Size of layout margin right --> + <dimen name="settingslib_switchbar_padding_right">16dp</dimen> + + <!-- Minimum width of switch --> + <dimen name="settingslib_min_switch_width">52dp</dimen> + + <!-- Minimum width of switch bar --> + <dimen name="settingslib_min_switch_bar_height">72dp</dimen> + + <!-- Radius of switch bar --> + <dimen name="settingslib_switch_bar_radius">28dp</dimen> +</resources> diff --git a/packages/SettingsLib/MainSwitchPreference/res/values-v31/styles.xml b/packages/SettingsLib/MainSwitchPreference/res/values-v31/styles.xml new file mode 100644 index 000000000000..a50fc7cc0028 --- /dev/null +++ b/packages/SettingsLib/MainSwitchPreference/res/values-v31/styles.xml @@ -0,0 +1,25 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + Copyright (C) 2021 The Android Open Source Project + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + --> + +<resources> + + <style name="MainSwitchText.Settingslib" parent="@android:style/TextAppearance.DeviceDefault.Widget.ActionBar.Title"> + <item name="android:textSize">20sp</item> + <item name="android:fontFamily">@string/settingslib_config_headlineFontFamily</item> + <item name="android:textColor">@android:color/black</item> + </style> +</resources> diff --git a/packages/SettingsLib/MainSwitchPreference/res/values/dimens.xml b/packages/SettingsLib/MainSwitchPreference/res/values/dimens.xml index 16b8af6a2dab..6362882e2332 100644 --- a/packages/SettingsLib/MainSwitchPreference/res/values/dimens.xml +++ b/packages/SettingsLib/MainSwitchPreference/res/values/dimens.xml @@ -1,6 +1,6 @@ <?xml version="1.0" encoding="utf-8"?> <!-- - Copyright (C) 2020 The Android Open Source Project + 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. @@ -17,30 +17,12 @@ <resources> - <!-- Size of layout margin --> - <dimen name="settingslib_switchbar_margin">16dp</dimen> - - <!-- Size of layout margin left --> - <dimen name="settingslib_switchbar_padding_left">24dp</dimen> - - <!-- Size of layout margin right --> - <dimen name="settingslib_switchbar_padding_right">16dp</dimen> - - <!-- Minimum width of switch --> - <dimen name="settingslib_min_switch_width">52dp</dimen> - - <!-- Minimum width of switch bar --> - <dimen name="settingslib_min_switch_bar_height">72dp</dimen> - <!-- Restricted icon size in switch bar --> - <dimen name="settingslib_restricted_icon_size">@*android:dimen/config_restrictedIconSize</dimen> + <dimen name="settingslib_restricted_icon_size">@android:dimen/config_restrictedIconSize</dimen> <!-- Restricted icon in switch bar --> <dimen name="settingslib_restricted_icon_margin_end">16dp</dimen> - <!-- Radius of switch bar --> - <dimen name="settingslib_switch_bar_radius">28dp</dimen> - <!-- Size of title margin --> <dimen name="settingslib_switch_title_margin">16dp</dimen> diff --git a/packages/SettingsLib/MainSwitchPreference/res/values/styles.xml b/packages/SettingsLib/MainSwitchPreference/res/values/styles.xml index 3924e301a2d3..870812ae6caf 100644 --- a/packages/SettingsLib/MainSwitchPreference/res/values/styles.xml +++ b/packages/SettingsLib/MainSwitchPreference/res/values/styles.xml @@ -1,6 +1,6 @@ <?xml version="1.0" encoding="utf-8"?> <!-- - Copyright (C) 2020 The Android Open Source Project + 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. @@ -17,13 +17,6 @@ <resources> - <style name="MainSwitchText.Settingslib" parent="@android:style/TextAppearance.DeviceDefault.Widget.ActionBar.Title"> - <item name="android:textSize">20sp</item> - <item name="android:fontFamily">@*android:string/config_headlineFontFamily</item> - <item name="android:textColor">@android:color/black</item> - </style> - - <style name="SwitchBar.Switch.Settingslib" parent="@android:style/Widget.Material.CompoundButton.Switch"> <item name="android:trackTint">@color/settingslib_switchbar_switch_track_tint</item> <item name="android:thumbTint">@color/settingslib_switchbar_switch_thumb_tint</item> diff --git a/packages/SettingsLib/SettingsTheme/res/color-v31/settingslib_surface_light.xml b/packages/SettingsLib/SettingsTheme/res/color-v31/settingslib_surface_light.xml new file mode 100644 index 000000000000..037b80abc6f9 --- /dev/null +++ b/packages/SettingsLib/SettingsTheme/res/color-v31/settingslib_surface_light.xml @@ -0,0 +1,18 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- Copyright (C) 2021 The Android Open Source Project + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +--> +<selector xmlns:android="http://schemas.android.com/apk/res/android"> + <item android:color="@android:color/system_neutral1_500" android:lStar="98" /> +</selector>
\ No newline at end of file diff --git a/packages/SettingsLib/SettingsTheme/res/values-night-v31/colors.xml b/packages/SettingsLib/SettingsTheme/res/values-night-v31/colors.xml index 8c7c7ed5b120..c20690342c19 100644 --- a/packages/SettingsLib/SettingsTheme/res/values-night-v31/colors.xml +++ b/packages/SettingsLib/SettingsTheme/res/values-night-v31/colors.xml @@ -36,4 +36,11 @@ <color name="settingslib_dialog_colorError">#f28b82</color> <!-- Red 300 --> <color name="settingslib_colorSurfaceVariant">@android:color/system_neutral1_700</color> + + <color name="settingslib_colorSurfaceHeader">@android:color/system_neutral1_700</color> + + <!-- copy from accent_primary_variant_dark_device_default--> + <color name="settingslib_accent_primary_variant">@android:color/system_accent1_300</color> + + <color name="settingslib_text_color_primary_device_default">@android:color/system_neutral1_50</color> </resources>
\ No newline at end of file diff --git a/packages/SettingsLib/SettingsTheme/res/values-v31/colors.xml b/packages/SettingsLib/SettingsTheme/res/values-v31/colors.xml index 77f1bcd17371..04010985fe74 100644 --- a/packages/SettingsLib/SettingsTheme/res/values-v31/colors.xml +++ b/packages/SettingsLib/SettingsTheme/res/values-v31/colors.xml @@ -37,9 +37,32 @@ <!-- Dialog accent color --> <color name="settingslib_dialog_accent">@android:color/system_accent1_600</color> <!-- Dialog background color --> - <color name="settingslib_dialog_background">@*android:color/surface_light</color> + <color name="settingslib_dialog_background">@color/settingslib_surface_light</color> <!-- Dialog error color. --> <color name="settingslib_dialog_colorError">#d93025</color> <!-- Red 600 --> <color name="settingslib_colorSurfaceVariant">@android:color/system_neutral2_100</color> + + <color name="settingslib_colorSurfaceHeader">@android:color/system_neutral1_100</color> + + <color name="settingslib_accent_device_default_dark">@android:color/system_accent1_100</color> + + <color name="settingslib_accent_device_default_light">@android:color/system_accent1_600</color> + + <color name="settingslib_primary_dark_device_default_settings">@android:color/system_neutral1_900</color> + + <color name="settingslib_primary_device_default_settings_light">@android:color/system_neutral1_50</color> + + <color name="settingslib_accent_primary_device_default">@android:color/system_accent1_100</color> + + <!-- copy from accent_primary_variant_light_device_default--> + <color name="settingslib_accent_primary_variant">@android:color/system_accent1_600</color> + + <color name="settingslib_accent_secondary_device_default">@android:color/system_accent2_100</color> + + <color name="settingslib_background_device_default_dark">@android:color/system_neutral1_900</color> + + <color name="settingslib_background_device_default_light">@android:color/system_neutral1_50</color> + + <color name="settingslib_text_color_primary_device_default">@android:color/system_neutral1_900</color> </resources> diff --git a/packages/SettingsLib/SettingsTheme/res/values-v31/dimens.xml b/packages/SettingsLib/SettingsTheme/res/values-v31/dimens.xml index ddcc83eee4bf..1c33f1a57ea5 100644 --- a/packages/SettingsLib/SettingsTheme/res/values-v31/dimens.xml +++ b/packages/SettingsLib/SettingsTheme/res/values-v31/dimens.xml @@ -19,4 +19,5 @@ <dimen name="app_preference_padding_start">20dp</dimen> <dimen name="app_icon_min_width">52dp</dimen> <dimen name="settingslib_preferred_minimum_touch_target">48dp</dimen> + <dimen name="settingslib_dialogCornerRadius">28dp</dimen> </resources> diff --git a/packages/SettingsLib/SettingsTheme/res/values-v31/strings.xml b/packages/SettingsLib/SettingsTheme/res/values-v31/strings.xml new file mode 100644 index 000000000000..6d072a936b15 --- /dev/null +++ b/packages/SettingsLib/SettingsTheme/res/values-v31/strings.xml @@ -0,0 +1,37 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- Copyright (C) 2021 The Android Open Source Project + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +--> + +<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> + <!-- Name of a font family to use for headlines in SettingsLib. --> + <string name="settingslib_config_headlineFontFamily" translatable="false"> + @*android:string/config_headlineFontFamily + </string> + + <!-- Name of a font family to use for headlines-medium in SettingsLib. --> + <string name="settingslib_config_headlineFontFamilyMedium" translatable="false"> + @*android:string/config_headlineFontFamilyMedium + </string> + + <!-- Name of a font family to use for body in SettingsLib. --> + <string name="settingslib_config_bodyFontFamily" translatable="false"> + @*android:string/config_bodyFontFamily + </string> + + <!-- Name of a font family to use for body-medium in SettingsLib. --> + <string name="settingslib_config_bodyFontFamilyMedium" translatable="false"> + @*android:string/config_bodyFontFamilyMedium + </string> +</resources> diff --git a/packages/SettingsLib/SettingsTheme/res/values-v31/styles.xml b/packages/SettingsLib/SettingsTheme/res/values-v31/styles.xml index 46f1e030af23..58006369988e 100644 --- a/packages/SettingsLib/SettingsTheme/res/values-v31/styles.xml +++ b/packages/SettingsLib/SettingsTheme/res/values-v31/styles.xml @@ -16,12 +16,16 @@ --> <resources> <style name="TextAppearance.PreferenceTitle.SettingsLib" - parent="@*android:style/TextAppearance.DeviceDefault.ListItem"> + parent="@android:style/TextAppearance.Material.Subhead"> + <item name="android:fontFamily">@string/settingslib_config_headlineFontFamily</item> <item name="android:textSize">20sp</item> </style> <style name="TextAppearance.CategoryTitle.SettingsLib" - parent="@*android:style/TextAppearance.DeviceDefault.Body2" /> + parent="@android:style/TextAppearance.DeviceDefault.Medium"> + <item name="android:textColor">?android:attr/textColorPrimary</item> + <item name="android:textSize">14sp</item> + </style> <style name="Switch.SettingsLib" parent="@android:style/Widget.Material.CompoundButton.Switch"> <item name="android:switchMinWidth">52dp</item> diff --git a/packages/SettingsLib/SettingsTheme/res/values-v31/themes.xml b/packages/SettingsLib/SettingsTheme/res/values-v31/themes.xml index 8034710b4341..6bf288b74d5a 100644 --- a/packages/SettingsLib/SettingsTheme/res/values-v31/themes.xml +++ b/packages/SettingsLib/SettingsTheme/res/values-v31/themes.xml @@ -50,6 +50,6 @@ <item name="android:clipToPadding">true</item> <item name="android:clipChildren">true</item> - <item name="dialogCornerRadius">@*android:dimen/config_dialogCornerRadius</item> + <item name="dialogCornerRadius">@dimen/settingslib_dialogCornerRadius</item> </style> </resources>
\ No newline at end of file diff --git a/packages/SettingsLib/SettingsTheme/res/values/dimens.xml b/packages/SettingsLib/SettingsTheme/res/values/dimens.xml index 25f9514c29b7..18af1f9c15d0 100644 --- a/packages/SettingsLib/SettingsTheme/res/values/dimens.xml +++ b/packages/SettingsLib/SettingsTheme/res/values/dimens.xml @@ -20,4 +20,5 @@ <dimen name="app_preference_padding_start">?android:attr/listPreferredItemPaddingStart</dimen> <dimen name="app_icon_min_width">56dp</dimen> <dimen name="two_target_min_width">72dp</dimen> + <dimen name="settingslib_dialogCornerRadius">8dp</dimen> </resources> diff --git a/packages/SettingsLib/SettingsTheme/res/values/themes.xml b/packages/SettingsLib/SettingsTheme/res/values/themes.xml index 6f2517746ddc..2d881d1a8a7b 100644 --- a/packages/SettingsLib/SettingsTheme/res/values/themes.xml +++ b/packages/SettingsLib/SettingsTheme/res/values/themes.xml @@ -35,7 +35,7 @@ <!-- TODO(b/189308264): fix the crash in Android R if set the attributes: <item name="colorAccent">@*android:color/accent_device_default_light</item> <item name="android:colorBackground">@color/settingslib_dialog_background</item> - <item name="dialogCornerRadius">@*android:dimen/config_dialogCornerRadius</item> + <item name="dialogCornerRadius">@dimen/settingslib_dialogCornerRadius</item> --> <item name="android:windowSoftInputMode">adjustResize</item> <item name="android:clipToPadding">true</item> diff --git a/packages/SettingsLib/TopIntroPreference/res/values/styles.xml b/packages/SettingsLib/TopIntroPreference/res/values/styles.xml index 65869b5580b5..b6ca41fb6b6d 100644 --- a/packages/SettingsLib/TopIntroPreference/res/values/styles.xml +++ b/packages/SettingsLib/TopIntroPreference/res/values/styles.xml @@ -16,8 +16,7 @@ --> <resources> <style name="TextAppearance.TopIntroText" - parent="@*android:style/TextAppearance.DeviceDefault"> - <item name="android:fontFamily">@*android:string/config_bodyFontFamily</item> + parent="@android:style/TextAppearance.DeviceDefault"> <item name="android:textSize">14sp</item> <item name="android:textColor">?android:attr/textColorSecondary</item> </style> diff --git a/packages/SystemUI/res/drawable/fingerprint_dialog_error_to_fp.xml b/packages/SystemUI/res/drawable/fingerprint_dialog_error_to_fp.xml index 33263a9131a0..0ae5dc745478 100644 --- a/packages/SystemUI/res/drawable/fingerprint_dialog_error_to_fp.xml +++ b/packages/SystemUI/res/drawable/fingerprint_dialog_error_to_fp.xml @@ -1,413 +1,242 @@ -<?xml version="1.0" encoding="utf-8"?><!-- - ~ Copyright (C) 2018 The Android Open Source Project - ~ - ~ Licensed under the Apache License, Version 2.0 (the "License"); - ~ you may not use this file except in compliance with the License. - ~ You may obtain a copy of the License at - ~ - ~ http://www.apache.org/licenses/LICENSE-2.0 - ~ - ~ Unless required by applicable law or agreed to in writing, software - ~ distributed under the License is distributed on an "AS IS" BASIS, - ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - ~ See the License for the specific language governing permissions and - ~ limitations under the License - --> +<?xml version="1.0" encoding="utf-8" ?> +<!-- Copyright (C) 2021 The Android Open Source Project + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +--> <animated-vector xmlns:android="http://schemas.android.com/apk/res/android" - xmlns:aapt="http://schemas.android.com/aapt"> + xmlns:aapt="http://schemas.android.com/aapt"> <aapt:attr name="android:drawable"> - <vector - android:width="60dp" - android:height="60dp" - android:viewportHeight="60" - android:viewportWidth="60"> + <vector android:height="60dp" android:width="60dp" android:viewportHeight="60" + android:viewportWidth="60"> <group android:name="_R_G"> - <group - android:name="_R_G_L_1_G_N_4_T_0" - android:translateX="30" - android:translateY="30"> - <group - android:name="_R_G_L_1_G" - android:pivotX="114" - android:pivotY="114" - android:scaleX="0.42200000000000004" - android:scaleY="0.42200000000000004" - android:translateX="-114" - android:translateY="-114"> - <path - android:name="_R_G_L_1_G_D_0_P_0" - android:pathData=" M79.63 67.24 C79.63,67.24 111.5,47.42 147.83,67.24 " - android:strokeAlpha="1" - android:strokeColor="@color/biometric_dialog_accent" - android:strokeLineCap="round" - android:strokeLineJoin="round" - android:strokeWidth="5.5" - android:trimPathEnd="0" - android:trimPathOffset="0" - android:trimPathStart="0" /> - <path - android:name="_R_G_L_1_G_D_1_P_0" - android:pathData=" M64.27 98.07 C64.27,98.07 80.13,73.02 113.98,73.02 C147.83,73.02 163.56,97.26 163.56,97.26 " - android:strokeAlpha="1" - android:strokeColor="@color/biometric_dialog_accent" - android:strokeLineCap="round" - android:strokeLineJoin="round" - android:strokeWidth="5.5" - android:trimPathEnd="0" - android:trimPathOffset="0" - android:trimPathStart="0" /> - <path - android:name="_R_G_L_1_G_D_2_P_0" - android:pathData=" M72.53 151.07 C72.53,151.07 62.46,122.89 76.16,105.55 C89.86,88.21 106.72,86.73 113.98,86.73 C121.08,86.73 153.51,90.62 158.7,125.87 C159.14,128.82 158.8,132.88 157.18,136.09 C154.88,140.63 150.62,143.63 145.85,143.97 C133.78,144.85 129.76,137.92 129.26,128.49 C128.88,121.19 122.49,115.35 113.15,115.35 C102.91,115.35 95.97,126.69 99.77,139.74 C103.57,152.78 111.33,163.85 130.32,169.13 " - android:strokeAlpha="1" - android:strokeColor="@color/biometric_dialog_accent" - android:strokeLineCap="round" - android:strokeLineJoin="round" - android:strokeWidth="5.5" - android:trimPathEnd="0" - android:trimPathOffset="0" - android:trimPathStart="0" /> - <path - android:name="_R_G_L_1_G_D_3_P_0" - android:pathData=" M100.6 167.84 C100.6,167.84 82.76,152.1 83.75,130.31 C84.75,108.53 102.58,100.7 113.73,100.7 C124.87,100.7 144.19,108.56 144.19,130.01 " - android:strokeAlpha="1" - android:strokeColor="@color/biometric_dialog_accent" - android:strokeLineCap="round" - android:strokeLineJoin="round" - android:strokeWidth="5.5" - android:trimPathEnd="0" - android:trimPathOffset="0" - android:trimPathStart="0" /> - <path - android:name="_R_G_L_1_G_D_4_P_0" - android:pathData=" M113.73 129.17 C113.73,129.17 113.15,161.33 149.15,156.58 " - android:strokeAlpha="1" - android:strokeColor="@color/biometric_dialog_accent" - android:strokeLineCap="round" - android:strokeLineJoin="round" - android:strokeWidth="5.5" - android:trimPathEnd="0" - android:trimPathOffset="0" - android:trimPathStart="0" /> + <group android:name="_R_G_L_1_G" android:translateX="-0.05000000000000071"> + <group android:name="_R_G_L_1_G_D_0_P_0_G_0_T_0" android:translateX="30" + android:translateY="38.75" android:scaleX="1" android:scaleY="1"> + <path android:name="_R_G_L_1_G_D_0_P_0" + android:fillColor="@color/biometric_dialog_error" + android:fillAlpha="1" android:fillType="nonZero" + android:pathData=" M-1.2 -1.25 C-1.2,-1.25 1.2,-1.25 1.2,-1.25 C1.2,-1.25 1.2,1.25 1.2,1.25 C1.2,1.25 -1.2,1.25 -1.2,1.25 C-1.2,1.25 -1.2,-1.25 -1.2,-1.25c "/> </group> - </group> - <group - android:name="_R_G_L_0_G_N_4_T_0" - android:translateX="30" - android:translateY="30"> - <group - android:name="_R_G_L_0_G" - android:translateX="-30.05" - android:translateY="-30"> - <group - android:name="_R_G_L_0_G_D_0_P_0_G_0_T_0" - android:scaleX="1" - android:scaleY="1" - android:translateX="30" - android:translateY="38.75"> - <path - android:name="_R_G_L_0_G_D_0_P_0" - android:fillAlpha="1" - android:fillColor="@color/biometric_dialog_error" - android:fillType="nonZero" - android:pathData=" M-1.2 -1.25 C-1.2,-1.25 1.2,-1.25 1.2,-1.25 C1.2,-1.25 1.2,1.25 1.2,1.25 C1.2,1.25 -1.2,1.25 -1.2,1.25 C-1.2,1.25 -1.2,-1.25 -1.2,-1.25c " /> - </group> - <group - android:name="_R_G_L_0_G_D_1_P_0_G_0_T_0" - android:pivotX="0.002" - android:pivotY="7.488" - android:scaleX="1" - android:scaleY="1" - android:translateX="30" - android:translateY="25"> - <path - android:name="_R_G_L_0_G_D_1_P_0" - android:fillAlpha="1" - android:fillColor="@color/biometric_dialog_error" - android:fillType="nonZero" - android:pathData=" M-1.2 -7.5 C-1.2,-7.5 1.2,-7.5 1.2,-7.5 C1.2,-7.5 1.2,7.5 1.2,7.5 C1.2,7.5 -1.2,7.5 -1.2,7.5 C-1.2,7.5 -1.2,-7.5 -1.2,-7.5c " /> - </group> - <path - android:name="_R_G_L_0_G_D_2_P_0" - android:pathData=" M30 6.2 C16.9,6.2 6.3,16.8 6.3,30 C6.3,43.2 16.9,53.8 30,53.8 C43.1,53.8 53.8,43.2 53.8,30 C53.8,16.8 43.1,6.2 30,6.2c " - android:strokeAlpha="1" - android:strokeColor="@color/biometric_dialog_error" - android:strokeLineCap="round" - android:strokeLineJoin="round" - android:strokeWidth="2.5" - android:trimPathEnd="1" - android:trimPathOffset="0" - android:trimPathStart="0" /> + <group android:name="_R_G_L_1_G_D_1_P_0_G_0_T_0" android:translateX="30" + android:translateY="25" android:pivotX="0.002" android:pivotY="7.488" + android:scaleX="1" android:scaleY="1"> + <path android:name="_R_G_L_1_G_D_1_P_0" + android:fillColor="@color/biometric_dialog_error" + android:fillAlpha="1" android:fillType="nonZero" + android:pathData=" M-1.2 -7.5 C-1.2,-7.5 1.2,-7.5 1.2,-7.5 C1.2,-7.5 1.2,7.5 1.2,7.5 C1.2,7.5 -1.2,7.5 -1.2,7.5 C-1.2,7.5 -1.2,-7.5 -1.2,-7.5c "/> </group> + <path android:name="_R_G_L_1_G_D_2_P_0" + android:strokeColor="@color/biometric_dialog_error" + android:strokeLineCap="round" android:strokeLineJoin="round" + android:strokeWidth="2.5" android:strokeAlpha="1" + android:trimPathStart="0" android:trimPathEnd="1" + android:trimPathOffset="0" + android:pathData=" M30 6.2 C16.9,6.2 6.3,16.8 6.3,30 C6.3,43.2 16.9,53.8 30,53.8 C43.1,53.8 53.8,43.2 53.8,30 C53.8,16.8 43.1,6.2 30,6.2c "/> + </group> + <group android:name="_R_G_L_0_G" android:translateX="-10.325" + android:translateY="-10.25"> + <path android:name="_R_G_L_0_G_D_0_P_0" + android:strokeColor="@color/biometric_dialog_accent" + android:strokeLineCap="round" android:strokeLineJoin="round" + android:strokeWidth="2" android:strokeAlpha="1" android:trimPathStart="0" + android:trimPathEnd="0" android:trimPathOffset="0" + android:pathData=" M31.41 48.43 C30.78,46.69 30.78,44.91 30.78,44.91 C30.78,40.09 34.88,36.16 40.32,36.16 C45.77,36.16 49.87,40.09 49.87,44.91 C49.87,44.91 49.87,45.17 49.87,45.17 C49.87,46.97 48.41,48.43 46.61,48.43 C45.28,48.43 44.09,47.63 43.6,46.39 C43.6,46.39 42.51,43.66 42.51,43.66 C42.02,42.42 40.82,41.61 39.49,41.61 C37.69,41.61 36.23,43.07 36.23,44.87 C36.23,47.12 37.26,49.26 39.02,50.67 C39.02,50.67 39.64,51.16 39.64,51.16 "/> + <path android:name="_R_G_L_0_G_D_1_P_0" + android:strokeColor="@color/biometric_dialog_accent" + android:strokeLineCap="round" android:strokeLineJoin="round" + android:strokeWidth="2" android:strokeAlpha="1" android:trimPathStart="0" + android:trimPathEnd="0" android:trimPathOffset="0" + android:pathData=" M32.14 27.3 C34.5,26 37.31,25.25 40.33,25.25 C43.34,25.25 46.15,26 48.51,27.3 "/> + <path android:name="_R_G_L_0_G_D_2_P_0" + android:strokeColor="@color/biometric_dialog_accent" + android:strokeLineCap="round" android:strokeLineJoin="round" + android:strokeWidth="2" android:strokeAlpha="1" android:trimPathStart="0" + android:trimPathEnd="0" android:trimPathOffset="0" + android:pathData=" M29.42 36.16 C31.35,32.94 35.51,30.71 40.33,30.71 C45.14,30.71 49.3,32.94 51.23,36.16 "/> + <path android:name="_R_G_L_0_G_D_3_P_0" + android:strokeColor="@color/biometric_dialog_accent" + android:strokeLineCap="round" android:strokeLineJoin="round" + android:strokeWidth="2" android:strokeAlpha="1" android:trimPathStart="0" + android:trimPathEnd="0" android:trimPathOffset="0" + android:pathData=" M47.14 52.52 C45.33,54.21 42.94,55.25 40.33,55.25 C37.71,55.25 35.32,54.21 33.51,52.52 "/> </group> </group> - <group android:name="time_group" /> + <group android:name="time_group"/> </vector> </aapt:attr> - <target android:name="_R_G_L_1_G_D_0_P_0"> + <target android:name="_R_G_L_1_G_D_0_P_0_G_0_T_0"> <aapt:attr name="android:animation"> <set android:ordering="together"> - <objectAnimator - android:duration="83" - android:propertyName="trimPathEnd" - android:startOffset="0" - android:valueFrom="0" - android:valueTo="0" - android:valueType="floatType"> + <objectAnimator android:propertyName="scaleX" android:duration="67" + android:startOffset="0" android:valueFrom="1" android:valueTo="1.1" + android:valueType="floatType"> <aapt:attr name="android:interpolator"> - <pathInterpolator android:pathData="M 0.0,0.0 c0.4,0 0.2,1 1.0,1.0" /> + <pathInterpolator android:pathData="M 0.0,0.0 c0.853,0 0.6,1 1.0,1.0"/> </aapt:attr> </objectAnimator> - <objectAnimator - android:duration="250" - android:propertyName="trimPathEnd" - android:startOffset="83" - android:valueFrom="0" - android:valueTo="1" - android:valueType="floatType"> + <objectAnimator android:propertyName="scaleY" android:duration="67" + android:startOffset="0" android:valueFrom="1" android:valueTo="1.1" + android:valueType="floatType"> <aapt:attr name="android:interpolator"> - <pathInterpolator android:pathData="M 0.0,0.0 c0.4,0 0.2,1 1.0,1.0" /> + <pathInterpolator android:pathData="M 0.0,0.0 c0.853,0 0.6,1 1.0,1.0"/> </aapt:attr> </objectAnimator> - </set> - </aapt:attr> - </target> - <target android:name="_R_G_L_1_G_D_1_P_0"> - <aapt:attr name="android:animation"> - <set android:ordering="together"> - <objectAnimator - android:duration="83" - android:propertyName="trimPathEnd" - android:startOffset="0" - android:valueFrom="0" - android:valueTo="0" - android:valueType="floatType"> + <objectAnimator android:propertyName="scaleX" android:duration="100" + android:startOffset="67" android:valueFrom="1.1" android:valueTo="0" + android:valueType="floatType"> <aapt:attr name="android:interpolator"> - <pathInterpolator android:pathData="M 0.0,0.0 c0.4,0 0.2,1 1.0,1.0" /> + <pathInterpolator android:pathData="M 0.0,0.0 c0.8,0 0.92,1.06 1.0,1.0"/> </aapt:attr> </objectAnimator> - <objectAnimator - android:duration="250" - android:propertyName="trimPathEnd" - android:startOffset="83" - android:valueFrom="0" - android:valueTo="1" - android:valueType="floatType"> + <objectAnimator android:propertyName="scaleY" android:duration="100" + android:startOffset="67" android:valueFrom="1.1" android:valueTo="0" + android:valueType="floatType"> <aapt:attr name="android:interpolator"> - <pathInterpolator android:pathData="M 0.0,0.0 c0.4,0 0.2,1 1.0,1.0" /> + <pathInterpolator android:pathData="M 0.0,0.0 c0.8,0 0.92,1.06 1.0,1.0"/> </aapt:attr> </objectAnimator> </set> </aapt:attr> </target> - <target android:name="_R_G_L_1_G_D_2_P_0"> + <target android:name="_R_G_L_1_G_D_1_P_0_G_0_T_0"> <aapt:attr name="android:animation"> <set android:ordering="together"> - <objectAnimator - android:duration="83" - android:propertyName="trimPathEnd" - android:startOffset="0" - android:valueFrom="0" - android:valueTo="0" - android:valueType="floatType"> + <objectAnimator android:propertyName="scaleX" android:duration="67" + android:startOffset="0" android:valueFrom="1" android:valueTo="1" + android:valueType="floatType"> <aapt:attr name="android:interpolator"> - <pathInterpolator android:pathData="M 0.0,0.0 c0.4,0 0.2,1 1.0,1.0" /> + <pathInterpolator android:pathData="M 0.0,0.0 c0.8,0 0.659,1 1.0,1.0"/> </aapt:attr> </objectAnimator> - <objectAnimator - android:duration="250" - android:propertyName="trimPathEnd" - android:startOffset="83" - android:valueFrom="0" - android:valueTo="1" - android:valueType="floatType"> + <objectAnimator android:propertyName="scaleY" android:duration="67" + android:startOffset="0" android:valueFrom="1" android:valueTo="1.1" + android:valueType="floatType"> <aapt:attr name="android:interpolator"> - <pathInterpolator android:pathData="M 0.0,0.0 c0.4,0 0.2,1 1.0,1.0" /> + <pathInterpolator android:pathData="M 0.0,0.0 c0.8,0 0.6,1 1.0,1.0"/> </aapt:attr> </objectAnimator> - </set> - </aapt:attr> - </target> - <target android:name="_R_G_L_1_G_D_3_P_0"> - <aapt:attr name="android:animation"> - <set android:ordering="together"> - <objectAnimator - android:duration="83" - android:propertyName="trimPathEnd" - android:startOffset="0" - android:valueFrom="0" - android:valueTo="0" - android:valueType="floatType"> + <objectAnimator android:propertyName="scaleX" android:duration="100" + android:startOffset="67" android:valueFrom="1" android:valueTo="1" + android:valueType="floatType"> <aapt:attr name="android:interpolator"> - <pathInterpolator android:pathData="M 0.0,0.0 c0.4,0 0.2,1 1.0,1.0" /> + <pathInterpolator android:pathData="M 0.0,0.0 c0.8,0 0.6,1 1.0,1.0"/> </aapt:attr> </objectAnimator> - <objectAnimator - android:duration="250" - android:propertyName="trimPathEnd" - android:startOffset="83" - android:valueFrom="0" - android:valueTo="1" - android:valueType="floatType"> + <objectAnimator android:propertyName="scaleY" android:duration="100" + android:startOffset="67" android:valueFrom="1.1" android:valueTo="0" + android:valueType="floatType"> <aapt:attr name="android:interpolator"> - <pathInterpolator android:pathData="M 0.0,0.0 c0.4,0 0.2,1 1.0,1.0" /> + <pathInterpolator android:pathData="M 0.0,0.0 c0.8,0 0.92,1.096 1.0,1.0"/> </aapt:attr> </objectAnimator> </set> </aapt:attr> </target> - <target android:name="_R_G_L_1_G_D_4_P_0"> + <target android:name="_R_G_L_1_G_D_2_P_0"> <aapt:attr name="android:animation"> <set android:ordering="together"> - <objectAnimator - android:duration="83" - android:propertyName="trimPathEnd" - android:startOffset="0" - android:valueFrom="0" - android:valueTo="0" - android:valueType="floatType"> + <objectAnimator android:propertyName="trimPathEnd" android:duration="67" + android:startOffset="0" android:valueFrom="1" android:valueTo="1" + android:valueType="floatType"> <aapt:attr name="android:interpolator"> - <pathInterpolator android:pathData="M 0.0,0.0 c0.4,0 0.2,1 1.0,1.0" /> + <pathInterpolator android:pathData="M 0.0,0.0 c0.4,0 0.2,1 1.0,1.0"/> </aapt:attr> </objectAnimator> - <objectAnimator - android:duration="250" - android:propertyName="trimPathEnd" - android:startOffset="83" - android:valueFrom="0" - android:valueTo="1" - android:valueType="floatType"> + <objectAnimator android:propertyName="trimPathEnd" android:duration="133" + android:startOffset="67" android:valueFrom="1" android:valueTo="0" + android:valueType="floatType"> <aapt:attr name="android:interpolator"> - <pathInterpolator android:pathData="M 0.0,0.0 c0.4,0 0.2,1 1.0,1.0" /> + <pathInterpolator android:pathData="M 0.0,0.0 c0.4,0 0.2,1 1.0,1.0"/> </aapt:attr> </objectAnimator> </set> </aapt:attr> </target> - <target android:name="_R_G_L_0_G_D_0_P_0_G_0_T_0"> + <target android:name="_R_G_L_0_G_D_0_P_0"> <aapt:attr name="android:animation"> <set android:ordering="together"> - <objectAnimator - android:duration="67" - android:propertyName="scaleX" - android:startOffset="0" - android:valueFrom="1" - android:valueTo="1.1" - android:valueType="floatType"> - <aapt:attr name="android:interpolator"> - <pathInterpolator android:pathData="M 0.0,0.0 c0.853,0 0.6,1 1.0,1.0" /> - </aapt:attr> - </objectAnimator> - <objectAnimator - android:duration="67" - android:propertyName="scaleY" - android:startOffset="0" - android:valueFrom="1" - android:valueTo="1.1" - android:valueType="floatType"> - <aapt:attr name="android:interpolator"> - <pathInterpolator android:pathData="M 0.0,0.0 c0.853,0 0.6,1 1.0,1.0" /> - </aapt:attr> - </objectAnimator> - <objectAnimator - android:duration="100" - android:propertyName="scaleX" - android:startOffset="67" - android:valueFrom="1.1" - android:valueTo="0" - android:valueType="floatType"> + <objectAnimator android:propertyName="trimPathEnd" android:duration="83" + android:startOffset="0" android:valueFrom="0" android:valueTo="0" + android:valueType="floatType"> <aapt:attr name="android:interpolator"> - <pathInterpolator android:pathData="M 0.0,0.0 c0.8,0 0.92,1.06 1.0,1.0" /> + <pathInterpolator android:pathData="M 0.0,0.0 c0.4,0 0.2,1 1.0,1.0"/> </aapt:attr> </objectAnimator> - <objectAnimator - android:duration="100" - android:propertyName="scaleY" - android:startOffset="67" - android:valueFrom="1.1" - android:valueTo="0" - android:valueType="floatType"> + <objectAnimator android:propertyName="trimPathEnd" android:duration="250" + android:startOffset="83" android:valueFrom="0" android:valueTo="1" + android:valueType="floatType"> <aapt:attr name="android:interpolator"> - <pathInterpolator android:pathData="M 0.0,0.0 c0.8,0 0.92,1.06 1.0,1.0" /> + <pathInterpolator android:pathData="M 0.0,0.0 c0.4,0 0.2,1 1.0,1.0"/> </aapt:attr> </objectAnimator> </set> </aapt:attr> </target> - <target android:name="_R_G_L_0_G_D_1_P_0_G_0_T_0"> + <target android:name="_R_G_L_0_G_D_1_P_0"> <aapt:attr name="android:animation"> <set android:ordering="together"> - <objectAnimator - android:duration="67" - android:propertyName="scaleX" - android:startOffset="0" - android:valueFrom="1" - android:valueTo="1" - android:valueType="floatType"> + <objectAnimator android:propertyName="trimPathEnd" android:duration="83" + android:startOffset="0" android:valueFrom="0" android:valueTo="0" + android:valueType="floatType"> <aapt:attr name="android:interpolator"> - <pathInterpolator android:pathData="M 0.0,0.0 c0.8,0 0.659,1 1.0,1.0" /> + <pathInterpolator android:pathData="M 0.0,0.0 c0.4,0 0.2,1 1.0,1.0"/> </aapt:attr> </objectAnimator> - <objectAnimator - android:duration="67" - android:propertyName="scaleY" - android:startOffset="0" - android:valueFrom="1" - android:valueTo="1.1" - android:valueType="floatType"> + <objectAnimator android:propertyName="trimPathEnd" android:duration="250" + android:startOffset="83" android:valueFrom="0" android:valueTo="1" + android:valueType="floatType"> <aapt:attr name="android:interpolator"> - <pathInterpolator android:pathData="M 0.0,0.0 c0.8,0 0.6,1 1.0,1.0" /> + <pathInterpolator android:pathData="M 0.0,0.0 c0.4,0 0.2,1 1.0,1.0"/> </aapt:attr> </objectAnimator> - <objectAnimator - android:duration="100" - android:propertyName="scaleX" - android:startOffset="67" - android:valueFrom="1" - android:valueTo="1" - android:valueType="floatType"> + </set> + </aapt:attr> + </target> + <target android:name="_R_G_L_0_G_D_2_P_0"> + <aapt:attr name="android:animation"> + <set android:ordering="together"> + <objectAnimator android:propertyName="trimPathEnd" android:duration="83" + android:startOffset="0" android:valueFrom="0" android:valueTo="0" + android:valueType="floatType"> <aapt:attr name="android:interpolator"> - <pathInterpolator android:pathData="M 0.0,0.0 c0.8,0 0.6,1 1.0,1.0" /> + <pathInterpolator android:pathData="M 0.0,0.0 c0.4,0 0.2,1 1.0,1.0"/> </aapt:attr> </objectAnimator> - <objectAnimator - android:duration="100" - android:propertyName="scaleY" - android:startOffset="67" - android:valueFrom="1.1" - android:valueTo="0" - android:valueType="floatType"> + <objectAnimator android:propertyName="trimPathEnd" android:duration="250" + android:startOffset="83" android:valueFrom="0" android:valueTo="1" + android:valueType="floatType"> <aapt:attr name="android:interpolator"> - <pathInterpolator android:pathData="M 0.0,0.0 c0.8,0 0.92,1.096 1.0,1.0" /> + <pathInterpolator android:pathData="M 0.0,0.0 c0.4,0 0.2,1 1.0,1.0"/> </aapt:attr> </objectAnimator> </set> </aapt:attr> </target> - <target android:name="_R_G_L_0_G_D_2_P_0"> + <target android:name="_R_G_L_0_G_D_3_P_0"> <aapt:attr name="android:animation"> <set android:ordering="together"> - <objectAnimator - android:duration="67" - android:propertyName="trimPathEnd" - android:startOffset="0" - android:valueFrom="1" - android:valueTo="1" - android:valueType="floatType"> + <objectAnimator android:propertyName="trimPathEnd" android:duration="83" + android:startOffset="0" android:valueFrom="0" android:valueTo="0" + android:valueType="floatType"> <aapt:attr name="android:interpolator"> - <pathInterpolator android:pathData="M 0.0,0.0 c0.4,0 0.2,1 1.0,1.0" /> + <pathInterpolator android:pathData="M 0.0,0.0 c0.4,0 0.2,1 1.0,1.0"/> </aapt:attr> </objectAnimator> - <objectAnimator - android:duration="133" - android:propertyName="trimPathEnd" - android:startOffset="67" - android:valueFrom="1" - android:valueTo="0" - android:valueType="floatType"> + <objectAnimator android:propertyName="trimPathEnd" android:duration="250" + android:startOffset="83" android:valueFrom="0" android:valueTo="1" + android:valueType="floatType"> <aapt:attr name="android:interpolator"> - <pathInterpolator android:pathData="M 0.0,0.0 c0.4,0 0.2,1 1.0,1.0" /> + <pathInterpolator android:pathData="M 0.0,0.0 c0.4,0 0.2,1 1.0,1.0"/> </aapt:attr> </objectAnimator> </set> @@ -416,14 +245,10 @@ <target android:name="time_group"> <aapt:attr name="android:animation"> <set android:ordering="together"> - <objectAnimator - android:duration="350" - android:propertyName="translateX" - android:startOffset="0" - android:valueFrom="0" - android:valueTo="1" - android:valueType="floatType" /> + <objectAnimator android:propertyName="translateX" android:duration="417" + android:startOffset="0" android:valueFrom="0" android:valueTo="1" + android:valueType="floatType"/> </set> </aapt:attr> </target> -</animated-vector>
\ No newline at end of file +</animated-vector> diff --git a/packages/SystemUI/res/drawable/fingerprint_dialog_fp_to_error.xml b/packages/SystemUI/res/drawable/fingerprint_dialog_fp_to_error.xml index b899828cd85c..fc2c7d00f3a7 100644 --- a/packages/SystemUI/res/drawable/fingerprint_dialog_fp_to_error.xml +++ b/packages/SystemUI/res/drawable/fingerprint_dialog_fp_to_error.xml @@ -1,391 +1,235 @@ -<?xml version="1.0" encoding="utf-8"?><!-- - ~ Copyright (C) 2018 The Android Open Source Project - ~ - ~ Licensed under the Apache License, Version 2.0 (the "License"); - ~ you may not use this file except in compliance with the License. - ~ You may obtain a copy of the License at - ~ - ~ http://www.apache.org/licenses/LICENSE-2.0 - ~ - ~ Unless required by applicable law or agreed to in writing, software - ~ distributed under the License is distributed on an "AS IS" BASIS, - ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - ~ See the License for the specific language governing permissions and - ~ limitations under the License - --> +<?xml version="1.0" encoding="utf-8" ?> +<!-- Copyright (C) 2021 The Android Open Source Project + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +--> <animated-vector xmlns:android="http://schemas.android.com/apk/res/android" - xmlns:aapt="http://schemas.android.com/aapt"> + xmlns:aapt="http://schemas.android.com/aapt"> <aapt:attr name="android:drawable"> - <vector - android:width="60dp" - android:height="60dp" - android:viewportHeight="60" - android:viewportWidth="60"> + <vector android:height="60dp" android:width="60dp" android:viewportHeight="60" + android:viewportWidth="60"> <group android:name="_R_G"> - <group - android:name="_R_G_L_1_G_N_4_T_0" - android:translateX="30" - android:translateY="30"> - <group - android:name="_R_G_L_1_G" - android:pivotX="114" - android:pivotY="114" - android:scaleX="0.42244" - android:scaleY="0.42244" - android:translateX="-114" - android:translateY="-114"> - <path - android:name="_R_G_L_1_G_D_0_P_0" - android:pathData=" M79.63 67.24 C79.63,67.24 111.5,47.42 147.83,67.24 " - android:strokeAlpha="1" - android:strokeColor="@color/biometric_dialog_accent" - android:strokeLineCap="round" - android:strokeLineJoin="round" - android:strokeWidth="5.5" - android:trimPathEnd="1" - android:trimPathOffset="0" - android:trimPathStart="0" /> - <path - android:name="_R_G_L_1_G_D_1_P_0" - android:pathData=" M64.27 98.07 C64.27,98.07 80.13,73.02 113.98,73.02 C147.83,73.02 163.56,97.26 163.56,97.26 " - android:strokeAlpha="1" - android:strokeColor="@color/biometric_dialog_accent" - android:strokeLineCap="round" - android:strokeLineJoin="round" - android:strokeWidth="5.5" - android:trimPathEnd="1" - android:trimPathOffset="0" - android:trimPathStart="0" /> - <path - android:name="_R_G_L_1_G_D_2_P_0" - android:pathData=" M72.53 151.07 C72.53,151.07 62.46,122.89 76.16,105.55 C89.86,88.21 106.72,86.73 113.98,86.73 C121.08,86.73 153.51,90.62 158.7,125.87 C159.14,128.82 158.8,132.88 157.18,136.09 C154.88,140.63 150.62,143.63 145.85,143.97 C133.78,144.85 129.76,137.92 129.26,128.49 C128.88,121.19 122.49,115.35 113.15,115.35 C102.91,115.35 95.97,126.69 99.77,139.74 C103.57,152.78 111.33,163.85 130.32,169.13 " - android:strokeAlpha="1" - android:strokeColor="@color/biometric_dialog_accent" - android:strokeLineCap="round" - android:strokeLineJoin="round" - android:strokeWidth="5.5" - android:trimPathEnd="1" - android:trimPathOffset="0" - android:trimPathStart="0" /> - <path - android:name="_R_G_L_1_G_D_3_P_0" - android:pathData=" M100.6 167.84 C100.6,167.84 82.76,152.1 83.75,130.31 C84.75,108.53 102.58,100.7 113.73,100.7 C124.87,100.7 144.19,108.56 144.19,130.01 " - android:strokeAlpha="1" - android:strokeColor="@color/biometric_dialog_accent" - android:strokeLineCap="round" - android:strokeLineJoin="round" - android:strokeWidth="5.5" - android:trimPathEnd="1" - android:trimPathOffset="0" - android:trimPathStart="0" /> - <path - android:name="_R_G_L_1_G_D_4_P_0" - android:pathData=" M113.73 129.17 C113.73,129.17 113.15,161.33 149.15,156.58 " - android:strokeAlpha="1" - android:strokeColor="@color/biometric_dialog_accent" - android:strokeLineCap="round" - android:strokeLineJoin="round" - android:strokeWidth="5.5" - android:trimPathEnd="1" - android:trimPathOffset="0" - android:trimPathStart="0" /> + <group android:name="_R_G_L_1_G" android:translateX="-0.05000000000000071"> + <group android:name="_R_G_L_1_G_D_0_P_0_G_0_T_0" android:translateX="30" + android:translateY="38.75" android:scaleX="0" android:scaleY="0"> + <path android:name="_R_G_L_1_G_D_0_P_0" + android:fillColor="@color/biometric_dialog_error" + android:fillAlpha="1" android:fillType="nonZero" + android:pathData=" M-1.2 -1.25 C-1.2,-1.25 1.2,-1.25 1.2,-1.25 C1.2,-1.25 1.2,1.25 1.2,1.25 C1.2,1.25 -1.2,1.25 -1.2,1.25 C-1.2,1.25 -1.2,-1.25 -1.2,-1.25c "/> </group> - </group> - <group - android:name="_R_G_L_0_G_N_4_T_0" - android:translateX="30" - android:translateY="30"> - <group - android:name="_R_G_L_0_G" - android:translateX="-30.05" - android:translateY="-30"> - <group - android:name="_R_G_L_0_G_D_0_P_0_G_0_T_0" - android:scaleX="0" - android:scaleY="0" - android:translateX="30" - android:translateY="38.75"> - <path - android:name="_R_G_L_0_G_D_0_P_0" - android:fillAlpha="1" - android:fillColor="@color/biometric_dialog_error" - android:fillType="nonZero" - android:pathData=" M-1.2 -1.25 C-1.2,-1.25 1.2,-1.25 1.2,-1.25 C1.2,-1.25 1.2,1.25 1.2,1.25 C1.2,1.25 -1.2,1.25 -1.2,1.25 C-1.2,1.25 -1.2,-1.25 -1.2,-1.25c " /> - </group> - <group - android:name="_R_G_L_0_G_D_1_P_0_G_0_T_0" - android:pivotX="0.002" - android:pivotY="7.488" - android:scaleX="1" - android:scaleY="0" - android:translateX="30" - android:translateY="25"> - <path - android:name="_R_G_L_0_G_D_1_P_0" - android:fillAlpha="1" - android:fillColor="@color/biometric_dialog_error" - android:fillType="nonZero" - android:pathData=" M-1.2 -7.5 C-1.2,-7.5 1.2,-7.5 1.2,-7.5 C1.2,-7.5 1.2,7.5 1.2,7.5 C1.2,7.5 -1.2,7.5 -1.2,7.5 C-1.2,7.5 -1.2,-7.5 -1.2,-7.5c " /> - </group> - <path - android:name="_R_G_L_0_G_D_2_P_0" - android:pathData=" M30 6.2 C16.9,6.2 6.3,16.8 6.3,30 C6.3,43.2 16.9,53.8 30,53.8 C43.1,53.8 53.8,43.2 53.8,30 C53.8,16.8 43.1,6.2 30,6.2c " - android:strokeAlpha="1" - android:strokeColor="@color/biometric_dialog_error" - android:strokeLineCap="round" - android:strokeLineJoin="round" - android:strokeWidth="2.5" - android:trimPathEnd="1" - android:trimPathOffset="0" - android:trimPathStart="1" /> + <group android:name="_R_G_L_1_G_D_1_P_0_G_0_T_0" android:translateX="30" + android:translateY="25" android:pivotX="0.002" android:pivotY="7.488" + android:scaleX="1" android:scaleY="0"> + <path android:name="_R_G_L_1_G_D_1_P_0" + android:fillColor="@color/biometric_dialog_error" + android:fillAlpha="1" android:fillType="nonZero" + android:pathData=" M-1.2 -7.5 C-1.2,-7.5 1.2,-7.5 1.2,-7.5 C1.2,-7.5 1.2,7.5 1.2,7.5 C1.2,7.5 -1.2,7.5 -1.2,7.5 C-1.2,7.5 -1.2,-7.5 -1.2,-7.5c "/> </group> + <path android:name="_R_G_L_1_G_D_2_P_0" + android:strokeColor="@color/biometric_dialog_error" + android:strokeLineCap="round" android:strokeLineJoin="round" + android:strokeWidth="2.5" android:strokeAlpha="1" + android:trimPathStart="1" android:trimPathEnd="1" + android:trimPathOffset="0" + android:pathData=" M30 6.2 C16.9,6.2 6.3,16.8 6.3,30 C6.3,43.2 16.9,53.8 30,53.8 C43.1,53.8 53.8,43.2 53.8,30 C53.8,16.8 43.1,6.2 30,6.2c "/> + </group> + <group android:name="_R_G_L_0_G" android:translateX="-10.325" + android:translateY="-10.25"> + <path android:name="_R_G_L_0_G_D_0_P_0" + android:strokeColor="@color/biometric_dialog_accent" + android:strokeLineCap="round" android:strokeLineJoin="round" + android:strokeWidth="2" android:strokeAlpha="1" android:trimPathStart="0" + android:trimPathEnd="1" android:trimPathOffset="0" + android:pathData=" M31.41 48.43 C30.78,46.69 30.78,44.91 30.78,44.91 C30.78,40.09 34.88,36.16 40.32,36.16 C45.77,36.16 49.87,40.09 49.87,44.91 C49.87,44.91 49.87,45.17 49.87,45.17 C49.87,46.97 48.41,48.43 46.61,48.43 C45.28,48.43 44.09,47.63 43.6,46.39 C43.6,46.39 42.51,43.66 42.51,43.66 C42.02,42.42 40.82,41.61 39.49,41.61 C37.69,41.61 36.23,43.07 36.23,44.87 C36.23,47.12 37.26,49.26 39.02,50.67 C39.02,50.67 39.64,51.16 39.64,51.16 "/> + <path android:name="_R_G_L_0_G_D_1_P_0" + android:strokeColor="@color/biometric_dialog_accent" + android:strokeLineCap="round" android:strokeLineJoin="round" + android:strokeWidth="2" android:strokeAlpha="1" android:trimPathStart="0" + android:trimPathEnd="1" android:trimPathOffset="0" + android:pathData=" M32.14 27.3 C34.5,26 37.31,25.25 40.33,25.25 C43.34,25.25 46.15,26 48.51,27.3 "/> + <path android:name="_R_G_L_0_G_D_2_P_0" + android:strokeColor="@color/biometric_dialog_accent" + android:strokeLineCap="round" android:strokeLineJoin="round" + android:strokeWidth="2" android:strokeAlpha="1" android:trimPathStart="0" + android:trimPathEnd="1" android:trimPathOffset="0" + android:pathData=" M29.42 36.16 C31.35,32.94 35.51,30.71 40.33,30.71 C45.14,30.71 49.3,32.94 51.23,36.16 "/> + <path android:name="_R_G_L_0_G_D_3_P_0" + android:strokeColor="@color/biometric_dialog_accent" + android:strokeLineCap="round" android:strokeLineJoin="round" + android:strokeWidth="2" android:strokeAlpha="1" android:trimPathStart="0" + android:trimPathEnd="1" android:trimPathOffset="0" + android:pathData=" M47.14 52.52 C45.33,54.21 42.94,55.25 40.33,55.25 C37.71,55.25 35.32,54.21 33.51,52.52 "/> </group> </group> - <group android:name="time_group" /> + <group android:name="time_group"/> </vector> </aapt:attr> - <target android:name="_R_G_L_1_G_D_0_P_0"> + <target android:name="_R_G_L_1_G_D_0_P_0_G_0_T_0"> <aapt:attr name="android:animation"> <set android:ordering="together"> - <objectAnimator - android:duration="167" - android:propertyName="trimPathStart" - android:startOffset="0" - android:valueFrom="0" - android:valueTo="1" - android:valueType="floatType"> + <objectAnimator android:propertyName="scaleX" android:duration="167" + android:startOffset="0" android:valueFrom="0" android:valueTo="0" + android:valueType="floatType"> <aapt:attr name="android:interpolator"> - <pathInterpolator android:pathData="M 0.0,0.0 c0.4,0 0.6,1 1.0,1.0" /> + <pathInterpolator android:pathData="M 0.0,0.0 c0.08,0.06 0.2,1 1.0,1.0"/> </aapt:attr> </objectAnimator> - </set> - </aapt:attr> - </target> - <target android:name="_R_G_L_1_G_D_1_P_0"> - <aapt:attr name="android:animation"> - <set android:ordering="together"> - <objectAnimator - android:duration="167" - android:propertyName="trimPathStart" - android:startOffset="0" - android:valueFrom="0" - android:valueTo="1" - android:valueType="floatType"> + <objectAnimator android:propertyName="scaleY" android:duration="167" + android:startOffset="0" android:valueFrom="0" android:valueTo="0" + android:valueType="floatType"> <aapt:attr name="android:interpolator"> - <pathInterpolator android:pathData="M 0.0,0.0 c0.4,0 0.6,1 1.0,1.0" /> + <pathInterpolator android:pathData="M 0.0,0.0 c0.08,0.06 0.2,1 1.0,1.0"/> </aapt:attr> </objectAnimator> - </set> - </aapt:attr> - </target> - <target android:name="_R_G_L_1_G_D_2_P_0"> - <aapt:attr name="android:animation"> - <set android:ordering="together"> - <objectAnimator - android:duration="167" - android:propertyName="trimPathStart" - android:startOffset="0" - android:valueFrom="0" - android:valueTo="1" - android:valueType="floatType"> + <objectAnimator android:propertyName="scaleX" android:duration="100" + android:startOffset="167" android:valueFrom="0" + android:valueTo="1.1" android:valueType="floatType"> <aapt:attr name="android:interpolator"> - <pathInterpolator android:pathData="M 0.0,0.0 c0.4,0 0.6,1 1.0,1.0" /> + <pathInterpolator android:pathData="M 0.0,0.0 c0.08,0.06 0.2,1 1.0,1.0"/> </aapt:attr> </objectAnimator> - </set> - </aapt:attr> - </target> - <target android:name="_R_G_L_1_G_D_3_P_0"> - <aapt:attr name="android:animation"> - <set android:ordering="together"> - <objectAnimator - android:duration="167" - android:propertyName="trimPathStart" - android:startOffset="0" - android:valueFrom="0" - android:valueTo="1" - android:valueType="floatType"> + <objectAnimator android:propertyName="scaleY" android:duration="100" + android:startOffset="167" android:valueFrom="0" + android:valueTo="1.1" android:valueType="floatType"> <aapt:attr name="android:interpolator"> - <pathInterpolator android:pathData="M 0.0,0.0 c0.4,0 0.6,1 1.0,1.0" /> + <pathInterpolator android:pathData="M 0.0,0.0 c0.08,0.06 0.2,1 1.0,1.0"/> </aapt:attr> </objectAnimator> - </set> - </aapt:attr> - </target> - <target android:name="_R_G_L_1_G_D_4_P_0"> - <aapt:attr name="android:animation"> - <set android:ordering="together"> - <objectAnimator - android:duration="167" - android:propertyName="trimPathStart" - android:startOffset="0" - android:valueFrom="0" - android:valueTo="1" - android:valueType="floatType"> + <objectAnimator android:propertyName="scaleX" android:duration="67" + android:startOffset="267" android:valueFrom="1.1" + android:valueTo="1" android:valueType="floatType"> + <aapt:attr name="android:interpolator"> + <pathInterpolator android:pathData="M 0.0,0.0 c0.4,0 0.147,1 1.0,1.0"/> + </aapt:attr> + </objectAnimator> + <objectAnimator android:propertyName="scaleY" android:duration="67" + android:startOffset="267" android:valueFrom="1.1" + android:valueTo="1" android:valueType="floatType"> <aapt:attr name="android:interpolator"> - <pathInterpolator android:pathData="M 0.0,0.0 c0.4,0 0.6,1 1.0,1.0" /> + <pathInterpolator android:pathData="M 0.0,0.0 c0.4,0 0.147,1 1.0,1.0"/> </aapt:attr> </objectAnimator> </set> </aapt:attr> </target> - <target android:name="_R_G_L_0_G_D_0_P_0_G_0_T_0"> + <target android:name="_R_G_L_1_G_D_1_P_0_G_0_T_0"> <aapt:attr name="android:animation"> <set android:ordering="together"> - <objectAnimator - android:duration="167" - android:propertyName="scaleX" - android:startOffset="0" - android:valueFrom="0" - android:valueTo="0" - android:valueType="floatType"> + <objectAnimator android:propertyName="scaleX" android:duration="167" + android:startOffset="0" android:valueFrom="1" android:valueTo="1" + android:valueType="floatType"> <aapt:attr name="android:interpolator"> - <pathInterpolator android:pathData="M 0.0,0.0 c0.08,0.06 0.2,1 1.0,1.0" /> + <pathInterpolator android:pathData="M 0.0,0.0 c0.4,0 0.2,1 1.0,1.0"/> </aapt:attr> </objectAnimator> - <objectAnimator - android:duration="167" - android:propertyName="scaleY" - android:startOffset="0" - android:valueFrom="0" - android:valueTo="0" - android:valueType="floatType"> + <objectAnimator android:propertyName="scaleY" android:duration="167" + android:startOffset="0" android:valueFrom="0" android:valueTo="0" + android:valueType="floatType"> <aapt:attr name="android:interpolator"> - <pathInterpolator android:pathData="M 0.0,0.0 c0.08,0.06 0.2,1 1.0,1.0" /> + <pathInterpolator android:pathData="M 0.0,0.0 c0.08,0.096 0.2,1 1.0,1.0"/> </aapt:attr> </objectAnimator> - <objectAnimator - android:duration="100" - android:propertyName="scaleX" - android:startOffset="167" - android:valueFrom="0" - android:valueTo="1.1" - android:valueType="floatType"> + <objectAnimator android:propertyName="scaleX" android:duration="100" + android:startOffset="167" android:valueFrom="1" android:valueTo="1" + android:valueType="floatType"> <aapt:attr name="android:interpolator"> - <pathInterpolator android:pathData="M 0.0,0.0 c0.08,0.06 0.2,1 1.0,1.0" /> + <pathInterpolator android:pathData="M 0.0,0.0 c0.4,0 0.2,1 1.0,1.0"/> </aapt:attr> </objectAnimator> - <objectAnimator - android:duration="100" - android:propertyName="scaleY" - android:startOffset="167" - android:valueFrom="0" - android:valueTo="1.1" - android:valueType="floatType"> + <objectAnimator android:propertyName="scaleY" android:duration="100" + android:startOffset="167" android:valueFrom="0" + android:valueTo="1.1" android:valueType="floatType"> <aapt:attr name="android:interpolator"> - <pathInterpolator android:pathData="M 0.0,0.0 c0.08,0.06 0.2,1 1.0,1.0" /> + <pathInterpolator android:pathData="M 0.0,0.0 c0.08,0.096 0.2,1 1.0,1.0"/> </aapt:attr> </objectAnimator> - <objectAnimator - android:duration="67" - android:propertyName="scaleX" - android:startOffset="267" - android:valueFrom="1.1" - android:valueTo="1" - android:valueType="floatType"> + <objectAnimator android:propertyName="scaleX" android:duration="67" + android:startOffset="267" android:valueFrom="1" android:valueTo="1" + android:valueType="floatType"> <aapt:attr name="android:interpolator"> - <pathInterpolator android:pathData="M 0.0,0.0 c0.4,0 0.147,1 1.0,1.0" /> + <pathInterpolator android:pathData="M 0.0,0.0 c0.341,0 0.2,1 1.0,1.0"/> </aapt:attr> </objectAnimator> - <objectAnimator - android:duration="67" - android:propertyName="scaleY" - android:startOffset="267" - android:valueFrom="1.1" - android:valueTo="1" - android:valueType="floatType"> + <objectAnimator android:propertyName="scaleY" android:duration="67" + android:startOffset="267" android:valueFrom="1.1" + android:valueTo="1" android:valueType="floatType"> <aapt:attr name="android:interpolator"> - <pathInterpolator android:pathData="M 0.0,0.0 c0.4,0 0.147,1 1.0,1.0" /> + <pathInterpolator android:pathData="M 0.0,0.0 c0.4,0 0.2,1 1.0,1.0"/> </aapt:attr> </objectAnimator> </set> </aapt:attr> </target> - <target android:name="_R_G_L_0_G_D_1_P_0_G_0_T_0"> + <target android:name="_R_G_L_1_G_D_2_P_0"> <aapt:attr name="android:animation"> <set android:ordering="together"> - <objectAnimator - android:duration="167" - android:propertyName="scaleX" - android:startOffset="0" - android:valueFrom="1" - android:valueTo="1" - android:valueType="floatType"> + <objectAnimator android:propertyName="trimPathStart" android:duration="267" + android:startOffset="0" android:valueFrom="1" android:valueTo="0" + android:valueType="floatType"> <aapt:attr name="android:interpolator"> - <pathInterpolator android:pathData="M 0.0,0.0 c0.4,0 0.2,1 1.0,1.0" /> + <pathInterpolator android:pathData="M 0.0,0.0 c0.4,0 0.2,1 1.0,1.0"/> </aapt:attr> </objectAnimator> - <objectAnimator - android:duration="167" - android:propertyName="scaleY" - android:startOffset="0" - android:valueFrom="0" - android:valueTo="0" - android:valueType="floatType"> - <aapt:attr name="android:interpolator"> - <pathInterpolator android:pathData="M 0.0,0.0 c0.08,0.096 0.2,1 1.0,1.0" /> - </aapt:attr> - </objectAnimator> - <objectAnimator - android:duration="100" - android:propertyName="scaleX" - android:startOffset="167" - android:valueFrom="1" - android:valueTo="1" - android:valueType="floatType"> - <aapt:attr name="android:interpolator"> - <pathInterpolator android:pathData="M 0.0,0.0 c0.4,0 0.2,1 1.0,1.0" /> - </aapt:attr> - </objectAnimator> - <objectAnimator - android:duration="100" - android:propertyName="scaleY" - android:startOffset="167" - android:valueFrom="0" - android:valueTo="1.1" - android:valueType="floatType"> + </set> + </aapt:attr> + </target> + <target android:name="_R_G_L_0_G_D_0_P_0"> + <aapt:attr name="android:animation"> + <set android:ordering="together"> + <objectAnimator android:propertyName="trimPathStart" android:duration="167" + android:startOffset="0" android:valueFrom="0" android:valueTo="1" + android:valueType="floatType"> <aapt:attr name="android:interpolator"> - <pathInterpolator android:pathData="M 0.0,0.0 c0.08,0.096 0.2,1 1.0,1.0" /> + <pathInterpolator android:pathData="M 0.0,0.0 c0.4,0 0.2,1 1.0,1.0"/> </aapt:attr> </objectAnimator> - <objectAnimator - android:duration="67" - android:propertyName="scaleX" - android:startOffset="267" - android:valueFrom="1" - android:valueTo="1" - android:valueType="floatType"> + </set> + </aapt:attr> + </target> + <target android:name="_R_G_L_0_G_D_1_P_0"> + <aapt:attr name="android:animation"> + <set android:ordering="together"> + <objectAnimator android:propertyName="trimPathStart" android:duration="167" + android:startOffset="0" android:valueFrom="0" android:valueTo="1" + android:valueType="floatType"> <aapt:attr name="android:interpolator"> - <pathInterpolator android:pathData="M 0.0,0.0 c0.341,0 0.2,1 1.0,1.0" /> + <pathInterpolator android:pathData="M 0.0,0.0 c0.4,0 0.2,1 1.0,1.0"/> </aapt:attr> </objectAnimator> - <objectAnimator - android:duration="67" - android:propertyName="scaleY" - android:startOffset="267" - android:valueFrom="1.1" - android:valueTo="1" - android:valueType="floatType"> + </set> + </aapt:attr> + </target> + <target android:name="_R_G_L_0_G_D_2_P_0"> + <aapt:attr name="android:animation"> + <set android:ordering="together"> + <objectAnimator android:propertyName="trimPathStart" android:duration="167" + android:startOffset="0" android:valueFrom="0" android:valueTo="1" + android:valueType="floatType"> <aapt:attr name="android:interpolator"> - <pathInterpolator android:pathData="M 0.0,0.0 c0.4,0 0.2,1 1.0,1.0" /> + <pathInterpolator android:pathData="M 0.0,0.0 c0.4,0 0.2,1 1.0,1.0"/> </aapt:attr> </objectAnimator> </set> </aapt:attr> </target> - <target android:name="_R_G_L_0_G_D_2_P_0"> + <target android:name="_R_G_L_0_G_D_3_P_0"> <aapt:attr name="android:animation"> <set android:ordering="together"> - <objectAnimator - android:duration="267" - android:propertyName="trimPathStart" - android:startOffset="0" - android:valueFrom="1" - android:valueTo="0" - android:valueType="floatType"> + <objectAnimator android:propertyName="trimPathStart" android:duration="167" + android:startOffset="0" android:valueFrom="0" android:valueTo="1" + android:valueType="floatType"> <aapt:attr name="android:interpolator"> - <pathInterpolator android:pathData="M 0.0,0.0 c0.4,0 0.2,1 1.0,1.0" /> + <pathInterpolator android:pathData="M 0.0,0.0 c0.4,0 0.2,1 1.0,1.0"/> </aapt:attr> </objectAnimator> </set> @@ -394,14 +238,10 @@ <target android:name="time_group"> <aapt:attr name="android:animation"> <set android:ordering="together"> - <objectAnimator - android:duration="350" - android:propertyName="translateX" - android:startOffset="0" - android:valueFrom="0" - android:valueTo="1" - android:valueType="floatType" /> + <objectAnimator android:propertyName="translateX" android:duration="350" + android:startOffset="0" android:valueFrom="0" android:valueTo="1" + android:valueType="floatType"/> </set> </aapt:attr> </target> -</animated-vector>
\ No newline at end of file +</animated-vector> diff --git a/packages/SystemUI/res/layout/status_bar_expanded.xml b/packages/SystemUI/res/layout/status_bar_expanded.xml index 6d5be301a82d..8b787323ccb6 100644 --- a/packages/SystemUI/res/layout/status_bar_expanded.xml +++ b/packages/SystemUI/res/layout/status_bar_expanded.xml @@ -54,8 +54,7 @@ <com.android.keyguard.LockIconView android:id="@+id/lock_icon_view" android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:layout_gravity="center"> + android:layout_height="wrap_content"> <!-- Background protection --> <ImageView android:id="@+id/lock_icon_bg" @@ -71,6 +70,19 @@ android:padding="@dimen/lock_icon_padding" android:layout_gravity="center" android:scaleType="centerCrop"/> + + <!-- Fingerprint --> + <!-- AOD dashed fingerprint icon with moving dashes --> + <com.airbnb.lottie.LottieAnimationView + android:id="@+id/lock_udfps_aod_fp" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:padding="@dimen/lock_icon_padding" + android:layout_gravity="center" + android:scaleType="centerCrop" + systemui:lottie_autoPlay="false" + systemui:lottie_loop="true" + systemui:lottie_rawRes="@raw/udfps_aod_fp"/> </com.android.keyguard.LockIconView> <com.android.systemui.statusbar.phone.NotificationsQuickSettingsContainer @@ -114,6 +126,7 @@ android:layout_width="@dimen/notification_panel_width" android:layout_height="match_parent" android:layout_marginBottom="@dimen/close_handle_underlap" + android:importantForAccessibility="no" systemui:layout_constraintStart_toStartOf="parent" systemui:layout_constraintEnd_toEndOf="parent" /> diff --git a/packages/SystemUI/res/layout/udfps_enroll_view.xml b/packages/SystemUI/res/layout/udfps_enroll_view.xml index f1ff6d669256..e41a632fa70a 100644 --- a/packages/SystemUI/res/layout/udfps_enroll_view.xml +++ b/packages/SystemUI/res/layout/udfps_enroll_view.xml @@ -20,10 +20,23 @@ android:layout_width="match_parent" android:layout_height="match_parent"> + <!-- The layout height/width are placeholders, which will be overwritten by + FingerprintSensorPropertiesInternal. --> + <View + android:id="@+id/udfps_enroll_accessibility_view" + android:layout_gravity="center" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:contentDescription="@string/accessibility_fingerprint_label"/> + + <ImageView + android:id="@+id/udfps_enroll_animation_fp_progress_view" + android:layout_width="match_parent" + android:layout_height="match_parent"/> + <!-- Fingerprint --> <ImageView android:id="@+id/udfps_enroll_animation_fp_view" android:layout_width="match_parent" - android:layout_height="match_parent" - android:contentDescription="@string/accessibility_fingerprint_label"/> + android:layout_height="match_parent"/> </com.android.systemui.biometrics.UdfpsEnrollView> diff --git a/packages/SystemUI/res/layout/udfps_view.xml b/packages/SystemUI/res/layout/udfps_view.xml index 687830d5c7b3..0fcbfa161ddf 100644 --- a/packages/SystemUI/res/layout/udfps_view.xml +++ b/packages/SystemUI/res/layout/udfps_view.xml @@ -20,7 +20,7 @@ android:id="@+id/udfps_view" android:layout_width="match_parent" android:layout_height="match_parent" - systemui:sensorTouchAreaCoefficient="0.75" + systemui:sensorTouchAreaCoefficient="1.0" android:contentDescription="@string/accessibility_fingerprint_label"> <ViewStub diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml index 6ad9ab9a26ec..78db2a8a3485 100644 --- a/packages/SystemUI/res/values/dimens.xml +++ b/packages/SystemUI/res/values/dimens.xml @@ -1462,6 +1462,10 @@ <!-- Maximum overshoot for the pulse expansion --> <dimen name="pulse_expansion_max_top_overshoot">32dp</dimen> + <!-- Alpha in duration in ms for the auth ripple to become fully vislble. If set to 0, + it is immediately visible. --> + <integer name="auth_ripple_alpha_in_duration">100</integer> + <dimen name="people_space_widget_radius">28dp</dimen> <dimen name="people_space_image_radius">20dp</dimen> <dimen name="people_space_messages_count_radius">12dp</dimen> diff --git a/packages/SystemUI/src/com/android/keyguard/LockIconView.java b/packages/SystemUI/src/com/android/keyguard/LockIconView.java index 622419a86bfc..5c34bebdaa4e 100644 --- a/packages/SystemUI/src/com/android/keyguard/LockIconView.java +++ b/packages/SystemUI/src/com/android/keyguard/LockIconView.java @@ -22,7 +22,6 @@ import android.graphics.PointF; import android.graphics.RectF; import android.graphics.drawable.Drawable; import android.util.AttributeSet; -import android.view.Gravity; import android.view.View; import android.widget.FrameLayout; import android.widget.ImageView; @@ -45,7 +44,7 @@ public class LockIconView extends FrameLayout implements Dumpable { private int mRadius; private ImageView mLockIcon; - private ImageView mUnlockBgView; + private ImageView mBgView; private int mLockIconColor; @@ -58,19 +57,19 @@ public class LockIconView extends FrameLayout implements Dumpable { public void onFinishInflate() { super.onFinishInflate(); mLockIcon = findViewById(R.id.lock_icon); - mUnlockBgView = findViewById(R.id.lock_icon_bg); + mBgView = findViewById(R.id.lock_icon_bg); } void updateColorAndBackgroundVisibility(boolean useBackground) { - if (useBackground) { + if (useBackground && mLockIcon.getDrawable() != null) { mLockIconColor = Utils.getColorAttrDefaultColor(getContext(), android.R.attr.textColorPrimary); - mUnlockBgView.setBackground(getContext().getDrawable(R.drawable.fingerprint_bg)); - mUnlockBgView.setVisibility(View.VISIBLE); + mBgView.setBackground(getContext().getDrawable(R.drawable.fingerprint_bg)); + mBgView.setVisibility(View.VISIBLE); } else { mLockIconColor = Utils.getColorAttrDefaultColor(getContext(), R.attr.wallpaperTextColorAccent); - mUnlockBgView.setVisibility(View.GONE); + mBgView.setVisibility(View.GONE); } mLockIcon.setImageTintList(ColorStateList.valueOf(mLockIconColor)); @@ -78,9 +77,14 @@ public class LockIconView extends FrameLayout implements Dumpable { void setImageDrawable(Drawable drawable) { mLockIcon.setImageDrawable(drawable); + if (drawable == null) { + mBgView.setVisibility(View.INVISIBLE); + } else { + mBgView.setVisibility(View.VISIBLE); + } } - void setCenterLocation(@NonNull PointF center, int radius) { + public void setCenterLocation(@NonNull PointF center, int radius) { mLockIconCenter = center; mRadius = radius; @@ -91,13 +95,11 @@ public class LockIconView extends FrameLayout implements Dumpable { mLockIconCenter.x + mRadius, mLockIconCenter.y + mRadius); - setX(mSensorRect.left); - setY(mSensorRect.top); - - final FrameLayout.LayoutParams lp = new FrameLayout.LayoutParams( - (int) (mSensorRect.right - mSensorRect.left), - (int) (mSensorRect.bottom - mSensorRect.top)); - lp.gravity = Gravity.CENTER; + final FrameLayout.LayoutParams lp = (FrameLayout.LayoutParams) getLayoutParams(); + lp.width = (int) (mSensorRect.right - mSensorRect.left); + lp.height = (int) (mSensorRect.bottom - mSensorRect.top); + lp.topMargin = (int) mSensorRect.top; + lp.setMarginStart((int) mSensorRect.left); setLayoutParams(lp); } @@ -114,5 +116,6 @@ public class LockIconView extends FrameLayout implements Dumpable { public void dump(@NonNull FileDescriptor fd, @NonNull PrintWriter pw, @NonNull String[] args) { pw.println("Center in px (x, y)= (" + mLockIconCenter.x + ", " + mLockIconCenter.y + ")"); pw.println("Radius in pixels: " + mRadius); + pw.println("topLeft= (" + getX() + ", " + getY() + ")"); } } diff --git a/packages/SystemUI/src/com/android/keyguard/LockIconViewController.java b/packages/SystemUI/src/com/android/keyguard/LockIconViewController.java index 509ac8a6d9fe..a41997ce3107 100644 --- a/packages/SystemUI/src/com/android/keyguard/LockIconViewController.java +++ b/packages/SystemUI/src/com/android/keyguard/LockIconViewController.java @@ -19,6 +19,8 @@ package com.android.keyguard; import static android.hardware.biometrics.BiometricSourceType.FINGERPRINT; import static com.android.systemui.classifier.Classifier.LOCK_ICON; +import static com.android.systemui.doze.util.BurnInHelperKt.getBurnInOffset; +import static com.android.systemui.doze.util.BurnInHelperKt.getBurnInProgressOffset; import android.content.Context; import android.content.res.Configuration; @@ -32,6 +34,7 @@ import android.media.AudioAttributes; import android.os.Process; import android.os.Vibrator; import android.util.DisplayMetrics; +import android.util.MathUtils; import android.view.GestureDetector; import android.view.GestureDetector.SimpleOnGestureListener; import android.view.MotionEvent; @@ -58,6 +61,8 @@ import com.android.systemui.statusbar.policy.KeyguardStateController; import com.android.systemui.util.ViewController; import com.android.systemui.util.concurrency.DelayableExecutor; +import com.airbnb.lottie.LottieAnimationView; + import java.io.FileDescriptor; import java.io.PrintWriter; import java.util.Objects; @@ -92,6 +97,8 @@ public class LockIconViewController extends ViewController<LockIconView> impleme @NonNull private final DelayableExecutor mExecutor; private boolean mUdfpsEnrolled; + @NonNull private LottieAnimationView mAodFp; + @NonNull private final AnimatedVectorDrawable mFpToUnlockIcon; @NonNull private final AnimatedVectorDrawable mLockToUnlockIcon; @NonNull private final Drawable mLockIcon; @@ -109,6 +116,7 @@ public class LockIconViewController extends ViewController<LockIconView> impleme private boolean mIsKeyguardShowing; private boolean mUserUnlockedWithBiometric; private Runnable mCancelDelayedUpdateVisibilityRunnable; + private Runnable mOnGestureDetectedRunnable; private boolean mUdfpsSupported; private float mHeightPixels; @@ -118,6 +126,12 @@ public class LockIconViewController extends ViewController<LockIconView> impleme private boolean mShowUnlockIcon; private boolean mShowLockIcon; + // for udfps when strong auth is required or unlocked on AOD + private boolean mShowAODFpIcon; + private final int mMaxBurnInOffsetX; + private final int mMaxBurnInOffsetY; + private float mInterpolatedDarkAmount; + private boolean mDownDetected; private boolean mDetectedLongPress; private final Rect mSensorTouchLocation = new Rect(); @@ -150,6 +164,12 @@ public class LockIconViewController extends ViewController<LockIconView> impleme mVibrator = vibrator; final Context context = view.getContext(); + mAodFp = mView.findViewById(R.id.lock_udfps_aod_fp); + mMaxBurnInOffsetX = context.getResources() + .getDimensionPixelSize(R.dimen.udfps_burn_in_offset_x); + mMaxBurnInOffsetY = context.getResources() + .getDimensionPixelSize(R.dimen.udfps_burn_in_offset_y); + mUnlockIcon = mView.getContext().getResources().getDrawable( R.drawable.ic_unlock, mView.getContext().getTheme()); @@ -173,15 +193,14 @@ public class LockIconViewController extends ViewController<LockIconView> impleme @Override protected void onViewAttached() { - // we check this here instead of onInit since the FingerprintManager + FaceManager may not - // have started up yet onInit - mUdfpsSupported = mAuthController.getUdfpsSensorLocation() != null; - + updateIsUdfpsEnrolled(); updateConfiguration(); updateKeyguardShowing(); mUserUnlockedWithBiometric = false; + mIsBouncerShowing = mKeyguardViewController.isBouncerShowing(); mIsDozing = mStatusBarStateController.isDozing(); + mInterpolatedDarkAmount = mStatusBarStateController.getDozeAmount(); mRunningFPS = mKeyguardUpdateMonitor.isFingerprintDetectionRunning(); mCanDismissLockScreen = mKeyguardStateController.canDismissLockScreen(); mStatusBarState = mStatusBarStateController.getState(); @@ -189,15 +208,18 @@ public class LockIconViewController extends ViewController<LockIconView> impleme updateColors(); mConfigurationController.addCallback(mConfigurationListener); + mAuthController.addCallback(mAuthControllerCallback); mKeyguardUpdateMonitor.registerCallback(mKeyguardUpdateMonitorCallback); mStatusBarStateController.addCallback(mStatusBarStateListener); mKeyguardStateController.addCallback(mKeyguardStateCallback); mDownDetected = false; + updateBurnInOffsets(); updateVisibility(); } @Override protected void onViewDetached() { + mAuthController.removeCallback(mAuthControllerCallback); mConfigurationController.removeCallback(mConfigurationListener); mKeyguardUpdateMonitor.removeCallback(mKeyguardUpdateMonitorCallback); mStatusBarStateController.removeCallback(mStatusBarStateListener); @@ -227,7 +249,7 @@ public class LockIconViewController extends ViewController<LockIconView> impleme mCancelDelayedUpdateVisibilityRunnable = null; } - if (!mIsKeyguardShowing) { + if (!mIsKeyguardShowing && !mIsDozing) { mView.setVisibility(View.INVISIBLE); return; } @@ -238,6 +260,7 @@ public class LockIconViewController extends ViewController<LockIconView> impleme mShowLockIcon = !mCanDismissLockScreen && !mUserUnlockedWithBiometric && isLockScreen() && (!mUdfpsEnrolled || !mRunningFPS); mShowUnlockIcon = mCanDismissLockScreen && isLockScreen(); + mShowAODFpIcon = mIsDozing && mUdfpsEnrolled && !mRunningFPS; final CharSequence prevContentDescription = mView.getContentDescription(); if (mShowLockIcon) { @@ -260,10 +283,22 @@ public class LockIconViewController extends ViewController<LockIconView> impleme } mView.setVisibility(View.VISIBLE); mView.setContentDescription(mUnlockedLabel); + } else if (mShowAODFpIcon) { + mView.setImageDrawable(null); + mView.setContentDescription(null); + mAodFp.setVisibility(View.VISIBLE); + mAodFp.setContentDescription(mCanDismissLockScreen ? mUnlockedLabel : mLockedLabel); + mView.setVisibility(View.VISIBLE); } else { mView.setVisibility(View.INVISIBLE); mView.setContentDescription(null); } + + if (!mShowAODFpIcon) { + mAodFp.setVisibility(View.INVISIBLE); + mAodFp.setContentDescription(null); + } + if (!Objects.equals(prevContentDescription, mView.getContentDescription()) && mView.getContentDescription() != null) { mView.announceForAccessibility(mView.getContentDescription()); @@ -340,10 +375,12 @@ public class LockIconViewController extends ViewController<LockIconView> impleme @Override public void dump(@NonNull FileDescriptor fd, @NonNull PrintWriter pw, @NonNull String[] args) { + pw.println("mUdfpsSupported: " + mUdfpsSupported); pw.println("mUdfpsEnrolled: " + mUdfpsEnrolled); pw.println("mIsKeyguardShowing: " + mIsKeyguardShowing); pw.println(" mShowUnlockIcon: " + mShowUnlockIcon); pw.println(" mShowLockIcon: " + mShowLockIcon); + pw.println(" mShowAODFpIcon: " + mShowAODFpIcon); pw.println(" mIsDozing: " + mIsDozing); pw.println(" mIsBouncerShowing: " + mIsBouncerShowing); pw.println(" mUserUnlockedWithBiometric: " + mUserUnlockedWithBiometric); @@ -351,17 +388,57 @@ public class LockIconViewController extends ViewController<LockIconView> impleme pw.println(" mCanDismissLockScreen: " + mCanDismissLockScreen); pw.println(" mStatusBarState: " + StatusBarState.toShortString(mStatusBarState)); pw.println(" mQsExpanded: " + mQsExpanded); + pw.println(" mInterpolatedDarkAmount: " + mInterpolatedDarkAmount); if (mView != null) { mView.dump(fd, pw, args); } } + /** Every minute, update the aod icon's burn in offset */ + public void dozeTimeTick() { + updateBurnInOffsets(); + } + + private void updateBurnInOffsets() { + float offsetX = MathUtils.lerp(0f, + getBurnInOffset(mMaxBurnInOffsetX * 2, true /* xAxis */) + - mMaxBurnInOffsetX, mInterpolatedDarkAmount); + float offsetY = MathUtils.lerp(0f, + getBurnInOffset(mMaxBurnInOffsetY * 2, false /* xAxis */) + - mMaxBurnInOffsetY, mInterpolatedDarkAmount); + float progress = MathUtils.lerp(0f, getBurnInProgressOffset(), mInterpolatedDarkAmount); + + mAodFp.setTranslationX(offsetX); + mAodFp.setTranslationY(offsetY); + mAodFp.setProgress(progress); + mAodFp.setAlpha(255 * mInterpolatedDarkAmount); + } + + private void updateIsUdfpsEnrolled() { + boolean wasUdfpsSupported = mUdfpsSupported; + boolean wasUdfpsEnrolled = mUdfpsEnrolled; + + mUdfpsSupported = mAuthController.getUdfpsSensorLocation() != null; + mUdfpsEnrolled = mKeyguardUpdateMonitor.isUdfpsEnrolled(); + if (wasUdfpsSupported != mUdfpsSupported || wasUdfpsEnrolled != mUdfpsEnrolled) { + updateVisibility(); + } + } + private StatusBarStateController.StateListener mStatusBarStateListener = new StatusBarStateController.StateListener() { @Override + public void onDozeAmountChanged(float linear, float eased) { + mInterpolatedDarkAmount = eased; + updateBurnInOffsets(); + } + + @Override public void onDozingChanged(boolean isDozing) { mIsDozing = isDozing; + updateBurnInOffsets(); + updateIsUdfpsEnrolled(); updateVisibility(); } @@ -435,7 +512,7 @@ public class LockIconViewController extends ViewController<LockIconView> impleme mKeyguardUpdateMonitor.getUserUnlockedWithBiometric( KeyguardUpdateMonitor.getCurrentUser()); } - mUdfpsEnrolled = mKeyguardUpdateMonitor.isUdfpsEnrolled(); + updateIsUdfpsEnrolled(); updateVisibility(); } @@ -481,8 +558,7 @@ public class LockIconViewController extends ViewController<LockIconView> impleme // intercept all following touches until we see MotionEvent.ACTION_CANCEL UP or // MotionEvent.ACTION_UP (see #onTouchEvent) - mDownDetected = true; - if (mVibrator != null) { + if (mVibrator != null && !mDownDetected) { mVibrator.vibrate( Process.myUid(), getContext().getOpPackageName(), @@ -490,6 +566,8 @@ public class LockIconViewController extends ViewController<LockIconView> impleme "lockIcon-onDown", VIBRATION_SONIFICATION_ATTRIBUTES); } + + mDownDetected = true; return true; } @@ -497,8 +575,10 @@ public class LockIconViewController extends ViewController<LockIconView> impleme if (!wasClickableOnDownEvent()) { return; } + mDetectedLongPress = true; - if (mVibrator != null) { + if (onAffordanceClick() && mVibrator != null) { + // only vibrate if the click went through and wasn't intercepted by falsing mVibrator.vibrate( Process.myUid(), getContext().getOpPackageName(), @@ -506,8 +586,6 @@ public class LockIconViewController extends ViewController<LockIconView> impleme "lockIcon-onLongPress", VIBRATION_SONIFICATION_ATTRIBUTES); } - mDetectedLongPress = true; - onAffordanceClick(); } public boolean onSingleTapUp(MotionEvent e) { @@ -531,15 +609,24 @@ public class LockIconViewController extends ViewController<LockIconView> impleme return mDownDetected; } - private void onAffordanceClick() { + /** + * Whether we tried to launch the affordance. + * + * If falsing intercepts the click, returns false. + */ + private boolean onAffordanceClick() { if (mFalsingManager.isFalseTouch(LOCK_ICON)) { - return; + return false; } // pre-emptively set to true to hide view mIsBouncerShowing = true; updateVisibility(); + if (mOnGestureDetectedRunnable != null) { + mOnGestureDetectedRunnable.run(); + } mKeyguardViewController.showBouncer(/* scrim */ true); + return true; } }); @@ -548,16 +635,18 @@ public class LockIconViewController extends ViewController<LockIconView> impleme * in a 'clickable' state * @return whether to intercept the touch event */ - public boolean onTouchEvent(MotionEvent event) { + public boolean onTouchEvent(MotionEvent event, Runnable onGestureDetectedRunnable) { if (mSensorTouchLocation.contains((int) event.getX(), (int) event.getY()) - && mView.getVisibility() == View.VISIBLE) { + && (mView.getVisibility() == View.VISIBLE + || mAodFp.getVisibility() == View.VISIBLE)) { + mOnGestureDetectedRunnable = onGestureDetectedRunnable; mGestureDetector.onTouchEvent(event); } // we continue to intercept all following touches until we see MotionEvent.ACTION_CANCEL UP // or MotionEvent.ACTION_UP. this is to avoid passing the touch to NPV // after the lock icon disappears on device entry - if (mDownDetected && mDetectedLongPress) { + if (mDownDetected) { if (event.getAction() == MotionEvent.ACTION_CANCEL || event.getAction() == MotionEvent.ACTION_UP) { mDownDetected = false; @@ -577,4 +666,12 @@ public class LockIconViewController extends ViewController<LockIconView> impleme public void setAlpha(float alpha) { mView.setAlpha(alpha); } + + private final AuthController.Callback mAuthControllerCallback = new AuthController.Callback() { + @Override + public void onAllAuthenticatorsRegistered() { + updateIsUdfpsEnrolled(); + updateConfiguration(); + } + }; } diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java b/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java index 0ce1846e7745..a4123c769d1e 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java +++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java @@ -786,7 +786,7 @@ public class AuthController extends SystemUI implements CommandQueue.Callbacks, .build(sensorIds, credentialAllowed, mFpProps, mFaceProps); } - interface Callback { + public interface Callback { /** * Called when authenticators are registered. If authenticators are already * registered before this call, this callback will never be triggered. diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthRippleController.kt b/packages/SystemUI/src/com/android/systemui/biometrics/AuthRippleController.kt index 1df8ad5e51fb..2630f119de00 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthRippleController.kt +++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthRippleController.kt @@ -16,14 +16,19 @@ package com.android.systemui.biometrics +import android.animation.ValueAnimator import android.content.Context import android.content.res.Configuration import android.graphics.PointF import android.hardware.biometrics.BiometricSourceType +import android.util.Log import androidx.annotation.VisibleForTesting import com.android.keyguard.KeyguardUpdateMonitor import com.android.keyguard.KeyguardUpdateMonitorCallback import com.android.settingslib.Utils +import com.android.systemui.R +import com.android.systemui.animation.Interpolators +import com.android.systemui.keyguard.WakefulnessLifecycle import com.android.systemui.statusbar.CircleReveal import com.android.systemui.statusbar.LightRevealEffect import com.android.systemui.statusbar.NotificationShadeWindowController @@ -34,9 +39,14 @@ import com.android.systemui.statusbar.phone.KeyguardBypassController import com.android.systemui.statusbar.phone.StatusBar import com.android.systemui.statusbar.phone.dagger.StatusBarComponent.StatusBarScope import com.android.systemui.statusbar.policy.ConfigurationController +import com.android.systemui.statusbar.policy.KeyguardStateController import com.android.systemui.util.ViewController import java.io.PrintWriter import javax.inject.Inject +import javax.inject.Provider +import com.android.systemui.plugins.statusbar.StatusBarStateController + +private const val WAKE_AND_UNLOCK_FADE_DURATION = 180L /*** * Controls the ripple effect that shows when authentication is successful. @@ -49,37 +59,65 @@ class AuthRippleController @Inject constructor( private val authController: AuthController, private val configurationController: ConfigurationController, private val keyguardUpdateMonitor: KeyguardUpdateMonitor, + private val keyguardStateController: KeyguardStateController, + private val wakefulnessLifecycle: WakefulnessLifecycle, private val commandRegistry: CommandRegistry, private val notificationShadeWindowController: NotificationShadeWindowController, private val bypassController: KeyguardBypassController, private val biometricUnlockController: BiometricUnlockController, + private val udfpsControllerProvider: Provider<UdfpsController>, + private val statusBarStateController: StatusBarStateController, rippleView: AuthRippleView? -) : ViewController<AuthRippleView>(rippleView) { +) : ViewController<AuthRippleView>(rippleView), KeyguardStateController.Callback, + WakefulnessLifecycle.Observer { + + @VisibleForTesting + internal var startLightRevealScrimOnKeyguardFadingAway = false var fingerprintSensorLocation: PointF? = null private var faceSensorLocation: PointF? = null private var circleReveal: LightRevealEffect? = null + private var udfpsController: UdfpsController? = null + + private var dwellScale = 2f + private var expandedDwellScale = 2.5f + private var aodDwellScale = 1.9f + private var aodExpandedDwellScale = 2.3f + private var udfpsRadius: Float = -1f + + override fun onInit() { + mView.setAlphaInDuration(sysuiContext.resources.getInteger( + R.integer.auth_ripple_alpha_in_duration).toLong()) + } + @VisibleForTesting public override fun onViewAttached() { + authController.addCallback(authControllerCallback) updateRippleColor() updateSensorLocation() - authController.addCallback(authControllerCallback) + updateUdfpsDependentParams() + udfpsController?.addCallback(udfpsControllerCallback) configurationController.addCallback(configurationChangedListener) keyguardUpdateMonitor.registerCallback(keyguardUpdateMonitorCallback) + keyguardStateController.addCallback(this) + wakefulnessLifecycle.addObserver(this) commandRegistry.registerCommand("auth-ripple") { AuthRippleCommand() } } @VisibleForTesting public override fun onViewDetached() { + udfpsController?.removeCallback(udfpsControllerCallback) authController.removeCallback(authControllerCallback) keyguardUpdateMonitor.removeCallback(keyguardUpdateMonitorCallback) configurationController.removeCallback(configurationChangedListener) + keyguardStateController.removeCallback(this) + wakefulnessLifecycle.removeObserver(this) commandRegistry.unregisterCommand("auth-ripple") notificationShadeWindowController.setForcePluginOpen(false, this) } - private fun showRipple(biometricSourceType: BiometricSourceType?) { + fun showRipple(biometricSourceType: BiometricSourceType?) { if (!keyguardUpdateMonitor.isKeyguardVisible || keyguardUpdateMonitor.userNeedsStrongAuth()) { return @@ -88,43 +126,61 @@ class AuthRippleController @Inject constructor( if (biometricSourceType == BiometricSourceType.FINGERPRINT && fingerprintSensorLocation != null) { mView.setSensorLocation(fingerprintSensorLocation!!) - showRipple() + showUnlockedRipple() } else if (biometricSourceType == BiometricSourceType.FACE && faceSensorLocation != null) { if (!bypassController.canBypass()) { return } mView.setSensorLocation(faceSensorLocation!!) - showRipple() + showUnlockedRipple() } } - private fun showRipple() { + private fun showUnlockedRipple() { notificationShadeWindowController.setForcePluginOpen(true, this) - val biometricUnlockMode = biometricUnlockController.mode - val useCircleReveal = circleReveal != null && - (biometricUnlockMode == BiometricUnlockController.MODE_WAKE_AND_UNLOCK || - biometricUnlockMode == BiometricUnlockController.MODE_WAKE_AND_UNLOCK_PULSING || - biometricUnlockMode == BiometricUnlockController.MODE_WAKE_AND_UNLOCK_FROM_DREAM) + val useCircleReveal = circleReveal != null && biometricUnlockController.isWakeAndUnlock val lightRevealScrim = statusBar.lightRevealScrim if (useCircleReveal) { lightRevealScrim?.revealEffect = circleReveal!! + startLightRevealScrimOnKeyguardFadingAway = true } - mView.startRipple( + mView.startUnlockedRipple( /* end runnable */ Runnable { notificationShadeWindowController.setForcePluginOpen(false, this) - }, - /* circleReveal */ - if (useCircleReveal) { - lightRevealScrim - } else { - null } ) } + override fun onKeyguardFadingAwayChanged() { + if (keyguardStateController.isKeyguardFadingAway) { + val lightRevealScrim = statusBar.lightRevealScrim + if (startLightRevealScrimOnKeyguardFadingAway && lightRevealScrim != null) { + val revealAnimator = ValueAnimator.ofFloat(.1f, 1f).apply { + interpolator = Interpolators.LINEAR_OUT_SLOW_IN + duration = RIPPLE_ANIMATION_DURATION + startDelay = keyguardStateController.keyguardFadingAwayDelay + addUpdateListener { animator -> + if (lightRevealScrim.revealEffect != circleReveal) { + // if the something else took over the reveal, let's do nothing. + return@addUpdateListener + } + lightRevealScrim.revealAmount = animator.animatedValue as Float + } + } + revealAnimator.start() + startLightRevealScrimOnKeyguardFadingAway = false + } + } + } + + override fun onStartedGoingToSleep() { + // reset the light reveal start in case we were pending an unlock + startLightRevealScrimOnKeyguardFadingAway = false + } + fun updateSensorLocation() { fingerprintSensorLocation = authController.fingerprintSensorLocation faceSensorLocation = authController.faceAuthSensorLocation @@ -146,7 +202,23 @@ class AuthRippleController @Inject constructor( Utils.getColorAttr(sysuiContext, android.R.attr.colorAccent).defaultColor) } - val keyguardUpdateMonitorCallback = + private fun showDwellRipple() { + if (statusBarStateController.isDozing) { + mView.startDwellRipple( + /* startRadius */ udfpsRadius, + /* endRadius */ udfpsRadius * aodDwellScale, + /* expandedRadius */ udfpsRadius * aodExpandedDwellScale, + /* isDozing */ true) + } else { + mView.startDwellRipple( + /* startRadius */ udfpsRadius, + /* endRadius */ udfpsRadius * dwellScale, + /* expandedRadius */ udfpsRadius * expandedDwellScale, + /* isDozing */ false) + } + } + + private val keyguardUpdateMonitorCallback = object : KeyguardUpdateMonitorCallback() { override fun onBiometricAuthenticated( userId: Int, @@ -155,9 +227,13 @@ class AuthRippleController @Inject constructor( ) { showRipple(biometricSourceType) } + + override fun onBiometricAuthFailed(biometricSourceType: BiometricSourceType?) { + mView.retractRipple() + } } - val configurationChangedListener = + private val configurationChangedListener = object : ConfigurationController.ConfigurationListener { override fun onConfigChanged(newConfig: Configuration?) { updateSensorLocation() @@ -173,14 +249,70 @@ class AuthRippleController @Inject constructor( } } - private val authControllerCallback = AuthController.Callback { updateSensorLocation() } + private val udfpsControllerCallback = + object : UdfpsController.Callback { + override fun onFingerDown() { + if (fingerprintSensorLocation == null) { + Log.e("AuthRipple", "fingerprintSensorLocation=null onFingerDown. " + + "Skip showing dwell ripple") + return + } + + mView.setSensorLocation(fingerprintSensorLocation!!) + showDwellRipple() + } + + override fun onFingerUp() { + mView.retractRipple() + } + } + + private val authControllerCallback = AuthController.Callback { + updateSensorLocation() + updateUdfpsDependentParams() + } + + private fun updateUdfpsDependentParams() { + authController.udfpsProps?.let { + if (it.size > 0) { + udfpsRadius = it[0].sensorRadius.toFloat() + udfpsController = udfpsControllerProvider.get() + + if (mView.isAttachedToWindow) { + udfpsController?.addCallback(udfpsControllerCallback) + } + } + } + } inner class AuthRippleCommand : Command { + fun printLockScreenDwellInfo(pw: PrintWriter) { + pw.println("lock screen dwell ripple: " + + "\n\tsensorLocation=$fingerprintSensorLocation" + + "\n\tdwellScale=$dwellScale" + + "\n\tdwellExpand=$expandedDwellScale") + } + + fun printAodDwellInfo(pw: PrintWriter) { + pw.println("aod dwell ripple: " + + "\n\tsensorLocation=$fingerprintSensorLocation" + + "\n\tdwellScale=$aodDwellScale" + + "\n\tdwellExpand=$aodExpandedDwellScale") + } + override fun execute(pw: PrintWriter, args: List<String>) { if (args.isEmpty()) { invalidCommand(pw) } else { when (args[0]) { + "dwell" -> { + showDwellRipple() + if (statusBarStateController.isDozing) { + printAodDwellInfo(pw) + } else { + printLockScreenDwellInfo(pw) + } + } "fingerprint" -> { pw.println("fingerprint ripple sensorLocation=$fingerprintSensorLocation") showRipple(BiometricSourceType.FINGERPRINT) @@ -199,7 +331,7 @@ class AuthRippleController @Inject constructor( pw.println("custom ripple sensorLocation=" + args[1].toFloat() + ", " + args[2].toFloat()) mView.setSensorLocation(PointF(args[1].toFloat(), args[2].toFloat())) - showRipple() + showUnlockedRipple() } else -> invalidCommand(pw) } @@ -209,6 +341,7 @@ class AuthRippleController @Inject constructor( override fun help(pw: PrintWriter) { pw.println("Usage: adb shell cmd statusbar auth-ripple <command>") pw.println("Available commands:") + pw.println(" dwell") pw.println(" fingerprint") pw.println(" face") pw.println(" custom <x-location: int> <y-location: int>") @@ -219,4 +352,8 @@ class AuthRippleController @Inject constructor( help(pw) } } + + companion object { + const val RIPPLE_ANIMATION_DURATION: Long = 1533 + } } diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthRippleView.kt b/packages/SystemUI/src/com/android/systemui/biometrics/AuthRippleView.kt index 95ea81003ecb..c6d26ffb9957 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthRippleView.kt +++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthRippleView.kt @@ -27,21 +27,40 @@ import android.util.AttributeSet import android.view.View import android.view.animation.PathInterpolator import com.android.internal.graphics.ColorUtils -import com.android.systemui.statusbar.LightRevealScrim +import com.android.systemui.animation.Interpolators import com.android.systemui.statusbar.charging.RippleShader -private const val RIPPLE_ANIMATION_DURATION: Long = 1533 private const val RIPPLE_SPARKLE_STRENGTH: Float = 0.4f /** - * Expanding ripple effect on the transition from biometric authentication success to showing + * Expanding ripple effect + * - startUnlockedRipple for the transition from biometric authentication success to showing * launcher. + * - startDwellRipple for the ripple expansion out when the user has their finger down on the UDFPS + * sensor area + * - retractRipple for the ripple animation inwards to signal a failure */ class AuthRippleView(context: Context?, attrs: AttributeSet?) : View(context, attrs) { - private var rippleInProgress: Boolean = false + private val retractInterpolator = PathInterpolator(.05f, .93f, .1f, 1f) + + private val dwellPulseDuration = 50L + private val dwellAlphaDuration = dwellPulseDuration + private val dwellAlpha: Float = 1f + private val dwellExpandDuration = 1200L - dwellPulseDuration + + private val aodDwellPulseDuration = 50L + private var aodDwellAlphaDuration = aodDwellPulseDuration + private var aodDwellAlpha: Float = .8f + private var aodDwellExpandDuration = 1200L - aodDwellPulseDuration + + private val retractDuration = 400L + private var alphaInDuration: Long = 0 + private var unlockedRippleInProgress: Boolean = false private val rippleShader = RippleShader() private val ripplePaint = Paint() - private var radius: Float = 0.0f + private var retractAnimator: Animator? = null + private var dwellPulseOutAnimator: Animator? = null + private var radius: Float = 0f set(value) { rippleShader.radius = value field = value @@ -62,51 +81,200 @@ class AuthRippleView(context: Context?, attrs: AttributeSet?) : View(context, at fun setSensorLocation(location: PointF) { origin = location - radius = maxOf(location.x, location.y, width - location.x, height - location.y) - .toFloat() + radius = maxOf(location.x, location.y, width - location.x, height - location.y).toFloat() } - fun startRipple(onAnimationEnd: Runnable?, lightReveal: LightRevealScrim?) { - if (rippleInProgress) { - return // Ignore if ripple effect is already playing + fun setAlphaInDuration(duration: Long) { + alphaInDuration = duration + } + + /** + * Animate ripple inwards back to radius 0 + */ + fun retractRipple() { + if (retractAnimator?.isRunning == true) { + return // let the animation finish + } + + if (dwellPulseOutAnimator?.isRunning == true) { + val retractRippleAnimator = ValueAnimator.ofFloat(rippleShader.progress, 0f) + .apply { + interpolator = retractInterpolator + duration = retractDuration + addUpdateListener { animator -> + val now = animator.currentPlayTime + rippleShader.progress = animator.animatedValue as Float + rippleShader.time = now.toFloat() + + invalidate() + } + } + + val retractAlphaAnimator = ValueAnimator.ofInt(255, 0).apply { + interpolator = Interpolators.LINEAR + duration = retractDuration + addUpdateListener { animator -> + rippleShader.color = ColorUtils.setAlphaComponent( + rippleShader.color, + animator.animatedValue as Int + ) + invalidate() + } + } + + retractAnimator = AnimatorSet().apply { + playTogether(retractRippleAnimator, retractAlphaAnimator) + addListener(object : AnimatorListenerAdapter() { + override fun onAnimationStart(animation: Animator?) { + dwellPulseOutAnimator?.cancel() + rippleShader.shouldFadeOutRipple = false + visibility = VISIBLE + } + + override fun onAnimationEnd(animation: Animator?) { + visibility = GONE + resetRippleAlpha() + } + }) + start() + } + } + } + + /** + * Ripple that moves animates from an outer ripple ring of + * startRadius => endRadius => expandedRadius + */ + fun startDwellRipple( + startRadius: Float, + endRadius: Float, + expandedRadius: Float, + isDozing: Boolean + ) { + if (unlockedRippleInProgress || dwellPulseOutAnimator?.isRunning == true) { + return } - val rippleAnimator = ValueAnimator.ofFloat(0f, 1f).apply { - interpolator = PathInterpolator(0.4f, 0f, 0f, 1f) - duration = RIPPLE_ANIMATION_DURATION + // we divide by 4 because the desired startRadius and endRadius is for the ripple's outer + // ring see RippleShader + val startDwellProgress = startRadius / radius / 4f + val endInitialDwellProgress = endRadius / radius / 4f + val endExpandDwellProgress = expandedRadius / radius / 4f + + val alpha = if (isDozing) aodDwellAlpha else dwellAlpha + val pulseOutEndAlpha = (255 * alpha).toInt() + val expandDwellEndAlpha = kotlin.math.min((255 * (alpha + .25f)).toInt(), 255) + val dwellPulseOutRippleAnimator = ValueAnimator.ofFloat(startDwellProgress, + endInitialDwellProgress).apply { + interpolator = Interpolators.LINEAR_OUT_SLOW_IN + duration = if (isDozing) aodDwellPulseDuration else dwellPulseDuration addUpdateListener { animator -> val now = animator.currentPlayTime rippleShader.progress = animator.animatedValue as Float rippleShader.time = now.toFloat() - lightReveal?.revealAmount = animator.animatedValue as Float invalidate() } } - val revealAnimator = ValueAnimator.ofFloat(0f, 1f).apply { - interpolator = rippleAnimator.interpolator - startDelay = 10 - duration = rippleAnimator.duration + val dwellPulseOutAlphaAnimator = ValueAnimator.ofInt(0, pulseOutEndAlpha).apply { + interpolator = Interpolators.LINEAR + duration = if (isDozing) aodDwellAlphaDuration else dwellAlphaDuration addUpdateListener { animator -> - lightReveal?.revealAmount = animator.animatedValue as Float + rippleShader.color = ColorUtils.setAlphaComponent( + rippleShader.color, + animator.animatedValue as Int + ) + invalidate() } } - val alphaInAnimator = ValueAnimator.ofInt(0, 127).apply { - duration = 167 + // slowly animate outwards until we receive a call to retractRipple or startUnlockedRipple + val expandDwellRippleAnimator = ValueAnimator.ofFloat(endInitialDwellProgress, + endExpandDwellProgress).apply { + interpolator = Interpolators.LINEAR_OUT_SLOW_IN + duration = if (isDozing) aodDwellExpandDuration else dwellExpandDuration + addUpdateListener { animator -> + val now = animator.currentPlayTime + rippleShader.progress = animator.animatedValue as Float + rippleShader.time = now.toFloat() + + invalidate() + } + } + + val expandDwellAlphaAnimator = ValueAnimator.ofInt(pulseOutEndAlpha, expandDwellEndAlpha) + .apply { + interpolator = Interpolators.LINEAR + duration = if (isDozing) aodDwellExpandDuration else dwellExpandDuration addUpdateListener { animator -> rippleShader.color = ColorUtils.setAlphaComponent( - rippleShader.color, - animator.animatedValue as Int + rippleShader.color, + animator.animatedValue as Int ) invalidate() } } - val alphaOutAnimator = ValueAnimator.ofInt(127, 0).apply { - startDelay = 417 - duration = 1116 + val initialDwellPulseOutAnimator = AnimatorSet().apply { + playTogether(dwellPulseOutRippleAnimator, dwellPulseOutAlphaAnimator) + } + val expandDwellAnimator = AnimatorSet().apply { + playTogether(expandDwellRippleAnimator, expandDwellAlphaAnimator) + } + + dwellPulseOutAnimator = AnimatorSet().apply { + playSequentially( + initialDwellPulseOutAnimator, + expandDwellAnimator + ) + addListener(object : AnimatorListenerAdapter() { + override fun onAnimationStart(animation: Animator?) { + retractAnimator?.cancel() + rippleShader.shouldFadeOutRipple = false + visibility = VISIBLE + } + + override fun onAnimationEnd(animation: Animator?) { + visibility = GONE + resetRippleAlpha() + } + }) + start() + } + } + + /** + * Ripple that bursts outwards from the position of the sensor to the edges of the screen + */ + fun startUnlockedRipple(onAnimationEnd: Runnable?) { + if (unlockedRippleInProgress) { + return // Ignore if ripple effect is already playing + } + + var rippleStart = 0f + var alphaDuration = alphaInDuration + if (dwellPulseOutAnimator?.isRunning == true || retractAnimator?.isRunning == true) { + rippleStart = rippleShader.progress + alphaDuration = 0 + dwellPulseOutAnimator?.cancel() + retractAnimator?.cancel() + } + + val rippleAnimator = ValueAnimator.ofFloat(rippleStart, 1f).apply { + interpolator = Interpolators.LINEAR_OUT_SLOW_IN + duration = AuthRippleController.RIPPLE_ANIMATION_DURATION + addUpdateListener { animator -> + val now = animator.currentPlayTime + rippleShader.progress = animator.animatedValue as Float + rippleShader.time = now.toFloat() + + invalidate() + } + } + + val alphaInAnimator = ValueAnimator.ofInt(0, 255).apply { + duration = alphaDuration addUpdateListener { animator -> rippleShader.color = ColorUtils.setAlphaComponent( rippleShader.color, @@ -119,19 +287,18 @@ class AuthRippleView(context: Context?, attrs: AttributeSet?) : View(context, at val animatorSet = AnimatorSet().apply { playTogether( rippleAnimator, - revealAnimator, - alphaInAnimator, - alphaOutAnimator + alphaInAnimator ) addListener(object : AnimatorListenerAdapter() { override fun onAnimationStart(animation: Animator?) { - rippleInProgress = true + unlockedRippleInProgress = true + rippleShader.shouldFadeOutRipple = true visibility = VISIBLE } override fun onAnimationEnd(animation: Animator?) { onAnimationEnd?.run() - rippleInProgress = false + unlockedRippleInProgress = false visibility = GONE } }) @@ -139,8 +306,16 @@ class AuthRippleView(context: Context?, attrs: AttributeSet?) : View(context, at animatorSet.start() } + fun resetRippleAlpha() { + rippleShader.color = ColorUtils.setAlphaComponent( + rippleShader.color, + 255 + ) + } + fun setColor(color: Int) { rippleShader.color = color + resetRippleAlpha() } override fun onDraw(canvas: Canvas?) { @@ -148,7 +323,7 @@ class AuthRippleView(context: Context?, attrs: AttributeSet?) : View(context, at // the active effect area. Values here should be kept in sync with the // animation implementation in the ripple shader. val maskRadius = (1 - (1 - rippleShader.progress) * (1 - rippleShader.progress) * - (1 - rippleShader.progress)) * radius * 1.5f + (1 - rippleShader.progress)) * radius * 2f canvas?.drawCircle(origin.x, origin.y, maskRadius, ripplePaint) } } diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java index e612fb4712fc..54f932184331 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java +++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java @@ -77,7 +77,9 @@ import com.android.systemui.statusbar.policy.KeyguardStateController; import com.android.systemui.util.concurrency.DelayableExecutor; import com.android.systemui.util.concurrency.Execution; +import java.util.HashSet; import java.util.Optional; +import java.util.Set; import javax.inject.Inject; @@ -157,6 +159,7 @@ public class UdfpsController implements DozeReceiver { private Runnable mAodInterruptRunnable; private boolean mOnFingerDown; private boolean mAttemptedToDismissKeyguard; + private Set<Callback> mCallbacks = new HashSet<>(); @VisibleForTesting public static final AudioAttributes VIBRATION_SONIFICATION_ATTRIBUTES = @@ -210,12 +213,14 @@ public class UdfpsController implements DozeReceiver { } void onAcquiredGood() { + Log.d(TAG, "onAcquiredGood"); if (mEnrollHelper != null) { mEnrollHelper.animateIfLastStep(); } } void onEnrollmentHelp() { + Log.d(TAG, "onEnrollmentHelp"); if (mEnrollHelper != null) { mEnrollHelper.onEnrollmentHelp(); } @@ -759,6 +764,7 @@ public class UdfpsController implements DozeReceiver { UdfpsEnrollView enrollView = (UdfpsEnrollView) mInflater.inflate( R.layout.udfps_enroll_view, null); mView.addView(enrollView); + enrollView.updateSensorLocation(mSensorProps); return new UdfpsEnrollViewController( enrollView, mServerRequest.mEnrollHelper, @@ -781,6 +787,7 @@ public class UdfpsController implements DozeReceiver { mKeyguardViewMediator, mLockscreenShadeTransitionController, mConfigurationController, + mKeyguardStateController, this ); case IUdfpsOverlayController.REASON_AUTH_BP: @@ -841,6 +848,10 @@ public class UdfpsController implements DozeReceiver { return; } + if (!mKeyguardUpdateMonitor.isFingerprintDetectionRunning()) { + return; + } + mAodInterruptRunnable = () -> { mIsAodInterruptActive = true; // Since the sensor that triggers the AOD interrupt doesn't provide @@ -860,6 +871,20 @@ public class UdfpsController implements DozeReceiver { } /** + * Add a callback for fingerUp and fingerDown events + */ + public void addCallback(Callback cb) { + mCallbacks.add(cb); + } + + /** + * Remove callback + */ + public void removeCallback(Callback cb) { + mCallbacks.remove(cb); + } + + /** * Cancel updfs scan affordances - ability to hide the HbmSurfaceView (white circle) before * user explicitly lifts their finger. Generally, this should be called whenever udfps fails * or errors. @@ -913,6 +938,10 @@ public class UdfpsController implements DozeReceiver { mFingerprintManager.onUiReady(mSensorProps.sensorId); Trace.endAsyncSection("UdfpsController.e2e.startIllumination", 0); }); + + for (Callback cb : mCallbacks) { + cb.onFingerDown(); + } } private void onFingerUp() { @@ -925,6 +954,9 @@ public class UdfpsController implements DozeReceiver { } if (mOnFingerDown) { mFingerprintManager.onPointerUp(mSensorProps.sensorId); + for (Callback cb : mCallbacks) { + cb.onFingerUp(); + } } mOnFingerDown = false; if (mView.isIlluminationRequested()) { @@ -945,4 +977,19 @@ public class UdfpsController implements DozeReceiver { mView.setOnTouchListener(mOnTouchListener); } } + + /** + * Callback for fingerUp and fingerDown events. + */ + public interface Callback { + /** + * Called onFingerUp events. Will only be called if the finger was previously down. + */ + void onFingerUp(); + + /** + * Called onFingerDown events. + */ + void onFingerDown(); + } } diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsEnrollDrawable.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsEnrollDrawable.java index ea69b1d626ae..2034ff35be70 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsEnrollDrawable.java +++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsEnrollDrawable.java @@ -16,9 +16,11 @@ package com.android.systemui.biometrics; +import android.animation.Animator; import android.animation.AnimatorSet; import android.animation.ValueAnimator; import android.content.Context; +import android.content.res.TypedArray; import android.graphics.Canvas; import android.graphics.Color; import android.graphics.Paint; @@ -26,11 +28,17 @@ import android.graphics.PointF; import android.graphics.Rect; import android.graphics.RectF; import android.graphics.drawable.Drawable; +import android.os.Handler; +import android.os.Looper; +import android.util.TypedValue; import android.view.animation.AccelerateDecelerateInterpolator; +import android.view.animation.LinearInterpolator; +import androidx.annotation.ColorInt; import androidx.annotation.NonNull; import androidx.annotation.Nullable; +import com.android.internal.graphics.ColorUtils; import com.android.systemui.R; /** @@ -39,11 +47,20 @@ import com.android.systemui.R; public class UdfpsEnrollDrawable extends UdfpsDrawable { private static final String TAG = "UdfpsAnimationEnroll"; - private static final long ANIM_DURATION = 800; + private static final long HINT_COLOR_ANIM_DELAY_MS = 233L; + private static final long HINT_COLOR_ANIM_DURATION_MS = 517L; + private static final long HINT_WIDTH_ANIM_DURATION_MS = 233L; + private static final long TARGET_ANIM_DURATION_LONG = 800L; + private static final long TARGET_ANIM_DURATION_SHORT = 600L; // 1 + SCALE_MAX is the maximum that the moving target will animate to private static final float SCALE_MAX = 0.25f; - @NonNull private final UdfpsEnrollProgressBarDrawable mProgressDrawable; + private static final float HINT_PADDING_DP = 10f; + private static final float HINT_MAX_WIDTH_DP = 6f; + private static final float HINT_ANGLE = 40f; + + private final Handler mHandler = new Handler(Looper.getMainLooper()); + @NonNull private final Drawable mMovingTargetFpIcon; @NonNull private final Paint mSensorOutlinePaint; @NonNull private final Paint mBlueFill; @@ -52,18 +69,41 @@ public class UdfpsEnrollDrawable extends UdfpsDrawable { @Nullable private UdfpsEnrollHelper mEnrollHelper; // Moving target animator set - @Nullable AnimatorSet mAnimatorSet; + @Nullable AnimatorSet mTargetAnimatorSet; // Moving target location float mCurrentX; float mCurrentY; // Moving target size float mCurrentScale = 1.f; + @ColorInt private final int mHintColorFaded; + @ColorInt private final int mHintColorHighlight; + private final float mHintMaxWidthPx; + private final float mHintPaddingPx; + + @NonNull private final Animator.AnimatorListener mTargetAnimListener; + + private boolean mShouldShowTipHint = false; + @NonNull private final Paint mTipHintPaint; + @Nullable private AnimatorSet mTipHintAnimatorSet; + @Nullable private ValueAnimator mTipHintColorAnimator; + @Nullable private ValueAnimator mTipHintWidthAnimator; + @NonNull private final ValueAnimator.AnimatorUpdateListener mTipHintColorUpdateListener; + @NonNull private final ValueAnimator.AnimatorUpdateListener mTipHintWidthUpdateListener; + @NonNull private final Animator.AnimatorListener mTipHintPulseListener; + + private boolean mShouldShowEdgeHint = false; + @NonNull private final Paint mEdgeHintPaint; + @Nullable private AnimatorSet mEdgeHintAnimatorSet; + @Nullable private ValueAnimator mEdgeHintColorAnimator; + @Nullable private ValueAnimator mEdgeHintWidthAnimator; + @NonNull private final ValueAnimator.AnimatorUpdateListener mEdgeHintColorUpdateListener; + @NonNull private final ValueAnimator.AnimatorUpdateListener mEdgeHintWidthUpdateListener; + @NonNull private final Animator.AnimatorListener mEdgeHintPulseListener; + UdfpsEnrollDrawable(@NonNull Context context) { super(context); - mProgressDrawable = new UdfpsEnrollProgressBarDrawable(context, this); - mSensorOutlinePaint = new Paint(0 /* flags */); mSensorOutlinePaint.setAntiAlias(true); mSensorOutlinePaint.setColor(mContext.getColor(R.color.udfps_enroll_icon)); @@ -80,6 +120,117 @@ public class UdfpsEnrollDrawable extends UdfpsDrawable { mMovingTargetFpIcon.mutate(); mFingerprintDrawable.setTint(mContext.getColor(R.color.udfps_enroll_icon)); + + mHintColorFaded = getHintColorFaded(context); + mHintColorHighlight = context.getColor(R.color.udfps_enroll_progress); + mHintMaxWidthPx = Utils.dpToPixels(context, HINT_MAX_WIDTH_DP); + mHintPaddingPx = Utils.dpToPixels(context, HINT_PADDING_DP); + + mTargetAnimListener = new Animator.AnimatorListener() { + @Override + public void onAnimationStart(Animator animation) {} + + @Override + public void onAnimationEnd(Animator animation) { + updateTipHintVisibility(); + } + + @Override + public void onAnimationCancel(Animator animation) {} + + @Override + public void onAnimationRepeat(Animator animation) {} + }; + + mTipHintPaint = new Paint(0 /* flags */); + mTipHintPaint.setAntiAlias(true); + mTipHintPaint.setColor(mHintColorFaded); + mTipHintPaint.setStyle(Paint.Style.STROKE); + mTipHintPaint.setStrokeCap(Paint.Cap.ROUND); + mTipHintPaint.setStrokeWidth(0f); + mTipHintColorUpdateListener = animation -> { + mTipHintPaint.setColor((int) animation.getAnimatedValue()); + invalidateSelf(); + }; + mTipHintWidthUpdateListener = animation -> { + mTipHintPaint.setStrokeWidth((float) animation.getAnimatedValue()); + invalidateSelf(); + }; + mTipHintPulseListener = new Animator.AnimatorListener() { + @Override + public void onAnimationStart(Animator animation) {} + + @Override + public void onAnimationEnd(Animator animation) { + mHandler.postDelayed(() -> { + mTipHintColorAnimator = + ValueAnimator.ofArgb(mTipHintPaint.getColor(), mHintColorFaded); + mTipHintColorAnimator.setInterpolator(new LinearInterpolator()); + mTipHintColorAnimator.setDuration(HINT_COLOR_ANIM_DURATION_MS); + mTipHintColorAnimator.addUpdateListener(mTipHintColorUpdateListener); + mTipHintColorAnimator.start(); + }, HINT_COLOR_ANIM_DELAY_MS); + } + + @Override + public void onAnimationCancel(Animator animation) {} + + @Override + public void onAnimationRepeat(Animator animation) {} + }; + + mEdgeHintPaint = new Paint(0 /* flags */); + mEdgeHintPaint.setAntiAlias(true); + mEdgeHintPaint.setColor(mHintColorFaded); + mEdgeHintPaint.setStyle(Paint.Style.STROKE); + mEdgeHintPaint.setStrokeCap(Paint.Cap.ROUND); + mEdgeHintPaint.setStrokeWidth(0f); + mEdgeHintColorUpdateListener = animation -> { + mEdgeHintPaint.setColor((int) animation.getAnimatedValue()); + invalidateSelf(); + }; + mEdgeHintWidthUpdateListener = animation -> { + mEdgeHintPaint.setStrokeWidth((float) animation.getAnimatedValue()); + invalidateSelf(); + }; + mEdgeHintPulseListener = new Animator.AnimatorListener() { + @Override + public void onAnimationStart(Animator animation) {} + + @Override + public void onAnimationEnd(Animator animation) { + mHandler.postDelayed(() -> { + mEdgeHintColorAnimator = + ValueAnimator.ofArgb(mEdgeHintPaint.getColor(), mHintColorFaded); + mEdgeHintColorAnimator.setInterpolator(new LinearInterpolator()); + mEdgeHintColorAnimator.setDuration(HINT_COLOR_ANIM_DURATION_MS); + mEdgeHintColorAnimator.addUpdateListener(mEdgeHintColorUpdateListener); + mEdgeHintColorAnimator.start(); + }, HINT_COLOR_ANIM_DELAY_MS); + } + + @Override + public void onAnimationCancel(Animator animation) {} + + @Override + public void onAnimationRepeat(Animator animation) {} + }; + } + + @ColorInt + private static int getHintColorFaded(@NonNull Context context) { + final TypedValue tv = new TypedValue(); + context.getTheme().resolveAttribute(android.R.attr.disabledAlpha, tv, true); + final int alpha = (int) (tv.getFloat() * 255f); + + final int[] attrs = new int[] {android.R.attr.colorControlNormal}; + final TypedArray ta = context.obtainStyledAttributes(attrs); + try { + @ColorInt final int color = ta.getColor(0, context.getColor(R.color.white_disabled)); + return ColorUtils.setAlphaComponent(color, alpha); + } finally { + ta.recycle(); + } } void setEnrollHelper(@NonNull UdfpsEnrollHelper helper) { @@ -100,59 +251,164 @@ public class UdfpsEnrollDrawable extends UdfpsDrawable { } void onEnrollmentProgress(int remaining, int totalSteps) { - mProgressDrawable.setEnrollmentProgress(remaining, totalSteps); + if (mEnrollHelper == null) { + return; + } - if (mEnrollHelper.isCenterEnrollmentComplete()) { - if (mAnimatorSet != null && mAnimatorSet.isRunning()) { - mAnimatorSet.end(); + if (!mEnrollHelper.isCenterEnrollmentStage()) { + if (mTargetAnimatorSet != null && mTargetAnimatorSet.isRunning()) { + mTargetAnimatorSet.end(); } final PointF point = mEnrollHelper.getNextGuidedEnrollmentPoint(); + if (mCurrentX != point.x || mCurrentY != point.y) { + final ValueAnimator x = ValueAnimator.ofFloat(mCurrentX, point.x); + x.addUpdateListener(animation -> { + mCurrentX = (float) animation.getAnimatedValue(); + invalidateSelf(); + }); + + final ValueAnimator y = ValueAnimator.ofFloat(mCurrentY, point.y); + y.addUpdateListener(animation -> { + mCurrentY = (float) animation.getAnimatedValue(); + invalidateSelf(); + }); + + final boolean isMovingToCenter = point.x == 0f && point.y == 0f; + final long duration = isMovingToCenter + ? TARGET_ANIM_DURATION_SHORT + : TARGET_ANIM_DURATION_LONG; + + final ValueAnimator scale = ValueAnimator.ofFloat(0, (float) Math.PI); + scale.setDuration(duration); + scale.addUpdateListener(animation -> { + // Grow then shrink + mCurrentScale = 1 + + SCALE_MAX * (float) Math.sin((float) animation.getAnimatedValue()); + invalidateSelf(); + }); + + mTargetAnimatorSet = new AnimatorSet(); + + mTargetAnimatorSet.setInterpolator(new AccelerateDecelerateInterpolator()); + mTargetAnimatorSet.setDuration(duration); + mTargetAnimatorSet.addListener(mTargetAnimListener); + mTargetAnimatorSet.playTogether(x, y, scale); + mTargetAnimatorSet.start(); + } else { + updateTipHintVisibility(); + } + } else { + updateTipHintVisibility(); + } + + updateEdgeHintVisibility(); + } + + private void updateTipHintVisibility() { + final boolean shouldShow = mEnrollHelper != null && mEnrollHelper.isTipEnrollmentStage(); + if (mShouldShowTipHint == shouldShow) { + return; + } + mShouldShowTipHint = shouldShow; + + if (mTipHintWidthAnimator != null && mTipHintWidthAnimator.isRunning()) { + mTipHintWidthAnimator.cancel(); + } + + final float targetWidth = shouldShow ? mHintMaxWidthPx : 0f; + mTipHintWidthAnimator = ValueAnimator.ofFloat(mTipHintPaint.getStrokeWidth(), targetWidth); + mTipHintWidthAnimator.setDuration(HINT_WIDTH_ANIM_DURATION_MS); + mTipHintWidthAnimator.addUpdateListener(mTipHintWidthUpdateListener); + + if (shouldShow) { + startTipHintPulseAnimation(); + } else { + mTipHintWidthAnimator.start(); + } + } - final ValueAnimator x = ValueAnimator.ofFloat(mCurrentX, point.x); - x.addUpdateListener(animation -> { - mCurrentX = (float) animation.getAnimatedValue(); - invalidateSelf(); - }); - - final ValueAnimator y = ValueAnimator.ofFloat(mCurrentY, point.y); - y.addUpdateListener(animation -> { - mCurrentY = (float) animation.getAnimatedValue(); - invalidateSelf(); - }); - - final ValueAnimator scale = ValueAnimator.ofFloat(0, (float) Math.PI); - scale.setDuration(ANIM_DURATION); - scale.addUpdateListener(animation -> { - // Grow then shrink - mCurrentScale = 1 + - SCALE_MAX * (float) Math.sin((float) animation.getAnimatedValue()); - invalidateSelf(); - }); - - mAnimatorSet = new AnimatorSet(); - - mAnimatorSet.setInterpolator(new AccelerateDecelerateInterpolator()); - mAnimatorSet.setDuration(ANIM_DURATION); - mAnimatorSet.playTogether(x, y, scale); - mAnimatorSet.start(); + private void updateEdgeHintVisibility() { + final boolean shouldShow = mEnrollHelper != null && mEnrollHelper.isEdgeEnrollmentStage(); + if (mShouldShowEdgeHint == shouldShow) { + return; + } + mShouldShowEdgeHint = shouldShow; + + if (mEdgeHintWidthAnimator != null && mEdgeHintWidthAnimator.isRunning()) { + mEdgeHintWidthAnimator.cancel(); + } + + final float targetWidth = shouldShow ? mHintMaxWidthPx : 0f; + mEdgeHintWidthAnimator = + ValueAnimator.ofFloat(mEdgeHintPaint.getStrokeWidth(), targetWidth); + mEdgeHintWidthAnimator.setDuration(HINT_WIDTH_ANIM_DURATION_MS); + mEdgeHintWidthAnimator.addUpdateListener(mEdgeHintWidthUpdateListener); + + if (shouldShow) { + startEdgeHintPulseAnimation(); + } else { + mEdgeHintWidthAnimator.start(); } } - void onLastStepAcquired() { - mProgressDrawable.onLastStepAcquired(); + private void startTipHintPulseAnimation() { + mHandler.removeCallbacksAndMessages(null); + if (mTipHintAnimatorSet != null && mTipHintAnimatorSet.isRunning()) { + mTipHintAnimatorSet.cancel(); + } + if (mTipHintColorAnimator != null && mTipHintColorAnimator.isRunning()) { + mTipHintColorAnimator.cancel(); + } + + mTipHintColorAnimator = ValueAnimator.ofArgb(mTipHintPaint.getColor(), mHintColorHighlight); + mTipHintColorAnimator.setDuration(HINT_WIDTH_ANIM_DURATION_MS); + mTipHintColorAnimator.addUpdateListener(mTipHintColorUpdateListener); + mTipHintColorAnimator.addListener(mTipHintPulseListener); + + mTipHintAnimatorSet = new AnimatorSet(); + mTipHintAnimatorSet.setInterpolator(new AccelerateDecelerateInterpolator()); + mTipHintAnimatorSet.playTogether(mTipHintColorAnimator, mTipHintWidthAnimator); + mTipHintAnimatorSet.start(); + } + + private void startEdgeHintPulseAnimation() { + mHandler.removeCallbacksAndMessages(null); + if (mEdgeHintAnimatorSet != null && mEdgeHintAnimatorSet.isRunning()) { + mEdgeHintAnimatorSet.cancel(); + } + if (mEdgeHintColorAnimator != null && mEdgeHintColorAnimator.isRunning()) { + mEdgeHintColorAnimator.cancel(); + } + + mEdgeHintColorAnimator = + ValueAnimator.ofArgb(mEdgeHintPaint.getColor(), mHintColorHighlight); + mEdgeHintColorAnimator.setDuration(HINT_WIDTH_ANIM_DURATION_MS); + mEdgeHintColorAnimator.addUpdateListener(mEdgeHintColorUpdateListener); + mEdgeHintColorAnimator.addListener(mEdgeHintPulseListener); + + mEdgeHintAnimatorSet = new AnimatorSet(); + mEdgeHintAnimatorSet.setInterpolator(new AccelerateDecelerateInterpolator()); + mEdgeHintAnimatorSet.playTogether(mEdgeHintColorAnimator, mEdgeHintWidthAnimator); + mEdgeHintAnimatorSet.start(); + } + + private boolean isTipHintVisible() { + return mTipHintPaint.getStrokeWidth() > 0f; + } + + private boolean isEdgeHintVisible() { + return mEdgeHintPaint.getStrokeWidth() > 0f; } @Override public void draw(@NonNull Canvas canvas) { - mProgressDrawable.draw(canvas); - if (isIlluminationShowing()) { return; } // Draw moving target - if (mEnrollHelper.isCenterEnrollmentComplete()) { + if (mEnrollHelper != null && !mEnrollHelper.isCenterEnrollmentStage()) { canvas.save(); canvas.translate(mCurrentX, mCurrentY); @@ -172,11 +428,59 @@ public class UdfpsEnrollDrawable extends UdfpsDrawable { mFingerprintDrawable.setAlpha(mAlpha); mSensorOutlinePaint.setAlpha(mAlpha); } - } - @Override - public void onBoundsChange(@NonNull Rect rect) { - mProgressDrawable.setBounds(rect); + // Draw the finger tip or edges hint. + if (isTipHintVisible() || isEdgeHintVisible()) { + canvas.save(); + + // Make arcs start from the top, rather than the right. + canvas.rotate(-90f, mSensorRect.centerX(), mSensorRect.centerY()); + + final float halfSensorHeight = Math.abs(mSensorRect.bottom - mSensorRect.top) / 2f; + final float halfSensorWidth = Math.abs(mSensorRect.right - mSensorRect.left) / 2f; + final float hintXOffset = halfSensorWidth + mHintPaddingPx; + final float hintYOffset = halfSensorHeight + mHintPaddingPx; + + if (isTipHintVisible()) { + canvas.drawArc( + mSensorRect.centerX() - hintXOffset, + mSensorRect.centerY() - hintYOffset, + mSensorRect.centerX() + hintXOffset, + mSensorRect.centerY() + hintYOffset, + -HINT_ANGLE / 2f, + HINT_ANGLE, + false /* useCenter */, + mTipHintPaint); + } + + if (isEdgeHintVisible()) { + // Draw right edge hint. + canvas.rotate(-90f, mSensorRect.centerX(), mSensorRect.centerY()); + canvas.drawArc( + mSensorRect.centerX() - hintXOffset, + mSensorRect.centerY() - hintYOffset, + mSensorRect.centerX() + hintXOffset, + mSensorRect.centerY() + hintYOffset, + -HINT_ANGLE / 2f, + HINT_ANGLE, + false /* useCenter */, + mEdgeHintPaint); + + // Draw left edge hint. + canvas.rotate(180f, mSensorRect.centerX(), mSensorRect.centerY()); + canvas.drawArc( + mSensorRect.centerX() - hintXOffset, + mSensorRect.centerY() - hintYOffset, + mSensorRect.centerX() + hintXOffset, + mSensorRect.centerY() + hintYOffset, + -HINT_ANGLE / 2f, + HINT_ANGLE, + false /* useCenter */, + mEdgeHintPaint); + } + + canvas.restore(); + } } @Override diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsEnrollHelper.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsEnrollHelper.java index 6a918a6c8d39..8ac6df7198b7 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsEnrollHelper.java +++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsEnrollHelper.java @@ -44,11 +44,19 @@ public class UdfpsEnrollHelper { private static final String NEW_COORDS_OVERRIDE = "com.android.systemui.biometrics.UdfpsNewCoords"; - // Enroll with two center touches before going to guided enrollment - private static final int NUM_CENTER_TOUCHES = 2; + static final int ENROLL_STAGE_COUNT = 4; + + // TODO(b/198928407): Consolidate with FingerprintEnrollEnrolling + private static final int[] STAGE_THRESHOLDS = new int[] { + 2, // center + 18, // guided + 22, // fingertip + 38, // edges + }; interface Listener { void onEnrollmentProgress(int remaining, int totalSteps); + void onEnrollmentHelp(int remaining, int totalSteps); void onLastStepAcquired(); } @@ -65,6 +73,8 @@ public class UdfpsEnrollHelper { // interface makes no promises about monotonically increasing by one each time. private int mLocationsEnrolled = 0; + private int mCenterTouchCount = 0; + @Nullable Listener mListener; public UdfpsEnrollHelper(@NonNull Context context, int reason) { @@ -117,17 +127,43 @@ public class UdfpsEnrollHelper { } } + static int getStageThreshold(int index) { + return STAGE_THRESHOLDS[index]; + } + + static int getLastStageThreshold() { + return STAGE_THRESHOLDS[ENROLL_STAGE_COUNT - 1]; + } + boolean shouldShowProgressBar() { return mEnrollReason == IUdfpsOverlayController.REASON_ENROLL_ENROLLING; } void onEnrollmentProgress(int remaining) { - if (mTotalSteps == -1) { - mTotalSteps = remaining; - } + Log.d(TAG, "onEnrollmentProgress: remaining = " + remaining + + ", mRemainingSteps = " + mRemainingSteps + + ", mTotalSteps = " + mTotalSteps + + ", mLocationsEnrolled = " + mLocationsEnrolled + + ", mCenterTouchCount = " + mCenterTouchCount); if (remaining != mRemainingSteps) { mLocationsEnrolled++; + if (isCenterEnrollmentStage()) { + mCenterTouchCount++; + } + } + + if (mTotalSteps == -1) { + mTotalSteps = remaining; + + // Allocate (or subtract) any extra steps for the first enroll stage. + final int extraSteps = mTotalSteps - getLastStageThreshold(); + if (extraSteps != 0) { + for (int stageIndex = 0; stageIndex < ENROLL_STAGE_COUNT; stageIndex++) { + STAGE_THRESHOLDS[stageIndex] = + Math.max(0, STAGE_THRESHOLDS[stageIndex] + extraSteps); + } + } } mRemainingSteps = remaining; @@ -138,7 +174,9 @@ public class UdfpsEnrollHelper { } void onEnrollmentHelp() { - + if (mListener != null) { + mListener.onEnrollmentHelp(mRemainingSteps, mTotalSteps); + } } void setListener(Listener listener) { @@ -152,19 +190,39 @@ public class UdfpsEnrollHelper { } } - boolean isCenterEnrollmentComplete() { + boolean isCenterEnrollmentStage() { + if (mTotalSteps == -1 || mRemainingSteps == -1) { + return true; + } + return mTotalSteps - mRemainingSteps < STAGE_THRESHOLDS[0]; + } + + boolean isGuidedEnrollmentStage() { + if (mAccessibilityEnabled || mTotalSteps == -1 || mRemainingSteps == -1) { + return false; + } + final int progressSteps = mTotalSteps - mRemainingSteps; + return progressSteps >= STAGE_THRESHOLDS[0] && progressSteps < STAGE_THRESHOLDS[1]; + } + + boolean isTipEnrollmentStage() { if (mTotalSteps == -1 || mRemainingSteps == -1) { return false; - } else if (mAccessibilityEnabled) { + } + final int progressSteps = mTotalSteps - mRemainingSteps; + return progressSteps >= STAGE_THRESHOLDS[1] && progressSteps < STAGE_THRESHOLDS[2]; + } + + boolean isEdgeEnrollmentStage() { + if (mTotalSteps == -1 || mRemainingSteps == -1) { return false; } - final int stepsEnrolled = mTotalSteps - mRemainingSteps; - return stepsEnrolled >= NUM_CENTER_TOUCHES; + return mTotalSteps - mRemainingSteps >= STAGE_THRESHOLDS[2]; } @NonNull PointF getNextGuidedEnrollmentPoint() { - if (mAccessibilityEnabled) { + if (mAccessibilityEnabled || !isGuidedEnrollmentStage()) { return new PointF(0f, 0f); } @@ -174,13 +232,14 @@ public class UdfpsEnrollHelper { SCALE_OVERRIDE, SCALE, UserHandle.USER_CURRENT); } - final int index = mLocationsEnrolled - NUM_CENTER_TOUCHES; + final int index = mLocationsEnrolled - mCenterTouchCount; final PointF originalPoint = mGuidedEnrollmentPoints .get(index % mGuidedEnrollmentPoints.size()); return new PointF(originalPoint.x * scale, originalPoint.y * scale); } void animateIfLastStep() { + Log.d(TAG, "animateIfLastStep: mRemainingSteps = " + mRemainingSteps); if (mListener == null) { Log.e(TAG, "animateIfLastStep, null listener"); return; diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsEnrollProgressBarDrawable.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsEnrollProgressBarDrawable.java index 4195009937c2..b56543f4851b 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsEnrollProgressBarDrawable.java +++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsEnrollProgressBarDrawable.java @@ -16,148 +16,107 @@ package com.android.systemui.biometrics; -import android.animation.ValueAnimator; import android.content.Context; -import android.content.res.TypedArray; import android.graphics.Canvas; import android.graphics.ColorFilter; -import android.graphics.Paint; import android.graphics.drawable.Drawable; import android.util.Log; -import android.util.TypedValue; import androidx.annotation.NonNull; import androidx.annotation.Nullable; -import com.android.systemui.R; +import java.util.ArrayList; +import java.util.List; /** * UDFPS enrollment progress bar. */ public class UdfpsEnrollProgressBarDrawable extends Drawable { + private static final String TAG = "UdfpsProgressBar"; - private static final String TAG = "UdfpsEnrollProgressBarDrawable"; - - private static final float PROGRESS_BAR_THICKNESS_DP = 12; - - @NonNull private final Context mContext; - @NonNull private final UdfpsEnrollDrawable mParent; - @NonNull private final Paint mBackgroundCirclePaint; - @NonNull private final Paint mProgressPaint; - - @Nullable private ValueAnimator mProgressAnimator; - private float mProgress; - private int mRotation; // After last step, rotate the progress bar once - private boolean mLastStepAcquired; - - public UdfpsEnrollProgressBarDrawable(@NonNull Context context, - @NonNull UdfpsEnrollDrawable parent) { - mContext = context; - mParent = parent; - - mBackgroundCirclePaint = new Paint(); - mBackgroundCirclePaint.setStrokeWidth(Utils.dpToPixels(context, PROGRESS_BAR_THICKNESS_DP)); - mBackgroundCirclePaint.setColor(context.getColor(R.color.white_disabled)); - mBackgroundCirclePaint.setAntiAlias(true); - mBackgroundCirclePaint.setStyle(Paint.Style.STROKE); - - // Background circle color + alpha - TypedArray tc = context.obtainStyledAttributes( - new int[] {android.R.attr.colorControlNormal}); - int tintColor = tc.getColor(0, mBackgroundCirclePaint.getColor()); - mBackgroundCirclePaint.setColor(tintColor); - tc.recycle(); - TypedValue alpha = new TypedValue(); - context.getTheme().resolveAttribute(android.R.attr.disabledAlpha, alpha, true); - mBackgroundCirclePaint.setAlpha((int) (alpha.getFloat() * 255)); - - // Progress should not be color extracted - mProgressPaint = new Paint(); - mProgressPaint.setStrokeWidth(Utils.dpToPixels(context, PROGRESS_BAR_THICKNESS_DP)); - mProgressPaint.setColor(context.getColor(R.color.udfps_enroll_progress)); - mProgressPaint.setAntiAlias(true); - mProgressPaint.setStyle(Paint.Style.STROKE); - mProgressPaint.setStrokeCap(Paint.Cap.ROUND); - } + private static final float SEGMENT_GAP_ANGLE = 12f; - void setEnrollmentProgress(int remaining, int totalSteps) { - // Add one so that the first steps actually changes progress, but also so that the last - // step ends at 1.0 - final float progress = (totalSteps - remaining + 1) / (float) (totalSteps + 1); - setEnrollmentProgress(progress); - } + @NonNull private final List<UdfpsEnrollProgressBarSegment> mSegments; - private void setEnrollmentProgress(float progress) { - if (mLastStepAcquired) { - return; + public UdfpsEnrollProgressBarDrawable(@NonNull Context context) { + mSegments = new ArrayList<>(UdfpsEnrollHelper.ENROLL_STAGE_COUNT); + float startAngle = SEGMENT_GAP_ANGLE / 2f; + final float sweepAngle = (360f / UdfpsEnrollHelper.ENROLL_STAGE_COUNT) - SEGMENT_GAP_ANGLE; + final Runnable invalidateRunnable = this::invalidateSelf; + for (int index = 0; index < UdfpsEnrollHelper.ENROLL_STAGE_COUNT; index++) { + mSegments.add(new UdfpsEnrollProgressBarSegment(context, getBounds(), startAngle, + sweepAngle, SEGMENT_GAP_ANGLE, invalidateRunnable)); + startAngle += sweepAngle + SEGMENT_GAP_ANGLE; } + } - long animationDuration = 150; - - if (progress == 1.f) { - animationDuration = 400; - final ValueAnimator rotationAnimator = ValueAnimator.ofInt(0, 400); - rotationAnimator.setDuration(animationDuration); - rotationAnimator.addUpdateListener(animation -> { - Log.d(TAG, "Rotation: " + mRotation); - mRotation = (int) animation.getAnimatedValue(); - mParent.invalidateSelf(); - }); - rotationAnimator.start(); + void setEnrollmentProgress(int remaining, int totalSteps) { + if (remaining == totalSteps) { + // Show some progress for the initial touch. + setEnrollmentProgress(1); + } else { + setEnrollmentProgress(totalSteps - remaining); } + } - if (mProgressAnimator != null && mProgressAnimator.isRunning()) { - mProgressAnimator.cancel(); + private void setEnrollmentProgress(int progressSteps) { + Log.d(TAG, "setEnrollmentProgress: progressSteps = " + progressSteps); + + int segmentIndex = 0; + int prevThreshold = 0; + while (segmentIndex < mSegments.size()) { + final UdfpsEnrollProgressBarSegment segment = mSegments.get(segmentIndex); + final int threshold = UdfpsEnrollHelper.getStageThreshold(segmentIndex); + + if (progressSteps >= threshold && !segment.isFilledOrFilling()) { + Log.d(TAG, "setEnrollmentProgress: segment[" + segmentIndex + "] complete"); + segment.updateProgress(1f); + break; + } else if (progressSteps >= prevThreshold && progressSteps < threshold) { + final int relativeSteps = progressSteps - prevThreshold; + final int relativeThreshold = threshold - prevThreshold; + final float segmentProgress = (float) relativeSteps / (float) relativeThreshold; + Log.d(TAG, "setEnrollmentProgress: segment[" + segmentIndex + "] progress = " + + segmentProgress); + segment.updateProgress(segmentProgress); + break; + } + + segmentIndex++; + prevThreshold = threshold; } - mProgressAnimator = ValueAnimator.ofFloat(mProgress, progress); - mProgressAnimator.setDuration(animationDuration); - mProgressAnimator.addUpdateListener(animation -> { - mProgress = (float) animation.getAnimatedValue(); - // Use the parent to invalidate, since it's the one that's attached as the view's - // drawable and has its callback set automatically. Invalidating via - // `this.invalidateSelf` actually does not invoke draw(), since this drawable's callback - // is not really set. - mParent.invalidateSelf(); - }); - mProgressAnimator.start(); + if (progressSteps >= UdfpsEnrollHelper.getLastStageThreshold()) { + Log.d(TAG, "setEnrollmentProgress: startCompletionAnimation"); + for (final UdfpsEnrollProgressBarSegment segment : mSegments) { + segment.startCompletionAnimation(); + } + } else { + Log.d(TAG, "setEnrollmentProgress: cancelCompletionAnimation"); + for (final UdfpsEnrollProgressBarSegment segment : mSegments) { + segment.cancelCompletionAnimation(); + } + } } void onLastStepAcquired() { - setEnrollmentProgress(1.f); - mLastStepAcquired = true; + Log.d(TAG, "setEnrollmentProgress: onLastStepAcquired"); + setEnrollmentProgress(UdfpsEnrollHelper.getLastStageThreshold()); } @Override public void draw(@NonNull Canvas canvas) { + Log.d(TAG, "setEnrollmentProgress: draw"); + canvas.save(); // Progress starts from the top, instead of the right - canvas.rotate(-90 + mRotation, getBounds().centerX(), getBounds().centerY()); - - // Progress bar "background track" - final float halfPaddingPx = Utils.dpToPixels(mContext, PROGRESS_BAR_THICKNESS_DP) / 2; - canvas.drawArc(halfPaddingPx, - halfPaddingPx, - getBounds().right - halfPaddingPx, - getBounds().bottom - halfPaddingPx, - 0, - 360, - false, - mBackgroundCirclePaint - ); - - final float progress = 360.f * mProgress; - // Progress - canvas.drawArc(halfPaddingPx, - halfPaddingPx, - getBounds().right - halfPaddingPx, - getBounds().bottom - halfPaddingPx, - 0, - progress, - false, - mProgressPaint - ); + canvas.rotate(-90f, getBounds().centerX(), getBounds().centerY()); + + // Draw each of the enroll segments. + for (final UdfpsEnrollProgressBarSegment segment : mSegments) { + segment.draw(canvas); + } canvas.restore(); } diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsEnrollProgressBarSegment.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsEnrollProgressBarSegment.java new file mode 100644 index 000000000000..5f24380b6ce3 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsEnrollProgressBarSegment.java @@ -0,0 +1,256 @@ +/* + * 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.Context; +import android.content.res.TypedArray; +import android.graphics.Canvas; +import android.graphics.Paint; +import android.graphics.Rect; +import android.os.Handler; +import android.os.Looper; +import android.util.Log; +import android.util.TypedValue; + +import androidx.annotation.ColorInt; +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; + +import com.android.systemui.R; + +/** + * A single segment of the UDFPS enrollment progress bar. + */ +public class UdfpsEnrollProgressBarSegment { + private static final String TAG = "UdfpsProgressBarSegment"; + + private static final long PROGRESS_ANIMATION_DURATION_MS = 400L; + private static final long OVER_SWEEP_ANIMATION_DELAY_MS = 200L; + private static final long OVER_SWEEP_ANIMATION_DURATION_MS = 200L; + + private static final float STROKE_WIDTH_DP = 12f; + + private final Handler mHandler = new Handler(Looper.getMainLooper()); + + @NonNull private final Rect mBounds; + @NonNull private final Runnable mInvalidateRunnable; + private final float mStartAngle; + private final float mSweepAngle; + private final float mMaxOverSweepAngle; + private final float mStrokeWidthPx; + + @NonNull private final Paint mBackgroundPaint; + @NonNull private final Paint mProgressPaint; + + private boolean mIsFilledOrFilling = false; + + private float mProgress = 0f; + @Nullable private ValueAnimator mProgressAnimator; + @NonNull private final ValueAnimator.AnimatorUpdateListener mProgressUpdateListener; + + private float mOverSweepAngle = 0f; + @Nullable private ValueAnimator mOverSweepAnimator; + @Nullable private ValueAnimator mOverSweepReverseAnimator; + @NonNull private final ValueAnimator.AnimatorUpdateListener mOverSweepUpdateListener; + @NonNull private final Runnable mOverSweepAnimationRunnable; + + public UdfpsEnrollProgressBarSegment(@NonNull Context context, @NonNull Rect bounds, + float startAngle, float sweepAngle, float maxOverSweepAngle, + @NonNull Runnable invalidateRunnable) { + + mBounds = bounds; + mInvalidateRunnable = invalidateRunnable; + mStartAngle = startAngle; + mSweepAngle = sweepAngle; + mMaxOverSweepAngle = maxOverSweepAngle; + mStrokeWidthPx = Utils.dpToPixels(context, STROKE_WIDTH_DP); + + mBackgroundPaint = new Paint(); + mBackgroundPaint.setStrokeWidth(mStrokeWidthPx); + mBackgroundPaint.setColor(context.getColor(R.color.white_disabled)); + mBackgroundPaint.setAntiAlias(true); + mBackgroundPaint.setStyle(Paint.Style.STROKE); + mBackgroundPaint.setStrokeCap(Paint.Cap.ROUND); + + // Background paint color + alpha + final int[] attrs = new int[] {android.R.attr.colorControlNormal}; + final TypedArray ta = context.obtainStyledAttributes(attrs); + @ColorInt final int tintColor = ta.getColor(0, mBackgroundPaint.getColor()); + mBackgroundPaint.setColor(tintColor); + ta.recycle(); + TypedValue alpha = new TypedValue(); + context.getTheme().resolveAttribute(android.R.attr.disabledAlpha, alpha, true); + mBackgroundPaint.setAlpha((int) (alpha.getFloat() * 255f)); + + // Progress should not be color extracted + mProgressPaint = new Paint(); + mProgressPaint.setStrokeWidth(mStrokeWidthPx); + mProgressPaint.setColor(context.getColor(R.color.udfps_enroll_progress)); + mProgressPaint.setAntiAlias(true); + mProgressPaint.setStyle(Paint.Style.STROKE); + mProgressPaint.setStrokeCap(Paint.Cap.ROUND); + + mProgressUpdateListener = animation -> { + mProgress = (float) animation.getAnimatedValue(); + mInvalidateRunnable.run(); + }; + + mOverSweepUpdateListener = animation -> { + mOverSweepAngle = (float) animation.getAnimatedValue(); + mInvalidateRunnable.run(); + }; + mOverSweepAnimationRunnable = () -> { + if (mOverSweepAnimator != null && mOverSweepAnimator.isRunning()) { + mOverSweepAnimator.cancel(); + } + mOverSweepAnimator = ValueAnimator.ofFloat(mOverSweepAngle, mMaxOverSweepAngle); + mOverSweepAnimator.setDuration(OVER_SWEEP_ANIMATION_DURATION_MS); + mOverSweepAnimator.addUpdateListener(mOverSweepUpdateListener); + mOverSweepAnimator.start(); + }; + } + + /** + * Draws this segment to the given canvas. + */ + public void draw(@NonNull Canvas canvas) { + Log.d(TAG, "draw: mProgress = " + mProgress); + + final float halfPaddingPx = mStrokeWidthPx / 2f; + + if (mProgress < 1f) { + // Draw the unfilled background color of the segment. + canvas.drawArc( + halfPaddingPx, + halfPaddingPx, + mBounds.right - halfPaddingPx, + mBounds.bottom - halfPaddingPx, + mStartAngle, + mSweepAngle, + false /* useCenter */, + mBackgroundPaint); + } + + if (mProgress > 0f) { + // Draw the filled progress portion of the segment. + canvas.drawArc( + halfPaddingPx, + halfPaddingPx, + mBounds.right - halfPaddingPx, + mBounds.bottom - halfPaddingPx, + mStartAngle, + mSweepAngle * mProgress + mOverSweepAngle, + false /* useCenter */, + mProgressPaint); + } + } + + /** + * @return Whether this segment is filled or in the process of being filled. + */ + public boolean isFilledOrFilling() { + return mIsFilledOrFilling; + } + + /** + * Updates the fill progress of this segment, animating if necessary. + * + * @param progress The new fill progress, in the range [0, 1]. + */ + public void updateProgress(float progress) { + updateProgress(progress, PROGRESS_ANIMATION_DURATION_MS); + } + + private void updateProgress(float progress, long animationDurationMs) { + Log.d(TAG, "updateProgress: progress = " + progress + + ", duration = " + animationDurationMs); + + if (mProgress == progress) { + Log.d(TAG, "updateProgress skipped: progress == mProgress"); + return; + } + + mIsFilledOrFilling = progress >= 1f; + + if (mProgressAnimator != null && mProgressAnimator.isRunning()) { + mProgressAnimator.cancel(); + } + + mProgressAnimator = ValueAnimator.ofFloat(mProgress, progress); + mProgressAnimator.setDuration(animationDurationMs); + mProgressAnimator.addUpdateListener(mProgressUpdateListener); + mProgressAnimator.start(); + } + + /** + * Queues and runs the completion animation for this segment. + */ + public void startCompletionAnimation() { + final boolean hasCallback = mHandler.hasCallbacks(mOverSweepAnimationRunnable); + if (hasCallback || mOverSweepAngle >= mMaxOverSweepAngle) { + Log.d(TAG, "startCompletionAnimation skipped: hasCallback = " + hasCallback + + ", mOverSweepAngle = " + mOverSweepAngle); + return; + } + + Log.d(TAG, "startCompletionAnimation: mProgress = " + mProgress + + ", mOverSweepAngle = " + mOverSweepAngle); + + // Reset sweep angle back to zero if the animation is being rolled back. + if (mOverSweepReverseAnimator != null && mOverSweepReverseAnimator.isRunning()) { + mOverSweepReverseAnimator.cancel(); + mOverSweepAngle = 0f; + } + + // Start filling the segment if it isn't already. + if (mProgress < 1f) { + updateProgress(1f, OVER_SWEEP_ANIMATION_DELAY_MS); + } + + // Queue the animation to run after fill completes. + mHandler.postDelayed(mOverSweepAnimationRunnable, OVER_SWEEP_ANIMATION_DELAY_MS); + } + + /** + * Cancels (and reverses, if necessary) a queued or running completion animation. + */ + public void cancelCompletionAnimation() { + Log.d(TAG, "cancelCompletionAnimation: mProgress = " + mProgress + + ", mOverSweepAngle = " + mOverSweepAngle); + + // Cancel the animation if it's queued or running. + mHandler.removeCallbacks(mOverSweepAnimationRunnable); + if (mOverSweepAnimator != null && mOverSweepAnimator.isRunning()) { + mOverSweepAnimator.cancel(); + } + + // Roll back the animation if it has at least partially run. + if (mOverSweepAngle > 0f) { + if (mOverSweepReverseAnimator != null && mOverSweepReverseAnimator.isRunning()) { + mOverSweepReverseAnimator.cancel(); + } + + final float completion = mOverSweepAngle / mMaxOverSweepAngle; + final long proratedDuration = (long) (OVER_SWEEP_ANIMATION_DURATION_MS * completion); + mOverSweepReverseAnimator = ValueAnimator.ofFloat(mOverSweepAngle, 0f); + mOverSweepReverseAnimator.setDuration(proratedDuration); + mOverSweepReverseAnimator.addUpdateListener(mOverSweepUpdateListener); + mOverSweepReverseAnimator.start(); + } + } +} diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsEnrollView.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsEnrollView.java index 2cdf49d6fc3c..6f02c64e4cf7 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsEnrollView.java +++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsEnrollView.java @@ -17,9 +17,12 @@ package com.android.systemui.biometrics; import android.content.Context; +import android.hardware.fingerprint.FingerprintSensorPropertiesInternal; import android.os.Handler; import android.os.Looper; import android.util.AttributeSet; +import android.view.View; +import android.view.ViewGroup; import android.widget.ImageView; import androidx.annotation.NonNull; @@ -32,20 +35,25 @@ import com.android.systemui.R; */ public class UdfpsEnrollView extends UdfpsAnimationView { @NonNull private final UdfpsEnrollDrawable mFingerprintDrawable; + @NonNull private final UdfpsEnrollProgressBarDrawable mFingerprintProgressDrawable; @NonNull private final Handler mHandler; @NonNull private ImageView mFingerprintView; + @NonNull private ImageView mFingerprintProgressView; public UdfpsEnrollView(Context context, @Nullable AttributeSet attrs) { super(context, attrs); mFingerprintDrawable = new UdfpsEnrollDrawable(mContext); + mFingerprintProgressDrawable = new UdfpsEnrollProgressBarDrawable(context); mHandler = new Handler(Looper.getMainLooper()); } @Override protected void onFinishInflate() { mFingerprintView = findViewById(R.id.udfps_enroll_animation_fp_view); + mFingerprintProgressView = findViewById(R.id.udfps_enroll_animation_fp_progress_view); mFingerprintView.setImageDrawable(mFingerprintDrawable); + mFingerprintProgressView.setImageDrawable(mFingerprintProgressDrawable); } @Override @@ -53,15 +61,36 @@ public class UdfpsEnrollView extends UdfpsAnimationView { return mFingerprintDrawable; } + void updateSensorLocation(@NonNull FingerprintSensorPropertiesInternal sensorProps) { + View fingerprintAccessibilityView = findViewById(R.id.udfps_enroll_accessibility_view); + final int sensorHeight = sensorProps.sensorRadius * 2; + final int sensorWidth = sensorHeight; + ViewGroup.LayoutParams params = fingerprintAccessibilityView.getLayoutParams(); + params.width = sensorWidth; + params.height = sensorHeight; + fingerprintAccessibilityView.setLayoutParams(params); + fingerprintAccessibilityView.requestLayout(); + } + void setEnrollHelper(UdfpsEnrollHelper enrollHelper) { mFingerprintDrawable.setEnrollHelper(enrollHelper); } void onEnrollmentProgress(int remaining, int totalSteps) { - mHandler.post(() -> mFingerprintDrawable.onEnrollmentProgress(remaining, totalSteps)); + mHandler.post(() -> { + mFingerprintProgressDrawable.setEnrollmentProgress(remaining, totalSteps); + mFingerprintDrawable.onEnrollmentProgress(remaining, totalSteps); + }); + } + + void onEnrollmentHelp(int remaining, int totalSteps) { + mHandler.post( + () -> mFingerprintProgressDrawable.setEnrollmentProgress(remaining, totalSteps)); } void onLastStepAcquired() { - mHandler.post(mFingerprintDrawable::onLastStepAcquired); + mHandler.post(() -> { + mFingerprintProgressDrawable.onLastStepAcquired(); + }); } } diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsEnrollViewController.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsEnrollViewController.java index 3dab010d917c..6cdd1c8b0d4e 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsEnrollViewController.java +++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsEnrollViewController.java @@ -33,16 +33,21 @@ public class UdfpsEnrollViewController extends UdfpsAnimationViewController<Udfp @NonNull private final UdfpsEnrollHelper mEnrollHelper; @NonNull private final UdfpsEnrollHelper.Listener mEnrollHelperListener = new UdfpsEnrollHelper.Listener() { - @Override - public void onEnrollmentProgress(int remaining, int totalSteps) { - mView.onEnrollmentProgress(remaining, totalSteps); - } + @Override + public void onEnrollmentProgress(int remaining, int totalSteps) { + mView.onEnrollmentProgress(remaining, totalSteps); + } - @Override - public void onLastStepAcquired() { - mView.onLastStepAcquired(); - } - }; + @Override + public void onEnrollmentHelp(int remaining, int totalSteps) { + mView.onEnrollmentHelp(remaining, totalSteps); + } + + @Override + public void onLastStepAcquired() { + mView.onLastStepAcquired(); + } + }; protected UdfpsEnrollViewController( @NonNull UdfpsEnrollView view, @@ -74,7 +79,7 @@ public class UdfpsEnrollViewController extends UdfpsAnimationViewController<Udfp @NonNull @Override public PointF getTouchTranslation() { - if (!mEnrollHelper.isCenterEnrollmentComplete()) { + if (!mEnrollHelper.isGuidedEnrollmentStage()) { return new PointF(0, 0); } else { return mEnrollHelper.getNextGuidedEnrollmentPoint(); diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardView.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardView.java index d46426a03621..9015396d26ab 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardView.java +++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardView.java @@ -155,6 +155,13 @@ public class UdfpsKeyguardView extends UdfpsAnimationView { updateAlpha(); } + /** + * @return alpha between 0 and 255 + */ + int getUnpausedAlpha() { + return mAlpha; + } + @Override protected int updateAlpha() { int alpha = super.updateAlpha(); diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardViewController.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardViewController.java index 4896305daa2e..888672316c8a 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardViewController.java +++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardViewController.java @@ -34,12 +34,12 @@ import com.android.systemui.statusbar.phone.KeyguardBouncer; import com.android.systemui.statusbar.phone.StatusBar; import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager; import com.android.systemui.statusbar.policy.ConfigurationController; +import com.android.systemui.statusbar.policy.KeyguardStateController; import com.android.systemui.util.concurrency.DelayableExecutor; import java.io.FileDescriptor; import java.io.PrintWriter; - /** * Class that coordinates non-HBM animations during keyguard authentication. */ @@ -50,6 +50,7 @@ public class UdfpsKeyguardViewController extends UdfpsAnimationViewController<Ud @NonNull private final KeyguardViewMediator mKeyguardViewMediator; @NonNull private final LockscreenShadeTransitionController mLockScreenShadeTransitionController; @NonNull private final ConfigurationController mConfigurationController; + @NonNull private final KeyguardStateController mKeyguardStateController; @NonNull private final UdfpsController mUdfpsController; private boolean mShowingUdfpsBouncer; @@ -60,6 +61,9 @@ public class UdfpsKeyguardViewController extends UdfpsAnimationViewController<Ud private float mTransitionToFullShadeProgress; private float mLastDozeAmount; + private float mStatusBarExpansion; + private boolean mLaunchTransitionFadingAway; + /** * hidden amount of pin/pattern/password bouncer * {@link KeyguardBouncer#EXPANSION_VISIBLE} (0f) to @@ -79,6 +83,7 @@ public class UdfpsKeyguardViewController extends UdfpsAnimationViewController<Ud @NonNull KeyguardViewMediator keyguardViewMediator, @NonNull LockscreenShadeTransitionController transitionController, @NonNull ConfigurationController configurationController, + @NonNull KeyguardStateController keyguardStateController, @NonNull UdfpsController udfpsController) { super(view, statusBarStateController, statusBar, dumpManager); mKeyguardViewManager = statusBarKeyguardViewManager; @@ -87,6 +92,7 @@ public class UdfpsKeyguardViewController extends UdfpsAnimationViewController<Ud mKeyguardViewMediator = keyguardViewMediator; mLockScreenShadeTransitionController = transitionController; mConfigurationController = configurationController; + mKeyguardStateController = keyguardStateController; mUdfpsController = udfpsController; } @@ -105,11 +111,14 @@ public class UdfpsKeyguardViewController extends UdfpsAnimationViewController<Ud mUdfpsRequested = false; + mLaunchTransitionFadingAway = mKeyguardStateController.isLaunchTransitionFadingAway(); + mKeyguardStateController.addCallback(mKeyguardStateControllerCallback); mStatusBarState = mStatusBarStateController.getState(); mQsExpanded = mKeyguardViewManager.isQsExpanded(); mInputBouncerHiddenAmount = KeyguardBouncer.EXPANSION_HIDDEN; mIsBouncerVisible = mKeyguardViewManager.bouncerIsOrWillBeShowing(); mConfigurationController.addCallback(mConfigurationListener); + mStatusBar.addExpansionChangedListener(mStatusBarExpansionChangedListener); updateAlpha(); updatePauseAuth(); @@ -122,10 +131,12 @@ public class UdfpsKeyguardViewController extends UdfpsAnimationViewController<Ud super.onViewDetached(); mFaceDetectRunning = false; + mKeyguardStateController.removeCallback(mKeyguardStateControllerCallback); mStatusBarStateController.removeCallback(mStateListener); mKeyguardViewManager.removeAlternateAuthInterceptor(mAlternateAuthInterceptor); mKeyguardUpdateMonitor.requestFaceAuthOnOccludingApp(false); mConfigurationController.removeCallback(mConfigurationListener); + mStatusBar.removeExpansionChangedListener(mStatusBarExpansionChangedListener); if (mLockScreenShadeTransitionController.getUdfpsKeyguardViewController() == this) { mLockScreenShadeTransitionController.setUdfpsKeyguardViewController(null); } @@ -140,9 +151,11 @@ public class UdfpsKeyguardViewController extends UdfpsAnimationViewController<Ud pw.println("mQsExpanded=" + mQsExpanded); pw.println("mIsBouncerVisible=" + mIsBouncerVisible); pw.println("mInputBouncerHiddenAmount=" + mInputBouncerHiddenAmount); - pw.println("mAlpha=" + mView.getAlpha()); + pw.println("mStatusBarExpansion=" + mStatusBarExpansion); + pw.println("unpausedAlpha=" + mView.getUnpausedAlpha()); pw.println("mUdfpsRequested=" + mUdfpsRequested); pw.println("mView.mUdfpsRequested=" + mView.mUdfpsRequested); + pw.println("mLaunchTransitionFadingAway=" + mLaunchTransitionFadingAway); } /** @@ -154,10 +167,10 @@ public class UdfpsKeyguardViewController extends UdfpsAnimationViewController<Ud return false; } + boolean udfpsAffordanceWasNotShowing = shouldPauseAuth(); mShowingUdfpsBouncer = show; - updatePauseAuth(); if (mShowingUdfpsBouncer) { - if (mStatusBarState == StatusBarState.SHADE_LOCKED) { + if (udfpsAffordanceWasNotShowing) { mView.animateInUdfpsBouncer(null); } @@ -170,6 +183,8 @@ public class UdfpsKeyguardViewController extends UdfpsAnimationViewController<Ud } else { mKeyguardUpdateMonitor.requestFaceAuthOnOccludingApp(false); } + updateAlpha(); + updatePauseAuth(); return true; } @@ -189,6 +204,10 @@ public class UdfpsKeyguardViewController extends UdfpsAnimationViewController<Ud return false; } + if (mLaunchTransitionFadingAway) { + return true; + } + if (mStatusBarState != KEYGUARD) { return true; } @@ -237,12 +256,17 @@ public class UdfpsKeyguardViewController extends UdfpsAnimationViewController<Ud } private void updateAlpha() { - // fade icon on transition to showing bouncer + // fade icon on transitions to showing the status bar, 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 : mStatusBarExpansion; int alpha = mShowingUdfpsBouncer ? 255 : (int) MathUtils.constrain( - MathUtils.map(.5f, .9f, 0f, 255f, mInputBouncerHiddenAmount), + MathUtils.map(.5f, .9f, 0f, 255f, expansion), 0f, 255f); - alpha *= (1.0f - mTransitionToFullShadeProgress); + if (!mShowingUdfpsBouncer) { + alpha *= (1.0f - mTransitionToFullShadeProgress); + } mView.setUnpausedAlpha(alpha); } @@ -287,6 +311,7 @@ public class UdfpsKeyguardViewController extends UdfpsAnimationViewController<Ud public void requestUdfps(boolean request, int color) { mUdfpsRequested = request; mView.requestUdfps(request, color); + updateAlpha(); updatePauseAuth(); } @@ -356,4 +381,23 @@ public class UdfpsKeyguardViewController extends UdfpsAnimationViewController<Ud mView.updateColor(); } }; + + private final StatusBar.ExpansionChangedListener mStatusBarExpansionChangedListener = + new StatusBar.ExpansionChangedListener() { + @Override + public void onExpansionChanged(float expansion, boolean expanded) { + mStatusBarExpansion = expansion; + updateAlpha(); + } + }; + + private final KeyguardStateController.Callback mKeyguardStateControllerCallback = + new KeyguardStateController.Callback() { + @Override + public void onLaunchTransitionFadingAwayChanged() { + mLaunchTransitionFadingAway = + mKeyguardStateController.isLaunchTransitionFadingAway(); + updatePauseAuth(); + } + }; } diff --git a/packages/SystemUI/src/com/android/systemui/classifier/ZigZagClassifier.java b/packages/SystemUI/src/com/android/systemui/classifier/ZigZagClassifier.java index e1349f2aba6d..40c28fab51b8 100644 --- a/packages/SystemUI/src/com/android/systemui/classifier/ZigZagClassifier.java +++ b/packages/SystemUI/src/com/android/systemui/classifier/ZigZagClassifier.java @@ -21,6 +21,7 @@ import static com.android.internal.config.sysui.SystemUiDeviceConfigFlags.BRIGHT import static com.android.internal.config.sysui.SystemUiDeviceConfigFlags.BRIGHTLINE_FALSING_ZIGZAG_Y_PRIMARY_DEVIANCE; import static com.android.internal.config.sysui.SystemUiDeviceConfigFlags.BRIGHTLINE_FALSING_ZIGZAG_Y_SECONDARY_DEVIANCE; import static com.android.systemui.classifier.Classifier.BRIGHTNESS_SLIDER; +import static com.android.systemui.classifier.Classifier.LOCK_ICON; import static com.android.systemui.classifier.Classifier.SHADE_DRAG; import android.graphics.Point; @@ -89,7 +90,9 @@ class ZigZagClassifier extends FalsingClassifier { Result calculateFalsingResult( @Classifier.InteractionType int interactionType, double historyBelief, double historyConfidence) { - if (interactionType == BRIGHTNESS_SLIDER || interactionType == SHADE_DRAG) { + if (interactionType == BRIGHTNESS_SLIDER + || interactionType == SHADE_DRAG + || interactionType == LOCK_ICON) { return Result.passed(0); } diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeLog.java b/packages/SystemUI/src/com/android/systemui/doze/DozeLog.java index 41964652ac49..657d9246be8f 100644 --- a/packages/SystemUI/src/com/android/systemui/doze/DozeLog.java +++ b/packages/SystemUI/src/com/android/systemui/doze/DozeLog.java @@ -214,6 +214,14 @@ public class DozeLog implements Dumpable { } /** + * Appends display state delayed by UDFPS event to the logs + * @param delayedDisplayState the display screen state that was delayed + */ + public void traceDisplayStateDelayedByUdfps(int delayedDisplayState) { + mLogger.logDisplayStateDelayedByUdfps(delayedDisplayState); + } + + /** * Appends display state changed event to the logs * @param displayState new DozeMachine state */ diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeLogger.kt b/packages/SystemUI/src/com/android/systemui/doze/DozeLogger.kt index 9bc74be9b9c3..fe37d49980b2 100644 --- a/packages/SystemUI/src/com/android/systemui/doze/DozeLogger.kt +++ b/packages/SystemUI/src/com/android/systemui/doze/DozeLogger.kt @@ -160,6 +160,14 @@ class DozeLogger @Inject constructor( }) } + fun logDisplayStateDelayedByUdfps(delayedDisplayState: Int) { + buffer.log(TAG, INFO, { + str1 = Display.stateToString(delayedDisplayState) + }, { + "Delaying display state change to: $str1 due to UDFPS activity" + }) + } + fun logDisplayStateChanged(displayState: Int) { buffer.log(TAG, INFO, { str1 = Display.stateToString(displayState) diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeScreenBrightness.java b/packages/SystemUI/src/com/android/systemui/doze/DozeScreenBrightness.java index 470d2f364c1c..8d4ac75a0748 100644 --- a/packages/SystemUI/src/com/android/systemui/doze/DozeScreenBrightness.java +++ b/packages/SystemUI/src/com/android/systemui/doze/DozeScreenBrightness.java @@ -36,6 +36,7 @@ import com.android.systemui.doze.dagger.DozeScope; import com.android.systemui.doze.dagger.WrappedService; import com.android.systemui.keyguard.WakefulnessLifecycle; import com.android.systemui.statusbar.phone.DozeParameters; +import com.android.systemui.statusbar.phone.UnlockedScreenOffAnimationController; import com.android.systemui.util.sensors.AsyncSensorManager; import java.io.PrintWriter; @@ -55,6 +56,16 @@ public class DozeScreenBrightness extends BroadcastReceiver implements DozeMachi "com.android.systemui.doze.AOD_BRIGHTNESS"; protected static final String BRIGHTNESS_BUCKET = "brightness_bucket"; + /** + * Just before the screen times out from user inactivity, DisplayPowerController dims the screen + * brightness to the lower of {@link #mScreenBrightnessDim}, or the current brightness minus + * DisplayPowerController#SCREEN_DIM_MINIMUM_REDUCTION_FLOAT. + * + * This value is 0.04f * 255, which converts SCREEN_DIM_MINIMUM_REDUCTION_FLOAT to the integer + * brightness values used by doze. + */ + private static final int SCREEN_DIM_MINIMUM_REDUCTION_INT = 10; + private final Context mContext; private final DozeMachine.Service mDozeService; private final DozeHost mDozeHost; @@ -81,13 +92,16 @@ public class DozeScreenBrightness extends BroadcastReceiver implements DozeMachi */ private int mDebugBrightnessBucket = -1; + private UnlockedScreenOffAnimationController mUnlockedScreenOffAnimationController; + @Inject public DozeScreenBrightness(Context context, @WrappedService DozeMachine.Service service, AsyncSensorManager sensorManager, @BrightnessSensor Optional<Sensor> lightSensorOptional, DozeHost host, Handler handler, AlwaysOnDisplayPolicy alwaysOnDisplayPolicy, WakefulnessLifecycle wakefulnessLifecycle, - DozeParameters dozeParameters) { + DozeParameters dozeParameters, + UnlockedScreenOffAnimationController unlockedScreenOffAnimationController) { mContext = context; mDozeService = service; mSensorManager = sensorManager; @@ -96,6 +110,7 @@ public class DozeScreenBrightness extends BroadcastReceiver implements DozeMachi mDozeParameters = dozeParameters; mDozeHost = host; mHandler = handler; + mUnlockedScreenOffAnimationController = unlockedScreenOffAnimationController; mDefaultDozeBrightness = alwaysOnDisplayPolicy.defaultDozeBrightness; mScreenBrightnessDim = alwaysOnDisplayPolicy.dimBrightness; @@ -146,14 +161,15 @@ public class DozeScreenBrightness extends BroadcastReceiver implements DozeMachi } } - private void updateBrightnessAndReady(boolean force) { + public void updateBrightnessAndReady(boolean force) { if (force || mRegistered || mDebugBrightnessBucket != -1) { int sensorValue = mDebugBrightnessBucket == -1 ? mLastSensorValue : mDebugBrightnessBucket; int brightness = computeBrightness(sensorValue); boolean brightnessReady = brightness > 0; if (brightnessReady) { - mDozeService.setDozeScreenBrightness(clampToUserSetting(brightness)); + mDozeService.setDozeScreenBrightness( + clampToDimBrightnessForScreenOff(clampToUserSetting(brightness))); } int scrimOpacity = -1; @@ -205,13 +221,21 @@ public class DozeScreenBrightness extends BroadcastReceiver implements DozeMachi /** * Clamp the brightness to the dim brightness value used by PowerManagerService just before the * device times out and goes to sleep, if we are sleeping from a timeout. This ensures that we - * don't raise the brightness back to the user setting before playing the screen off animation. + * don't raise the brightness back to the user setting before or during the screen off + * animation. */ private int clampToDimBrightnessForScreenOff(int brightness) { - if (mDozeParameters.shouldControlUnlockedScreenOff() + if (mUnlockedScreenOffAnimationController.isScreenOffAnimationPlaying() && mWakefulnessLifecycle.getLastSleepReason() == PowerManager.GO_TO_SLEEP_REASON_TIMEOUT) { - return Math.min(mScreenBrightnessDim, brightness); + return Math.max( + PowerManager.BRIGHTNESS_OFF, + // Use the lower of either the dim brightness, or the current brightness reduced + // by the minimum dim amount. This is the same logic used in + // DisplayPowerController#updatePowerState to apply a minimum dim amount. + Math.min( + brightness - SCREEN_DIM_MINIMUM_REDUCTION_INT, + mScreenBrightnessDim)); } else { return brightness; } diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeScreenState.java b/packages/SystemUI/src/com/android/systemui/doze/DozeScreenState.java index 8c50a16b566f..038be48b53ee 100644 --- a/packages/SystemUI/src/com/android/systemui/doze/DozeScreenState.java +++ b/packages/SystemUI/src/com/android/systemui/doze/DozeScreenState.java @@ -26,6 +26,11 @@ import android.os.Handler; import android.util.Log; import android.view.Display; +import androidx.annotation.Nullable; + +import com.android.keyguard.KeyguardUpdateMonitor; +import com.android.systemui.biometrics.AuthController; +import com.android.systemui.biometrics.UdfpsController; import com.android.systemui.dagger.qualifiers.Main; import com.android.systemui.doze.dagger.DozeScope; import com.android.systemui.doze.dagger.WrappedService; @@ -34,6 +39,7 @@ import com.android.systemui.util.wakelock.SettableWakeLock; import com.android.systemui.util.wakelock.WakeLock; import javax.inject.Inject; +import javax.inject.Provider; /** * Controls the screen when dozing. @@ -56,23 +62,64 @@ public class DozeScreenState implements DozeMachine.Part { */ public static final int ENTER_DOZE_HIDE_WALLPAPER_DELAY = 2500; + /** + * Add an extra delay to the transition to DOZE when udfps is current activated before + * the display state transitions from ON => DOZE. + */ + public static final int UDFPS_DISPLAY_STATE_DELAY = 1200; + private final DozeMachine.Service mDozeService; private final Handler mHandler; private final Runnable mApplyPendingScreenState = this::applyPendingScreenState; private final DozeParameters mParameters; private final DozeHost mDozeHost; + private final AuthController mAuthController; + private final Provider<UdfpsController> mUdfpsControllerProvider; + @Nullable private UdfpsController mUdfpsController; + private final DozeLog mDozeLog; + private final DozeScreenBrightness mDozeScreenBrightness; private int mPendingScreenState = Display.STATE_UNKNOWN; private SettableWakeLock mWakeLock; @Inject - public DozeScreenState(@WrappedService DozeMachine.Service service, @Main Handler handler, - DozeHost host, DozeParameters parameters, WakeLock wakeLock) { + public DozeScreenState( + @WrappedService DozeMachine.Service service, + @Main Handler handler, + DozeHost host, + DozeParameters parameters, + WakeLock wakeLock, + AuthController authController, + Provider<UdfpsController> udfpsControllerProvider, + DozeLog dozeLog, + DozeScreenBrightness dozeScreenBrightness) { mDozeService = service; mHandler = handler; mParameters = parameters; mDozeHost = host; mWakeLock = new SettableWakeLock(wakeLock, TAG); + mAuthController = authController; + mUdfpsControllerProvider = udfpsControllerProvider; + mDozeLog = dozeLog; + mDozeScreenBrightness = dozeScreenBrightness; + + updateUdfpsController(); + if (mUdfpsController == null) { + mAuthController.addCallback(new AuthController.Callback() { + @Override + public void onAllAuthenticatorsRegistered() { + updateUdfpsController(); + } + }); + } + } + + private void updateUdfpsController() { + if (mAuthController.isUdfpsEnrolled(KeyguardUpdateMonitor.getCurrentUser())) { + mUdfpsController = mUdfpsControllerProvider.get(); + } else { + mUdfpsController = null; + } } @Override @@ -110,21 +157,28 @@ public class DozeScreenState implements DozeMachine.Part { mPendingScreenState = screenState; // Delay screen state transitions even longer while animations are running. - boolean shouldDelayTransition = newState == DOZE_AOD + boolean shouldDelayTransitionEnteringDoze = newState == DOZE_AOD && mParameters.shouldControlScreenOff() && !turningOn; - if (shouldDelayTransition) { + // Delay screen state transition longer if UDFPS is actively authenticating a fp + boolean shouldDelayTransitionForUDFPS = newState == DOZE_AOD + && mUdfpsController != null && mUdfpsController.isFingerDown(); + + if (shouldDelayTransitionEnteringDoze || shouldDelayTransitionForUDFPS) { mWakeLock.setAcquired(true); } if (!messagePending) { if (DEBUG) { Log.d(TAG, "Display state changed to " + screenState + " delayed by " - + (shouldDelayTransition ? ENTER_DOZE_DELAY : 1)); + + (shouldDelayTransitionEnteringDoze ? ENTER_DOZE_DELAY : 1)); } - if (shouldDelayTransition) { + if (shouldDelayTransitionEnteringDoze) { mHandler.postDelayed(mApplyPendingScreenState, ENTER_DOZE_DELAY); + } else if (shouldDelayTransitionForUDFPS) { + mDozeLog.traceDisplayStateDelayedByUdfps(mPendingScreenState); + mHandler.postDelayed(mApplyPendingScreenState, UDFPS_DISPLAY_STATE_DELAY); } else { mHandler.post(mApplyPendingScreenState); } @@ -139,6 +193,12 @@ public class DozeScreenState implements DozeMachine.Part { } private void applyPendingScreenState() { + if (mUdfpsController != null && mUdfpsController.isFingerDown()) { + mDozeLog.traceDisplayStateDelayedByUdfps(mPendingScreenState); + mHandler.postDelayed(mApplyPendingScreenState, UDFPS_DISPLAY_STATE_DELAY); + return; + } + applyScreenState(mPendingScreenState); mPendingScreenState = Display.STATE_UNKNOWN; } @@ -147,6 +207,12 @@ public class DozeScreenState implements DozeMachine.Part { if (screenState != Display.STATE_UNKNOWN) { if (DEBUG) Log.d(TAG, "setDozeScreenState(" + screenState + ")"); mDozeService.setDozeScreenState(screenState); + if (screenState == Display.STATE_DOZE) { + // If we're entering doze, update the doze screen brightness. We might have been + // clamping it to the dim brightness during the screen off animation, and we should + // now change it to the brightness we actually want according to the sensor. + mDozeScreenBrightness.updateBrightnessAndReady(false /* force */); + } mPendingScreenState = Display.STATE_UNKNOWN; mWakeLock.setAcquired(false); } diff --git a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsImpl.java b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsImpl.java index a641ad4b338b..c4508e043c7d 100644 --- a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsImpl.java +++ b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsImpl.java @@ -97,7 +97,7 @@ public class GlobalActionsImpl implements GlobalActions, CommandQueue.Callbacks int backgroundAlpha = (int) (ScrimController.BUSY_SCRIM_ALPHA * 255); background.setAlpha(backgroundAlpha); mBlurUtils.applyBlur(d.getWindow().getDecorView().getViewRootImpl(), - mBlurUtils.blurRadiusOfRatio(1), backgroundAlpha == 255); + (int) mBlurUtils.blurRadiusOfRatio(1), backgroundAlpha == 255); } else { float backgroundAlpha = mContext.getResources().getFloat( com.android.systemui.R.dimen.shutdown_scrim_behind_alpha); diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java index ee3d40edc2eb..1a8af3bb650b 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java +++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java @@ -818,6 +818,7 @@ public class KeyguardViewMediator extends SystemUI implements Dumpable, private final KeyguardStateController mKeyguardStateController; private final Lazy<KeyguardUnlockAnimationController> mKeyguardUnlockAnimationControllerLazy; + private boolean mWallpaperSupportsAmbientMode; /** * Injected constructor. See {@link KeyguardModule}. @@ -2089,13 +2090,14 @@ public class KeyguardViewMediator extends SystemUI implements Dumpable, int flags = 0; if (mKeyguardViewControllerLazy.get().shouldDisableWindowAnimationsForUnlock() - || (mWakeAndUnlocking && !mPulsing) - || isAnimatingBetweenKeyguardAndSurfaceBehindOrWillBe()) { + || mWakeAndUnlocking && !mWallpaperSupportsAmbientMode) { flags |= WindowManagerPolicyConstants .KEYGUARD_GOING_AWAY_FLAG_NO_WINDOW_ANIMATIONS; } if (mKeyguardViewControllerLazy.get().isGoingToNotificationShade() - || (mWakeAndUnlocking && mPulsing)) { + || mWakeAndUnlocking && mWallpaperSupportsAmbientMode) { + // When the wallpaper supports ambient mode, the scrim isn't fully opaque during + // wake and unlock and we should fade in the app on top of the wallpaper flags |= WindowManagerPolicyConstants.KEYGUARD_GOING_AWAY_FLAG_TO_SHADE; } if (mKeyguardViewControllerLazy.get().isUnlockWithWallpaper()) { @@ -2784,6 +2786,15 @@ public class KeyguardViewMediator extends SystemUI implements Dumpable, mPulsing = pulsing; } + /** + * Set if the wallpaper supports ambient mode. This is used to trigger the right animation. + * In case it does support it, we have to fade in the incoming app, otherwise we'll reveal it + * with the light reveal scrim. + */ + public void setWallpaperSupportsAmbientMode(boolean supportsAmbientMode) { + mWallpaperSupportsAmbientMode = supportsAmbientMode; + } + private static class StartKeyguardExitAnimParams { @WindowManager.TransitionOldType int mTransit; diff --git a/packages/SystemUI/src/com/android/systemui/sensorprivacy/SensorUseDialog.kt b/packages/SystemUI/src/com/android/systemui/sensorprivacy/SensorUseDialog.kt new file mode 100644 index 000000000000..c50365f1bf38 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/sensorprivacy/SensorUseDialog.kt @@ -0,0 +1,74 @@ +package com.android.systemui.sensorprivacy + +import android.content.Context +import android.content.DialogInterface +import android.content.res.Resources +import android.text.Html +import android.view.LayoutInflater +import android.view.View +import android.view.WindowManager +import android.widget.ImageView +import com.android.internal.widget.DialogTitle +import com.android.systemui.R +import com.android.systemui.statusbar.phone.SystemUIDialog + +class SensorUseDialog( + context: Context, + val sensor: Int, + val clickListener: DialogInterface.OnClickListener +) : SystemUIDialog(context) { + + // TODO move to onCreate (b/200815309) + init { + window!!.addFlags(WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED) + window!!.addSystemFlags( + WindowManager.LayoutParams.SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS) + + val layoutInflater = LayoutInflater.from(context) + val customTitleView = layoutInflater.inflate(R.layout.sensor_use_started_title, null) + customTitleView.requireViewById<DialogTitle>(R.id.sensor_use_started_title_message) + .setText(when (sensor) { + SensorUseStartedActivity.MICROPHONE -> + R.string.sensor_privacy_start_use_mic_dialog_title + SensorUseStartedActivity.CAMERA -> + R.string.sensor_privacy_start_use_camera_dialog_title + SensorUseStartedActivity.ALL_SENSORS -> + R.string.sensor_privacy_start_use_mic_camera_dialog_title + else -> Resources.ID_NULL + }) + customTitleView.requireViewById<ImageView>(R.id.sensor_use_microphone_icon).visibility = + if (sensor == SensorUseStartedActivity.MICROPHONE || + sensor == SensorUseStartedActivity.ALL_SENSORS) { + View.VISIBLE + } else { + View.GONE + } + customTitleView.requireViewById<ImageView>(R.id.sensor_use_camera_icon).visibility = + if (sensor == SensorUseStartedActivity.CAMERA || + sensor == SensorUseStartedActivity.ALL_SENSORS) { + View.VISIBLE + } else { + View.GONE + } + + setCustomTitle(customTitleView) + setMessage(Html.fromHtml(context.getString(when (sensor) { + SensorUseStartedActivity.MICROPHONE -> + R.string.sensor_privacy_start_use_mic_dialog_content + SensorUseStartedActivity.CAMERA -> + R.string.sensor_privacy_start_use_camera_dialog_content + SensorUseStartedActivity.ALL_SENSORS -> + R.string.sensor_privacy_start_use_mic_camera_dialog_content + else -> Resources.ID_NULL + }), 0)) + + setButton(BUTTON_POSITIVE, + context.getString(com.android.internal.R.string + .sensor_privacy_start_use_dialog_turn_on_button), clickListener) + setButton(BUTTON_NEGATIVE, + context.getString(com.android.internal.R.string + .cancel), clickListener) + + setCancelable(false) + } +} diff --git a/packages/SystemUI/src/com/android/systemui/sensorprivacy/SensorUseStartedActivity.kt b/packages/SystemUI/src/com/android/systemui/sensorprivacy/SensorUseStartedActivity.kt index f0fb5ebf9e1d..b0071d92481d 100644 --- a/packages/SystemUI/src/com/android/systemui/sensorprivacy/SensorUseStartedActivity.kt +++ b/packages/SystemUI/src/com/android/systemui/sensorprivacy/SensorUseStartedActivity.kt @@ -16,33 +16,28 @@ package com.android.systemui.sensorprivacy +import android.app.Activity +import android.app.AlertDialog import android.content.DialogInterface +import android.content.DialogInterface.BUTTON_NEGATIVE +import android.content.DialogInterface.BUTTON_POSITIVE import android.content.Intent import android.content.Intent.EXTRA_PACKAGE_NAME -import android.content.pm.PackageManager -import android.content.res.Resources import android.hardware.SensorPrivacyManager import android.hardware.SensorPrivacyManager.EXTRA_ALL_SENSORS import android.hardware.SensorPrivacyManager.EXTRA_SENSOR import android.hardware.SensorPrivacyManager.Sources.DIALOG import android.os.Bundle import android.os.Handler -import android.text.Html -import android.view.View.GONE -import android.view.View.VISIBLE -import android.widget.ImageView -import com.android.internal.app.AlertActivity -import com.android.internal.widget.DialogTitle -import com.android.systemui.R +import com.android.internal.util.FrameworkStatsLog.PRIVACY_TOGGLE_DIALOG_INTERACTION +import com.android.internal.util.FrameworkStatsLog.PRIVACY_TOGGLE_DIALOG_INTERACTION__ACTION__CANCEL +import com.android.internal.util.FrameworkStatsLog.PRIVACY_TOGGLE_DIALOG_INTERACTION__ACTION__ENABLE +import com.android.internal.util.FrameworkStatsLog.write import com.android.systemui.dagger.qualifiers.Background import com.android.systemui.statusbar.phone.KeyguardDismissUtil import com.android.systemui.statusbar.policy.IndividualSensorPrivacyController import com.android.systemui.statusbar.policy.KeyguardStateController import javax.inject.Inject -import com.android.internal.util.FrameworkStatsLog.PRIVACY_TOGGLE_DIALOG_INTERACTION -import com.android.internal.util.FrameworkStatsLog.PRIVACY_TOGGLE_DIALOG_INTERACTION__ACTION__ENABLE -import com.android.internal.util.FrameworkStatsLog.PRIVACY_TOGGLE_DIALOG_INTERACTION__ACTION__CANCEL -import com.android.internal.util.FrameworkStatsLog.write /** * Dialog to be shown on top of apps that are attempting to use a sensor (e.g. microphone) which is @@ -55,7 +50,7 @@ class SensorUseStartedActivity @Inject constructor( private val keyguardStateController: KeyguardStateController, private val keyguardDismissUtil: KeyguardDismissUtil, @Background private val bgHandler: Handler -) : AlertActivity(), DialogInterface.OnClickListener { +) : Activity(), DialogInterface.OnClickListener { companion object { private val LOG_TAG = SensorUseStartedActivity::class.java.simpleName @@ -63,9 +58,9 @@ class SensorUseStartedActivity @Inject constructor( private const val SUPPRESS_REMINDERS_REMOVAL_DELAY_MILLIS = 2000L private const val UNLOCK_DELAY_MILLIS = 200L - private const val CAMERA = SensorPrivacyManager.Sensors.CAMERA - private const val MICROPHONE = SensorPrivacyManager.Sensors.MICROPHONE - private const val ALL_SENSORS = Integer.MAX_VALUE + internal const val CAMERA = SensorPrivacyManager.Sensors.CAMERA + internal const val MICROPHONE = SensorPrivacyManager.Sensors.MICROPHONE + internal const val ALL_SENSORS = Integer.MAX_VALUE } private var sensor = -1 @@ -74,6 +69,8 @@ class SensorUseStartedActivity @Inject constructor( private lateinit var sensorPrivacyListener: IndividualSensorPrivacyController.Callback + private var mDialog: AlertDialog? = null + override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) @@ -91,7 +88,7 @@ class SensorUseStartedActivity @Inject constructor( IndividualSensorPrivacyController.Callback { _, _ -> if (!sensorPrivacyController.isSensorBlocked(MICROPHONE) && !sensorPrivacyController.isSensorBlocked(CAMERA)) { - dismiss() + finish() } } @@ -109,71 +106,22 @@ class SensorUseStartedActivity @Inject constructor( } } sensorPrivacyListener = - IndividualSensorPrivacyController.Callback { - whichSensor: Int, isBlocked: Boolean -> + IndividualSensorPrivacyController.Callback { whichSensor: Int, + isBlocked: Boolean -> if (whichSensor == sensor && !isBlocked) { - dismiss() + finish() } } sensorPrivacyController.addCallback(sensorPrivacyListener) - sensorPrivacyController.addCallback { _, isBlocked -> - if (!isBlocked) { - dismiss() - } - } - } - - mAlertParams.apply { - try { - mCustomTitleView = mInflater.inflate(R.layout.sensor_use_started_title, null) - mCustomTitleView.findViewById<DialogTitle>(R.id.sensor_use_started_title_message)!! - .setText(when (sensor) { - MICROPHONE -> - R.string.sensor_privacy_start_use_mic_dialog_title - CAMERA -> - R.string.sensor_privacy_start_use_camera_dialog_title - ALL_SENSORS -> - R.string.sensor_privacy_start_use_mic_camera_dialog_title - else -> Resources.ID_NULL - }) - - mCustomTitleView.findViewById<ImageView>(R.id.sensor_use_microphone_icon)!! - .visibility = if (sensor == MICROPHONE || sensor == ALL_SENSORS) { - VISIBLE - } else { - GONE - } - mCustomTitleView.findViewById<ImageView>(R.id.sensor_use_camera_icon)!! - .visibility = if (sensor == CAMERA || sensor == ALL_SENSORS) { - VISIBLE - } else { - GONE - } - - mMessage = Html.fromHtml(getString(when (sensor) { - MICROPHONE -> - R.string.sensor_privacy_start_use_mic_dialog_content - CAMERA -> - R.string.sensor_privacy_start_use_camera_dialog_content - ALL_SENSORS -> - R.string.sensor_privacy_start_use_mic_camera_dialog_content - else -> Resources.ID_NULL - }, packageManager.getApplicationInfo(sensorUsePackageName, 0) - .loadLabel(packageManager)), 0) - } catch (e: PackageManager.NameNotFoundException) { + if (!sensorPrivacyController.isSensorBlocked(sensor)) { finish() return } - - mPositiveButtonText = getString( - com.android.internal.R.string.sensor_privacy_start_use_dialog_turn_on_button) - mNegativeButtonText = getString(android.R.string.cancel) - mPositiveButtonListener = this@SensorUseStartedActivity - mNegativeButtonListener = this@SensorUseStartedActivity } - setupAlert() + mDialog = SensorUseDialog(this, sensor, this) + mDialog!!.show() } override fun onStart() { @@ -212,7 +160,7 @@ class SensorUseStartedActivity @Inject constructor( } } - dismiss() + finish() } override fun onStop() { @@ -229,6 +177,7 @@ class SensorUseStartedActivity @Inject constructor( override fun onDestroy() { super.onDestroy() + mDialog?.dismiss() sensorPrivacyController.removeCallback(sensorPrivacyListener) } @@ -263,4 +212,4 @@ class SensorUseStartedActivity @Inject constructor( .suppressSensorPrivacyReminders(sensor, suppressed) } } -}
\ No newline at end of file +} diff --git a/packages/SystemUI/src/com/android/systemui/sensorprivacy/television/TvUnblockSensorActivity.java b/packages/SystemUI/src/com/android/systemui/sensorprivacy/television/TvUnblockSensorActivity.java index 8cd3632b65ba..cc5cf4b63f99 100644 --- a/packages/SystemUI/src/com/android/systemui/sensorprivacy/television/TvUnblockSensorActivity.java +++ b/packages/SystemUI/src/com/android/systemui/sensorprivacy/television/TvUnblockSensorActivity.java @@ -24,6 +24,7 @@ import android.hardware.SensorPrivacyManager; import android.os.Bundle; import android.util.Log; import android.view.View; +import android.view.WindowManager; import android.widget.Button; import android.widget.ImageView; import android.widget.TextView; @@ -57,6 +58,8 @@ public class TvUnblockSensorActivity extends TvBottomSheetActivity { @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); + getWindow().addSystemFlags( + WindowManager.LayoutParams.SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS); boolean allSensors = getIntent().getBooleanExtra(SensorPrivacyManager.EXTRA_ALL_SENSORS, false); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/BlurUtils.kt b/packages/SystemUI/src/com/android/systemui/statusbar/BlurUtils.kt index dce19cf86b35..cfbe3b29783a 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/BlurUtils.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/BlurUtils.kt @@ -54,22 +54,22 @@ open class BlurUtils @Inject constructor( /** * Translates a ratio from 0 to 1 to a blur radius in pixels. */ - fun blurRadiusOfRatio(ratio: Float): Int { + fun blurRadiusOfRatio(ratio: Float): Float { if (ratio == 0f) { - return 0 + return 0f } - return MathUtils.lerp(minBlurRadius.toFloat(), maxBlurRadius.toFloat(), ratio).toInt() + return MathUtils.lerp(minBlurRadius.toFloat(), maxBlurRadius.toFloat(), ratio) } /** * Translates a blur radius in pixels to a ratio between 0 to 1. */ - fun ratioOfBlurRadius(blur: Int): Float { - if (blur == 0) { + fun ratioOfBlurRadius(blur: Float): Float { + if (blur == 0f) { return 0f } return MathUtils.map(minBlurRadius.toFloat(), maxBlurRadius.toFloat(), - 0f /* maxStart */, 1f /* maxStop */, blur.toFloat()) + 0f /* maxStart */, 1f /* maxStop */, blur) } /** diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java b/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java index 503b5c0ee4b0..1c933505172f 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java @@ -811,13 +811,8 @@ public class KeyguardIndicationController { mStatusBarKeyguardViewManager.showBouncerMessage(message, mInitialTextColorState); } } else if (mKeyguardUpdateMonitor.isScreenOn()) { - if (mKeyguardUpdateMonitor.isUdfpsAvailable()) { - showTransientIndication(mContext.getString(R.string.keyguard_unlock_press), - false /* isError */, true /* hideOnScreenOff */); - } else { - showTransientIndication(mContext.getString(R.string.keyguard_unlock), - false /* isError */, true /* hideOnScreenOff */); - } + showTransientIndication(mContext.getString(R.string.keyguard_unlock), + false /* isError */, true /* hideOnScreenOff */); } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShadeDepthController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShadeDepthController.kt index 002c9c7d2544..b8334272c157 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShadeDepthController.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShadeDepthController.kt @@ -68,7 +68,7 @@ class NotificationShadeDepthController @Inject constructor( private const val VELOCITY_SCALE = 100f private const val MAX_VELOCITY = 3000f private const val MIN_VELOCITY = -MAX_VELOCITY - private const val INTERACTION_BLUR_FRACTION = 0.4f + private const val INTERACTION_BLUR_FRACTION = 0.8f private const val ANIMATION_BLUR_FRACTION = 1f - INTERACTION_BLUR_FRACTION private const val TAG = "DepthController" } @@ -92,8 +92,6 @@ class NotificationShadeDepthController @Inject constructor( // Only for dumpsys private var lastAppliedBlur = 0 - @VisibleForTesting - var shadeSpring = DepthAnimation() var shadeAnimation = DepthAnimation() @VisibleForTesting @@ -101,12 +99,16 @@ class NotificationShadeDepthController @Inject constructor( var brightnessMirrorVisible: Boolean = false set(value) { field = value - brightnessMirrorSpring.animateTo(if (value) blurUtils.blurRadiusOfRatio(1f) + brightnessMirrorSpring.animateTo(if (value) blurUtils.blurRadiusOfRatio(1f).toInt() else 0) } var qsPanelExpansion = 0f set(value) { + if (value.isNaN()) { + Log.w(TAG, "Invalid qs expansion") + return + } if (field == value) return field = value scheduleUpdate() @@ -134,15 +136,13 @@ class NotificationShadeDepthController @Inject constructor( field = value scheduleUpdate() - if (shadeSpring.radius == 0 && shadeAnimation.radius == 0) { + if (shadeExpansion == 0f && shadeAnimation.radius == 0f) { return } // Do not remove blurs when we're re-enabling them if (!value) { return } - shadeSpring.animateTo(0) - shadeSpring.finishIfRunning() shadeAnimation.animateTo(0) shadeAnimation.finishIfRunning() @@ -161,7 +161,7 @@ class NotificationShadeDepthController @Inject constructor( /** * Blur radius of the wake-up animation on this frame. */ - private var wakeAndUnlockBlurRadius = 0 + private var wakeAndUnlockBlurRadius = 0f set(value) { if (field == value) return field = value @@ -174,26 +174,30 @@ class NotificationShadeDepthController @Inject constructor( @VisibleForTesting val updateBlurCallback = Choreographer.FrameCallback { updateScheduled = false - val normalizedBlurRadius = MathUtils.constrain(shadeAnimation.radius, - blurUtils.minBlurRadius, blurUtils.maxBlurRadius) - var combinedBlur = (shadeSpring.radius * INTERACTION_BLUR_FRACTION + - normalizedBlurRadius * ANIMATION_BLUR_FRACTION).toInt() + val animationRadius = MathUtils.constrain(shadeAnimation.radius, + blurUtils.minBlurRadius.toFloat(), blurUtils.maxBlurRadius.toFloat()) + val expansionRadius = blurUtils.blurRadiusOfRatio( + Interpolators.getNotificationScrimAlpha( + if (shouldApplyShadeBlur()) shadeExpansion else 0f, false)) + var combinedBlur = (expansionRadius * INTERACTION_BLUR_FRACTION + + animationRadius * ANIMATION_BLUR_FRACTION) val qsExpandedRatio = qsPanelExpansion * shadeExpansion combinedBlur = max(combinedBlur, blurUtils.blurRadiusOfRatio(qsExpandedRatio)) combinedBlur = max(combinedBlur, blurUtils.blurRadiusOfRatio(transitionToFullShadeProgress)) - var shadeRadius = max(combinedBlur, wakeAndUnlockBlurRadius).toFloat() + var shadeRadius = max(combinedBlur, wakeAndUnlockBlurRadius) if (blursDisabledForAppLaunch) { shadeRadius = 0f } + var zoomOut = MathUtils.saturate(blurUtils.ratioOfBlurRadius(shadeRadius)) var blur = shadeRadius.toInt() // Make blur be 0 if it is necessary to stop blur effect. if (scrimsVisible) { blur = 0 + zoomOut = 0f } - val zoomOut = blurUtils.ratioOfBlurRadius(blur) if (!blurUtils.supportsBlursOnWindows()) { blur = 0 @@ -266,12 +270,11 @@ class NotificationShadeDepthController @Inject constructor( override fun onStateChanged(newState: Int) { updateShadeAnimationBlur( shadeExpansion, prevTracking, prevShadeVelocity, prevShadeDirection) - updateShadeBlur() + scheduleUpdate() } override fun onDozingChanged(isDozing: Boolean) { if (isDozing) { - shadeSpring.finishIfRunning() shadeAnimation.finishIfRunning() brightnessMirrorSpring.finishIfRunning() } @@ -336,7 +339,7 @@ class NotificationShadeDepthController @Inject constructor( prevTracking = tracking prevTimestamp = timestamp - updateShadeBlur() + scheduleUpdate() } private fun updateShadeAnimationBlur( @@ -399,15 +402,7 @@ class NotificationShadeDepthController @Inject constructor( } shadeAnimation.setStartVelocity(velocity) - shadeAnimation.animateTo(blurUtils.blurRadiusOfRatio(targetBlurNormalized)) - } - - private fun updateShadeBlur() { - var newBlur = 0 - if (shouldApplyShadeBlur()) { - newBlur = blurUtils.blurRadiusOfRatio(shadeExpansion) - } - shadeSpring.animateTo(newBlur) + shadeAnimation.animateTo(blurUtils.blurRadiusOfRatio(targetBlurNormalized).toInt()) } private fun scheduleUpdate(viewToBlur: View? = null) { @@ -433,7 +428,8 @@ class NotificationShadeDepthController @Inject constructor( IndentingPrintWriter(pw, " ").let { it.println("StatusBarWindowBlurController:") it.increaseIndent() - it.println("shadeRadius: ${shadeSpring.radius}") + it.println("shadeExpansion: $shadeExpansion") + it.println("shouldApplyShaeBlur: ${shouldApplyShadeBlur()}") it.println("shadeAnimation: ${shadeAnimation.radius}") it.println("brightnessMirrorRadius: ${brightnessMirrorSpring.radius}") it.println("wakeAndUnlockBlur: $wakeAndUnlockBlurRadius") @@ -452,7 +448,7 @@ class NotificationShadeDepthController @Inject constructor( /** * Blur radius visible on the UI, in pixels. */ - var radius = 0 + var radius = 0f /** * Depth ratio of the current blur radius. @@ -473,12 +469,12 @@ class NotificationShadeDepthController @Inject constructor( private var springAnimation = SpringAnimation(this, object : FloatPropertyCompat<DepthAnimation>("blurRadius") { override fun setValue(rect: DepthAnimation?, value: Float) { - radius = value.toInt() + radius = value scheduleUpdate(view) } override fun getValue(rect: DepthAnimation?): Float { - return radius.toFloat() + return radius } }) diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/charging/RippleShader.kt b/packages/SystemUI/src/com/android/systemui/statusbar/charging/RippleShader.kt index 146046b33375..5175977d4e81 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/charging/RippleShader.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/charging/RippleShader.kt @@ -147,8 +147,12 @@ class RippleShader internal constructor() : RuntimeShader(SHADER, false) { val fadeIn = subProgress(0f, 0.1f, value) val fadeOutNoise = subProgress(0.4f, 1f, value) - val fadeOutRipple = subProgress(0.3f, 1f, value) - val fadeCircle = subProgress(0f, 0.2f, value) + var fadeOutRipple = 0f + var fadeCircle = 0f + if (shouldFadeOutRipple) { + fadeCircle = subProgress(0f, 0.2f, value) + fadeOutRipple = subProgress(0.3f, 1f, value) + } setUniform("in_fadeSparkle", Math.min(fadeIn, 1 - fadeOutNoise)) setUniform("in_fadeCircle", 1 - fadeCircle) setUniform("in_fadeRing", Math.min(fadeIn, 1 - fadeOutRipple)) @@ -200,4 +204,6 @@ class RippleShader internal constructor() : RuntimeShader(SHADER, false) { field = value setUniform("in_pixelDensity", value) } + + var shouldFadeOutRipple: Boolean = true } 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 0660daab3720..ae852053f0c5 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 @@ -256,6 +256,7 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable private boolean mExpandedInThisMotion; private boolean mShouldShowShelfOnly; protected boolean mScrollingEnabled; + private boolean mIsCurrentUserSetup; protected FooterView mFooterView; protected EmptyShadeView mEmptyShadeView; private boolean mDismissAllInProgress; @@ -683,6 +684,7 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable mController.hasActiveClearableNotifications(ROWS_ALL); RemoteInputController remoteInputController = mRemoteInputManager.getController(); boolean showFooterView = (showDismissView || getVisibleNotificationCount() > 0) + && mIsCurrentUserSetup // see: b/193149550 && mStatusBarState != StatusBarState.KEYGUARD && !mUnlockedScreenOffAnimationController.isScreenOffAnimationPlaying() && (remoteInputController == null || !remoteInputController.isRemoteInputActive()); @@ -5567,6 +5569,16 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable } /** + * Sets whether the current user is set up, which is required to show the footer (b/193149550) + */ + public void setCurrentUserSetup(boolean isCurrentUserSetup) { + if (mIsCurrentUserSetup != isCurrentUserSetup) { + mIsCurrentUserSetup = isCurrentUserSetup; + updateFooter(); + } + } + + /** * A listener that is notified when the empty space below the notifications is clicked on */ @ShadeViewRefactor(RefactorComponent.SHADE_VIEW) diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java index 9e4adce47e0c..3ceb655b1c0c 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java @@ -117,6 +117,8 @@ import com.android.systemui.statusbar.phone.StatusBar; import com.android.systemui.statusbar.phone.dagger.StatusBarComponent; import com.android.systemui.statusbar.policy.ConfigurationController; import com.android.systemui.statusbar.policy.ConfigurationController.ConfigurationListener; +import com.android.systemui.statusbar.policy.DeviceProvisionedController; +import com.android.systemui.statusbar.policy.DeviceProvisionedController.DeviceProvisionedListener; import com.android.systemui.statusbar.policy.OnHeadsUpChangedListener; import com.android.systemui.statusbar.policy.ZenModeController; import com.android.systemui.tuner.TunerService; @@ -144,6 +146,7 @@ public class NotificationStackScrollLayoutController { private final HeadsUpManagerPhone mHeadsUpManager; private final NotificationRoundnessManager mNotificationRoundnessManager; private final TunerService mTunerService; + private final DeviceProvisionedController mDeviceProvisionedController; private final DynamicPrivacyController mDynamicPrivacyController; private final ConfigurationController mConfigurationController; private final ZenModeController mZenModeController; @@ -218,6 +221,28 @@ public class NotificationStackScrollLayoutController { } }; + private final DeviceProvisionedListener mDeviceProvisionedListener = + new DeviceProvisionedListener() { + @Override + public void onDeviceProvisionedChanged() { + updateCurrentUserIsSetup(); + } + + @Override + public void onUserSwitched() { + updateCurrentUserIsSetup(); + } + + @Override + public void onUserSetupChanged() { + updateCurrentUserIsSetup(); + } + + private void updateCurrentUserIsSetup() { + mView.setCurrentUserSetup(mDeviceProvisionedController.isCurrentUserSetup()); + } + }; + private final DynamicPrivacyController.Listener mDynamicPrivacyControllerListener = () -> { if (mView.isExpanded()) { // The bottom might change because we're using the final actual height of the view @@ -587,6 +612,7 @@ public class NotificationStackScrollLayoutController { HeadsUpManagerPhone headsUpManager, NotificationRoundnessManager notificationRoundnessManager, TunerService tunerService, + DeviceProvisionedController deviceProvisionedController, DynamicPrivacyController dynamicPrivacyController, ConfigurationController configurationController, SysuiStatusBarStateController statusBarStateController, @@ -623,6 +649,7 @@ public class NotificationStackScrollLayoutController { mHeadsUpManager = headsUpManager; mNotificationRoundnessManager = notificationRoundnessManager; mTunerService = tunerService; + mDeviceProvisionedController = deviceProvisionedController; mDynamicPrivacyController = dynamicPrivacyController; mConfigurationController = configurationController; mStatusBarStateController = statusBarStateController; @@ -759,6 +786,9 @@ public class NotificationStackScrollLayoutController { return Unit.INSTANCE; }); + // callback is invoked synchronously, updating mView immediately + mDeviceProvisionedController.addCallback(mDeviceProvisionedListener); + if (mView.isAttachedToWindow()) { mOnAttachStateChangeListener.onViewAttachedToWindow(mView); } 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 6e201048abdb..2c76cfeff733 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/BiometricUnlockController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/BiometricUnlockController.java @@ -116,7 +116,7 @@ public class BiometricUnlockController extends KeyguardUpdateMonitorCallback imp /** * Mode in which fingerprint unlocks the device or passive auth (ie face auth) unlocks the - * device while being requested when keyguard is occluded. + * device while being requested when keyguard is occluded or showing. */ public static final int MODE_UNLOCK_COLLAPSING = 5; @@ -425,6 +425,11 @@ public class BiometricUnlockController extends KeyguardUpdateMonitorCallback imp if (!wasDeviceInteractive) { mPendingShowBouncer = true; } else { + mShadeController.animateCollapsePanels( + CommandQueue.FLAG_EXCLUDE_NONE, + true /* force */, + false /* delayed */, + BIOMETRIC_COLLAPSE_SPEEDUP_FACTOR); mPendingShowBouncer = false; mKeyguardViewController.notifyKeyguardAuthenticated( false /* strongAuth */); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java index 15e0716f8c49..440f19cca01a 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java @@ -3655,6 +3655,7 @@ public class NotificationPanelViewController extends PanelViewController { } public void dozeTimeTick() { + mLockIconViewController.dozeTimeTick(); mKeyguardBottomArea.dozeTimeTick(); mKeyguardStatusViewController.dozeTimeTick(); if (mInterpolatedDarkAmount > 0) { @@ -3868,6 +3869,9 @@ public class NotificationPanelViewController extends PanelViewController { @Override protected TouchHandler createTouchHandler() { return new TouchHandler() { + + private long mLastTouchDownTime = -1L; + @Override public boolean onInterceptTouchEvent(MotionEvent event) { if (mBlockTouches || mQsFullyExpanded && mQs.disallowPanelTouches()) { @@ -3897,6 +3901,19 @@ public class NotificationPanelViewController extends PanelViewController { @Override public boolean onTouch(View v, MotionEvent event) { + if (event.getAction() == MotionEvent.ACTION_DOWN) { + if (event.getDownTime() == mLastTouchDownTime) { + // An issue can occur when swiping down after unlock, where multiple down + // events are received in this handler with identical downTimes. Until the + // source of the issue can be located, detect this case and ignore. + // see b/193350347 + Log.w(TAG, "Duplicate down event detected... ignoring"); + return true; + } + mLastTouchDownTime = event.getDownTime(); + } + + if (mBlockTouches || (mQsFullyExpanded && mQs != null && mQs.disallowPanelTouches())) { return false; @@ -3958,10 +3975,6 @@ public class NotificationPanelViewController extends PanelViewController { mStatusBarKeyguardViewManager.updateKeyguardPosition(event.getX()); } - if (mLockIconViewController.onTouchEvent(event)) { - return true; - } - handled |= super.onTouch(v, event); return !mDozing || mPulsing || handled; } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationShadeWindowViewController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationShadeWindowViewController.java index b5d9bd67bd2d..66a6e723ede2 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationShadeWindowViewController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationShadeWindowViewController.java @@ -35,6 +35,7 @@ import android.view.View; import android.view.ViewGroup; import com.android.internal.annotations.VisibleForTesting; +import com.android.keyguard.LockIconViewController; import com.android.systemui.R; import com.android.systemui.classifier.FalsingCollector; import com.android.systemui.dock.DockManager; @@ -88,6 +89,7 @@ public class NotificationShadeWindowViewController { private final NotificationShadeDepthController mDepthController; private final NotificationStackScrollLayoutController mNotificationStackScrollLayoutController; private final LockscreenShadeTransitionController mLockscreenShadeTransitionController; + private final LockIconViewController mLockIconViewController; private final StatusBarKeyguardViewManager mStatusBarKeyguardViewManager; private GestureDetector mGestureDetector; @@ -138,7 +140,8 @@ public class NotificationShadeWindowViewController { NotificationPanelViewController notificationPanelViewController, SuperStatusBarViewFactory statusBarViewFactory, NotificationStackScrollLayoutController notificationStackScrollLayoutController, - StatusBarKeyguardViewManager statusBarKeyguardViewManager) { + StatusBarKeyguardViewManager statusBarKeyguardViewManager, + LockIconViewController lockIconViewController) { mInjectionInflationController = injectionInflationController; mCoordinator = coordinator; mPulseExpansionHandler = pulseExpansionHandler; @@ -163,6 +166,7 @@ public class NotificationShadeWindowViewController { mStatusBarViewFactory = statusBarViewFactory; mNotificationStackScrollLayoutController = notificationStackScrollLayoutController; mStatusBarKeyguardViewManager = statusBarKeyguardViewManager; + mLockIconViewController = lockIconViewController; // This view is not part of the newly inflated expanded status bar. mBrightnessMirror = mView.findViewById(R.id.brightness_mirror_container); @@ -235,6 +239,7 @@ public class NotificationShadeWindowViewController { if (!isCancel && mService.shouldIgnoreTouch()) { return false; } + if (isDown) { setTouchActive(true); mTouchCancelled = false; @@ -245,6 +250,7 @@ public class NotificationShadeWindowViewController { if (mTouchCancelled || mExpandAnimationRunning) { return false; } + mFalsingCollector.onTouchEvent(ev); mGestureDetector.onTouchEvent(ev); mStatusBarKeyguardViewManager.onTouch(ev); @@ -260,9 +266,17 @@ public class NotificationShadeWindowViewController { if (isDown) { mNotificationStackScrollLayoutController.closeControlsIfOutsideTouch(ev); } + if (mStatusBarStateController.isDozing()) { mService.mDozeScrimController.extendPulse(); } + mLockIconViewController.onTouchEvent( + ev, + () -> mService.wakeUpIfDozing( + SystemClock.uptimeMillis(), + mView, + "LOCK_ICON_TOUCH")); + // In case we start outside of the view bounds (below the status bar), we need to // dispatch // the touch manually as the view system can't accommodate for touches outside of diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java index cfcea9684c3b..7d25aeef6f98 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java @@ -111,6 +111,20 @@ public class ScrimController implements ViewTreeObserver.OnPreDrawListener, Dump */ private boolean mTransitioningToFullShade; + /** + * Is there currently an unocclusion animation running. Used to avoid bright flickers + * of the notification scrim. + */ + private boolean mUnOcclusionAnimationRunning; + + /** + * Set whether an unocclusion animation is currently running on the notification panel. Used + * to avoid bright flickers of the notification scrim. + */ + public void setUnocclusionAnimationRunning(boolean unocclusionAnimationRunning) { + mUnOcclusionAnimationRunning = unocclusionAnimationRunning; + } + @IntDef(prefix = {"VISIBILITY_"}, value = { TRANSPARENT, SEMI_TRANSPARENT, @@ -418,7 +432,7 @@ public class ScrimController implements ViewTreeObserver.OnPreDrawListener, Dump if (mKeyguardUpdateMonitor.needsSlowUnlockTransition() && mState == ScrimState.UNLOCKED) { mAnimationDelay = StatusBar.FADE_KEYGUARD_START_DELAY; scheduleUpdate(); - } else if ((oldState == ScrimState.AOD // leaving doze + } else if (((oldState == ScrimState.AOD || oldState == ScrimState.PULSING) // leaving doze && (!mDozeParameters.getAlwaysOn() || mState == ScrimState.UNLOCKED)) || (mState == ScrimState.AOD && !mDozeParameters.getDisplayNeedsBlanking())) { // Scheduling a frame isn't enough when: @@ -466,6 +480,7 @@ public class ScrimController implements ViewTreeObserver.OnPreDrawListener, Dump public void onExpandingFinished() { mTracking = false; + setUnocclusionAnimationRunning(false); } @VisibleForTesting @@ -694,6 +709,11 @@ public class ScrimController implements ViewTreeObserver.OnPreDrawListener, Dump mNotificationsTint = mState.getNotifTint(); mBehindTint = behindTint; } + if (mUnOcclusionAnimationRunning && mState == ScrimState.KEYGUARD) { + // We're unoccluding the keyguard and don't want to have a bright flash. + mNotificationsAlpha = KEYGUARD_SCRIM_ALPHA; + mNotificationsTint = ScrimState.KEYGUARD.getNotifTint(); + } } if (isNaN(mBehindAlpha) || isNaN(mInFrontAlpha) || isNaN(mNotificationsAlpha)) { throw new IllegalStateException("Scrim opacity is NaN for state: " + mState diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimState.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimState.java index 06811932ac0c..2c0de629de8a 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimState.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimState.java @@ -244,7 +244,8 @@ public enum ScrimState { ? mKeyguardFadingAwayDuration : StatusBar.FADE_KEYGUARD_DURATION; - mAnimateChange = !mLaunchingAffordanceWithPreview; + boolean fromAod = previousState == AOD || previousState == PULSING; + mAnimateChange = !mLaunchingAffordanceWithPreview && !fromAod; mFrontTint = Color.TRANSPARENT; mBehindTint = Color.BLACK; diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java index 394e4ad76df1..90fc779c32cb 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java @@ -592,6 +592,7 @@ public class StatusBar extends SystemUI implements DemoMode, mNotificationShadeWindowController.setWallpaperSupportsAmbientMode(supportsAmbientMode); mScrimController.setWallpaperSupportsAmbientMode(supportsAmbientMode); + mKeyguardViewMediator.setWallpaperSupportsAmbientMode(supportsAmbientMode); } }; @@ -1148,6 +1149,9 @@ public class StatusBar extends SystemUI implements DemoMode, mStatusBarView.setPanel(mNotificationPanelViewController); mStatusBarView.setScrimController(mScrimController); mStatusBarView.setExpansionChangedListeners(mExpansionChangedListeners); + for (ExpansionChangedListener listener : mExpansionChangedListeners) { + sendInitialExpansionAmount(listener); + } // CollapsedStatusBarFragment re-inflated PhoneStatusBarView and both of // mStatusBarView.mExpanded and mStatusBarView.mBouncerShowing are false. @@ -3579,6 +3583,7 @@ public class StatusBar extends SystemUI implements DemoMode, public void animateKeyguardUnoccluding() { mNotificationPanelViewController.setExpandedFraction(0f); animateExpandNotificationsPanel(); + mScrimController.setUnocclusionAnimationRunning(true); } /** @@ -3909,7 +3914,8 @@ public class StatusBar extends SystemUI implements DemoMode, @Override public void onDozeAmountChanged(float linear, float eased) { if (mFeatureFlags.useNewLockscreenAnimations() - && !(mLightRevealScrim.getRevealEffect() instanceof CircleReveal)) { + && !(mLightRevealScrim.getRevealEffect() instanceof CircleReveal) + && !mBiometricUnlockController.isWakeAndUnlock()) { mLightRevealScrim.setRevealAmount(1f - linear); } } @@ -4465,10 +4471,8 @@ public class StatusBar extends SystemUI implements DemoMode, ScrimState state = mStatusBarKeyguardViewManager.bouncerNeedsScrimming() ? ScrimState.BOUNCER_SCRIMMED : ScrimState.BOUNCER; mScrimController.transitionTo(state); - } else if (isInLaunchTransition() - || mLaunchCameraWhenFinishedWaking - || launchingAffordanceWithPreview) { - // TODO(b/170133395) Investigate whether Emergency Gesture flag should be included here. + } else if (launchingAffordanceWithPreview) { + // We want to avoid animating when launching with a preview. mScrimController.transitionTo(ScrimState.UNLOCKED, mUnlockScrimCallback); } else if (mBrightnessMirrorVisible) { mScrimController.transitionTo(ScrimState.BRIGHTNESS_MIRROR); @@ -4933,6 +4937,14 @@ public class StatusBar extends SystemUI implements DemoMode, public void addExpansionChangedListener(@NonNull ExpansionChangedListener listener) { mExpansionChangedListeners.add(listener); + sendInitialExpansionAmount(listener); + } + + private void sendInitialExpansionAmount(ExpansionChangedListener expansionChangedListener) { + if (mStatusBarView != null) { + expansionChangedListener.onExpansionChanged(mStatusBarView.getExpansionFraction(), + mStatusBarView.isExpanded()); + } } public void removeExpansionChangedListener(@NonNull ExpansionChangedListener listener) { 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 8a7708aaa8c2..3188a522dfad 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java @@ -194,6 +194,7 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb private boolean mLastGesturalNav; private boolean mLastIsDocked; private boolean mLastPulsing; + private boolean mLastAnimatedToSleep; private int mLastBiometricMode; private boolean mQsExpanded; private boolean mAnimatedToSleep; @@ -990,6 +991,7 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb mLastBiometricMode = mBiometricUnlockController.getMode(); mLastGesturalNav = mGesturalNav; mLastIsDocked = mIsDocked; + mLastAnimatedToSleep = mAnimatedToSleep; mStatusBar.onKeyguardViewManagerStatesUpdated(); } @@ -1033,7 +1035,7 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb boolean hideWhileDozing = mLastDozing && mLastBiometricMode != MODE_WAKE_AND_UNLOCK_PULSING; boolean keyguardWithGestureNav = (keyguardShowing && !mLastDozing || mLastPulsing && !mLastIsDocked) && mLastGesturalNav; - return (!keyguardShowing && !hideWhileDozing || mLastBouncerShowing + return (!mLastAnimatedToSleep && !keyguardShowing && !hideWhileDozing || mLastBouncerShowing || mLastRemoteInputActive || keyguardWithGestureNav || mLastGlobalActionsVisible); } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardStateController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardStateController.java index fcfc9670b8b0..705761854532 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardStateController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardStateController.java @@ -245,5 +245,11 @@ public interface KeyguardStateController extends CallbackController<Callback> { * animation. */ default void onKeyguardDismissAmountChanged() {} + + /** + * Triggered when the notification panel is starting or has finished + * fading away on transition to an app. + */ + default void onLaunchTransitionFadingAwayChanged() {} } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardStateControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardStateControllerImpl.java index 64750bd803d5..f787ecf37372 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardStateControllerImpl.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardStateControllerImpl.java @@ -343,6 +343,7 @@ public class KeyguardStateControllerImpl implements KeyguardStateController, Dum @Override public void setLaunchTransitionFadingAway(boolean fadingAway) { mLaunchTransitionFadingAway = fadingAway; + new ArrayList<>(mCallbacks).forEach(Callback::onLaunchTransitionFadingAwayChanged); } @Override diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthRippleControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthRippleControllerTest.kt index d87a26b096fd..f2f0029708ed 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthRippleControllerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthRippleControllerTest.kt @@ -19,16 +19,23 @@ package com.android.systemui.biometrics import android.graphics.PointF import android.hardware.biometrics.BiometricSourceType import android.testing.AndroidTestingRunner +import android.testing.TestableLooper.RunWithLooper import androidx.test.filters.SmallTest import com.android.keyguard.KeyguardUpdateMonitor import com.android.keyguard.KeyguardUpdateMonitorCallback import com.android.systemui.SysuiTestCase +import com.android.systemui.plugins.statusbar.StatusBarStateController +import com.android.systemui.keyguard.WakefulnessLifecycle +import com.android.systemui.statusbar.LightRevealScrim import com.android.systemui.statusbar.NotificationShadeWindowController import com.android.systemui.statusbar.commandline.CommandRegistry import com.android.systemui.statusbar.phone.BiometricUnlockController import com.android.systemui.statusbar.phone.KeyguardBypassController import com.android.systemui.statusbar.phone.StatusBar import com.android.systemui.statusbar.policy.ConfigurationController +import com.android.systemui.statusbar.policy.KeyguardStateController +import org.junit.Assert.assertFalse +import org.junit.Assert.assertTrue import org.junit.Before import org.junit.Test import org.junit.runner.RunWith @@ -42,6 +49,8 @@ import org.mockito.Mockito.reset import org.mockito.Mockito.verify import org.mockito.MockitoAnnotations +import javax.inject.Provider + @SmallTest @RunWith(AndroidTestingRunner::class) class AuthRippleControllerTest : SysuiTestCase() { @@ -52,26 +61,39 @@ class AuthRippleControllerTest : SysuiTestCase() { @Mock private lateinit var configurationController: ConfigurationController @Mock private lateinit var keyguardUpdateMonitor: KeyguardUpdateMonitor @Mock private lateinit var authController: AuthController + @Mock private lateinit var keyguardStateController: KeyguardStateController + @Mock private lateinit var wakefulnessLifecycle: WakefulnessLifecycle @Mock private lateinit var notificationShadeWindowController: NotificationShadeWindowController @Mock private lateinit var bypassController: KeyguardBypassController @Mock private lateinit var biometricUnlockController: BiometricUnlockController + @Mock private lateinit var udfpsControllerProvider: Provider<UdfpsController> + @Mock private lateinit var udfpsController: UdfpsController + @Mock private lateinit var statusBarStateController: StatusBarStateController + @Mock private lateinit var lightRevealScrim: LightRevealScrim @Before fun setUp() { MockitoAnnotations.initMocks(this) + `when`(udfpsControllerProvider.get()).thenReturn(udfpsController) + controller = AuthRippleController( statusBar, context, authController, configurationController, keyguardUpdateMonitor, + keyguardStateController, + wakefulnessLifecycle, commandRegistry, notificationShadeWindowController, bypassController, biometricUnlockController, + udfpsControllerProvider, + statusBarStateController, rippleView ) controller.init() + `when`(statusBar.lightRevealScrim).thenReturn(lightRevealScrim) } @Test @@ -93,7 +115,7 @@ class AuthRippleControllerTest : SysuiTestCase() { // THEN update sensor location and show ripple verify(rippleView).setSensorLocation(fpsLocation) - verify(rippleView).startRipple(any(), any()) + verify(rippleView).startUnlockedRipple(any()) } @Test @@ -114,7 +136,7 @@ class AuthRippleControllerTest : SysuiTestCase() { false /* isStrongBiometric */) // THEN no ripple - verify(rippleView, never()).startRipple(any(), any()) + verify(rippleView, never()).startUnlockedRipple(any()) } @Test @@ -135,7 +157,7 @@ class AuthRippleControllerTest : SysuiTestCase() { false /* isStrongBiometric */) // THEN no ripple - verify(rippleView, never()).startRipple(any(), any()) + verify(rippleView, never()).startUnlockedRipple(any()) } @Test @@ -159,7 +181,7 @@ class AuthRippleControllerTest : SysuiTestCase() { // THEN show ripple verify(rippleView).setSensorLocation(faceLocation) - verify(rippleView).startRipple(any(), any()) + verify(rippleView).startUnlockedRipple(any()) } @Test @@ -179,7 +201,7 @@ class AuthRippleControllerTest : SysuiTestCase() { false /* isStrongBiometric */) // THEN no ripple - verify(rippleView, never()).startRipple(any(), any()) + verify(rippleView, never()).startUnlockedRipple(any()) } @Test @@ -194,7 +216,7 @@ class AuthRippleControllerTest : SysuiTestCase() { 0 /* userId */, BiometricSourceType.FACE /* type */, false /* isStrongBiometric */) - verify(rippleView, never()).startRipple(any(), any()) + verify(rippleView, never()).startUnlockedRipple(any()) } @Test @@ -209,7 +231,39 @@ class AuthRippleControllerTest : SysuiTestCase() { 0 /* userId */, BiometricSourceType.FINGERPRINT /* type */, false /* isStrongBiometric */) - verify(rippleView, never()).startRipple(any(), any()) + verify(rippleView, never()).startUnlockedRipple(any()) + } + + @Test + fun registersAndDeregisters() { + controller.onViewAttached() + val captor = ArgumentCaptor + .forClass(KeyguardStateController.Callback::class.java) + verify(keyguardStateController).addCallback(captor.capture()) + val captor2 = ArgumentCaptor + .forClass(WakefulnessLifecycle.Observer::class.java) + verify(wakefulnessLifecycle).addObserver(captor2.capture()) + controller.onViewDetached() + verify(keyguardStateController).removeCallback(any()) + verify(wakefulnessLifecycle).removeObserver(any()) + } + + @Test + @RunWithLooper(setAsMainLooper = true) + fun testAnimatorRunWhenWakeAndUnlock() { + val fpsLocation = PointF(5f, 5f) + `when`(authController.fingerprintSensorLocation).thenReturn(fpsLocation) + controller.onViewAttached() + `when`(keyguardUpdateMonitor.isKeyguardVisible).thenReturn(true) + `when`(biometricUnlockController.isWakeAndUnlock).thenReturn(true) + + controller.showRipple(BiometricSourceType.FINGERPRINT) + assertTrue("reveal didn't start on keyguardFadingAway", + controller.startLightRevealScrimOnKeyguardFadingAway) + `when`(keyguardStateController.isKeyguardFadingAway).thenReturn(true) + controller.onKeyguardFadingAwayChanged() + assertFalse("reveal triggers multiple times", + controller.startLightRevealScrimOnKeyguardFadingAway) } @Test 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 2120b0ee4790..1a390170c736 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerTest.java @@ -456,11 +456,12 @@ public class UdfpsControllerTest extends SysuiTestCase { @Test public void aodInterrupt() throws RemoteException { - // GIVEN that the overlay is showing and screen is on + // GIVEN that the overlay is showing and screen is on and fp is running mOverlayController.showUdfpsOverlay(TEST_UDFPS_SENSOR_ID, IUdfpsOverlayController.REASON_AUTH_FPM_KEYGUARD, mUdfpsOverlayControllerCallback); mScreenObserver.onScreenTurnedOn(); mFgExecutor.runAllReady(); + when(mKeyguardUpdateMonitor.isFingerprintDetectionRunning()).thenReturn(true); // WHEN fingerprint is requested because of AOD interrupt mUdfpsController.onAodInterrupt(0, 0, 2f, 3f); // THEN illumination begins @@ -478,6 +479,7 @@ public class UdfpsControllerTest extends SysuiTestCase { IUdfpsOverlayController.REASON_AUTH_FPM_KEYGUARD, mUdfpsOverlayControllerCallback); mScreenObserver.onScreenTurnedOn(); mFgExecutor.runAllReady(); + when(mKeyguardUpdateMonitor.isFingerprintDetectionRunning()).thenReturn(true); mUdfpsController.onAodInterrupt(0, 0, 0f, 0f); when(mUdfpsView.isIlluminationRequested()).thenReturn(true); // WHEN it is cancelled @@ -493,6 +495,7 @@ public class UdfpsControllerTest extends SysuiTestCase { IUdfpsOverlayController.REASON_AUTH_FPM_KEYGUARD, mUdfpsOverlayControllerCallback); mScreenObserver.onScreenTurnedOn(); mFgExecutor.runAllReady(); + when(mKeyguardUpdateMonitor.isFingerprintDetectionRunning()).thenReturn(true); mUdfpsController.onAodInterrupt(0, 0, 0f, 0f); when(mUdfpsView.isIlluminationRequested()).thenReturn(true); // WHEN it times out @@ -511,6 +514,23 @@ public class UdfpsControllerTest extends SysuiTestCase { mFgExecutor.runAllReady(); // WHEN aod interrupt is received + when(mKeyguardUpdateMonitor.isFingerprintDetectionRunning()).thenReturn(true); + mUdfpsController.onAodInterrupt(0, 0, 0f, 0f); + + // THEN no illumination because screen is off + verify(mUdfpsView, never()).startIllumination(any()); + } + + @Test + public void aodInterrupt_fingerprintNotRunning() throws RemoteException { + // GIVEN showing overlay + mOverlayController.showUdfpsOverlay(TEST_UDFPS_SENSOR_ID, + IUdfpsOverlayController.REASON_AUTH_FPM_KEYGUARD, mUdfpsOverlayControllerCallback); + mScreenObserver.onScreenTurnedOn(); + mFgExecutor.runAllReady(); + + // WHEN aod interrupt is received when the fingerprint service isn't running + when(mKeyguardUpdateMonitor.isFingerprintDetectionRunning()).thenReturn(false); mUdfpsController.onAodInterrupt(0, 0, 0f, 0f); // THEN no illumination because screen is off 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 0c03a51f816e..0fbf9af159a6 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsKeyguardViewControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsKeyguardViewControllerTest.java @@ -20,6 +20,11 @@ import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.Mockito.atLeast; + +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; @@ -30,7 +35,6 @@ import android.testing.TestableLooper.RunWithLooper; import androidx.test.filters.SmallTest; import com.android.keyguard.KeyguardUpdateMonitor; -import com.android.keyguard.KeyguardUpdateMonitorCallback; import com.android.systemui.SysuiTestCase; import com.android.systemui.dump.DumpManager; import com.android.systemui.keyguard.KeyguardViewMediator; @@ -40,6 +44,7 @@ import com.android.systemui.statusbar.StatusBarState; import com.android.systemui.statusbar.phone.StatusBar; import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager; import com.android.systemui.statusbar.policy.ConfigurationController; +import com.android.systemui.statusbar.policy.KeyguardStateController; import com.android.systemui.util.concurrency.DelayableExecutor; import org.junit.Before; @@ -50,6 +55,8 @@ import org.mockito.Captor; import org.mockito.Mock; import org.mockito.MockitoAnnotations; +import java.util.List; + @SmallTest @RunWith(AndroidTestingRunner.class) @@ -75,6 +82,8 @@ public class UdfpsKeyguardViewControllerTest extends SysuiTestCase { @Mock private KeyguardUpdateMonitor mKeyguardUpdateMonitor; @Mock + private KeyguardStateController mKeyguardStateController; + @Mock private KeyguardViewMediator mKeyguardViewMediator; @Mock private ConfigurationController mConfigurationController; @@ -88,14 +97,15 @@ public class UdfpsKeyguardViewControllerTest extends SysuiTestCase { private StatusBarStateController.StateListener mStatusBarStateListener; @Captor private ArgumentCaptor<StatusBar.ExpansionChangedListener> mExpansionListenerCaptor; - private StatusBar.ExpansionChangedListener mExpansionListener; + private List<StatusBar.ExpansionChangedListener> mExpansionListeners; @Captor private ArgumentCaptor<StatusBarKeyguardViewManager.AlternateAuthInterceptor> mAltAuthInterceptorCaptor; private StatusBarKeyguardViewManager.AlternateAuthInterceptor mAltAuthInterceptor; - @Captor private ArgumentCaptor<KeyguardUpdateMonitorCallback> mUpdateMonitorCallbackCaptor; - private KeyguardUpdateMonitorCallback mKeyguardUpdateMonitorCallback; + @Captor private ArgumentCaptor<KeyguardStateController.Callback> + mKeyguardStateControllerCallbackCaptor; + private KeyguardStateController.Callback mKeyguardStateControllerCallback; @Before public void setUp() { @@ -114,13 +124,14 @@ public class UdfpsKeyguardViewControllerTest extends SysuiTestCase { mKeyguardViewMediator, mLockscreenShadeTransitionController, mConfigurationController, + mKeyguardStateController, mUdfpsController); } @Test public void testRegistersExpansionChangedListenerOnAttached() { mController.onViewAttached(); - captureExpansionListener(); + captureExpansionListeners(); } @Test @@ -149,11 +160,15 @@ public class UdfpsKeyguardViewControllerTest extends SysuiTestCase { public void testListenersUnregisteredOnDetached() { mController.onViewAttached(); captureStatusBarStateListeners(); - captureExpansionListener(); + captureExpansionListeners(); + captureKeyguardStateControllerCallback(); mController.onViewDetached(); verify(mStatusBarStateController).removeCallback(mStatusBarStateListener); - verify(mStatusBar).removeExpansionChangedListener(mExpansionListener); + for (StatusBar.ExpansionChangedListener listener : mExpansionListeners) { + verify(mStatusBar).removeExpansionChangedListener(listener); + } + verify(mKeyguardStateController).removeCallback(mKeyguardStateControllerCallback); } @Test @@ -172,7 +187,6 @@ public class UdfpsKeyguardViewControllerTest extends SysuiTestCase { public void testShouldPauseAuthBouncerShowing() { mController.onViewAttached(); captureStatusBarStateListeners(); - captureExpansionListener(); sendStatusBarStateChanged(StatusBarState.KEYGUARD); @@ -183,7 +197,6 @@ public class UdfpsKeyguardViewControllerTest extends SysuiTestCase { public void testShouldNotPauseAuthOnKeyguard() { mController.onViewAttached(); captureStatusBarStateListeners(); - captureExpansionListener(); sendStatusBarStateChanged(StatusBarState.KEYGUARD); @@ -191,10 +204,25 @@ public class UdfpsKeyguardViewControllerTest extends SysuiTestCase { } @Test + public void testShouldPauseAuthIsLaunchTransitionFadingAway() { + // GIVEN view is attached and we're on the keyguard (see testShouldNotPauseAuthOnKeyguard) + mController.onViewAttached(); + captureStatusBarStateListeners(); + sendStatusBarStateChanged(StatusBarState.KEYGUARD); + + // WHEN isLaunchTransitionFadingAway=true + captureKeyguardStateControllerCallback(); + when(mKeyguardStateController.isLaunchTransitionFadingAway()).thenReturn(true); + mKeyguardStateControllerCallback.onLaunchTransitionFadingAwayChanged(); + + // THEN pause auth + assertTrue(mController.shouldPauseAuth()); + } + + @Test public void testShouldPauseAuthOnShadeLocked() { mController.onViewAttached(); captureStatusBarStateListeners(); - captureExpansionListener(); sendStatusBarStateChanged(StatusBarState.SHADE_LOCKED); @@ -205,7 +233,6 @@ public class UdfpsKeyguardViewControllerTest extends SysuiTestCase { public void testShouldPauseAuthOnShade() { mController.onViewAttached(); captureStatusBarStateListeners(); - captureExpansionListener(); // WHEN not on keyguard yet (shade = home) sendStatusBarStateChanged(StatusBarState.SHADE); @@ -218,7 +245,6 @@ public class UdfpsKeyguardViewControllerTest extends SysuiTestCase { public void testShouldPauseAuthAnimatingScreenOffFromShade() { mController.onViewAttached(); captureStatusBarStateListeners(); - captureExpansionListener(); // WHEN transitioning from home/shade => keyguard + animating screen off mStatusBarStateListener.onStatePreChange(StatusBarState.SHADE, StatusBarState.KEYGUARD); @@ -232,7 +258,6 @@ public class UdfpsKeyguardViewControllerTest extends SysuiTestCase { public void testDoNotPauseAuthAnimatingScreenOffFromLS() { mController.onViewAttached(); captureStatusBarStateListeners(); - captureExpansionListener(); // WHEN animating screen off transition from LS => AOD sendStatusBarStateChanged(StatusBarState.KEYGUARD); @@ -273,6 +298,74 @@ public class UdfpsKeyguardViewControllerTest extends SysuiTestCase { verify(mStatusBarKeyguardViewManager).removeAlternateAuthInterceptor(mAltAuthInterceptor); } + @Test + public void testFadeInWithStatusBarExpansion() { + // GIVEN view is attached + mController.onViewAttached(); + captureExpansionListeners(); + captureKeyguardStateControllerCallback(); + reset(mView); + + // WHEN status bar expansion is 0 + updateStatusBarExpansion(0, true); + + // THEN alpha is 0 + verify(mView).setUnpausedAlpha(0); + } + + @Test + public void testShowUdfpsBouncer() { + // GIVEN view is attached and status bar expansion is 0 + mController.onViewAttached(); + captureExpansionListeners(); + captureKeyguardStateControllerCallback(); + captureAltAuthInterceptor(); + updateStatusBarExpansion(0, true); + reset(mView); + when(mView.getContext()).thenReturn(mResourceContext); + when(mResourceContext.getString(anyInt())).thenReturn("test string"); + + // WHEN status bar expansion is 0 but udfps bouncer is requested + mAltAuthInterceptor.showAlternateAuthBouncer(); + + // THEN alpha is 255 + verify(mView).setUnpausedAlpha(255); + } + + @Test + public void testTransitionToFullShadeProgress() { + // GIVEN view is attached and status bar expansion is 1f + mController.onViewAttached(); + captureExpansionListeners(); + updateStatusBarExpansion(1f, true); + reset(mView); + + // WHEN we're transitioning to the full shade + float transitionProgress = .6f; + mController.setTransitionToFullShadeProgress(transitionProgress); + + // THEN alpha is between 0 and 255 + verify(mView).setUnpausedAlpha((int) ((1f - transitionProgress) * 255)); + } + + @Test + public void testShowUdfpsBouncer_transitionToFullShadeProgress() { + // GIVEN view is attached and status bar expansion is 1f + mController.onViewAttached(); + captureExpansionListeners(); + captureKeyguardStateControllerCallback(); + captureAltAuthInterceptor(); + updateStatusBarExpansion(1f, true); + mAltAuthInterceptor.showAlternateAuthBouncer(); + reset(mView); + + // WHEN we're transitioning to the full shade + mController.setTransitionToFullShadeProgress(1.0f); + + // THEN alpha is 255 (b/c udfps bouncer is requested) + verify(mView).setUnpausedAlpha(255); + } + private void sendStatusBarStateChanged(int statusBarState) { mStatusBarStateListener.onStateChanged(statusBarState); } @@ -282,9 +375,18 @@ public class UdfpsKeyguardViewControllerTest extends SysuiTestCase { mStatusBarStateListener = mStateListenerCaptor.getValue(); } - private void captureExpansionListener() { - verify(mStatusBar).addExpansionChangedListener(mExpansionListenerCaptor.capture()); - mExpansionListener = mExpansionListenerCaptor.getValue(); + private void captureExpansionListeners() { + verify(mStatusBar, times(2)) + .addExpansionChangedListener(mExpansionListenerCaptor.capture()); + // first (index=0) is from super class, UdfpsAnimationViewController. + // second (index=1) is from UdfpsKeyguardViewController + mExpansionListeners = mExpansionListenerCaptor.getAllValues(); + } + + private void updateStatusBarExpansion(float expansion, boolean expanded) { + for (StatusBar.ExpansionChangedListener listener : mExpansionListeners) { + listener.onExpansionChanged(expansion, expanded); + } } private void captureAltAuthInterceptor() { @@ -293,8 +395,9 @@ public class UdfpsKeyguardViewControllerTest extends SysuiTestCase { mAltAuthInterceptor = mAltAuthInterceptorCaptor.getValue(); } - private void captureKeyguardUpdateMonitorCallback() { - verify(mKeyguardUpdateMonitor).registerCallback(mUpdateMonitorCallbackCaptor.capture()); - mKeyguardUpdateMonitorCallback = mUpdateMonitorCallbackCaptor.getValue(); + private void captureKeyguardStateControllerCallback() { + verify(mKeyguardStateController).addCallback( + mKeyguardStateControllerCallbackCaptor.capture()); + mKeyguardStateControllerCallback = mKeyguardStateControllerCallbackCaptor.getValue(); } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/doze/DozeScreenBrightnessTest.java b/packages/SystemUI/tests/src/com/android/systemui/doze/DozeScreenBrightnessTest.java index 4e8b59c95681..cb55efacbb0e 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/doze/DozeScreenBrightnessTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/doze/DozeScreenBrightnessTest.java @@ -49,6 +49,7 @@ import androidx.test.filters.SmallTest; import com.android.systemui.SysuiTestCase; import com.android.systemui.keyguard.WakefulnessLifecycle; import com.android.systemui.statusbar.phone.DozeParameters; +import com.android.systemui.statusbar.phone.UnlockedScreenOffAnimationController; import com.android.systemui.util.concurrency.FakeExecutor; import com.android.systemui.util.concurrency.FakeThreadFactory; import com.android.systemui.util.sensors.AsyncSensorManager; @@ -82,6 +83,8 @@ public class DozeScreenBrightnessTest extends SysuiTestCase { WakefulnessLifecycle mWakefulnessLifecycle; @Mock DozeParameters mDozeParameters; + @Mock + private UnlockedScreenOffAnimationController mUnlockedScreenOffAnimationController; private FakeExecutor mFakeExecutor = new FakeExecutor(new FakeSystemClock()); private FakeThreadFactory mFakeThreadFactory = new FakeThreadFactory(mFakeExecutor); @@ -109,8 +112,8 @@ public class DozeScreenBrightnessTest extends SysuiTestCase { mSensor = fakeSensorManager.getFakeLightSensor(); mScreen = new DozeScreenBrightness(mContext, mServiceFake, mSensorManager, Optional.of(mSensor.getSensor()), mDozeHost, null /* handler */, - mAlwaysOnDisplayPolicy, mWakefulnessLifecycle, mDozeParameters); - + mAlwaysOnDisplayPolicy, mWakefulnessLifecycle, mDozeParameters, + mUnlockedScreenOffAnimationController); mScreen.onScreenState(Display.STATE_ON); } @@ -175,7 +178,8 @@ public class DozeScreenBrightnessTest extends SysuiTestCase { public void testPulsing_withoutLightSensor_setsAoDDimmingScrimTransparent() throws Exception { mScreen = new DozeScreenBrightness(mContext, mServiceFake, mSensorManager, Optional.empty() /* sensor */, mDozeHost, null /* handler */, - mAlwaysOnDisplayPolicy, mWakefulnessLifecycle, mDozeParameters); + mAlwaysOnDisplayPolicy, mWakefulnessLifecycle, mDozeParameters, + mUnlockedScreenOffAnimationController); mScreen.transitionTo(UNINITIALIZED, INITIALIZED); mScreen.transitionTo(INITIALIZED, DOZE); reset(mDozeHost); @@ -216,7 +220,8 @@ public class DozeScreenBrightnessTest extends SysuiTestCase { public void testNullSensor() throws Exception { mScreen = new DozeScreenBrightness(mContext, mServiceFake, mSensorManager, Optional.empty() /* sensor */, mDozeHost, null /* handler */, - mAlwaysOnDisplayPolicy, mWakefulnessLifecycle, mDozeParameters); + mAlwaysOnDisplayPolicy, mWakefulnessLifecycle, mDozeParameters, + mUnlockedScreenOffAnimationController); mScreen.transitionTo(UNINITIALIZED, INITIALIZED); mScreen.transitionTo(INITIALIZED, DOZE_AOD); @@ -296,13 +301,14 @@ public class DozeScreenBrightnessTest extends SysuiTestCase { when(mWakefulnessLifecycle.getLastSleepReason()).thenReturn( PowerManager.GO_TO_SLEEP_REASON_TIMEOUT); when(mDozeParameters.shouldControlUnlockedScreenOff()).thenReturn(true); + when(mUnlockedScreenOffAnimationController.isScreenOffAnimationPlaying()).thenReturn(true); mScreen.transitionTo(UNINITIALIZED, INITIALIZED); mScreen.transitionTo(INITIALIZED, DOZE); // If we're dozing after a timeout, and playing the unlocked screen animation, we should - // stay at dim brightness, because the screen dims just before timeout. - assertEquals(mServiceFake.screenBrightness, DIM_BRIGHTNESS); + // stay at or below dim brightness, because the screen dims just before timeout. + assertTrue(mServiceFake.screenBrightness <= DIM_BRIGHTNESS); } @Test @@ -310,6 +316,7 @@ public class DozeScreenBrightnessTest extends SysuiTestCase { when(mWakefulnessLifecycle.getLastSleepReason()).thenReturn( PowerManager.GO_TO_SLEEP_REASON_POWER_BUTTON); when(mDozeParameters.shouldControlUnlockedScreenOff()).thenReturn(true); + when(mUnlockedScreenOffAnimationController.isScreenOffAnimationPlaying()).thenReturn(true); mScreen.transitionTo(UNINITIALIZED, INITIALIZED); mScreen.transitionTo(INITIALIZED, DOZE); @@ -324,6 +331,7 @@ public class DozeScreenBrightnessTest extends SysuiTestCase { when(mWakefulnessLifecycle.getLastSleepReason()).thenReturn( PowerManager.GO_TO_SLEEP_REASON_TIMEOUT); when(mDozeParameters.shouldControlUnlockedScreenOff()).thenReturn(false); + when(mUnlockedScreenOffAnimationController.isScreenOffAnimationPlaying()).thenReturn(false); mScreen.transitionTo(UNINITIALIZED, INITIALIZED); mScreen.transitionTo(INITIALIZED, DOZE); diff --git a/packages/SystemUI/tests/src/com/android/systemui/doze/DozeScreenStateTest.java b/packages/SystemUI/tests/src/com/android/systemui/doze/DozeScreenStateTest.java index 41d7fd64fe7a..3e19cc436dca 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/doze/DozeScreenStateTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/doze/DozeScreenStateTest.java @@ -21,6 +21,7 @@ import static com.android.systemui.doze.DozeMachine.State.DOZE_AOD; import static com.android.systemui.doze.DozeMachine.State.DOZE_AOD_DOCKED; import static com.android.systemui.doze.DozeMachine.State.DOZE_AOD_PAUSED; import static com.android.systemui.doze.DozeMachine.State.DOZE_AOD_PAUSING; +import static com.android.systemui.doze.DozeMachine.State.DOZE_PULSE_DONE; import static com.android.systemui.doze.DozeMachine.State.DOZE_PULSING; import static com.android.systemui.doze.DozeMachine.State.DOZE_REQUEST_PULSE; import static com.android.systemui.doze.DozeMachine.State.FINISH; @@ -34,6 +35,7 @@ import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertThat; import static org.junit.Assert.assertTrue; import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.anyInt; import static org.mockito.Mockito.doAnswer; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; @@ -45,6 +47,8 @@ import androidx.test.filters.SmallTest; import androidx.test.runner.AndroidJUnit4; import com.android.systemui.SysuiTestCase; +import com.android.systemui.biometrics.AuthController; +import com.android.systemui.biometrics.UdfpsController; import com.android.systemui.statusbar.phone.DozeParameters; import com.android.systemui.util.wakelock.WakeLockFake; import com.android.systemui.utils.os.FakeHandler; @@ -56,6 +60,8 @@ import org.mockito.ArgumentCaptor; import org.mockito.Mock; import org.mockito.MockitoAnnotations; +import javax.inject.Provider; + @RunWith(AndroidJUnit4.class) @SmallTest public class DozeScreenStateTest extends SysuiTestCase { @@ -68,17 +74,32 @@ public class DozeScreenStateTest extends SysuiTestCase { private DozeParameters mDozeParameters; private WakeLockFake mWakeLock; private DozeScreenState mScreen; + @Mock + private Provider<UdfpsController> mUdfpsControllerProvider; + @Mock + private AuthController mAuthController; + @Mock + private UdfpsController mUdfpsController; + @Mock + private DozeLog mDozeLog; + @Mock + private DozeScreenBrightness mDozeScreenBrightness; @Before public void setUp() throws Exception { MockitoAnnotations.initMocks(this); when(mDozeParameters.getDisplayNeedsBlanking()).thenReturn(true); when(mDozeParameters.getAlwaysOn()).thenReturn(true); + when(mUdfpsControllerProvider.get()).thenReturn(mUdfpsController); + when(mAuthController.isUdfpsEnrolled(anyInt())).thenReturn(true); + when(mUdfpsController.isFingerDown()).thenReturn(false); + mServiceFake = new DozeServiceFake(); mHandlerFake = new FakeHandler(Looper.getMainLooper()); mWakeLock = new WakeLockFake(); mScreen = new DozeScreenState(mServiceFake, mHandlerFake, mDozeHost, mDozeParameters, - mWakeLock); + mWakeLock, mAuthController, mUdfpsControllerProvider, mDozeLog, + mDozeScreenBrightness); } @Test @@ -233,4 +254,56 @@ public class DozeScreenStateTest extends SysuiTestCase { assertEquals(Display.STATE_OFF, mServiceFake.screenState); } + @Test + public void testDelayEnterDozeScreenState_whenUdfpsFingerDown() { + // GIVEN AOD is initialized + when(mDozeParameters.shouldControlScreenOff()).thenReturn(true); + mHandlerFake.setMode(QUEUEING); + mScreen.transitionTo(UNINITIALIZED, INITIALIZED); + mHandlerFake.dispatchQueuedMessages(); + + mScreen.transitionTo(INITIALIZED, DOZE_AOD); + + // WHEN udfps is activated (fingerDown) + when(mUdfpsController.isFingerDown()).thenReturn(true); + mHandlerFake.dispatchQueuedMessages(); + + // THEN the display screen state doesn't immediately change + assertEquals(Display.STATE_ON, mServiceFake.screenState); + + // WHEN udfpsController finger is no longer down and the queued messages are run + when(mUdfpsController.isFingerDown()).thenReturn(false); + mHandlerFake.dispatchQueuedMessages(); + + // THEN the display screen state will change + assertEquals(Display.STATE_DOZE_SUSPEND, mServiceFake.screenState); + } + + @Test + public void testDelayExitPulsingScreenState_whenUdfpsFingerDown() { + // GIVEN we're pulsing + when(mDozeParameters.shouldControlScreenOff()).thenReturn(true); + mHandlerFake.setMode(QUEUEING); + mScreen.transitionTo(UNINITIALIZED, INITIALIZED); + mScreen.transitionTo(INITIALIZED, DOZE_AOD); + mScreen.transitionTo(DOZE_AOD, DOZE_REQUEST_PULSE); + mScreen.transitionTo(DOZE_REQUEST_PULSE, DOZE_PULSING); + mScreen.transitionTo(DOZE_PULSING, DOZE_PULSE_DONE); + mHandlerFake.dispatchQueuedMessages(); + + // WHEN udfps is activated while are transitioning back to DOZE_AOD + mScreen.transitionTo(DOZE_PULSE_DONE, DOZE_AOD); + when(mUdfpsController.isFingerDown()).thenReturn(true); + mHandlerFake.dispatchQueuedMessages(); + + // THEN the display screen state doesn't immediately change + assertEquals(Display.STATE_ON, mServiceFake.screenState); + + // WHEN udfpsController finger is no longer down and the queued messages are run + when(mUdfpsController.isFingerDown()).thenReturn(false); + mHandlerFake.dispatchQueuedMessages(); + + // THEN the display screen state will change + assertEquals(Display.STATE_DOZE_SUSPEND, mServiceFake.screenState); + } }
\ No newline at end of file diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/LockIconViewControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/keyguard/LockIconViewControllerTest.java new file mode 100644 index 000000000000..9c3016c57ccf --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/LockIconViewControllerTest.java @@ -0,0 +1,195 @@ +/* + * 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.keyguard; + +import static junit.framework.Assert.assertEquals; + +import static org.mockito.Mockito.anyInt; +import static org.mockito.Mockito.eq; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import android.content.Context; +import android.content.res.Resources; +import android.graphics.PointF; +import android.hardware.fingerprint.FingerprintSensorPropertiesInternal; +import android.os.Vibrator; +import android.testing.AndroidTestingRunner; +import android.testing.TestableLooper; +import android.util.DisplayMetrics; +import android.view.View; +import android.view.accessibility.AccessibilityManager; + +import androidx.test.filters.SmallTest; + +import com.android.keyguard.KeyguardUpdateMonitor; +import com.android.keyguard.KeyguardViewController; +import com.android.keyguard.LockIconView; +import com.android.keyguard.LockIconViewController; +import com.android.systemui.SysuiTestCase; +import com.android.systemui.biometrics.AuthController; +import com.android.systemui.biometrics.AuthRippleController; +import com.android.systemui.dump.DumpManager; +import com.android.systemui.plugins.FalsingManager; +import com.android.systemui.plugins.statusbar.StatusBarStateController; +import com.android.systemui.statusbar.policy.ConfigurationController; +import com.android.systemui.statusbar.policy.KeyguardStateController; +import com.android.systemui.util.concurrency.DelayableExecutor; + +import com.airbnb.lottie.LottieAnimationView; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.ArgumentCaptor; +import org.mockito.Captor; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +import java.util.ArrayList; +import java.util.List; + +@SmallTest +@RunWith(AndroidTestingRunner.class) +@TestableLooper.RunWithLooper +public class LockIconViewControllerTest extends SysuiTestCase { + private @Mock LockIconView mLockIconView; + private @Mock Context mContext; + private @Mock Resources mResources; + private @Mock DisplayMetrics mDisplayMetrics; + private @Mock StatusBarStateController mStatusBarStateController; + private @Mock KeyguardUpdateMonitor mKeyguardUpdateMonitor; + private @Mock KeyguardViewController mKeyguardViewController; + private @Mock KeyguardStateController mKeyguardStateController; + private @Mock FalsingManager mFalsingManager; + private @Mock AuthController mAuthController; + private @Mock DumpManager mDumpManager; + private @Mock AccessibilityManager mAccessibilityManager; + private @Mock ConfigurationController mConfigurationController; + private @Mock DelayableExecutor mDelayableExecutor; + private @Mock Vibrator mVibrator; + private @Mock AuthRippleController mAuthRippleController; + private @Mock LottieAnimationView mAodFp; + + private LockIconViewController mLockIconViewController; + + // Capture listeners so that they can be used to send events + @Captor private ArgumentCaptor<View.OnAttachStateChangeListener> mAttachCaptor = + ArgumentCaptor.forClass(View.OnAttachStateChangeListener.class); + private View.OnAttachStateChangeListener mAttachListener; + + @Captor private ArgumentCaptor<AuthController.Callback> mAuthControllerCallbackCaptor; + private AuthController.Callback mAuthControllerCallback; + + @Captor private ArgumentCaptor<PointF> mPointCaptor; + + @Before + public void setUp() throws Exception { + MockitoAnnotations.initMocks(this); + + when(mLockIconView.getResources()).thenReturn(mResources); + when(mLockIconView.getContext()).thenReturn(mContext); + when(mContext.getResources()).thenReturn(mResources); + when(mResources.getDisplayMetrics()).thenReturn(mDisplayMetrics); + when(mLockIconView.findViewById(anyInt())).thenReturn(mAodFp); + + mLockIconViewController = new LockIconViewController( + mLockIconView, + mStatusBarStateController, + mKeyguardUpdateMonitor, + mKeyguardViewController, + mKeyguardStateController, + mFalsingManager, + mAuthController, + mDumpManager, + mAccessibilityManager, + mConfigurationController, + mDelayableExecutor, + mVibrator + ); + } + + @Test + public void testUpdateFingerprintLocationOnInit() { + // GIVEN fp sensor location is available pre-attached + final PointF udfpsLocation = new PointF(50, 75); + final int radius = 33; + final FingerprintSensorPropertiesInternal fpProps = + new FingerprintSensorPropertiesInternal( + /* sensorId */ 0, + /* strength */ 0, + /* max enrollments per user */ 5, + /* component info */ new ArrayList<>(), + /* sensorType */ 3, + /* resetLockoutRequiresHwToken */ false, + (int) udfpsLocation.x, (int) udfpsLocation.y, radius); + when(mAuthController.getUdfpsSensorLocation()).thenReturn(udfpsLocation); + when(mAuthController.getUdfpsProps()).thenReturn(List.of(fpProps)); + + // WHEN lock icon view controller is initialized and attached + mLockIconViewController.init(); + captureAttachListener(); + mAttachListener.onViewAttachedToWindow(mLockIconView); + + // THEN lock icon view location is updated with the same coordinates as fpProps + verify(mLockIconView).setCenterLocation(mPointCaptor.capture(), eq(radius)); + assertEquals(udfpsLocation, mPointCaptor.getValue()); + } + + @Test + public void testUpdateFingerprintLocationOnAuthenticatorsRegistered() { + // GIVEN fp sensor location is not available pre-init + when(mAuthController.getFingerprintSensorLocation()).thenReturn(null); + when(mAuthController.getUdfpsProps()).thenReturn(null); + mLockIconViewController.init(); + captureAttachListener(); + mAttachListener.onViewAttachedToWindow(mLockIconView); + + // GIVEN fp sensor location is available post-atttached + captureAuthControllerCallback(); + final PointF udfpsLocation = new PointF(50, 75); + final int radius = 33; + final FingerprintSensorPropertiesInternal fpProps = + new FingerprintSensorPropertiesInternal( + /* sensorId */ 0, + /* strength */ 0, + /* max enrollments per user */ 5, + /* component info */ new ArrayList<>(), + /* sensorType */ 3, + /* resetLockoutRequiresHwToken */ false, + (int) udfpsLocation.x, (int) udfpsLocation.y, radius); + when(mAuthController.getUdfpsSensorLocation()).thenReturn(udfpsLocation); + when(mAuthController.getUdfpsProps()).thenReturn(List.of(fpProps)); + + // WHEN all authenticators are registered + mAuthControllerCallback.onAllAuthenticatorsRegistered(); + + // THEN lock icon view location is updated with the same coordinates as fpProps + verify(mLockIconView).setCenterLocation(mPointCaptor.capture(), eq(radius)); + assertEquals(udfpsLocation, mPointCaptor.getValue()); + } + + private void captureAuthControllerCallback() { + verify(mAuthController).addCallback(mAuthControllerCallbackCaptor.capture()); + mAuthControllerCallback = mAuthControllerCallbackCaptor.getValue(); + } + + private void captureAttachListener() { + verify(mLockIconView).addOnAttachStateChangeListener(mAttachCaptor.capture()); + mAttachListener = mAttachCaptor.getValue(); + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationShadeDepthControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationShadeDepthControllerTest.kt index 7c045c1f2894..a7b14460f925 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationShadeDepthControllerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationShadeDepthControllerTest.kt @@ -69,7 +69,6 @@ class NotificationShadeDepthControllerTest : SysuiTestCase() { @Mock private lateinit var root: View @Mock private lateinit var viewRootImpl: ViewRootImpl @Mock private lateinit var windowToken: IBinder - @Mock private lateinit var shadeSpring: NotificationShadeDepthController.DepthAnimation @Mock private lateinit var shadeAnimation: NotificationShadeDepthController.DepthAnimation @Mock private lateinit var brightnessSpring: NotificationShadeDepthController.DepthAnimation @Mock private lateinit var listener: NotificationShadeDepthController.DepthListener @@ -89,10 +88,10 @@ class NotificationShadeDepthControllerTest : SysuiTestCase() { `when`(root.isAttachedToWindow).thenReturn(true) `when`(statusBarStateController.state).then { statusBarState } `when`(blurUtils.blurRadiusOfRatio(anyFloat())).then { answer -> - (answer.arguments[0] as Float * maxBlur).toInt() + answer.arguments[0] as Float * maxBlur.toFloat() } - `when`(blurUtils.ratioOfBlurRadius(anyInt())).then { answer -> - answer.arguments[0] as Int / maxBlur.toFloat() + `when`(blurUtils.ratioOfBlurRadius(anyFloat())).then { answer -> + answer.arguments[0] as Float / maxBlur.toFloat() } `when`(blurUtils.supportsBlursOnWindows()).thenReturn(true) `when`(blurUtils.maxBlurRadius).thenReturn(maxBlur) @@ -102,7 +101,6 @@ class NotificationShadeDepthControllerTest : SysuiTestCase() { statusBarStateController, blurUtils, biometricUnlockController, keyguardStateController, choreographer, wallpaperManager, notificationShadeWindowController, dozeParameters, dumpManager) - notificationShadeDepthController.shadeSpring = shadeSpring notificationShadeDepthController.shadeAnimation = shadeAnimation notificationShadeDepthController.brightnessMirrorSpring = brightnessSpring notificationShadeDepthController.root = root @@ -123,7 +121,6 @@ class NotificationShadeDepthControllerTest : SysuiTestCase() { fun onPanelExpansionChanged_apliesBlur_ifShade() { notificationShadeDepthController.onPanelExpansionChanged(1f /* expansion */, false /* tracking */) - verify(shadeSpring).animateTo(eq(maxBlur), any()) verify(shadeAnimation).animateTo(eq(maxBlur), any()) } @@ -172,12 +169,10 @@ class NotificationShadeDepthControllerTest : SysuiTestCase() { @Test fun onStateChanged_reevalutesBlurs_ifSameRadiusAndNewState() { onPanelExpansionChanged_apliesBlur_ifShade() - clearInvocations(shadeSpring) - clearInvocations(shadeAnimation) + clearInvocations(choreographer) statusBarState = StatusBarState.KEYGUARD statusBarStateListener.onStateChanged(statusBarState) - verify(shadeSpring).animateTo(eq(0), any()) verify(shadeAnimation).animateTo(eq(0), any()) } @@ -186,7 +181,7 @@ class NotificationShadeDepthControllerTest : SysuiTestCase() { notificationShadeDepthController.qsPanelExpansion = 1f notificationShadeDepthController.onPanelExpansionChanged(0.5f, tracking = false) notificationShadeDepthController.updateBlurCallback.doFrame(0) - verify(blurUtils).applyBlur(any(), eq(maxBlur / 2), eq(false)) + verify(blurUtils).applyBlur(any(), anyInt(), eq(false)) } @Test @@ -207,10 +202,10 @@ class NotificationShadeDepthControllerTest : SysuiTestCase() { fun setFullShadeTransition_appliesBlur_onlyIfSupported() { reset(blurUtils) `when`(blurUtils.blurRadiusOfRatio(anyFloat())).then { answer -> - (answer.arguments[0] as Float * maxBlur).toInt() + answer.arguments[0] as Float * maxBlur } - `when`(blurUtils.ratioOfBlurRadius(anyInt())).then { answer -> - answer.arguments[0] as Int / maxBlur.toFloat() + `when`(blurUtils.ratioOfBlurRadius(anyFloat())).then { answer -> + answer.arguments[0] as Float / maxBlur.toFloat() } `when`(blurUtils.maxBlurRadius).thenReturn(maxBlur) `when`(blurUtils.maxBlurRadius).thenReturn(maxBlur) @@ -239,16 +234,16 @@ class NotificationShadeDepthControllerTest : SysuiTestCase() { @Test fun updateBlurCallback_setsBlur_whenExpanded() { - `when`(shadeSpring.radius).thenReturn(maxBlur) - `when`(shadeAnimation.radius).thenReturn(maxBlur) + notificationShadeDepthController.onPanelExpansionChanged(1f, false) + `when`(shadeAnimation.radius).thenReturn(maxBlur.toFloat()) notificationShadeDepthController.updateBlurCallback.doFrame(0) verify(blurUtils).applyBlur(any(), eq(maxBlur), eq(false)) } @Test fun updateBlurCallback_ignoreShadeBlurUntilHidden_overridesZoom() { - `when`(shadeSpring.radius).thenReturn(maxBlur) - `when`(shadeAnimation.radius).thenReturn(maxBlur) + notificationShadeDepthController.onPanelExpansionChanged(1f, false) + `when`(shadeAnimation.radius).thenReturn(maxBlur.toFloat()) notificationShadeDepthController.blursDisabledForAppLaunch = true notificationShadeDepthController.updateBlurCallback.doFrame(0) verify(blurUtils).applyBlur(any(), eq(0), eq(false)) @@ -293,8 +288,8 @@ class NotificationShadeDepthControllerTest : SysuiTestCase() { // Brightness mirror is fully visible `when`(brightnessSpring.ratio).thenReturn(1f) // And shade is blurred - `when`(shadeSpring.radius).thenReturn(maxBlur) - `when`(shadeAnimation.radius).thenReturn(maxBlur) + notificationShadeDepthController.onPanelExpansionChanged(1f, false) + `when`(shadeAnimation.radius).thenReturn(maxBlur.toFloat()) notificationShadeDepthController.updateBlurCallback.doFrame(0) verify(notificationShadeWindowController).setBackgroundBlurRadius(eq(0)) @@ -304,10 +299,8 @@ class NotificationShadeDepthControllerTest : SysuiTestCase() { @Test fun ignoreShadeBlurUntilHidden_whennNull_ignoresIfShadeHasNoBlur() { - `when`(shadeSpring.radius).thenReturn(0) - `when`(shadeAnimation.radius).thenReturn(0) + `when`(shadeAnimation.radius).thenReturn(0f) notificationShadeDepthController.blursDisabledForAppLaunch = true - verify(shadeSpring, never()).animateTo(anyInt(), any()) verify(shadeAnimation, never()).animateTo(anyInt(), any()) } }
\ No newline at end of file diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java index b03df880f0ba..4151ab2044f4 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java @@ -290,6 +290,8 @@ public class NotificationStackScrollLayoutTest extends SysuiTestCase { @Test public void testUpdateFooter_noNotifications() { setBarStateForTest(StatusBarState.SHADE); + mStackScroller.setCurrentUserSetup(true); + FooterView view = mock(FooterView.class); mStackScroller.setFooterView(view); mStackScroller.updateFooter(); @@ -299,6 +301,7 @@ public class NotificationStackScrollLayoutTest extends SysuiTestCase { @Test public void testUpdateFooter_remoteInput() { setBarStateForTest(StatusBarState.SHADE); + mStackScroller.setCurrentUserSetup(true); ExpandableNotificationRow row = mock(ExpandableNotificationRow.class); when(row.canViewBeDismissed()).thenReturn(true); @@ -318,6 +321,7 @@ public class NotificationStackScrollLayoutTest extends SysuiTestCase { @Test public void testUpdateFooter_oneClearableNotification() { setBarStateForTest(StatusBarState.SHADE); + mStackScroller.setCurrentUserSetup(true); when(mEmptyShadeView.getVisibility()).thenReturn(GONE); when(mStackScrollLayoutController.hasActiveClearableNotifications(ROWS_ALL)) @@ -331,8 +335,25 @@ public class NotificationStackScrollLayoutTest extends SysuiTestCase { } @Test + public void testUpdateFooter_oneClearableNotification_beforeUserSetup() { + setBarStateForTest(StatusBarState.SHADE); + mStackScroller.setCurrentUserSetup(false); + + when(mEmptyShadeView.getVisibility()).thenReturn(GONE); + when(mStackScrollLayoutController.hasActiveClearableNotifications(ROWS_ALL)) + .thenReturn(true); + when(mStackScrollLayoutController.hasActiveNotifications()).thenReturn(true); + + FooterView view = mock(FooterView.class); + mStackScroller.setFooterView(view); + mStackScroller.updateFooter(); + verify(mStackScroller).updateFooterView(false, true, true); + } + + @Test public void testUpdateFooter_oneNonClearableNotification() { setBarStateForTest(StatusBarState.SHADE); + mStackScroller.setCurrentUserSetup(true); ExpandableNotificationRow row = mock(ExpandableNotificationRow.class); when(row.canViewBeDismissed()).thenReturn(false); @@ -351,6 +372,8 @@ public class NotificationStackScrollLayoutTest extends SysuiTestCase { @Test public void testUpdateFooter_atEnd() { + mStackScroller.setCurrentUserSetup(true); + // add footer mStackScroller.inflateFooterView(); diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollerControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollerControllerTest.java index f376e88b2cb1..42f38891b1bb 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollerControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollerControllerTest.java @@ -74,6 +74,7 @@ import com.android.systemui.statusbar.phone.ScrimController; import com.android.systemui.statusbar.phone.ShadeController; import com.android.systemui.statusbar.phone.StatusBar; import com.android.systemui.statusbar.policy.ConfigurationController; +import com.android.systemui.statusbar.policy.DeviceProvisionedController; import com.android.systemui.statusbar.policy.ZenModeController; import com.android.systemui.tuner.TunerService; @@ -98,6 +99,7 @@ public class NotificationStackScrollerControllerTest extends SysuiTestCase { @Mock private HeadsUpManagerPhone mHeadsUpManager; @Mock private NotificationRoundnessManager mNotificationRoundnessManager; @Mock private TunerService mTunerService; + @Mock private DeviceProvisionedController mDeviceProvisionedController; @Mock private DynamicPrivacyController mDynamicPrivacyController; @Mock private ConfigurationController mConfigurationController; @Mock private NotificationStackScrollLayout mNotificationStackScrollLayout; @@ -153,6 +155,7 @@ public class NotificationStackScrollerControllerTest extends SysuiTestCase { mHeadsUpManager, mNotificationRoundnessManager, mTunerService, + mDeviceProvisionedController, mDynamicPrivacyController, mConfigurationController, mSysuiStatusBarStateController, 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 60f0b68acac3..4276f7ce7b12 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 @@ -274,6 +274,26 @@ public class BiometricsUnlockControllerTest extends SysuiTestCase { } @Test + public void onBiometricAuthenticated_onLockScreen() { + // GIVEN not dozing + when(mUpdateMonitor.isDeviceInteractive()).thenReturn(true); + + // WHEN we want to unlock collapse + mBiometricUnlockController.startWakeAndUnlock( + BiometricUnlockController.MODE_UNLOCK_COLLAPSING); + + // THEN we collpase the panels and notify authenticated + verify(mShadeController).animateCollapsePanels( + /* flags */ anyInt(), + /* force */ eq(true), + /* delayed */ eq(false), + /* speedUpFactor */ anyFloat() + ); + verify(mStatusBarKeyguardViewManager).notifyKeyguardAuthenticated( + /* strongAuth */ eq(false)); + } + + @Test public void onBiometricAuthenticated_whenFace_noBypass_encrypted_doNothing() { reset(mUpdateMonitor); mBiometricUnlockController.setKeyguardViewController(mStatusBarKeyguardViewManager); diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationShadeWindowViewTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationShadeWindowViewTest.java index 6c1a3c90d83d..9b7c78f7dba1 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationShadeWindowViewTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationShadeWindowViewTest.java @@ -27,6 +27,7 @@ import android.view.MotionEvent; import androidx.test.filters.SmallTest; +import com.android.keyguard.LockIconViewController; import com.android.systemui.R; import com.android.systemui.SystemUIFactory; import com.android.systemui.SysuiTestCase; @@ -90,6 +91,7 @@ public class NotificationShadeWindowViewTest extends SysuiTestCase { @Mock private NotificationStackScrollLayoutController mNotificationStackScrollLayoutController; @Mock private StatusBarKeyguardViewManager mStatusBarKeyguardViewManager; @Mock private LockscreenShadeTransitionController mLockscreenShadeTransitionController; + @Mock private LockIconViewController mLockIconViewController; @Before public void setUp() { @@ -131,7 +133,8 @@ public class NotificationShadeWindowViewTest extends SysuiTestCase { mNotificationPanelViewController, mStatusBarViewFactory, mNotificationStackScrollLayoutController, - mStatusBarKeyguardViewManager); + mStatusBarKeyguardViewManager, + mLockIconViewController); mController.setupExpandedStatusBar(); mController.setService(mStatusBar, mNotificationShadeWindowController); mController.setDragDownHelper(mDragDownHelper); diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ScrimControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ScrimControllerTest.java index 678b193073c2..6de58667fb01 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ScrimControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ScrimControllerTest.java @@ -16,10 +16,13 @@ package com.android.systemui.statusbar.phone; +import static com.android.systemui.statusbar.phone.ScrimController.KEYGUARD_SCRIM_ALPHA; import static com.android.systemui.statusbar.phone.ScrimController.OPAQUE; import static com.android.systemui.statusbar.phone.ScrimController.SEMI_TRANSPARENT; import static com.android.systemui.statusbar.phone.ScrimController.TRANSPARENT; +import static com.google.common.truth.Truth.assertThat; + import static org.junit.Assert.assertEquals; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyFloat; @@ -734,20 +737,13 @@ public class ScrimControllerTest extends SysuiTestCase { } @Test - public void transitionToUnlockedFromAod() { - // Simulate unlock with fingerprint - mScrimController.transitionTo(ScrimState.AOD); + public void transitionToUnlockedFromOff() { + // Simulate unlock with fingerprint without AOD + mScrimController.transitionTo(ScrimState.OFF); mScrimController.setPanelExpansion(0f); finishAnimationsImmediately(); mScrimController.transitionTo(ScrimState.UNLOCKED); - // Immediately tinted black after the transition starts - assertScrimTinted(Map.of( - mScrimInFront, true, - mScrimBehind, true, - mScrimForBubble, true - )); - finishAnimationsImmediately(); // All scrims should be transparent at the end of fade transition. @@ -765,6 +761,28 @@ public class ScrimControllerTest extends SysuiTestCase { } @Test + public void transitionToUnlockedFromAod() { + // Simulate unlock with fingerprint + mScrimController.transitionTo(ScrimState.AOD); + mScrimController.setPanelExpansion(0f); + finishAnimationsImmediately(); + mScrimController.transitionTo(ScrimState.UNLOCKED); + + finishAnimationsImmediately(); + + // All scrims should be transparent at the end of fade transition. + assertScrimAlpha(Map.of( + mScrimInFront, TRANSPARENT, + mScrimBehind, TRANSPARENT)); + + // Make sure at the very end of the animation, we're reset to transparent + assertScrimTinted(Map.of( + mScrimInFront, false, + mScrimBehind, true + )); + } + + @Test public void scrimBlanksBeforeLeavingAod() { // Simulate unlock with fingerprint mScrimController.transitionTo(ScrimState.AOD); @@ -1081,6 +1099,26 @@ public class ScrimControllerTest extends SysuiTestCase { } @Test + public void testDoesntAnimate_whenUnlocking() { + // LightRevealScrim will animate the transition, we should only hide the keyguard scrims. + ScrimState.UNLOCKED.prepare(ScrimState.KEYGUARD); + assertThat(ScrimState.UNLOCKED.getAnimateChange()).isTrue(); + ScrimState.UNLOCKED.prepare(ScrimState.PULSING); + assertThat(ScrimState.UNLOCKED.getAnimateChange()).isFalse(); + + ScrimState.UNLOCKED.prepare(ScrimState.KEYGUARD); + assertThat(ScrimState.UNLOCKED.getAnimateChange()).isTrue(); + ScrimState.UNLOCKED.prepare(ScrimState.AOD); + assertThat(ScrimState.UNLOCKED.getAnimateChange()).isFalse(); + + // LightRevealScrim doesn't animate when AOD is disabled. We need to use the legacy anim. + ScrimState.UNLOCKED.prepare(ScrimState.KEYGUARD); + assertThat(ScrimState.UNLOCKED.getAnimateChange()).isTrue(); + ScrimState.UNLOCKED.prepare(ScrimState.OFF); + assertThat(ScrimState.UNLOCKED.getAnimateChange()).isTrue(); + } + + @Test public void testScrimsVisible_whenShadeVisible_clippingQs() { mScrimController.setClipsQsScrim(true); mScrimController.transitionTo(ScrimState.UNLOCKED); @@ -1138,6 +1176,21 @@ public class ScrimControllerTest extends SysuiTestCase { } @Test + public void testNotificationTransparency_unnocclusion() { + mScrimController.transitionTo(ScrimState.KEYGUARD); + mScrimController.setUnocclusionAnimationRunning(true); + + assertAlphaAfterExpansion(mNotificationsScrim, /* alpha */ KEYGUARD_SCRIM_ALPHA, + /* expansion */ 0.0f); + assertAlphaAfterExpansion(mNotificationsScrim, /* alpha */ KEYGUARD_SCRIM_ALPHA, + /* expansion */ 1.0f); + + // Verify normal behavior after + mScrimController.setUnocclusionAnimationRunning(false); + assertAlphaAfterExpansion(mNotificationsScrim, /* alpha */ 0.2f, /* expansion */ 0.4f); + } + + @Test public void testNotificationTransparency_inKeyguardState() { mScrimController.transitionTo(ScrimState.KEYGUARD); diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarTest.java index 2c2833a864b9..bd9835c0e8e3 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarTest.java @@ -812,6 +812,30 @@ public class StatusBarTest extends SysuiTestCase { } @Test + public void testTransitionLaunch_goesToUnlocked() { + mStatusBar.setBarStateForTest(StatusBarState.KEYGUARD); + mStatusBar.showKeyguardImpl(); + + // Starting a pulse should change the scrim controller to the pulsing state + when(mNotificationPanelViewController.isLaunchTransitionRunning()).thenReturn(true); + when(mNotificationPanelViewController.isLaunchingAffordanceWithPreview()).thenReturn(true); + mStatusBar.updateScrimController(); + verify(mScrimController).transitionTo(eq(ScrimState.UNLOCKED), any()); + } + + @Test + public void testTransitionLaunch_noPreview_doesntGoUnlocked() { + mStatusBar.setBarStateForTest(StatusBarState.KEYGUARD); + mStatusBar.showKeyguardImpl(); + + // Starting a pulse should change the scrim controller to the pulsing state + when(mNotificationPanelViewController.isLaunchTransitionRunning()).thenReturn(true); + when(mNotificationPanelViewController.isLaunchingAffordanceWithPreview()).thenReturn(false); + mStatusBar.updateScrimController(); + verify(mScrimController).transitionTo(eq(ScrimState.KEYGUARD)); + } + + @Test public void testSetOccluded_propagatesToScrimController() { mStatusBar.setOccluded(true); verify(mScrimController).setKeyguardOccluded(eq(true)); diff --git a/services/core/java/com/android/server/RescueParty.java b/services/core/java/com/android/server/RescueParty.java index c3543e7ba368..c1c9fbb121a8 100644 --- a/services/core/java/com/android/server/RescueParty.java +++ b/services/core/java/com/android/server/RescueParty.java @@ -327,18 +327,23 @@ public class RescueParty { } } - private static int getMaxRescueLevel() { - return SystemProperties.getBoolean(PROP_DISABLE_FACTORY_RESET_FLAG, false) - ? LEVEL_RESET_SETTINGS_TRUSTED_DEFAULTS : LEVEL_FACTORY_RESET; + private static int getMaxRescueLevel(boolean mayPerformFactoryReset) { + if (!mayPerformFactoryReset + || SystemProperties.getBoolean(PROP_DISABLE_FACTORY_RESET_FLAG, false)) { + return LEVEL_RESET_SETTINGS_TRUSTED_DEFAULTS; + } + return LEVEL_FACTORY_RESET; } /** * Get the rescue level to perform if this is the n-th attempt at mitigating failure. * * @param mitigationCount: the mitigation attempt number (1 = first attempt etc.) + * @param mayPerformFactoryReset: whether or not a factory reset may be performed for the given + * failure. * @return the rescue level for the n-th mitigation attempt. */ - private static int getRescueLevel(int mitigationCount) { + private static int getRescueLevel(int mitigationCount, boolean mayPerformFactoryReset) { if (mitigationCount == 1) { return LEVEL_RESET_SETTINGS_UNTRUSTED_DEFAULTS; } else if (mitigationCount == 2) { @@ -346,9 +351,9 @@ public class RescueParty { } else if (mitigationCount == 3) { return LEVEL_RESET_SETTINGS_TRUSTED_DEFAULTS; } else if (mitigationCount == 4) { - return Math.min(getMaxRescueLevel(), LEVEL_WARM_REBOOT); + return Math.min(getMaxRescueLevel(mayPerformFactoryReset), LEVEL_WARM_REBOOT); } else if (mitigationCount >= 5) { - return Math.min(getMaxRescueLevel(), LEVEL_FACTORY_RESET); + return Math.min(getMaxRescueLevel(mayPerformFactoryReset), LEVEL_FACTORY_RESET); } else { Slog.w(TAG, "Expected positive mitigation count, was " + mitigationCount); return LEVEL_NONE; @@ -614,7 +619,8 @@ public class RescueParty { @FailureReasons int failureReason, int mitigationCount) { if (!isDisabled() && (failureReason == PackageWatchdog.FAILURE_REASON_APP_CRASH || failureReason == PackageWatchdog.FAILURE_REASON_APP_NOT_RESPONDING)) { - return mapRescueLevelToUserImpact(getRescueLevel(mitigationCount)); + return mapRescueLevelToUserImpact(getRescueLevel(mitigationCount, + mayPerformFactoryReset(failedPackage))); } else { return PackageHealthObserverImpact.USER_IMPACT_NONE; } @@ -628,7 +634,8 @@ public class RescueParty { } if (failureReason == PackageWatchdog.FAILURE_REASON_APP_CRASH || failureReason == PackageWatchdog.FAILURE_REASON_APP_NOT_RESPONDING) { - final int level = getRescueLevel(mitigationCount); + final int level = getRescueLevel(mitigationCount, + mayPerformFactoryReset(failedPackage)); executeRescueLevel(mContext, failedPackage == null ? null : failedPackage.getPackageName(), level); return true; @@ -653,12 +660,7 @@ public class RescueParty { } catch (PackageManager.NameNotFoundException ignore) { } - try { - ApplicationInfo info = pm.getApplicationInfo(packageName, 0); - return (info.flags & PERSISTENT_MASK) == PERSISTENT_MASK; - } catch (PackageManager.NameNotFoundException e) { - return false; - } + return isPersistentSystemApp(packageName); } @Override @@ -666,7 +668,7 @@ public class RescueParty { if (isDisabled()) { return PackageHealthObserverImpact.USER_IMPACT_NONE; } - return mapRescueLevelToUserImpact(getRescueLevel(mitigationCount)); + return mapRescueLevelToUserImpact(getRescueLevel(mitigationCount, true)); } @Override @@ -674,7 +676,8 @@ public class RescueParty { if (isDisabled()) { return false; } - executeRescueLevel(mContext, /*failedPackage=*/ null, getRescueLevel(mitigationCount)); + executeRescueLevel(mContext, /*failedPackage=*/ null, + getRescueLevel(mitigationCount, true)); return true; } @@ -683,6 +686,29 @@ public class RescueParty { return NAME; } + /** + * Returns {@code true} if the failing package is non-null and performing a reboot or + * prompting a factory reset is an acceptable mitigation strategy for the package's + * failure, {@code false} otherwise. + */ + private boolean mayPerformFactoryReset(@Nullable VersionedPackage failingPackage) { + if (failingPackage == null) { + return false; + } + + return isPersistentSystemApp(failingPackage.getPackageName()); + } + + private boolean isPersistentSystemApp(@NonNull String packageName) { + PackageManager pm = mContext.getPackageManager(); + try { + ApplicationInfo info = pm.getApplicationInfo(packageName, 0); + return (info.flags & PERSISTENT_MASK) == PERSISTENT_MASK; + } catch (PackageManager.NameNotFoundException e) { + return false; + } + } + private synchronized void recordDeviceConfigAccess(@NonNull String callingPackage, @NonNull String namespace) { // Record it in calling packages to namespace map diff --git a/services/core/java/com/android/server/SensorPrivacyService.java b/services/core/java/com/android/server/SensorPrivacyService.java index 91b2440f71fe..75d0796a9e83 100644 --- a/services/core/java/com/android/server/SensorPrivacyService.java +++ b/services/core/java/com/android/server/SensorPrivacyService.java @@ -708,6 +708,16 @@ public final class SensorPrivacyService extends SystemService { @Override public void setIndividualSensorPrivacy(@UserIdInt int userId, @SensorPrivacyManager.Sources.Source int source, int sensor, boolean enable) { + if (DEBUG) { + Log.d(TAG, "callingUid=" + Binder.getCallingUid() + + " callingPid=" + Binder.getCallingPid() + + " setIndividualSensorPrivacy(" + + "userId=" + userId + + " source=" + source + + " sensor=" + sensor + + " enable=" + enable + + ")"); + } enforceManageSensorPrivacyPermission(); if (userId == UserHandle.USER_CURRENT) { userId = mCurrentUser; @@ -892,6 +902,14 @@ public final class SensorPrivacyService extends SystemService { @Override public boolean isIndividualSensorPrivacyEnabled(@UserIdInt int userId, int sensor) { + if (DEBUG) { + Log.d(TAG, "callingUid=" + Binder.getCallingUid() + + " callingPid=" + Binder.getCallingPid() + + " isIndividualSensorPrivacyEnabled(" + + "userId=" + userId + + " sensor=" + sensor + + ")"); + } enforceObserveSensorPrivacyPermission(); if (userId == UserHandle.USER_CURRENT) { userId = mCurrentUser; diff --git a/services/core/java/com/android/server/TelephonyRegistry.java b/services/core/java/com/android/server/TelephonyRegistry.java index 5ed6c86bdc18..4775127389d3 100644 --- a/services/core/java/com/android/server/TelephonyRegistry.java +++ b/services/core/java/com/android/server/TelephonyRegistry.java @@ -1856,11 +1856,12 @@ public class TelephonyRegistry extends ITelephonyRegistry.Stub { try { if (!mConfigurationProvider.isDisplayInfoNrAdvancedSupported( r.callingPackage, Binder.getCallingUserHandle())) { - telephonyDisplayInfo = + r.callback.onDisplayInfoChanged( getBackwardCompatibleTelephonyDisplayInfo( - telephonyDisplayInfo); + telephonyDisplayInfo)); + } else { + r.callback.onDisplayInfoChanged(telephonyDisplayInfo); } - r.callback.onDisplayInfoChanged(telephonyDisplayInfo); } catch (RemoteException ex) { mRemoveList.add(r.binder); } diff --git a/services/core/java/com/android/server/am/CachedAppOptimizer.java b/services/core/java/com/android/server/am/CachedAppOptimizer.java index f32aa2295cb8..6230919d8d2d 100644 --- a/services/core/java/com/android/server/am/CachedAppOptimizer.java +++ b/services/core/java/com/android/server/am/CachedAppOptimizer.java @@ -157,6 +157,9 @@ public final class CachedAppOptimizer { static final int SYNC_RECEIVED_WHILE_FROZEN = 1; static final int ASYNC_RECEIVED_WHILE_FROZEN = 2; + // Bitfield values for sync transactions received by frozen binder threads + static final int TXNS_PENDING_WHILE_FROZEN = 4; + /** * This thread must be moved to the system background cpuset. * If that doesn't happen, it's probably going to draw a lot of power. @@ -611,8 +614,9 @@ public final class CachedAppOptimizer { * binder for the specificed pid. * * @throws RuntimeException in case a flush/freeze operation could not complete successfully. + * @return 0 if success, or -EAGAIN indicating there's pending transaction. */ - private static native void freezeBinder(int pid, boolean freeze); + private static native int freezeBinder(int pid, boolean freeze); /** * Retrieves binder freeze info about a process. @@ -641,21 +645,25 @@ public final class CachedAppOptimizer { char state = (char) fr.read(); if (state == '1' || state == '0') { + // Also check freezer binder ioctl + getBinderFreezeInfo(Process.myPid()); supported = true; } else { Slog.e(TAG_AM, "unexpected value in cgroup.freeze"); } } catch (java.io.FileNotFoundException e) { - Slog.d(TAG_AM, "cgroup.freeze not present"); + Slog.w(TAG_AM, "cgroup.freeze not present"); + } catch (RuntimeException e) { + Slog.w(TAG_AM, "unable to read freezer info"); } catch (Exception e) { - Slog.d(TAG_AM, "unable to read cgroup.freeze: " + e.toString()); + Slog.w(TAG_AM, "unable to read cgroup.freeze: " + e.toString()); } if (fr != null) { try { fr.close(); } catch (java.io.IOException e) { - Slog.e(TAG_AM, "Exception closing freezer.killable: " + e.toString()); + Slog.e(TAG_AM, "Exception closing cgroup.freeze: " + e.toString()); } } @@ -948,7 +956,7 @@ public final class CachedAppOptimizer { int freezeInfo = getBinderFreezeInfo(pid); if ((freezeInfo & SYNC_RECEIVED_WHILE_FROZEN) != 0) { - Slog.d(TAG_AM, "pid " + pid + " " + app.processName + " " + Slog.d(TAG_AM, "pid " + pid + " " + app.processName + " received sync transactions while frozen, killing"); app.killLocked("Sync transaction while in frozen state", ApplicationExitInfo.REASON_OTHER, @@ -956,8 +964,8 @@ public final class CachedAppOptimizer { processKilled = true; } - if ((freezeInfo & ASYNC_RECEIVED_WHILE_FROZEN) != 0) { - Slog.d(TAG_AM, "pid " + pid + " " + app.processName + " " + if ((freezeInfo & ASYNC_RECEIVED_WHILE_FROZEN) != 0 && DEBUG_FREEZER) { + Slog.d(TAG_AM, "pid " + pid + " " + app.processName + " received async transactions while frozen"); } } catch (Exception e) { @@ -1292,7 +1300,9 @@ public final class CachedAppOptimizer { public void handleMessage(Message msg) { switch (msg.what) { case SET_FROZEN_PROCESS_MSG: - freezeProcess((ProcessRecord) msg.obj); + synchronized (mAm) { + freezeProcess((ProcessRecord) msg.obj); + } break; case REPORT_UNFREEZE_MSG: int pid = msg.arg1; @@ -1306,6 +1316,15 @@ public final class CachedAppOptimizer { } } + @GuardedBy({"mAm", "mProcLock"}) + private void rescheduleFreeze(final ProcessRecord proc, final String reason) { + Slog.d(TAG_AM, "Reschedule freeze for process " + proc.getPid() + + " " + proc.processName + " (" + reason + ")"); + unfreezeAppLSP(proc); + freezeAppAsyncLSP(proc); + } + + @GuardedBy({"mAm"}) private void freezeProcess(final ProcessRecord proc) { int pid = proc.getPid(); // Unlocked intentionally final String name = proc.processName; @@ -1355,10 +1374,15 @@ public final class CachedAppOptimizer { return; } + Slog.d(TAG_AM, "freezing " + pid + " " + name); + // Freeze binder interface before the process, to flush any // transactions that might be pending. try { - freezeBinder(pid, true); + if (freezeBinder(pid, true) != 0) { + rescheduleFreeze(proc, "outstanding txns"); + return; + } } catch (RuntimeException e) { Slog.e(TAG_AM, "Unable to freeze binder for " + pid + " " + name); mFreezeHandler.post(() -> { @@ -1404,24 +1428,36 @@ public final class CachedAppOptimizer { try { // post-check to prevent races + int freezeInfo = getBinderFreezeInfo(pid); + + if ((freezeInfo & TXNS_PENDING_WHILE_FROZEN) != 0) { + synchronized (mProcLock) { + rescheduleFreeze(proc, "new pending txns"); + } + return; + } + } catch (RuntimeException e) { + Slog.e(TAG_AM, "Unable to freeze binder for " + pid + " " + name); + mFreezeHandler.post(() -> { + synchronized (mAm) { + proc.killLocked("Unable to freeze binder interface", + ApplicationExitInfo.REASON_OTHER, + ApplicationExitInfo.SUBREASON_FREEZER_BINDER_IOCTL, true); + } + }); + } + + try { + // post-check to prevent races if (mProcLocksReader.hasFileLocks(pid)) { if (DEBUG_FREEZER) { Slog.d(TAG_AM, name + " (" + pid + ") holds file locks, reverting freeze"); } - - synchronized (mAm) { - synchronized (mProcLock) { - unfreezeAppLSP(proc); - } - } + unfreezeAppLSP(proc); } } catch (Exception e) { Slog.e(TAG_AM, "Unable to check file locks for " + name + "(" + pid + "): " + e); - synchronized (mAm) { - synchronized (mProcLock) { - unfreezeAppLSP(proc); - } - } + unfreezeAppLSP(proc); } } diff --git a/services/core/java/com/android/server/am/OWNERS b/services/core/java/com/android/server/am/OWNERS index a5cfc4a62c96..c4efbd7e8f51 100644 --- a/services/core/java/com/android/server/am/OWNERS +++ b/services/core/java/com/android/server/am/OWNERS @@ -19,6 +19,7 @@ ogunwale@google.com # Permissions & Packages svetoslavganov@google.com +toddke@google.com patb@google.com # Battery Stats diff --git a/services/core/java/com/android/server/biometrics/sensors/BaseClientMonitor.java b/services/core/java/com/android/server/biometrics/sensors/BaseClientMonitor.java index e5e1385fa605..3eb6f4ae48a2 100644 --- a/services/core/java/com/android/server/biometrics/sensors/BaseClientMonitor.java +++ b/services/core/java/com/android/server/biometrics/sensors/BaseClientMonitor.java @@ -206,7 +206,6 @@ public abstract class BaseClientMonitor extends LoggableMonitor } mToken = null; } - mListener = null; } @Override diff --git a/services/core/java/com/android/server/biometrics/sensors/BiometricScheduler.java b/services/core/java/com/android/server/biometrics/sensors/BiometricScheduler.java index b20316e4c6df..feb9e2a5b03f 100644 --- a/services/core/java/com/android/server/biometrics/sensors/BiometricScheduler.java +++ b/services/core/java/com/android/server/biometrics/sensors/BiometricScheduler.java @@ -295,6 +295,7 @@ public class BiometricScheduler { @Override public void onClientFinished(@NonNull BaseClientMonitor clientMonitor, boolean success) { mHandler.post(() -> { + clientMonitor.destroy(); if (mCurrentOperation == null) { Slog.e(getTag(), "[Finishing] " + clientMonitor + " but current operation is null, success: " + success 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 37ee76adeece..4400834efb3f 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 @@ -164,13 +164,17 @@ class FingerprintAuthenticationClient extends AuthenticationClient<ISession> imp @Override protected void stopHalOperation() { UdfpsHelper.hideUdfpsOverlay(getSensorId(), mUdfpsOverlayController); - try { - mCancellationSignal.cancel(); - } catch (RemoteException e) { - Slog.e(TAG, "Remote exception", e); - onError(BiometricFingerprintConstants.FINGERPRINT_ERROR_HW_UNAVAILABLE, - 0 /* vendorCode */); - mCallback.onClientFinished(this, false /* success */); + if (mCancellationSignal != null) { + try { + mCancellationSignal.cancel(); + } catch (RemoteException e) { + Slog.e(TAG, "Remote exception", e); + onError(BiometricFingerprintConstants.FINGERPRINT_ERROR_HW_UNAVAILABLE, + 0 /* vendorCode */); + mCallback.onClientFinished(this, false /* success */); + } + } else { + Slog.e(TAG, "cancellation signal was null"); } } diff --git a/services/core/java/com/android/server/devicestate/OWNERS b/services/core/java/com/android/server/devicestate/OWNERS index 7708505ae2dd..ae79fc0e2c2d 100644 --- a/services/core/java/com/android/server/devicestate/OWNERS +++ b/services/core/java/com/android/server/devicestate/OWNERS @@ -1,2 +1,3 @@ ogunwale@google.com akulian@google.com +darryljohnson@google.com diff --git a/services/core/java/com/android/server/display/DisplayModeDirector.java b/services/core/java/com/android/server/display/DisplayModeDirector.java index 329a69634f71..4005b33c213c 100644 --- a/services/core/java/com/android/server/display/DisplayModeDirector.java +++ b/services/core/java/com/android/server/display/DisplayModeDirector.java @@ -36,9 +36,14 @@ import android.hardware.display.DisplayManagerInternal.RefreshRateRange; import android.hardware.fingerprint.IUdfpsHbmListener; import android.net.Uri; import android.os.Handler; +import android.os.IThermalEventListener; +import android.os.IThermalService; import android.os.Looper; import android.os.Message; +import android.os.RemoteException; +import android.os.ServiceManager; import android.os.SystemClock; +import android.os.Temperature; import android.os.UserHandle; import android.provider.DeviceConfig; import android.provider.Settings; @@ -48,6 +53,7 @@ import android.util.Pair; import android.util.Slog; import android.util.SparseArray; import android.util.SparseBooleanArray; +import android.util.SparseIntArray; import android.view.Display; import android.view.DisplayInfo; @@ -72,7 +78,6 @@ import java.util.List; import java.util.Locale; import java.util.Objects; - /** * The DisplayModeDirector is responsible for determining what modes are allowed to be automatically * picked by the system based on system-wide and display-specific configuration. @@ -87,6 +92,8 @@ public class DisplayModeDirector { private static final int MSG_REFRESH_RATE_IN_LOW_ZONE_CHANGED = 4; private static final int MSG_REFRESH_RATE_IN_HIGH_ZONE_CHANGED = 5; private static final int MSG_HIGH_BRIGHTNESS_THRESHOLDS_CHANGED = 6; + private static final int MSG_REFRESH_RATE_IN_HBM_SUNLIGHT_CHANGED = 7; + private static final int MSG_REFRESH_RATE_IN_HBM_HDR_CHANGED = 8; // Special ID used to indicate that given vote is to be applied globally, rather than to a // specific display. @@ -108,6 +115,7 @@ public class DisplayModeDirector { private final UdfpsObserver mUdfpsObserver; private final SensorObserver mSensorObserver; private final HbmObserver mHbmObserver; + private final SkinThermalStatusObserver mSkinThermalStatusObserver; private final DeviceConfigInterface mDeviceConfig; private final DeviceConfigDisplaySettings mDeviceConfigDisplaySettings; @@ -155,8 +163,10 @@ public class DisplayModeDirector { } }; mSensorObserver = new SensorObserver(context, ballotBox, injector); - mHbmObserver = new HbmObserver(injector, ballotBox, BackgroundThread.getHandler()); + mSkinThermalStatusObserver = new SkinThermalStatusObserver(injector, ballotBox); mDeviceConfigDisplaySettings = new DeviceConfigDisplaySettings(); + mHbmObserver = new HbmObserver(injector, ballotBox, BackgroundThread.getHandler(), + mDeviceConfigDisplaySettings); mDeviceConfig = injector.getDeviceConfig(); mAlwaysRespectAppRequest = false; } @@ -174,6 +184,7 @@ public class DisplayModeDirector { mBrightnessObserver.observe(sensorManager); mSensorObserver.observe(); mHbmObserver.observe(); + mSkinThermalStatusObserver.observe(); synchronized (mLock) { // We may have a listener already registered before the call to start, so go ahead and // notify them to pick up our newly initialized state. @@ -609,6 +620,7 @@ public class DisplayModeDirector { mUdfpsObserver.dumpLocked(pw); mSensorObserver.dumpLocked(pw); mHbmObserver.dumpLocked(pw); + mSkinThermalStatusObserver.dumpLocked(pw); } } @@ -717,6 +729,10 @@ public class DisplayModeDirector { return mUdfpsObserver; } + @VisibleForTesting + HbmObserver getHbmObserver() { + return mHbmObserver; + } @VisibleForTesting DesiredDisplayModeSpecs getDesiredDisplayModeSpecsWithInjectedFpsSettings( @@ -787,6 +803,19 @@ public class DisplayModeDirector { (DesiredDisplayModeSpecsListener) msg.obj; desiredDisplayModeSpecsListener.onDesiredDisplayModeSpecsChanged(); break; + + case MSG_REFRESH_RATE_IN_HBM_SUNLIGHT_CHANGED: { + int refreshRateInHbmSunlight = msg.arg1; + mHbmObserver.onDeviceConfigRefreshRateInHbmSunlightChanged( + refreshRateInHbmSunlight); + break; + } + + case MSG_REFRESH_RATE_IN_HBM_HDR_CHANGED: { + int refreshRateInHbmHdr = msg.arg1; + mHbmObserver.onDeviceConfigRefreshRateInHbmHdrChanged(refreshRateInHbmHdr); + break; + } } } } @@ -913,16 +942,19 @@ public class DisplayModeDirector { // result is a range. public static final int PRIORITY_FLICKER_REFRESH_RATE = 1; + // High-brightness-mode may need a specific range of refresh-rates to function properly. + public static final int PRIORITY_HIGH_BRIGHTNESS_MODE = 2; + // SETTING_MIN_REFRESH_RATE is used to propose a lower bound of display refresh rate. // It votes [MIN_REFRESH_RATE, Float.POSITIVE_INFINITY] - public static final int PRIORITY_USER_SETTING_MIN_REFRESH_RATE = 2; + public static final int PRIORITY_USER_SETTING_MIN_REFRESH_RATE = 3; // APP_REQUEST_REFRESH_RATE_RANGE is used to for internal apps to limit the refresh // rate in certain cases, mostly to preserve power. // @see android.view.WindowManager.LayoutParams#preferredMinRefreshRate // @see android.view.WindowManager.LayoutParams#preferredMaxRefreshRate // It votes to [preferredMinRefreshRate, preferredMaxRefreshRate]. - public static final int PRIORITY_APP_REQUEST_REFRESH_RATE_RANGE = 3; + public static final int PRIORITY_APP_REQUEST_REFRESH_RATE_RANGE = 4; // We split the app request into different priorities in case we can satisfy one desire // without the other. @@ -937,32 +969,32 @@ public class DisplayModeDirector { // The preferred refresh rate is set on the main surface of the app outside of // DisplayModeDirector. // @see com.android.server.wm.WindowState#updateFrameRateSelectionPriorityIfNeeded - public static final int PRIORITY_APP_REQUEST_BASE_MODE_REFRESH_RATE = 4; - public static final int PRIORITY_APP_REQUEST_SIZE = 5; + public static final int PRIORITY_APP_REQUEST_BASE_MODE_REFRESH_RATE = 5; + public static final int PRIORITY_APP_REQUEST_SIZE = 6; // SETTING_PEAK_REFRESH_RATE has a high priority and will restrict the bounds of the rest // of low priority voters. It votes [0, max(PEAK, MIN)] - public static final int PRIORITY_USER_SETTING_PEAK_REFRESH_RATE = 6; + public static final int PRIORITY_USER_SETTING_PEAK_REFRESH_RATE = 7; // LOW_POWER_MODE force display to [0, 60HZ] if Settings.Global.LOW_POWER_MODE is on. - public static final int PRIORITY_LOW_POWER_MODE = 7; + public static final int PRIORITY_LOW_POWER_MODE = 8; // PRIORITY_FLICKER_REFRESH_RATE_SWITCH votes for disabling refresh rate switching. If the // higher priority voters' result is a range, it will fix the rate to a single choice. // It's used to avoid refresh rate switches in certain conditions which may result in the // user seeing the display flickering when the switches occur. - public static final int PRIORITY_FLICKER_REFRESH_RATE_SWITCH = 8; + public static final int PRIORITY_FLICKER_REFRESH_RATE_SWITCH = 9; - // High-brightness-mode may need a specific range of refresh-rates to function properly. - public static final int PRIORITY_HIGH_BRIGHTNESS_MODE = 9; + // Force display to [0, 60HZ] if skin temperature is at or above CRITICAL. + public static final int PRIORITY_SKIN_TEMPERATURE = 10; // The proximity sensor needs the refresh rate to be locked in order to function, so this is // set to a high priority. - public static final int PRIORITY_PROXIMITY = 10; + public static final int PRIORITY_PROXIMITY = 11; // The Under-Display Fingerprint Sensor (UDFPS) needs the refresh rate to be locked in order // to function, so this needs to be the highest priority of all votes. - public static final int PRIORITY_UDFPS = 11; + public static final int PRIORITY_UDFPS = 12; // Whenever a new priority is added, remember to update MIN_PRIORITY, MAX_PRIORITY, and // APP_REQUEST_REFRESH_RATE_RANGE_PRIORITY_CUTOFF, as well as priorityToString. @@ -1057,6 +1089,8 @@ public class DisplayModeDirector { return "PRIORITY_PROXIMITY"; case PRIORITY_LOW_POWER_MODE: return "PRIORITY_LOW_POWER_MODE"; + case PRIORITY_SKIN_TEMPERATURE: + return "PRIORITY_SKIN_TEMPERATURE"; case PRIORITY_UDFPS: return "PRIORITY_UDFPS"; case PRIORITY_USER_SETTING_MIN_REFRESH_RATE: @@ -2248,33 +2282,78 @@ public class DisplayModeDirector { * HBM that are associated with that display. Restrictions are retrieved from * DisplayManagerInternal but originate in the display-device-config file. */ - private static class HbmObserver implements DisplayManager.DisplayListener { + public static class HbmObserver implements DisplayManager.DisplayListener { private final BallotBox mBallotBox; private final Handler mHandler; - private final SparseBooleanArray mHbmEnabled = new SparseBooleanArray(); + private final SparseIntArray mHbmMode = new SparseIntArray(); private final Injector mInjector; + private final DeviceConfigDisplaySettings mDeviceConfigDisplaySettings; + private int mRefreshRateInHbmSunlight; + private int mRefreshRateInHbmHdr; private DisplayManagerInternal mDisplayManagerInternal; - HbmObserver(Injector injector, BallotBox ballotBox, Handler handler) { + HbmObserver(Injector injector, BallotBox ballotBox, Handler handler, + DeviceConfigDisplaySettings displaySettings) { mInjector = injector; mBallotBox = ballotBox; mHandler = handler; + mDeviceConfigDisplaySettings = displaySettings; } public void observe() { + mRefreshRateInHbmSunlight = mDeviceConfigDisplaySettings.getRefreshRateInHbmSunlight(); + mRefreshRateInHbmHdr = mDeviceConfigDisplaySettings.getRefreshRateInHbmHdr(); + mDisplayManagerInternal = LocalServices.getService(DisplayManagerInternal.class); mInjector.registerDisplayListener(this, mHandler, DisplayManager.EVENT_FLAG_DISPLAY_BRIGHTNESS | DisplayManager.EVENT_FLAG_DISPLAY_REMOVED); } + /** + * @return the refresh to lock to when the device is in high brightness mode for Sunlight. + */ + @VisibleForTesting + int getRefreshRateInHbmSunlight() { + return mRefreshRateInHbmSunlight; + } + + /** + * @return the refresh to lock to when the device is in high brightness mode for HDR. + */ + @VisibleForTesting + int getRefreshRateInHbmHdr() { + return mRefreshRateInHbmHdr; + } + + /** + * Recalculates the HBM vote when the device config has been changed. + */ + public void onDeviceConfigRefreshRateInHbmSunlightChanged(int refreshRate) { + if (refreshRate != mRefreshRateInHbmSunlight) { + mRefreshRateInHbmSunlight = refreshRate; + onDeviceConfigRefreshRateInHbmChanged(); + } + } + + /** + * Recalculates the HBM vote when the device config has been changed. + */ + public void onDeviceConfigRefreshRateInHbmHdrChanged(int refreshRate) { + if (refreshRate != mRefreshRateInHbmHdr) { + mRefreshRateInHbmHdr = refreshRate; + onDeviceConfigRefreshRateInHbmChanged(); + } + } + @Override public void onDisplayAdded(int displayId) {} @Override public void onDisplayRemoved(int displayId) { mBallotBox.vote(displayId, Vote.PRIORITY_HIGH_BRIGHTNESS_MODE, null); + mHbmMode.delete(displayId); } @Override @@ -2284,31 +2363,102 @@ public class DisplayModeDirector { // Display no longer there. Assume we'll get an onDisplayRemoved very soon. return; } - final boolean isHbmEnabled = - info.highBrightnessMode != BrightnessInfo.HIGH_BRIGHTNESS_MODE_OFF; - if (isHbmEnabled == mHbmEnabled.get(displayId)) { + final int hbmMode = info.highBrightnessMode; + if (hbmMode == mHbmMode.get(displayId)) { // no change, ignore. return; } + mHbmMode.put(displayId, hbmMode); + recalculateVotesForDisplay(displayId); + } + + private void onDeviceConfigRefreshRateInHbmChanged() { + final int[] displayIds = mHbmMode.copyKeys(); + if (displayIds != null) { + for (int id : displayIds) { + recalculateVotesForDisplay(id); + } + } + } + + private void recalculateVotesForDisplay(int displayId) { + final int hbmMode = mHbmMode.get(displayId, BrightnessInfo.HIGH_BRIGHTNESS_MODE_OFF); Vote vote = null; - mHbmEnabled.put(displayId, isHbmEnabled); - if (isHbmEnabled) { - final List<RefreshRateLimitation> limits = + if (hbmMode == BrightnessInfo.HIGH_BRIGHTNESS_MODE_SUNLIGHT) { + // Device resource properties take priority over DisplayDeviceConfig + if (mRefreshRateInHbmSunlight > 0) { + vote = Vote.forRefreshRates(mRefreshRateInHbmSunlight, + mRefreshRateInHbmSunlight); + } else { + final List<RefreshRateLimitation> limits = mDisplayManagerInternal.getRefreshRateLimitations(displayId); - for (int i = 0; limits != null && i < limits.size(); i++) { - final RefreshRateLimitation limitation = limits.get(i); - if (limitation.type == REFRESH_RATE_LIMIT_HIGH_BRIGHTNESS_MODE) { - vote = Vote.forRefreshRates(limitation.range.min, limitation.range.max); - break; + for (int i = 0; limits != null && i < limits.size(); i++) { + final RefreshRateLimitation limitation = limits.get(i); + if (limitation.type == REFRESH_RATE_LIMIT_HIGH_BRIGHTNESS_MODE) { + vote = Vote.forRefreshRates(limitation.range.min, limitation.range.max); + break; + } } } } + if (hbmMode == BrightnessInfo.HIGH_BRIGHTNESS_MODE_HDR + && mRefreshRateInHbmHdr > 0) { + vote = Vote.forRefreshRates(mRefreshRateInHbmHdr, mRefreshRateInHbmHdr); + } mBallotBox.vote(displayId, Vote.PRIORITY_HIGH_BRIGHTNESS_MODE, vote); } void dumpLocked(PrintWriter pw) { pw.println(" HbmObserver"); - pw.println(" mHbmEnabled: " + mHbmEnabled); + pw.println(" mHbmMode: " + mHbmMode); + pw.println(" mRefreshRateInHbmSunlight: " + mRefreshRateInHbmSunlight); + pw.println(" mRefreshRateInHbmHdr: " + mRefreshRateInHbmHdr); + } + } + + private final class SkinThermalStatusObserver extends IThermalEventListener.Stub { + private final BallotBox mBallotBox; + private final Injector mInjector; + + private @Temperature.ThrottlingStatus int mStatus = -1; + + SkinThermalStatusObserver(Injector injector, BallotBox ballotBox) { + mInjector = injector; + mBallotBox = ballotBox; + } + + @Override + public void notifyThrottling(Temperature temp) { + mStatus = temp.getStatus(); + if (mLoggingEnabled) { + Slog.d(TAG, "New thermal throttling status " + + ", current thermal status = " + mStatus); + } + final Vote vote; + if (mStatus >= Temperature.THROTTLING_CRITICAL) { + vote = Vote.forRefreshRates(0f, 60f); + } else { + vote = null; + } + mBallotBox.vote(GLOBAL_ID, Vote.PRIORITY_SKIN_TEMPERATURE, vote); + } + + public void observe() { + IThermalService thermalService = mInjector.getThermalService(); + if (thermalService == null) { + Slog.w(TAG, "Could not observe thermal status. Service not available"); + return; + } + try { + thermalService.registerThermalEventListenerWithType(this, Temperature.TYPE_SKIN); + } catch (RemoteException e) { + Slog.e(TAG, "Failed to register thermal status listener", e); + } + } + + void dumpLocked(PrintWriter writer) { + writer.println(" SkinThermalStatusObserver:"); + writer.println(" mStatus: " + mStatus); } } @@ -2381,6 +2531,29 @@ public class DisplayModeDirector { return refreshRate; } + public int getRefreshRateInHbmSunlight() { + final int defaultRefreshRateInHbmSunlight = + mContext.getResources().getInteger( + R.integer.config_defaultRefreshRateInHbmSunlight); + + final int refreshRate = mDeviceConfig.getInt(DeviceConfig.NAMESPACE_DISPLAY_MANAGER, + DisplayManager.DeviceConfig.KEY_REFRESH_RATE_IN_HBM_SUNLIGHT, + defaultRefreshRateInHbmSunlight); + + return refreshRate; + } + + public int getRefreshRateInHbmHdr() { + final int defaultRefreshRateInHbmHdr = + mContext.getResources().getInteger(R.integer.config_defaultRefreshRateInHbmHdr); + + final int refreshRate = mDeviceConfig.getInt(DeviceConfig.NAMESPACE_DISPLAY_MANAGER, + DisplayManager.DeviceConfig.KEY_REFRESH_RATE_IN_HBM_HDR, + defaultRefreshRateInHbmHdr); + + return refreshRate; + } + /* * Return null if no such property */ @@ -2420,6 +2593,15 @@ public class DisplayModeDirector { .sendToTarget(); mHandler.obtainMessage(MSG_REFRESH_RATE_IN_HIGH_ZONE_CHANGED, refreshRateInHighZone, 0) .sendToTarget(); + + final int refreshRateInHbmSunlight = getRefreshRateInHbmSunlight(); + mHandler.obtainMessage(MSG_REFRESH_RATE_IN_HBM_SUNLIGHT_CHANGED, + refreshRateInHbmSunlight, 0) + .sendToTarget(); + + final int refreshRateInHbmHdr = getRefreshRateInHbmHdr(); + mHandler.obtainMessage(MSG_REFRESH_RATE_IN_HBM_HDR_CHANGED, refreshRateInHbmHdr, 0) + .sendToTarget(); } private int[] getIntArrayProperty(String prop) { @@ -2473,6 +2655,8 @@ public class DisplayModeDirector { BrightnessInfo getBrightnessInfo(int displayId); boolean isDozeState(Display d); + + IThermalService getThermalService(); } @VisibleForTesting @@ -2533,6 +2717,12 @@ public class DisplayModeDirector { return Display.isDozeState(d.getState()); } + @Override + public IThermalService getThermalService() { + return IThermalService.Stub.asInterface( + ServiceManager.getService(Context.THERMAL_SERVICE)); + } + private DisplayManager getDisplayManager() { if (mDisplayManager == null) { mDisplayManager = mContext.getSystemService(DisplayManager.class); diff --git a/services/core/java/com/android/server/display/DisplayPowerState.java b/services/core/java/com/android/server/display/DisplayPowerState.java index 6af192371e3d..147050cd271f 100644 --- a/services/core/java/com/android/server/display/DisplayPowerState.java +++ b/services/core/java/com/android/server/display/DisplayPowerState.java @@ -411,7 +411,7 @@ final class DisplayPowerState { * Updates the state of the screen and backlight asynchronously on a separate thread. */ private final class PhotonicModulator extends Thread { - private static final int INITIAL_SCREEN_STATE = Display.STATE_OFF; // unknown, assume off + private static final int INITIAL_SCREEN_STATE = Display.STATE_UNKNOWN; private static final float INITIAL_BACKLIGHT_FLOAT = PowerManager.BRIGHTNESS_INVALID_FLOAT; private final Object mLock = new Object(); @@ -494,7 +494,9 @@ final class DisplayPowerState { if (!backlightChanged) { mBacklightChangeInProgress = false; } - if (!stateChanged && !backlightChanged) { + boolean valid = state != Display.STATE_UNKNOWN && !Float.isNaN(brightnessState); + boolean changed = stateChanged || backlightChanged; + if (!valid || !changed) { try { mLock.wait(); } catch (InterruptedException ex) { diff --git a/services/core/java/com/android/server/om/OverlayManagerService.java b/services/core/java/com/android/server/om/OverlayManagerService.java index 27b164830572..ee0b3d52eb3d 100644 --- a/services/core/java/com/android/server/om/OverlayManagerService.java +++ b/services/core/java/com/android/server/om/OverlayManagerService.java @@ -71,6 +71,7 @@ import android.text.TextUtils; import android.util.ArrayMap; import android.util.ArraySet; import android.util.AtomicFile; +import android.util.EventLog; import android.util.Slog; import android.util.SparseArray; @@ -81,7 +82,6 @@ import com.android.server.FgThread; import com.android.server.LocalServices; import com.android.server.SystemConfig; import com.android.server.SystemService; - import com.android.server.pm.UserManagerService; import com.android.server.pm.parsing.pkg.AndroidPackage; @@ -285,6 +285,12 @@ public final class OverlayManagerService extends SystemService { restoreSettings(); + // Wipe all shell overlays on boot, to recover from a potentially broken device + String shellPkgName = TextUtils.emptyIfNull( + getContext().getString(android.R.string.config_systemShell)); + mSettings.removeIf(overlayInfo -> overlayInfo.isFabricated + && shellPkgName.equals(overlayInfo.packageName)); + initIfNeeded(); onSwitchUser(UserHandle.USER_SYSTEM); @@ -891,6 +897,16 @@ public final class OverlayManagerService extends SystemService { throw new IllegalArgumentException(request.typeToString() + " unsupported for user " + request.userId); } + + // Normal apps are blocked from accessing OMS via SELinux, so to block non-root, + // non privileged callers, a simple check against the shell UID is sufficient, since + // that's the only exception from the other categories. This is enough while OMS + // is not a public API, but this will have to be changed if it's ever exposed. + if (callingUid == Process.SHELL_UID) { + EventLog.writeEvent(0x534e4554, "202768292", -1, ""); + throw new IllegalArgumentException("Non-root shell cannot fabricate overlays"); + } + realUserId = UserHandle.USER_ALL; // Enforce that the calling process can only register and unregister fabricated diff --git a/services/core/java/com/android/server/pm/PackageInstallerService.java b/services/core/java/com/android/server/pm/PackageInstallerService.java index 00e54b1f6846..99f4a595ae8f 100644 --- a/services/core/java/com/android/server/pm/PackageInstallerService.java +++ b/services/core/java/com/android/server/pm/PackageInstallerService.java @@ -135,6 +135,8 @@ public class PackageInstallerService extends IPackageInstaller.Stub implements private static final long MAX_ACTIVE_SESSIONS_NO_PERMISSION = 50; /** Upper bound on number of historical sessions for a UID */ private static final long MAX_HISTORICAL_SESSIONS = 1048576; + /** Destroy sessions older than this on storage free request */ + private static final long MAX_SESSION_AGE_ON_LOW_STORAGE_MILLIS = 8 * DateUtils.HOUR_IN_MILLIS; /** * Allow verification-skipping if it's a development app installed through ADB with @@ -338,22 +340,28 @@ public class PackageInstallerService extends IPackageInstaller.Stub implements @GuardedBy("mSessions") private void reconcileStagesLocked(String volumeUuid) { - final File stagingDir = getTmpSessionDir(volumeUuid); - final ArraySet<File> unclaimedStages = newArraySet( - stagingDir.listFiles(sStageFilter)); - - // We also need to clean up orphaned staging directory for staged sessions - final File stagedSessionStagingDir = Environment.getDataStagingDirectory(volumeUuid); - unclaimedStages.addAll(newArraySet(stagedSessionStagingDir.listFiles())); - + final ArraySet<File> unclaimedStages = getStagingDirsOnVolume(volumeUuid); // Ignore stages claimed by active sessions for (int i = 0; i < mSessions.size(); i++) { final PackageInstallerSession session = mSessions.valueAt(i); unclaimedStages.remove(session.stageDir); } + removeStagingDirs(unclaimedStages); + } + + private ArraySet<File> getStagingDirsOnVolume(String volumeUuid) { + final File stagingDir = getTmpSessionDir(volumeUuid); + final ArraySet<File> stagingDirs = newArraySet(stagingDir.listFiles(sStageFilter)); + + // We also need to clean up orphaned staging directory for staged sessions + final File stagedSessionStagingDir = Environment.getDataStagingDirectory(volumeUuid); + stagingDirs.addAll(newArraySet(stagedSessionStagingDir.listFiles())); + return stagingDirs; + } + private void removeStagingDirs(ArraySet<File> stagingDirsToRemove) { // Clean up orphaned staging directories - for (File stage : unclaimedStages) { + for (File stage : stagingDirsToRemove) { Slog.w(TAG, "Deleting orphan stage " + stage); synchronized (mPm.mInstallLock) { mPm.removeCodePathLI(stage); @@ -367,6 +375,33 @@ public class PackageInstallerService extends IPackageInstaller.Stub implements } } + /** + * Called to free up some storage space from obsolete installation files + */ + public void freeStageDirs(String volumeUuid) { + final ArraySet<File> unclaimedStagingDirsOnVolume = getStagingDirsOnVolume(volumeUuid); + final long currentTimeMillis = System.currentTimeMillis(); + synchronized (mSessions) { + for (int i = 0; i < mSessions.size(); i++) { + final PackageInstallerSession session = mSessions.valueAt(i); + if (!unclaimedStagingDirsOnVolume.contains(session.stageDir)) { + // Only handles sessions stored on the target volume + continue; + } + final long age = currentTimeMillis - session.createdMillis; + if (age >= MAX_SESSION_AGE_ON_LOW_STORAGE_MILLIS) { + // Aggressively close old sessions because we are running low on storage + // Their staging dirs will be removed too + session.abandon(); + } else { + // Session is new enough, so it deserves to be kept even on low storage + unclaimedStagingDirsOnVolume.remove(session.stageDir); + } + } + } + removeStagingDirs(unclaimedStagingDirsOnVolume); + } + public static boolean isStageName(String name) { final boolean isFile = name.startsWith("vmdl") && name.endsWith(".tmp"); final boolean isContainer = name.startsWith("smdl") && name.endsWith(".tmp"); diff --git a/services/core/java/com/android/server/pm/PackageInstallerSession.java b/services/core/java/com/android/server/pm/PackageInstallerSession.java index 542948491dc8..d0e445749698 100644 --- a/services/core/java/com/android/server/pm/PackageInstallerSession.java +++ b/services/core/java/com/android/server/pm/PackageInstallerSession.java @@ -673,7 +673,7 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { final Runnable r; synchronized (mLock) { assertNotChildLocked("StagedSession#abandon"); - assertCallerIsOwnerOrRoot(); + assertCallerIsOwnerOrRootOrSystem(); if (isInTerminalState()) { // We keep the session in the database if it's in a finalized state. It will be // removed by PackageInstallerService when the last update time is old enough. @@ -3704,7 +3704,7 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { private void abandonNonStaged() { synchronized (mLock) { assertNotChildLocked("abandonNonStaged"); - assertCallerIsOwnerOrRoot(); + assertCallerIsOwnerOrRootOrSystem(); if (mRelinquished) { if (LOGD) Slog.d(TAG, "Ignoring abandon after commit relinquished control"); return; diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java index 827dfc0caa16..2d08da13e632 100644 --- a/services/core/java/com/android/server/pm/PackageManagerService.java +++ b/services/core/java/com/android/server/pm/PackageManagerService.java @@ -8971,6 +8971,10 @@ public class PackageManagerService extends IPackageManager.Stub if (freeBytesRequired > 0) { smInternal.freeCache(volumeUuid, freeBytesRequired); } + + // 12. Clear temp install session files + mInstallerService.freeStageDirs(volumeUuid); + if (file.getUsableSpace() >= bytes) return; } else { try { diff --git a/services/core/java/com/android/server/pm/ShortcutService.java b/services/core/java/com/android/server/pm/ShortcutService.java index fcbf40e29933..62d6717e847a 100644 --- a/services/core/java/com/android/server/pm/ShortcutService.java +++ b/services/core/java/com/android/server/pm/ShortcutService.java @@ -1664,6 +1664,19 @@ public class ShortcutService extends IShortcutService.Stub { mContext.enforceCallingPermission(permission, message); } + private void verifyCallerUserId(@UserIdInt int userId) { + if (isCallerSystem()) { + return; // no check + } + + final int callingUid = injectBinderCallingUid(); + + // Otherwise, make sure the arguments are valid. + if (UserHandle.getUserId(callingUid) != userId) { + throw new SecurityException("Invalid user-ID"); + } + } + private void verifyCaller(@NonNull String packageName, @UserIdInt int userId) { Preconditions.checkStringNotEmpty(packageName, "packageName"); @@ -2847,6 +2860,8 @@ public class ShortcutService extends IShortcutService.Stub { @Override public boolean isRequestPinItemSupported(int callingUserId, int requestType) { + verifyCallerUserId(callingUserId); + final long token = injectClearCallingIdentity(); try { return mShortcutRequestPinProcessor diff --git a/services/core/java/com/android/server/pm/permission/PermissionManagerService.java b/services/core/java/com/android/server/pm/permission/PermissionManagerService.java index 7b12709e4efd..54a6c678e0da 100644 --- a/services/core/java/com/android/server/pm/permission/PermissionManagerService.java +++ b/services/core/java/com/android/server/pm/permission/PermissionManagerService.java @@ -1215,6 +1215,7 @@ public class PermissionManagerService extends IPermissionManager.Stub { private boolean checkExistsAndEnforceCannotModifyImmutablyRestrictedPermission( @NonNull String permName) { + final String permissionPackageName; final boolean isImmutablyRestrictedPermission; synchronized (mLock) { final Permission bp = mRegistry.getPermission(permName); @@ -1222,15 +1223,25 @@ public class PermissionManagerService extends IPermissionManager.Stub { Slog.w(TAG, "No such permissions: " + permName); return false; } + permissionPackageName = bp.getPackageName(); isImmutablyRestrictedPermission = bp.isHardOrSoftRestricted() && bp.isImmutablyRestricted(); } + + final int callingUid = getCallingUid(); + final int callingUserId = UserHandle.getUserId(callingUid); + if (mPackageManagerInt.filterAppAccess(permissionPackageName, callingUid, callingUserId)) { + EventLog.writeEvent(0x534e4554, "186404356", callingUid, permName); + return false; + } + if (isImmutablyRestrictedPermission && mContext.checkCallingOrSelfPermission( Manifest.permission.WHITELIST_RESTRICTED_PERMISSIONS) != PackageManager.PERMISSION_GRANTED) { throw new SecurityException("Cannot modify allowlisting of an immutably " + "restricted permission: " + permName); } + return true; } diff --git a/services/core/java/com/android/server/power/PowerManagerService.java b/services/core/java/com/android/server/power/PowerManagerService.java index d982336f9d5f..73bce319d5f2 100644 --- a/services/core/java/com/android/server/power/PowerManagerService.java +++ b/services/core/java/com/android/server/power/PowerManagerService.java @@ -129,6 +129,7 @@ import java.lang.annotation.RetentionPolicy; import java.util.ArrayList; import java.util.Arrays; import java.util.List; +import java.util.NoSuchElementException; import java.util.Objects; /** @@ -1495,7 +1496,11 @@ public final class PowerManagerService extends SystemService mRequestWaitForNegativeProximity = true; } - wakeLock.mLock.unlinkToDeath(wakeLock, 0); + try { + wakeLock.mLock.unlinkToDeath(wakeLock, 0); + } catch (NoSuchElementException e) { + Slog.wtf(TAG, "Failed to unlink wakelock", e); + } removeWakeLockLocked(wakeLock, index); } } diff --git a/services/core/java/com/android/server/powerstats/PowerStatsDataStorage.java b/services/core/java/com/android/server/powerstats/PowerStatsDataStorage.java index 2a95416747a6..06253a08d937 100644 --- a/services/core/java/com/android/server/powerstats/PowerStatsDataStorage.java +++ b/services/core/java/com/android/server/powerstats/PowerStatsDataStorage.java @@ -124,12 +124,8 @@ public class PowerStatsDataStorage { @Override public void read(InputStream in) throws IOException { while (in.available() > 0) { - try { - DataElement dataElement = new DataElement(in); - mCallback.onReadDataElement(dataElement.getData()); - } catch (IOException e) { - Slog.e(TAG, "Failed to read from storage. " + e.getMessage()); - } + DataElement dataElement = new DataElement(in); + mCallback.onReadDataElement(dataElement.getData()); } } } diff --git a/services/core/java/com/android/server/wm/ImeInsetsSourceProvider.java b/services/core/java/com/android/server/wm/ImeInsetsSourceProvider.java index da47328691c0..ed1e784bf275 100644 --- a/services/core/java/com/android/server/wm/ImeInsetsSourceProvider.java +++ b/services/core/java/com/android/server/wm/ImeInsetsSourceProvider.java @@ -90,6 +90,24 @@ final class ImeInsetsSourceProvider extends InsetsSourceProvider { onSourceChanged(); } + @Override + protected boolean updateClientVisibility(InsetsControlTarget caller) { + boolean changed = super.updateClientVisibility(caller); + if (changed && caller.getRequestedVisibility(mSource.getType())) { + reportImeDrawnForOrganizer(caller); + } + return changed; + } + + private void reportImeDrawnForOrganizer(InsetsControlTarget caller) { + if (caller.getWindow() != null && caller.getWindow().getTask() != null) { + if (caller.getWindow().getTask().isOrganized()) { + mWin.mWmService.mAtmService.mTaskOrganizerController.reportImeDrawnOnTask( + caller.getWindow().getTask()); + } + } + } + private void onSourceChanged() { if (mLastSource.equals(mSource)) { return; diff --git a/services/core/java/com/android/server/wm/KeyguardController.java b/services/core/java/com/android/server/wm/KeyguardController.java index a7216da9bed5..054854aef121 100644 --- a/services/core/java/com/android/server/wm/KeyguardController.java +++ b/services/core/java/com/android/server/wm/KeyguardController.java @@ -106,13 +106,13 @@ class KeyguardController { } /** - * @return {@code true} for default display when AOD is showing. Otherwise, same as - * {@link #isKeyguardOrAodShowing(int)} + * @return {@code true} for default display when AOD is showing, not going away. Otherwise, same + * as {@link #isKeyguardOrAodShowing(int)} * TODO(b/125198167): Replace isKeyguardOrAodShowing() by this logic. */ boolean isKeyguardUnoccludedOrAodShowing(int displayId) { if (displayId == DEFAULT_DISPLAY && mAodShowing) { - return true; + return !mKeyguardGoingAway; } return isKeyguardOrAodShowing(displayId); } @@ -461,7 +461,7 @@ class KeyguardController { final KeyguardDisplayState state = getDisplayState(displayId); if (isKeyguardUnoccludedOrAodShowing(displayId)) { state.mSleepTokenAcquirer.acquire(displayId); - } else if (!isKeyguardUnoccludedOrAodShowing(displayId)) { + } else { state.mSleepTokenAcquirer.release(displayId); } } diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java index 963485554b5e..cae4f22f913f 100644 --- a/services/core/java/com/android/server/wm/Task.java +++ b/services/core/java/com/android/server/wm/Task.java @@ -5147,7 +5147,7 @@ class Task extends WindowContainer<WindowContainer> { /** * @return true if the task is currently focused. */ - private boolean isFocused() { + boolean isFocused() { if (mDisplayContent == null || mDisplayContent.mCurrentFocus == null) { return false; } diff --git a/services/core/java/com/android/server/wm/TaskOrganizerController.java b/services/core/java/com/android/server/wm/TaskOrganizerController.java index 09c5581385dd..88467baa6c34 100644 --- a/services/core/java/com/android/server/wm/TaskOrganizerController.java +++ b/services/core/java/com/android/server/wm/TaskOrganizerController.java @@ -733,6 +733,17 @@ class TaskOrganizerController extends ITaskOrganizerController.Stub { mPendingTaskEvents.clear(); } + void reportImeDrawnOnTask(Task task) { + final TaskOrganizerState state = mTaskOrganizerStates.get(task.mTaskOrganizer.asBinder()); + if (state != null) { + try { + state.mOrganizer.mTaskOrganizer.onImeDrawnOnTask(task.mTaskId); + } catch (RemoteException e) { + Slog.e(TAG, "Exception sending onImeDrawnOnTask callback", e); + } + } + } + void onTaskInfoChanged(Task task, boolean force) { if (!task.mTaskAppearedSent) { // Skip if task still not appeared. diff --git a/services/core/jni/com_android_server_am_CachedAppOptimizer.cpp b/services/core/jni/com_android_server_am_CachedAppOptimizer.cpp index e319e3febc21..4190a91710fc 100644 --- a/services/core/jni/com_android_server_am_CachedAppOptimizer.cpp +++ b/services/core/jni/com_android_server_am_CachedAppOptimizer.cpp @@ -55,6 +55,7 @@ using android::base::unique_fd; #define SYNC_RECEIVED_WHILE_FROZEN (1) #define ASYNC_RECEIVED_WHILE_FROZEN (2) +#define TXNS_PENDING_WHILE_FROZEN (4) namespace android { @@ -232,17 +233,20 @@ static void com_android_server_am_CachedAppOptimizer_compactProcess(JNIEnv*, job compactProcessOrFallback(pid, compactionFlags); } -static void com_android_server_am_CachedAppOptimizer_freezeBinder( +static jint com_android_server_am_CachedAppOptimizer_freezeBinder( JNIEnv *env, jobject clazz, jint pid, jboolean freeze) { - if (IPCThreadState::freeze(pid, freeze, 100 /* timeout [ms] */) != 0) { + jint retVal = IPCThreadState::freeze(pid, freeze, 100 /* timeout [ms] */); + if (retVal != 0 && retVal != -EAGAIN) { jniThrowException(env, "java/lang/RuntimeException", "Unable to freeze/unfreeze binder"); } + + return retVal; } static jint com_android_server_am_CachedAppOptimizer_getBinderFreezeInfo(JNIEnv *env, jobject clazz, jint pid) { - bool syncReceived = false, asyncReceived = false; + uint32_t syncReceived = 0, asyncReceived = 0; int error = IPCThreadState::getProcessFreezeInfo(pid, &syncReceived, &asyncReceived); @@ -252,13 +256,12 @@ static jint com_android_server_am_CachedAppOptimizer_getBinderFreezeInfo(JNIEnv jint retVal = 0; - if(syncReceived) { - retVal |= SYNC_RECEIVED_WHILE_FROZEN;; - } - - if(asyncReceived) { - retVal |= ASYNC_RECEIVED_WHILE_FROZEN; - } + // bit 0 of sync_recv goes to bit 0 of retVal + retVal |= syncReceived & SYNC_RECEIVED_WHILE_FROZEN; + // bit 0 of async_recv goes to bit 1 of retVal + retVal |= (asyncReceived << 1) & ASYNC_RECEIVED_WHILE_FROZEN; + // bit 1 of sync_recv goes to bit 2 of retVal + retVal |= (syncReceived << 1) & TXNS_PENDING_WHILE_FROZEN; return retVal; } @@ -278,7 +281,7 @@ static const JNINativeMethod sMethods[] = { /* name, signature, funcPtr */ {"compactSystem", "()V", (void*)com_android_server_am_CachedAppOptimizer_compactSystem}, {"compactProcess", "(II)V", (void*)com_android_server_am_CachedAppOptimizer_compactProcess}, - {"freezeBinder", "(IZ)V", (void*)com_android_server_am_CachedAppOptimizer_freezeBinder}, + {"freezeBinder", "(IZ)I", (void*)com_android_server_am_CachedAppOptimizer_freezeBinder}, {"getBinderFreezeInfo", "(I)I", (void*)com_android_server_am_CachedAppOptimizer_getBinderFreezeInfo}, {"getFreezerCheckPath", "()Ljava/lang/String;", diff --git a/services/core/jni/stats/OWNERS b/services/core/jni/stats/OWNERS index 552cc0d31304..2611e5b6cee2 100644 --- a/services/core/jni/stats/OWNERS +++ b/services/core/jni/stats/OWNERS @@ -1,6 +1,8 @@ jeffreyhuang@google.com jtnguyen@google.com muhammadq@google.com +sharaieko@google.com singhtejinder@google.com tsaichristine@google.com yaochen@google.com +yro@google.com diff --git a/services/tests/mockingservicestests/src/com/android/server/DeviceIdleControllerTest.java b/services/tests/mockingservicestests/src/com/android/server/DeviceIdleControllerTest.java index acf50b4569c6..e472b062388e 100644 --- a/services/tests/mockingservicestests/src/com/android/server/DeviceIdleControllerTest.java +++ b/services/tests/mockingservicestests/src/com/android/server/DeviceIdleControllerTest.java @@ -323,6 +323,7 @@ public class DeviceIdleControllerTest { when(mPowerManager.newWakeLock(anyInt(), anyString())).thenReturn(mWakeLock); doNothing().when(mWakeLock).acquire(); doNothing().when(mAlarmManager).set(anyInt(), anyLong(), anyString(), any(), any()); + doNothing().when(mAlarmManager).setExact(anyInt(), anyLong(), anyString(), any(), any()); doNothing().when(mAlarmManager) .setWindow(anyInt(), anyLong(), anyLong(), anyString(), any(), any()); doReturn(mock(Sensor.class)).when(mSensorManager) diff --git a/services/tests/mockingservicestests/src/com/android/server/RescuePartyTest.java b/services/tests/mockingservicestests/src/com/android/server/RescuePartyTest.java index fd364ae77240..51fa8517e45f 100644 --- a/services/tests/mockingservicestests/src/com/android/server/RescuePartyTest.java +++ b/services/tests/mockingservicestests/src/com/android/server/RescuePartyTest.java @@ -37,6 +37,8 @@ import static org.mockito.Mockito.times; import android.content.ContentResolver; import android.content.Context; +import android.content.pm.ApplicationInfo; +import android.content.pm.PackageManager; import android.content.pm.VersionedPackage; import android.os.Bundle; import android.os.RecoverySystem; @@ -83,6 +85,8 @@ public class RescuePartyTest { private static final String CALLING_PACKAGE1 = "com.package.name1"; private static final String CALLING_PACKAGE2 = "com.package.name2"; private static final String CALLING_PACKAGE3 = "com.package.name3"; + private static final String PERSISTENT_PACKAGE = "com.persistent.package"; + private static final String NON_PERSISTENT_PACKAGE = "com.nonpersistent.package"; private static final String NAMESPACE1 = "namespace1"; private static final String NAMESPACE2 = "namespace2"; private static final String NAMESPACE3 = "namespace3"; @@ -103,6 +107,8 @@ public class RescuePartyTest { private PackageWatchdog mMockPackageWatchdog; @Mock(answer = Answers.RETURNS_DEEP_STUBS) private ContentResolver mMockContentResolver; + @Mock(answer = Answers.RETURNS_DEEP_STUBS) + private PackageManager mPackageManager; @Captor private ArgumentCaptor<RemoteCallback> mMonitorCallbackCaptor; @@ -129,6 +135,17 @@ public class RescuePartyTest { mNamespacesWiped = new HashSet<>(); when(mMockContext.getContentResolver()).thenReturn(mMockContentResolver); + when(mMockContext.getPackageManager()).thenReturn(mPackageManager); + ApplicationInfo persistentApplicationInfo = new ApplicationInfo(); + persistentApplicationInfo.flags |= + ApplicationInfo.FLAG_SYSTEM | ApplicationInfo.FLAG_PERSISTENT; + + // If the package name is PERSISTENT_PACKAGE, then set the flags to be persistent and + // system. Don't set any flags otherwise. + when(mPackageManager.getApplicationInfo(eq(PERSISTENT_PACKAGE), + anyInt())).thenReturn(persistentApplicationInfo); + when(mPackageManager.getApplicationInfo(eq(NON_PERSISTENT_PACKAGE), + anyInt())).thenReturn(new ApplicationInfo()); // Reset observer instance to get new mock context on every run RescuePartyObserver.reset(); @@ -241,29 +258,53 @@ public class RescuePartyTest { @Test public void testPersistentAppCrashDetectionWithExecutionForAllRescueLevels() { - notePersistentAppCrash(1); + noteAppCrash(1, true); verifySettingsResets(Settings.RESET_MODE_UNTRUSTED_DEFAULTS, /*resetNamespaces=*/ null, /*configResetVerifiedTimesMap=*/ null); - notePersistentAppCrash(2); + noteAppCrash(2, true); verifySettingsResets(Settings.RESET_MODE_UNTRUSTED_CHANGES, /*resetNamespaces=*/ null, /*configResetVerifiedTimesMap=*/ null); - notePersistentAppCrash(3); + noteAppCrash(3, true); verifySettingsResets(Settings.RESET_MODE_TRUSTED_DEFAULTS, /*resetNamespaces=*/ null, /*configResetVerifiedTimesMap=*/ null); - notePersistentAppCrash(4); + noteAppCrash(4, true); assertTrue(RescueParty.isRebootPropertySet()); - notePersistentAppCrash(5); + noteAppCrash(5, true); assertTrue(RescueParty.isFactoryResetPropertySet()); } @Test + public void testNonPersistentAppOnlyPerformsFlagResets() { + noteAppCrash(1, false); + + verifySettingsResets(Settings.RESET_MODE_UNTRUSTED_DEFAULTS, /*resetNamespaces=*/ null, + /*configResetVerifiedTimesMap=*/ null); + + noteAppCrash(2, false); + + verifySettingsResets(Settings.RESET_MODE_UNTRUSTED_CHANGES, /*resetNamespaces=*/ null, + /*configResetVerifiedTimesMap=*/ null); + + noteAppCrash(3, false); + + verifySettingsResets(Settings.RESET_MODE_TRUSTED_DEFAULTS, /*resetNamespaces=*/ null, + /*configResetVerifiedTimesMap=*/ null); + + noteAppCrash(4, false); + assertFalse(RescueParty.isRebootPropertySet()); + + noteAppCrash(5, false); + assertFalse(RescueParty.isFactoryResetPropertySet()); + } + + @Test public void testNonPersistentAppCrashDetectionWithScopedResets() { RescueParty.onSettingsProviderPublished(mMockContext); verify(() -> Settings.Config.registerMonitorCallback(eq(mMockContentResolver), @@ -311,11 +352,11 @@ public class RescuePartyTest { observer.execute(new VersionedPackage( CALLING_PACKAGE1, 1), PackageWatchdog.FAILURE_REASON_APP_NOT_RESPONDING, 4); - assertTrue(RescueParty.isRebootPropertySet()); + assertFalse(RescueParty.isRebootPropertySet()); observer.execute(new VersionedPackage( CALLING_PACKAGE1, 1), PackageWatchdog.FAILURE_REASON_APP_NOT_RESPONDING, 5); - assertTrue(RescueParty.isFactoryResetPropertySet()); + assertFalse(RescueParty.isFactoryResetPropertySet()); } @Test @@ -376,11 +417,11 @@ public class RescuePartyTest { observer.execute(new VersionedPackage( CALLING_PACKAGE1, 1), PackageWatchdog.FAILURE_REASON_APP_NOT_RESPONDING, 4); - assertTrue(RescueParty.isRebootPropertySet()); + assertFalse(RescueParty.isRebootPropertySet()); observer.execute(new VersionedPackage( CALLING_PACKAGE1, 1), PackageWatchdog.FAILURE_REASON_APP_NOT_RESPONDING, 5); - assertTrue(RescueParty.isFactoryResetPropertySet()); + assertFalse(RescueParty.isFactoryResetPropertySet()); } @Test @@ -627,9 +668,10 @@ public class RescuePartyTest { RescuePartyObserver.getInstance(mMockContext).executeBootLoopMitigation(mitigationCount); } - private void notePersistentAppCrash(int mitigationCount) { + private void noteAppCrash(int mitigationCount, boolean isPersistent) { + String packageName = isPersistent ? PERSISTENT_PACKAGE : NON_PERSISTENT_PACKAGE; RescuePartyObserver.getInstance(mMockContext).execute(new VersionedPackage( - "com.package.name", 1), PackageWatchdog.FAILURE_REASON_APP_CRASH, mitigationCount); + packageName, 1), PackageWatchdog.FAILURE_REASON_APP_CRASH, mitigationCount); } private Bundle getConfigAccessBundle(String callingPackage, String namespace) { diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/BiometricSchedulerTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/BiometricSchedulerTest.java index f4d14995f7c7..a41f79e8f682 100644 --- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/BiometricSchedulerTest.java +++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/BiometricSchedulerTest.java @@ -348,6 +348,17 @@ public class BiometricSchedulerTest { verify((Interruptable) interruptableMonitor, never()).cancel(); } + @Test + public void testClientDestroyed_afterFinish() { + final HalClientMonitor.LazyDaemon<Object> nonNullDaemon = () -> mock(Object.class); + final TestClientMonitor client = + new TestClientMonitor(mContext, mToken, nonNullDaemon); + mScheduler.scheduleClientMonitor(client); + client.mCallback.onClientFinished(client, true /* success */); + waitForIdle(); + assertTrue(client.wasDestroyed()); + } + private BiometricSchedulerProto getDump(boolean clearSchedulerBuffer) throws Exception { return BiometricSchedulerProto.parseFrom(mScheduler.dumpProtoState(clearSchedulerBuffer)); } @@ -437,6 +448,7 @@ public class BiometricSchedulerTest { private static class TestClientMonitor extends HalClientMonitor<Object> { private boolean mUnableToStart; private boolean mStarted; + private boolean mDestroyed; public TestClientMonitor(@NonNull Context context, @NonNull IBinder token, @NonNull LazyDaemon<Object> lazyDaemon) { @@ -475,6 +487,11 @@ public class BiometricSchedulerTest { } + @Override + public void destroy() { + mDestroyed = true; + } + public boolean wasUnableToStart() { return mUnableToStart; } @@ -482,6 +499,11 @@ public class BiometricSchedulerTest { public boolean hasStarted() { return mStarted; } + + public boolean wasDestroyed() { + return mDestroyed; + } + } private static void waitForIdle() { diff --git a/services/tests/servicestests/src/com/android/server/display/DisplayModeDirectorTest.java b/services/tests/servicestests/src/com/android/server/display/DisplayModeDirectorTest.java index 1ac28abb4c2f..0dd5c61121db 100644 --- a/services/tests/servicestests/src/com/android/server/display/DisplayModeDirectorTest.java +++ b/services/tests/servicestests/src/com/android/server/display/DisplayModeDirectorTest.java @@ -20,6 +20,8 @@ import static android.hardware.display.DisplayManager.DeviceConfig.KEY_FIXED_REF import static android.hardware.display.DisplayManager.DeviceConfig.KEY_FIXED_REFRESH_RATE_HIGH_DISPLAY_BRIGHTNESS_THRESHOLDS; import static android.hardware.display.DisplayManager.DeviceConfig.KEY_FIXED_REFRESH_RATE_LOW_AMBIENT_BRIGHTNESS_THRESHOLDS; import static android.hardware.display.DisplayManager.DeviceConfig.KEY_FIXED_REFRESH_RATE_LOW_DISPLAY_BRIGHTNESS_THRESHOLDS; +import static android.hardware.display.DisplayManager.DeviceConfig.KEY_REFRESH_RATE_IN_HBM_SUNLIGHT; +import static android.hardware.display.DisplayManager.DeviceConfig.KEY_REFRESH_RATE_IN_HBM_HDR; import static android.hardware.display.DisplayManager.DeviceConfig.KEY_REFRESH_RATE_IN_HIGH_ZONE; import static android.hardware.display.DisplayManager.DeviceConfig.KEY_REFRESH_RATE_IN_LOW_ZONE; @@ -56,7 +58,11 @@ import android.hardware.display.DisplayManagerInternal.RefreshRateLimitation; import android.hardware.display.DisplayManagerInternal.RefreshRateRange; import android.hardware.fingerprint.IUdfpsHbmListener; import android.os.Handler; +import android.os.IThermalEventListener; +import android.os.IThermalService; import android.os.Looper; +import android.os.RemoteException; +import android.os.Temperature; import android.provider.DeviceConfig; import android.provider.Settings; import android.test.mock.MockContentResolver; @@ -116,6 +122,8 @@ public class DisplayModeDirectorTest { public SensorManagerInternal mSensorManagerInternalMock; @Mock public DisplayManagerInternal mDisplayManagerInternalMock; + @Mock + public IThermalService mThermalServiceMock; @Before public void setUp() throws Exception { @@ -124,6 +132,7 @@ public class DisplayModeDirectorTest { final MockContentResolver resolver = mSettingsProviderRule.mockContentResolver(mContext); when(mContext.getContentResolver()).thenReturn(resolver); mInjector = spy(new FakesInjector()); + when(mInjector.getThermalService()).thenReturn(mThermalServiceMock); mHandler = new Handler(Looper.getMainLooper()); LocalServices.removeServiceForTest(StatusBarManagerInternal.class); @@ -1401,6 +1410,12 @@ public class DisplayModeDirectorTest { public void testHbmVoting_forHdr() { DisplayModeDirector director = createDirectorFromRefreshRateArray(new float[] {60.0f, 90.0f}, 0); + final int hbmRefreshRate = 72; + + // Specify limitation before starting DisplayModeDirector to avoid waiting on property + // propagation + mInjector.getDeviceConfig().setRefreshRateInHbmHdr(hbmRefreshRate); + director.start(createMockSensorManager()); ArgumentCaptor<DisplayListener> captor = @@ -1425,7 +1440,7 @@ public class DisplayModeDirectorTest { new BrightnessInfo(0.45f, 0.0f, 1.0f, BrightnessInfo.HIGH_BRIGHTNESS_MODE_HDR)); listener.onDisplayChanged(DISPLAY_ID); vote = director.getVote(DISPLAY_ID, Vote.PRIORITY_HIGH_BRIGHTNESS_MODE); - assertVoteForRefreshRate(vote, 60.f); + assertVoteForRefreshRate(vote, hbmRefreshRate); // Turn off HBM when(mInjector.getBrightnessInfo(DISPLAY_ID)).thenReturn( @@ -1436,6 +1451,44 @@ public class DisplayModeDirectorTest { } @Test + public void testHbmObserverGetsUpdatedRefreshRateInHbmSunlight() { + DisplayModeDirector director = + createDirectorFromRefreshRateArray(new float[] {60.f, 90.f}, /* baseModeId= */ 0); + + final int initialRefreshRate = 60; + mInjector.getDeviceConfig().setRefreshRateInHbmSunlight(initialRefreshRate); + director.start(createMockSensorManager()); + assertThat(director.getHbmObserver().getRefreshRateInHbmSunlight()) + .isEqualTo(initialRefreshRate); + + final int updatedRefreshRate = 90; + mInjector.getDeviceConfig().setRefreshRateInHbmSunlight(updatedRefreshRate); + // Need to wait for the property change to propagate to the main thread. + waitForIdleSync(); + assertThat(director.getHbmObserver().getRefreshRateInHbmSunlight()) + .isEqualTo(updatedRefreshRate); + } + + @Test + public void testHbmObserverGetsUpdatedRefreshRateInHbmHdr() { + DisplayModeDirector director = + createDirectorFromRefreshRateArray(new float[] {60.f, 90.f}, /* baseModeId= */ 0); + + final int initialRefreshRate = 60; + mInjector.getDeviceConfig().setRefreshRateInHbmHdr(initialRefreshRate); + director.start(createMockSensorManager()); + assertThat(director.getHbmObserver().getRefreshRateInHbmHdr()) + .isEqualTo(initialRefreshRate); + + final int updatedRefreshRate = 90; + mInjector.getDeviceConfig().setRefreshRateInHbmHdr(updatedRefreshRate); + // Need to wait for the property change to propagate to the main thread. + waitForIdleSync(); + assertThat(director.getHbmObserver().getRefreshRateInHbmHdr()) + .isEqualTo(updatedRefreshRate); + } + + @Test public void testHbmVoting_forSunlight() { DisplayModeDirector director = createDirectorFromRefreshRateArray(new float[] {60.0f, 90.0f}, 0); @@ -1448,11 +1501,12 @@ public class DisplayModeDirectorTest { | DisplayManager.EVENT_FLAG_DISPLAY_REMOVED)); DisplayListener listener = captor.getValue(); + final int initialRefreshRate = 60; // Specify Limitation when(mDisplayManagerInternalMock.getRefreshRateLimitations(DISPLAY_ID)).thenReturn( List.of(new RefreshRateLimitation( DisplayManagerInternal.REFRESH_RATE_LIMIT_HIGH_BRIGHTNESS_MODE, - 60.f, 60.f))); + initialRefreshRate, initialRefreshRate))); // Verify that there is no HBM vote initially Vote vote = director.getVote(DISPLAY_ID, Vote.PRIORITY_HIGH_BRIGHTNESS_MODE); @@ -1463,7 +1517,39 @@ public class DisplayModeDirectorTest { new BrightnessInfo(1.0f, 0.0f, 1.0f, BrightnessInfo.HIGH_BRIGHTNESS_MODE_SUNLIGHT)); listener.onDisplayChanged(DISPLAY_ID); vote = director.getVote(DISPLAY_ID, Vote.PRIORITY_HIGH_BRIGHTNESS_MODE); - assertVoteForRefreshRate(vote, 60.f); + assertVoteForRefreshRate(vote, initialRefreshRate); + + // Change refresh rate vote value through DeviceConfig, ensure it takes precedence + final int updatedRefreshRate = 90; + mInjector.getDeviceConfig().setRefreshRateInHbmSunlight(updatedRefreshRate); + // Need to wait for the property change to propagate to the main thread. + waitForIdleSync(); + assertThat(director.getHbmObserver().getRefreshRateInHbmSunlight()) + .isEqualTo(updatedRefreshRate); + vote = director.getVote(DISPLAY_ID, Vote.PRIORITY_HIGH_BRIGHTNESS_MODE); + assertVoteForRefreshRate(vote, updatedRefreshRate); + + // Turn off HBM + when(mInjector.getBrightnessInfo(DISPLAY_ID)).thenReturn( + new BrightnessInfo(0.43f, 0.1f, 0.8f, BrightnessInfo.HIGH_BRIGHTNESS_MODE_OFF)); + listener.onDisplayChanged(DISPLAY_ID); + vote = director.getVote(DISPLAY_ID, Vote.PRIORITY_HIGH_BRIGHTNESS_MODE); + assertNull(vote); + + // Turn HBM on again and ensure the updated vote value stuck + when(mInjector.getBrightnessInfo(DISPLAY_ID)).thenReturn( + new BrightnessInfo(1.0f, 0.0f, 1.0f, BrightnessInfo.HIGH_BRIGHTNESS_MODE_SUNLIGHT)); + listener.onDisplayChanged(DISPLAY_ID); + vote = director.getVote(DISPLAY_ID, Vote.PRIORITY_HIGH_BRIGHTNESS_MODE); + assertVoteForRefreshRate(vote, updatedRefreshRate); + + // Reset DeviceConfig refresh rate, ensure vote falls back to the initial value + mInjector.getDeviceConfig().setRefreshRateInHbmSunlight(0); + // Need to wait for the property change to propagate to the main thread. + waitForIdleSync(); + assertThat(director.getHbmObserver().getRefreshRateInHbmSunlight()).isEqualTo(0); + vote = director.getVote(DISPLAY_ID, Vote.PRIORITY_HIGH_BRIGHTNESS_MODE); + assertVoteForRefreshRate(vote, initialRefreshRate); // Turn off HBM when(mInjector.getBrightnessInfo(DISPLAY_ID)).thenReturn( @@ -1511,6 +1597,63 @@ public class DisplayModeDirectorTest { assertNull(vote); } + private void setHbmAndAssertRefreshRate( + DisplayModeDirector director, DisplayListener listener, int mode, float rr) { + when(mInjector.getBrightnessInfo(DISPLAY_ID)) + .thenReturn(new BrightnessInfo(1.0f, 0.0f, 1.0f, mode)); + listener.onDisplayChanged(DISPLAY_ID); + + final Vote vote = director.getVote(DISPLAY_ID, Vote.PRIORITY_HIGH_BRIGHTNESS_MODE); + if (Float.isNaN(rr)) { + assertNull(vote); + } else { + assertVoteForRefreshRate(vote, rr); + } + } + + @Test + public void testHbmVoting_forSunlightAndHdr() { + DisplayModeDirector director = + createDirectorFromRefreshRateArray(new float[] {60.0f, 90.0f}, 0); + + // Specify HDR limitation before starting DisplayModeDirector to avoid waiting on property + // propagation + final int hdrRr = 60; + mInjector.getDeviceConfig().setRefreshRateInHbmHdr(hdrRr); + director.start(createMockSensorManager()); + + ArgumentCaptor<DisplayListener> captor = ArgumentCaptor.forClass(DisplayListener.class); + verify(mInjector).registerDisplayListener(captor.capture(), any(Handler.class), + eq(DisplayManager.EVENT_FLAG_DISPLAY_BRIGHTNESS + | DisplayManager.EVENT_FLAG_DISPLAY_REMOVED)); + DisplayListener listener = captor.getValue(); + + // Specify Sunlight limitations + final float sunlightRr = 90.0f; + when(mDisplayManagerInternalMock.getRefreshRateLimitations(DISPLAY_ID)) + .thenReturn(List.of(new RefreshRateLimitation( + DisplayManagerInternal.REFRESH_RATE_LIMIT_HIGH_BRIGHTNESS_MODE, sunlightRr, + sunlightRr))); + + // Verify that there is no HBM vote initially + Vote vote = director.getVote(DISPLAY_ID, Vote.PRIORITY_HIGH_BRIGHTNESS_MODE); + assertNull(vote); + + // Verify all state transitions + setHbmAndAssertRefreshRate( + director, listener, BrightnessInfo.HIGH_BRIGHTNESS_MODE_SUNLIGHT, sunlightRr); + setHbmAndAssertRefreshRate( + director, listener, BrightnessInfo.HIGH_BRIGHTNESS_MODE_HDR, hdrRr); + setHbmAndAssertRefreshRate( + director, listener, BrightnessInfo.HIGH_BRIGHTNESS_MODE_OFF, Float.NaN); + setHbmAndAssertRefreshRate( + director, listener, BrightnessInfo.HIGH_BRIGHTNESS_MODE_HDR, hdrRr); + setHbmAndAssertRefreshRate( + director, listener, BrightnessInfo.HIGH_BRIGHTNESS_MODE_SUNLIGHT, sunlightRr); + setHbmAndAssertRefreshRate( + director, listener, BrightnessInfo.HIGH_BRIGHTNESS_MODE_OFF, Float.NaN); + } + @Test public void testHbmVoting_RemovedDisplay() { DisplayModeDirector director = @@ -1547,12 +1690,52 @@ public class DisplayModeDirectorTest { assertNull(vote); } + @Test + public void testSkinTemperature() throws RemoteException { + DisplayModeDirector director = + createDirectorFromRefreshRateArray(new float[] {60.0f, 90.0f}, 0); + director.start(createMockSensorManager()); + + ArgumentCaptor<IThermalEventListener> thermalEventListener = + ArgumentCaptor.forClass(IThermalEventListener.class); + + verify(mThermalServiceMock).registerThermalEventListenerWithType( + thermalEventListener.capture(), eq(Temperature.TYPE_SKIN)); + final IThermalEventListener listener = thermalEventListener.getValue(); + + // Verify that there is no skin temperature vote initially. + Vote vote = director.getVote(DISPLAY_ID, Vote.PRIORITY_SKIN_TEMPERATURE); + assertNull(vote); + + // Set the skin temperature to critical and verify that we added a vote. + listener.notifyThrottling(getSkinTemp(Temperature.THROTTLING_CRITICAL)); + vote = director.getVote(DISPLAY_ID, Vote.PRIORITY_SKIN_TEMPERATURE); + assertVoteForRefreshRateRange(vote, 0f, 60.f); + + // Set the skin temperature to severe and verify that the vote is gone. + listener.notifyThrottling(getSkinTemp(Temperature.THROTTLING_SEVERE)); + vote = director.getVote(DISPLAY_ID, Vote.PRIORITY_SKIN_TEMPERATURE); + assertNull(vote); + } + + private Temperature getSkinTemp(@Temperature.ThrottlingStatus int status) { + return new Temperature(30.0f, Temperature.TYPE_SKIN, "test_skin_temp", status); + } + private void assertVoteForRefreshRate(Vote vote, float refreshRate) { assertThat(vote).isNotNull(); final RefreshRateRange expectedRange = new RefreshRateRange(refreshRate, refreshRate); assertThat(vote.refreshRateRange).isEqualTo(expectedRange); } + private void assertVoteForRefreshRateRange( + Vote vote, float refreshRateLow, float refreshRateHigh) { + assertThat(vote).isNotNull(); + final RefreshRateRange expectedRange = + new RefreshRateRange(refreshRateLow, refreshRateHigh); + assertThat(vote.refreshRateRange).isEqualTo(expectedRange); + } + public static class FakeDeviceConfig extends FakeDeviceConfigInterface { @Override public String getProperty(String namespace, String name) { @@ -1575,6 +1758,16 @@ public class DisplayModeDirectorTest { String.valueOf(fps)); } + void setRefreshRateInHbmSunlight(int fps) { + putPropertyAndNotify(DeviceConfig.NAMESPACE_DISPLAY_MANAGER, + KEY_REFRESH_RATE_IN_HBM_SUNLIGHT, String.valueOf(fps)); + } + + void setRefreshRateInHbmHdr(int fps) { + putPropertyAndNotify(DeviceConfig.NAMESPACE_DISPLAY_MANAGER, + KEY_REFRESH_RATE_IN_HBM_HDR, String.valueOf(fps)); + } + void setLowDisplayBrightnessThresholds(int[] brightnessThresholds) { String thresholds = toPropertyValue(brightnessThresholds); @@ -1748,6 +1941,11 @@ public class DisplayModeDirectorTest { return false; } + @Override + public IThermalService getThermalService() { + return null; + } + void notifyPeakRefreshRateChanged() { if (mPeakRefreshRateObserver != null) { mPeakRefreshRateObserver.dispatchChange(false /*selfChange*/, diff --git a/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java b/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java index 12fc2f4ea1f4..5a6581f94154 100644 --- a/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java @@ -2166,6 +2166,21 @@ public class DisplayContentTests extends WindowTestsBase { } @Test + public void testKeyguardGoingAwayWhileAodShown() { + mDisplayContent.getDisplayPolicy().setAwake(true); + + final WindowState appWin = createWindow(null, TYPE_APPLICATION, mDisplayContent, "appWin"); + final ActivityRecord activity = appWin.mActivityRecord; + + mAtm.mKeyguardController.setKeyguardShown(true /* keyguardShowing */, + true /* aodShowing */); + assertFalse(activity.isVisibleRequested()); + + mAtm.mKeyguardController.keyguardGoingAway(0 /* flags */); + assertTrue(activity.isVisibleRequested()); + } + + @Test public void testRemoveRootTaskInWindowingModes() { removeRootTaskTests(() -> mRootWindowContainer.removeRootTasksInWindowingModes( WINDOWING_MODE_FULLSCREEN)); diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowOrganizerTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowOrganizerTests.java index d6a8401f5b18..ab496cf34acc 100644 --- a/services/tests/wmtests/src/com/android/server/wm/WindowOrganizerTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/WindowOrganizerTests.java @@ -797,6 +797,9 @@ public class WindowOrganizerTests extends WindowTestsBase { public void onBackPressedOnTaskRoot(RunningTaskInfo taskInfo) { } @Override + public void onImeDrawnOnTask(int taskId) throws RemoteException { + } + @Override public void onAppSplashScreenViewRemoved(int taskId) { } }; diff --git a/services/usb/java/com/android/server/usb/UsbDeviceManager.java b/services/usb/java/com/android/server/usb/UsbDeviceManager.java index 1579aaf9a571..8cb0909def5d 100644 --- a/services/usb/java/com/android/server/usb/UsbDeviceManager.java +++ b/services/usb/java/com/android/server/usb/UsbDeviceManager.java @@ -1195,7 +1195,8 @@ public class UsbDeviceManager implements ActivityTaskManagerInternal.ScreenObser } else if (mCurrentFunctions == UsbManager.FUNCTION_MIDI) { titleRes = com.android.internal.R.string.usb_midi_notification_title; id = SystemMessage.NOTE_USB_MIDI; - } else if (mCurrentFunctions == UsbManager.FUNCTION_RNDIS) { + } else if ((mCurrentFunctions == UsbManager.FUNCTION_RNDIS) + || (mCurrentFunctions == UsbManager.FUNCTION_NCM)) { titleRes = com.android.internal.R.string.usb_tether_notification_title; id = SystemMessage.NOTE_USB_TETHER; } else if (mCurrentFunctions == UsbManager.FUNCTION_ACCESSORY) { diff --git a/telecomm/java/android/telecom/Connection.java b/telecomm/java/android/telecom/Connection.java index d06fe45a104c..9fec96b7f549 100644 --- a/telecomm/java/android/telecom/Connection.java +++ b/telecomm/java/android/telecom/Connection.java @@ -781,6 +781,21 @@ public abstract class Connection extends Conferenceable { "android.telecom.extra.REMOTE_PHONE_ACCOUNT_HANDLE"; /** + * The Telecom call ID of the conference an existing connection should be added to. This is + * required when {@link com.android.services.telephony.TelephonyConnectionService} adds a + * {@link Conference} to Telecom using the + * {@link ConnectionService#addExistingConnection(PhoneAccountHandle, Connection, Conference)} + * API. That API specifies a parent conference associated with the new existing connection + * being added, and there is no equivalent as part of the {@link RemoteConnectionService} API. + * This extra key is used to stack the ID of the conference to which the existing connection + * will be added so that Telecom can link it up correctly when the {@link RemoteConference} + * is added to Telecom by the connection manager. + * @hide + */ + public static final String EXTRA_ADD_TO_CONFERENCE_ID = + "android.telecom.extra.ADD_TO_CONFERENCE_ID"; + + /** * Extra key set from a {@link ConnectionService} when using the remote connection APIs * (e.g. {@link RemoteConnectionService#createRemoteConnection(PhoneAccountHandle, * ConnectionRequest, boolean)}) to create a remote connection. Provides the receiving diff --git a/telecomm/java/android/telecom/RemoteConnectionService.java b/telecomm/java/android/telecom/RemoteConnectionService.java index bf6a6ef793ff..efe35d21c003 100644 --- a/telecomm/java/android/telecom/RemoteConnectionService.java +++ b/telecomm/java/android/telecom/RemoteConnectionService.java @@ -239,13 +239,9 @@ final class RemoteConnectionService { conference.addConnection(c); } } - if (conference.getConnections().size() == 0) { - // A conference was created, but none of its connections are ones that have been - // created by, and therefore being tracked by, this remote connection service. It - // is of no interest to us. - Log.d(this, "addConferenceCall - skipping"); - return; - } + // We used to skip adding empty conferences; however in the world of IMS conference + // calls we need to add them to the remote connection service because they will always + // start with no participants. conference.setState(parcel.getState()); conference.setConnectionCapabilities(parcel.getConnectionCapabilities()); @@ -379,6 +375,8 @@ final class RemoteConnectionService { @Override public void addExistingConnection(String callId, ParcelableConnection connection, Session.Info sessionInfo) { + Log.i(RemoteConnectionService.this, "addExistingConnection: callId=%s, conn=%s", callId, + connection); String callingPackage = mOurConnectionServiceImpl.getApplicationContext(). getOpPackageName(); int callingTargetSdkVersion = mOurConnectionServiceImpl.getApplicationInfo() @@ -390,6 +388,20 @@ final class RemoteConnectionService { Bundle newExtras = new Bundle(); newExtras.putParcelable(Connection.EXTRA_REMOTE_PHONE_ACCOUNT_HANDLE, connection.getPhoneAccount()); + if (connection.getParentCallId() != null) { + RemoteConference parentConf = mConferenceById.get(connection.getParentCallId()); + // If there is a parent being set, we need to stash the conference ID here. + // Telephony can add an existing connection while specifying a parent conference. + // There is no equivalent version of that operation as part of the remote connection + // API, so we will stash the pre-defined parent's ID in the extras. When the + // connectionmanager copies over the extras from the remote connection to the + // actual one, it'll get passed to Telecom so that it can make the association. + if (parentConf != null) { + newExtras.putString(Connection.EXTRA_ADD_TO_CONFERENCE_ID, parentConf.getId()); + Log.i(this, "addExistingConnection: stash parent of %s as %s", + connection.getParentCallId(), parentConf.getId()); + } + } remoteConnection.putExtras(newExtras); mConnectionById.put(callId, remoteConnection); remoteConnection.registerCallback(new RemoteConnection.Callback() { |