diff options
82 files changed, 1620 insertions, 547 deletions
diff --git a/core/api/current.txt b/core/api/current.txt index db6dc5d5dc38..e53f9445b8a0 100644 --- a/core/api/current.txt +++ b/core/api/current.txt @@ -54946,6 +54946,7 @@ package android.view.animation { public class AnimationUtils { ctor public AnimationUtils(); method public static long currentAnimationTimeMillis(); + method public static long getExpectedPresentationTimeMillis(); method public static long getExpectedPresentationTimeNanos(); method public static android.view.animation.Animation loadAnimation(android.content.Context, @AnimRes int) throws android.content.res.Resources.NotFoundException; method public static android.view.animation.Interpolator loadInterpolator(android.content.Context, @AnimRes @InterpolatorRes int) throws android.content.res.Resources.NotFoundException; diff --git a/core/api/system-current.txt b/core/api/system-current.txt index 070822ef49ed..cf24f201315c 100644 --- a/core/api/system-current.txt +++ b/core/api/system-current.txt @@ -15221,7 +15221,7 @@ package android.telephony.data { method @NonNull public android.telephony.data.DataCallResponse.Builder setGatewayAddresses(@NonNull java.util.List<java.net.InetAddress>); method @NonNull public android.telephony.data.DataCallResponse.Builder setHandoverFailureMode(int); method @NonNull public android.telephony.data.DataCallResponse.Builder setId(int); - method @NonNull public android.telephony.data.DataCallResponse.Builder setInterfaceName(@NonNull String); + method @NonNull public android.telephony.data.DataCallResponse.Builder setInterfaceName(@Nullable String); method @NonNull public android.telephony.data.DataCallResponse.Builder setLinkStatus(int); method @Deprecated @NonNull public android.telephony.data.DataCallResponse.Builder setMtu(int); method @NonNull public android.telephony.data.DataCallResponse.Builder setMtuV4(int); diff --git a/core/java/android/content/BroadcastReceiver.java b/core/java/android/content/BroadcastReceiver.java index 3d76b28a3ccc..d7195a76d873 100644 --- a/core/java/android/content/BroadcastReceiver.java +++ b/core/java/android/content/BroadcastReceiver.java @@ -65,7 +65,7 @@ public abstract class BroadcastReceiver { * thread of your app. * * <p>Note on threading: the state inside of this class is not itself - * thread-safe, however you can use it from any thread if you properly + * thread-safe. However, you can use it from any thread if you make * sure that you do not have races. Typically this means you will hand * the entire object to another thread, which will be solely responsible * for setting any results and finally calling {@link #finish()}. diff --git a/core/java/android/nfc/NfcAdapter.java b/core/java/android/nfc/NfcAdapter.java index 9e97216ac632..c111138d6550 100644 --- a/core/java/android/nfc/NfcAdapter.java +++ b/core/java/android/nfc/NfcAdapter.java @@ -26,8 +26,6 @@ import android.annotation.SdkConstant.SdkConstantType; import android.annotation.SystemApi; import android.annotation.UserIdInt; import android.app.Activity; -import android.app.ActivityThread; -import android.app.OnActivityPausedListener; import android.app.PendingIntent; import android.compat.annotation.UnsupportedAppUsage; import android.content.Context; @@ -1473,17 +1471,11 @@ public final class NfcAdapter { if (activity == null || intent == null) { throw new NullPointerException(); } - if (!activity.isResumed()) { - throw new IllegalStateException("Foreground dispatch can only be enabled " + - "when your activity is resumed"); - } try { TechListParcel parcel = null; if (techLists != null && techLists.length > 0) { parcel = new TechListParcel(techLists); } - ActivityThread.currentActivityThread().registerOnActivityPausedListener(activity, - mForegroundDispatchListener); sService.setForegroundDispatch(intent, filters, parcel); } catch (RemoteException e) { attemptDeadServiceRecovery(e); @@ -1511,25 +1503,8 @@ public final class NfcAdapter { throw new UnsupportedOperationException(); } } - ActivityThread.currentActivityThread().unregisterOnActivityPausedListener(activity, - mForegroundDispatchListener); - disableForegroundDispatchInternal(activity, false); - } - - OnActivityPausedListener mForegroundDispatchListener = new OnActivityPausedListener() { - @Override - public void onPaused(Activity activity) { - disableForegroundDispatchInternal(activity, true); - } - }; - - void disableForegroundDispatchInternal(Activity activity, boolean force) { try { sService.setForegroundDispatch(null, null, null); - if (!force && !activity.isResumed()) { - throw new IllegalStateException("You must disable foreground dispatching " + - "while your activity is still resumed"); - } } catch (RemoteException e) { attemptDeadServiceRecovery(e); } diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java index 1ecfd74b28a9..565ed61ce30d 100644 --- a/core/java/android/view/View.java +++ b/core/java/android/view/View.java @@ -8662,15 +8662,43 @@ public class View implements Drawable.Callback, KeyEvent.Callback, } /** - * Dispatches an {@link AccessibilityEvent} to the {@link View} first and then - * to its children for adding their text content to the event. Note that the - * event text is populated in a separate dispatch path since we add to the + * Dispatches an {@link AccessibilityEvent} to the {@link View} to add the text content of the + * view and its children. + * <p> + * <b>Note:</b> This method should only be used with event.setText(). + * Avoid mutating other event state in this method. In general, put UI metadata in the node for + * services to easily query. + * <ul> + * <li> If you are modifying other event properties, you may be eliminating semantics + * accessibility services may want. Instead, send a separate event using + * {@link #sendAccessibilityEvent(int)} and override + * {@link #onInitializeAccessibilityEvent(AccessibilityEvent)}. + * </li> + * <li>If you are checking for type {@link AccessibilityEvent#TYPE_WINDOW_STATE_CHANGED} + * to generate window/title announcements, you may be causing disruptive announcements + * (or making no announcements at all). Instead, follow the practices described in + * {@link View#announceForAccessibility(CharSequence)}. <b>Note:</b> this does not suggest + * calling announceForAccessibility(), but using the suggestions listed in its + * documentation. + * </li> + * <li>If you are making changes based on the state of accessibility, such as checking for + * an event type to trigger a UI update, while well-intentioned, you are creating brittle, + * less well-maintained code that works for some users but not others. Instead, leverage + * existing code for equitable experiences and less technical debt. See + * {@link AccessibilityManager#isEnabled()} for an example. + * </li> + * </ul> + * <p> + * Note that the event text is populated in a separate dispatch path + * ({@link #onPopulateAccessibilityEvent(AccessibilityEvent)}) since we add to the * event not only the text of the source but also the text of all its descendants. + * <p> * A typical implementation will call - * {@link #onPopulateAccessibilityEvent(AccessibilityEvent)} on the this view + * {@link #onPopulateAccessibilityEvent(AccessibilityEvent)} on this view * and then call the {@link #dispatchPopulateAccessibilityEvent(AccessibilityEvent)} - * on each child. Override this method if custom population of the event text - * content is required. + * on each child or the first child that is visible. Override this method if custom population + * of the event text content is required. + * * <p> * If an {@link AccessibilityDelegate} has been specified via calling * {@link #setAccessibilityDelegate(AccessibilityDelegate)} its @@ -8714,9 +8742,12 @@ public class View implements Drawable.Callback, KeyEvent.Callback, /** * Called from {@link #dispatchPopulateAccessibilityEvent(AccessibilityEvent)} * giving a chance to this View to populate the accessibility event with its - * text content. While this method is free to modify event - * attributes other than text content, doing so should normally be performed in - * {@link #onInitializeAccessibilityEvent(AccessibilityEvent)}. + * text content. + * <p> + * <b>Note:</b> This method should only be used with event.setText(). + * Avoid mutating other event state in this method. Instead, follow the practices described in + * {@link #dispatchPopulateAccessibilityEvent(AccessibilityEvent)}. In general, put UI + * metadata in the node for services to easily query, than in transient events. * <p> * Example: Adding formatted date string to an accessibility event in addition * to the text added by the super implementation: @@ -11256,26 +11287,36 @@ public class View implements Drawable.Callback, KeyEvent.Callback, } /** - * Sets the id of a view before which this one is visited in accessibility traversal. - * A screen-reader must visit the content of this view before the content of the one - * it precedes. For example, if view B is set to be before view A, then a screen-reader - * will traverse the entire content of B before traversing the entire content of A, - * regardles of what traversal strategy it is using. + * Sets the id of a view that screen readers are requested to visit after this view. + * * <p> - * Views that do not have specified before/after relationships are traversed in order - * determined by the screen-reader. - * </p> + * + * For example, if view B should be visited before view A, with + * B.setAccessibilityTraversalBefore(A), this requests that screen readers visit and traverse + * view B before visiting view A. + * * <p> - * Setting that this view is before a view that is not important for accessibility - * or if this view is not important for accessibility will have no effect as the - * screen-reader is not aware of unimportant views. - * </p> + * <b>Note:</b> Views are visited in the order determined by the screen reader. Avoid + * explicitly manipulating focus order, as this may result in inconsistent user + * experiences between apps. Instead, use other semantics, such as restructuring the view + * hierarchy layout, to communicate order. + * + * <p> + * Setting this view to be after a view that is not important for accessibility, + * or if this view is not important for accessibility, means this method will have no effect if + * the service is not aware of unimportant views. + * + * <p> + * To avoid a risk of loops, set clear relationships between views. For example, if focus order + * should be B -> A, and B.setAccessibilityTraversalBefore(A), then also call + * A.setAccessibilityTraversalAfter(B). * * @param beforeId The id of a view this one precedes in accessibility traversal. * * @attr ref android.R.styleable#View_accessibilityTraversalBefore * * @see #setImportantForAccessibility(int) + * @see #setAccessibilityTraversalAfter(int) */ @RemotableViewMethod public void setAccessibilityTraversalBefore(@IdRes int beforeId) { @@ -11302,26 +11343,34 @@ public class View implements Drawable.Callback, KeyEvent.Callback, } /** - * Sets the id of a view after which this one is visited in accessibility traversal. - * A screen-reader must visit the content of the other view before the content of this - * one. For example, if view B is set to be after view A, then a screen-reader - * will traverse the entire content of A before traversing the entire content of B, - * regardles of what traversal strategy it is using. + * Sets the id of a view that screen readers are requested to visit before this view. + * * <p> - * Views that do not have specified before/after relationships are traversed in order - * determined by the screen-reader. - * </p> + * For example, if view B should be visited after A, with B.setAccessibilityTraversalAfter(A), + * then this requests that screen readers visit and traverse view A before visiting view B. + * * <p> - * Setting that this view is after a view that is not important for accessibility - * or if this view is not important for accessibility will have no effect as the - * screen-reader is not aware of unimportant views. - * </p> + * <b>Note:</b> Views are visited in the order determined by the screen reader. Avoid + * explicitly manipulating focus order, as this may result in inconsistent user + * experiences between apps. Instead, use other semantics, such as restructuring the view + * hierarchy layout, to communicate order. + * + * <p> + * Setting this view to be after a view that is not important for accessibility, + * or if this view is not important for accessibility, means this method will have no effect if + * the service is not aware of unimportant views. * - * @param afterId The id of a view this one succedees in accessibility traversal. + * <p> + * To avoid a risk of loops, set clear relationships between views. For example, if focus order + * should be B -> A, and B.setAccessibilityTraversalBefore(A), then also call + * A.setAccessibilityTraversalAfter(B). + * + * @param afterId The id of a view this one succeeds in accessibility traversal. * * @attr ref android.R.styleable#View_accessibilityTraversalAfter * * @see #setImportantForAccessibility(int) + * @see #setAccessibilityTraversalBefore(int) */ @RemotableViewMethod public void setAccessibilityTraversalAfter(@IdRes int afterId) { @@ -14619,19 +14668,31 @@ public class View implements Drawable.Callback, KeyEvent.Callback, * to the view's content description or text, or to the content descriptions * or text of the view's children (where applicable). * <p> - * For example, in a login screen with a TextView that displays an "incorrect - * password" notification, that view should be marked as a live region with - * mode {@link #ACCESSIBILITY_LIVE_REGION_POLITE}. + * To indicate that the user should be notified of changes, use + * {@link #ACCESSIBILITY_LIVE_REGION_POLITE}. Announcements from this region are queued and + * do not disrupt ongoing speech. + * <p> + * For example, selecting an option in a dropdown menu may update a panel below with the updated + * content. This panel may be marked as a live region with + * {@link #ACCESSIBILITY_LIVE_REGION_POLITE} to notify users of the change. + * <p> + * For notifying users about errors, such as in a login screen with text that displays an + * "incorrect password" notification, that view should send an AccessibilityEvent of type + * {@link AccessibilityEvent#CONTENT_CHANGE_TYPE_ERROR} and set + * {@link AccessibilityNodeInfo#setError(CharSequence)} instead. Custom widgets should expose + * error-setting methods that support accessibility automatically. For example, instead of + * explicitly sending this event when using a TextView, use + * {@link android.widget.TextView#setError(CharSequence)}. * <p> * To disable change notifications for this view, use * {@link #ACCESSIBILITY_LIVE_REGION_NONE}. This is the default live region * mode for most views. * <p> - * To indicate that the user should be notified of changes, use - * {@link #ACCESSIBILITY_LIVE_REGION_POLITE}. - * <p> * If the view's changes should interrupt ongoing speech and notify the user - * immediately, use {@link #ACCESSIBILITY_LIVE_REGION_ASSERTIVE}. + * immediately, use {@link #ACCESSIBILITY_LIVE_REGION_ASSERTIVE}. This may result in disruptive + * announcements from an accessibility service, so it should generally be used only to convey + * information that is time-sensitive or critical for use of the application. Examples may + * include an incoming call or an emergency alert. * <p> * <b>Note:</b> Use {@link androidx.core.view.ViewCompat#setAccessibilityLiveRegion(View, int)} * for backwards-compatibility. </aside> diff --git a/core/java/android/view/animation/AnimationUtils.java b/core/java/android/view/animation/AnimationUtils.java index f31a43f5924f..8ba8b8cca5ed 100644 --- a/core/java/android/view/animation/AnimationUtils.java +++ b/core/java/android/view/animation/AnimationUtils.java @@ -30,6 +30,7 @@ import android.content.res.Resources.Theme; import android.content.res.XmlResourceParser; import android.os.SystemClock; import android.util.AttributeSet; +import android.util.TimeUtils; import android.util.Xml; import android.view.InflateException; @@ -156,6 +157,18 @@ public class AnimationUtils { } /** + * The expected presentation time of a frame in the {@link SystemClock#uptimeMillis()}. + * Developers should prefer using this method over {@link #currentAnimationTimeMillis()} + * because it offers a more accurate time for the calculating animation progress. + * + * @return the expected presentation time of a frame in the + * {@link SystemClock#uptimeMillis()} time base. + */ + public static long getExpectedPresentationTimeMillis() { + return getExpectedPresentationTimeNanos() / TimeUtils.NANOS_PER_MS; + } + + /** * Loads an {@link Animation} object from a resource * * @param context Application context used to access resources diff --git a/core/jni/Android.bp b/core/jni/Android.bp index 8024a6315e04..c19265a83441 100644 --- a/core/jni/Android.bp +++ b/core/jni/Android.bp @@ -379,7 +379,6 @@ cc_library_shared { "libbinary_parse", "libdng_sdk", "libft2", - "libhostgraphics", "libhwui", "libimage_type_recognition", "libjpeg", diff --git a/core/res/res/drawable-watch/global_actions_item_grey_background_shape.xml b/core/res/res/drawable-watch/global_actions_item_grey_background_shape.xml index f2df319c888d..3ac9ffba3cbf 100644 --- a/core/res/res/drawable-watch/global_actions_item_grey_background_shape.xml +++ b/core/res/res/drawable-watch/global_actions_item_grey_background_shape.xml @@ -18,5 +18,5 @@ <shape xmlns:android="http://schemas.android.com/apk/res/android" android:shape="rectangle"> <corners android:radius="26dp"/> - <solid android:color="@color/wear_material_grey_900"/> + <solid android:color="?attr/colorSurface"/> </shape>
\ No newline at end of file diff --git a/core/res/res/drawable-watch/global_actions_item_red_background_shape.xml b/core/res/res/drawable-watch/global_actions_item_red_background_shape.xml index 4f23700a9014..b85e01dd2e06 100644 --- a/core/res/res/drawable-watch/global_actions_item_red_background_shape.xml +++ b/core/res/res/drawable-watch/global_actions_item_red_background_shape.xml @@ -18,5 +18,5 @@ <shape xmlns:android="http://schemas.android.com/apk/res/android" android:shape="rectangle"> <corners android:radius="26dp"/> - <solid android:color="@color/wear_material_red_mid"/> + <solid android:color="?attr/colorError"/> </shape>
\ No newline at end of file diff --git a/core/res/res/layout-watch/global_actions_item.xml b/core/res/res/layout-watch/global_actions_item.xml index f964a4a53d4f..021c9abb81f6 100644 --- a/core/res/res/layout-watch/global_actions_item.xml +++ b/core/res/res/layout-watch/global_actions_item.xml @@ -36,7 +36,7 @@ android:textSize="15sp" android:letterSpacing="0.013" android:fadingEdgeLength="12dp" - android:textColor="@android:color/white" + android:textColor="?attr/textColorPrimary" android:layout_weight="1" android:fontFamily="google-sans-text-medium" android:layout_width="wrap_content" diff --git a/core/res/res/values-watch/colors.xml b/core/res/res/values-watch/colors.xml deleted file mode 100644 index 6d908be48ff0..000000000000 --- a/core/res/res/values-watch/colors.xml +++ /dev/null @@ -1,22 +0,0 @@ -<?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> - <!-- Wear Material standard colors --> - <color name="wear_material_red_mid">#CC5D58</color> - <color name="wear_material_grey_900">#202124</color> -</resources>
\ No newline at end of file diff --git a/core/res/res/values/attrs.xml b/core/res/res/values/attrs.xml index 55122ce25ea1..d80cfa340dcb 100644 --- a/core/res/res/values/attrs.xml +++ b/core/res/res/values/attrs.xml @@ -3030,15 +3030,15 @@ representation this attribute can be used for providing such. --> <attr name="contentDescription" format="string" localization="suggested" /> - <!-- Sets the id of a view before which this one is visited in accessibility traversal. - A screen-reader must visit the content of this view before the content of the one - it precedes. + <!-- Sets the id of a view that screen readers are requested to visit after this view. + Requests that a screen-reader visits the content of this view before the content of the + one it precedes. This does nothing if either view is not important for accessibility. {@see android.view.View#setAccessibilityTraversalBefore(int)} --> <attr name="accessibilityTraversalBefore" format="integer" /> - <!-- Sets the id of a view after which this one is visited in accessibility traversal. - A screen-reader must visit the content of the other view before the content of - this one. + <!-- Sets the id of a view that screen readers are requested to visit before this view. + Requests that a screen-reader visits the content of the other view before the content + of this one. This does nothing if either view is not important for accessibility. {@see android.view.View#setAccessibilityTraversalAfter(int)} --> <attr name="accessibilityTraversalAfter" format="integer" /> diff --git a/core/res/res/xml/sms_short_codes.xml b/core/res/res/xml/sms_short_codes.xml index f28da1faf770..3b099e8ccafc 100644 --- a/core/res/res/xml/sms_short_codes.xml +++ b/core/res/res/xml/sms_short_codes.xml @@ -40,7 +40,7 @@ <shortcode country="al" pattern="\\d{5}" premium="15191|55[56]00" /> <!-- Argentina: 5 digits, known short codes listed --> - <shortcode country="ar" pattern="\\d{5}" free="11711|28291|44077" /> + <shortcode country="ar" pattern="\\d{5}" free="11711|28291|44077|78887" /> <!-- Armenia: 3-4 digits, emergency numbers 10[123] --> <shortcode country="am" pattern="\\d{3,4}" premium="11[2456]1|3024" free="10[123]" /> @@ -162,7 +162,7 @@ <shortcode country="jp" pattern="\\d{1,5}" free="8083" /> <!-- Kenya: 5 digits, known premium codes listed --> - <shortcode country="ke" pattern="\\d{5}" free="21725|21562|40520|23342" /> + <shortcode country="ke" pattern="\\d{5}" free="21725|21562|40520|23342|40023" /> <!-- Kyrgyzstan: 4 digits, known premium codes listed --> <shortcode country="kg" pattern="\\d{4}" premium="415[2367]|444[69]" /> @@ -208,7 +208,7 @@ <shortcode country="nz" pattern="\\d{3,4}" premium="3903|8995|4679" free="1737|176|2141|3067|3068|3110|4006|4053|4061|4062|4202|4300|4334|4412|4575|5626|8006|8681" /> <!-- Peru: 4-5 digits (not confirmed), known premium codes listed --> - <shortcode country="pe" pattern="\\d{4,5}" free="9963|40777" /> + <shortcode country="pe" pattern="\\d{4,5}" free="9963|40778" /> <!-- Philippines --> <shortcode country="ph" pattern="\\d{1,5}" free="2147|5495|5496" /> diff --git a/graphics/java/android/graphics/Bitmap.java b/graphics/java/android/graphics/Bitmap.java index b9d3756ac6d2..a4c655c8ce55 100644 --- a/graphics/java/android/graphics/Bitmap.java +++ b/graphics/java/android/graphics/Bitmap.java @@ -782,10 +782,13 @@ public final class Bitmap implements Parcelable { @Nullable public static Bitmap wrapHardwareBuffer(@NonNull HardwareBuffer hardwareBuffer, @Nullable ColorSpace colorSpace) { - if ((hardwareBuffer.getUsage() & HardwareBuffer.USAGE_GPU_SAMPLED_IMAGE) == 0) { + final long usage = hardwareBuffer.getUsage(); + if ((usage & HardwareBuffer.USAGE_GPU_SAMPLED_IMAGE) == 0) { throw new IllegalArgumentException("usage flags must contain USAGE_GPU_SAMPLED_IMAGE."); } - int format = hardwareBuffer.getFormat(); + if ((usage & HardwareBuffer.USAGE_PROTECTED_CONTENT) != 0) { + throw new IllegalArgumentException("Bitmap is not compatible with protected buffers"); + } if (colorSpace == null) { colorSpace = ColorSpace.get(ColorSpace.Named.SRGB); } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransition.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransition.java index 6d7f7613b56e..73eb62ae47e9 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransition.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransition.java @@ -339,19 +339,36 @@ public class PipTransition extends PipTransitionController { } // This means an expand happened before enter-pip finished and we are now "merging" a // no-op transition that happens to match our exit-pip. + // Or that the keyguard is up and preventing the transition from applying, in which case we + // want to manually reset pip. (b/283783868) boolean cancelled = false; if (mPipAnimationController.getCurrentAnimator() != null) { mPipAnimationController.getCurrentAnimator().cancel(); + mPipAnimationController.resetAnimatorState(); cancelled = true; } + // Unset exitTransition AFTER cancel so that finishResize knows we are merging. mExitTransition = null; - if (!cancelled || aborted) return; + if (!cancelled) return; final ActivityManager.RunningTaskInfo taskInfo = mPipOrganizer.getTaskInfo(); if (taskInfo != null) { - startExpandAnimation(taskInfo, mPipOrganizer.getSurfaceControl(), - mPipBoundsState.getBounds(), mPipBoundsState.getBounds(), - new Rect(mExitDestinationBounds), Surface.ROTATION_0, null /* startT */); + if (aborted) { + // keyguard case - the transition got aborted, so we want to reset state and + // windowing mode before reapplying the resize transaction + sendOnPipTransitionFinished(TRANSITION_DIRECTION_LEAVE_PIP); + mPipOrganizer.onExitPipFinished(taskInfo); + + WindowContainerTransaction wct = new WindowContainerTransaction(); + mPipOrganizer.applyWindowingModeChangeOnExit(wct, TRANSITION_DIRECTION_LEAVE_PIP); + wct.setBounds(taskInfo.token, null); + mPipOrganizer.applyFinishBoundsResize(wct, TRANSITION_DIRECTION_LEAVE_PIP, false); + } else { + // merge case + startExpandAnimation(taskInfo, mPipOrganizer.getSurfaceControl(), + mPipBoundsState.getBounds(), mPipBoundsState.getBounds(), + new Rect(mExitDestinationBounds), Surface.ROTATION_0, null /* startT */); + } } mExitDestinationBounds.setEmpty(); mCurrentPipTaskToken = null; 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 c964df1452e0..c2f15f6cba75 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 @@ -16,6 +16,7 @@ package com.android.wm.shell.startingsurface; +import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED; import static android.graphics.Color.WHITE; import static android.os.Trace.TRACE_TAG_WINDOW_MANAGER; import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_STARTING; @@ -77,6 +78,13 @@ public class TaskSnapshotWindow { @NonNull Runnable clearWindowHandler) { final ActivityManager.RunningTaskInfo runningTaskInfo = info.taskInfo; final int taskId = runningTaskInfo.taskId; + + // if we're in PIP we don't want to create the snapshot + if (runningTaskInfo.getWindowingMode() == WINDOWING_MODE_PINNED) { + ProtoLog.v(ShellProtoLogGroup.WM_SHELL_STARTING_WINDOW, + "did not create taskSnapshot due to being in PIP"); + return null; + } ProtoLog.v(ShellProtoLogGroup.WM_SHELL_STARTING_WINDOW, "create taskSnapshot surface for task: %d", taskId); diff --git a/packages/CarrierDefaultApp/assets/slice_purchase_test.html b/packages/CarrierDefaultApp/assets/slice_purchase_test.html index 917276b9ce65..ad18a9d64074 100644 --- a/packages/CarrierDefaultApp/assets/slice_purchase_test.html +++ b/packages/CarrierDefaultApp/assets/slice_purchase_test.html @@ -81,5 +81,7 @@ Dismiss flow </button> <p id="dismiss_flow"></p> + + <h2>Test <a href="http://www.google.com">hyperlink</a></h2> </body> </html> diff --git a/packages/CarrierDefaultApp/src/com/android/carrierdefaultapp/SlicePurchaseActivity.java b/packages/CarrierDefaultApp/src/com/android/carrierdefaultapp/SlicePurchaseActivity.java index 2530257d629a..b1009808cccc 100644 --- a/packages/CarrierDefaultApp/src/com/android/carrierdefaultapp/SlicePurchaseActivity.java +++ b/packages/CarrierDefaultApp/src/com/android/carrierdefaultapp/SlicePurchaseActivity.java @@ -29,6 +29,7 @@ import android.util.Log; import android.view.KeyEvent; import android.webkit.CookieManager; import android.webkit.WebView; +import android.webkit.WebViewClient; import com.android.phone.slice.SlicePurchaseController; @@ -113,8 +114,10 @@ public class SlicePurchaseActivity extends Activity { return; } - // Create and configure WebView - setupWebView(); + // Clear any cookies that might be persisted from previous sessions before loading WebView + CookieManager.getInstance().removeAllCookies(value -> { + setupWebView(); + }); } protected void onPurchaseSuccessful() { @@ -176,12 +179,7 @@ public class SlicePurchaseActivity extends Activity { private void setupWebView() { // Create WebView mWebView = new WebView(this); - - // Clear any cookies and state that might be saved from previous sessions - CookieManager.getInstance().removeAllCookies(null); - CookieManager.getInstance().flush(); - mWebView.clearCache(true); - mWebView.clearHistory(); + mWebView.setWebViewClient(new WebViewClient()); // Enable JavaScript for the carrier purchase website to send results back to // the slice purchase application. diff --git a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/AnimatableClockView.kt b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/AnimatableClockView.kt index d2084047583b..b9d66432b5f2 100644 --- a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/AnimatableClockView.kt +++ b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/AnimatableClockView.kt @@ -33,8 +33,8 @@ import com.android.internal.annotations.VisibleForTesting import com.android.systemui.animation.GlyphCallback import com.android.systemui.animation.TextAnimator import com.android.systemui.customization.R -import com.android.systemui.log.LogBuffer -import com.android.systemui.log.core.LogLevel.DEBUG +import com.android.systemui.log.core.Logger +import com.android.systemui.log.core.MessageBuffer import java.io.PrintWriter import java.util.Calendar import java.util.Locale @@ -51,7 +51,12 @@ class AnimatableClockView @JvmOverloads constructor( defStyleAttr: Int = 0, defStyleRes: Int = 0 ) : TextView(context, attrs, defStyleAttr, defStyleRes) { - var logBuffer: LogBuffer? = null + var messageBuffer: MessageBuffer? = null + set(value) { + logger = if (value != null) Logger(value, TAG) else null + } + + private var logger: Logger? = null private val time = Calendar.getInstance() @@ -129,7 +134,7 @@ class AnimatableClockView @JvmOverloads constructor( override fun onAttachedToWindow() { super.onAttachedToWindow() - logBuffer?.log(TAG, DEBUG, "onAttachedToWindow") + logger?.d("onAttachedToWindow") refreshFormat() } @@ -145,39 +150,32 @@ class AnimatableClockView @JvmOverloads constructor( time.timeInMillis = timeOverrideInMillis ?: System.currentTimeMillis() contentDescription = DateFormat.format(descFormat, time) val formattedText = DateFormat.format(format, time) - logBuffer?.log(TAG, DEBUG, - { str1 = formattedText?.toString() }, - { "refreshTime: new formattedText=$str1" } - ) + logger?.d({ "refreshTime: new formattedText=$str1" }) { str1 = formattedText?.toString() } // Setting text actually triggers a layout pass (because the text view is set to // wrap_content width and TextView always relayouts for this). Avoid needless // relayout if the text didn't actually change. if (!TextUtils.equals(text, formattedText)) { text = formattedText - logBuffer?.log(TAG, DEBUG, - { str1 = formattedText?.toString() }, - { "refreshTime: done setting new time text to: $str1" } - ) + logger?.d({ "refreshTime: done setting new time text to: $str1" }) { + str1 = formattedText?.toString() + } // Because the TextLayout may mutate under the hood as a result of the new text, we // notify the TextAnimator that it may have changed and request a measure/layout. A // crash will occur on the next invocation of setTextStyle if the layout is mutated // without being notified TextInterpolator being notified. if (layout != null) { textAnimator?.updateLayout(layout) - logBuffer?.log(TAG, DEBUG, "refreshTime: done updating textAnimator layout") + logger?.d("refreshTime: done updating textAnimator layout") } requestLayout() - logBuffer?.log(TAG, DEBUG, "refreshTime: after requestLayout") + logger?.d("refreshTime: after requestLayout") } } fun onTimeZoneChanged(timeZone: TimeZone?) { time.timeZone = timeZone refreshFormat() - logBuffer?.log(TAG, DEBUG, - { str1 = timeZone?.toString() }, - { "onTimeZoneChanged newTimeZone=$str1" } - ) + logger?.d({ "onTimeZoneChanged newTimeZone=$str1" }) { str1 = timeZone?.toString() } } @SuppressLint("DrawAllocation") @@ -191,7 +189,7 @@ class AnimatableClockView @JvmOverloads constructor( } else { animator.updateLayout(layout) } - logBuffer?.log(TAG, DEBUG, "onMeasure") + logger?.d("onMeasure") } override fun onDraw(canvas: Canvas) { @@ -203,12 +201,12 @@ class AnimatableClockView @JvmOverloads constructor( } else { super.onDraw(canvas) } - logBuffer?.log(TAG, DEBUG, "onDraw") + logger?.d("onDraw") } override fun invalidate() { super.invalidate() - logBuffer?.log(TAG, DEBUG, "invalidate") + logger?.d("invalidate") } override fun onTextChanged( @@ -218,10 +216,7 @@ class AnimatableClockView @JvmOverloads constructor( lengthAfter: Int ) { super.onTextChanged(text, start, lengthBefore, lengthAfter) - logBuffer?.log(TAG, DEBUG, - { str1 = text.toString() }, - { "onTextChanged text=$str1" } - ) + logger?.d({ "onTextChanged text=$str1" }) { str1 = text.toString() } } fun setLineSpacingScale(scale: Float) { @@ -235,7 +230,7 @@ class AnimatableClockView @JvmOverloads constructor( } fun animateColorChange() { - logBuffer?.log(TAG, DEBUG, "animateColorChange") + logger?.d("animateColorChange") setTextStyle( weight = lockScreenWeight, textSize = -1f, @@ -257,7 +252,7 @@ class AnimatableClockView @JvmOverloads constructor( } fun animateAppearOnLockscreen() { - logBuffer?.log(TAG, DEBUG, "animateAppearOnLockscreen") + logger?.d("animateAppearOnLockscreen") setTextStyle( weight = dozingWeight, textSize = -1f, @@ -283,7 +278,7 @@ class AnimatableClockView @JvmOverloads constructor( if (isAnimationEnabled && textAnimator == null) { return } - logBuffer?.log(TAG, DEBUG, "animateFoldAppear") + logger?.d("animateFoldAppear") setTextStyle( weight = lockScreenWeightInternal, textSize = -1f, @@ -310,7 +305,7 @@ class AnimatableClockView @JvmOverloads constructor( // Skip charge animation if dozing animation is already playing. return } - logBuffer?.log(TAG, DEBUG, "animateCharge") + logger?.d("animateCharge") val startAnimPhase2 = Runnable { setTextStyle( weight = if (isDozing()) dozingWeight else lockScreenWeight, @@ -334,7 +329,7 @@ class AnimatableClockView @JvmOverloads constructor( } fun animateDoze(isDozing: Boolean, animate: Boolean) { - logBuffer?.log(TAG, DEBUG, "animateDoze") + logger?.d("animateDoze") setTextStyle( weight = if (isDozing) dozingWeight else lockScreenWeight, textSize = -1f, @@ -453,10 +448,7 @@ class AnimatableClockView @JvmOverloads constructor( isSingleLineInternal && !use24HourFormat -> Patterns.sClockView12 else -> DOUBLE_LINE_FORMAT_12_HOUR } - logBuffer?.log(TAG, DEBUG, - { str1 = format?.toString() }, - { "refreshFormat format=$str1" } - ) + logger?.d({ "refreshFormat format=$str1" }) { str1 = format?.toString() } descFormat = if (use24HourFormat) Patterns.sClockView24 else Patterns.sClockView12 refreshTime() diff --git a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/ClockRegistry.kt b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/ClockRegistry.kt index 12f7452fe913..d65edae1caf0 100644 --- a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/ClockRegistry.kt +++ b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/ClockRegistry.kt @@ -23,10 +23,11 @@ import android.os.UserHandle import android.provider.Settings import android.util.Log import androidx.annotation.OpenForTesting -import com.android.systemui.log.LogBuffer import com.android.systemui.log.LogMessageImpl import com.android.systemui.log.core.LogLevel import com.android.systemui.log.core.LogMessage +import com.android.systemui.log.core.Logger +import com.android.systemui.log.core.MessageBuffer import com.android.systemui.log.core.MessageInitializer import com.android.systemui.log.core.MessagePrinter import com.android.systemui.plugins.ClockController @@ -75,7 +76,7 @@ private fun <TKey, TVal> ConcurrentHashMap<TKey, TVal>.concurrentGetOrPut( private val TMP_MESSAGE: LogMessage by lazy { LogMessageImpl.Factory.create() } -private inline fun LogBuffer?.tryLog( +private inline fun Logger?.tryLog( tag: String, level: LogLevel, messageInitializer: MessageInitializer, @@ -84,7 +85,7 @@ private inline fun LogBuffer?.tryLog( ) { if (this != null) { // Wrap messagePrinter to convert it from crossinline to noinline - this.log(tag, level, messageInitializer, messagePrinter, ex) + this.log(level, messagePrinter, ex, messageInitializer) } else { messageInitializer(TMP_MESSAGE) val msg = messagePrinter(TMP_MESSAGE) @@ -110,7 +111,7 @@ open class ClockRegistry( val handleAllUsers: Boolean, defaultClockProvider: ClockProvider, val fallbackClockId: ClockId = DEFAULT_CLOCK_ID, - val logBuffer: LogBuffer? = null, + messageBuffer: MessageBuffer? = null, val keepAllLoaded: Boolean, subTag: String, var isTransitClockEnabled: Boolean = false, @@ -124,6 +125,7 @@ open class ClockRegistry( fun onAvailableClocksChanged() {} } + private val logger: Logger? = if (messageBuffer != null) Logger(messageBuffer, TAG) else null private val availableClocks = ConcurrentHashMap<ClockId, ClockInfo>() private val clockChangeListeners = mutableListOf<ClockChangeListener>() private val settingObserver = @@ -150,7 +152,7 @@ open class ClockRegistry( val knownClocks = KNOWN_PLUGINS.get(manager.getPackage()) if (knownClocks == null) { - logBuffer.tryLog( + logger.tryLog( TAG, LogLevel.WARNING, { str1 = manager.getPackage() }, @@ -159,7 +161,7 @@ open class ClockRegistry( return true } - logBuffer.tryLog( + logger.tryLog( TAG, LogLevel.INFO, { str1 = manager.getPackage() }, @@ -176,7 +178,7 @@ open class ClockRegistry( } if (manager != info.manager) { - logBuffer.tryLog( + logger.tryLog( TAG, LogLevel.ERROR, { str1 = id }, @@ -216,7 +218,7 @@ open class ClockRegistry( } if (manager != info.manager) { - logBuffer.tryLog( + logger.tryLog( TAG, LogLevel.ERROR, { str1 = id }, @@ -244,7 +246,7 @@ open class ClockRegistry( val id = clock.clockId val info = availableClocks[id] if (info?.manager != manager) { - logBuffer.tryLog( + logger.tryLog( TAG, LogLevel.ERROR, { str1 = id }, @@ -319,7 +321,7 @@ open class ClockRegistry( ClockSettings.deserialize(json) } catch (ex: Exception) { - logBuffer.tryLog(TAG, LogLevel.ERROR, {}, { "Failed to parse clock settings" }, ex) + logger.tryLog(TAG, LogLevel.ERROR, {}, { "Failed to parse clock settings" }, ex) null } settings = result @@ -348,7 +350,7 @@ open class ClockRegistry( ) } } catch (ex: Exception) { - logBuffer.tryLog(TAG, LogLevel.ERROR, {}, { "Failed to set clock settings" }, ex) + logger.tryLog(TAG, LogLevel.ERROR, {}, { "Failed to set clock settings" }, ex) } settings = value } @@ -508,9 +510,9 @@ open class ClockRegistry( } private fun onConnected(clockId: ClockId) { - logBuffer.tryLog(TAG, LogLevel.DEBUG, { str1 = clockId }, { "Connected $str1" }) + logger.tryLog(TAG, LogLevel.DEBUG, { str1 = clockId }, { "Connected $str1" }) if (currentClockId == clockId) { - logBuffer.tryLog( + logger.tryLog( TAG, LogLevel.INFO, { str1 = clockId }, @@ -520,10 +522,10 @@ open class ClockRegistry( } private fun onLoaded(clockId: ClockId) { - logBuffer.tryLog(TAG, LogLevel.DEBUG, { str1 = clockId }, { "Loaded $str1" }) + logger.tryLog(TAG, LogLevel.DEBUG, { str1 = clockId }, { "Loaded $str1" }) if (currentClockId == clockId) { - logBuffer.tryLog( + logger.tryLog( TAG, LogLevel.INFO, { str1 = clockId }, @@ -534,10 +536,10 @@ open class ClockRegistry( } private fun onUnloaded(clockId: ClockId) { - logBuffer.tryLog(TAG, LogLevel.DEBUG, { str1 = clockId }, { "Unloaded $str1" }) + logger.tryLog(TAG, LogLevel.DEBUG, { str1 = clockId }, { "Unloaded $str1" }) if (currentClockId == clockId) { - logBuffer.tryLog( + logger.tryLog( TAG, LogLevel.WARNING, { str1 = clockId }, @@ -548,10 +550,10 @@ open class ClockRegistry( } private fun onDisconnected(clockId: ClockId) { - logBuffer.tryLog(TAG, LogLevel.DEBUG, { str1 = clockId }, { "Disconnected $str1" }) + logger.tryLog(TAG, LogLevel.DEBUG, { str1 = clockId }, { "Disconnected $str1" }) if (currentClockId == clockId) { - logBuffer.tryLog( + logger.tryLog( TAG, LogLevel.WARNING, { str1 = clockId }, @@ -597,22 +599,17 @@ open class ClockRegistry( if (isEnabled && clockId.isNotEmpty()) { val clock = createClock(clockId) if (clock != null) { - logBuffer.tryLog( - TAG, - LogLevel.INFO, - { str1 = clockId }, - { "Rendering clock $str1" } - ) + logger.tryLog(TAG, LogLevel.INFO, { str1 = clockId }, { "Rendering clock $str1" }) return clock } else if (availableClocks.containsKey(clockId)) { - logBuffer.tryLog( + logger.tryLog( TAG, LogLevel.WARNING, { str1 = clockId }, { "Clock $str1 not loaded; using default" } ) } else { - logBuffer.tryLog( + logger.tryLog( TAG, LogLevel.ERROR, { str1 = clockId }, diff --git a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/DefaultClockController.kt b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/DefaultClockController.kt index e557c8e5902a..e539c955a3c6 100644 --- a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/DefaultClockController.kt +++ b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/DefaultClockController.kt @@ -24,7 +24,7 @@ import android.view.View import android.widget.FrameLayout import androidx.annotation.VisibleForTesting import com.android.systemui.customization.R -import com.android.systemui.log.LogBuffer +import com.android.systemui.log.core.MessageBuffer import com.android.systemui.plugins.ClockAnimations import com.android.systemui.plugins.ClockConfig import com.android.systemui.plugins.ClockController @@ -108,10 +108,10 @@ class DefaultClockController( override val config = ClockFaceConfig() - override var logBuffer: LogBuffer? - get() = view.logBuffer + override var messageBuffer: MessageBuffer? + get() = view.messageBuffer set(value) { - view.logBuffer = value + view.messageBuffer = value } override var animations: DefaultClockAnimations = DefaultClockAnimations(view, 0f, 0f) diff --git a/packages/SystemUI/plugin/src/com/android/systemui/plugins/ClockProviderPlugin.kt b/packages/SystemUI/plugin/src/com/android/systemui/plugins/ClockProviderPlugin.kt index 537b7a41a898..d962732ba884 100644 --- a/packages/SystemUI/plugin/src/com/android/systemui/plugins/ClockProviderPlugin.kt +++ b/packages/SystemUI/plugin/src/com/android/systemui/plugins/ClockProviderPlugin.kt @@ -18,7 +18,7 @@ import android.graphics.Rect import android.graphics.drawable.Drawable import android.view.View import com.android.internal.annotations.Keep -import com.android.systemui.log.LogBuffer +import com.android.systemui.log.core.MessageBuffer import com.android.systemui.plugins.annotations.ProvidesInterface import java.io.PrintWriter import java.util.Locale @@ -95,7 +95,7 @@ interface ClockFaceController { val animations: ClockAnimations /** Some clocks may log debug information */ - var logBuffer: LogBuffer? + var messageBuffer: MessageBuffer? } /** Events that should call when various rendering parameters change */ diff --git a/packages/SystemUI/proguard_common.flags b/packages/SystemUI/proguard_common.flags index 02d55104c288..319481523cdf 100644 --- a/packages/SystemUI/proguard_common.flags +++ b/packages/SystemUI/proguard_common.flags @@ -62,15 +62,12 @@ -keep class ** extends androidx.preference.PreferenceFragment -keep class com.android.systemui.tuner.* -# The plugins, log & common subpackages act as shared libraries that might be referenced in +# The plugins and core log subpackages act as shared libraries that might be referenced in # dynamically-loaded plugin APKs. -keep class com.android.systemui.plugins.** { *; } --keep class com.android.systemui.log.** { - *; -} --keep class com.android.systemui.common.** { +-keep class com.android.systemui.log.core.** { *; } -keep class com.android.systemui.fragments.FragmentService$FragmentCreator { diff --git a/packages/SystemUI/src/com/android/keyguard/ClockEventController.kt b/packages/SystemUI/src/com/android/keyguard/ClockEventController.kt index 91937af6f540..4e0e8d0dbcd7 100644 --- a/packages/SystemUI/src/com/android/keyguard/ClockEventController.kt +++ b/packages/SystemUI/src/com/android/keyguard/ClockEventController.kt @@ -91,9 +91,9 @@ constructor( field = value if (value != null) { smallLogBuffer?.log(TAG, DEBUG, {}, { "New Clock" }) - value.smallClock.logBuffer = smallLogBuffer + value.smallClock.messageBuffer = smallLogBuffer largeLogBuffer?.log(TAG, DEBUG, {}, { "New Clock" }) - value.largeClock.logBuffer = largeLogBuffer + value.largeClock.messageBuffer = largeLogBuffer value.initialize(resources, dozeAmount, 0f) diff --git a/packages/SystemUI/src/com/android/keyguard/LockIconView.java b/packages/SystemUI/src/com/android/keyguard/LockIconView.java index d1fffaa926ea..76b073ef73af 100644 --- a/packages/SystemUI/src/com/android/keyguard/LockIconView.java +++ b/packages/SystemUI/src/com/android/keyguard/LockIconView.java @@ -32,7 +32,6 @@ import android.widget.ImageView; import androidx.annotation.IntDef; import androidx.annotation.NonNull; -import androidx.annotation.VisibleForTesting; import com.android.internal.graphics.ColorUtils; import com.android.settingslib.Utils; @@ -126,7 +125,6 @@ public class LockIconView extends FrameLayout implements Dumpable { /** * Set the location of the lock icon. */ - @VisibleForTesting public void setCenterLocation(@NonNull Point center, float radius, int drawablePadding) { mLockIconCenter = center; mRadius = radius; diff --git a/packages/SystemUI/src/com/android/keyguard/LockIconViewController.java b/packages/SystemUI/src/com/android/keyguard/LockIconViewController.java index 239a0cc01c45..e255f5c2ded6 100644 --- a/packages/SystemUI/src/com/android/keyguard/LockIconViewController.java +++ b/packages/SystemUI/src/com/android/keyguard/LockIconViewController.java @@ -60,6 +60,7 @@ import com.android.systemui.dagger.SysUISingleton; import com.android.systemui.dagger.qualifiers.Main; import com.android.systemui.dump.DumpManager; import com.android.systemui.flags.FeatureFlags; +import com.android.systemui.flags.Flags; import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor; import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor; import com.android.systemui.keyguard.shared.model.TransitionStep; @@ -387,15 +388,17 @@ public class LockIconViewController extends ViewController<LockIconView> impleme private void updateLockIconLocation() { final float scaleFactor = mAuthController.getScaleFactor(); final int scaledPadding = (int) (mDefaultPaddingPx * scaleFactor); - if (mUdfpsSupported) { - mView.setCenterLocation(mAuthController.getUdfpsLocation(), - mAuthController.getUdfpsRadius(), scaledPadding); - } else { - mView.setCenterLocation( - new Point((int) mWidthPixels / 2, - (int) (mHeightPixels - - ((mBottomPaddingPx + sLockIconRadiusPx) * scaleFactor))), + if (!mFeatureFlags.isEnabled(Flags.MIGRATE_LOCK_ICON)) { + if (mUdfpsSupported) { + mView.setCenterLocation(mAuthController.getUdfpsLocation(), + mAuthController.getUdfpsRadius(), scaledPadding); + } else { + mView.setCenterLocation( + new Point((int) mWidthPixels / 2, + (int) (mHeightPixels + - ((mBottomPaddingPx + sLockIconRadiusPx) * scaleFactor))), sLockIconRadiusPx * scaleFactor, scaledPadding); + } } } diff --git a/packages/SystemUI/src/com/android/systemui/dagger/SystemUICoreStartableModule.kt b/packages/SystemUI/src/com/android/systemui/dagger/SystemUICoreStartableModule.kt index 76002d3f9693..40db63d609ba 100644 --- a/packages/SystemUI/src/com/android/systemui/dagger/SystemUICoreStartableModule.kt +++ b/packages/SystemUI/src/com/android/systemui/dagger/SystemUICoreStartableModule.kt @@ -49,6 +49,7 @@ import com.android.systemui.settings.dagger.MultiUserUtilsModule import com.android.systemui.shortcut.ShortcutKeyDispatcher import com.android.systemui.statusbar.notification.InstantAppNotifier import com.android.systemui.statusbar.phone.KeyguardLiftController +import com.android.systemui.statusbar.phone.LockscreenWallpaper import com.android.systemui.stylus.StylusUsiPowerStartable import com.android.systemui.temporarydisplay.chipbar.ChipbarCoordinator import com.android.systemui.theme.ThemeOverlayController @@ -301,4 +302,9 @@ abstract class SystemUICoreStartableModule { @IntoMap @ClassKey(KeyguardViewConfigurator::class) abstract fun bindKeyguardViewConfigurator(impl: KeyguardViewConfigurator): CoreStartable + + @Binds + @IntoMap + @ClassKey(LockscreenWallpaper::class) + abstract fun bindLockscreenWallpaper(impl: LockscreenWallpaper): CoreStartable } diff --git a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java index f229ffe9d0dc..06769dc3f831 100644 --- a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java +++ b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java @@ -53,6 +53,7 @@ import com.android.systemui.dump.DumpManager; import com.android.systemui.flags.FeatureFlags; import com.android.systemui.flags.FlagsModule; import com.android.systemui.keyboard.KeyboardModule; +import com.android.systemui.keyguard.ui.view.layout.LockscreenLayoutModule; import com.android.systemui.log.dagger.LogModule; import com.android.systemui.log.dagger.MonitorLog; import com.android.systemui.log.table.TableLogBuffer; @@ -177,6 +178,7 @@ import javax.inject.Named; GarbageMonitorModule.class, KeyboardModule.class, LetterboxModule.class, + LockscreenLayoutModule.class, LogModule.class, MediaProjectionModule.class, MotionToolModule.class, diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewConfigurator.kt b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewConfigurator.kt index df83aafecb4b..f59ad90d86ff 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewConfigurator.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewConfigurator.kt @@ -26,6 +26,8 @@ import com.android.systemui.flags.FeatureFlags import com.android.systemui.flags.Flags import com.android.systemui.keyguard.ui.binder.KeyguardIndicationAreaBinder import com.android.systemui.keyguard.ui.view.KeyguardRootView +import com.android.systemui.keyguard.ui.view.layout.KeyguardLayoutManager +import com.android.systemui.keyguard.ui.view.layout.KeyguardLayoutManagerCommandListener import com.android.systemui.keyguard.ui.viewmodel.KeyguardIndicationAreaViewModel import com.android.systemui.shade.NotificationShadeWindowView import com.android.systemui.statusbar.KeyguardIndicationController @@ -42,6 +44,8 @@ constructor( private val notificationShadeWindowView: NotificationShadeWindowView, private val featureFlags: FeatureFlags, private val indicationController: KeyguardIndicationController, + private val keyguardLayoutManager: KeyguardLayoutManager, + private val keyguardLayoutManagerCommandListener: KeyguardLayoutManagerCommandListener, ) : CoreStartable { private var indicationAreaHandle: DisposableHandle? = null @@ -51,6 +55,8 @@ constructor( notificationShadeWindowView.requireViewById(R.id.notification_panel) as ViewGroup bindIndicationArea(notificationPanel) bindLockIconView(notificationPanel) + keyguardLayoutManager.layoutViews() + keyguardLayoutManagerCommandListener.start() } fun bindIndicationArea(legacyParent: ViewGroup) { diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromLockscreenTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromLockscreenTransitionInteractor.kt index b7963340228b..ed1bf3ef2753 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromLockscreenTransitionInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromLockscreenTransitionInteractor.kt @@ -167,9 +167,10 @@ constructor( from = KeyguardState.PRIMARY_BOUNCER, to = KeyguardState.LOCKSCREEN, animator = - getDefaultAnimatorForTransitionsToState(KeyguardState.LOCKSCREEN).apply { - duration = 0 - } + getDefaultAnimatorForTransitionsToState( + KeyguardState.LOCKSCREEN + ) + .apply { duration = 0 } ) ) } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/TransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/TransitionInteractor.kt index ae6fc9e6e6dc..0dda625a8b41 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/TransitionInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/TransitionInteractor.kt @@ -44,9 +44,9 @@ sealed class TransitionInteractor( abstract fun start() fun startTransitionTo( - toState: KeyguardState, - animator: ValueAnimator? = getDefaultAnimatorForTransitionsToState(toState), - resetIfCancelled: Boolean = false + toState: KeyguardState, + animator: ValueAnimator? = getDefaultAnimatorForTransitionsToState(toState), + resetIfCancelled: Boolean = false ): UUID? { if ( fromState != transitionInteractor.startedKeyguardState.value && diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/KeyguardRootView.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/KeyguardRootView.kt index a62f383ed704..0077f2d68c7e 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/KeyguardRootView.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/KeyguardRootView.kt @@ -19,10 +19,7 @@ package com.android.systemui.keyguard.ui.view import android.content.Context import android.util.AttributeSet -import android.view.Gravity -import android.view.ViewGroup.LayoutParams.MATCH_PARENT -import android.view.ViewGroup.LayoutParams.WRAP_CONTENT -import android.widget.FrameLayout +import androidx.constraintlayout.widget.ConstraintLayout import com.android.keyguard.LockIconView import com.android.systemui.R @@ -31,7 +28,7 @@ class KeyguardRootView( context: Context, private val attrs: AttributeSet?, ) : - FrameLayout( + ConstraintLayout( context, attrs, ) { @@ -43,31 +40,11 @@ class KeyguardRootView( private fun addIndicationTextArea() { val view = KeyguardIndicationArea(context, attrs) - addView( - view, - FrameLayout.LayoutParams( - MATCH_PARENT, - WRAP_CONTENT, - ) - .apply { - gravity = Gravity.BOTTOM or Gravity.CENTER_HORIZONTAL - bottomMargin = R.dimen.keyguard_indication_margin_bottom.dp() - } - ) + addView(view) } private fun addLockIconView() { val view = LockIconView(context, attrs).apply { id = R.id.lock_icon_view } - addView( - view, - LayoutParams( - WRAP_CONTENT, - WRAP_CONTENT, - ) - ) - } - - private fun Int.dp(): Int { - return context.resources.getDimensionPixelSize(this) + addView(view) } } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/DefaultLockscreenLayout.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/DefaultLockscreenLayout.kt new file mode 100644 index 000000000000..baaeb60f364e --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/DefaultLockscreenLayout.kt @@ -0,0 +1,152 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package com.android.systemui.keyguard.ui.view.layout + +import android.content.Context +import android.graphics.Point +import android.graphics.Rect +import android.util.DisplayMetrics +import android.view.View +import android.view.ViewGroup.LayoutParams.MATCH_PARENT +import android.view.ViewGroup.LayoutParams.WRAP_CONTENT +import android.view.WindowManager +import androidx.annotation.VisibleForTesting +import androidx.constraintlayout.widget.ConstraintLayout +import androidx.constraintlayout.widget.ConstraintSet +import androidx.constraintlayout.widget.ConstraintSet.BOTTOM +import androidx.constraintlayout.widget.ConstraintSet.END +import androidx.constraintlayout.widget.ConstraintSet.PARENT_ID +import androidx.constraintlayout.widget.ConstraintSet.START +import androidx.constraintlayout.widget.ConstraintSet.TOP +import com.android.keyguard.KeyguardUpdateMonitor +import com.android.systemui.R +import com.android.systemui.biometrics.AuthController +import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.keyguard.ui.view.KeyguardRootView +import javax.inject.Inject + +/** + * Positions elements of the lockscreen to the default position. + * + * This will be the most common use case for phones in portrait mode. + */ +@SysUISingleton +class DefaultLockscreenLayout +@Inject +constructor( + private val authController: AuthController, + private val keyguardUpdateMonitor: KeyguardUpdateMonitor, + private val windowManager: WindowManager, + private val context: Context, +) : LockscreenLayout { + override val id: String = DEFAULT + + override fun layoutIndicationArea(rootView: KeyguardRootView) { + val indicationArea = rootView.findViewById<View>(R.id.keyguard_indication_area) ?: return + + rootView.getConstraintSet().apply { + constrainWidth(indicationArea.id, MATCH_PARENT) + constrainHeight(indicationArea.id, WRAP_CONTENT) + connect( + indicationArea.id, + BOTTOM, + PARENT_ID, + BOTTOM, + R.dimen.keyguard_indication_margin_bottom.dp() + ) + connect(indicationArea.id, START, PARENT_ID, START) + connect(indicationArea.id, END, PARENT_ID, END) + applyTo(rootView) + } + } + + override fun layoutLockIcon(rootView: KeyguardRootView) { + val isUdfpsSupported = keyguardUpdateMonitor.isUdfpsSupported + val scaleFactor: Float = authController.scaleFactor + val mBottomPaddingPx = R.dimen.lock_icon_margin_bottom.dp() + val mDefaultPaddingPx = R.dimen.lock_icon_padding.dp() + val scaledPadding: Int = (mDefaultPaddingPx * scaleFactor).toInt() + val bounds = windowManager.currentWindowMetrics.bounds + val widthPixels = bounds.right.toFloat() + val heightPixels = bounds.bottom.toFloat() + val defaultDensity = + DisplayMetrics.DENSITY_DEVICE_STABLE.toFloat() / + DisplayMetrics.DENSITY_DEFAULT.toFloat() + val lockIconRadiusPx = (defaultDensity * 36).toInt() + + if (isUdfpsSupported) { + authController.udfpsLocation?.let { udfpsLocation -> + centerLockIcon(udfpsLocation, authController.udfpsRadius, scaledPadding, rootView) + } + } else { + centerLockIcon( + Point( + (widthPixels / 2).toInt(), + (heightPixels - ((mBottomPaddingPx + lockIconRadiusPx) * scaleFactor)).toInt() + ), + lockIconRadiusPx * scaleFactor, + scaledPadding, + rootView + ) + } + } + + @VisibleForTesting + internal fun centerLockIcon( + center: Point, + radius: Float, + drawablePadding: Int, + rootView: KeyguardRootView, + ) { + val lockIconView = rootView.findViewById<View>(R.id.lock_icon_view) ?: return + val lockIcon = lockIconView.findViewById<View>(R.id.lock_icon) ?: return + lockIcon.setPadding(drawablePadding, drawablePadding, drawablePadding, drawablePadding) + + val sensorRect = + Rect().apply { + set( + center.x - radius.toInt(), + center.y - radius.toInt(), + center.x + radius.toInt(), + center.y + radius.toInt(), + ) + } + + rootView.getConstraintSet().apply { + constrainWidth(lockIconView.id, sensorRect.right - sensorRect.left) + constrainHeight(lockIconView.id, sensorRect.bottom - sensorRect.top) + connect(lockIconView.id, TOP, PARENT_ID, TOP, sensorRect.top) + connect(lockIconView.id, START, PARENT_ID, START, sensorRect.left) + applyTo(rootView) + } + } + + private fun Int.dp(): Int { + return context.resources.getDimensionPixelSize(this) + } + + private fun ConstraintLayout.getConstraintSet(): ConstraintSet { + val cs = ConstraintSet() + cs.clone(this) + return cs + } + + companion object { + const val DEFAULT = "default" + } +} diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/KeyguardLayoutManager.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/KeyguardLayoutManager.kt new file mode 100644 index 000000000000..9bc630265a3f --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/KeyguardLayoutManager.kt @@ -0,0 +1,91 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package com.android.systemui.keyguard.ui.view.layout + +import android.content.res.Configuration +import androidx.constraintlayout.widget.ConstraintSet +import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.keyguard.ui.view.KeyguardRootView +import com.android.systemui.keyguard.ui.view.layout.DefaultLockscreenLayout.Companion.DEFAULT +import com.android.systemui.statusbar.policy.ConfigurationController +import javax.inject.Inject + +/** + * Manages layout changes for the lockscreen. + * + * To add a layout, add an entry to the map with a unique id and call #transitionToLayout(string). + */ +@SysUISingleton +class KeyguardLayoutManager +@Inject +constructor( + configurationController: ConfigurationController, + layouts: Set<@JvmSuppressWildcards LockscreenLayout>, + private val keyguardRootView: KeyguardRootView, +) { + internal val layoutIdMap: Map<String, LockscreenLayout> = layouts.associateBy { it.id } + private var layout: LockscreenLayout? = layoutIdMap[DEFAULT] + + init { + configurationController.addCallback( + object : ConfigurationController.ConfigurationListener { + override fun onConfigChanged(newConfig: Configuration?) { + layoutViews() + } + } + ) + } + + /** + * Transitions to a layout. + * + * @param layoutId + * @return whether the transition has succeeded. + */ + fun transitionToLayout(layoutId: String): Boolean { + layout = layoutIdMap[layoutId] ?: return false + layoutViews() + return true + } + + fun layoutViews() { + layout?.layoutViews(keyguardRootView) + } + + companion object { + const val TAG = "KeyguardLayoutManager" + } +} + +interface LockscreenLayout { + val id: String + + fun layoutViews(rootView: KeyguardRootView) { + // Clear constraints. + ConstraintSet() + .apply { + clone(rootView) + knownIds.forEach { getConstraint(it).layout.copyFrom(ConstraintSet.Layout()) } + } + .applyTo(rootView) + layoutIndicationArea(rootView) + layoutLockIcon(rootView) + } + fun layoutIndicationArea(rootView: KeyguardRootView) + fun layoutLockIcon(rootView: KeyguardRootView) +} diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/KeyguardLayoutManagerCommandListener.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/KeyguardLayoutManagerCommandListener.kt new file mode 100644 index 000000000000..b351ea8923f6 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/KeyguardLayoutManagerCommandListener.kt @@ -0,0 +1,63 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.keyguard.ui.view.layout + +import com.android.systemui.statusbar.commandline.Command +import com.android.systemui.statusbar.commandline.CommandRegistry +import java.io.PrintWriter +import javax.inject.Inject + +/** Uses $ adb shell cmd statusbar layout <LayoutId> */ +class KeyguardLayoutManagerCommandListener +@Inject +constructor( + private val commandRegistry: CommandRegistry, + private val keyguardLayoutManager: KeyguardLayoutManager +) { + private val layoutCommand = KeyguardLayoutManagerCommand() + + fun start() { + commandRegistry.registerCommand(COMMAND) { layoutCommand } + } + + internal inner class KeyguardLayoutManagerCommand : Command { + override fun execute(pw: PrintWriter, args: List<String>) { + val arg = args.getOrNull(0) + if (arg == null || arg.lowercase() == "help") { + help(pw) + return + } + + if (keyguardLayoutManager.transitionToLayout(arg)) { + pw.println("Transition succeeded!") + } else { + pw.println("Invalid argument! To see available layout ids, run:") + pw.println("$ adb shell cmd statusbar layout help") + } + } + + override fun help(pw: PrintWriter) { + pw.println("Usage: $ adb shell cmd statusbar layout <layoutId>") + pw.println("Existing Layout Ids: ") + keyguardLayoutManager.layoutIdMap.forEach { entry -> pw.println("${entry.key}") } + } + } + + companion object { + internal const val COMMAND = "layout" + } +} diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/LockscreenLayoutModule.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/LockscreenLayoutModule.kt new file mode 100644 index 000000000000..00f93e33f370 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/LockscreenLayoutModule.kt @@ -0,0 +1,31 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package com.android.systemui.keyguard.ui.view.layout + +import dagger.Binds +import dagger.Module +import dagger.multibindings.IntoSet + +@Module +abstract class LockscreenLayoutModule { + @Binds + @IntoSet + abstract fun bindDefaultLayout( + defaultLockscreenLayout: DefaultLockscreenLayout + ): LockscreenLayout +} diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/KeyguardMediaController.kt b/packages/SystemUI/src/com/android/systemui/media/controls/ui/KeyguardMediaController.kt index 30ee147e302a..2883210805d3 100644 --- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/KeyguardMediaController.kt +++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/KeyguardMediaController.kt @@ -128,6 +128,15 @@ constructor( var visibilityChangedListener: ((Boolean) -> Unit)? = null + /** + * Whether the doze wake up animation is delayed and we are currently waiting for it to start. + */ + var isDozeWakeUpAnimationWaiting: Boolean = false + set(value) { + field = value + refreshMediaPosition() + } + /** single pane media container placed at the top of the notifications list */ var singlePaneContainer: MediaContainerView? = null private set @@ -221,7 +230,13 @@ constructor( // by the clock. This is not the case for single-line clock though. // For single shade, we don't need to do it, because media is a child of NSSL, which already // gets hidden on AOD. - return !statusBarStateController.isDozing + // Media also has to be hidden when waking up from dozing, and the doze wake up animation is + // delayed and waiting to be started. + // This is to stay in sync with the delaying of the horizontal alignment of the rest of the + // keyguard container, that is also delayed until the "wait" is over. + // If we show media during this waiting period, the shade will still be centered, and using + // the entire width of the screen, and making media show fully stretched. + return !statusBarStateController.isDozing && !isDozeWakeUpAnimationWaiting } private fun showMediaPlayer() { diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java index 85550468b114..d97db3b27c87 100644 --- a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java +++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java @@ -1627,6 +1627,7 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump mWillPlayDelayedDozeAmountAnimation = willPlay; mWakeUpCoordinator.logDelayingClockWakeUpAnimation(willPlay); + mKeyguardMediaController.setDozeWakeUpAnimationWaiting(willPlay); // Once changing this value, see if we should move the clock. positionClockAndNotifications(); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java index 39b13d95345f..0d3dfaeb85b8 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java @@ -2177,9 +2177,6 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces { */ @Override public void setLockscreenUser(int newUserId) { - if (mLockscreenWallpaper != null && !mWallpaperManager.isLockscreenLiveWallpaperEnabled()) { - mLockscreenWallpaper.setCurrentUser(newUserId); - } if (mWallpaperSupported) { mWallpaperChangedReceiver.onReceive(mContext, null); } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LockscreenWallpaper.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LockscreenWallpaper.java index c07b5e062d70..b2c39f7e289f 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LockscreenWallpaper.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LockscreenWallpaper.java @@ -40,12 +40,17 @@ import androidx.annotation.NonNull; import com.android.internal.util.IndentingPrintWriter; import com.android.keyguard.KeyguardUpdateMonitor; +import com.android.systemui.CoreStartable; import com.android.systemui.Dumpable; import com.android.systemui.dagger.SysUISingleton; import com.android.systemui.dagger.qualifiers.Main; import com.android.systemui.dump.DumpManager; import com.android.systemui.settings.UserTracker; import com.android.systemui.statusbar.NotificationMediaManager; +import com.android.systemui.user.data.model.SelectedUserModel; +import com.android.systemui.user.data.model.SelectionStatus; +import com.android.systemui.user.data.repository.UserRepository; +import com.android.systemui.util.kotlin.JavaAdapter; import libcore.io.IoUtils; @@ -59,7 +64,7 @@ import javax.inject.Inject; */ @SysUISingleton public class LockscreenWallpaper extends IWallpaperManagerCallback.Stub implements Runnable, - Dumpable { + Dumpable, CoreStartable { private static final String TAG = "LockscreenWallpaper"; @@ -72,6 +77,8 @@ public class LockscreenWallpaper extends IWallpaperManagerCallback.Stub implemen private final WallpaperManager mWallpaperManager; private final KeyguardUpdateMonitor mUpdateMonitor; private final Handler mH; + private final JavaAdapter mJavaAdapter; + private final UserRepository mUserRepository; private boolean mCached; private Bitmap mCache; @@ -88,6 +95,8 @@ public class LockscreenWallpaper extends IWallpaperManagerCallback.Stub implemen DumpManager dumpManager, NotificationMediaManager mediaManager, @Main Handler mainHandler, + JavaAdapter javaAdapter, + UserRepository userRepository, UserTracker userTracker) { dumpManager.registerDumpable(getClass().getSimpleName(), this); mWallpaperManager = wallpaperManager; @@ -95,6 +104,8 @@ public class LockscreenWallpaper extends IWallpaperManagerCallback.Stub implemen mUpdateMonitor = keyguardUpdateMonitor; mMediaManager = mediaManager; mH = mainHandler; + mJavaAdapter = javaAdapter; + mUserRepository = userRepository; if (iWallpaperManager != null && !mWallpaperManager.isLockscreenLiveWallpaperEnabled()) { // Service is disabled on some devices like Automotive @@ -106,6 +117,14 @@ public class LockscreenWallpaper extends IWallpaperManagerCallback.Stub implemen } } + @Override + public void start() { + if (!isLockscreenLiveWallpaperEnabled()) { + mJavaAdapter.alwaysCollectFlow( + mUserRepository.getSelectedUser(), this::setSelectedUser); + } + } + public Bitmap getBitmap() { assertLockscreenLiveWallpaperNotEnabled(); @@ -169,9 +188,15 @@ public class LockscreenWallpaper extends IWallpaperManagerCallback.Stub implemen } } - public void setCurrentUser(int user) { + private void setSelectedUser(SelectedUserModel selectedUserModel) { assertLockscreenLiveWallpaperNotEnabled(); + if (selectedUserModel.getSelectionStatus().equals(SelectionStatus.SELECTION_IN_PROGRESS)) { + // Wait until the selection has finished before updating. + return; + } + + int user = selectedUserModel.getUserInfo().id; if (user != mCurrentUserId) { if (mSelectedUser == null || user != mSelectedUser.getIdentifier()) { mCached = false; diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/demo/DemoWifiRepository.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/demo/DemoWifiRepository.kt index a4fbc2c93647..a57be665f105 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/demo/DemoWifiRepository.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/demo/DemoWifiRepository.kt @@ -96,7 +96,7 @@ constructor( networkId = DEMO_NET_ID, isValidated = validated ?: true, level = level ?: 0, - ssid = ssid, + ssid = ssid ?: DEMO_NET_SSID, // These fields below aren't supported in demo mode, since they aren't needed to satisfy // the interface. @@ -115,5 +115,6 @@ constructor( companion object { private const val DEMO_NET_ID = 1234 + private const val DEMO_NET_SSID = "Demo SSID" } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/window/StatusBarWindowController.java b/packages/SystemUI/src/com/android/systemui/statusbar/window/StatusBarWindowController.java index bcf3b0cbfc86..4a9921eb2d83 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/window/StatusBarWindowController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/window/StatusBarWindowController.java @@ -240,8 +240,10 @@ public class StatusBarWindowController { Insets.of(0, safeTouchRegionHeight, 0, 0)); } lp.providedInsets = new InsetsFrameProvider[] { - new InsetsFrameProvider(mInsetsSourceOwner, 0, statusBars()), - new InsetsFrameProvider(mInsetsSourceOwner, 0, tappableElement()), + new InsetsFrameProvider(mInsetsSourceOwner, 0, statusBars()) + .setInsetsSize(Insets.of(0, height, 0, 0)), + new InsetsFrameProvider(mInsetsSourceOwner, 0, tappableElement()) + .setInsetsSize(Insets.of(0, height, 0, 0)), gestureInsetsProvider }; return lp; diff --git a/packages/SystemUI/src/com/android/systemui/user/data/model/SelectedUserModel.kt b/packages/SystemUI/src/com/android/systemui/user/data/model/SelectedUserModel.kt new file mode 100644 index 000000000000..cefd466374c0 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/user/data/model/SelectedUserModel.kt @@ -0,0 +1,35 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.user.data.model + +import android.content.pm.UserInfo + +/** A model for the currently selected user. */ +data class SelectedUserModel( + /** Information about the user. */ + val userInfo: UserInfo, + /** The current status of the selection. */ + val selectionStatus: SelectionStatus, +) + +/** The current status of the selection. */ +enum class SelectionStatus { + /** This user has started being selected but the selection hasn't completed. */ + SELECTION_IN_PROGRESS, + /** The selection of this user has completed. */ + SELECTION_COMPLETE, +} diff --git a/packages/SystemUI/src/com/android/systemui/user/data/repository/UserRepository.kt b/packages/SystemUI/src/com/android/systemui/user/data/repository/UserRepository.kt index 3de75ca2ed87..954765c4581d 100644 --- a/packages/SystemUI/src/com/android/systemui/user/data/repository/UserRepository.kt +++ b/packages/SystemUI/src/com/android/systemui/user/data/repository/UserRepository.kt @@ -33,6 +33,8 @@ import com.android.systemui.dagger.qualifiers.Main import com.android.systemui.flags.FeatureFlags import com.android.systemui.flags.Flags.FACE_AUTH_REFACTOR import com.android.systemui.settings.UserTracker +import com.android.systemui.user.data.model.SelectedUserModel +import com.android.systemui.user.data.model.SelectionStatus import com.android.systemui.user.data.model.UserSwitcherSettingsModel import com.android.systemui.util.settings.GlobalSettings import com.android.systemui.util.settings.SettingsProxyExt.observerFlow @@ -69,6 +71,9 @@ interface UserRepository { /** List of all users on the device. */ val userInfos: Flow<List<UserInfo>> + /** Information about the currently-selected user, including [UserInfo] and other details. */ + val selectedUser: StateFlow<SelectedUserModel> + /** [UserInfo] of the currently-selected user. */ val selectedUserInfo: Flow<UserInfo> @@ -146,9 +151,6 @@ constructor( private val _userInfos = MutableStateFlow<List<UserInfo>?>(null) override val userInfos: Flow<List<UserInfo>> = _userInfos.filterNotNull() - private val _selectedUserInfo = MutableStateFlow<UserInfo?>(null) - override val selectedUserInfo: Flow<UserInfo> = _selectedUserInfo.filterNotNull() - override var mainUserId: Int = UserHandle.USER_NULL private set override var lastSelectedNonGuestUserId: Int = UserHandle.USER_NULL @@ -174,12 +176,57 @@ constructor( override var isRefreshUsersPaused: Boolean = false init { - observeSelectedUser() if (featureFlags.isEnabled(FACE_AUTH_REFACTOR)) { observeUserSwitching() } } + override val selectedUser: StateFlow<SelectedUserModel> = run { + // Some callbacks don't modify the selection status, so maintain the current value. + var currentSelectionStatus = SelectionStatus.SELECTION_COMPLETE + conflatedCallbackFlow { + fun send(selectionStatus: SelectionStatus) { + currentSelectionStatus = selectionStatus + trySendWithFailureLogging( + SelectedUserModel(tracker.userInfo, selectionStatus), + TAG, + ) + } + + val callback = + object : UserTracker.Callback { + override fun onUserChanging(newUser: Int, userContext: Context) { + send(SelectionStatus.SELECTION_IN_PROGRESS) + } + + override fun onUserChanged(newUser: Int, userContext: Context) { + send(SelectionStatus.SELECTION_COMPLETE) + } + + override fun onProfilesChanged(profiles: List<UserInfo>) { + send(currentSelectionStatus) + } + } + + tracker.addCallback(callback, mainDispatcher.asExecutor()) + send(currentSelectionStatus) + + awaitClose { tracker.removeCallback(callback) } + } + .onEach { + if (!it.userInfo.isGuest) { + lastSelectedNonGuestUserId = it.userInfo.id + } + } + .stateIn( + applicationScope, + SharingStarted.Eagerly, + initialValue = SelectedUserModel(tracker.userInfo, currentSelectionStatus) + ) + } + + override val selectedUserInfo: Flow<UserInfo> = selectedUser.map { it.userInfo } + override fun refreshUsers() { applicationScope.launch { val result = withContext(backgroundDispatcher) { manager.aliveUsers } @@ -201,7 +248,7 @@ constructor( } override fun getSelectedUserInfo(): UserInfo { - return checkNotNull(_selectedUserInfo.value) + return selectedUser.value.userInfo } override fun isSimpleUserSwitcher(): Boolean { @@ -234,38 +281,6 @@ constructor( .launchIn(applicationScope) } - private fun observeSelectedUser() { - conflatedCallbackFlow { - fun send() { - trySendWithFailureLogging(tracker.userInfo, TAG) - } - - val callback = - object : UserTracker.Callback { - override fun onUserChanging(newUser: Int, userContext: Context) { - send() - } - - override fun onProfilesChanged(profiles: List<UserInfo>) { - send() - } - } - - tracker.addCallback(callback, mainDispatcher.asExecutor()) - send() - - awaitClose { tracker.removeCallback(callback) } - } - .onEach { - if (!it.isGuest) { - lastSelectedNonGuestUserId = it.id - } - - _selectedUserInfo.value = it - } - .launchIn(applicationScope) - } - private suspend fun getSettings(): UserSwitcherSettingsModel { return withContext(backgroundDispatcher) { val isSimpleUserSwitcher = diff --git a/packages/SystemUI/tests/src/com/android/keyguard/ClockEventControllerTest.kt b/packages/SystemUI/tests/src/com/android/keyguard/ClockEventControllerTest.kt index 0dcd404d2fc5..231898884adf 100644 --- a/packages/SystemUI/tests/src/com/android/keyguard/ClockEventControllerTest.kt +++ b/packages/SystemUI/tests/src/com/android/keyguard/ClockEventControllerTest.kt @@ -21,21 +21,22 @@ import android.view.View import android.widget.TextView import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase +import com.android.systemui.bouncer.data.repository.FakeKeyguardBouncerRepository import com.android.systemui.broadcast.BroadcastDispatcher +import com.android.systemui.common.ui.data.repository.FakeConfigurationRepository import com.android.systemui.flags.FeatureFlags -import com.android.systemui.bouncer.data.repository.FakeKeyguardBouncerRepository import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository import com.android.systemui.keyguard.data.repository.KeyguardTransitionRepository import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor -import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor +import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractorFactory +import com.android.systemui.log.LogBuffer import com.android.systemui.plugins.ClockAnimations import com.android.systemui.plugins.ClockController import com.android.systemui.plugins.ClockEvents -import com.android.systemui.plugins.ClockFaceController import com.android.systemui.plugins.ClockFaceConfig +import com.android.systemui.plugins.ClockFaceController import com.android.systemui.plugins.ClockFaceEvents import com.android.systemui.plugins.ClockTickRate -import com.android.systemui.log.LogBuffer import com.android.systemui.statusbar.CommandQueue import com.android.systemui.statusbar.policy.BatteryController import com.android.systemui.statusbar.policy.ConfigurationController @@ -64,7 +65,6 @@ import org.mockito.Mockito.verify import org.mockito.junit.MockitoJUnit import java.util.TimeZone import java.util.concurrent.Executor -import com.android.systemui.common.ui.data.repository.FakeConfigurationRepository import org.mockito.Mockito.`when` as whenever @RunWith(AndroidTestingRunner::class) @@ -122,7 +122,9 @@ class ClockEventControllerTest : SysuiTestCase() { bouncerRepository = bouncerRepository, configurationRepository = FakeConfigurationRepository(), ), - KeyguardTransitionInteractor(transitionRepository, TestScope().backgroundScope), + KeyguardTransitionInteractorFactory.create( + scope = TestScope().backgroundScope, + ).keyguardTransitionInteractor, broadcastDispatcher, batteryController, keyguardUpdateMonitor, diff --git a/packages/SystemUI/tests/src/com/android/keyguard/LockIconViewControllerBaseTest.java b/packages/SystemUI/tests/src/com/android/keyguard/LockIconViewControllerBaseTest.java index c88c4d65f412..e7e1cc94b7c8 100644 --- a/packages/SystemUI/tests/src/com/android/keyguard/LockIconViewControllerBaseTest.java +++ b/packages/SystemUI/tests/src/com/android/keyguard/LockIconViewControllerBaseTest.java @@ -19,6 +19,7 @@ package com.android.keyguard; import static com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession; import static com.android.systemui.flags.Flags.DOZING_MIGRATION_1; import static com.android.systemui.flags.Flags.FACE_AUTH_REFACTOR; +import static com.android.systemui.flags.Flags.MIGRATE_LOCK_ICON; import static org.mockito.Mockito.any; import static org.mockito.Mockito.anyInt; @@ -143,6 +144,7 @@ public class LockIconViewControllerBaseTest extends SysuiTestCase { mFeatureFlags = new FakeFeatureFlags(); mFeatureFlags.set(FACE_AUTH_REFACTOR, false); + mFeatureFlags.set(MIGRATE_LOCK_ICON, false); mUnderTest = new LockIconViewController( mLockIconView, mStatusBarStateController, diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/domain/interactor/LogContextInteractorImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/domain/interactor/LogContextInteractorImplTest.kt index 263ce1a2e9f5..8f8004f1cbb8 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/domain/interactor/LogContextInteractorImplTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/domain/interactor/LogContextInteractorImplTest.kt @@ -7,7 +7,7 @@ import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.coroutines.collectLastValue import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository -import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor +import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractorFactory import com.android.systemui.keyguard.shared.model.KeyguardState import com.android.systemui.keyguard.shared.model.TransitionState import com.android.systemui.keyguard.shared.model.TransitionStep @@ -54,10 +54,11 @@ class LogContextInteractorImplTest : SysuiTestCase() { LogContextInteractorImpl( testScope.backgroundScope, foldProvider, - KeyguardTransitionInteractor( - keyguardTransitionRepository, - testScope.backgroundScope - ), + KeyguardTransitionInteractorFactory.create( + repository = keyguardTransitionRepository, + scope = testScope.backgroundScope, + ) + .keyguardTransitionInteractor, ) } diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ResourceTrimmerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ResourceTrimmerTest.kt index b4bd473b8b8c..925ac30b99fd 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ResourceTrimmerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ResourceTrimmerTest.kt @@ -10,7 +10,7 @@ import com.android.systemui.flags.Flags import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository import com.android.systemui.keyguard.domain.interactor.KeyguardInteractorFactory -import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor +import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractorFactory import com.android.systemui.keyguard.shared.model.KeyguardState import com.android.systemui.keyguard.shared.model.TransitionStep import com.android.systemui.keyguard.shared.model.WakeSleepReason @@ -67,10 +67,11 @@ class ResourceTrimmerTest : SysuiTestCase() { resourceTrimmer = ResourceTrimmer( keyguardInteractor, - KeyguardTransitionInteractor( - keyguardTransitionRepository, - testScope.backgroundScope - ), + KeyguardTransitionInteractorFactory.create( + scope = TestScope().backgroundScope, + repository = keyguardTransitionRepository, + ) + .keyguardTransitionInteractor, globalWindowManager, testScope.backgroundScope, testDispatcher, diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/DeviceEntryFaceAuthRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/DeviceEntryFaceAuthRepositoryTest.kt index e042564646e7..d62db5d1b890 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/DeviceEntryFaceAuthRepositoryTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/DeviceEntryFaceAuthRepositoryTest.kt @@ -49,7 +49,7 @@ import com.android.systemui.flags.FakeFeatureFlags import com.android.systemui.flags.Flags.FACE_AUTH_REFACTOR import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor import com.android.systemui.keyguard.domain.interactor.KeyguardInteractorFactory -import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor +import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractorFactory import com.android.systemui.keyguard.shared.model.AuthenticationStatus import com.android.systemui.keyguard.shared.model.DetectionStatus import com.android.systemui.keyguard.shared.model.ErrorAuthenticationStatus @@ -216,7 +216,11 @@ class DeviceEntryFaceAuthRepositoryTest : SysuiTestCase() { ) keyguardTransitionRepository = FakeKeyguardTransitionRepository() val keyguardTransitionInteractor = - KeyguardTransitionInteractor(keyguardTransitionRepository, testScope.backgroundScope) + KeyguardTransitionInteractorFactory.create( + scope = TestScope().backgroundScope, + repository = keyguardTransitionRepository, + ) + .keyguardTransitionInteractor return DeviceEntryFaceAuthRepositoryImpl( mContext, fmOverride, diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardFaceAuthInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardFaceAuthInteractorTest.kt index ee5c1cc31b0b..3e81cd336824 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardFaceAuthInteractorTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardFaceAuthInteractorTest.kt @@ -89,7 +89,11 @@ class KeyguardFaceAuthInteractorTest : SysuiTestCase() { faceAuthRepository = FakeDeviceEntryFaceAuthRepository() keyguardTransitionRepository = FakeKeyguardTransitionRepository() keyguardTransitionInteractor = - KeyguardTransitionInteractor(keyguardTransitionRepository, testScope.backgroundScope) + KeyguardTransitionInteractorFactory.create( + scope = TestScope().backgroundScope, + repository = keyguardTransitionRepository, + ) + .keyguardTransitionInteractor underTest = SystemUIKeyguardFaceAuthInteractor( diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardLongPressInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardLongPressInteractorTest.kt index e9c22f9d551d..0050d64d7505 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardLongPressInteractorTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardLongPressInteractorTest.kt @@ -292,10 +292,11 @@ class KeyguardLongPressInteractorTest : SysuiTestCase() { appContext = mContext, scope = testScope.backgroundScope, transitionInteractor = - KeyguardTransitionInteractor( - keyguardTransitionRepository, - testScope.backgroundScope - ), + KeyguardTransitionInteractorFactory.create( + scope = testScope.backgroundScope, + repository = keyguardTransitionRepository, + ) + .keyguardTransitionInteractor, repository = keyguardRepository, logger = logger, featureFlags = diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractorTest.kt index fa4941cbb895..9e9c25eafa33 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractorTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractorTest.kt @@ -17,9 +17,9 @@ package com.android.systemui.keyguard.domain.interactor -import com.android.systemui.RoboPilotTest import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest +import com.android.systemui.RoboPilotTest import com.android.systemui.SysuiTestCase import com.android.systemui.coroutines.collectValues import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository @@ -54,7 +54,10 @@ class KeyguardTransitionInteractorTest : SysuiTestCase() { @Before fun setUp() { repository = FakeKeyguardTransitionRepository() - underTest = KeyguardTransitionInteractor(repository, testScope.backgroundScope) + underTest = KeyguardTransitionInteractorFactory.create( + scope = testScope.backgroundScope, + repository = repository, + ).keyguardTransitionInteractor } @Test diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionScenariosTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionScenariosTest.kt index b5590154f7f4..d01a46e06b9b 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionScenariosTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionScenariosTest.kt @@ -19,6 +19,7 @@ package com.android.systemui.keyguard.domain.interactor import androidx.test.filters.SmallTest import com.android.keyguard.KeyguardSecurityModel import com.android.keyguard.KeyguardSecurityModel.SecurityMode.PIN +import com.android.keyguard.TestScopeProvider import com.android.systemui.SysuiTestCase import com.android.systemui.bouncer.data.repository.FakeKeyguardBouncerRepository import com.android.systemui.flags.FakeFeatureFlags @@ -92,76 +93,97 @@ class KeyguardTransitionScenariosTest : SysuiTestCase() { @Before fun setUp() { MockitoAnnotations.initMocks(this) - testScope = TestScope() + testScope = TestScopeProvider.getTestScope() keyguardRepository = FakeKeyguardRepository() bouncerRepository = FakeKeyguardBouncerRepository() shadeRepository = FakeShadeRepository() transitionRepository = spy(FakeKeyguardTransitionRepository()) - transitionInteractor = KeyguardTransitionInteractor( - transitionRepository, testScope.backgroundScope) whenever(keyguardSecurityModel.getSecurityMode(anyInt())).thenReturn(PIN) featureFlags = FakeFeatureFlags().apply { set(Flags.FACE_AUTH_REFACTOR, true) } - fromLockscreenTransitionInteractor = FromLockscreenTransitionInteractor( - scope = testScope, - keyguardInteractor = createKeyguardInteractor(), - transitionRepository = transitionRepository, - transitionInteractor = transitionInteractor, - shadeRepository = shadeRepository, - ).apply { start() } - - fromPrimaryBouncerTransitionInteractor = FromPrimaryBouncerTransitionInteractor( - scope = testScope, - keyguardInteractor = createKeyguardInteractor(), - transitionRepository = transitionRepository, - transitionInteractor = transitionInteractor, - keyguardSecurityModel = keyguardSecurityModel, - ).apply { start() } - - fromDreamingTransitionInteractor = FromDreamingTransitionInteractor( - scope = testScope, - keyguardInteractor = createKeyguardInteractor(), - transitionRepository = transitionRepository, - transitionInteractor = transitionInteractor, - ).apply { start() } - - fromAodTransitionInteractor = FromAodTransitionInteractor( - scope = testScope, - keyguardInteractor = createKeyguardInteractor(), - transitionRepository = transitionRepository, - transitionInteractor = transitionInteractor, - ).apply { start() } - - fromGoneTransitionInteractor = FromGoneTransitionInteractor( - scope = testScope, - keyguardInteractor = createKeyguardInteractor(), - transitionRepository = transitionRepository, - transitionInteractor = transitionInteractor, - ).apply { start() } - - fromDozingTransitionInteractor = FromDozingTransitionInteractor( - scope = testScope, - keyguardInteractor = createKeyguardInteractor(), - transitionRepository = transitionRepository, - transitionInteractor = transitionInteractor, - ).apply { start() } - - fromOccludedTransitionInteractor = FromOccludedTransitionInteractor( - scope = testScope, - keyguardInteractor = createKeyguardInteractor(), - transitionRepository = transitionRepository, - transitionInteractor = transitionInteractor, - ).apply { start() } - - fromAlternateBouncerTransitionInteractor = FromAlternateBouncerTransitionInteractor( - scope = testScope, - keyguardInteractor = createKeyguardInteractor(), - transitionRepository = transitionRepository, - transitionInteractor = transitionInteractor, - ).apply { start() } + transitionInteractor = + KeyguardTransitionInteractorFactory.create( + scope = testScope, + repository = transitionRepository, + ) + .keyguardTransitionInteractor + + fromLockscreenTransitionInteractor = + FromLockscreenTransitionInteractor( + scope = testScope, + keyguardInteractor = createKeyguardInteractor(), + transitionRepository = transitionRepository, + transitionInteractor = transitionInteractor, + shadeRepository = shadeRepository, + ) + .apply { start() } + + fromPrimaryBouncerTransitionInteractor = + FromPrimaryBouncerTransitionInteractor( + scope = testScope, + keyguardInteractor = createKeyguardInteractor(), + transitionRepository = transitionRepository, + transitionInteractor = transitionInteractor, + keyguardSecurityModel = keyguardSecurityModel, + ) + .apply { start() } + + fromDreamingTransitionInteractor = + FromDreamingTransitionInteractor( + scope = testScope, + keyguardInteractor = createKeyguardInteractor(), + transitionRepository = transitionRepository, + transitionInteractor = transitionInteractor, + ) + .apply { start() } + + fromAodTransitionInteractor = + FromAodTransitionInteractor( + scope = testScope, + keyguardInteractor = createKeyguardInteractor(), + transitionRepository = transitionRepository, + transitionInteractor = transitionInteractor, + ) + .apply { start() } + + fromGoneTransitionInteractor = + FromGoneTransitionInteractor( + scope = testScope, + keyguardInteractor = createKeyguardInteractor(), + transitionRepository = transitionRepository, + transitionInteractor = transitionInteractor, + ) + .apply { start() } + + fromDozingTransitionInteractor = + FromDozingTransitionInteractor( + scope = testScope, + keyguardInteractor = createKeyguardInteractor(), + transitionRepository = transitionRepository, + transitionInteractor = transitionInteractor, + ) + .apply { start() } + + fromOccludedTransitionInteractor = + FromOccludedTransitionInteractor( + scope = testScope, + keyguardInteractor = createKeyguardInteractor(), + transitionRepository = transitionRepository, + transitionInteractor = transitionInteractor, + ) + .apply { start() } + + fromAlternateBouncerTransitionInteractor = + FromAlternateBouncerTransitionInteractor( + scope = testScope, + keyguardInteractor = createKeyguardInteractor(), + transitionRepository = transitionRepository, + transitionInteractor = transitionInteractor, + ) + .apply { start() } } @Test diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/LightRevealScrimInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/LightRevealScrimInteractorTest.kt index 08e99dc6b7d0..6e7ba6dd1ecb 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/LightRevealScrimInteractorTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/LightRevealScrimInteractorTest.kt @@ -46,7 +46,11 @@ class LightRevealScrimInteractorTest : SysuiTestCase() { private val fakeLightRevealScrimRepository = FakeLightRevealScrimRepository() private val keyguardTransitionInteractor = - KeyguardTransitionInteractor(fakeKeyguardTransitionRepository, TestScope().backgroundScope) + KeyguardTransitionInteractorFactory.create( + scope = TestScope().backgroundScope, + repository = fakeKeyguardTransitionRepository, + ) + .keyguardTransitionInteractor private lateinit var underTest: LightRevealScrimInteractor diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/DefaultLockscreenLayoutTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/DefaultLockscreenLayoutTest.kt new file mode 100644 index 000000000000..2e97208e44de --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/DefaultLockscreenLayoutTest.kt @@ -0,0 +1,94 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package com.android.systemui.keyguard.ui.view.layout + +import android.graphics.Point +import android.view.ViewGroup +import android.view.WindowManager +import androidx.constraintlayout.widget.ConstraintSet +import androidx.test.filters.SmallTest +import com.android.keyguard.KeyguardUpdateMonitor +import com.android.systemui.R +import com.android.systemui.SysuiTestCase +import com.android.systemui.biometrics.AuthController +import com.android.systemui.keyguard.ui.view.KeyguardRootView +import com.android.systemui.monet.utils.ArgbSubject.assertThat +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith +import org.junit.runners.JUnit4 +import org.mockito.Answers +import org.mockito.Mock +import org.mockito.MockitoAnnotations + +@RunWith(JUnit4::class) +@SmallTest +class DefaultLockscreenLayoutTest : SysuiTestCase() { + private lateinit var defaultLockscreenLayout: DefaultLockscreenLayout + private lateinit var rootView: KeyguardRootView + @Mock private lateinit var authController: AuthController + @Mock private lateinit var keyguardUpdateMonitor: KeyguardUpdateMonitor + @Mock(answer = Answers.RETURNS_DEEP_STUBS) private lateinit var windowManager: WindowManager + + @Before + fun setup() { + MockitoAnnotations.initMocks(this) + rootView = KeyguardRootView(context, null) + defaultLockscreenLayout = + DefaultLockscreenLayout(authController, keyguardUpdateMonitor, windowManager, context) + } + + @Test + fun testLayoutViews_KeyguardIndicationArea() { + defaultLockscreenLayout.layoutViews(rootView) + val constraint = getViewConstraint(R.id.keyguard_indication_area) + assertThat(constraint.layout.bottomToBottom).isEqualTo(ConstraintSet.PARENT_ID) + assertThat(constraint.layout.startToStart).isEqualTo(ConstraintSet.PARENT_ID) + assertThat(constraint.layout.endToEnd).isEqualTo(ConstraintSet.PARENT_ID) + assertThat(constraint.layout.mWidth).isEqualTo(ViewGroup.LayoutParams.MATCH_PARENT) + assertThat(constraint.layout.mHeight).isEqualTo(ViewGroup.LayoutParams.WRAP_CONTENT) + } + + @Test + fun testLayoutViews_lockIconView() { + defaultLockscreenLayout.layoutViews(rootView) + val constraint = getViewConstraint(R.id.lock_icon_view) + assertThat(constraint.layout.topToTop).isEqualTo(ConstraintSet.PARENT_ID) + assertThat(constraint.layout.startToStart).isEqualTo(ConstraintSet.PARENT_ID) + } + + @Test + fun testCenterLockIcon() { + defaultLockscreenLayout.centerLockIcon(Point(5, 6), 1F, 5, rootView) + val constraint = getViewConstraint(R.id.lock_icon_view) + + assertThat(constraint.layout.mWidth).isEqualTo(2) + assertThat(constraint.layout.mHeight).isEqualTo(2) + assertThat(constraint.layout.topToTop).isEqualTo(ConstraintSet.PARENT_ID) + assertThat(constraint.layout.startToStart).isEqualTo(ConstraintSet.PARENT_ID) + assertThat(constraint.layout.topMargin).isEqualTo(5) + assertThat(constraint.layout.startMargin).isEqualTo(4) + } + + /** Get the ConstraintLayout constraint of the view. */ + private fun getViewConstraint(viewId: Int): ConstraintSet.Constraint { + val constraintSet = ConstraintSet() + constraintSet.clone(rootView) + return constraintSet.getConstraint(viewId) + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/KeyguardLayoutManagerCommandListenerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/KeyguardLayoutManagerCommandListenerTest.kt new file mode 100644 index 000000000000..145b2fdb000f --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/KeyguardLayoutManagerCommandListenerTest.kt @@ -0,0 +1,88 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.keyguard.ui.view.layout + +import androidx.test.filters.SmallTest +import com.android.systemui.SysuiTestCase +import com.android.systemui.statusbar.commandline.Command +import com.android.systemui.statusbar.commandline.CommandRegistry +import com.android.systemui.util.mockito.eq +import com.android.systemui.util.mockito.whenever +import com.android.systemui.util.mockito.withArgCaptor +import java.io.PrintWriter +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith +import org.junit.runners.JUnit4 +import org.mockito.ArgumentMatchers.anyString +import org.mockito.Mock +import org.mockito.Mockito.atLeastOnce +import org.mockito.Mockito.mock +import org.mockito.Mockito.never +import org.mockito.Mockito.verify +import org.mockito.MockitoAnnotations + +@RunWith(JUnit4::class) +@SmallTest +class KeyguardLayoutManagerCommandListenerTest : SysuiTestCase() { + private lateinit var keyguardLayoutManagerCommandListener: KeyguardLayoutManagerCommandListener + @Mock private lateinit var commandRegistry: CommandRegistry + @Mock private lateinit var keyguardLayoutManager: KeyguardLayoutManager + @Mock private lateinit var pw: PrintWriter + private lateinit var command: () -> Command + + @Before + fun setup() { + MockitoAnnotations.initMocks(this) + keyguardLayoutManagerCommandListener = + KeyguardLayoutManagerCommandListener( + commandRegistry, + keyguardLayoutManager, + ) + keyguardLayoutManagerCommandListener.start() + command = + withArgCaptor<() -> Command> { + verify(commandRegistry).registerCommand(eq("layout"), capture()) + } + } + + @Test + fun testHelp() { + command().execute(pw, listOf("help")) + verify(pw, atLeastOnce()).println(anyString()) + verify(keyguardLayoutManager, never()).transitionToLayout(anyString()) + } + + @Test + fun testBlank() { + command().execute(pw, listOf()) + verify(pw, atLeastOnce()).println(anyString()) + verify(keyguardLayoutManager, never()).transitionToLayout(anyString()) + } + + @Test + fun testValidArg() { + bindFakeIdMapToLayoutManager() + command().execute(pw, listOf("fake")) + verify(keyguardLayoutManager).transitionToLayout("fake") + } + + private fun bindFakeIdMapToLayoutManager() { + val map = mapOf("fake" to mock(LockscreenLayout::class.java)) + whenever(keyguardLayoutManager.layoutIdMap).thenReturn(map) + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/KeyguardLayoutManagerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/KeyguardLayoutManagerTest.kt new file mode 100644 index 000000000000..95b2030de923 --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/KeyguardLayoutManagerTest.kt @@ -0,0 +1,69 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package com.android.systemui.keyguard.ui.view.layout + +import androidx.test.filters.SmallTest +import com.android.systemui.SysuiTestCase +import com.android.systemui.keyguard.ui.view.KeyguardRootView +import com.android.systemui.keyguard.ui.view.layout.DefaultLockscreenLayout.Companion.DEFAULT +import com.android.systemui.statusbar.policy.ConfigurationController +import com.android.systemui.util.mockito.whenever +import com.google.common.truth.Truth.assertThat +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith +import org.junit.runners.JUnit4 +import org.mockito.Mock +import org.mockito.Mockito.verify +import org.mockito.MockitoAnnotations + +@RunWith(JUnit4::class) +@SmallTest +class KeyguardLayoutManagerTest : SysuiTestCase() { + private lateinit var keyguardLayoutManager: KeyguardLayoutManager + @Mock lateinit var configurationController: ConfigurationController + @Mock lateinit var defaultLockscreenLayout: DefaultLockscreenLayout + @Mock lateinit var keyguardRootView: KeyguardRootView + + @Before + fun setup() { + MockitoAnnotations.initMocks(this) + whenever(defaultLockscreenLayout.id).thenReturn(DEFAULT) + keyguardLayoutManager = + KeyguardLayoutManager( + configurationController, + setOf(defaultLockscreenLayout), + keyguardRootView + ) + } + + @Test + fun testDefaultLayout() { + keyguardLayoutManager.transitionToLayout(DEFAULT) + verify(defaultLockscreenLayout).layoutViews(keyguardRootView) + } + + @Test + fun testTransitionToLayout_validId() { + assertThat(keyguardLayoutManager.transitionToLayout(DEFAULT)).isTrue() + } + @Test + fun testTransitionToLayout_invalidId() { + assertThat(keyguardLayoutManager.transitionToLayout("abc")).isFalse() + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/DreamingToLockscreenTransitionViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/DreamingToLockscreenTransitionViewModelTest.kt index ab994b72a45b..c67f53519957 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/DreamingToLockscreenTransitionViewModelTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/DreamingToLockscreenTransitionViewModelTest.kt @@ -22,6 +22,8 @@ import com.android.systemui.RoboPilotTest import com.android.systemui.SysuiTestCase import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor +import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractorFactory +import com.android.systemui.keyguard.shared.model.KeyguardState import com.android.systemui.keyguard.shared.model.KeyguardState.AOD import com.android.systemui.keyguard.shared.model.KeyguardState.DOZING import com.android.systemui.keyguard.shared.model.KeyguardState.DREAMING @@ -55,7 +57,12 @@ class DreamingToLockscreenTransitionViewModelTest : SysuiTestCase() { @Before fun setUp() { repository = FakeKeyguardTransitionRepository() - val interactor = KeyguardTransitionInteractor(repository, TestScope().backgroundScope) + val interactor = + KeyguardTransitionInteractorFactory.create( + scope = TestScope().backgroundScope, + repository = repository, + ) + .keyguardTransitionInteractor underTest = DreamingToLockscreenTransitionViewModel(interactor, mock()) } diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/GoneToDreamingTransitionViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/GoneToDreamingTransitionViewModelTest.kt index 694539b0cbfe..75c8bff326b0 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/GoneToDreamingTransitionViewModelTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/GoneToDreamingTransitionViewModelTest.kt @@ -21,7 +21,7 @@ import androidx.test.filters.SmallTest import com.android.systemui.RoboPilotTest import com.android.systemui.SysuiTestCase import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository -import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor +import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractorFactory import com.android.systemui.keyguard.shared.model.KeyguardState import com.android.systemui.keyguard.shared.model.TransitionState import com.android.systemui.keyguard.shared.model.TransitionStep @@ -46,7 +46,12 @@ class GoneToDreamingTransitionViewModelTest : SysuiTestCase() { @Before fun setUp() { repository = FakeKeyguardTransitionRepository() - val interactor = KeyguardTransitionInteractor(repository, TestScope().backgroundScope) + val interactor = + KeyguardTransitionInteractorFactory.create( + scope = TestScope().backgroundScope, + repository = repository, + ) + .keyguardTransitionInteractor underTest = GoneToDreamingTransitionViewModel(interactor) } diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBottomAreaViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBottomAreaViewModelTest.kt index 29886d5481b9..d02b3fccef1f 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBottomAreaViewModelTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBottomAreaViewModelTest.kt @@ -41,13 +41,12 @@ import com.android.systemui.keyguard.data.quickaffordance.KeyguardQuickAffordanc import com.android.systemui.keyguard.data.quickaffordance.KeyguardQuickAffordanceRemoteUserSelectionManager import com.android.systemui.keyguard.data.repository.FakeBiometricSettingsRepository import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository -import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository import com.android.systemui.keyguard.data.repository.KeyguardQuickAffordanceRepository import com.android.systemui.keyguard.domain.interactor.KeyguardBottomAreaInteractor import com.android.systemui.keyguard.domain.interactor.KeyguardInteractorFactory import com.android.systemui.keyguard.domain.interactor.KeyguardLongPressInteractor import com.android.systemui.keyguard.domain.interactor.KeyguardQuickAffordanceInteractor -import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor +import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractorFactory import com.android.systemui.keyguard.domain.quickaffordance.FakeKeyguardQuickAffordanceRegistry import com.android.systemui.keyguard.shared.quickaffordance.ActivationState import com.android.systemui.keyguard.shared.quickaffordance.KeyguardQuickAffordancePosition @@ -210,10 +209,10 @@ class KeyguardBottomAreaViewModelTest : SysuiTestCase() { appContext = mContext, scope = testScope.backgroundScope, transitionInteractor = - KeyguardTransitionInteractor( - repository = FakeKeyguardTransitionRepository(), - scope = testScope.backgroundScope - ), + KeyguardTransitionInteractorFactory.create( + scope = TestScope().backgroundScope, + ) + .keyguardTransitionInteractor, repository = repository, logger = UiEventLoggerFake(), featureFlags = featureFlags, diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToDreamingTransitionViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToDreamingTransitionViewModelTest.kt index ea17751782c4..12fe07f0827e 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToDreamingTransitionViewModelTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToDreamingTransitionViewModelTest.kt @@ -21,7 +21,7 @@ import androidx.test.filters.SmallTest import com.android.systemui.RoboPilotTest import com.android.systemui.SysuiTestCase import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository -import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor +import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractorFactory import com.android.systemui.keyguard.shared.model.KeyguardState import com.android.systemui.keyguard.shared.model.TransitionState import com.android.systemui.keyguard.shared.model.TransitionStep @@ -46,7 +46,12 @@ class LockscreenToDreamingTransitionViewModelTest : SysuiTestCase() { @Before fun setUp() { repository = FakeKeyguardTransitionRepository() - val interactor = KeyguardTransitionInteractor(repository, TestScope().backgroundScope) + val interactor = + KeyguardTransitionInteractorFactory.create( + scope = TestScope().backgroundScope, + repository = repository, + ) + .keyguardTransitionInteractor underTest = LockscreenToDreamingTransitionViewModel(interactor) } diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToOccludedTransitionViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToOccludedTransitionViewModelTest.kt index bf56a981fa31..83ae631ed164 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToOccludedTransitionViewModelTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToOccludedTransitionViewModelTest.kt @@ -21,7 +21,7 @@ import androidx.test.filters.SmallTest import com.android.systemui.RoboPilotTest import com.android.systemui.SysuiTestCase import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository -import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor +import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractorFactory import com.android.systemui.keyguard.shared.model.KeyguardState import com.android.systemui.keyguard.shared.model.TransitionState import com.android.systemui.keyguard.shared.model.TransitionStep @@ -46,7 +46,12 @@ class LockscreenToOccludedTransitionViewModelTest : SysuiTestCase() { @Before fun setUp() { repository = FakeKeyguardTransitionRepository() - val interactor = KeyguardTransitionInteractor(repository, TestScope().backgroundScope) + val interactor = + KeyguardTransitionInteractorFactory.create( + scope = TestScope().backgroundScope, + repository = repository, + ) + .keyguardTransitionInteractor underTest = LockscreenToOccludedTransitionViewModel(interactor) } diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/OccludedToLockscreenTransitionViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/OccludedToLockscreenTransitionViewModelTest.kt index 34da26ecc0bf..88603999cdd7 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/OccludedToLockscreenTransitionViewModelTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/OccludedToLockscreenTransitionViewModelTest.kt @@ -21,7 +21,7 @@ import androidx.test.filters.SmallTest import com.android.systemui.RoboPilotTest import com.android.systemui.SysuiTestCase import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository -import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor +import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractorFactory import com.android.systemui.keyguard.shared.model.KeyguardState import com.android.systemui.keyguard.shared.model.TransitionState import com.android.systemui.keyguard.shared.model.TransitionStep @@ -46,7 +46,12 @@ class OccludedToLockscreenTransitionViewModelTest : SysuiTestCase() { @Before fun setUp() { repository = FakeKeyguardTransitionRepository() - val interactor = KeyguardTransitionInteractor(repository, TestScope().backgroundScope) + val interactor = + KeyguardTransitionInteractorFactory.create( + scope = TestScope().backgroundScope, + repository = repository, + ) + .keyguardTransitionInteractor underTest = OccludedToLockscreenTransitionViewModel(interactor) } diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToGoneTransitionViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToGoneTransitionViewModelTest.kt index f88b71d469cf..d8c78ebdca49 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToGoneTransitionViewModelTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToGoneTransitionViewModelTest.kt @@ -22,7 +22,7 @@ import com.android.systemui.RoboPilotTest import com.android.systemui.SysuiTestCase import com.android.systemui.bouncer.domain.interactor.PrimaryBouncerInteractor import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository -import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor +import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractorFactory import com.android.systemui.keyguard.shared.model.KeyguardState import com.android.systemui.keyguard.shared.model.ScrimAlpha import com.android.systemui.keyguard.shared.model.TransitionState @@ -55,7 +55,12 @@ class PrimaryBouncerToGoneTransitionViewModelTest : SysuiTestCase() { fun setUp() { MockitoAnnotations.initMocks(this) repository = FakeKeyguardTransitionRepository() - val interactor = KeyguardTransitionInteractor(repository, TestScope().backgroundScope) + val interactor = + KeyguardTransitionInteractorFactory.create( + scope = TestScope().backgroundScope, + repository = repository, + ) + .keyguardTransitionInteractor underTest = PrimaryBouncerToGoneTransitionViewModel( interactor, diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/KeyguardMediaControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/KeyguardMediaControllerTest.kt index b40ebc9bb156..91b0245be8d3 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/KeyguardMediaControllerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/KeyguardMediaControllerTest.kt @@ -193,6 +193,17 @@ class KeyguardMediaControllerTest : SysuiTestCase() { } @Test + fun dozeWakeUpAnimationWaiting_inSplitShade_mediaIsHidden() { + val splitShadeContainer = FrameLayout(context) + keyguardMediaController.attachSplitShadeContainer(splitShadeContainer) + keyguardMediaController.useSplitShade = true + + keyguardMediaController.isDozeWakeUpAnimationWaiting = true + + assertThat(splitShadeContainer.visibility).isEqualTo(GONE) + } + + @Test fun dozing_inSingleShade_mediaIsVisible() { val splitShadeContainer = FrameLayout(context) keyguardMediaController.attachSplitShadeContainer(splitShadeContainer) @@ -203,6 +214,17 @@ class KeyguardMediaControllerTest : SysuiTestCase() { assertThat(mediaContainerView.visibility).isEqualTo(VISIBLE) } + @Test + fun dozeWakeUpAnimationWaiting_inSingleShade_mediaIsVisible() { + val splitShadeContainer = FrameLayout(context) + keyguardMediaController.attachSplitShadeContainer(splitShadeContainer) + keyguardMediaController.useSplitShade = false + + keyguardMediaController.isDozeWakeUpAnimationWaiting = true + + assertThat(mediaContainerView.visibility).isEqualTo(VISIBLE) + } + private fun setDozing() { whenever(statusBarStateController.isDozing).thenReturn(true) statusBarStateListener.onDozingChanged(true) diff --git a/packages/SystemUI/tests/src/com/android/systemui/multishade/domain/interactor/MultiShadeMotionEventInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/multishade/domain/interactor/MultiShadeMotionEventInteractorTest.kt index 9e5422470d8b..5890cbd06476 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/multishade/domain/interactor/MultiShadeMotionEventInteractorTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/multishade/domain/interactor/MultiShadeMotionEventInteractorTest.kt @@ -25,7 +25,7 @@ import com.android.systemui.coroutines.collectLastValue import com.android.systemui.flags.FakeFeatureFlags import com.android.systemui.flags.Flags import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository -import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor +import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractorFactory import com.android.systemui.keyguard.shared.model.KeyguardState import com.android.systemui.keyguard.shared.model.TransitionState import com.android.systemui.keyguard.shared.model.TransitionStep @@ -97,10 +97,11 @@ class MultiShadeMotionEventInteractorTest : SysuiTestCase() { multiShadeInteractor = interactor, featureFlags = featureFlags, keyguardTransitionInteractor = - KeyguardTransitionInteractor( - repository = keyguardTransitionRepository, - scope = testScope.backgroundScope - ), + KeyguardTransitionInteractorFactory.create( + scope = TestScope().backgroundScope, + repository = keyguardTransitionRepository, + ) + .keyguardTransitionInteractor, falsingManager = falsingManager, shadeController = shadeController, ) diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerTest.java index 5802eb3d9618..eb4ae1a743ef 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerTest.java @@ -514,6 +514,17 @@ public class NotificationPanelViewControllerTest extends NotificationPanelViewCo } @Test + public void keyguardStatusView_willPlayDelayedDoze_notifiesKeyguardMediaController() { + when(mNotificationStackScrollLayoutController.getVisibleNotificationCount()).thenReturn(2); + mStatusBarStateController.setState(KEYGUARD); + enableSplitShade(/* enabled= */ true); + + mNotificationPanelViewController.setWillPlayDelayedDozeAmountAnimation(true); + + verify(mKeyguardMediaController).setDozeWakeUpAnimationWaiting(true); + } + + @Test public void keyguardStatusView_willPlayDelayedDoze_isCentered_thenStillCenteredIfNoNotifs() { when(mNotificationStackScrollLayoutController.getVisibleNotificationCount()).thenReturn(0); mStatusBarStateController.setState(KEYGUARD); diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewControllerTest.kt index 2a9b403cb2e6..5fb3a7955b5c 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewControllerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewControllerTest.kt @@ -28,6 +28,11 @@ import com.android.keyguard.dagger.KeyguardBouncerComponent import com.android.systemui.R import com.android.systemui.SysuiTestCase import com.android.systemui.back.domain.interactor.BackActionInteractor +import com.android.systemui.bouncer.data.factory.BouncerMessageFactory +import com.android.systemui.bouncer.data.repository.FakeBouncerMessageRepository +import com.android.systemui.bouncer.domain.interactor.BouncerMessageInteractor +import com.android.systemui.bouncer.domain.interactor.CountDownTimerUtil +import com.android.systemui.bouncer.ui.viewmodel.KeyguardBouncerViewModel import com.android.systemui.classifier.FalsingCollectorFake import com.android.systemui.classifier.FalsingManagerFake import com.android.systemui.dock.DockManager @@ -35,14 +40,9 @@ import com.android.systemui.dump.logcatLogBuffer import com.android.systemui.flags.FakeFeatureFlags import com.android.systemui.flags.Flags import com.android.systemui.keyguard.KeyguardUnlockAnimationController -import com.android.systemui.bouncer.data.factory.BouncerMessageFactory -import com.android.systemui.bouncer.domain.interactor.BouncerMessageInteractor -import com.android.systemui.bouncer.domain.interactor.CountDownTimerUtil -import com.android.systemui.bouncer.data.repository.FakeBouncerMessageRepository -import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor +import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractorFactory import com.android.systemui.keyguard.shared.model.TransitionStep -import com.android.systemui.bouncer.ui.viewmodel.KeyguardBouncerViewModel import com.android.systemui.keyguard.ui.viewmodel.PrimaryBouncerToGoneTransitionViewModel import com.android.systemui.log.BouncerLogger import com.android.systemui.multishade.data.remoteproxy.MultiShadeInputProxy @@ -80,9 +80,9 @@ import org.mockito.Mockito.anyFloat import org.mockito.Mockito.mock import org.mockito.Mockito.never import org.mockito.Mockito.verify -import org.mockito.Mockito.`when` as whenever import org.mockito.MockitoAnnotations import java.util.Optional +import org.mockito.Mockito.`when` as whenever @OptIn(ExperimentalCoroutinesApi::class) @SmallTest @@ -198,10 +198,9 @@ class NotificationShadeWindowViewControllerTest : SysuiTestCase() { multiShadeInteractor = multiShadeInteractor, featureFlags = featureFlags, keyguardTransitionInteractor = - KeyguardTransitionInteractor( - repository = FakeKeyguardTransitionRepository(), - scope = testScope.backgroundScope - ), + KeyguardTransitionInteractorFactory.create( + scope = TestScope().backgroundScope, + ).keyguardTransitionInteractor, falsingManager = FalsingManagerFake(), shadeController = shadeController, ) diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewTest.kt index 252a03bb07d2..544137e95779 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewTest.kt @@ -40,8 +40,8 @@ import com.android.systemui.dump.logcatLogBuffer import com.android.systemui.flags.FakeFeatureFlags import com.android.systemui.flags.Flags import com.android.systemui.keyguard.KeyguardUnlockAnimationController -import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor +import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractorFactory import com.android.systemui.keyguard.ui.viewmodel.PrimaryBouncerToGoneTransitionViewModel import com.android.systemui.log.BouncerLogger import com.android.systemui.multishade.data.remoteproxy.MultiShadeInputProxy @@ -211,10 +211,10 @@ class NotificationShadeWindowViewTest : SysuiTestCase() { multiShadeInteractor = multiShadeInteractor, featureFlags = featureFlags, keyguardTransitionInteractor = - KeyguardTransitionInteractor( - repository = FakeKeyguardTransitionRepository(), - scope = testScope.backgroundScope - ), + KeyguardTransitionInteractorFactory.create( + scope = TestScope().backgroundScope, + ) + .keyguardTransitionInteractor, falsingManager = FalsingManagerFake(), shadeController = shadeController, ) diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/data/repository/ShadeInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/data/repository/ShadeInteractorTest.kt index 45247977283a..3ea8f5412b40 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/shade/data/repository/ShadeInteractorTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/shade/data/repository/ShadeInteractorTest.kt @@ -84,6 +84,7 @@ class ShadeInteractorTest : SysuiTestCase() { MockitoAnnotations.initMocks(this) featureFlags.set(Flags.FACE_AUTH_REFACTOR, false) + featureFlags.set(Flags.FULL_SCREEN_USER_SWITCHER, true) val refreshUsersScheduler = RefreshUsersScheduler( diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/LockscreenWallpaperTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/LockscreenWallpaperTest.kt new file mode 100644 index 000000000000..47671fbadd0a --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/LockscreenWallpaperTest.kt @@ -0,0 +1,101 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License + */ + +package com.android.systemui.statusbar.phone + +import android.app.WallpaperManager +import android.content.pm.UserInfo +import android.os.Looper +import androidx.test.filters.SmallTest +import com.android.systemui.SysuiTestCase +import com.android.systemui.user.data.model.SelectionStatus +import com.android.systemui.user.data.repository.FakeUserRepository +import com.android.systemui.util.kotlin.JavaAdapter +import com.android.systemui.util.mockito.any +import com.android.systemui.util.mockito.mock +import com.android.systemui.util.mockito.whenever +import com.android.systemui.utils.os.FakeHandler +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.test.StandardTestDispatcher +import kotlinx.coroutines.test.TestScope +import kotlinx.coroutines.test.runTest +import org.junit.Before +import org.junit.Test +import org.mockito.ArgumentMatchers.eq +import org.mockito.Mockito.verify + +@SmallTest +@OptIn(ExperimentalCoroutinesApi::class) +class LockscreenWallpaperTest : SysuiTestCase() { + + private lateinit var underTest: LockscreenWallpaper + + private val testScope = TestScope(StandardTestDispatcher()) + private val userRepository = FakeUserRepository() + + private val wallpaperManager: WallpaperManager = mock() + + @Before + fun setUp() { + whenever(wallpaperManager.isLockscreenLiveWallpaperEnabled).thenReturn(false) + whenever(wallpaperManager.isWallpaperSupported).thenReturn(true) + underTest = + LockscreenWallpaper( + /* wallpaperManager= */ wallpaperManager, + /* iWallpaperManager= */ mock(), + /* keyguardUpdateMonitor= */ mock(), + /* dumpManager= */ mock(), + /* mediaManager= */ mock(), + /* mainHandler= */ FakeHandler(Looper.getMainLooper()), + /* javaAdapter= */ JavaAdapter(testScope.backgroundScope), + /* userRepository= */ userRepository, + /* userTracker= */ mock(), + ) + underTest.start() + } + + @Test + fun getBitmap_matchesUserIdFromUserRepo() = + testScope.runTest { + val info = UserInfo(/* id= */ 5, /* name= */ "id5", /* flags= */ 0) + userRepository.setUserInfos(listOf(info)) + userRepository.setSelectedUserInfo(info) + + underTest.bitmap + + verify(wallpaperManager).getWallpaperFile(any(), eq(5)) + } + + @Test + fun getBitmap_usesOldUserIfNewUserInProgress() = + testScope.runTest { + val info5 = UserInfo(/* id= */ 5, /* name= */ "id5", /* flags= */ 0) + val info6 = UserInfo(/* id= */ 6, /* name= */ "id6", /* flags= */ 0) + userRepository.setUserInfos(listOf(info5, info6)) + userRepository.setSelectedUserInfo(info5) + + // WHEN the selection of user 6 is only in progress + userRepository.setSelectedUserInfo( + info6, + selectionStatus = SelectionStatus.SELECTION_IN_PROGRESS + ) + + underTest.bitmap + + // THEN we still use user 5 for wallpaper selection + verify(wallpaperManager).getWallpaperFile(any(), eq(5)) + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/CollapsedStatusBarViewModelImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/CollapsedStatusBarViewModelImplTest.kt index 6301fa0be463..842d548c8358 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/CollapsedStatusBarViewModelImplTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/CollapsedStatusBarViewModelImplTest.kt @@ -20,7 +20,7 @@ import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.coroutines.collectValues import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository -import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor +import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractorFactory import com.android.systemui.keyguard.shared.model.KeyguardState import com.android.systemui.keyguard.shared.model.TransitionState import com.android.systemui.keyguard.shared.model.TransitionStep @@ -48,7 +48,11 @@ class CollapsedStatusBarViewModelImplTest : SysuiTestCase() { keyguardTransitionRepository = FakeKeyguardTransitionRepository() val interactor = - KeyguardTransitionInteractor(keyguardTransitionRepository, testScope.backgroundScope) + KeyguardTransitionInteractorFactory.create( + scope = TestScope().backgroundScope, + repository = keyguardTransitionRepository, + ) + .keyguardTransitionInteractor underTest = CollapsedStatusBarViewModelImpl(interactor, testScope.backgroundScope) } diff --git a/packages/SystemUI/tests/src/com/android/systemui/user/data/repository/UserRepositoryImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/user/data/repository/UserRepositoryImplTest.kt index 079fbcd0304c..0c28cbb52831 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/user/data/repository/UserRepositoryImplTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/user/data/repository/UserRepositoryImplTest.kt @@ -26,6 +26,8 @@ import com.android.systemui.SysuiTestCase import com.android.systemui.flags.FakeFeatureFlags import com.android.systemui.flags.Flags.FACE_AUTH_REFACTOR import com.android.systemui.settings.FakeUserTracker +import com.android.systemui.user.data.model.SelectedUserModel +import com.android.systemui.user.data.model.SelectionStatus import com.android.systemui.user.data.model.UserSwitcherSettingsModel import com.android.systemui.util.settings.FakeSettings import com.google.common.truth.Truth.assertThat @@ -224,6 +226,40 @@ class UserRepositoryImplTest : SysuiTestCase() { } @Test + fun userTrackerCallback_updatesSelectionStatus() = runSelfCancelingTest { + underTest = create(this) + var selectedUser: SelectedUserModel? = null + underTest.selectedUser.onEach { selectedUser = it }.launchIn(this) + setUpUsers(count = 2, selectedIndex = 1) + + // WHEN the user is changing + tracker.onUserChanging(userId = 1) + + // THEN the selection status is IN_PROGRESS + assertThat(selectedUser!!.selectionStatus).isEqualTo(SelectionStatus.SELECTION_IN_PROGRESS) + + // WHEN the user has finished changing + tracker.onUserChanged(userId = 1) + + // THEN the selection status is COMPLETE + assertThat(selectedUser!!.selectionStatus).isEqualTo(SelectionStatus.SELECTION_COMPLETE) + + tracker.onProfileChanged() + assertThat(selectedUser!!.selectionStatus).isEqualTo(SelectionStatus.SELECTION_COMPLETE) + + setUpUsers(count = 2, selectedIndex = 0) + + tracker.onUserChanging(userId = 0) + assertThat(selectedUser!!.selectionStatus).isEqualTo(SelectionStatus.SELECTION_IN_PROGRESS) + + // WHEN a profile change occurs while a user is changing + tracker.onProfileChanged() + + // THEN the selection status remains as IN_PROGRESS + assertThat(selectedUser!!.selectionStatus).isEqualTo(SelectionStatus.SELECTION_IN_PROGRESS) + } + + @Test fun userSwitchingInProgress_registersUserTrackerCallback() = runSelfCancelingTest { underTest = create(this) diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractorFactory.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractorFactory.kt new file mode 100644 index 000000000000..312ade510784 --- /dev/null +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractorFactory.kt @@ -0,0 +1,47 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License + */ + +package com.android.systemui.keyguard.domain.interactor + +import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository +import com.android.systemui.keyguard.data.repository.KeyguardTransitionRepository +import kotlinx.coroutines.CoroutineScope + +/** + * Helper to create a new KeyguardTransitionInteractor in a way that doesn't require modifying 20+ + * tests whenever we add a constructor param. + */ +object KeyguardTransitionInteractorFactory { + @JvmOverloads + @JvmStatic + fun create( + scope: CoroutineScope, + repository: KeyguardTransitionRepository = FakeKeyguardTransitionRepository(), + ): WithDependencies { + return WithDependencies( + repository = repository, + KeyguardTransitionInteractor( + scope = scope, + repository = repository, + ) + ) + } + + data class WithDependencies( + val repository: KeyguardTransitionRepository, + val keyguardTransitionInteractor: KeyguardTransitionInteractor, + ) +} diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/user/data/repository/FakeUserRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/user/data/repository/FakeUserRepository.kt index 61e5b5fc27ea..51ee0c00cb0d 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/user/data/repository/FakeUserRepository.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/user/data/repository/FakeUserRepository.kt @@ -19,18 +19,28 @@ package com.android.systemui.user.data.repository import android.content.pm.UserInfo import android.os.UserHandle +import com.android.systemui.user.data.model.SelectedUserModel +import com.android.systemui.user.data.model.SelectionStatus import com.android.systemui.user.data.model.UserSwitcherSettingsModel import java.util.concurrent.atomic.AtomicBoolean import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.asStateFlow -import kotlinx.coroutines.flow.filterNotNull +import kotlinx.coroutines.flow.map import kotlinx.coroutines.yield class FakeUserRepository : UserRepository { companion object { // User id to represent a non system (human) user id. We presume this is the main user. private const val MAIN_USER_ID = 10 + + private val DEFAULT_SELECTED_USER = 0 + private val DEFAULT_SELECTED_USER_INFO = + UserInfo( + /* id= */ DEFAULT_SELECTED_USER, + /* name= */ "default selected user", + /* flags= */ 0, + ) } private val _userSwitcherSettings = MutableStateFlow(UserSwitcherSettingsModel()) @@ -40,8 +50,11 @@ class FakeUserRepository : UserRepository { private val _userInfos = MutableStateFlow<List<UserInfo>>(emptyList()) override val userInfos: Flow<List<UserInfo>> = _userInfos.asStateFlow() - private val _selectedUserInfo = MutableStateFlow<UserInfo?>(null) - override val selectedUserInfo: Flow<UserInfo> = _selectedUserInfo.filterNotNull() + override val selectedUser = + MutableStateFlow( + SelectedUserModel(DEFAULT_SELECTED_USER_INFO, SelectionStatus.SELECTION_COMPLETE) + ) + override val selectedUserInfo: Flow<UserInfo> = selectedUser.map { it.userInfo } private val _userSwitchingInProgress = MutableStateFlow(false) override val userSwitchingInProgress: Flow<Boolean> @@ -72,7 +85,7 @@ class FakeUserRepository : UserRepository { } override fun getSelectedUserInfo(): UserInfo { - return checkNotNull(_selectedUserInfo.value) + return selectedUser.value.userInfo } override fun isSimpleUserSwitcher(): Boolean { @@ -87,12 +100,15 @@ class FakeUserRepository : UserRepository { _userInfos.value = infos } - suspend fun setSelectedUserInfo(userInfo: UserInfo) { + suspend fun setSelectedUserInfo( + userInfo: UserInfo, + selectionStatus: SelectionStatus = SelectionStatus.SELECTION_COMPLETE, + ) { check(_userInfos.value.contains(userInfo)) { "Cannot select the following user, it is not in the list of user infos: $userInfo!" } - _selectedUserInfo.value = userInfo + selectedUser.value = SelectedUserModel(userInfo, selectionStatus) yield() } diff --git a/services/core/java/com/android/server/display/LocalDisplayAdapter.java b/services/core/java/com/android/server/display/LocalDisplayAdapter.java index dec9f62c8739..b00b7a1f0e2a 100644 --- a/services/core/java/com/android/server/display/LocalDisplayAdapter.java +++ b/services/core/java/com/android/server/display/LocalDisplayAdapter.java @@ -496,7 +496,7 @@ final class LocalDisplayAdapter extends DisplayAdapter { private void loadDisplayDeviceConfig() { // Load display device config final Context context = getOverlayContext(); - mDisplayDeviceConfig = DisplayDeviceConfig.create(context, mPhysicalDisplayId, + mDisplayDeviceConfig = mInjector.createDisplayDeviceConfig(context, mPhysicalDisplayId, mIsFirstDisplay); // Load brightness HWC quirk @@ -1336,6 +1336,11 @@ final class LocalDisplayAdapter extends DisplayAdapter { public SurfaceControlProxy getSurfaceControlProxy() { return new SurfaceControlProxy(); } + + public DisplayDeviceConfig createDisplayDeviceConfig(Context context, + long physicalDisplayId, boolean isFirstDisplay) { + return DisplayDeviceConfig.create(context, physicalDisplayId, isFirstDisplay); + } } public interface DisplayEventListener { diff --git a/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceTv.java b/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceTv.java index 70870627b8c7..3ea51d068424 100644 --- a/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceTv.java +++ b/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceTv.java @@ -377,6 +377,7 @@ public final class HdmiCecLocalDeviceTv extends HdmiCecLocalDevice { return; } int oldPath = getActivePortId() != Constants.INVALID_PORT_ID + && getActivePortId() != Constants.CEC_SWITCH_HOME ? mService.portIdToPath(getActivePortId()) : getDeviceInfo().getPhysicalAddress(); setActivePath(oldPath); if (mSkipRoutingControl) { diff --git a/services/core/java/com/android/server/locksettings/SyntheticPasswordManager.java b/services/core/java/com/android/server/locksettings/SyntheticPasswordManager.java index 6ba75850f95d..d700c6adfebb 100644 --- a/services/core/java/com/android/server/locksettings/SyntheticPasswordManager.java +++ b/services/core/java/com/android/server/locksettings/SyntheticPasswordManager.java @@ -36,6 +36,7 @@ import android.hardware.weaver.IWeaver; import android.hardware.weaver.WeaverConfig; import android.hardware.weaver.WeaverReadResponse; import android.hardware.weaver.WeaverReadStatus; +import android.os.IBinder; import android.os.RemoteCallbackList; import android.os.RemoteException; import android.os.ServiceManager; @@ -505,7 +506,7 @@ class SyntheticPasswordManager { private final Context mContext; private LockSettingsStorage mStorage; - private IWeaver mWeaver; + private volatile IWeaver mWeaver; private WeaverConfig mWeaverConfig; private PasswordSlotManager mPasswordSlotManager; @@ -536,13 +537,33 @@ class SyntheticPasswordManager { } } - private IWeaver getWeaverService() { + private class WeaverDiedRecipient implements IBinder.DeathRecipient { + // Not synchronized on the outer class, since setting the pointer to null is atomic, and we + // don't want to have to worry about any sort of deadlock here. + @Override + public void binderDied() { + // Weaver died. Try to recover by setting mWeaver to null, which makes + // getWeaverService() look up the service again. This is done only as a simple + // robustness measure; it should not be relied on. If this triggers, the root cause is + // almost certainly a bug in the device's Weaver implementation, which must be fixed. + Slog.wtf(TAG, "Weaver service has died"); + mWeaver.asBinder().unlinkToDeath(this, 0); + mWeaver = null; + } + } + + private @Nullable IWeaver getWeaverServiceInternal() { // Try to get the AIDL service first try { IWeaver aidlWeaver = IWeaver.Stub.asInterface( ServiceManager.waitForDeclaredService(IWeaver.DESCRIPTOR + "/default")); if (aidlWeaver != null) { Slog.i(TAG, "Using AIDL weaver service"); + try { + aidlWeaver.asBinder().linkToDeath(new WeaverDiedRecipient(), 0); + } catch (RemoteException e) { + Slog.w(TAG, "Unable to register Weaver death recipient", e); + } return aidlWeaver; } } catch (SecurityException e) { @@ -568,15 +589,20 @@ class SyntheticPasswordManager { return LockPatternUtils.isAutoPinConfirmFeatureAvailable(); } - private synchronized boolean isWeaverAvailable() { - if (mWeaver != null) { - return true; + /** + * Returns a handle to the Weaver service, or null if Weaver is unavailable. Note that not all + * devices support Weaver. + */ + private synchronized @Nullable IWeaver getWeaverService() { + IWeaver weaver = mWeaver; + if (weaver != null) { + return weaver; } // Re-initialize weaver in case there was a transient error preventing access to it. - IWeaver weaver = getWeaverService(); + weaver = getWeaverServiceInternal(); if (weaver == null) { - return false; + return null; } final WeaverConfig weaverConfig; @@ -584,19 +610,18 @@ class SyntheticPasswordManager { weaverConfig = weaver.getConfig(); } catch (RemoteException | ServiceSpecificException e) { Slog.e(TAG, "Failed to get weaver config", e); - return false; + return null; } if (weaverConfig == null || weaverConfig.slots <= 0) { Slog.e(TAG, "Invalid weaver config"); - return false; + return null; } mWeaver = weaver; mWeaverConfig = weaverConfig; mPasswordSlotManager.refreshActiveSlots(getUsedWeaverSlots()); Slog.i(TAG, "Weaver service initialized"); - - return true; + return weaver; } /** @@ -606,7 +631,7 @@ class SyntheticPasswordManager { * * @return the value stored in the weaver slot, or null if the operation fails */ - private byte[] weaverEnroll(int slot, byte[] key, @Nullable byte[] value) { + private byte[] weaverEnroll(IWeaver weaver, int slot, byte[] key, @Nullable byte[] value) { if (slot == INVALID_WEAVER_SLOT || slot >= mWeaverConfig.slots) { throw new IllegalArgumentException("Invalid slot for weaver"); } @@ -619,7 +644,7 @@ class SyntheticPasswordManager { value = SecureRandomUtils.randomBytes(mWeaverConfig.valueSize); } try { - mWeaver.write(slot, key, value); + weaver.write(slot, key, value); } catch (RemoteException e) { Slog.e(TAG, "weaver write binder call failed, slot: " + slot, e); return null; @@ -648,7 +673,7 @@ class SyntheticPasswordManager { * the verification is successful, throttled or failed. If successful, the bound secret * is also returned. */ - private VerifyCredentialResponse weaverVerify(int slot, byte[] key) { + private VerifyCredentialResponse weaverVerify(IWeaver weaver, int slot, byte[] key) { if (slot == INVALID_WEAVER_SLOT || slot >= mWeaverConfig.slots) { throw new IllegalArgumentException("Invalid slot for weaver"); } @@ -659,7 +684,7 @@ class SyntheticPasswordManager { } final WeaverReadResponse readResponse; try { - readResponse = mWeaver.read(slot, key); + readResponse = weaver.read(slot, key); } catch (RemoteException e) { Slog.e(TAG, "weaver read failed, slot: " + slot, e); return VerifyCredentialResponse.ERROR; @@ -870,14 +895,15 @@ class SyntheticPasswordManager { int slot = loadWeaverSlot(protectorId, userId); destroyState(WEAVER_SLOT_NAME, protectorId, userId); if (slot != INVALID_WEAVER_SLOT) { - if (!isWeaverAvailable()) { + final IWeaver weaver = getWeaverService(); + if (weaver == null) { Slog.e(TAG, "Cannot erase Weaver slot because Weaver is unavailable"); return; } Set<Integer> usedSlots = getUsedWeaverSlots(); if (!usedSlots.contains(slot)) { Slogf.i(TAG, "Erasing Weaver slot %d", slot); - weaverEnroll(slot, null, null); + weaverEnroll(weaver, slot, null, null); mPasswordSlotManager.markSlotDeleted(slot); } else { Slogf.i(TAG, "Weaver slot %d was already reused; not erasing it", slot); @@ -955,13 +981,14 @@ class SyntheticPasswordManager { Slogf.i(TAG, "Creating LSKF-based protector %016x for user %d", protectorId, userId); - if (isWeaverAvailable()) { + final IWeaver weaver = getWeaverService(); + if (weaver != null) { // Weaver is available, so make the protector use it to verify the LSKF. Do this even // if the LSKF is empty, as that gives us support for securely deleting the protector. int weaverSlot = getNextAvailableWeaverSlot(); Slogf.i(TAG, "Enrolling LSKF for user %d into Weaver slot %d", userId, weaverSlot); - byte[] weaverSecret = weaverEnroll(weaverSlot, stretchedLskfToWeaverKey(stretchedLskf), - null); + byte[] weaverSecret = weaverEnroll(weaver, weaverSlot, + stretchedLskfToWeaverKey(stretchedLskf), null); if (weaverSecret == null) { throw new IllegalStateException( "Fail to enroll user password under weaver " + userId); @@ -1048,7 +1075,8 @@ class SyntheticPasswordManager { } return VerifyCredentialResponse.fromGateKeeperResponse(response); } else if (persistentData.type == PersistentData.TYPE_SP_WEAVER) { - if (!isWeaverAvailable()) { + final IWeaver weaver = getWeaverService(); + if (weaver == null) { Slog.e(TAG, "No weaver service to verify SP-based persistent data credential"); return VerifyCredentialResponse.ERROR; } @@ -1056,7 +1084,8 @@ class SyntheticPasswordManager { byte[] stretchedLskf = stretchLskf(userCredential, pwd); int weaverSlot = persistentData.userId; - return weaverVerify(weaverSlot, stretchedLskfToWeaverKey(stretchedLskf)).stripPayload(); + return weaverVerify(weaver, weaverSlot, + stretchedLskfToWeaverKey(stretchedLskf)).stripPayload(); } else { Slog.e(TAG, "persistentData.type must be TYPE_SP_GATEKEEPER or TYPE_SP_WEAVER, but is " + persistentData.type); @@ -1209,7 +1238,7 @@ class SyntheticPasswordManager { TokenData tokenData = new TokenData(); tokenData.mType = type; final byte[] secdiscardable = SecureRandomUtils.randomBytes(SECDISCARDABLE_LENGTH); - if (isWeaverAvailable()) { + if (getWeaverService() != null) { tokenData.weaverSecret = SecureRandomUtils.randomBytes(mWeaverConfig.valueSize); tokenData.secdiscardableOnDisk = SyntheticPasswordCrypto.encrypt(tokenData.weaverSecret, PERSONALIZATION_WEAVER_TOKEN, secdiscardable); @@ -1252,10 +1281,11 @@ class SyntheticPasswordManager { return false; } Slogf.i(TAG, "Creating token-based protector %016x for user %d", tokenHandle, userId); - if (isWeaverAvailable()) { + final IWeaver weaver = getWeaverService(); + if (weaver != null) { int slot = getNextAvailableWeaverSlot(); Slogf.i(TAG, "Using Weaver slot %d for new token-based protector", slot); - if (weaverEnroll(slot, null, tokenData.weaverSecret) == null) { + if (weaverEnroll(weaver, slot, null, tokenData.weaverSecret) == null) { Slog.e(TAG, "Failed to enroll weaver secret when activating token"); return false; } @@ -1344,12 +1374,14 @@ class SyntheticPasswordManager { int weaverSlot = loadWeaverSlot(protectorId, userId); if (weaverSlot != INVALID_WEAVER_SLOT) { // Protector uses Weaver to verify the LSKF - if (!isWeaverAvailable()) { + final IWeaver weaver = getWeaverService(); + if (weaver == null) { Slog.e(TAG, "Protector uses Weaver, but Weaver is unavailable"); result.gkResponse = VerifyCredentialResponse.ERROR; return result; } - result.gkResponse = weaverVerify(weaverSlot, stretchedLskfToWeaverKey(stretchedLskf)); + result.gkResponse = weaverVerify(weaver, weaverSlot, + stretchedLskfToWeaverKey(stretchedLskf)); if (result.gkResponse.getResponseCode() != VerifyCredentialResponse.RESPONSE_OK) { return result; } @@ -1517,12 +1549,13 @@ class SyntheticPasswordManager { } int slotId = loadWeaverSlot(protectorId, userId); if (slotId != INVALID_WEAVER_SLOT) { - if (!isWeaverAvailable()) { + final IWeaver weaver = getWeaverService(); + if (weaver == null) { Slog.e(TAG, "Protector uses Weaver, but Weaver is unavailable"); result.gkResponse = VerifyCredentialResponse.ERROR; return result; } - VerifyCredentialResponse response = weaverVerify(slotId, null); + VerifyCredentialResponse response = weaverVerify(weaver, slotId, null); if (response.getResponseCode() != VerifyCredentialResponse.RESPONSE_OK || response.getGatekeeperHAT() == null) { Slog.e(TAG, diff --git a/services/tests/PackageManagerServiceTests/server/res/raw/install_app2_cert5_rotated_cert6 b/services/tests/PackageManagerServiceTests/server/res/raw/install_app2_cert5_rotated_cert6 Binary files differindex 30bb6478d18d..6feebb8c833c 100644 --- a/services/tests/PackageManagerServiceTests/server/res/raw/install_app2_cert5_rotated_cert6 +++ b/services/tests/PackageManagerServiceTests/server/res/raw/install_app2_cert5_rotated_cert6 diff --git a/services/tests/mockingservicestests/src/com/android/server/display/LocalDisplayAdapterTest.java b/services/tests/mockingservicestests/src/com/android/server/display/LocalDisplayAdapterTest.java index f89f73c98cfd..aa0a2fea1a5a 100644 --- a/services/tests/mockingservicestests/src/com/android/server/display/LocalDisplayAdapterTest.java +++ b/services/tests/mockingservicestests/src/com/android/server/display/LocalDisplayAdapterTest.java @@ -1257,6 +1257,17 @@ public class LocalDisplayAdapterTest { public LocalDisplayAdapter.SurfaceControlProxy getSurfaceControlProxy() { return mSurfaceControlProxy; } + + // Instead of using DisplayDeviceConfig.create(context, physicalDisplayId, isFirstDisplay) + // we should use DisplayDeviceConfig.create(context, isFirstDisplay) for the test to ensure + // that real device DisplayDeviceConfig is not loaded for FakeDisplay and we are getting + // consistent behaviour. Please also note that context passed to this method, is + // mMockContext and values will be loaded from mMockResources. + @Override + public DisplayDeviceConfig createDisplayDeviceConfig(Context context, + long physicalDisplayId, boolean isFirstDisplay) { + return DisplayDeviceConfig.create(context, isFirstDisplay); + } } private class TestListener implements DisplayAdapter.Listener { diff --git a/services/tests/servicestests/src/com/android/server/pm/UserManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/pm/UserManagerServiceTest.java index 4e1196ff60c4..be918ec2637e 100644 --- a/services/tests/servicestests/src/com/android/server/pm/UserManagerServiceTest.java +++ b/services/tests/servicestests/src/com/android/server/pm/UserManagerServiceTest.java @@ -70,6 +70,9 @@ public class UserManagerServiceTest { LocalServices.removeServiceForTest(UserManagerInternal.class); mUserManagerService = new UserManagerService(InstrumentationRegistry.getContext()); + // Put the current user to mUsers. UMS can't find userlist.xml, and fallbackToSingleUserLP. + mUserManagerService.putUserInfo( + new UserInfo(ActivityManager.getCurrentUser(), "Current User", 0)); restrictionsFile = new File(mContext.getCacheDir(), "restrictions.xml"); restrictionsFile.delete(); diff --git a/telephony/java/android/telephony/data/DataCallResponse.java b/telephony/java/android/telephony/data/DataCallResponse.java index b5c1d7d9e5f4..c7f0c5f753db 100644 --- a/telephony/java/android/telephony/data/DataCallResponse.java +++ b/telephony/java/android/telephony/data/DataCallResponse.java @@ -36,8 +36,10 @@ import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.net.InetAddress; import java.util.ArrayList; +import java.util.Collections; import java.util.List; import java.util.Objects; +import java.util.Set; /** * Description of the response of a setup data call connection request. @@ -172,63 +174,57 @@ public final class DataCallResponse implements Parcelable { @Nullable List<InetAddress> dnsAddresses, @Nullable List<InetAddress> gatewayAddresses, @Nullable List<InetAddress> pcscfAddresses, int mtu) { - mCause = cause; - mSuggestedRetryTime = suggestedRetryTime; - mId = id; - mLinkStatus = linkStatus; - mProtocolType = protocolType; - mInterfaceName = (interfaceName == null) ? "" : interfaceName; - mAddresses = (addresses == null) - ? new ArrayList<>() : new ArrayList<>(addresses); - mDnsAddresses = (dnsAddresses == null) - ? new ArrayList<>() : new ArrayList<>(dnsAddresses); - mGatewayAddresses = (gatewayAddresses == null) - ? new ArrayList<>() : new ArrayList<>(gatewayAddresses); - mPcscfAddresses = (pcscfAddresses == null) - ? new ArrayList<>() : new ArrayList<>(pcscfAddresses); - mMtu = mMtuV4 = mMtuV6 = mtu; - mHandoverFailureMode = HANDOVER_FAILURE_MODE_LEGACY; - mPduSessionId = PDU_SESSION_ID_NOT_SET; - mDefaultQos = null; - mQosBearerSessions = new ArrayList<>(); - mSliceInfo = null; - mTrafficDescriptors = new ArrayList<>(); + this(cause, suggestedRetryTime, id, + linkStatus, protocolType, + interfaceName == null ? "" : interfaceName, + addresses == null ? Collections.emptyList() : addresses, + dnsAddresses == null ? Collections.emptyList() : dnsAddresses, + gatewayAddresses == null ? Collections.emptyList() : gatewayAddresses, + pcscfAddresses == null ? Collections.emptyList() : pcscfAddresses, + mtu, mtu /* mtuV4 */, mtu /* mtuV6 */, + HANDOVER_FAILURE_MODE_LEGACY, PDU_SESSION_ID_NOT_SET, + null /* defaultQos */, Collections.emptyList() /* qosBearerSessions */, + null /* sliceInfo */, + Collections.emptyList() /* trafficDescriptors */); } private DataCallResponse(@DataFailureCause int cause, long suggestedRetryTime, int id, @LinkStatus int linkStatus, @ProtocolType int protocolType, - @Nullable String interfaceName, @Nullable List<LinkAddress> addresses, - @Nullable List<InetAddress> dnsAddresses, @Nullable List<InetAddress> gatewayAddresses, - @Nullable List<InetAddress> pcscfAddresses, int mtu, int mtuV4, int mtuV6, + @NonNull String interfaceName, @NonNull List<LinkAddress> addresses, + @NonNull List<InetAddress> dnsAddresses, @NonNull List<InetAddress> gatewayAddresses, + @NonNull List<InetAddress> pcscfAddresses, int mtu, int mtuV4, int mtuV6, @HandoverFailureMode int handoverFailureMode, int pduSessionId, - @Nullable Qos defaultQos, @Nullable List<QosBearerSession> qosBearerSessions, + @Nullable Qos defaultQos, @NonNull List<QosBearerSession> qosBearerSessions, @Nullable NetworkSliceInfo sliceInfo, - @Nullable List<TrafficDescriptor> trafficDescriptors) { + @NonNull List<TrafficDescriptor> trafficDescriptors) { mCause = cause; mSuggestedRetryTime = suggestedRetryTime; mId = id; mLinkStatus = linkStatus; mProtocolType = protocolType; - mInterfaceName = (interfaceName == null) ? "" : interfaceName; - mAddresses = (addresses == null) - ? new ArrayList<>() : new ArrayList<>(addresses); - mDnsAddresses = (dnsAddresses == null) - ? new ArrayList<>() : new ArrayList<>(dnsAddresses); - mGatewayAddresses = (gatewayAddresses == null) - ? new ArrayList<>() : new ArrayList<>(gatewayAddresses); - mPcscfAddresses = (pcscfAddresses == null) - ? new ArrayList<>() : new ArrayList<>(pcscfAddresses); + mInterfaceName = interfaceName; + mAddresses = new ArrayList<>(addresses); + mDnsAddresses = new ArrayList<>(dnsAddresses); + mGatewayAddresses = new ArrayList<>(gatewayAddresses); + mPcscfAddresses = new ArrayList<>(pcscfAddresses); mMtu = mtu; mMtuV4 = mtuV4; mMtuV6 = mtuV6; mHandoverFailureMode = handoverFailureMode; mPduSessionId = pduSessionId; mDefaultQos = defaultQos; - mQosBearerSessions = (qosBearerSessions == null) - ? new ArrayList<>() : new ArrayList<>(qosBearerSessions); + mQosBearerSessions = new ArrayList<>(qosBearerSessions); mSliceInfo = sliceInfo; - mTrafficDescriptors = (trafficDescriptors == null) - ? new ArrayList<>() : new ArrayList<>(trafficDescriptors); + mTrafficDescriptors = new ArrayList<>(trafficDescriptors); + + if (mLinkStatus == LINK_STATUS_ACTIVE + || mLinkStatus == LINK_STATUS_DORMANT) { + Objects.requireNonNull( + mInterfaceName, "Active data calls must be on a valid interface!"); + if (mCause != DataFailCause.NONE) { + throw new IllegalStateException("Active data call must not have a failure!"); + } + } } /** @hide */ @@ -241,24 +237,39 @@ public final class DataCallResponse implements Parcelable { mProtocolType = source.readInt(); mInterfaceName = source.readString(); mAddresses = new ArrayList<>(); - source.readList(mAddresses, LinkAddress.class.getClassLoader(), android.net.LinkAddress.class); + source.readList(mAddresses, + LinkAddress.class.getClassLoader(), + android.net.LinkAddress.class); mDnsAddresses = new ArrayList<>(); - source.readList(mDnsAddresses, InetAddress.class.getClassLoader(), java.net.InetAddress.class); + source.readList(mDnsAddresses, + InetAddress.class.getClassLoader(), + java.net.InetAddress.class); mGatewayAddresses = new ArrayList<>(); - source.readList(mGatewayAddresses, InetAddress.class.getClassLoader(), java.net.InetAddress.class); + source.readList(mGatewayAddresses, + InetAddress.class.getClassLoader(), + java.net.InetAddress.class); mPcscfAddresses = new ArrayList<>(); - source.readList(mPcscfAddresses, InetAddress.class.getClassLoader(), java.net.InetAddress.class); + source.readList(mPcscfAddresses, + InetAddress.class.getClassLoader(), + java.net.InetAddress.class); mMtu = source.readInt(); mMtuV4 = source.readInt(); mMtuV6 = source.readInt(); mHandoverFailureMode = source.readInt(); mPduSessionId = source.readInt(); - mDefaultQos = source.readParcelable(Qos.class.getClassLoader(), android.telephony.data.Qos.class); + mDefaultQos = source.readParcelable(Qos.class.getClassLoader(), + android.telephony.data.Qos.class); mQosBearerSessions = new ArrayList<>(); - source.readList(mQosBearerSessions, QosBearerSession.class.getClassLoader(), android.telephony.data.QosBearerSession.class); - mSliceInfo = source.readParcelable(NetworkSliceInfo.class.getClassLoader(), android.telephony.data.NetworkSliceInfo.class); + source.readList(mQosBearerSessions, + QosBearerSession.class.getClassLoader(), + android.telephony.data.QosBearerSession.class); + mSliceInfo = source.readParcelable( + NetworkSliceInfo.class.getClassLoader(), + android.telephony.data.NetworkSliceInfo.class); mTrafficDescriptors = new ArrayList<>(); - source.readList(mTrafficDescriptors, TrafficDescriptor.class.getClassLoader(), android.telephony.data.TrafficDescriptor.class); + source.readList(mTrafficDescriptors, + TrafficDescriptor.class.getClassLoader(), + android.telephony.data.TrafficDescriptor.class); } /** @@ -322,28 +333,36 @@ public final class DataCallResponse implements Parcelable { * @return A list of addresses of this data connection. */ @NonNull - public List<LinkAddress> getAddresses() { return mAddresses; } + public List<LinkAddress> getAddresses() { + return Collections.unmodifiableList(mAddresses); + } /** * @return A list of DNS server addresses, e.g., "192.0.1.3" or * "192.0.1.11 2001:db8::1". Empty list if no dns server addresses returned. */ @NonNull - public List<InetAddress> getDnsAddresses() { return mDnsAddresses; } + public List<InetAddress> getDnsAddresses() { + return Collections.unmodifiableList(mDnsAddresses); + } /** * @return A list of default gateway addresses, e.g., "192.0.1.3" or * "192.0.1.11 2001:db8::1". Empty list if the addresses represent point to point connections. */ @NonNull - public List<InetAddress> getGatewayAddresses() { return mGatewayAddresses; } + public List<InetAddress> getGatewayAddresses() { + return Collections.unmodifiableList(mGatewayAddresses); + } /** * @return A list of Proxy Call State Control Function address via PCO (Protocol Configuration * Option) for IMS client. */ @NonNull - public List<InetAddress> getPcscfAddresses() { return mPcscfAddresses; } + public List<InetAddress> getPcscfAddresses() { + return Collections.unmodifiableList(mPcscfAddresses); + } /** * @return MTU (maximum transmission unit) in bytes received from network. Zero or negative @@ -404,7 +423,7 @@ public final class DataCallResponse implements Parcelable { */ @NonNull public List<QosBearerSession> getQosBearerSessions() { - return mQosBearerSessions; + return Collections.unmodifiableList(mQosBearerSessions); } /** @@ -420,7 +439,7 @@ public final class DataCallResponse implements Parcelable { */ @NonNull public List<TrafficDescriptor> getTrafficDescriptors() { - return mTrafficDescriptors; + return Collections.unmodifiableList(mTrafficDescriptors); } @NonNull @@ -461,18 +480,6 @@ public final class DataCallResponse implements Parcelable { DataCallResponse other = (DataCallResponse) o; - final boolean isQosBearerSessionsSame = - (mQosBearerSessions == null || other.mQosBearerSessions == null) - ? mQosBearerSessions == other.mQosBearerSessions - : (mQosBearerSessions.size() == other.mQosBearerSessions.size() - && mQosBearerSessions.containsAll(other.mQosBearerSessions)); - - final boolean isTrafficDescriptorsSame = - (mTrafficDescriptors == null || other.mTrafficDescriptors == null) - ? mTrafficDescriptors == other.mTrafficDescriptors - : (mTrafficDescriptors.size() == other.mTrafficDescriptors.size() - && mTrafficDescriptors.containsAll(other.mTrafficDescriptors)); - return mCause == other.mCause && mSuggestedRetryTime == other.mSuggestedRetryTime && mId == other.mId @@ -493,42 +500,20 @@ public final class DataCallResponse implements Parcelable { && mHandoverFailureMode == other.mHandoverFailureMode && mPduSessionId == other.mPduSessionId && Objects.equals(mDefaultQos, other.mDefaultQos) - && isQosBearerSessionsSame + && mQosBearerSessions.size() == other.mQosBearerSessions.size() // non-null + && mQosBearerSessions.containsAll(other.mQosBearerSessions) // non-null && Objects.equals(mSliceInfo, other.mSliceInfo) - && isTrafficDescriptorsSame; + && mTrafficDescriptors.size() == other.mTrafficDescriptors.size() // non-null + && mTrafficDescriptors.containsAll(other.mTrafficDescriptors); // non-null } @Override public int hashCode() { - // Generate order-independent hashes for lists - int addressesHash = mAddresses.stream() - .map(LinkAddress::hashCode) - .mapToInt(Integer::intValue) - .sum(); - int dnsAddressesHash = mDnsAddresses.stream() - .map(InetAddress::hashCode) - .mapToInt(Integer::intValue) - .sum(); - int gatewayAddressesHash = mGatewayAddresses.stream() - .map(InetAddress::hashCode) - .mapToInt(Integer::intValue) - .sum(); - int pcscfAddressesHash = mPcscfAddresses.stream() - .map(InetAddress::hashCode) - .mapToInt(Integer::intValue) - .sum(); - int qosBearerSessionsHash = mQosBearerSessions.stream() - .map(QosBearerSession::hashCode) - .mapToInt(Integer::intValue) - .sum(); - int trafficDescriptorsHash = mTrafficDescriptors.stream() - .map(TrafficDescriptor::hashCode) - .mapToInt(Integer::intValue) - .sum(); return Objects.hash(mCause, mSuggestedRetryTime, mId, mLinkStatus, mProtocolType, - mInterfaceName, addressesHash, dnsAddressesHash, gatewayAddressesHash, - pcscfAddressesHash, mMtu, mMtuV4, mMtuV6, mHandoverFailureMode, mPduSessionId, - mDefaultQos, qosBearerSessionsHash, mSliceInfo, trafficDescriptorsHash); + mInterfaceName, Set.copyOf(mAddresses), Set.copyOf(mDnsAddresses), + Set.copyOf(mGatewayAddresses), Set.copyOf(mPcscfAddresses), mMtu, mMtuV4, mMtuV6, + mHandoverFailureMode, mPduSessionId, mDefaultQos, Set.copyOf(mQosBearerSessions), + mSliceInfo, Set.copyOf(mTrafficDescriptors)); } @Override @@ -616,15 +601,15 @@ public final class DataCallResponse implements Parcelable { private @ProtocolType int mProtocolType; - private String mInterfaceName; + private String mInterfaceName = ""; - private List<LinkAddress> mAddresses; + private List<LinkAddress> mAddresses = Collections.emptyList(); - private List<InetAddress> mDnsAddresses; + private List<InetAddress> mDnsAddresses = Collections.emptyList(); - private List<InetAddress> mGatewayAddresses; + private List<InetAddress> mGatewayAddresses = Collections.emptyList(); - private List<InetAddress> mPcscfAddresses; + private List<InetAddress> mPcscfAddresses = Collections.emptyList(); private int mMtu; @@ -636,11 +621,11 @@ public final class DataCallResponse implements Parcelable { private int mPduSessionId = PDU_SESSION_ID_NOT_SET; - private Qos mDefaultQos; + private @Nullable Qos mDefaultQos; private List<QosBearerSession> mQosBearerSessions = new ArrayList<>(); - private NetworkSliceInfo mSliceInfo; + private @Nullable NetworkSliceInfo mSliceInfo; private List<TrafficDescriptor> mTrafficDescriptors = new ArrayList<>(); @@ -653,7 +638,9 @@ public final class DataCallResponse implements Parcelable { /** * Set data call fail cause. * - * @param cause Data call fail cause. {@link DataFailCause#NONE} indicates no error. + * @param cause Data call fail cause. {@link DataFailCause#NONE} indicates no error, which + * is the only valid value for data calls that are {@link LINK_STATUS_ACTIVE} or + * {@link LINK_STATUS_DORMANT}. * @return The same instance of the builder. */ public @NonNull Builder setCause(@DataFailureCause int cause) { @@ -722,10 +709,13 @@ public final class DataCallResponse implements Parcelable { /** * Set the network interface name. * - * @param interfaceName The network interface name (e.g. "rmnet_data1"). + * @param interfaceName The network interface name (e.g. "rmnet_data1"). This value may not + * be null for valid data calls (those that are {@link LINK_STATUS_ACTIVE} or + * {@link LINK_STATUS_DORMANT}). * @return The same instance of the builder. */ - public @NonNull Builder setInterfaceName(@NonNull String interfaceName) { + public @NonNull Builder setInterfaceName(@Nullable String interfaceName) { + if (interfaceName == null) interfaceName = ""; mInterfaceName = interfaceName; return this; } @@ -737,6 +727,7 @@ public final class DataCallResponse implements Parcelable { * @return The same instance of the builder. */ public @NonNull Builder setAddresses(@NonNull List<LinkAddress> addresses) { + Objects.requireNonNull(addresses); mAddresses = addresses; return this; } @@ -748,6 +739,7 @@ public final class DataCallResponse implements Parcelable { * @return The same instance of the builder. */ public @NonNull Builder setDnsAddresses(@NonNull List<InetAddress> dnsAddresses) { + Objects.requireNonNull(dnsAddresses); mDnsAddresses = dnsAddresses; return this; } @@ -759,6 +751,7 @@ public final class DataCallResponse implements Parcelable { * @return The same instance of the builder. */ public @NonNull Builder setGatewayAddresses(@NonNull List<InetAddress> gatewayAddresses) { + Objects.requireNonNull(gatewayAddresses); mGatewayAddresses = gatewayAddresses; return this; } @@ -771,6 +764,7 @@ public final class DataCallResponse implements Parcelable { * @return The same instance of the builder. */ public @NonNull Builder setPcscfAddresses(@NonNull List<InetAddress> pcscfAddresses) { + Objects.requireNonNull(pcscfAddresses); mPcscfAddresses = pcscfAddresses; return this; } |