diff options
89 files changed, 1452 insertions, 757 deletions
diff --git a/core/api/current.txt b/core/api/current.txt index f5dcf2de4c51..9ebb5068bf19 100644 --- a/core/api/current.txt +++ b/core/api/current.txt @@ -18112,6 +18112,7 @@ package android.graphics.drawable { method public void setThickness(@Px int); method public void setThicknessRatio(@FloatRange(from=0.0f, fromInclusive=false) float); method public void setUseLevel(boolean); + field @FlaggedApi("com.android.graphics.flags.gradient_drawable_shape_rounded_cap") public static final int ARC = 4; // 0x4 field public static final int LINE = 2; // 0x2 field public static final int LINEAR_GRADIENT = 0; // 0x0 field public static final int OVAL = 1; // 0x1 diff --git a/core/java/android/content/pm/PackageManager.java b/core/java/android/content/pm/PackageManager.java index f91b2474fdac..9e91f5944504 100644 --- a/core/java/android/content/pm/PackageManager.java +++ b/core/java/android/content/pm/PackageManager.java @@ -2038,7 +2038,7 @@ public abstract class PackageManager { public static final int INSTALL_SCENARIO_DEFAULT = 0; /** - * Installation scenario providing the fastest “install button to launch" experience possible. + * Installation scenario providing the fastest "install button to launch" experience possible. */ public static final int INSTALL_SCENARIO_FAST = 1; @@ -3585,7 +3585,7 @@ public abstract class PackageManager { /** * Feature for {@link #getSystemAvailableFeatures} and {@link #hasSystemFeature}: The device is - * compatible with Android’s security model. + * compatible with Android's security model. * * <p>See sections 2 and 9 in the * <a href="https://source.android.com/compatibility/android-cdd">Android CDD</a> for more diff --git a/core/java/android/os/CombinedMessageQueue/MessageQueue.java b/core/java/android/os/CombinedMessageQueue/MessageQueue.java index 74972346bf2e..3c03bb5626c8 100644 --- a/core/java/android/os/CombinedMessageQueue/MessageQueue.java +++ b/core/java/android/os/CombinedMessageQueue/MessageQueue.java @@ -130,7 +130,7 @@ public final class MessageQueue { MessageQueue(boolean quitAllowed) { initIsProcessAllowedToUseConcurrent(); - mUseConcurrent = sIsProcessAllowedToUseConcurrent; + mUseConcurrent = sIsProcessAllowedToUseConcurrent && !isInstrumenting(); mQuitAllowed = quitAllowed; mPtr = nativeInit(); mThread = Thread.currentThread(); @@ -202,6 +202,15 @@ public final class MessageQueue { return; } + private static boolean isInstrumenting() { + final ActivityThread activityThread = ActivityThread.currentActivityThread(); + if (activityThread == null) { + return false; + } + final Instrumentation instrumentation = activityThread.getInstrumentation(); + return instrumentation != null && instrumentation.isInstrumenting(); + } + @Override protected void finalize() throws Throwable { try { diff --git a/core/java/android/view/NotificationHeaderView.java b/core/java/android/view/NotificationHeaderView.java index df680c054f56..fe868e1c43be 100644 --- a/core/java/android/view/NotificationHeaderView.java +++ b/core/java/android/view/NotificationHeaderView.java @@ -223,8 +223,14 @@ public class NotificationHeaderView extends RelativeLayout { super.onMeasure(widthMeasureSpec, heightMeasureSpec); if (notificationsRedesignTemplates()) { - mTopLineTranslation = measureCenterTranslation(mTopLineView); - mExpandButtonTranslation = measureCenterTranslation(mExpandButton); + // TODO: b/378660052 - These should never be null in practice, consider using + // requireViewById() in the onFinishInflate. + if (mTopLineView != null) { + mTopLineTranslation = measureCenterTranslation(mTopLineView); + } + if (mExpandButton != null) { + mExpandButtonTranslation = measureCenterTranslation(mExpandButton); + } } } diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java index 0866e0d832b1..c048d7987515 100644 --- a/core/java/android/view/View.java +++ b/core/java/android/view/View.java @@ -66,6 +66,7 @@ import static com.android.internal.util.FrameworkStatsLog.TOUCH_GESTURE_CLASSIFI import static com.android.internal.util.FrameworkStatsLog.TOUCH_GESTURE_CLASSIFIED__CLASSIFICATION__UNKNOWN_CLASSIFICATION; import static com.android.window.flags.Flags.FLAG_DELEGATE_UNHANDLED_DRAGS; import static com.android.window.flags.Flags.FLAG_SUPPORTS_DRAG_ASSISTANT_TO_MULTIWINDOW; +import static com.android.window.flags.Flags.reduceChangedExclusionRectsMsgs; import static java.lang.Math.max; @@ -247,6 +248,7 @@ import java.util.HashMap; import java.util.List; import java.util.Locale; import java.util.Map; +import java.util.Objects; import java.util.concurrent.CopyOnWriteArrayList; import java.util.concurrent.Executor; import java.util.concurrent.atomic.AtomicInteger; @@ -12888,15 +12890,20 @@ public class View implements Drawable.Callback, KeyEvent.Callback, if (rects.isEmpty() && mListenerInfo == null) return; final ListenerInfo info = getListenerInfo(); + final boolean rectsChanged = !reduceChangedExclusionRectsMsgs() + || !Objects.equals(info.mSystemGestureExclusionRects, rects); if (info.mSystemGestureExclusionRects != null) { - info.mSystemGestureExclusionRects.clear(); - info.mSystemGestureExclusionRects.addAll(rects); + if (rectsChanged) { + info.mSystemGestureExclusionRects.clear(); + info.mSystemGestureExclusionRects.addAll(rects); + } } else { info.mSystemGestureExclusionRects = new ArrayList<>(rects); } - - updatePositionUpdateListener(); - postUpdate(this::updateSystemGestureExclusionRects); + if (rectsChanged) { + updatePositionUpdateListener(); + postUpdate(this::updateSystemGestureExclusionRects); + } } private void updatePositionUpdateListener() { diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java index cb9832282dc4..9498407fb33b 100644 --- a/core/java/android/view/ViewRootImpl.java +++ b/core/java/android/view/ViewRootImpl.java @@ -136,6 +136,7 @@ import static com.android.text.flags.Flags.disableHandwritingInitiatorForIme; import static com.android.window.flags.Flags.enableBufferTransformHintFromDisplay; import static com.android.window.flags.Flags.enableWindowContextResourcesUpdateOnConfigChange; import static com.android.window.flags.Flags.predictiveBackSwipeEdgeNoneApi; +import static com.android.window.flags.Flags.reduceChangedExclusionRectsMsgs; import static com.android.window.flags.Flags.setScPropertiesInClient; import android.Manifest; @@ -6074,8 +6075,12 @@ public final class ViewRootImpl implements ViewParent, } void updateSystemGestureExclusionRectsForView(View view) { + boolean msgInQueue = reduceChangedExclusionRectsMsgs() + && mGestureExclusionTracker.isWaitingForComputeChanges(); mGestureExclusionTracker.updateRectsForView(view); - mHandler.sendEmptyMessage(MSG_SYSTEM_GESTURE_EXCLUSION_CHANGED); + if (!msgInQueue) { + mHandler.sendEmptyMessage(MSG_SYSTEM_GESTURE_EXCLUSION_CHANGED); + } } void systemGestureExclusionChanged() { @@ -6119,8 +6124,12 @@ public final class ViewRootImpl implements ViewParent, * the root's view hierarchy. */ public void setRootSystemGestureExclusionRects(@NonNull List<Rect> rects) { + boolean msgInQueue = reduceChangedExclusionRectsMsgs() + && mGestureExclusionTracker.isWaitingForComputeChanges(); mGestureExclusionTracker.setRootRects(rects); - mHandler.sendEmptyMessage(MSG_SYSTEM_GESTURE_EXCLUSION_CHANGED); + if (!msgInQueue) { + mHandler.sendEmptyMessage(MSG_SYSTEM_GESTURE_EXCLUSION_CHANGED); + } } /** diff --git a/core/java/android/view/ViewRootRectTracker.java b/core/java/android/view/ViewRootRectTracker.java index 152729b8d1d8..0bef3255f1dc 100644 --- a/core/java/android/view/ViewRootRectTracker.java +++ b/core/java/android/view/ViewRootRectTracker.java @@ -20,6 +20,7 @@ import android.annotation.NonNull; import android.annotation.Nullable; import android.graphics.Rect; +import com.android.internal.annotations.VisibleForTesting; import com.android.internal.util.Preconditions; import java.lang.ref.WeakReference; @@ -32,8 +33,10 @@ import java.util.function.Function; /** * Abstract class to track a collection of rects reported by the views under the same * {@link ViewRootImpl}. + * @hide */ -class ViewRootRectTracker { +@VisibleForTesting +public class ViewRootRectTracker { private final Function<View, List<Rect>> mRectCollector; private boolean mViewsChanged = false; private boolean mRootRectsChanged = false; @@ -41,11 +44,18 @@ class ViewRootRectTracker { private List<ViewInfo> mViewInfos = new ArrayList<>(); private List<Rect> mRects = Collections.emptyList(); + // Keeps track of whether updateRectsForView has been called but there was no subsequent call + // on computeChanges yet. Since updateRectsForView is called when sending a message and + // computeChanges when it is received, this tracks whether such message is in the queue already + private boolean mWaitingForComputeChanges = false; + /** * @param rectCollector given a view returns a list of the rects of interest for this * ViewRootRectTracker + * @hide */ - ViewRootRectTracker(Function<View, List<Rect>> rectCollector) { + @VisibleForTesting + public ViewRootRectTracker(Function<View, List<Rect>> rectCollector) { mRectCollector = rectCollector; } @@ -70,6 +80,7 @@ class ViewRootRectTracker { mViewInfos.add(new ViewInfo(view)); mViewsChanged = true; } + mWaitingForComputeChanges = true; } /** @@ -92,6 +103,7 @@ class ViewRootRectTracker { * @return {@code true} if there were changes, {@code false} otherwise. */ public boolean computeChanges() { + mWaitingForComputeChanges = false; boolean changed = mRootRectsChanged; final Iterator<ViewInfo> i = mViewInfos.iterator(); final List<Rect> rects = new ArrayList<>(mRootRects); @@ -121,6 +133,10 @@ class ViewRootRectTracker { return false; } + public boolean isWaitingForComputeChanges() { + return mWaitingForComputeChanges; + } + /** * Returns a List of all Rects from all visible Views in the global (root) coordinate system. * This list is only updated when calling {@link #computeChanges()} or @@ -140,6 +156,7 @@ class ViewRootRectTracker { Preconditions.checkNotNull(rects, "rects must not be null"); mRootRects = rects; mRootRectsChanged = true; + mWaitingForComputeChanges = true; } @NonNull diff --git a/core/java/android/window/flags/lse_desktop_experience.aconfig b/core/java/android/window/flags/lse_desktop_experience.aconfig index 891c5e382a58..3e3b8e100d6d 100644 --- a/core/java/android/window/flags/lse_desktop_experience.aconfig +++ b/core/java/android/window/flags/lse_desktop_experience.aconfig @@ -196,6 +196,16 @@ flag { } flag { + name: "enable_camera_compat_for_desktop_windowing_opt_out" + namespace: "lse_desktop_experience" + description: "Whether to allow developers to opt-out of Camera Compat treatment to fixed-orientation apps in desktop windowing mode" + bug: "328616176" + metadata { + purpose: PURPOSE_BUGFIX + } +} + +flag { name: "enable_task_stack_observer_in_shell" namespace: "lse_desktop_experience" description: "Introduces a new observer in shell to track the task stack." diff --git a/core/java/android/window/flags/windowing_frontend.aconfig b/core/java/android/window/flags/windowing_frontend.aconfig index 60f2c811dd1f..a4d128fa3caf 100644 --- a/core/java/android/window/flags/windowing_frontend.aconfig +++ b/core/java/android/window/flags/windowing_frontend.aconfig @@ -428,6 +428,17 @@ flag { } flag { + name: "reduce_changed_exclusion_rects_msgs" + namespace: "windowing_frontend" + description: "Don't send MSG_SYSTEM_GESTURE_EXCLUSION_CHANGED when there is no change" + bug: "388231176" + is_fixed_read_only: true + metadata { + purpose: PURPOSE_BUGFIX + } +} + +flag { name: "keep_app_window_hide_while_locked" namespace: "windowing_frontend" description: "Do not let app window visible while device is locked" diff --git a/core/java/com/android/internal/os/LongArrayMultiStateCounter.java b/core/java/com/android/internal/os/LongArrayMultiStateCounter.java index 2931bd2c83dd..fe616e085488 100644 --- a/core/java/com/android/internal/os/LongArrayMultiStateCounter.java +++ b/core/java/com/android/internal/os/LongArrayMultiStateCounter.java @@ -16,6 +16,7 @@ package com.android.internal.os; +import android.annotation.Nullable; import android.os.Parcel; import android.os.Parcelable; import android.ravenwood.annotation.RavenwoodKeepWholeClass; @@ -155,8 +156,9 @@ public final class LongArrayMultiStateCounter implements Parcelable { /** * Adds the supplied values to the current accumulated values in the counter. + * Null `values` parameter is interpreted as an array of zeros. */ - public void incrementValues(long[] values, long timestampMs) { + public void incrementValues(@Nullable long[] values, long timestampMs) { native_incrementValues(mNativeObject, values, timestampMs); } diff --git a/core/java/com/android/internal/os/LongArrayMultiStateCounter_ravenwood.java b/core/java/com/android/internal/os/LongArrayMultiStateCounter_ravenwood.java index 7030d8e84b70..4f5f37d0640e 100644 --- a/core/java/com/android/internal/os/LongArrayMultiStateCounter_ravenwood.java +++ b/core/java/com/android/internal/os/LongArrayMultiStateCounter_ravenwood.java @@ -16,6 +16,7 @@ package com.android.internal.os; +import android.annotation.Nullable; import android.os.BadParcelableException; import android.os.Parcel; import android.ravenwood.annotation.RavenwoodKeepWholeClass; @@ -147,10 +148,12 @@ class LongArrayMultiStateCounter_ravenwood { mLastUpdateTimestampMs = timestampMs; } - public void incrementValues(long[] delta, long timestampMs) { + public void incrementValues(@Nullable long[] delta, long timestampMs) { long[] values = Arrays.copyOf(mValues, mValues.length); - for (int i = 0; i < mArrayLength; i++) { - values[i] += delta[i]; + if (delta != null) { + for (int i = 0; i < mArrayLength; i++) { + values[i] += delta[i]; + } } updateValue(values, timestampMs); } @@ -304,7 +307,8 @@ class LongArrayMultiStateCounter_ravenwood { getInstance(targetInstanceId).copyStatesFrom(getInstance(sourceInstanceId)); } - public static void native_incrementValues(long instanceId, long[] delta, long timestampMs) { + public static void native_incrementValues(long instanceId, @Nullable long[] delta, + long timestampMs) { getInstance(instanceId).incrementValues(delta, timestampMs); } diff --git a/core/java/com/android/internal/pm/pkg/component/ParsedComponentImpl.java b/core/java/com/android/internal/pm/pkg/component/ParsedComponentImpl.java index 7ee22f30ace0..69c04807c604 100644 --- a/core/java/com/android/internal/pm/pkg/component/ParsedComponentImpl.java +++ b/core/java/com/android/internal/pm/pkg/component/ParsedComponentImpl.java @@ -157,7 +157,7 @@ public abstract class ParsedComponentImpl implements ParsedComponent, Parcelable @Override public void writeToParcel(Parcel dest, int flags) { - dest.writeString(this.name); + sForInternedString.parcel(this.name, dest, flags); dest.writeInt(this.getIcon()); dest.writeInt(this.getLabelRes()); dest.writeCharSequence(this.getNonLocalizedLabel()); @@ -175,7 +175,7 @@ public abstract class ParsedComponentImpl implements ParsedComponent, Parcelable // We use the boot classloader for all classes that we load. final ClassLoader boot = Object.class.getClassLoader(); //noinspection ConstantConditions - this.name = in.readString(); + this.name = sForInternedString.unparcel(in); this.icon = in.readInt(); this.labelRes = in.readInt(); this.nonLocalizedLabel = in.readCharSequence(); diff --git a/core/java/com/android/internal/protolog/ProtoLogCommandHandler.java b/core/java/com/android/internal/protolog/ProtoLogCommandHandler.java index 82d8d3431a9d..6d4a40899a65 100644 --- a/core/java/com/android/internal/protolog/ProtoLogCommandHandler.java +++ b/core/java/com/android/internal/protolog/ProtoLogCommandHandler.java @@ -145,11 +145,11 @@ public class ProtoLogCommandHandler extends ShellCommand { switch (cmd) { case "enable" -> { - mProtoLogConfigurationService.enableProtoLogToLogcat(processGroups()); + mProtoLogConfigurationService.enableProtoLogToLogcat(pw, processGroups()); return 0; } case "disable" -> { - mProtoLogConfigurationService.disableProtoLogToLogcat(processGroups()); + mProtoLogConfigurationService.disableProtoLogToLogcat(pw, processGroups()); return 0; } default -> { diff --git a/core/java/com/android/internal/protolog/ProtoLogConfigurationService.java b/core/java/com/android/internal/protolog/ProtoLogConfigurationService.java index d65aaae7deaa..a19690bbd0e4 100644 --- a/core/java/com/android/internal/protolog/ProtoLogConfigurationService.java +++ b/core/java/com/android/internal/protolog/ProtoLogConfigurationService.java @@ -18,6 +18,8 @@ package com.android.internal.protolog; import android.annotation.NonNull; +import java.io.PrintWriter; + public interface ProtoLogConfigurationService extends IProtoLogConfigurationService { /** * Get the list of groups clients have registered to the protolog service. @@ -37,11 +39,11 @@ public interface ProtoLogConfigurationService extends IProtoLogConfigurationServ * Enable logging target groups to logcat. * @param groups we want to enable logging them to logcat for. */ - void enableProtoLogToLogcat(@NonNull String... groups); + void enableProtoLogToLogcat(@NonNull PrintWriter pw, @NonNull String... groups); /** * Disable logging target groups to logcat. * @param groups we want to disable from being logged to logcat. */ - void disableProtoLogToLogcat(@NonNull String... groups); + void disableProtoLogToLogcat(@NonNull PrintWriter pw, @NonNull String... groups); } diff --git a/core/java/com/android/internal/protolog/ProtoLogConfigurationServiceImpl.java b/core/java/com/android/internal/protolog/ProtoLogConfigurationServiceImpl.java index f83359dddfcc..ac1022ff1e0f 100644 --- a/core/java/com/android/internal/protolog/ProtoLogConfigurationServiceImpl.java +++ b/core/java/com/android/internal/protolog/ProtoLogConfigurationServiceImpl.java @@ -44,6 +44,7 @@ import java.io.FileDescriptor; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.IOException; +import java.io.PrintWriter; import java.util.HashMap; import java.util.HashSet; import java.util.Map; @@ -183,8 +184,8 @@ public class ProtoLogConfigurationServiceImpl extends IProtoLogConfigurationServ * @param groups we want to enable logging them to logcat for. */ @Override - public void enableProtoLogToLogcat(@NonNull String... groups) { - toggleProtoLogToLogcat(true, groups); + public void enableProtoLogToLogcat(@NonNull PrintWriter pw, @NonNull String... groups) { + toggleProtoLogToLogcat(pw, true, groups); } /** @@ -192,8 +193,8 @@ public class ProtoLogConfigurationServiceImpl extends IProtoLogConfigurationServ * @param groups we want to disable from being logged to logcat. */ @Override - public void disableProtoLogToLogcat(@NonNull String... groups) { - toggleProtoLogToLogcat(false, groups); + public void disableProtoLogToLogcat(@NonNull PrintWriter pw, @NonNull String... groups) { + toggleProtoLogToLogcat(pw, false, groups); } /** @@ -249,7 +250,9 @@ public class ProtoLogConfigurationServiceImpl extends IProtoLogConfigurationServ } } - private void toggleProtoLogToLogcat(boolean enabled, @NonNull String[] groups) { + private void toggleProtoLogToLogcat( + @NonNull PrintWriter pw, boolean enabled, @NonNull String[] groups + ) { final var clientToGroups = new HashMap<IProtoLogClient, Set<String>>(); for (String group : groups) { @@ -257,8 +260,10 @@ public class ProtoLogConfigurationServiceImpl extends IProtoLogConfigurationServ if (clients == null) { // No clients associated to this group - Log.w(LOG_TAG, "Attempting to toggle log to logcat for group " + group - + " with no registered clients."); + var warning = "Attempting to toggle log to logcat for group " + group + + " with no registered clients. This is a no-op."; + Log.w(LOG_TAG, warning); + pw.println("WARNING: " + warning); continue; } @@ -270,8 +275,14 @@ public class ProtoLogConfigurationServiceImpl extends IProtoLogConfigurationServ for (IProtoLogClient client : clientToGroups.keySet()) { try { - client.toggleLogcat(enabled, clientToGroups.get(client).toArray(new String[0])); + final var clientGroups = clientToGroups.get(client).toArray(new String[0]); + pw.println("Toggling logcat logging for client " + client.toString() + + " to " + enabled + " for groups: [" + + String.join(", ", clientGroups) + "]"); + client.toggleLogcat(enabled, clientGroups); + pw.println("- Done"); } catch (RemoteException e) { + pw.println("- Failed"); throw new RuntimeException( "Failed to toggle logcat status for groups on client", e); } diff --git a/core/jni/com_android_internal_os_LongArrayMultiStateCounter.cpp b/core/jni/com_android_internal_os_LongArrayMultiStateCounter.cpp index 7ffe0ed7c6cd..8f36ecb8b01a 100644 --- a/core/jni/com_android_internal_os_LongArrayMultiStateCounter.cpp +++ b/core/jni/com_android_internal_os_LongArrayMultiStateCounter.cpp @@ -97,7 +97,13 @@ static void native_updateValues(JNIEnv *env, jclass, jlong nativePtr, jlongArray static void native_incrementValues(JNIEnv *env, jclass, jlong nativePtr, jlongArray values, jlong timestamp) { auto *counter = reinterpret_cast<LongArrayMultiStateCounter *>(nativePtr); - counter->incrementValue(JavaUint64Array(env, values), timestamp); + if (values != nullptr) { + counter->incrementValue(JavaUint64Array(env, values), timestamp); + } else { + // Pass an empty Uint64Array, which is equivalent to an array of zeros. + // This is done to ensure that the timestamp is still updated in the counter. + counter->incrementValue(Uint64Array(), timestamp); + } } static void native_addCounts(JNIEnv *env, jclass, jlong nativePtr, jlongArray values) { diff --git a/core/proto/OWNERS b/core/proto/OWNERS index b51f72dee260..aa8f8419ac46 100644 --- a/core/proto/OWNERS +++ b/core/proto/OWNERS @@ -5,7 +5,6 @@ joeo@google.com singhtejinder@google.com yanmin@google.com yaochen@google.com -yro@google.com zhouwenjie@google.com # Frameworks diff --git a/core/res/res/drawable/progress_ring_watch.xml b/core/res/res/drawable/progress_ring_watch.xml index 8250ee600a8f..2e65ff13b3de 100644 --- a/core/res/res/drawable/progress_ring_watch.xml +++ b/core/res/res/drawable/progress_ring_watch.xml @@ -16,27 +16,27 @@ --> <rotate xmlns:android="http://schemas.android.com/apk/res/android" - android:fromDegrees = "270" - android:toDegrees="270" - android:pivotX="50%" - android:pivotY="50%" > + android:fromDegrees="270" + android:toDegrees="270"> <layer-list> <item> <shape - android:shape="ring" - android:innerRadiusRatio="@dimen/progressbar_inner_radius_ratio" + android:shape="arc" + android:useLevel="false" android:thickness="@dimen/progressbar_thickness" - android:useLevel="false"> - <solid android:color="@color/materialColorSurfaceContainer"/> + android:innerRadiusRatio="@dimen/progressbar_inner_radius_ratio" + android:strokeCap="round"> + <solid android:color="@*android:color/materialColorSurfaceContainer"/> </shape> </item> <item> <shape - android:shape="ring" - android:innerRadiusRatio="@dimen/progressbar_inner_radius_ratio" + android:shape="arc" + android:useLevel="true" android:thickness="@dimen/progressbar_thickness" - android:useLevel="true"> - <solid android:color="@color/materialColorPrimary"/> + android:innerRadiusRatio="@dimen/progressbar_inner_radius_ratio" + android:strokeCap="round"> + <solid android:color="@*android:color/materialColorPrimary"/> </shape> </item> </layer-list> diff --git a/core/res/res/values/attrs.xml b/core/res/res/values/attrs.xml index 66111785af4f..d2c993aecb0d 100644 --- a/core/res/res/values/attrs.xml +++ b/core/res/res/values/attrs.xml @@ -6934,6 +6934,8 @@ <enum name="line" value="2" /> <!-- Ring shape. --> <enum name="ring" value="3" /> + <!-- ARC shape. --> + <enum name="arc" value="4"/> </attr> <!-- Inner radius of the ring expressed as a ratio of the ring's width. For instance, if innerRadiusRatio=9, then the inner radius equals the ring's width divided by 9. @@ -6966,6 +6968,12 @@ <attr name="opticalInsetRight" /> <!-- Bottom optical inset. --> <attr name="opticalInsetBottom" /> + <!-- Attributes that customize the stroke line cap. @hide --> + <attr name="strokeCap" format="enum"> + <enum name="butt" value="0"/> + <enum name="round" value="1"/> + <enum name="square" value="2"/> + </attr> </declare-styleable> <!-- Used to specify the size of the shape for GradientDrawable. --> diff --git a/core/res/res/values/dimens_watch.xml b/core/res/res/values/dimens_watch.xml index 2aae98715973..7462b733b0ae 100644 --- a/core/res/res/values/dimens_watch.xml +++ b/core/res/res/values/dimens_watch.xml @@ -52,7 +52,7 @@ <dimen name="primary_content_alpha_device_default">0.38</dimen> <!-- values for wear material3 progress bar(progress indicator) --> - <item name="progressbar_inner_radius_ratio" format="float" type="dimen">2.12</item> + <item name="progressbar_inner_radius_ratio" format="float" type="dimen">2</item> <dimen name="progressbar_thickness">8dp</dimen> <dimen name="progressbar_elevation">0.1dp</dimen> diff --git a/core/tests/coretests/src/android/view/ViewRootRectTrackerTest.java b/core/tests/coretests/src/android/view/ViewRootRectTrackerTest.java new file mode 100644 index 000000000000..c66e10079bc8 --- /dev/null +++ b/core/tests/coretests/src/android/view/ViewRootRectTrackerTest.java @@ -0,0 +1,70 @@ +/* + * Copyright (C) 2025 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.view; + +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; +import static org.mockito.Mockito.mock; +import static org.testng.AssertJUnit.assertEquals; + +import android.graphics.Rect; + +import androidx.test.ext.junit.runners.AndroidJUnit4; +import androidx.test.filters.SmallTest; + +import com.google.common.collect.Lists; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; + +import java.util.Collections; +import java.util.List; + +@SmallTest +@RunWith(AndroidJUnit4.class) +public class ViewRootRectTrackerTest { + private ViewRootRectTracker mTracker; + private final List<Rect> mRects = Lists.newArrayList(new Rect(0, 0, 5, 5), + new Rect(5, 5, 10, 10)); + + @Before + public void setUp() { + mTracker = new ViewRootRectTracker(v -> Collections.emptyList()); + } + + @Test + public void setRootRectsAndComputeTest() { + mTracker.setRootRects(mRects); + mTracker.computeChanges(); + assertEquals(mRects, mTracker.getLastComputedRects()); + } + + @Test + public void waitingForComputeChangesTest() { + mTracker.setRootRects(mRects); + assertTrue(mTracker.isWaitingForComputeChanges()); + mTracker.computeChangedRects(); + assertFalse(mTracker.isWaitingForComputeChanges()); + + View mockView = mock(View.class); + mTracker.updateRectsForView(mockView); + assertTrue(mTracker.isWaitingForComputeChanges()); + mTracker.computeChangedRects(); + assertFalse(mTracker.isWaitingForComputeChanges()); + } +} diff --git a/graphics/java/android/framework_graphics.aconfig b/graphics/java/android/framework_graphics.aconfig index a63cbee4d707..fdbee3ccbebe 100644 --- a/graphics/java/android/framework_graphics.aconfig +++ b/graphics/java/android/framework_graphics.aconfig @@ -42,3 +42,11 @@ flag { description: "Add DISPLAY_BT2020 ColorSpace support" bug: "344038816" } + +flag { + name: "gradient_drawable_shape_rounded_cap" + is_fixed_read_only: true + namespace: "core_graphics" + description: "Make GradientDrawable support drawing ring with rounded stroke cap." + bug: "380000245" +} diff --git a/graphics/java/android/graphics/drawable/GradientDrawable.java b/graphics/java/android/graphics/drawable/GradientDrawable.java index 29d033e64aea..ff1dc93d787b 100644 --- a/graphics/java/android/graphics/drawable/GradientDrawable.java +++ b/graphics/java/android/graphics/drawable/GradientDrawable.java @@ -16,7 +16,11 @@ package android.graphics.drawable; +import static com.android.graphics.flags.Flags.FLAG_GRADIENT_DRAWABLE_SHAPE_ROUNDED_CAP; +import static com.android.graphics.flags.Flags.gradientDrawableShapeRoundedCap; + import android.annotation.ColorInt; +import android.annotation.FlaggedApi; import android.annotation.FloatRange; import android.annotation.IntDef; import android.annotation.NonNull; @@ -125,8 +129,14 @@ public class GradientDrawable extends Drawable { */ public static final int RING = 3; + /** + * Shape is an arc. + */ + @FlaggedApi(FLAG_GRADIENT_DRAWABLE_SHAPE_ROUNDED_CAP) + public static final int ARC = 4; + /** @hide */ - @IntDef({RECTANGLE, OVAL, LINE, RING}) + @IntDef({RECTANGLE, OVAL, LINE, RING, ARC}) @Retention(RetentionPolicy.SOURCE) public @interface Shape {} @@ -167,6 +177,17 @@ public class GradientDrawable extends Drawable { @Retention(RetentionPolicy.SOURCE) public @interface RadiusType {} + private static final int BUTT = 0; + + private static final int ROUND = 1; + + private static final int SQUARE = 2; + + /** @hide */ + @IntDef({BUTT, ROUND, SQUARE}) + @Retention(RetentionPolicy.SOURCE) + public @interface StrokeCap {} + private static final float DEFAULT_INNER_RADIUS_RATIO = 3.0f; private static final float DEFAULT_THICKNESS_RATIO = 9.0f; @@ -191,6 +212,8 @@ public class GradientDrawable extends Drawable { private boolean mMutated; private Path mRingPath; private boolean mPathIsDirty = true; + private Path mArcPath; + private Path mArcOutlinePath; /** Current gradient radius, valid when {@link #mGradientIsDirty} is false. */ private float mGradientRadius; @@ -850,6 +873,55 @@ public class GradientDrawable extends Drawable { } break; } + case ARC: + if (gradientDrawableShapeRoundedCap()) { + // TODO(b/394988176): Consider applying ARC drawing logic to RING shape. + float centerX = mRect.centerX(); + float centerY = mRect.centerY(); + float thickness = + st.mThickness != -1 ? st.mThickness + : mRect.width() / st.mThicknessRatio; + float radius = st.mInnerRadius != -1 ? st.mInnerRadius + : mRect.width() / st.mInnerRadiusRatio; + radius -= thickness; + float sweep = st.mUseLevelForShape ? (360.0f * getLevel() / 10000.0f) : 360f; + mRect.set(centerX - radius, centerY - radius, centerX + radius, + centerY + radius); + + // Prepare paint. Set style to STROKE for purpose of drawing line ARC. + mFillPaint.setStyle(Paint.Style.STROKE); + mFillPaint.setStrokeWidth(thickness); + mFillPaint.setStrokeCap(getStrokeLineCapForPaint(st.mStrokeCap)); + canvas.drawArc(mRect, 0.0f, sweep, /* useCenter= */ false, mFillPaint); + + if (haveStroke) { + if (mArcPath == null) { + mArcPath = new Path(); + } else { + mArcPath.reset(); + } + if (mArcOutlinePath == null) { + mArcOutlinePath = new Path(); + } else { + mArcOutlinePath.reset(); + } + if (sweep == 360f) { + mArcPath.addOval(mRect, Path.Direction.CW); + } else { + mArcPath.arcTo(mRect, 0.0f, sweep, /* forceMoveTo= */ false); + } + + // The arc path doesn't have width. So, to get the outline of the result arc + // shape, we need to apply the paint effect to the path; then use the + // output as the result outline. + mFillPaint.getFillPath(mArcPath, mArcOutlinePath); + canvas.drawPath(mArcOutlinePath, mStrokePaint); + } + + // Restore to FILL + mFillPaint.setStyle(Paint.Style.FILL); + break; + } case RING: Path path = buildRing(st); canvas.drawPath(path, mFillPaint); @@ -993,6 +1065,36 @@ public class GradientDrawable extends Drawable { } /** + * Return current drawable's stroke line cap. Note that this is only respected when drawable is + * {@link Shape#ARC}. + * + * @return the {@link StrokeCap} of current drawable. + * @attr ref android.R.styleable#GradientDrawable_strokeCap + * @see #setStrokeCap(int) + * + * @hide + */ + @StrokeCap + public int getStrokeCap() { + return mGradientState.mStrokeCap; + } + + /** + * Configure the stroke line cap type that drawable will use while drawing. Note that this is + * only respected when drawable is {@link Shape#ARC}. + * + * @param strokeCapType the stroke line cap type that the drawable will use while drawing. + * @attr ref android.R.styleable#GradientDrawable_strokeCap + * @see #getStrokeCap + * + * @hide + */ + public void setStrokeCap(@StrokeCap int strokeCapType) { + mGradientState.mStrokeCap = strokeCapType; + invalidateSelf(); + } + + /** * Configure the padding of the gradient shape * @param left Left padding of the gradient shape * @param top Top padding of the gradient shape @@ -1066,6 +1168,15 @@ public class GradientDrawable extends Drawable { return ringPath; } + private Paint.Cap getStrokeLineCapForPaint(@StrokeCap int strokeLineCap) { + return switch (strokeLineCap) { + case BUTT -> Paint.Cap.BUTT; + case ROUND -> Paint.Cap.ROUND; + case SQUARE -> Paint.Cap.SQUARE; + default -> Paint.Cap.SQUARE; + }; + } + /** * Changes this drawable to use a single color instead of a gradient. * <p> @@ -1484,7 +1595,7 @@ public class GradientDrawable extends Drawable { state.mShape = a.getInt(R.styleable.GradientDrawable_shape, state.mShape); state.mDither = a.getBoolean(R.styleable.GradientDrawable_dither, state.mDither); - if (state.mShape == RING) { + if (state.mShape == RING || state.mShape == ARC) { state.mInnerRadius = a.getDimensionPixelSize( R.styleable.GradientDrawable_innerRadius, state.mInnerRadius); @@ -1503,6 +1614,9 @@ public class GradientDrawable extends Drawable { state.mUseLevelForShape = a.getBoolean( R.styleable.GradientDrawable_useLevel, state.mUseLevelForShape); + + state.mStrokeCap = a.getInt( + R.styleable.GradientDrawable_strokeCap, state.mStrokeCap); } final int tintMode = a.getInt(R.styleable.GradientDrawable_tintMode, -1); @@ -2045,6 +2159,9 @@ public class GradientDrawable extends Drawable { public int mInnerRadius = -1; @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 124050218) public int mThickness = -1; + @UnsupportedAppUsage(trackingBug = 380000245) + @StrokeCap public int mStrokeCap = ROUND; + public boolean mDither = false; public Insets mOpticalInsets = Insets.NONE; diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java index f6a2c8d9695e..305fcdd5fb7d 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java @@ -912,7 +912,7 @@ public class BubbleController implements ConfigurationChangeListener, // TODO(b/393172431) : Utilise DragZoneFactory once it is ready final int bubbleBarDropZoneSideSize = getContext().getResources().getDimensionPixelSize( R.dimen.bubble_bar_drop_zone_side_size); - int top = t - bubbleBarDropZoneSideSize; + int top = b - bubbleBarDropZoneSideSize; result.put(BubbleBarLocation.LEFT, new Rect(l, top, l + bubbleBarDropZoneSideSize, b)); result.put(BubbleBarLocation.RIGHT, diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarLayerView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarLayerView.java index e3b0872df593..29837dc04423 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarLayerView.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarLayerView.java @@ -135,6 +135,7 @@ public class BubbleBarLayerView extends FrameLayout /** Shows the expanded view drop target at the requested {@link BubbleBarLocation location} */ public void showBubbleBarExtendedViewDropTarget(@NonNull BubbleBarLocation bubbleBarLocation) { + setVisibility(VISIBLE); mBubbleExpandedViewPinController.showDropTarget(bubbleBarLocation); } diff --git a/packages/SettingsLib/AndroidManifest.xml b/packages/SettingsLib/AndroidManifest.xml index 412ff29aec32..473b7eb07666 100644 --- a/packages/SettingsLib/AndroidManifest.xml +++ b/packages/SettingsLib/AndroidManifest.xml @@ -21,6 +21,13 @@ <uses-permission android:name="android.permission.READ_DEVICE_CONFIG" /> <application> + <activity + android:name=".users.CreateUserActivity" + android:excludeFromRecents="true" + android:exported="false" + android:finishOnCloseSystemDialogs="true" + android:launchMode="singleInstance" + android:theme="@style/Theme.Transparent"/> </application> </manifest> diff --git a/packages/SettingsLib/IllustrationPreference/AndroidManifest.xml b/packages/SettingsLib/IllustrationPreference/AndroidManifest.xml index a0d10c383445..56b05159a30f 100644 --- a/packages/SettingsLib/IllustrationPreference/AndroidManifest.xml +++ b/packages/SettingsLib/IllustrationPreference/AndroidManifest.xml @@ -18,6 +18,6 @@ <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.android.settingslib.widget.preference.illustration"> - <uses-sdk android:minSdkVersion="28" /> + <uses-sdk android:minSdkVersion="30" /> </manifest> diff --git a/packages/SettingsLib/IllustrationPreference/res/values/strings.xml b/packages/SettingsLib/IllustrationPreference/res/values/strings.xml new file mode 100644 index 000000000000..3a8aaf8b5092 --- /dev/null +++ b/packages/SettingsLib/IllustrationPreference/res/values/strings.xml @@ -0,0 +1,27 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + Copyright (C) 2025 The Android Open Source Project + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + --> + +<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> + <!-- Label for an accessibility action that starts an animation [CHAR LIMIT=30] --> + <string name="settingslib_action_label_resume">resume</string> + <!-- Label for an accessibility action that stops an animation [CHAR LIMIT=30] --> + <string name="settingslib_action_label_pause">pause</string> + <!-- Label for an accessibility action that stops an animation [CHAR LIMIT=50] --> + <string name="settingslib_state_animation_playing">Animation playing</string> + <!-- Label for an accessibility action that stops an animation [CHAR LIMIT=50] --> + <string name="settingslib_state_animation_paused">Animation paused</string> +</resources> diff --git a/packages/SettingsLib/IllustrationPreference/src/com/android/settingslib/widget/IllustrationPreference.java b/packages/SettingsLib/IllustrationPreference/src/com/android/settingslib/widget/IllustrationPreference.java index af40c647e805..e818a603c5b4 100644 --- a/packages/SettingsLib/IllustrationPreference/src/com/android/settingslib/widget/IllustrationPreference.java +++ b/packages/SettingsLib/IllustrationPreference/src/com/android/settingslib/widget/IllustrationPreference.java @@ -32,6 +32,8 @@ import android.util.Log; import android.view.View; import android.view.ViewGroup; import android.view.ViewGroup.LayoutParams; +import android.view.accessibility.AccessibilityNodeInfo; +import android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction; import android.widget.FrameLayout; import android.widget.ImageView; @@ -73,6 +75,7 @@ public class IllustrationPreference extends Preference implements GroupSectionDi private boolean mLottieDynamicColor; private CharSequence mContentDescription; private boolean mIsTablet; + private boolean mIsAnimatable; private boolean mIsAnimationPaused; /** @@ -81,6 +84,7 @@ public class IllustrationPreference extends Preference implements GroupSectionDi public interface OnBindListener { /** * Called when when {@link #onBindViewHolder(PreferenceViewHolder)} occurs. + * * @param animationView the animation view for this preference. */ void onBind(LottieAnimationView animationView); @@ -144,16 +148,6 @@ public class IllustrationPreference extends Preference implements GroupSectionDi (FrameLayout) holder.findViewById(R.id.middleground_layout); final LottieAnimationView illustrationView = (LottieAnimationView) holder.findViewById(R.id.lottie_view); - // Pause and resume animation - illustrationFrame.setOnClickListener(v -> { - mIsAnimationPaused = !mIsAnimationPaused; - if (mIsAnimationPaused) { - illustrationView.pauseAnimation(); - } else { - illustrationView.resumeAnimation(); - } - }); - if (illustrationView != null && !TextUtils.isEmpty(mContentDescription)) { illustrationView.setContentDescription(mContentDescription); illustrationView.setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_YES); @@ -171,12 +165,13 @@ public class IllustrationPreference extends Preference implements GroupSectionDi illustrationView.setCacheComposition(mCacheComposition); handleImageWithAnimation(illustrationView, illustrationFrame); + handleAnimationControl(illustrationView, illustrationFrame); handleImageFrameMaxHeight(backgroundView, illustrationView); if (mIsAutoScale) { illustrationView.setScaleType(mIsAutoScale - ? ImageView.ScaleType.CENTER_CROP - : ImageView.ScaleType.CENTER_INSIDE); + ? ImageView.ScaleType.CENTER_CROP + : ImageView.ScaleType.CENTER_INSIDE); } handleMiddleGroundView(middleGroundLayout); @@ -377,6 +372,7 @@ public class IllustrationPreference extends Preference implements GroupSectionDi final Drawable drawable = illustrationView.getDrawable(); if (drawable != null) { startAnimation(drawable); + mIsAnimatable = false; } } @@ -386,10 +382,12 @@ public class IllustrationPreference extends Preference implements GroupSectionDi final Drawable drawable = illustrationView.getDrawable(); if (drawable != null) { startAnimation(drawable); + mIsAnimatable = false; } else { // The lottie image from the raw folder also returns null because the ImageView // couldn't handle it now. startLottieAnimationWith(illustrationView, mImageUri); + mIsAnimatable = true; } } @@ -418,10 +416,12 @@ public class IllustrationPreference extends Preference implements GroupSectionDi final Drawable drawable = illustrationView.getDrawable(); if (drawable != null) { startAnimation(drawable); + mIsAnimatable = false; } else { // The lottie image from the raw folder also returns null because the ImageView // couldn't handle it now. startLottieAnimationWith(illustrationView, mImageResId); + mIsAnimatable = true; } } } @@ -459,6 +459,60 @@ public class IllustrationPreference extends Preference implements GroupSectionDi ((Animatable) drawable).start(); } + private void handleAnimationControl(LottieAnimationView illustrationView, + ViewGroup container) { + if (mIsAnimatable) { + // TODO(b/397340540): list out pages having illustration without a content description. + if (TextUtils.isEmpty(mContentDescription)) { + Log.w(TAG, "Illustration should have a content description. preference key = " + + getKey()); + } + // Enable pause and resume abilities to animation only + container.setOnClickListener(v -> { + mIsAnimationPaused = !mIsAnimationPaused; + if (mIsAnimationPaused) { + illustrationView.pauseAnimation(); + } else { + illustrationView.resumeAnimation(); + } + updateAccessibilityAction(container); + }); + + updateAccessibilityAction(container); + } + } + + private void updateAccessibilityAction(ViewGroup container) { + // Setting the state of animation + container.setStateDescription(getStateDescriptionForAnimation()); + container.setAccessibilityDelegate(new View.AccessibilityDelegate() { + @Override + public void onInitializeAccessibilityNodeInfo(View host, AccessibilityNodeInfo info) { + super.onInitializeAccessibilityNodeInfo(host, info); + final AccessibilityAction clickAction = new AccessibilityAction( + AccessibilityNodeInfo.ACTION_CLICK, + getActionLabelForAnimation()); + info.addAction(clickAction); + } + }); + } + + private String getActionLabelForAnimation() { + if (mIsAnimationPaused) { + return getContext().getString(R.string.settingslib_action_label_resume); + } else { + return getContext().getString(R.string.settingslib_action_label_pause); + } + } + + private String getStateDescriptionForAnimation() { + if (mIsAnimationPaused) { + return getContext().getString(R.string.settingslib_state_animation_paused); + } else { + return getContext().getString(R.string.settingslib_state_animation_playing); + } + } + private static void startLottieAnimationWith(LottieAnimationView illustrationView, Uri imageUri) { final InputStream inputStream = @@ -514,15 +568,20 @@ public class IllustrationPreference extends Preference implements GroupSectionDi mIsAutoScale = false; if (attrs != null) { TypedArray a = context.obtainStyledAttributes(attrs, - com.airbnb.lottie.R.styleable.LottieAnimationView, 0 /*defStyleAttr*/, 0 /*defStyleRes*/); - mImageResId = a.getResourceId(com.airbnb.lottie.R.styleable.LottieAnimationView_lottie_rawRes, 0); + com.airbnb.lottie.R.styleable.LottieAnimationView, /* defStyleAttr= */ 0, + /* defStyleRes= */ 0); + mImageResId = a.getResourceId( + com.airbnb.lottie.R.styleable.LottieAnimationView_lottie_rawRes, + /* defValue= */ 0); mCacheComposition = a.getBoolean( - com.airbnb.lottie.R.styleable.LottieAnimationView_lottie_cacheComposition, true); + com.airbnb.lottie.R.styleable.LottieAnimationView_lottie_cacheComposition, + /* defValue= */ true); a = context.obtainStyledAttributes(attrs, - R.styleable.IllustrationPreference, 0 /*defStyleAttr*/, 0 /*defStyleRes*/); + R.styleable.IllustrationPreference, /* defStyleAttr= */ 0, + /* defStyleRes= */ 0); mLottieDynamicColor = a.getBoolean(R.styleable.IllustrationPreference_dynamicColor, - false); + /* defValue= */ false); a.recycle(); } diff --git a/packages/SettingsLib/res/layout/activity_create_new_user.xml b/packages/SettingsLib/res/layout/activity_create_new_user.xml new file mode 100644 index 000000000000..7453b53a6956 --- /dev/null +++ b/packages/SettingsLib/res/layout/activity_create_new_user.xml @@ -0,0 +1,22 @@ +<?xml version="1.0" encoding="utf-8"?><!-- + ~ Copyright (C) 2025 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. + --> + +<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:background="@android:color/transparent" + android:orientation="vertical"> +</LinearLayout>
\ No newline at end of file diff --git a/packages/SettingsLib/res/values/styles.xml b/packages/SettingsLib/res/values/styles.xml index 3326b6034237..7ab096fae0db 100644 --- a/packages/SettingsLib/res/values/styles.xml +++ b/packages/SettingsLib/res/values/styles.xml @@ -116,4 +116,13 @@ <item name="android:textAppearance">?android:attr/textAppearanceSmall</item> <item name="android:textSize">16dp</item> </style> + + <style name="Theme.Transparent" parent="@android:style/Theme.DeviceDefault.Settings"> + <item name="android:windowActionBar">false</item> + <item name="android:windowNoTitle">true</item> + <item name="android:windowIsTranslucent">true</item> + <item name="android:windowBackground">@android:color/transparent</item> + <item name="android:windowContentOverlay">@null</item> + </style> + </resources> diff --git a/packages/SettingsLib/src/com/android/settingslib/users/CreateUserActivity.java b/packages/SettingsLib/src/com/android/settingslib/users/CreateUserActivity.java new file mode 100644 index 000000000000..c5e6f60e3fa6 --- /dev/null +++ b/packages/SettingsLib/src/com/android/settingslib/users/CreateUserActivity.java @@ -0,0 +1,145 @@ +/* + * Copyright (C) 2025 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.settingslib.users; + +import android.app.Activity; +import android.app.Dialog; +import android.content.Context; +import android.content.Intent; +import android.graphics.drawable.Drawable; +import android.os.Bundle; +import android.view.MotionEvent; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; + +import com.android.internal.annotations.VisibleForTesting; +import com.android.settingslib.R; + + +public class CreateUserActivity extends Activity { + private static final String TAG = "CreateUserActivity"; + + public static final String EXTRA_USER_NAME = "new_user_name"; + public static final String EXTRA_IS_ADMIN = "is_admin"; + public static final String EXTRA_USER_ICON_PATH = "user_icon_path"; + private static final String DIALOG_STATE_KEY = "create_user_dialog_state"; + private static final String EXTRA_CAN_CREATE_ADMIN = "can_create_admin"; + private static final String EXTRA_FILE_AUTHORITY = "file_authority"; + + private CreateUserDialogController mCreateUserDialogController; + @VisibleForTesting + Dialog mSetupUserDialog; + + + /** + * Creates intent to start CreateUserActivity + */ + public static @NonNull Intent createIntentForStart(@NonNull Context context, + boolean canCreateAdminUser, @NonNull String fileAuth) { + Intent intent = new Intent(context, CreateUserActivity.class); + intent.putExtra(EXTRA_CAN_CREATE_ADMIN, canCreateAdminUser); + intent.putExtra(EXTRA_FILE_AUTHORITY, fileAuth); + return intent; + } + + @Override + protected void onCreate(@Nullable Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + Intent intent = getIntent(); + + mCreateUserDialogController = new CreateUserDialogController( + intent.getStringExtra(EXTRA_FILE_AUTHORITY)); + setContentView(R.layout.activity_create_new_user); + if (savedInstanceState != null) { + mCreateUserDialogController.onRestoreInstanceState(savedInstanceState); + } + mSetupUserDialog = createDialog(intent.getBooleanExtra(EXTRA_CAN_CREATE_ADMIN, false)); + mSetupUserDialog.show(); + } + + @Override + protected void onRestoreInstanceState(@NonNull Bundle savedInstanceState) { + super.onRestoreInstanceState(savedInstanceState); + Bundle savedDialogState = savedInstanceState.getBundle(DIALOG_STATE_KEY); + if (savedDialogState != null && mSetupUserDialog != null) { + mSetupUserDialog.onRestoreInstanceState(savedDialogState); + } + } + + private Dialog createDialog(boolean canCreateAdminUser) { + return mCreateUserDialogController.createDialog( + this, + this::startActivity, + canCreateAdminUser, + this::setSuccessResult, + this::cancel + ); + } + + @Override + public boolean onTouchEvent(@Nullable MotionEvent event) { + onBackInvoked(); + return super.onTouchEvent(event); + } + + private void onBackInvoked() { + if (mSetupUserDialog != null) { + mSetupUserDialog.dismiss(); + } + setResult(RESULT_CANCELED); + finish(); + } + + @VisibleForTesting + void setSuccessResult(String userName, Drawable userIcon, String path, Boolean isAdmin) { + Intent intent = new Intent(this, CreateUserActivity.class); + intent.putExtra(EXTRA_USER_NAME, userName); + intent.putExtra(EXTRA_IS_ADMIN, isAdmin); + intent.putExtra(EXTRA_USER_ICON_PATH, path); + + mSetupUserDialog.dismiss(); + setResult(RESULT_OK, intent); + finish(); + } + + @VisibleForTesting + void cancel() { + mSetupUserDialog.dismiss(); + setResult(RESULT_CANCELED); + finish(); + } + + @Override + protected void onSaveInstanceState(@NonNull Bundle outState) { + if (mSetupUserDialog != null && mSetupUserDialog.isShowing()) { + outState.putBundle(DIALOG_STATE_KEY, mSetupUserDialog.onSaveInstanceState()); + } + mCreateUserDialogController.onSaveInstanceState(outState); + super.onSaveInstanceState(outState); + } + + @Override + protected void onActivityResult(int requestCode, int resultCode, Intent data) { + mCreateUserDialogController.onActivityResult(requestCode, resultCode, data); + } + + private void startActivity(Intent intent, int requestCode) { + startActivityForResult(intent, requestCode); + mCreateUserDialogController.startingActivityForResult(); + } +} diff --git a/packages/SettingsLib/src/com/android/settingslib/users/CreateUserDialogController.java b/packages/SettingsLib/src/com/android/settingslib/users/CreateUserDialogController.java index d71b337228f6..d9f1b632323c 100644 --- a/packages/SettingsLib/src/com/android/settingslib/users/CreateUserDialogController.java +++ b/packages/SettingsLib/src/com/android/settingslib/users/CreateUserDialogController.java @@ -242,7 +242,7 @@ public class CreateUserDialogController { .setMessage(messageResId) .setNegativeButtonText(R.string.cancel) .setPositiveButtonText(R.string.next); - mCustomDialogHelper.requestFocusOnTitle(); + focus(); break; case GRANT_ADMIN_DIALOG: mEditUserInfoView.setVisibility(View.GONE); @@ -255,7 +255,7 @@ public class CreateUserDialogController { .setMessage(R.string.user_grant_admin_message) .setNegativeButtonText(R.string.back) .setPositiveButtonText(R.string.next); - mCustomDialogHelper.requestFocusOnTitle(); + focus(); if (mIsAdmin == null) { mCustomDialogHelper.setButtonEnabled(false); } @@ -267,7 +267,7 @@ public class CreateUserDialogController { .setTitle(R.string.user_info_settings_title) .setNegativeButtonText(R.string.back) .setPositiveButtonText(R.string.done); - mCustomDialogHelper.requestFocusOnTitle(); + focus(); mEditUserInfoView.setVisibility(View.VISIBLE); mGrantAdminView.setVisibility(View.GONE); break; @@ -282,7 +282,7 @@ public class CreateUserDialogController { mCustomDialogHelper.getDialog().dismiss(); break; case EXIT_DIALOG: - mCustomDialogHelper.getDialog().dismiss(); + finish(); break; default: if (mCurrentState < EXIT_DIALOG) { @@ -394,13 +394,21 @@ public class CreateUserDialogController { return mCustomDialogHelper != null && mCustomDialogHelper.getDialog() != null; } + void focus() { + mCustomDialogHelper.requestFocusOnTitle(); + } + /** * Runs callback and clears saved values after dialog is dismissed. */ public void finish() { if (mCurrentState == CREATE_USER_AND_CLOSE) { if (mSuccessCallback != null) { - mSuccessCallback.onSuccess(mUserName, mNewUserIcon, Boolean.TRUE.equals(mIsAdmin)); + if (mEditUserPhotoController != null && mCachedDrawablePath == null) { + mCachedDrawablePath = mEditUserPhotoController.getCachedDrawablePath(); + } + mSuccessCallback.onSuccess(mUserName, mNewUserIcon, mCachedDrawablePath, + Boolean.TRUE.equals(mIsAdmin)); } } else { if (mCancelCallback != null) { diff --git a/packages/SettingsLib/src/com/android/settingslib/users/NewUserData.java b/packages/SettingsLib/src/com/android/settingslib/users/NewUserData.java index 3d18b59258b3..eed608e98cc9 100644 --- a/packages/SettingsLib/src/com/android/settingslib/users/NewUserData.java +++ b/packages/SettingsLib/src/com/android/settingslib/users/NewUserData.java @@ -18,6 +18,8 @@ package com.android.settingslib.users; import android.graphics.drawable.Drawable; +import androidx.annotation.Nullable; + /** * Defines a callback when a new user data is filled out. */ @@ -27,8 +29,10 @@ public interface NewUserData { * Consumes data relevant to new user that needs to be created. * @param userName New user name. * @param userImage New user icon. + * @param iconPath New user icon path. * @param isNewUserAdmin A boolean that indicated whether new user has admin status. */ - void onSuccess(String userName, Drawable userImage, Boolean isNewUserAdmin); + void onSuccess(@Nullable String userName, @Nullable Drawable userImage, + @Nullable String iconPath, @Nullable Boolean isNewUserAdmin); } diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/users/CreateUserActivityTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/users/CreateUserActivityTest.java new file mode 100644 index 000000000000..f58eb7cc2e31 --- /dev/null +++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/users/CreateUserActivityTest.java @@ -0,0 +1,114 @@ +/* + * Copyright (C) 2025 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.settingslib.users; + +import static com.android.settingslib.users.CreateUserActivity.EXTRA_IS_ADMIN; +import static com.android.settingslib.users.CreateUserActivity.EXTRA_USER_ICON_PATH; +import static com.android.settingslib.users.CreateUserActivity.EXTRA_USER_NAME; + +import static com.google.common.truth.Truth.assertThat; + +import static org.mockito.Mockito.mock; +import static org.robolectric.Shadows.shadowOf; + +import android.app.Activity; +import android.content.Context; +import android.content.Intent; +import android.graphics.drawable.Drawable; +import android.os.Bundle; +import android.view.MotionEvent; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.robolectric.Robolectric; +import org.robolectric.RobolectricTestRunner; +import org.robolectric.RuntimeEnvironment; + +@RunWith(RobolectricTestRunner.class) +public class CreateUserActivityTest { + + private static final String TEST_USER_NAME = "test_user"; + private static final String TEST_USER_ICON_PATH = "/test_path"; + private static final boolean TEST_IS_ADMIN = true; + + private Context mContext; + private CreateUserActivity mCreateUserActivity; + + @Before + public void setUp() { + mContext = RuntimeEnvironment.application; + mCreateUserActivity = Robolectric.buildActivity(CreateUserActivity.class).setup().get(); + } + + @Test + public void startActivity_startsActivityForResult() { + Intent activityIntent = CreateUserActivity.createIntentForStart(mContext, true, ""); + mCreateUserActivity.startActivity(activityIntent, null); + + assertThat(shadowOf(mCreateUserActivity).getNextStartedActivityForResult().intent) + .isEqualTo(activityIntent); + } + + @Test + public void onTouchEvent_dismissesDialogAndCancelsResult() { + mCreateUserActivity.onTouchEvent(MotionEvent.obtain(0, 0, MotionEvent.ACTION_DOWN, 0, 0, + 0)); + + assertThat(mCreateUserActivity.mSetupUserDialog.isShowing()).isFalse(); + assertThat(shadowOf(mCreateUserActivity).getResultCode()) + .isEqualTo(Activity.RESULT_CANCELED); + } + + @Test + public void setSuccessResult_dismissesDialogAndSetsSuccessResult() { + Drawable mockDrawable = mock(Drawable.class); + + mCreateUserActivity.setSuccessResult(TEST_USER_NAME, mockDrawable, TEST_USER_ICON_PATH, + TEST_IS_ADMIN); + + assertThat(mCreateUserActivity.mSetupUserDialog.isShowing()).isFalse(); + assertThat(shadowOf(mCreateUserActivity).getResultCode()).isEqualTo(Activity.RESULT_OK); + + Intent resultIntent = shadowOf(mCreateUserActivity).getResultIntent(); + assertThat(resultIntent.getStringExtra(EXTRA_USER_NAME)).isEqualTo(TEST_USER_NAME); + assertThat(resultIntent.getBooleanExtra(EXTRA_IS_ADMIN, false)).isEqualTo(TEST_IS_ADMIN); + assertThat(resultIntent.getStringExtra(EXTRA_USER_ICON_PATH)) + .isEqualTo(TEST_USER_ICON_PATH); + } + + @Test + public void cancel_dismissesDialogAndSetsCancelResult() { + mCreateUserActivity.cancel(); + + assertThat(mCreateUserActivity.mSetupUserDialog.isShowing()).isFalse(); + assertThat(shadowOf(mCreateUserActivity).getResultCode()) + .isEqualTo(Activity.RESULT_CANCELED); + } + + @Test + public void onSaveInstanceState_savesDialogState() { + Bundle outState = new Bundle(); + mCreateUserActivity.onSaveInstanceState(outState); + + CreateUserActivity restoredActivity = + Robolectric.buildActivity(CreateUserActivity.class).setup(outState).get(); + + assertThat(restoredActivity.mSetupUserDialog).isNotNull(); + assertThat(restoredActivity.mSetupUserDialog.isShowing()).isTrue(); + } +} diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/users/CreateUserDialogControllerTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/users/CreateUserDialogControllerTest.java index 68312223b4b1..e60232339e4c 100644 --- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/users/CreateUserDialogControllerTest.java +++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/users/CreateUserDialogControllerTest.java @@ -211,7 +211,7 @@ public class CreateUserDialogControllerTest { editText.setText(expectedNewName); next.performClick(); verify(successCallback, times(1)) - .onSuccess(expectedNewName, null, true); + .onSuccess(expectedNewName, null, null, true); verifyNoInteractions(cancelCallback); } @@ -233,7 +233,7 @@ public class CreateUserDialogControllerTest { editText.setText(expectedNewName); next.performClick(); verify(successCallback, times(1)) - .onSuccess(expectedNewName, null, false); + .onSuccess(expectedNewName, null, null, false); verifyNoInteractions(cancelCallback); } diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java index 8e3aa65fa5c7..bc281eea39d8 100644 --- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java +++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java @@ -1503,7 +1503,7 @@ public class SettingsProvider extends ContentProvider { if (DEBUG) { Slog.v(LOG_TAG, "insertGlobalSetting(" + name + ", " + value + ", " + ", " + tag + ", " + makeDefault + ", " + requestingUserId - + ", " + forceNotify + ")"); + + ", " + forceNotify + ", " + overrideableByRestore + ")"); } return mutateGlobalSetting(name, value, tag, makeDefault, requestingUserId, MUTATION_OPERATION_INSERT, forceNotify, 0, overrideableByRestore); @@ -1785,7 +1785,7 @@ public class SettingsProvider extends ContentProvider { if (DEBUG) { Slog.v(LOG_TAG, "insertSecureSetting(" + name + ", " + value + ", " + ", " + tag + ", " + makeDefault + ", " + requestingUserId - + ", " + forceNotify + ")"); + + ", " + forceNotify + ", " + overrideableByRestore + ")"); } return mutateSecureSetting(name, value, tag, makeDefault, requestingUserId, MUTATION_OPERATION_INSERT, forceNotify, 0, overrideableByRestore); @@ -1946,7 +1946,7 @@ public class SettingsProvider extends ContentProvider { boolean overrideableByRestore) { if (DEBUG) { Slog.v(LOG_TAG, "insertSystemSetting(" + name + ", " + value + ", " - + requestingUserId + ")"); + + requestingUserId + ", " + overrideableByRestore + ")"); } return mutateSystemSetting(name, value, /* tag= */ null, requestingUserId, diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsService.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsService.java index 17ebf6fc3235..0484defeb4d7 100644 --- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsService.java +++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsService.java @@ -30,6 +30,7 @@ import android.os.ShellCommand; import android.os.UserHandle; import android.os.UserManager; import android.provider.Settings; +import android.util.Slog; import java.io.FileDescriptor; import java.io.PrintWriter; @@ -94,6 +95,8 @@ final public class SettingsService extends Binder { } final static class MyShellCommand extends ShellCommand { + private static final String LOG_TAG = "SettingsShellCmd"; + final SettingsProvider mProvider; final boolean mDumping; @@ -115,6 +118,7 @@ final public class SettingsService extends Binder { String mTag = null; int mResetMode = -1; boolean mMakeDefault; + boolean mOverrideableByRestore; MyShellCommand(SettingsProvider provider, boolean dumping) { mProvider = provider; @@ -209,6 +213,7 @@ final public class SettingsService extends Binder { return -1; } break; + // At this point, mVerb == PUT } else if (mKey == null) { mKey = arg; // keep going; there's another PUT arg @@ -217,36 +222,8 @@ final public class SettingsService extends Binder { // what we have so far is a valid command valid = true; // keep going; there may be another PUT arg - } else if (mTag == null) { - mTag = arg; - if ("default".equalsIgnoreCase(mTag)) { - mTag = null; - mMakeDefault = true; - if (peekNextArg() == null) { - valid = true; - } else { - perr.println("Too many arguments"); - return -1; - } - break; - } - if (peekNextArg() == null) { - valid = true; - break; - } - } else { // PUT, final arg - if (!"default".equalsIgnoreCase(arg)) { - perr.println("Argument expected to be 'default'"); - return -1; - } - mMakeDefault = true; - if (peekNextArg() == null) { - valid = true; - } else { - perr.println("Too many arguments"); - return -1; - } - break; + } else { + valid = parseOptionalPutArgument(arg); } } while ((arg = getNextArg()) != null); @@ -275,7 +252,8 @@ final public class SettingsService extends Binder { pout.println(getForUser(iprovider, mUser, mTable, mKey)); break; case PUT: - putForUser(iprovider, mUser, mTable, mKey, mValue, mTag, mMakeDefault); + putForUser(iprovider, mUser, mTable, mKey, mValue, mTag, mMakeDefault, + mOverrideableByRestore); break; case DELETE: pout.println("Deleted " @@ -297,6 +275,41 @@ final public class SettingsService extends Binder { return 0; } + private boolean parseOptionalPutArgument(String arg) { + boolean valid = true; + // Given that the order is TAG default overrideableByRestore, we need to parse from the + // opposite direction + switch (arg) { + case "overrideableByRestore": + if (mOverrideableByRestore) { + valid = false; + } else { + mOverrideableByRestore = true; + } + break; + case "default": + if (mMakeDefault || mOverrideableByRestore) { + valid = false; + } else { + mMakeDefault = true; + } + break; + default: // tag + if (mMakeDefault || mOverrideableByRestore || mTag != null) { + valid = false; + } else { + mTag = arg; + } + break; + } + if (!valid) { + Slog.e(LOG_TAG, "parseOptionalPutArgument(" + arg + "): invalid state (" + + "mTag=" + mTag + ", mMakeDefault=" + mMakeDefault + + ", mOverrideableByRestore=" + mOverrideableByRestore + ")"); + } + return valid; + } + List<String> listForUser(IContentProvider provider, int userHandle, String table) { final String callListCommand; if ("system".equals(table)) callListCommand = Settings.CALL_METHOD_LIST_SYSTEM; @@ -351,7 +364,11 @@ final public class SettingsService extends Binder { } void putForUser(IContentProvider provider, int userHandle, final String table, - final String key, final String value, String tag, boolean makeDefault) { + final String key, final String value, String tag, boolean makeDefault, + boolean overrideableByRestore) { + Slog.v(LOG_TAG, "putForUser(userId=" + userHandle + ", table=" + table + ", key=" + key + + ", value=" + value + ", tag=" + tag + ", makeDefault=" + makeDefault + + ", overrideableByRestore=" + overrideableByRestore + ")"); final String callPutCommand; if ("system".equals(table)) { callPutCommand = Settings.CALL_METHOD_PUT_SYSTEM; @@ -377,6 +394,9 @@ final public class SettingsService extends Binder { if (makeDefault) { arg.putBoolean(Settings.CALL_METHOD_MAKE_DEFAULT_KEY, true); } + if (overrideableByRestore) { + arg.putBoolean(Settings.CALL_METHOD_OVERRIDEABLE_BY_RESTORE_KEY, true); + } final AttributionSource attributionSource = new AttributionSource( Binder.getCallingUid(), resolveCallingPackage(), /*attributionTag*/ null); provider.call(attributionSource, Settings.AUTHORITY, @@ -474,10 +494,11 @@ final public class SettingsService extends Binder { pw.println(" Print this help text."); pw.println(" get [--user <USER_ID> | current] NAMESPACE KEY"); pw.println(" Retrieve the current value of KEY."); - pw.println(" put [--user <USER_ID> | current] NAMESPACE KEY VALUE [TAG] [default]"); + pw.println(" put [--user <USER_ID> | current] NAMESPACE KEY VALUE [TAG] [default] [overrideableByRestore]"); pw.println(" Change the contents of KEY to VALUE."); - pw.println(" TAG to associate with the setting."); + pw.println(" TAG to associate with the setting (cannot be default or overrideableByRestore)."); pw.println(" {default} to set as the default, case-insensitive only for global/secure namespace"); + pw.println(" {overrideableByRestore} to let the value be overridden by BackupManager on restore operations"); pw.println(" delete [--user <USER_ID> | current] NAMESPACE KEY"); pw.println(" Delete the entry for KEY."); pw.println(" reset [--user <USER_ID> | current] NAMESPACE {PACKAGE_NAME | RESET_MODE}"); diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/ShadeHeader.kt b/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/ShadeHeader.kt index 02de78bc84ce..30623acbbd31 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/ShadeHeader.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/ShadeHeader.kt @@ -149,9 +149,6 @@ fun ContentScope.CollapsedShadeHeader( } } - val longerDateText by viewModel.longerDateText.collectAsStateWithLifecycle() - val shorterDateText by viewModel.shorterDateText.collectAsStateWithLifecycle() - val isShadeLayoutWide = viewModel.isShadeLayoutWide val isPrivacyChipVisible by viewModel.isPrivacyChipVisible.collectAsStateWithLifecycle() @@ -167,8 +164,8 @@ fun ContentScope.CollapsedShadeHeader( ) { Clock(scale = 1f, onClick = viewModel::onClockClicked) VariableDayDate( - longerDateText = longerDateText, - shorterDateText = shorterDateText, + longerDateText = viewModel.longerDateText, + shorterDateText = viewModel.shorterDateText, chipHighlight = viewModel.notificationsChipHighlight, modifier = Modifier.element(ShadeHeader.Elements.CollapsedContentStart), ) @@ -229,8 +226,6 @@ fun ContentScope.ExpandedShadeHeader( derivedStateOf { shouldUseExpandedFormat(layoutState.transitionState) } } - val longerDateText by viewModel.longerDateText.collectAsStateWithLifecycle() - val shorterDateText by viewModel.shorterDateText.collectAsStateWithLifecycle() val isPrivacyChipVisible by viewModel.isPrivacyChipVisible.collectAsStateWithLifecycle() Box(modifier = modifier.sysuiResTag(ShadeHeader.TestTags.Root)) { @@ -269,8 +264,8 @@ fun ContentScope.ExpandedShadeHeader( modifier = Modifier.element(ShadeHeader.Elements.ExpandedContent), ) { VariableDayDate( - longerDateText = longerDateText, - shorterDateText = shorterDateText, + longerDateText = viewModel.longerDateText, + shorterDateText = viewModel.shorterDateText, chipHighlight = viewModel.notificationsChipHighlight, modifier = Modifier.widthIn(max = 90.dp), ) @@ -337,12 +332,9 @@ fun ContentScope.OverlayShadeHeader( modifier = Modifier.width(IntrinsicSize.Min).height(20.dp), ) } else { - val longerDateText by viewModel.longerDateText.collectAsStateWithLifecycle() - val shorterDateText by - viewModel.shorterDateText.collectAsStateWithLifecycle() VariableDayDate( - longerDateText = longerDateText, - shorterDateText = shorterDateText, + longerDateText = viewModel.longerDateText, + shorterDateText = viewModel.shorterDateText, chipHighlight = viewModel.notificationsChipHighlight, ) } @@ -546,11 +538,8 @@ private fun BatteryIcon( @Composable private fun ShadeCarrierGroup(viewModel: ShadeHeaderViewModel, modifier: Modifier = Modifier) { - Row(modifier = modifier) { - val subIds by viewModel.mobileSubIds.collectAsStateWithLifecycle() - - for (subId in subIds) { - Spacer(modifier = Modifier.width(5.dp)) + Row(modifier = modifier, horizontalArrangement = Arrangement.spacedBy(5.dp)) { + for (subId in viewModel.mobileSubIds) { AndroidView( factory = { context -> ModernShadeCarrierGroupMobileView.constructAndBind( diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/AuthContainerViewTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/AuthContainerViewTest.kt index 50762edc1179..88c9e74551fd 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/AuthContainerViewTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/AuthContainerViewTest.kt @@ -21,6 +21,7 @@ import android.content.res.Configuration import android.hardware.biometrics.BiometricAuthenticator import android.hardware.biometrics.BiometricConstants import android.hardware.biometrics.BiometricManager +import android.hardware.biometrics.BiometricPrompt import android.hardware.biometrics.PromptContentViewWithMoreOptionsButton import android.hardware.biometrics.PromptInfo import android.hardware.biometrics.PromptVerticalListContentView @@ -290,7 +291,7 @@ open class AuthContainerViewTest : SysuiTestCase() { verify(callback) .onDismissed( - eq(AuthDialogCallback.DISMISSED_BIOMETRIC_AUTHENTICATED), + eq(BiometricPrompt.DISMISSED_REASON_BIOMETRIC_CONFIRM_NOT_REQUIRED), eq<ByteArray?>(null), /* credentialAttestation */ eq(authContainer?.requestId ?: 0L), ) @@ -310,7 +311,7 @@ open class AuthContainerViewTest : SysuiTestCase() { ) verify(callback) .onDismissed( - eq(AuthDialogCallback.DISMISSED_USER_CANCELED), + eq(BiometricPrompt.DISMISSED_REASON_USER_CANCEL), eq<ByteArray?>(null), /* credentialAttestation */ eq(authContainer?.requestId ?: 0L), ) @@ -325,7 +326,7 @@ open class AuthContainerViewTest : SysuiTestCase() { verify(callback) .onDismissed( - eq(AuthDialogCallback.DISMISSED_BUTTON_NEGATIVE), + eq(BiometricPrompt.DISMISSED_REASON_NEGATIVE), eq<ByteArray?>(null), /* credentialAttestation */ eq(authContainer?.requestId ?: 0L), ) @@ -352,7 +353,7 @@ open class AuthContainerViewTest : SysuiTestCase() { verify(callback) .onDismissed( - eq(AuthDialogCallback.DISMISSED_ERROR), + eq(BiometricPrompt.DISMISSED_REASON_ERROR), eq<ByteArray?>(null), /* credentialAttestation */ eq(authContainer?.requestId ?: 0L), ) diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/AuthControllerTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/AuthControllerTest.java index acc97a9f8642..a1a2aa70d869 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/AuthControllerTest.java +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/AuthControllerTest.java @@ -138,9 +138,9 @@ public class AuthControllerTest extends SysuiTestCase { @Mock private IBiometricContextListener mContextListener; @Mock - private AuthDialog mDialog1; + private AuthContainerView mDialog1; @Mock - private AuthDialog mDialog2; + private AuthContainerView mDialog2; @Mock private CommandQueue mCommandQueue; @Mock @@ -382,7 +382,7 @@ public class AuthControllerTest extends SysuiTestCase { @Test public void testSendsReasonUserCanceled_whenDismissedByUserCancel() throws Exception { showDialog(new int[]{1} /* sensorIds */, false /* credentialAllowed */); - mAuthController.onDismissed(AuthDialogCallback.DISMISSED_USER_CANCELED, + mAuthController.onDismissed(BiometricPrompt.DISMISSED_REASON_USER_CANCEL, null, /* credentialAttestation */ mAuthController.mCurrentDialog.getRequestId()); verify(mReceiver).onDialogDismissed( @@ -393,7 +393,7 @@ public class AuthControllerTest extends SysuiTestCase { @Test public void testSendsReasonNegative_whenDismissedByButtonNegative() throws Exception { showDialog(new int[] {1} /* sensorIds */, false /* credentialAllowed */); - mAuthController.onDismissed(AuthDialogCallback.DISMISSED_BUTTON_NEGATIVE, + mAuthController.onDismissed(BiometricPrompt.DISMISSED_REASON_NEGATIVE, null, /* credentialAttestation */ mAuthController.mCurrentDialog.getRequestId()); verify(mReceiver).onDialogDismissed( @@ -404,7 +404,7 @@ public class AuthControllerTest extends SysuiTestCase { @Test public void testSendsReasonConfirmed_whenDismissedByButtonPositive() throws Exception { showDialog(new int[] {1} /* sensorIds */, false /* credentialAllowed */); - mAuthController.onDismissed(AuthDialogCallback.DISMISSED_BUTTON_POSITIVE, + mAuthController.onDismissed(BiometricPrompt.DISMISSED_REASON_BIOMETRIC_CONFIRMED, null, /* credentialAttestation */ mAuthController.mCurrentDialog.getRequestId()); verify(mReceiver).onDialogDismissed( @@ -415,7 +415,7 @@ public class AuthControllerTest extends SysuiTestCase { @Test public void testSendsReasonConfirmNotRequired_whenDismissedByAuthenticated() throws Exception { showDialog(new int[] {1} /* sensorIds */, false /* credentialAllowed */); - mAuthController.onDismissed(AuthDialogCallback.DISMISSED_BIOMETRIC_AUTHENTICATED, + mAuthController.onDismissed(BiometricPrompt.DISMISSED_REASON_BIOMETRIC_CONFIRM_NOT_REQUIRED, null, /* credentialAttestation */ mAuthController.mCurrentDialog.getRequestId()); verify(mReceiver).onDialogDismissed( @@ -426,7 +426,7 @@ public class AuthControllerTest extends SysuiTestCase { @Test public void testSendsReasonError_whenDismissedByError() throws Exception { showDialog(new int[] {1} /* sensorIds */, false /* credentialAllowed */); - mAuthController.onDismissed(AuthDialogCallback.DISMISSED_ERROR, + mAuthController.onDismissed(BiometricPrompt.DISMISSED_REASON_ERROR, null, /* credentialAttestation */ mAuthController.mCurrentDialog.getRequestId()); verify(mReceiver).onDialogDismissed( @@ -437,7 +437,7 @@ public class AuthControllerTest extends SysuiTestCase { @Test public void testSendsReasonServerRequested_whenDismissedByServer() throws Exception { showDialog(new int[] {1} /* sensorIds */, false /* credentialAllowed */); - mAuthController.onDismissed(AuthDialogCallback.DISMISSED_BY_SYSTEM_SERVER, + mAuthController.onDismissed(BiometricPrompt.DISMISSED_REASON_SERVER_REQUESTED, null, /* credentialAttestation */ mAuthController.mCurrentDialog.getRequestId()); verify(mReceiver).onDialogDismissed( @@ -452,7 +452,7 @@ public class AuthControllerTest extends SysuiTestCase { final byte[] credentialAttestation = generateRandomHAT(); - mAuthController.onDismissed(AuthDialogCallback.DISMISSED_CREDENTIAL_AUTHENTICATED, + mAuthController.onDismissed(BiometricPrompt.DISMISSED_REASON_CREDENTIAL_CONFIRMED, credentialAttestation, mAuthController.mCurrentDialog.getRequestId()); verify(mReceiver).onDialogDismissed( eq(BiometricPrompt.DISMISSED_REASON_CREDENTIAL_CONFIRMED), @@ -462,7 +462,7 @@ public class AuthControllerTest extends SysuiTestCase { @Test public void testSendsReasonContentViewMoreOptions_whenButtonPressed() throws Exception { showDialog(new int[]{1} /* sensorIds */, false /* credentialAllowed */); - mAuthController.onDismissed(AuthDialogCallback.DISMISSED_BUTTON_CONTENT_VIEW_MORE_OPTIONS, + mAuthController.onDismissed(BiometricPrompt.DISMISSED_REASON_CONTENT_VIEW_MORE_OPTIONS, null, /* credentialAttestation */ mAuthController.mCurrentDialog.getRequestId()); verify(mReceiver).onDialogDismissed( @@ -696,7 +696,7 @@ public class AuthControllerTest extends SysuiTestCase { final byte[] credentialAttestation = generateRandomHAT(); - mAuthController.onDismissed(AuthDialogCallback.DISMISSED_CREDENTIAL_AUTHENTICATED, + mAuthController.onDismissed(BiometricPrompt.DISMISSED_REASON_CREDENTIAL_CONFIRMED, credentialAttestation, mAuthController.mCurrentDialog.getRequestId()); verify(mReceiver).onDialogDismissed( eq(BiometricPrompt.DISMISSED_REASON_CREDENTIAL_CONFIRMED), @@ -755,7 +755,7 @@ public class AuthControllerTest extends SysuiTestCase { public void testDoesNotCrash_whenTryAgainPressedAfterDismissal() { showDialog(new int[] {1} /* sensorIds */, false /* credentialAllowed */); final long requestID = mAuthController.mCurrentDialog.getRequestId(); - mAuthController.onDismissed(AuthDialogCallback.DISMISSED_USER_CANCELED, + mAuthController.onDismissed(BiometricPrompt.DISMISSED_REASON_USER_CANCEL, null, /* credentialAttestation */requestID); mAuthController.onTryAgainPressed(requestID); } @@ -764,7 +764,7 @@ public class AuthControllerTest extends SysuiTestCase { public void testDoesNotCrash_whenDeviceCredentialPressedAfterDismissal() { showDialog(new int[] {1} /* sensorIds */, false /* credentialAllowed */); final long requestID = mAuthController.mCurrentDialog.getRequestId(); - mAuthController.onDismissed(AuthDialogCallback.DISMISSED_USER_CANCELED, + mAuthController.onDismissed(BiometricPrompt.DISMISSED_REASON_USER_CANCEL, null /* credentialAttestation */, requestID); mAuthController.onDeviceCredentialPressed(requestID); } @@ -818,7 +818,7 @@ public class AuthControllerTest extends SysuiTestCase { // WHEN dialog is shown and then dismissed showDialog(new int[]{1} /* sensorIds */, false /* credentialAllowed */); - mAuthController.onDismissed(AuthDialogCallback.DISMISSED_USER_CANCELED, + mAuthController.onDismissed(BiometricPrompt.DISMISSED_REASON_USER_CANCEL, null /* credentialAttestation */, mAuthController.mCurrentDialog.getRequestId()); @@ -1218,14 +1218,14 @@ public class AuthControllerTest extends SysuiTestCase { } @Override - protected AuthDialog buildDialog(DelayableExecutor bgExecutor, PromptInfo promptInfo, + protected AuthContainerView buildDialog(DelayableExecutor bgExecutor, PromptInfo promptInfo, boolean requireConfirmation, int userId, int[] sensorIds, String opPackageName, boolean skipIntro, long operationId, long requestId, WakefulnessLifecycle wakefulnessLifecycle, UserManager userManager, LockPatternUtils lockPatternUtils, PromptViewModel viewModel) { - AuthDialog dialog; + AuthContainerView dialog; if (mBuildCount == 0) { dialog = mDialog1; } else if (mBuildCount == 1) { diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/DozingToDreamingTransitionViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/DozingToDreamingTransitionViewModelTest.kt new file mode 100644 index 000000000000..052dfd52887f --- /dev/null +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/DozingToDreamingTransitionViewModelTest.kt @@ -0,0 +1,54 @@ +/* + * Copyright (C) 2025 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.keyguard.ui.viewmodel + +import androidx.test.ext.junit.runners.AndroidJUnit4 +import androidx.test.filters.SmallTest +import com.android.systemui.SysuiTestCase +import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository +import com.android.systemui.keyguard.shared.model.KeyguardState +import com.android.systemui.kosmos.collectValues +import com.android.systemui.kosmos.runTest +import com.android.systemui.kosmos.testScope +import com.android.systemui.testKosmos +import com.google.common.truth.Truth.assertThat +import org.junit.Test +import org.junit.runner.RunWith + +@SmallTest +@RunWith(AndroidJUnit4::class) +class DozingToDreamingTransitionViewModelTest : SysuiTestCase() { + val kosmos = testKosmos() + + val underTest by lazy { kosmos.dozingToDreamingTransitionViewModel } + + @Test + fun notificationShadeAlpha() = + kosmos.runTest { + val values by collectValues(underTest.notificationAlpha) + assertThat(values).isEmpty() + + fakeKeyguardTransitionRepository.sendTransitionSteps( + from = KeyguardState.DOZING, + to = KeyguardState.DREAMING, + testScope, + ) + + assertThat(values).isNotEmpty() + values.forEach { assertThat(it).isEqualTo(0) } + } +} diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/domain/interactor/ShadeHeaderClockInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/domain/interactor/ShadeHeaderClockInteractorTest.kt index 84fc93008f49..a3dd67f85150 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/domain/interactor/ShadeHeaderClockInteractorTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/domain/interactor/ShadeHeaderClockInteractorTest.kt @@ -22,6 +22,9 @@ import android.provider.AlarmClock import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase +import com.android.systemui.broadcast.broadcastDispatcher +import com.android.systemui.coroutines.collectLastValue +import com.android.systemui.coroutines.collectValues import com.android.systemui.kosmos.testScope import com.android.systemui.plugins.activityStarter import com.android.systemui.statusbar.policy.NextAlarmController.NextAlarmChangeCallback @@ -31,22 +34,32 @@ import com.android.systemui.util.mockito.any import com.android.systemui.util.mockito.argThat import com.android.systemui.util.mockito.mock import com.android.systemui.util.mockito.withArgCaptor +import com.google.common.truth.Truth.assertThat +import java.util.Date +import kotlin.time.Duration +import kotlin.time.Duration.Companion.milliseconds +import kotlin.time.Duration.Companion.seconds +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.test.TestScope +import kotlinx.coroutines.test.advanceTimeBy +import kotlinx.coroutines.test.runCurrent import kotlinx.coroutines.test.runTest import org.junit.Test import org.junit.runner.RunWith import org.mockito.ArgumentMatcher -import org.mockito.Mockito.times import org.mockito.Mockito.verify +@OptIn(ExperimentalCoroutinesApi::class) @SmallTest @RunWith(AndroidJUnit4::class) class ShadeHeaderClockInteractorTest : SysuiTestCase() { + private val kosmos = testKosmos() private val testScope = kosmos.testScope private val activityStarter = kosmos.activityStarter private val nextAlarmController = kosmos.nextAlarmController - val underTest = kosmos.shadeHeaderClockInteractor + private val underTest = kosmos.shadeHeaderClockInteractor @Test fun launchClockActivity_default() = @@ -55,7 +68,7 @@ class ShadeHeaderClockInteractorTest : SysuiTestCase() { verify(activityStarter) .postStartActivityDismissingKeyguard( argThat(IntentMatcherAction(AlarmClock.ACTION_SHOW_ALARMS)), - any() + any(), ) } @@ -71,6 +84,75 @@ class ShadeHeaderClockInteractorTest : SysuiTestCase() { underTest.launchClockActivity() verify(activityStarter).postStartActivityDismissingKeyguard(any()) } + + @Test + fun onTimezoneOrLocaleChanged_localeAndTimezoneChanged_emitsForEach() = + testScope.runTest { + val timeZoneOrLocaleChanges by collectValues(underTest.onTimezoneOrLocaleChanged) + + sendIntentActionBroadcast(Intent.ACTION_TIMEZONE_CHANGED) + sendIntentActionBroadcast(Intent.ACTION_LOCALE_CHANGED) + sendIntentActionBroadcast(Intent.ACTION_LOCALE_CHANGED) + sendIntentActionBroadcast(Intent.ACTION_TIMEZONE_CHANGED) + + assertThat(timeZoneOrLocaleChanges).hasSize(4) + } + + @Test + fun onTimezoneOrLocaleChanged_timeChanged_doesNotEmit() = + testScope.runTest { + val timeZoneOrLocaleChanges by collectValues(underTest.onTimezoneOrLocaleChanged) + assertThat(timeZoneOrLocaleChanges).hasSize(1) + + sendIntentActionBroadcast(Intent.ACTION_TIME_CHANGED) + sendIntentActionBroadcast(Intent.ACTION_TIME_TICK) + + // Expect only 1 event to have been emitted onStart, but no more. + assertThat(timeZoneOrLocaleChanges).hasSize(1) + } + + @Test + fun currentTime_timeChanged() = + testScope.runTest { + val currentTime by collectLastValue(underTest.currentTime) + + sendIntentActionBroadcast(Intent.ACTION_TIME_CHANGED) + val earlierTime = checkNotNull(currentTime) + + advanceTimeBy(3.seconds) + runCurrent() + + sendIntentActionBroadcast(Intent.ACTION_TIME_CHANGED) + val laterTime = checkNotNull(currentTime) + + assertThat(differenceBetween(laterTime, earlierTime)).isEqualTo(3.seconds) + } + + @Test + fun currentTime_timeTicked() = + testScope.runTest { + val currentTime by collectLastValue(underTest.currentTime) + + sendIntentActionBroadcast(Intent.ACTION_TIME_TICK) + val earlierTime = checkNotNull(currentTime) + + advanceTimeBy(7.seconds) + runCurrent() + + sendIntentActionBroadcast(Intent.ACTION_TIME_TICK) + val laterTime = checkNotNull(currentTime) + + assertThat(differenceBetween(laterTime, earlierTime)).isEqualTo(7.seconds) + } + + private fun differenceBetween(date1: Date, date2: Date): Duration { + return (date1.time - date2.time).milliseconds + } + + private fun TestScope.sendIntentActionBroadcast(intentAction: String) { + kosmos.broadcastDispatcher.sendIntentToMatchingReceiversOnly(context, Intent(intentAction)) + runCurrent() + } } private class IntentMatcherAction(private val action: String) : ArgumentMatcher<Intent> { diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/ui/viewmodel/ShadeHeaderViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/ui/viewmodel/ShadeHeaderViewModelTest.kt index 061e04ef29f7..37b4688f753d 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/ui/viewmodel/ShadeHeaderViewModelTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/ui/viewmodel/ShadeHeaderViewModelTest.kt @@ -25,12 +25,15 @@ import com.android.systemui.shade.domain.interactor.disableDualShade import com.android.systemui.shade.domain.interactor.enableDualShade import com.android.systemui.shade.domain.interactor.enableSingleShade import com.android.systemui.shade.domain.interactor.enableSplitShade +import com.android.systemui.shade.domain.interactor.shadeMode +import com.android.systemui.shade.shared.model.ShadeMode import com.android.systemui.shade.ui.viewmodel.ShadeHeaderViewModel.HeaderChipHighlight import com.android.systemui.statusbar.pipeline.mobile.data.model.SubscriptionModel import com.android.systemui.statusbar.pipeline.mobile.domain.interactor.fakeMobileIconsInteractor import com.android.systemui.testKosmos import com.android.systemui.util.mockito.argThat import com.google.common.truth.Truth.assertThat +import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.test.TestScope import kotlinx.coroutines.test.runCurrent @@ -43,6 +46,7 @@ import org.mockito.ArgumentMatchers.anyInt import org.mockito.Mockito.verify import org.mockito.MockitoAnnotations +@OptIn(ExperimentalCoroutinesApi::class) @SmallTest @RunWith(AndroidJUnit4::class) @EnableSceneContainer @@ -64,14 +68,15 @@ class ShadeHeaderViewModelTest : SysuiTestCase() { @Test fun mobileSubIds_update() = testScope.runTest { - val mobileSubIds by collectLastValue(underTest.mobileSubIds) mobileIconsInteractor.filteredSubscriptions.value = listOf(SUB_1) + runCurrent() - assertThat(mobileSubIds).isEqualTo(listOf(1)) + assertThat(underTest.mobileSubIds).isEqualTo(listOf(1)) mobileIconsInteractor.filteredSubscriptions.value = listOf(SUB_1, SUB_2) + runCurrent() - assertThat(mobileSubIds).isEqualTo(listOf(1, 2)) + assertThat(underTest.mobileSubIds).isEqualTo(listOf(1, 2)) } @Test @@ -116,13 +121,9 @@ class ShadeHeaderViewModelTest : SysuiTestCase() { @Test fun onSystemIconChipClicked_lockedOnQsShade_collapsesShadeToLockscreen() = testScope.runTest { - kosmos.enableDualShade() + setupDualShadeState(scene = Scenes.Lockscreen, overlay = Overlays.QuickSettingsShade) val currentScene by collectLastValue(sceneInteractor.currentScene) val currentOverlays by collectLastValue(sceneInteractor.currentOverlays) - setDeviceEntered(false) - setScene(Scenes.Lockscreen) - setOverlay(Overlays.QuickSettingsShade) - assertThat(currentOverlays).isNotEmpty() underTest.onSystemIconChipClicked() runCurrent() @@ -134,13 +135,9 @@ class ShadeHeaderViewModelTest : SysuiTestCase() { @Test fun onSystemIconChipClicked_lockedOnNotifShade_expandsQsShade() = testScope.runTest { - kosmos.enableDualShade() + setupDualShadeState(scene = Scenes.Lockscreen, overlay = Overlays.NotificationsShade) val currentScene by collectLastValue(sceneInteractor.currentScene) val currentOverlays by collectLastValue(sceneInteractor.currentOverlays) - setDeviceEntered(false) - setScene(Scenes.Lockscreen) - setOverlay(Overlays.NotificationsShade) - assertThat(currentOverlays).isNotEmpty() underTest.onSystemIconChipClicked() runCurrent() @@ -166,13 +163,9 @@ class ShadeHeaderViewModelTest : SysuiTestCase() { @Test fun onSystemIconChipClicked_unlockedOnQsShade_collapsesShadeToGone() = testScope.runTest { - kosmos.enableDualShade() + setupDualShadeState(scene = Scenes.Gone, overlay = Overlays.QuickSettingsShade) val currentScene by collectLastValue(sceneInteractor.currentScene) val currentOverlays by collectLastValue(sceneInteractor.currentOverlays) - setDeviceEntered(true) - setScene(Scenes.Gone) - setOverlay(Overlays.QuickSettingsShade) - assertThat(currentOverlays).isNotEmpty() underTest.onSystemIconChipClicked() runCurrent() @@ -184,13 +177,9 @@ class ShadeHeaderViewModelTest : SysuiTestCase() { @Test fun onSystemIconChipClicked_unlockedOnNotifShade_expandsQsShade() = testScope.runTest { - kosmos.enableDualShade() + setupDualShadeState(scene = Scenes.Gone, overlay = Overlays.NotificationsShade) val currentScene by collectLastValue(sceneInteractor.currentScene) val currentOverlays by collectLastValue(sceneInteractor.currentOverlays) - setDeviceEntered(true) - setScene(Scenes.Gone) - setOverlay(Overlays.NotificationsShade) - assertThat(currentOverlays).isNotEmpty() underTest.onSystemIconChipClicked() runCurrent() @@ -203,13 +192,9 @@ class ShadeHeaderViewModelTest : SysuiTestCase() { @Test fun onNotificationIconChipClicked_lockedOnNotifShade_collapsesShadeToLockscreen() = testScope.runTest { - kosmos.enableDualShade() + setupDualShadeState(scene = Scenes.Lockscreen, overlay = Overlays.NotificationsShade) val currentScene by collectLastValue(sceneInteractor.currentScene) val currentOverlays by collectLastValue(sceneInteractor.currentOverlays) - setDeviceEntered(false) - setScene(Scenes.Lockscreen) - setOverlay(Overlays.NotificationsShade) - assertThat(currentOverlays).isNotEmpty() underTest.onNotificationIconChipClicked() runCurrent() @@ -221,13 +206,9 @@ class ShadeHeaderViewModelTest : SysuiTestCase() { @Test fun onNotificationIconChipClicked_lockedOnQsShade_expandsNotifShade() = testScope.runTest { - kosmos.enableDualShade() + setupDualShadeState(scene = Scenes.Lockscreen, overlay = Overlays.QuickSettingsShade) val currentScene by collectLastValue(sceneInteractor.currentScene) val currentOverlays by collectLastValue(sceneInteractor.currentOverlays) - setDeviceEntered(false) - setScene(Scenes.Lockscreen) - setOverlay(Overlays.QuickSettingsShade) - assertThat(currentOverlays).isNotEmpty() underTest.onNotificationIconChipClicked() runCurrent() @@ -240,13 +221,9 @@ class ShadeHeaderViewModelTest : SysuiTestCase() { @Test fun onNotificationIconChipClicked_unlockedOnNotifShade_collapsesShadeToGone() = testScope.runTest { - kosmos.enableDualShade() + setupDualShadeState(scene = Scenes.Gone, overlay = Overlays.NotificationsShade) val currentScene by collectLastValue(sceneInteractor.currentScene) val currentOverlays by collectLastValue(sceneInteractor.currentOverlays) - setDeviceEntered(true) - setScene(Scenes.Gone) - setOverlay(Overlays.NotificationsShade) - assertThat(currentOverlays).isNotEmpty() underTest.onNotificationIconChipClicked() runCurrent() @@ -258,13 +235,9 @@ class ShadeHeaderViewModelTest : SysuiTestCase() { @Test fun onNotificationIconChipClicked_unlockedOnQsShade_expandsNotifShade() = testScope.runTest { - kosmos.enableDualShade() + setupDualShadeState(scene = Scenes.Gone, overlay = Overlays.QuickSettingsShade) val currentScene by collectLastValue(sceneInteractor.currentScene) val currentOverlays by collectLastValue(sceneInteractor.currentOverlays) - setDeviceEntered(true) - setScene(Scenes.Gone) - setOverlay(Overlays.QuickSettingsShade) - assertThat(currentOverlays).isNotEmpty() underTest.onNotificationIconChipClicked() runCurrent() @@ -319,22 +292,13 @@ class ShadeHeaderViewModelTest : SysuiTestCase() { @Test fun highlightChips_notifsOpenInDualShade_notifsStrongQuickSettingsWeak() = testScope.runTest { - kosmos.enableDualShade() - val currentScene by collectLastValue(sceneInteractor.currentScene) - val currentOverlays by collectLastValue(sceneInteractor.currentOverlays) - // Test the lockscreen scenario. - setScene(Scenes.Lockscreen) - setOverlay(Overlays.NotificationsShade) + setupDualShadeState(scene = Scenes.Lockscreen, overlay = Overlays.NotificationsShade) assertThat(underTest.notificationsChipHighlight).isEqualTo(HeaderChipHighlight.Strong) assertThat(underTest.quickSettingsChipHighlight).isEqualTo(HeaderChipHighlight.Weak) // Test the unlocked scenario. - setDeviceEntered(true) - setScene(Scenes.Gone) - setOverlay(Overlays.NotificationsShade) - assertThat(currentScene).isEqualTo(Scenes.Gone) - assertThat(currentOverlays).isNotEmpty() + setupDualShadeState(scene = Scenes.Gone, overlay = Overlays.NotificationsShade) assertThat(underTest.notificationsChipHighlight).isEqualTo(HeaderChipHighlight.Strong) assertThat(underTest.quickSettingsChipHighlight).isEqualTo(HeaderChipHighlight.Weak) } @@ -342,22 +306,13 @@ class ShadeHeaderViewModelTest : SysuiTestCase() { @Test fun highlightChips_quickSettingsOpenInDualShade_notifsWeakQuickSettingsStrong() = testScope.runTest { - kosmos.enableDualShade() - val currentScene by collectLastValue(sceneInteractor.currentScene) - val currentOverlays by collectLastValue(sceneInteractor.currentOverlays) - // Test the lockscreen scenario. - setScene(Scenes.Lockscreen) - setOverlay(Overlays.QuickSettingsShade) + setupDualShadeState(scene = Scenes.Lockscreen, overlay = Overlays.QuickSettingsShade) assertThat(underTest.notificationsChipHighlight).isEqualTo(HeaderChipHighlight.Weak) assertThat(underTest.quickSettingsChipHighlight).isEqualTo(HeaderChipHighlight.Strong) // Test the unlocked scenario. - setDeviceEntered(true) - setScene(Scenes.Gone) - setOverlay(Overlays.QuickSettingsShade) - assertThat(currentScene).isEqualTo(Scenes.Gone) - assertThat(currentOverlays).isNotEmpty() + setupDualShadeState(scene = Scenes.Gone, overlay = Overlays.QuickSettingsShade) assertThat(underTest.notificationsChipHighlight).isEqualTo(HeaderChipHighlight.Weak) assertThat(underTest.quickSettingsChipHighlight).isEqualTo(HeaderChipHighlight.Strong) } @@ -365,21 +320,13 @@ class ShadeHeaderViewModelTest : SysuiTestCase() { @Test fun highlightChips_noOverlaysInDualShade_bothNone() = testScope.runTest { - kosmos.enableDualShade() - val currentScene by collectLastValue(sceneInteractor.currentScene) - val currentOverlays by collectLastValue(sceneInteractor.currentOverlays) - // Test the lockscreen scenario. - setScene(Scenes.Lockscreen) - assertThat(currentOverlays).isEmpty() + setupDualShadeState(scene = Scenes.Lockscreen) assertThat(underTest.notificationsChipHighlight).isEqualTo(HeaderChipHighlight.None) assertThat(underTest.quickSettingsChipHighlight).isEqualTo(HeaderChipHighlight.None) // Test the unlocked scenario. - setDeviceEntered(true) - setScene(Scenes.Gone) - assertThat(currentScene).isEqualTo(Scenes.Gone) - assertThat(currentOverlays).isEmpty() + setupDualShadeState(scene = Scenes.Gone) assertThat(underTest.notificationsChipHighlight).isEqualTo(HeaderChipHighlight.None) assertThat(underTest.quickSettingsChipHighlight).isEqualTo(HeaderChipHighlight.None) } @@ -401,21 +348,43 @@ class ShadeHeaderViewModelTest : SysuiTestCase() { ) } - private fun setScene(key: SceneKey) { - sceneInteractor.changeScene(key, "test") + private fun TestScope.setupDualShadeState(scene: SceneKey, overlay: OverlayKey? = null) { + kosmos.enableDualShade() + val shadeMode by collectLastValue(kosmos.shadeMode) + val currentScene by collectLastValue(sceneInteractor.currentScene) + val currentOverlays by collectLastValue(sceneInteractor.currentOverlays) + if (scene == Scenes.Gone) { + // Unlock the device, marking the device has been entered. + kosmos.fakeDeviceEntryFingerprintAuthRepository.setAuthenticationStatus( + SuccessFingerprintAuthenticationStatus(0, true) + ) + } + runCurrent() + assertThat(shadeMode).isEqualTo(ShadeMode.Dual) + + sceneInteractor.changeScene(scene, "test") + checkNotNull(currentOverlays).forEach { sceneInteractor.instantlyHideOverlay(it, "test") } + runCurrent() + overlay?.let { sceneInteractor.showOverlay(it, "test") } sceneInteractor.setTransitionState( - MutableStateFlow<ObservableTransitionState>(ObservableTransitionState.Idle(key)) + MutableStateFlow<ObservableTransitionState>( + ObservableTransitionState.Idle(scene, setOfNotNull(overlay)) + ) ) - testScope.runCurrent() + runCurrent() + + assertThat(currentScene).isEqualTo(scene) + if (overlay == null) { + assertThat(currentOverlays).isEmpty() + } else { + assertThat(currentOverlays).containsExactly(overlay) + } } - private fun setOverlay(key: OverlayKey) { - val currentOverlays = sceneInteractor.currentOverlays.value + key - sceneInteractor.showOverlay(key, "test") + private fun setScene(key: SceneKey) { + sceneInteractor.changeScene(key, "test") sceneInteractor.setTransitionState( - MutableStateFlow<ObservableTransitionState>( - ObservableTransitionState.Idle(sceneInteractor.currentScene.value, currentOverlays) - ) + MutableStateFlow<ObservableTransitionState>(ObservableTransitionState.Idle(key)) ) testScope.runCurrent() } diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java b/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java index b6537118324e..4c8a8f1c13d7 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java +++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java @@ -33,6 +33,7 @@ import android.graphics.PixelFormat; import android.hardware.biometrics.BiometricAuthenticator.Modality; import android.hardware.biometrics.BiometricConstants; import android.hardware.biometrics.BiometricManager.Authenticators; +import android.hardware.biometrics.BiometricPrompt; import android.hardware.biometrics.PromptInfo; import android.hardware.face.FaceSensorPropertiesInternal; import android.hardware.fingerprint.FingerprintSensorPropertiesInternal; @@ -100,7 +101,7 @@ import javax.inject.Provider; */ @Deprecated public class AuthContainerView extends LinearLayout - implements AuthDialog, WakefulnessLifecycle.Observer, CredentialView.Host { + implements WakefulnessLifecycle.Observer, CredentialView.Host { private static final String TAG = "AuthContainerView"; @@ -158,12 +159,11 @@ public class AuthContainerView extends LinearLayout private final Set<Integer> mFailedModalities = new HashSet<Integer>(); private final OnBackInvokedCallback mBackCallback = this::onBackInvoked; - private final @Background DelayableExecutor mBackgroundExecutor; private final MSDLPlayer mMSDLPlayer; // Non-null only if the dialog is in the act of dismissing and has not sent the reason yet. - @Nullable @AuthDialogCallback.DismissedReason private Integer mPendingCallbackReason; + @Nullable @BiometricPrompt.DismissedReason private Integer mPendingCallbackReason; // HAT received from LockSettingsService when credential is verified. @Nullable private byte[] mCredentialAttestation; @@ -188,18 +188,18 @@ public class AuthContainerView extends LinearLayout final class BiometricCallback implements Spaghetti.Callback { @Override public void onAuthenticated() { - animateAway(AuthDialogCallback.DISMISSED_BIOMETRIC_AUTHENTICATED); + animateAway(BiometricPrompt.DISMISSED_REASON_BIOMETRIC_CONFIRM_NOT_REQUIRED); } @Override public void onUserCanceled() { sendEarlyUserCanceled(); - animateAway(AuthDialogCallback.DISMISSED_USER_CANCELED); + animateAway(BiometricPrompt.DISMISSED_REASON_USER_CANCEL); } @Override public void onButtonNegative() { - animateAway(AuthDialogCallback.DISMISSED_BUTTON_NEGATIVE); + animateAway(BiometricPrompt.DISMISSED_REASON_NEGATIVE); } @Override @@ -210,12 +210,12 @@ public class AuthContainerView extends LinearLayout @Override public void onContentViewMoreOptionsButtonPressed() { - animateAway(AuthDialogCallback.DISMISSED_BUTTON_CONTENT_VIEW_MORE_OPTIONS); + animateAway(BiometricPrompt.DISMISSED_REASON_CONTENT_VIEW_MORE_OPTIONS); } @Override public void onError() { - animateAway(AuthDialogCallback.DISMISSED_ERROR); + animateAway(BiometricPrompt.DISMISSED_REASON_ERROR); } @Override @@ -234,20 +234,20 @@ public class AuthContainerView extends LinearLayout @Override public void onAuthenticatedAndConfirmed() { - animateAway(AuthDialogCallback.DISMISSED_BUTTON_POSITIVE); + animateAway(BiometricPrompt.DISMISSED_REASON_BIOMETRIC_CONFIRMED); } } @Override public void onCredentialMatched(@NonNull byte[] attestation) { mCredentialAttestation = attestation; - animateAway(AuthDialogCallback.DISMISSED_CREDENTIAL_AUTHENTICATED); + animateAway(BiometricPrompt.DISMISSED_REASON_CREDENTIAL_CONFIRMED); } @Override public void onCredentialAborted() { sendEarlyUserCanceled(); - animateAway(AuthDialogCallback.DISMISSED_USER_CANCELED); + animateAway(BiometricPrompt.DISMISSED_REASON_USER_CANCEL); } @Override @@ -277,7 +277,7 @@ public class AuthContainerView extends LinearLayout com.android.settingslib.R.string.failed_attempts_now_wiping_dialog_dismiss, null /* OnClickListener */) .setOnDismissListener( - dialog -> animateAway(AuthDialogCallback.DISMISSED_ERROR)) + dialog -> animateAway(BiometricPrompt.DISMISSED_REASON_ERROR)) .create(); alertDialog.getWindow().setType(WindowManager.LayoutParams.TYPE_KEYGUARD_DIALOG); alertDialog.show(); @@ -349,7 +349,6 @@ public class AuthContainerView extends LinearLayout mPanelView = mLayout.findViewById(R.id.panel); mPanelController = new AuthPanelController(mContext, mPanelView); - mBackgroundExecutor = bgExecutor; mInteractionJankMonitor = jankMonitor; mCredentialViewModelProvider = credentialViewModelProvider; @@ -394,7 +393,7 @@ public class AuthContainerView extends LinearLayout @VisibleForTesting public void onBackInvoked() { sendEarlyUserCanceled(); - animateAway(AuthDialogCallback.DISMISSED_USER_CANCELED); + animateAway(BiometricPrompt.DISMISSED_REASON_USER_CANCEL); } void sendEarlyUserCanceled() { @@ -402,7 +401,6 @@ public class AuthContainerView extends LinearLayout BiometricConstants.BIOMETRIC_SYSTEM_EVENT_EARLY_USER_CANCEL, getRequestId()); } - @Override public boolean isAllowDeviceCredentials() { return Utils.isDeviceCredentialAllowed(mConfig.mPromptInfo); } @@ -450,7 +448,6 @@ public class AuthContainerView extends LinearLayout mPanelController.setContainerDimensions(getMeasuredWidth(), getMeasuredHeight()); } - @Override public void onOrientationChanged() { } @@ -538,10 +535,9 @@ public class AuthContainerView extends LinearLayout @Override public void onStartedGoingToSleep() { - animateAway(AuthDialogCallback.DISMISSED_USER_CANCELED); + animateAway(BiometricPrompt.DISMISSED_REASON_USER_CANCEL); } - @Override public void show(WindowManager wm) { wm.addView(this, getLayoutParams(mWindowToken, mConfig.mPromptInfo.getTitle())); } @@ -559,7 +555,6 @@ public class AuthContainerView extends LinearLayout } } - @Override public void dismissWithoutCallback(boolean animate) { if (animate) { animateAway(false /* sendReason */, 0 /* reason */); @@ -569,12 +564,10 @@ public class AuthContainerView extends LinearLayout } } - @Override public void dismissFromSystemServer() { animateAway(false /* sendReason */, 0 /* reason */); } - @Override public void onAuthenticationSucceeded(@Modality int modality) { if (mBiometricView != null) { mBiometricView.onAuthenticationSucceeded(modality); @@ -583,7 +576,6 @@ public class AuthContainerView extends LinearLayout } } - @Override public void onAuthenticationFailed(@Modality int modality, String failureReason) { if (mBiometricView != null) { mFailedModalities.add(modality); @@ -593,7 +585,6 @@ public class AuthContainerView extends LinearLayout } } - @Override public void onHelp(@Modality int modality, String help) { if (mBiometricView != null) { mBiometricView.onHelp(modality, help); @@ -602,7 +593,6 @@ public class AuthContainerView extends LinearLayout } } - @Override public void onError(@Modality int modality, String error) { if (mBiometricView != null) { mBiometricView.onError(modality, error); @@ -611,7 +601,6 @@ public class AuthContainerView extends LinearLayout } } - @Override public void onPointerDown() { if (mBiometricView != null) { if (mFailedModalities.contains(TYPE_FACE)) { @@ -624,22 +613,18 @@ public class AuthContainerView extends LinearLayout } } - @Override public String getOpPackageName() { return mConfig.mOpPackageName; } - @Override public String getClassNameIfItIsConfirmDeviceCredentialActivity() { return mConfig.mPromptInfo.getClassNameIfItIsConfirmDeviceCredentialActivity(); } - @Override public long getRequestId() { return mConfig.mRequestId; } - @Override public void animateToCredentialUI(boolean isError) { if (mBiometricView != null) { mBiometricView.startTransitionToCredentialUI(isError); @@ -648,11 +633,11 @@ public class AuthContainerView extends LinearLayout } } - void animateAway(@AuthDialogCallback.DismissedReason int reason) { + void animateAway(@BiometricPrompt.DismissedReason int reason) { animateAway(true /* sendReason */, reason); } - private void animateAway(boolean sendReason, @AuthDialogCallback.DismissedReason int reason) { + private void animateAway(boolean sendReason, @BiometricPrompt.DismissedReason int reason) { if (mContainerState == STATE_ANIMATING_IN) { Log.w(TAG, "startDismiss(): waiting for onDialogAnimatedIn"); mContainerState = STATE_PENDING_DISMISS; @@ -732,7 +717,7 @@ public class AuthContainerView extends LinearLayout private void onDialogAnimatedIn() { if (mContainerState == STATE_PENDING_DISMISS) { Log.d(TAG, "onDialogAnimatedIn(): mPendingDismissDialog=true, dismissing now"); - animateAway(AuthDialogCallback.DISMISSED_USER_CANCELED); + animateAway(BiometricPrompt.DISMISSED_REASON_USER_CANCEL); return; } if (mContainerState == STATE_ANIMATING_OUT || mContainerState == STATE_GONE) { @@ -748,7 +733,6 @@ public class AuthContainerView extends LinearLayout } } - @Override public PromptViewModel getViewModel() { return mPromptViewModel; } @@ -776,7 +760,6 @@ public class AuthContainerView extends LinearLayout return lp; } - @Override public void dump(@NonNull PrintWriter pw, @NonNull String[] args) { pw.println(" isAttachedToWindow=" + isAttachedToWindow()); pw.println(" containerState=" + mContainerState); diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java b/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java index eee5f9e34317..68a282018ba4 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java +++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java @@ -163,7 +163,7 @@ public class AuthController implements // TODO: These should just be saved from onSaveState private SomeArgs mCurrentDialogArgs; @VisibleForTesting - AuthDialog mCurrentDialog; + AuthContainerView mCurrentDialog; @NonNull private final WindowManager mWindowManager; @NonNull private final DisplayManager mDisplayManager; @@ -222,7 +222,7 @@ public class AuthController implements closeDialog(BiometricPrompt.DISMISSED_REASON_USER_CANCEL, reasonString); } - private void closeDialog(@DismissedReason int reason, String reasonString) { + private void closeDialog(@BiometricPrompt.DismissedReason int reason, String reasonString) { if (isShowing()) { Log.i(TAG, "Close BP, reason :" + reasonString); mCurrentDialog.dismissWithoutCallback(true /* animate */); @@ -511,60 +511,14 @@ public class AuthController implements } @Override - public void onDismissed(@DismissedReason int reason, - @Nullable byte[] credentialAttestation, long requestId) { - + public void onDismissed(@BiometricPrompt.DismissedReason int reason, + @Nullable byte[] credentialAttestation, long requestId) { if (mCurrentDialog != null && requestId != mCurrentDialog.getRequestId()) { Log.w(TAG, "requestId doesn't match, skip onDismissed"); return; } - switch (reason) { - case AuthDialogCallback.DISMISSED_USER_CANCELED: - sendResultAndCleanUp(BiometricPrompt.DISMISSED_REASON_USER_CANCEL, - credentialAttestation); - break; - - case AuthDialogCallback.DISMISSED_BUTTON_NEGATIVE: - sendResultAndCleanUp(BiometricPrompt.DISMISSED_REASON_NEGATIVE, - credentialAttestation); - break; - - case AuthDialogCallback.DISMISSED_BUTTON_POSITIVE: - sendResultAndCleanUp(BiometricPrompt.DISMISSED_REASON_BIOMETRIC_CONFIRMED, - credentialAttestation); - break; - - case AuthDialogCallback.DISMISSED_BIOMETRIC_AUTHENTICATED: - sendResultAndCleanUp( - BiometricPrompt.DISMISSED_REASON_BIOMETRIC_CONFIRM_NOT_REQUIRED, - credentialAttestation); - break; - - case AuthDialogCallback.DISMISSED_ERROR: - sendResultAndCleanUp(BiometricPrompt.DISMISSED_REASON_ERROR, - credentialAttestation); - break; - - case AuthDialogCallback.DISMISSED_BY_SYSTEM_SERVER: - sendResultAndCleanUp(BiometricPrompt.DISMISSED_REASON_SERVER_REQUESTED, - credentialAttestation); - break; - - case AuthDialogCallback.DISMISSED_CREDENTIAL_AUTHENTICATED: - sendResultAndCleanUp(BiometricPrompt.DISMISSED_REASON_CREDENTIAL_CONFIRMED, - credentialAttestation); - break; - - case AuthDialogCallback.DISMISSED_BUTTON_CONTENT_VIEW_MORE_OPTIONS: - sendResultAndCleanUp( - BiometricPrompt.DISMISSED_REASON_CONTENT_VIEW_MORE_OPTIONS, - credentialAttestation); - break; - default: - Log.e(TAG, "Unhandled reason: " + reason); - break; - } + sendResultAndCleanUp(reason, credentialAttestation); } @Override @@ -699,7 +653,7 @@ public class AuthController implements mUdfpsController.onAodInterrupt(screenX, screenY, major, minor); } - private void sendResultAndCleanUp(@DismissedReason int reason, + private void sendResultAndCleanUp(@BiometricPrompt.DismissedReason int reason, @Nullable byte[] credentialAttestation) { if (mReceiver == null) { Log.e(TAG, "sendResultAndCleanUp: Receiver is null"); @@ -1244,7 +1198,7 @@ public class AuthController implements final long requestId = args.argl2; // Create a new dialog but do not replace the current one yet. - final AuthDialog newDialog = buildDialog( + final AuthContainerView newDialog = buildDialog( mBackgroundExecutor, promptInfo, requireConfirmation, @@ -1327,7 +1281,7 @@ public class AuthController implements return mContext.createDisplayContext(display).getSystemService(WindowManager.class); } - private void onDialogDismissed(@DismissedReason int reason) { + private void onDialogDismissed(@BiometricPrompt.DismissedReason int reason) { if (DEBUG) Log.d(TAG, "onDialogDismissed: " + reason); if (mCurrentDialog == null) { Log.w(TAG, "Dialog already dismissed"); @@ -1361,7 +1315,7 @@ public class AuthController implements } } - protected AuthDialog buildDialog(@Background DelayableExecutor bgExecutor, + protected AuthContainerView buildDialog(@Background DelayableExecutor bgExecutor, PromptInfo promptInfo, boolean requireConfirmation, int userId, int[] sensorIds, String opPackageName, boolean skipIntro, long operationId, long requestId, @NonNull WakefulnessLifecycle wakefulnessLifecycle, @@ -1389,7 +1343,7 @@ public class AuthController implements @Override public void dump(@NonNull PrintWriter pw, @NonNull String[] args) { - final AuthDialog dialog = mCurrentDialog; + final AuthContainerView dialog = mCurrentDialog; pw.println(" mCachedDisplayInfo=" + mCachedDisplayInfo); pw.println(" mScaleFactor=" + mScaleFactor); pw.println(" fingerprintSensorLocationInNaturalOrientation=" diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthDialog.java b/packages/SystemUI/src/com/android/systemui/biometrics/AuthDialog.java deleted file mode 100644 index 861191671ba9..000000000000 --- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthDialog.java +++ /dev/null @@ -1,125 +0,0 @@ -/* - * Copyright (C) 2019 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.systemui.biometrics; - -import android.hardware.biometrics.BiometricAuthenticator.Modality; -import android.view.WindowManager; - -import com.android.systemui.Dumpable; -import com.android.systemui.biometrics.ui.viewmodel.PromptViewModel; - -/** - * Interface for the biometric dialog UI. - * - * TODO(b/287311775): remove along with legacy controller once flag is removed - */ -@Deprecated -public interface AuthDialog extends Dumpable { - - /** - * Parameters used when laying out {@link AuthBiometricView}, its subclasses, and - * {@link AuthPanelController}. - */ - class LayoutParams { - public final int mMediumHeight; - public final int mMediumWidth; - - public LayoutParams(int mediumWidth, int mediumHeight) { - mMediumWidth = mediumWidth; - mMediumHeight = mediumHeight; - } - } - - /** - * Show the dialog. - * @param wm - */ - void show(WindowManager wm); - - /** - * Dismiss the dialog without sending a callback. - */ - void dismissWithoutCallback(boolean animate); - - /** - * Dismiss the dialog. Animate away. - */ - void dismissFromSystemServer(); - - /** - * Biometric authenticated. May be pending user confirmation, or completed. - */ - void onAuthenticationSucceeded(@Modality int modality); - - /** - * Authentication failed (reject, timeout). Dialog stays showing. - * @param modality sensor modality that triggered the error - * @param failureReason message - */ - void onAuthenticationFailed(@Modality int modality, String failureReason); - - /** - * Authentication rejected, or help message received. - * @param modality sensor modality that triggered the help message - * @param help message - */ - void onHelp(@Modality int modality, String help); - - /** - * Authentication failed. Dialog going away. - * @param modality sensor modality that triggered the error - * @param error message - */ - void onError(@Modality int modality, String error); - - /** UDFPS pointer down event. */ - void onPointerDown(); - - /** - * Get the client's package name - */ - String getOpPackageName(); - - /** - * Get the class name of ConfirmDeviceCredentialActivity. Returns null if the direct caller is - * not ConfirmDeviceCredentialActivity. - */ - String getClassNameIfItIsConfirmDeviceCredentialActivity(); - - /** The requestId of the underlying operation within the framework. */ - long getRequestId(); - - /** - * Animate to credential UI. Typically called after biometric is locked out. - */ - void animateToCredentialUI(boolean isError); - - /** - * @return true if device credential is allowed. - */ - boolean isAllowDeviceCredentials(); - - /** - * Called when the device's orientation changed and the dialog may need to do another - * layout. This is most relevant to UDFPS since configuration changes are not sent by - * the framework in equivalent cases (landscape to reverse landscape) but the dialog - * must remain fixed on the physical sensor location. - */ - void onOrientationChanged(); - - PromptViewModel getViewModel(); -} diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthDialogCallback.java b/packages/SystemUI/src/com/android/systemui/biometrics/AuthDialogCallback.java index 024c6eaa75bb..31c63d3c57e0 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthDialogCallback.java +++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthDialogCallback.java @@ -16,40 +16,20 @@ package com.android.systemui.biometrics; -import android.annotation.IntDef; import android.annotation.Nullable; +import android.hardware.biometrics.BiometricPrompt; /** * Callback interface for dialog views. These should be implemented by the controller (e.g. * FingerprintDialogImpl) and passed into their views (e.g. FingerprintDialogView). */ public interface AuthDialogCallback { - - int DISMISSED_USER_CANCELED = 1; - int DISMISSED_BUTTON_NEGATIVE = 2; - int DISMISSED_BUTTON_POSITIVE = 3; - int DISMISSED_BIOMETRIC_AUTHENTICATED = 4; - int DISMISSED_ERROR = 5; - int DISMISSED_BY_SYSTEM_SERVER = 6; - int DISMISSED_CREDENTIAL_AUTHENTICATED = 7; - int DISMISSED_BUTTON_CONTENT_VIEW_MORE_OPTIONS = 8; - - @IntDef({DISMISSED_USER_CANCELED, - DISMISSED_BUTTON_NEGATIVE, - DISMISSED_BUTTON_POSITIVE, - DISMISSED_BIOMETRIC_AUTHENTICATED, - DISMISSED_ERROR, - DISMISSED_BY_SYSTEM_SERVER, - DISMISSED_CREDENTIAL_AUTHENTICATED, - DISMISSED_BUTTON_CONTENT_VIEW_MORE_OPTIONS}) - @interface DismissedReason {} - /** * Invoked when the dialog is dismissed - * @param reason + * @param reason - the {@link BiometricPrompt.DismissedReason} for dismissing * @param credentialAttestation the HAT received from LockSettingsService upon verification */ - void onDismissed(@DismissedReason int reason, + void onDismissed(@BiometricPrompt.DismissedReason int reason, @Nullable byte[] credentialAttestation, long requestId); /** diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDozingTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDozingTransitionInteractor.kt index 6f5f662d6fa3..0700ec639153 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDozingTransitionInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDozingTransitionInteractor.kt @@ -159,6 +159,7 @@ constructor( val isKeyguardOccludedLegacy = keyguardInteractor.isKeyguardOccluded.value val primaryBouncerShowing = keyguardInteractor.primaryBouncerShowing.value val isKeyguardGoingAway = keyguardInteractor.isKeyguardGoingAway.value + val canStartDreaming = dreamManager.canStartDreaming(false) if (!deviceEntryInteractor.isLockscreenEnabled()) { if (!SceneContainerFlag.isEnabled) { @@ -191,6 +192,13 @@ constructor( if (!SceneContainerFlag.isEnabled) { transitionToGlanceableHub() } + } else if (canStartDreaming) { + // If we're waking up to dream, transition directly to dreaming without + // showing the lockscreen. + startTransitionTo( + KeyguardState.DREAMING, + ownerReason = "moving from doze to dream", + ) } else { startTransitionTo(KeyguardState.LOCKSCREEN) } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DozingToDreamingTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DozingToDreamingTransitionViewModel.kt index e6a85c6860c5..9018c58a7e36 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DozingToDreamingTransitionViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DozingToDreamingTransitionViewModel.kt @@ -39,4 +39,6 @@ constructor(animationFlow: KeyguardTransitionAnimationFlow) { ) val lockscreenAlpha: Flow<Float> = transitionAnimation.immediatelyTransitionTo(0f) + // Notifications should not be shown while transitioning to dream. + val notificationAlpha = transitionAnimation.immediatelyTransitionTo(0f) } diff --git a/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeHeaderClockInteractor.kt b/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeHeaderClockInteractor.kt index 186bfcbbc8e2..a4de1d675a15 100644 --- a/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeHeaderClockInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeHeaderClockInteractor.kt @@ -17,11 +17,19 @@ package com.android.systemui.shade.domain.interactor import android.content.Intent +import android.content.IntentFilter +import android.os.UserHandle import android.provider.AlarmClock +import com.android.systemui.broadcast.BroadcastDispatcher import com.android.systemui.dagger.SysUISingleton import com.android.systemui.plugins.ActivityStarter import com.android.systemui.shade.data.repository.ShadeHeaderClockRepository +import com.android.systemui.util.kotlin.emitOnStart +import com.android.systemui.util.time.SystemClock +import java.util.Date import javax.inject.Inject +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.map @SysUISingleton class ShadeHeaderClockInteractor @@ -29,7 +37,20 @@ class ShadeHeaderClockInteractor constructor( private val repository: ShadeHeaderClockRepository, private val activityStarter: ActivityStarter, + private val broadcastDispatcher: BroadcastDispatcher, + private val systemClock: SystemClock, ) { + /** [Flow] that emits `Unit` whenever the timezone or locale has changed. */ + val onTimezoneOrLocaleChanged: Flow<Unit> = + broadcastFlowForActions(Intent.ACTION_TIMEZONE_CHANGED, Intent.ACTION_LOCALE_CHANGED) + .emitOnStart() + + /** [Flow] that emits the current `Date` every minute, or when the system time has changed. */ + val currentTime: Flow<Date> = + broadcastFlowForActions(Intent.ACTION_TIME_TICK, Intent.ACTION_TIME_CHANGED) + .emitOnStart() + .map { Date(systemClock.currentTimeMillis()) } + /** Launch the clock activity. */ fun launchClockActivity() { val nextAlarmIntent = repository.nextAlarmIntent @@ -38,8 +59,22 @@ constructor( } else { activityStarter.postStartActivityDismissingKeyguard( Intent(AlarmClock.ACTION_SHOW_ALARMS), - 0 + 0, ) } } + + /** + * Returns a `Flow` that, when collected, emits `Unit` whenever a broadcast matching one of the + * given [actionsToFilter] is received. + */ + private fun broadcastFlowForActions( + vararg actionsToFilter: String, + user: UserHandle = UserHandle.SYSTEM, + ): Flow<Unit> { + return broadcastDispatcher.broadcastFlow( + filter = IntentFilter().apply { actionsToFilter.forEach(::addAction) }, + user = user, + ) + } } diff --git a/packages/SystemUI/src/com/android/systemui/shade/ui/viewmodel/ShadeHeaderViewModel.kt b/packages/SystemUI/src/com/android/systemui/shade/ui/viewmodel/ShadeHeaderViewModel.kt index 8c38d2e7550c..63161db7902b 100644 --- a/packages/SystemUI/src/com/android/systemui/shade/ui/viewmodel/ShadeHeaderViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/shade/ui/viewmodel/ShadeHeaderViewModel.kt @@ -18,16 +18,13 @@ package com.android.systemui.shade.ui.viewmodel import android.content.Context import android.content.Intent -import android.content.IntentFilter import android.icu.text.DateFormat import android.icu.text.DisplayContext -import android.os.UserHandle import android.provider.Settings import android.view.ViewGroup import androidx.compose.runtime.getValue import com.android.app.tracing.coroutines.launchTraced as launch import com.android.systemui.battery.BatteryMeterViewController -import com.android.systemui.broadcast.BroadcastDispatcher import com.android.systemui.lifecycle.ExclusiveActivatable import com.android.systemui.lifecycle.Hydrator import com.android.systemui.plugins.ActivityStarter @@ -50,18 +47,18 @@ import com.android.systemui.statusbar.pipeline.mobile.domain.interactor.MobileIc import com.android.systemui.statusbar.pipeline.mobile.ui.viewmodel.MobileIconsViewModel import dagger.assisted.AssistedFactory import dagger.assisted.AssistedInject -import java.util.Date import java.util.Locale +import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.awaitCancellation import kotlinx.coroutines.coroutineScope -import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.StateFlow -import kotlinx.coroutines.flow.asStateFlow -import kotlinx.coroutines.flow.launchIn +import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.map -import kotlinx.coroutines.flow.onEach +import kotlinx.coroutines.flow.mapLatest /** Models UI state for the shade header. */ +@OptIn(ExperimentalCoroutinesApi::class) class ShadeHeaderViewModel @AssistedInject constructor( @@ -70,7 +67,7 @@ constructor( private val sceneInteractor: SceneInteractor, private val shadeInteractor: ShadeInteractor, private val shadeModeInteractor: ShadeModeInteractor, - private val mobileIconsInteractor: MobileIconsInteractor, + mobileIconsInteractor: MobileIconsInteractor, val mobileIconsViewModel: MobileIconsViewModel, private val privacyChipInteractor: PrivacyChipInteractor, private val clockInteractor: ShadeHeaderClockInteractor, @@ -78,8 +75,8 @@ constructor( private val batteryMeterViewControllerFactory: BatteryMeterViewController.Factory, val statusBarIconController: StatusBarIconController, val notificationIconContainerStatusBarViewBinder: NotificationIconContainerStatusBarViewBinder, - private val broadcastDispatcher: BroadcastDispatcher, ) : ExclusiveActivatable() { + private val hydrator = Hydrator("ShadeHeaderViewModel.hydrator") val createTintedIconManager: (ViewGroup, StatusBarLocation) -> TintedIconManager = @@ -127,9 +124,16 @@ constructor( /** True if there is exactly one mobile connection. */ val isSingleCarrier: StateFlow<Boolean> = mobileIconsInteractor.isSingleCarrier - private val _mobileSubIds = MutableStateFlow(emptyList<Int>()) /** The list of subscription Ids for current mobile connections. */ - val mobileSubIds: StateFlow<List<Int>> = _mobileSubIds.asStateFlow() + val mobileSubIds: List<Int> by + hydrator.hydratedStateOf( + traceName = "mobileSubIds", + initialValue = emptyList(), + source = + mobileIconsInteractor.filteredSubscriptions.map { list -> + list.map { it.subscriptionId } + }, + ) /** The list of PrivacyItems to be displayed by the privacy chip. */ val privacyItems: StateFlow<List<PrivacyItem>> = privacyChipInteractor.privacyItems @@ -150,45 +154,34 @@ constructor( private val longerPattern = context.getString(R.string.abbrev_wday_month_day_no_year_alarm) private val shorterPattern = context.getString(R.string.abbrev_month_day_no_year) - private val longerDateFormat = MutableStateFlow(getFormatFromPattern(longerPattern)) - private val shorterDateFormat = MutableStateFlow(getFormatFromPattern(shorterPattern)) - private val _shorterDateText: MutableStateFlow<String> = MutableStateFlow("") - val shorterDateText: StateFlow<String> = _shorterDateText.asStateFlow() + private val longerDateFormat: Flow<DateFormat> = + clockInteractor.onTimezoneOrLocaleChanged.mapLatest { getFormatFromPattern(longerPattern) } + private val shorterDateFormat: Flow<DateFormat> = + clockInteractor.onTimezoneOrLocaleChanged.mapLatest { getFormatFromPattern(shorterPattern) } + + val longerDateText: String by + hydrator.hydratedStateOf( + traceName = "longerDateText", + initialValue = "", + source = + combine(longerDateFormat, clockInteractor.currentTime) { format, time -> + format.format(time) + }, + ) - private val _longerDateText: MutableStateFlow<String> = MutableStateFlow("") - val longerDateText: StateFlow<String> = _longerDateText.asStateFlow() + val shorterDateText: String by + hydrator.hydratedStateOf( + traceName = "shorterDateText", + initialValue = "", + source = + combine(shorterDateFormat, clockInteractor.currentTime) { format, time -> + format.format(time) + }, + ) override suspend fun onActivated(): Nothing { coroutineScope { - launch { - broadcastDispatcher - .broadcastFlow( - filter = - IntentFilter().apply { - addAction(Intent.ACTION_TIME_TICK) - addAction(Intent.ACTION_TIME_CHANGED) - addAction(Intent.ACTION_TIMEZONE_CHANGED) - addAction(Intent.ACTION_LOCALE_CHANGED) - }, - user = UserHandle.SYSTEM, - map = { intent, _ -> - intent.action == Intent.ACTION_TIMEZONE_CHANGED || - intent.action == Intent.ACTION_LOCALE_CHANGED - }, - ) - .onEach { invalidateFormats -> updateDateTexts(invalidateFormats) } - .launchIn(this) - } - - launch { updateDateTexts(false) } - - launch { - mobileIconsInteractor.filteredSubscriptions - .map { list -> list.map { it.subscriptionId } } - .collect { _mobileSubIds.value = it } - } - launch { hydrator.activate() } awaitCancellation() @@ -260,26 +253,9 @@ constructor( data object Strong : HeaderChipHighlight } - private fun updateDateTexts(invalidateFormats: Boolean) { - if (invalidateFormats) { - longerDateFormat.value = getFormatFromPattern(longerPattern) - shorterDateFormat.value = getFormatFromPattern(shorterPattern) - } - - val currentTime = Date() - - _longerDateText.value = longerDateFormat.value.format(currentTime) - _shorterDateText.value = shorterDateFormat.value.format(currentTime) - } - private fun getFormatFromPattern(pattern: String?): DateFormat { - val l = Locale.getDefault() - val format = DateFormat.getInstanceForSkeleton(pattern, l) - // The use of CAPITALIZATION_FOR_BEGINNING_OF_SENTENCE instead of - // CAPITALIZATION_FOR_STANDALONE is to address - // https://unicode-org.atlassian.net/browse/ICU-21631 - // TODO(b/229287642): Switch back to CAPITALIZATION_FOR_STANDALONE - format.setContext(DisplayContext.CAPITALIZATION_FOR_BEGINNING_OF_SENTENCE) + val format = DateFormat.getInstanceForSkeleton(pattern, Locale.getDefault()) + format.setContext(DisplayContext.CAPITALIZATION_FOR_STANDALONE) return format } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/headsup/AvalancheController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/headsup/AvalancheController.kt index dba8a9a6ddec..867db8ec8235 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/headsup/AvalancheController.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/headsup/AvalancheController.kt @@ -163,6 +163,7 @@ constructor( if (entry in nextMap) nextMap.remove(entry) if (entry in nextList) nextList.remove(entry) + outcome = "add next" addToNext(entry, runnable) // Shorten headsUpEntryShowing display time @@ -174,7 +175,7 @@ constructor( headsUpEntryShowing!!.updateEntry( /* updatePostTime= */ false, /* updateEarliestRemovalTime= */ false, - /* reason= */ "avalanche duration update", + /* reason= */ "shorten duration of previously-last HUN", ) } } @@ -269,8 +270,10 @@ constructor( } nextList.sort() val entryList = showingList + nextList + val thisKey = getKey(entry) if (entryList.isEmpty()) { - log { "No avalanche HUNs, use default ms: $autoDismissMs" } + headsUpManagerLogger.logAvalancheDuration( + thisKey, autoDismissMs, "No avalanche HUNs, use default", nextKey = "") return autoDismissMs } // entryList.indexOf(entry) returns -1 even when the entry is in entryList @@ -281,27 +284,29 @@ constructor( } } if (thisEntryIndex == -1) { - log { "Untracked entry, use default ms: $autoDismissMs" } + headsUpManagerLogger.logAvalancheDuration( + thisKey, autoDismissMs, "Untracked entry, use default", nextKey = "") return autoDismissMs } val nextEntryIndex = thisEntryIndex + 1 - - // If last entry, use default duration if (nextEntryIndex >= entryList.size) { - log { "Last entry, use default ms: $autoDismissMs" } + headsUpManagerLogger.logAvalancheDuration( + thisKey, autoDismissMs, "Last entry, use default", nextKey = "") return autoDismissMs } val nextEntry = entryList[nextEntryIndex] + val nextKey = getKey(nextEntry) if (nextEntry.compareNonTimeFields(entry) == -1) { - // Next entry is higher priority - log { "Next entry is higher priority: 500ms" } + headsUpManagerLogger.logAvalancheDuration( + thisKey, 500, "LOWER priority than next: ", nextKey) return 500 } else if (nextEntry.compareNonTimeFields(entry) == 0) { - // Next entry is same priority - log { "Next entry is same priority: 1000ms" } + headsUpManagerLogger.logAvalancheDuration( + thisKey, 1000, "SAME priority as next: ", nextKey) return 1000 } else { - log { "Next entry is lower priority, use default ms: $autoDismissMs" } + headsUpManagerLogger.logAvalancheDuration( + thisKey, autoDismissMs, "HIGHER priority than next: ", nextKey) return autoDismissMs } } @@ -355,25 +360,28 @@ constructor( } private fun showNow(entry: HeadsUpEntry, runnableList: MutableList<Runnable>) { - log { "SHOW: " + getKey(entry) } - + headsUpManagerLogger.logAvalancheStage("show", getKey(entry)) uiEventLogger.log(ThrottleEvent.AVALANCHE_THROTTLING_HUN_SHOWN) headsUpEntryShowing = entry - runnableList.forEach { - if (it in debugRunnableLabelMap) { - log { "RUNNABLE: ${debugRunnableLabelMap[it]}" } + runnableList.forEach { runnable -> + if (debug) { + debugRunnableLabelMap[runnable]?.let { label -> + headsUpManagerLogger.logAvalancheStage("run", label) + // Remove label after logging to avoid memory leak + debugRunnableLabelMap.remove(runnable) + } } - it.run() + runnable.run() } } private fun showNext() { - log { "SHOW NEXT" } + headsUpManagerLogger.logAvalancheStage("show next", key = "") headsUpEntryShowing = null if (nextList.isEmpty()) { - log { "NO MORE TO SHOW" } + headsUpManagerLogger.logAvalancheStage("no more", key = "") previousHunKey = "" return } @@ -395,11 +403,7 @@ constructor( debugRunnableLabelMap.remove(r) } } - val queue = ArrayList<String>() - for (entry in listToDrop) { - queue.add("[${getKey(entry)}]") - } - val dropList = java.lang.String.join("\n", queue) + val dropList = listToDrop.joinToString("\n ") { getKey(it) } headsUpManagerLogger.logDroppedHuns(dropList) } clearNext() @@ -424,38 +428,24 @@ constructor( // Methods below are for logging only ========================================================== - private inline fun log(s: () -> String) { - if (debug) { - Log.d(tag, s()) - } - } - private fun getStateStr(): String { - return "\navalanche state:" + - "\n\tshowing: [${getKey(headsUpEntryShowing)}]" + - "\n\tprevious: [$previousHunKey]" + - "\n\tnext list: $nextListStr" + - "\n\tnext map: $nextMapStr" + - "\nBHUM.mHeadsUpEntryMap: " + - baseEntryMapStr() + return "\n[AC state]" + + "\nshow: ${getKey(headsUpEntryShowing)}" + + "\nprevious: $previousHunKey" + + "\n$nextStr" + + "\n[HeadsUpManagerImpl.mHeadsUpEntryMap] " + baseEntryMapStr() + "\n" } - private val nextListStr: String + private val nextStr: String get() { - val queue = ArrayList<String>() - for (entry in nextList) { - queue.add("[${getKey(entry)}]") + val nextListStr = nextList.joinToString("\n ") { getKey(it) } + if (nextList.toSet() == nextMap.keys.toSet()) { + return "next (${nextList.size}):\n $nextListStr" } - return java.lang.String.join("\n", queue) - } - - private val nextMapStr: String - get() { - val queue = ArrayList<String>() - for (entry in nextMap.keys) { - queue.add("[${getKey(entry)}]") - } - return java.lang.String.join("\n", queue) + // This should never happen + val nextMapStr = nextMap.keys.joinToString("\n ") { getKey(it) } + return "next list (${nextList.size}):\n $nextListStr" + + "\nnext map (${nextMap.size}):\n $nextMapStr" } fun getKey(entry: HeadsUpEntry?): String { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/headsup/HeadsUpManagerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/headsup/HeadsUpManagerImpl.java index 7d74a496853f..87b9bf87c680 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/headsup/HeadsUpManagerImpl.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/headsup/HeadsUpManagerImpl.java @@ -856,11 +856,11 @@ public class HeadsUpManagerImpl private String getEntryMapStr() { if (mHeadsUpEntryMap.isEmpty()) { - return "EMPTY"; + return ""; } StringBuilder entryMapStr = new StringBuilder(); for (HeadsUpEntry entry: mHeadsUpEntryMap.values()) { - entryMapStr.append("\n\t").append( + entryMapStr.append("\n ").append( entry.mEntry == null ? "null" : entry.mEntry.getKey()); } return entryMapStr.toString(); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/headsup/HeadsUpManagerLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/headsup/HeadsUpManagerLogger.kt index 65fb9ca558d7..388d357b3b15 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/headsup/HeadsUpManagerLogger.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/headsup/HeadsUpManagerLogger.kt @@ -71,7 +71,7 @@ constructor(@NotificationHeadsUpLog private val buffer: LogBuffer) { str3 = outcome bool1 = isEnabled }, - { "$str1\n\t=> AC[isEnabled:$bool1] update: $str2\n\t=> $str3" }, + { "$str1\n=> AC[enabled:$bool1] update: $str2\n=> $str3" }, ) } @@ -90,7 +90,33 @@ constructor(@NotificationHeadsUpLog private val buffer: LogBuffer) { str3 = outcome bool1 = isEnabled }, - { "$str1\n\t=> AC[isEnabled:$bool1] delete: $str2\n\t=> $str3" }, + { "$str1\n=> AC[enabled:$bool1] delete: $str2\n=> $str3" }, + ) + } + + fun logAvalancheStage(stage: String, key: String) { + buffer.log( + TAG, + INFO, + { + str1 = stage + str2 = key + }, + { "[AC] $str1 $str2" }, + ) + } + + fun logAvalancheDuration(thisKey: String, duration: Int, reason: String, nextKey: String) { + buffer.log( + TAG, + INFO, + { + str1 = thisKey + int1 = duration + str2 = reason + str3 = nextKey + }, + { "[AC] $str1 | $int1 ms | $str2 $str3" }, ) } @@ -325,7 +351,7 @@ constructor(@NotificationHeadsUpLog private val buffer: LogBuffer) { } fun logDroppedHuns(entryList: String) { - buffer.log(TAG, VERBOSE, { str1 = entryList }, { "[AC] Drop HUNs: $str1" }) + buffer.log(TAG, VERBOSE, { str1 = entryList }, { "[AC] dropped:\n $str1" }) } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModel.kt index c0ebe7dd7023..5c81c8e22bfc 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModel.kt @@ -49,6 +49,7 @@ import com.android.systemui.keyguard.ui.viewmodel.AodToGoneTransitionViewModel import com.android.systemui.keyguard.ui.viewmodel.AodToLockscreenTransitionViewModel import com.android.systemui.keyguard.ui.viewmodel.AodToOccludedTransitionViewModel import com.android.systemui.keyguard.ui.viewmodel.AodToPrimaryBouncerTransitionViewModel +import com.android.systemui.keyguard.ui.viewmodel.DozingToDreamingTransitionViewModel import com.android.systemui.keyguard.ui.viewmodel.DozingToGlanceableHubTransitionViewModel import com.android.systemui.keyguard.ui.viewmodel.DozingToLockscreenTransitionViewModel import com.android.systemui.keyguard.ui.viewmodel.DozingToOccludedTransitionViewModel @@ -139,6 +140,7 @@ constructor( private val aodToOccludedTransitionViewModel: AodToOccludedTransitionViewModel, private val aodToGlanceableHubTransitionViewModel: AodToGlanceableHubTransitionViewModel, private val aodToPrimaryBouncerTransitionViewModel: AodToPrimaryBouncerTransitionViewModel, + private val dozingToDreamingTransitionViewModel: DozingToDreamingTransitionViewModel, dozingToGlanceableHubTransitionViewModel: DozingToGlanceableHubTransitionViewModel, private val dozingToLockscreenTransitionViewModel: DozingToLockscreenTransitionViewModel, private val dozingToOccludedTransitionViewModel: DozingToOccludedTransitionViewModel, @@ -577,6 +579,7 @@ constructor( aodToOccludedTransitionViewModel.lockscreenAlpha(viewState), aodToGlanceableHubTransitionViewModel.lockscreenAlpha(viewState), aodToPrimaryBouncerTransitionViewModel.notificationAlpha, + dozingToDreamingTransitionViewModel.notificationAlpha, dozingToLockscreenTransitionViewModel.lockscreenAlpha, dozingToOccludedTransitionViewModel.lockscreenAlpha(viewState), dozingToPrimaryBouncerTransitionViewModel.notificationAlpha, diff --git a/packages/SystemUI/src/com/android/systemui/user/CreateUserActivity.java b/packages/SystemUI/src/com/android/systemui/user/CreateUserActivity.java index 32f2ca6fb696..367f54cf4936 100644 --- a/packages/SystemUI/src/com/android/systemui/user/CreateUserActivity.java +++ b/packages/SystemUI/src/com/android/systemui/user/CreateUserActivity.java @@ -147,7 +147,7 @@ public class CreateUserActivity extends Activity { super.onDestroy(); } - private void addUserNow(String userName, Drawable userIcon, Boolean isAdmin) { + private void addUserNow(String userName, Drawable userIcon, String iconPath, Boolean isAdmin) { mSetupUserDialog.dismiss(); userName = (userName == null || userName.trim().isEmpty()) ? getString(com.android.settingslib.R.string.user_new_user_name) diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/shade/domain/interactor/ShadeHeaderClockInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/shade/domain/interactor/ShadeHeaderClockInteractorKosmos.kt index 6fd7cf6edbe4..2ea2119970ad 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/shade/domain/interactor/ShadeHeaderClockInteractorKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/shade/domain/interactor/ShadeHeaderClockInteractorKosmos.kt @@ -16,14 +16,18 @@ package com.android.systemui.shade.domain.interactor +import com.android.systemui.broadcast.broadcastDispatcher import com.android.systemui.kosmos.Kosmos import com.android.systemui.plugins.activityStarter import com.android.systemui.shade.data.repository.shadeHeaderClockRepository +import com.android.systemui.util.time.systemClock var Kosmos.shadeHeaderClockInteractor: ShadeHeaderClockInteractor by Kosmos.Fixture { ShadeHeaderClockInteractor( repository = shadeHeaderClockRepository, activityStarter = activityStarter, + broadcastDispatcher = broadcastDispatcher, + systemClock = systemClock, ) } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/shade/ui/viewmodel/ShadeHeaderViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/shade/ui/viewmodel/ShadeHeaderViewModelKosmos.kt index 08de73be1128..2f8387b2d0f9 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/shade/ui/viewmodel/ShadeHeaderViewModelKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/shade/ui/viewmodel/ShadeHeaderViewModelKosmos.kt @@ -18,7 +18,6 @@ package com.android.systemui.shade.ui.viewmodel import android.content.applicationContext import com.android.systemui.battery.batteryMeterViewControllerFactory -import com.android.systemui.broadcast.broadcastDispatcher import com.android.systemui.kosmos.Kosmos import com.android.systemui.plugins.activityStarter import com.android.systemui.scene.domain.interactor.sceneInteractor @@ -50,7 +49,6 @@ val Kosmos.shadeHeaderViewModel: ShadeHeaderViewModel by statusBarIconController = mock<StatusBarIconController>(), notificationIconContainerStatusBarViewBinder = mock<NotificationIconContainerStatusBarViewBinder>(), - broadcastDispatcher = broadcastDispatcher, ) } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModelKosmos.kt index 17ef208fe12e..51bb94fd2ab9 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModelKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModelKosmos.kt @@ -30,6 +30,7 @@ import com.android.systemui.keyguard.ui.viewmodel.aodToGoneTransitionViewModel import com.android.systemui.keyguard.ui.viewmodel.aodToLockscreenTransitionViewModel import com.android.systemui.keyguard.ui.viewmodel.aodToOccludedTransitionViewModel import com.android.systemui.keyguard.ui.viewmodel.aodToPrimaryBouncerTransitionViewModel +import com.android.systemui.keyguard.ui.viewmodel.dozingToDreamingTransitionViewModel import com.android.systemui.keyguard.ui.viewmodel.dozingToGlanceableHubTransitionViewModel import com.android.systemui.keyguard.ui.viewmodel.dozingToLockscreenTransitionViewModel import com.android.systemui.keyguard.ui.viewmodel.dozingToOccludedTransitionViewModel @@ -83,6 +84,7 @@ val Kosmos.sharedNotificationContainerViewModel by Fixture { aodToLockscreenTransitionViewModel = aodToLockscreenTransitionViewModel, aodToOccludedTransitionViewModel = aodToOccludedTransitionViewModel, aodToPrimaryBouncerTransitionViewModel = aodToPrimaryBouncerTransitionViewModel, + dozingToDreamingTransitionViewModel = dozingToDreamingTransitionViewModel, dozingToGlanceableHubTransitionViewModel = dozingToGlanceableHubTransitionViewModel, dozingToLockscreenTransitionViewModel = dozingToLockscreenTransitionViewModel, dozingToOccludedTransitionViewModel = dozingToOccludedTransitionViewModel, diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/util/time/FakeSystemClockKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/util/time/FakeSystemClockKosmos.kt index 06af32e69b75..b23ccbfbd93f 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/util/time/FakeSystemClockKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/util/time/FakeSystemClockKosmos.kt @@ -14,12 +14,15 @@ * limitations under the License. */ +@file:OptIn(ExperimentalCoroutinesApi::class) + package com.android.systemui.util.time import com.android.systemui.kosmos.Kosmos import com.android.systemui.kosmos.testScope import com.android.systemui.util.mockito.mock import com.android.systemui.util.mockito.whenever +import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.test.currentTime var Kosmos.systemClock by @@ -27,6 +30,7 @@ var Kosmos.systemClock by mock { whenever(elapsedRealtime()).thenAnswer { testScope.currentTime } whenever(uptimeMillis()).thenAnswer { testScope.currentTime } + whenever(currentTimeMillis()).thenAnswer { testScope.currentTime } } } diff --git a/services/core/java/com/android/server/am/CachedAppOptimizer.java b/services/core/java/com/android/server/am/CachedAppOptimizer.java index b677297dfef2..4bfee1d8398f 100644 --- a/services/core/java/com/android/server/am/CachedAppOptimizer.java +++ b/services/core/java/com/android/server/am/CachedAppOptimizer.java @@ -1464,7 +1464,10 @@ public class CachedAppOptimizer { void onProcessFrozen(ProcessRecord frozenProc) { if (useCompaction()) { synchronized (mProcLock) { - compactApp(frozenProc, CompactProfile.FULL, CompactSource.APP, false); + // only full-compact if process is cached + if (frozenProc.mState.getSetAdj() >= mCompactThrottleMinOomAdj) { + compactApp(frozenProc, CompactProfile.FULL, CompactSource.APP, false); + } } } frozenProc.onProcessFrozen(); diff --git a/services/core/java/com/android/server/display/AutomaticBrightnessController.java b/services/core/java/com/android/server/display/AutomaticBrightnessController.java index 88f5c81231b8..c41b8db1ce75 100644 --- a/services/core/java/com/android/server/display/AutomaticBrightnessController.java +++ b/services/core/java/com/android/server/display/AutomaticBrightnessController.java @@ -333,7 +333,7 @@ public class AutomaticBrightnessController { int ambientLightHorizonLong, float userLux, float userNits, DisplayManagerFlags displayManagerFlags) { mInjector = injector; - mClock = injector.createClock(displayManagerFlags.offloadControlsDozeAutoBrightness()); + mClock = injector.createClock(); mContext = context; mCallbacks = callbacks; mSensorManager = sensorManager; @@ -1402,8 +1402,7 @@ public class AutomaticBrightnessController { public void onSensorChanged(SensorEvent event) { if (mLightSensorEnabled) { // The time received from the sensor is in nano seconds, hence changing it to ms - final long time = (mDisplayManagerFlags.offloadControlsDozeAutoBrightness()) - ? TimeUnit.NANOSECONDS.toMillis(event.timestamp) : mClock.uptimeMillis(); + final long time = TimeUnit.NANOSECONDS.toMillis(event.timestamp); final float lux = event.values[0]; handleLightSensorEvent(time, lux); } @@ -1616,20 +1615,13 @@ public class AutomaticBrightnessController { } private static class RealClock implements Clock { - private final boolean mOffloadControlsDozeBrightness; - - RealClock(boolean offloadControlsDozeBrightness) { - mOffloadControlsDozeBrightness = offloadControlsDozeBrightness; - } - @Override public long uptimeMillis() { return SystemClock.uptimeMillis(); } public long getSensorEventScaleTime() { - return (mOffloadControlsDozeBrightness) - ? SystemClock.elapsedRealtime() : uptimeMillis(); + return SystemClock.elapsedRealtime(); } } @@ -1638,8 +1630,8 @@ public class AutomaticBrightnessController { return BackgroundThread.getHandler(); } - Clock createClock(boolean offloadControlsDozeBrightness) { - return new RealClock(offloadControlsDozeBrightness); + Clock createClock() { + return new RealClock(); } } } diff --git a/services/core/java/com/android/server/display/brightness/DisplayBrightnessStrategySelector.java b/services/core/java/com/android/server/display/brightness/DisplayBrightnessStrategySelector.java index 2c6f37448735..6510441ba28f 100644 --- a/services/core/java/com/android/server/display/brightness/DisplayBrightnessStrategySelector.java +++ b/services/core/java/com/android/server/display/brightness/DisplayBrightnessStrategySelector.java @@ -291,8 +291,7 @@ public class DisplayBrightnessStrategySelector { void setAllowAutoBrightnessWhileDozing( DisplayManagerInternal.DisplayOffloadSession displayOffloadSession) { mAllowAutoBrightnessWhileDozing = mAllowAutoBrightnessWhileDozingConfig; - if (mDisplayManagerFlags.offloadControlsDozeAutoBrightness() - && mDisplayManagerFlags.isDisplayOffloadEnabled() + if (mDisplayManagerFlags.isDisplayOffloadEnabled() && displayOffloadSession != null) { mAllowAutoBrightnessWhileDozing &= displayOffloadSession.allowAutoBrightnessInDoze(); } diff --git a/services/core/java/com/android/server/display/feature/DisplayManagerFlags.java b/services/core/java/com/android/server/display/feature/DisplayManagerFlags.java index e4b595ab7c55..c3057ded66eb 100644 --- a/services/core/java/com/android/server/display/feature/DisplayManagerFlags.java +++ b/services/core/java/com/android/server/display/feature/DisplayManagerFlags.java @@ -156,11 +156,6 @@ public class DisplayManagerFlags { Flags.FLAG_DOZE_BRIGHTNESS_FLOAT, Flags::dozeBrightnessFloat); - private final FlagState mOffloadControlsDozeAutoBrightness = new FlagState( - Flags.FLAG_OFFLOAD_CONTROLS_DOZE_AUTO_BRIGHTNESS, - Flags::offloadControlsDozeAutoBrightness - ); - private final FlagState mPeakRefreshRatePhysicalLimit = new FlagState( Flags.FLAG_ENABLE_PEAK_REFRESH_RATE_PHYSICAL_LIMIT, Flags::enablePeakRefreshRatePhysicalLimit @@ -440,13 +435,6 @@ public class DisplayManagerFlags { return mDozeBrightnessFloat.isEnabled(); } - /** - * @return Whether DisplayOffload should control auto-brightness in doze - */ - public boolean offloadControlsDozeAutoBrightness() { - return mOffloadControlsDozeAutoBrightness.isEnabled(); - } - public boolean isPeakRefreshRatePhysicalLimitEnabled() { return mPeakRefreshRatePhysicalLimit.isEnabled(); } @@ -647,7 +635,6 @@ public class DisplayManagerFlags { pw.println(" " + mResolutionBackupRestore); pw.println(" " + mUseFusionProxSensor); pw.println(" " + mDozeBrightnessFloat); - pw.println(" " + mOffloadControlsDozeAutoBrightness); pw.println(" " + mPeakRefreshRatePhysicalLimit); pw.println(" " + mIgnoreAppPreferredRefreshRate); pw.println(" " + mSynthetic60hzModes); diff --git a/services/core/java/com/android/server/display/feature/display_flags.aconfig b/services/core/java/com/android/server/display/feature/display_flags.aconfig index acdc0e0cf891..f5451307afb7 100644 --- a/services/core/java/com/android/server/display/feature/display_flags.aconfig +++ b/services/core/java/com/android/server/display/feature/display_flags.aconfig @@ -255,17 +255,6 @@ flag { } flag { - name: "offload_controls_doze_auto_brightness" - namespace: "display_manager" - description: "Allows the registered DisplayOffloader to control if auto-brightness is used in doze" - bug: "327392714" - is_fixed_read_only: true - metadata { - purpose: PURPOSE_BUGFIX - } -} - -flag { name: "enable_peak_refresh_rate_physical_limit" namespace: "display_manager" description: "Flag for adding physical refresh rate limit if smooth display setting is on " diff --git a/services/core/java/com/android/server/dreams/DreamManagerService.java b/services/core/java/com/android/server/dreams/DreamManagerService.java index 7e8bb28b6a37..2af74f620c95 100644 --- a/services/core/java/com/android/server/dreams/DreamManagerService.java +++ b/services/core/java/com/android/server/dreams/DreamManagerService.java @@ -569,7 +569,8 @@ public final class DreamManagerService extends SystemService { } private void requestDreamInternal() { - if (isDreamingInternal() && !dreamIsFrontmost() && mController.bringDreamToFront()) { + if (isDreamingInternal() && !dreamIsFrontmost() && mController.bringDreamToFront() + && !isDozingInternal()) { return; } diff --git a/services/core/java/com/android/server/locksettings/recoverablekeystore/OWNERS b/services/core/java/com/android/server/locksettings/recoverablekeystore/OWNERS index bb487fb52c9f..ebf7e6bed064 100644 --- a/services/core/java/com/android/server/locksettings/recoverablekeystore/OWNERS +++ b/services/core/java/com/android/server/locksettings/recoverablekeystore/OWNERS @@ -1,4 +1,3 @@ aseemk@google.com bozhu@google.com dementyev@google.com -robertberry@google.com diff --git a/services/core/java/com/android/server/policy/PhoneWindowManager.java b/services/core/java/com/android/server/policy/PhoneWindowManager.java index 76c5240ab623..95ac908efcf0 100644 --- a/services/core/java/com/android/server/policy/PhoneWindowManager.java +++ b/services/core/java/com/android/server/policy/PhoneWindowManager.java @@ -1163,6 +1163,15 @@ public class PhoneWindowManager implements WindowManagerPolicy { } } + private boolean shouldShowHub() { + final boolean hubEnabled = Settings.Secure.getIntForUser( + mContext.getContentResolver(), Settings.Secure.GLANCEABLE_HUB_ENABLED, + 1, mCurrentUserId) == 1; + + return mUserManagerInternal != null && mUserManagerInternal.isUserUnlocked(mCurrentUserId) + && hubEnabled && mDreamManagerInternal.dreamConditionActive(); + } + @VisibleForTesting void powerPress(long eventTime, int count, int displayId) { // SideFPS still needs to know about suppressed power buttons, in case it needs to block @@ -1251,7 +1260,8 @@ public class PhoneWindowManager implements WindowManagerPolicy { mContext.getContentResolver(), Settings.Secure.GLANCEABLE_HUB_ENABLED, 1, mCurrentUserId) == 1; - if (mDreamManagerInternal.isDreaming() || isKeyguardShowing()) { + if ((mDreamManagerInternal != null && mDreamManagerInternal.isDreaming()) + || isKeyguardShowing()) { // If the device is already dreaming or on keyguard, go to sleep. sleepDefaultDisplayFromPowerButton(eventTime, 0); break; @@ -1261,9 +1271,10 @@ public class PhoneWindowManager implements WindowManagerPolicy { // show hub. boolean keyguardAvailable = !mLockPatternUtils.isLockScreenDisabled( mCurrentUserId); - if (mUserManagerInternal.isUserUnlocked(mCurrentUserId) && hubEnabled - && keyguardAvailable && mDreamManagerInternal.dreamConditionActive()) { - // If the hub can be launched, send a message to keyguard. + if (shouldShowHub() && keyguardAvailable) { + // If the hub can be launched, send a message to keyguard. We do not know if + // the hub is already running or not, keyguard handles turning screen off if + // it is. Bundle options = new Bundle(); options.putBoolean(EXTRA_TRIGGER_HUB, true); lockNow(options); @@ -1324,14 +1335,14 @@ public class PhoneWindowManager implements WindowManagerPolicy { * @param isScreenOn Whether the screen is currently on. * @param noDreamAction The action to perform if dreaming is not possible. */ - private void attemptToDreamFromShortPowerButtonPress( + private boolean attemptToDreamFromShortPowerButtonPress( boolean isScreenOn, Runnable noDreamAction) { if (mShortPressOnPowerBehavior != SHORT_PRESS_POWER_DREAM_OR_SLEEP && mShortPressOnPowerBehavior != SHORT_PRESS_POWER_HUB_OR_DREAM_OR_SLEEP) { // If the power button behavior isn't one that should be able to trigger the dream, give // up. noDreamAction.run(); - return; + return false; } final DreamManagerInternal dreamManagerInternal = getDreamManagerInternal(); @@ -1339,7 +1350,7 @@ public class PhoneWindowManager implements WindowManagerPolicy { Slog.d(TAG, "Can't start dreaming when attempting to dream from short power" + " press (isScreenOn=" + isScreenOn + ")"); noDreamAction.run(); - return; + return false; } synchronized (mLock) { @@ -1350,6 +1361,8 @@ public class PhoneWindowManager implements WindowManagerPolicy { } dreamManagerInternal.requestDream(); + + return true; } /** @@ -2327,6 +2340,10 @@ public class PhoneWindowManager implements WindowManagerPolicy { WindowWakeUpPolicy getWindowWakeUpPolicy() { return new WindowWakeUpPolicy(mContext); } + + DreamManagerInternal getDreamManagerInternal() { + return LocalServices.getService(DreamManagerInternal.class); + } } /** {@inheritDoc} */ @@ -2345,7 +2362,7 @@ public class PhoneWindowManager implements WindowManagerPolicy { mActivityTaskManagerInternal = LocalServices.getService(ActivityTaskManagerInternal.class); mInputManager = mContext.getSystemService(InputManager.class); mInputManagerInternal = LocalServices.getService(InputManagerInternal.class); - mDreamManagerInternal = LocalServices.getService(DreamManagerInternal.class); + mDreamManagerInternal = injector.getDreamManagerInternal(); mPowerManagerInternal = LocalServices.getService(PowerManagerInternal.class); mAppOpsManager = mContext.getSystemService(AppOpsManager.class); mSensorPrivacyManager = mContext.getSystemService(SensorPrivacyManager.class); @@ -6398,6 +6415,17 @@ public class PhoneWindowManager implements WindowManagerPolicy { event.getDisplayId(), event.getKeyCode(), "wakeUpFromWakeKey")) { return; } + + if (!shouldShowHub() + && mShortPressOnPowerBehavior == SHORT_PRESS_POWER_HUB_OR_DREAM_OR_SLEEP + && event.getKeyCode() == KEYCODE_POWER + && attemptToDreamFromShortPowerButtonPress(false, () -> {})) { + // In the case that we should wake to dream and successfully initiate dreaming, do not + // continue waking up. Doing so will exit the dream state and cause UI to react + // accordingly. + return; + } + wakeUpFromWakeKey( event.getEventTime(), event.getKeyCode(), diff --git a/services/core/java/com/android/server/power/stats/processor/BinaryStatePowerStatsProcessor.java b/services/core/java/com/android/server/power/stats/processor/BinaryStatePowerStatsProcessor.java index 5eaaac9570d5..c89dddf45609 100644 --- a/services/core/java/com/android/server/power/stats/processor/BinaryStatePowerStatsProcessor.java +++ b/services/core/java/com/android/server/power/stats/processor/BinaryStatePowerStatsProcessor.java @@ -274,12 +274,13 @@ abstract class BinaryStatePowerStatsProcessor extends PowerStatsProcessor { int uid = uids.get(k); if (stats.getUidStats(mTmpUidStatsArray, uid, proportionalEstimate.stateValues)) { - double power = intermediates.power - * mStatsLayout.getUidUsageDuration(mTmpUidStatsArray) - / intermediates.duration; - mStatsLayout.setUidPowerEstimate(mTmpUidStatsArray, power); - stats.setUidStats(uid, proportionalEstimate.stateValues, - mTmpUidStatsArray); + long duration = mStatsLayout.getUidUsageDuration(mTmpUidStatsArray); + if (duration != 0) { + double power = intermediates.power * duration / intermediates.duration; + mStatsLayout.setUidPowerEstimate(mTmpUidStatsArray, power); + stats.setUidStats(uid, proportionalEstimate.stateValues, + mTmpUidStatsArray); + } } } } diff --git a/services/core/java/com/android/server/power/stats/processor/BluetoothPowerStatsProcessor.java b/services/core/java/com/android/server/power/stats/processor/BluetoothPowerStatsProcessor.java index 9e8b553dfe29..c1cd3acf1656 100644 --- a/services/core/java/com/android/server/power/stats/processor/BluetoothPowerStatsProcessor.java +++ b/services/core/java/com/android/server/power/stats/processor/BluetoothPowerStatsProcessor.java @@ -299,8 +299,10 @@ class BluetoothPowerStatsProcessor extends PowerStatsProcessor { / intermediates.txBytes; } } - mStatsLayout.setUidPowerEstimate(mTmpUidStatsArray, power); - stats.setUidStats(uid, proportionalEstimate.stateValues, mTmpUidStatsArray); + if (power != 0) { + mStatsLayout.setUidPowerEstimate(mTmpUidStatsArray, power); + stats.setUidStats(uid, proportionalEstimate.stateValues, mTmpUidStatsArray); + } } } } diff --git a/services/core/java/com/android/server/power/stats/processor/CpuPowerStatsProcessor.java b/services/core/java/com/android/server/power/stats/processor/CpuPowerStatsProcessor.java index b442dc2655d4..6ce2cf738192 100644 --- a/services/core/java/com/android/server/power/stats/processor/CpuPowerStatsProcessor.java +++ b/services/core/java/com/android/server/power/stats/processor/CpuPowerStatsProcessor.java @@ -545,8 +545,10 @@ class CpuPowerStatsProcessor extends PowerStatsProcessor { power = Math.max(0, power - wakelockPowerEstimate); } - mStatsLayout.setUidPowerEstimate(mTmpUidStatsArray, power); - stats.setUidStats(uid, proportionalEstimate.stateValues, mTmpUidStatsArray); + if (power != 0) { + mStatsLayout.setUidPowerEstimate(mTmpUidStatsArray, power); + stats.setUidStats(uid, proportionalEstimate.stateValues, mTmpUidStatsArray); + } } } } diff --git a/services/core/java/com/android/server/power/stats/processor/CustomEnergyConsumerPowerStatsProcessor.java b/services/core/java/com/android/server/power/stats/processor/CustomEnergyConsumerPowerStatsProcessor.java index 05865e2074dd..76ea7e841106 100644 --- a/services/core/java/com/android/server/power/stats/processor/CustomEnergyConsumerPowerStatsProcessor.java +++ b/services/core/java/com/android/server/power/stats/processor/CustomEnergyConsumerPowerStatsProcessor.java @@ -72,9 +72,12 @@ class CustomEnergyConsumerPowerStatsProcessor extends PowerStatsProcessor { int uid = uids.get(k); if (stats.getUidStats(mTmpUidStatsArray, uid, proportionalEstimate.stateValues)) { - sLayout.setUidPowerEstimate(mTmpUidStatsArray, - uCtoMah(sLayout.getUidConsumedEnergy(mTmpUidStatsArray, 0))); - stats.setUidStats(uid, proportionalEstimate.stateValues, mTmpUidStatsArray); + double power = uCtoMah(sLayout.getUidConsumedEnergy(mTmpUidStatsArray, 0)); + if (power != 0) { + sLayout.setUidPowerEstimate(mTmpUidStatsArray, power); + stats.setUidStats(uid, proportionalEstimate.stateValues, + mTmpUidStatsArray); + } } } } diff --git a/services/core/java/com/android/server/power/stats/processor/MobileRadioPowerStatsProcessor.java b/services/core/java/com/android/server/power/stats/processor/MobileRadioPowerStatsProcessor.java index c5db19bb3f32..a544daad82f1 100644 --- a/services/core/java/com/android/server/power/stats/processor/MobileRadioPowerStatsProcessor.java +++ b/services/core/java/com/android/server/power/stats/processor/MobileRadioPowerStatsProcessor.java @@ -383,8 +383,10 @@ class MobileRadioPowerStatsProcessor extends PowerStatsProcessor { / intermediates.txPackets; } - mStatsLayout.setUidPowerEstimate(mTmpUidStatsArray, power); - stats.setUidStats(uid, proportionalEstimate.stateValues, mTmpUidStatsArray); + if (power != 0) { + mStatsLayout.setUidPowerEstimate(mTmpUidStatsArray, power); + stats.setUidStats(uid, proportionalEstimate.stateValues, mTmpUidStatsArray); + } if (DEBUG) { Slog.d(TAG, "UID: " + uid diff --git a/services/core/java/com/android/server/power/stats/processor/MultiStateStats.java b/services/core/java/com/android/server/power/stats/processor/MultiStateStats.java index 28474a554b38..69325757c79d 100644 --- a/services/core/java/com/android/server/power/stats/processor/MultiStateStats.java +++ b/services/core/java/com/android/server/power/stats/processor/MultiStateStats.java @@ -16,6 +16,7 @@ package com.android.server.power.stats.processor; +import android.annotation.Nullable; import android.util.Slog; import com.android.internal.annotations.VisibleForTesting; @@ -333,9 +334,9 @@ class MultiStateStats { /** * Adds the delta to the metrics. The number of values must correspond to the dimension count - * supplied to the Factory constructor + * supplied to the Factory constructor. Null values is equivalent to an array of zeros. */ - void increment(long[] values, long timestampMs) { + void increment(@Nullable long[] values, long timestampMs) { mCounter.incrementValues(values, timestampMs); mTracking = true; } diff --git a/services/core/java/com/android/server/power/stats/processor/PowerComponentAggregatedPowerStats.java b/services/core/java/com/android/server/power/stats/processor/PowerComponentAggregatedPowerStats.java index 33baad8cc441..f9b9da9171cb 100644 --- a/services/core/java/com/android/server/power/stats/processor/PowerComponentAggregatedPowerStats.java +++ b/services/core/java/com/android/server/power/stats/processor/PowerComponentAggregatedPowerStats.java @@ -72,7 +72,6 @@ class PowerComponentAggregatedPowerStats { private MultiStateStats mDeviceStats; private final SparseArray<MultiStateStats> mStateStats = new SparseArray<>(); private final SparseArray<UidStats> mUidStats = new SparseArray<>(); - private long[] mZeroArray; private static class UidStats { public int[] states; @@ -251,11 +250,8 @@ class PowerComponentAggregatedPowerStats { for (int i = mUidStats.size() - 1; i >= 0; i--) { PowerComponentAggregatedPowerStats.UidStats uidStats = mUidStats.valueAt(i); if (!uidStats.updated && uidStats.stats != null) { - if (mZeroArray == null - || mZeroArray.length != mPowerStatsDescriptor.uidStatsArrayLength) { - mZeroArray = new long[mPowerStatsDescriptor.uidStatsArrayLength]; - } - uidStats.stats.increment(mZeroArray, timestampMs); + // Null stands for an array of zeros + uidStats.stats.increment(null, timestampMs); } uidStats.updated = false; } diff --git a/services/core/java/com/android/server/power/stats/processor/ScreenPowerStatsProcessor.java b/services/core/java/com/android/server/power/stats/processor/ScreenPowerStatsProcessor.java index 5a8e8b40bc3c..9df3d7eea27b 100644 --- a/services/core/java/com/android/server/power/stats/processor/ScreenPowerStatsProcessor.java +++ b/services/core/java/com/android/server/power/stats/processor/ScreenPowerStatsProcessor.java @@ -230,9 +230,11 @@ class ScreenPowerStatsProcessor extends PowerStatsProcessor { int uid = uids.get(j); if (stats.getUidStats(mTmpUidStatsArray, uid, uidStateValues)) { long duration = mStatsLayout.getUidTopActivityDuration(mTmpUidStatsArray); - double power = intermediates.power * duration / totalTopActivityDuration; - mStatsLayout.setUidPowerEstimate(mTmpUidStatsArray, power); - stats.setUidStats(uid, uidStateValues, mTmpUidStatsArray); + if (duration != 0) { + double power = intermediates.power * duration / totalTopActivityDuration; + mStatsLayout.setUidPowerEstimate(mTmpUidStatsArray, power); + stats.setUidStats(uid, uidStateValues, mTmpUidStatsArray); + } } } } diff --git a/services/core/java/com/android/server/power/stats/processor/WifiPowerStatsProcessor.java b/services/core/java/com/android/server/power/stats/processor/WifiPowerStatsProcessor.java index f1797bd2301d..8cc0b6eb6150 100644 --- a/services/core/java/com/android/server/power/stats/processor/WifiPowerStatsProcessor.java +++ b/services/core/java/com/android/server/power/stats/processor/WifiPowerStatsProcessor.java @@ -375,8 +375,10 @@ class WifiPowerStatsProcessor extends PowerStatsProcessor { / intermediates.batchedScanDuration; } } - mStatsLayout.setUidPowerEstimate(mTmpUidStatsArray, power); - stats.setUidStats(uid, proportionalEstimate.stateValues, mTmpUidStatsArray); + if (power != 0) { + mStatsLayout.setUidPowerEstimate(mTmpUidStatsArray, power); + stats.setUidStats(uid, proportionalEstimate.stateValues, mTmpUidStatsArray); + } if (DEBUG) { Slog.d(TAG, "UID: " + uid diff --git a/services/core/java/com/android/server/wm/TaskFragment.java b/services/core/java/com/android/server/wm/TaskFragment.java index a698a9e82929..bbda849262b2 100644 --- a/services/core/java/com/android/server/wm/TaskFragment.java +++ b/services/core/java/com/android/server/wm/TaskFragment.java @@ -1910,7 +1910,8 @@ class TaskFragment extends WindowContainer<WindowContainer> { if (!hasDirectChildActivities()) { return false; } - if (mResumedActivity != null && mTransitionController.isTransientLaunch(mResumedActivity)) { + if (mResumedActivity != null && !mResumedActivity.finishing + && mTransitionController.isTransientLaunch(mResumedActivity)) { // Even if the transient activity is occluded, defer pausing (addToStopping will still // be called) it until the transient transition is done. So the current resuming // activity won't need to wait for additional pause complete. diff --git a/services/tests/displayservicetests/src/com/android/server/display/AutomaticBrightnessControllerTest.java b/services/tests/displayservicetests/src/com/android/server/display/AutomaticBrightnessControllerTest.java index 7d25acd7f5e7..a42116351c2d 100644 --- a/services/tests/displayservicetests/src/com/android/server/display/AutomaticBrightnessControllerTest.java +++ b/services/tests/displayservicetests/src/com/android/server/display/AutomaticBrightnessControllerTest.java @@ -152,7 +152,7 @@ public class AutomaticBrightnessControllerTest { } @Override - AutomaticBrightnessController.Clock createClock(boolean isEnabled) { + AutomaticBrightnessController.Clock createClock() { return new AutomaticBrightnessController.Clock() { @Override public long uptimeMillis() { @@ -618,39 +618,46 @@ public class AutomaticBrightnessControllerTest { long increment = 500; // set autobrightness to low // t = 0 - listener.onSensorChanged(TestUtils.createSensorEvent(mLightSensor, 0)); + listener.onSensorChanged(TestUtils.createSensorEvent(mLightSensor, 0, + (mClock.now() + ANDROID_SLEEP_TIME) * NANO_SECONDS_MULTIPLIER)); // t = 500 mClock.fastForward(increment); - listener.onSensorChanged(TestUtils.createSensorEvent(mLightSensor, 0)); + listener.onSensorChanged(TestUtils.createSensorEvent(mLightSensor, 0, + (mClock.now() + ANDROID_SLEEP_TIME) * NANO_SECONDS_MULTIPLIER)); // t = 1000 mClock.fastForward(increment); - listener.onSensorChanged(TestUtils.createSensorEvent(mLightSensor, 0)); + listener.onSensorChanged(TestUtils.createSensorEvent(mLightSensor, 0, + (mClock.now() + ANDROID_SLEEP_TIME) * NANO_SECONDS_MULTIPLIER)); assertEquals(0.0f, mController.getAmbientLux(), EPSILON); // t = 1500 mClock.fastForward(increment); - listener.onSensorChanged(TestUtils.createSensorEvent(mLightSensor, 0)); + listener.onSensorChanged(TestUtils.createSensorEvent(mLightSensor, 0, + (mClock.now() + ANDROID_SLEEP_TIME) * NANO_SECONDS_MULTIPLIER)); assertEquals(0.0f, mController.getAmbientLux(), EPSILON); // t = 2000 // ensure that our reading is at 0. mClock.fastForward(increment); - listener.onSensorChanged(TestUtils.createSensorEvent(mLightSensor, 0)); + listener.onSensorChanged(TestUtils.createSensorEvent(mLightSensor, 0, + (mClock.now() + ANDROID_SLEEP_TIME) * NANO_SECONDS_MULTIPLIER)); assertEquals(0.0f, mController.getAmbientLux(), EPSILON); // t = 2500 // first 10000 lux sensor event reading mClock.fastForward(increment); - listener.onSensorChanged(TestUtils.createSensorEvent(mLightSensor, 10000)); + listener.onSensorChanged(TestUtils.createSensorEvent(mLightSensor, 10000, + (mClock.now() + ANDROID_SLEEP_TIME) * NANO_SECONDS_MULTIPLIER)); assertTrue(mController.getAmbientLux() > 0.0f); assertTrue(mController.getAmbientLux() < 10000.0f); // t = 3000 // lux reading should still not yet be 10000. mClock.fastForward(increment); - listener.onSensorChanged(TestUtils.createSensorEvent(mLightSensor, 10000)); + listener.onSensorChanged(TestUtils.createSensorEvent(mLightSensor, 10000, + (mClock.now() + ANDROID_SLEEP_TIME) * NANO_SECONDS_MULTIPLIER)); assertTrue(mController.getAmbientLux() > 0.0f); assertTrue(mController.getAmbientLux() < 10000.0f); @@ -659,45 +666,53 @@ public class AutomaticBrightnessControllerTest { // lux has been high (10000) for 1000ms. // lux reading should be 10000 // short horizon (ambient lux) is high, long horizon is still not high - listener.onSensorChanged(TestUtils.createSensorEvent(mLightSensor, 10000)); + listener.onSensorChanged(TestUtils.createSensorEvent(mLightSensor, 10000, + (mClock.now() + ANDROID_SLEEP_TIME) * NANO_SECONDS_MULTIPLIER)); assertEquals(10000.0f, mController.getAmbientLux(), EPSILON); // t = 4000 // stay high mClock.fastForward(increment); - listener.onSensorChanged(TestUtils.createSensorEvent(mLightSensor, 10000)); + listener.onSensorChanged(TestUtils.createSensorEvent(mLightSensor, 10000, + (mClock.now() + ANDROID_SLEEP_TIME) * NANO_SECONDS_MULTIPLIER)); assertEquals(10000.0f, mController.getAmbientLux(), EPSILON); // t = 4500 Mockito.clearInvocations(mBrightnessMappingStrategy); mClock.fastForward(increment); // short horizon is high, long horizon is high too - listener.onSensorChanged(TestUtils.createSensorEvent(mLightSensor, 10000)); + listener.onSensorChanged(TestUtils.createSensorEvent(mLightSensor, 10000, + (mClock.now() + ANDROID_SLEEP_TIME) * NANO_SECONDS_MULTIPLIER)); verify(mBrightnessMappingStrategy, times(1)).getBrightness(10000, null, -1); assertEquals(10000.0f, mController.getAmbientLux(), EPSILON); // t = 5000 mClock.fastForward(increment); - listener.onSensorChanged(TestUtils.createSensorEvent(mLightSensor, 0)); + listener.onSensorChanged(TestUtils.createSensorEvent(mLightSensor, 0, + (mClock.now() + ANDROID_SLEEP_TIME) * NANO_SECONDS_MULTIPLIER)); assertTrue(mController.getAmbientLux() > 0.0f); assertTrue(mController.getAmbientLux() < 10000.0f); // t = 5500 mClock.fastForward(increment); - listener.onSensorChanged(TestUtils.createSensorEvent(mLightSensor, 0)); + listener.onSensorChanged(TestUtils.createSensorEvent(mLightSensor, 0, + (mClock.now() + ANDROID_SLEEP_TIME) * NANO_SECONDS_MULTIPLIER)); assertTrue(mController.getAmbientLux() > 0.0f); assertTrue(mController.getAmbientLux() < 10000.0f); // t = 6000 mClock.fastForward(increment); // ambient lux goes to 0 - listener.onSensorChanged(TestUtils.createSensorEvent(mLightSensor, 0)); + listener.onSensorChanged(TestUtils.createSensorEvent(mLightSensor, 0, + (mClock.now() + ANDROID_SLEEP_TIME) * NANO_SECONDS_MULTIPLIER)); assertEquals(0.0f, mController.getAmbientLux(), EPSILON); // only the values within the horizon should be kept assertArrayEquals(new float[] {10000, 10000, 0, 0, 0}, mController.getLastSensorValues(), EPSILON); - assertArrayEquals(new long[] {4000, 4500, 5000, 5500, 6000}, + assertArrayEquals(new long[]{4000 + ANDROID_SLEEP_TIME, 4500 + ANDROID_SLEEP_TIME, + 5000 + ANDROID_SLEEP_TIME, 5500 + ANDROID_SLEEP_TIME, + 6000 + ANDROID_SLEEP_TIME}, mController.getLastSensorTimestamps()); } @@ -793,7 +808,8 @@ public class AutomaticBrightnessControllerTest { for (int i = 0; i < 1000; i++) { lux += increment; mClock.fastForward(increment); - listener.onSensorChanged(TestUtils.createSensorEvent(mLightSensor, lux)); + listener.onSensorChanged(TestUtils.createSensorEvent(mLightSensor, lux, + (mClock.now() + ANDROID_SLEEP_TIME) * NANO_SECONDS_MULTIPLIER)); } int valuesCount = (int) Math.ceil((double) AMBIENT_LIGHT_HORIZON_LONG / increment + 1); @@ -807,17 +823,17 @@ public class AutomaticBrightnessControllerTest { long sensorTimestamp = mClock.now(); for (int i = valuesCount - 1; i >= 1; i--) { assertEquals(lux, sensorValues[i], EPSILON); - assertEquals(sensorTimestamp, sensorTimestamps[i]); + assertEquals(sensorTimestamp + ANDROID_SLEEP_TIME, sensorTimestamps[i]); lux -= increment; sensorTimestamp -= increment; } assertEquals(lux, sensorValues[0], EPSILON); - assertEquals(mClock.now() - AMBIENT_LIGHT_HORIZON_LONG, sensorTimestamps[0]); + assertEquals(mClock.now() - AMBIENT_LIGHT_HORIZON_LONG + ANDROID_SLEEP_TIME, + sensorTimestamps[0]); } @Test public void testAmbientLuxBuffers_prunedBeyondLongHorizonExceptLatestValue() throws Exception { - when(mDisplayManagerFlags.offloadControlsDozeAutoBrightness()).thenReturn(true); ArgumentCaptor<SensorEventListener> listenerCaptor = ArgumentCaptor.forClass(SensorEventListener.class); verify(mSensorManager).registerListener(listenerCaptor.capture(), eq(mLightSensor), @@ -867,7 +883,8 @@ public class AutomaticBrightnessControllerTest { for (int i = 0; i < 20; i++) { lux += increment1; mClock.fastForward(increment1); - listener.onSensorChanged(TestUtils.createSensorEvent(mLightSensor, lux)); + listener.onSensorChanged(TestUtils.createSensorEvent(mLightSensor, lux, + (mClock.now() + ANDROID_SLEEP_TIME) * NANO_SECONDS_MULTIPLIER)); } int valuesCount = (int) Math.ceil((double) AMBIENT_LIGHT_HORIZON_LONG / increment1 + 1); @@ -877,7 +894,8 @@ public class AutomaticBrightnessControllerTest { for (int i = 0; i < initialCapacity - valuesCount; i++) { lux += increment2; mClock.fastForward(increment2); - listener.onSensorChanged(TestUtils.createSensorEvent(mLightSensor, lux)); + listener.onSensorChanged(TestUtils.createSensorEvent(mLightSensor, lux, + (mClock.now() + ANDROID_SLEEP_TIME) * NANO_SECONDS_MULTIPLIER)); } float[] sensorValues = mController.getLastSensorValues(); @@ -890,7 +908,7 @@ public class AutomaticBrightnessControllerTest { long sensorTimestamp = mClock.now(); for (int i = initialCapacity - 1; i >= 1; i--) { assertEquals(lux, sensorValues[i], EPSILON); - assertEquals(sensorTimestamp, sensorTimestamps[i]); + assertEquals(sensorTimestamp + ANDROID_SLEEP_TIME, sensorTimestamps[i]); if (i >= valuesCount) { lux -= increment2; @@ -901,7 +919,8 @@ public class AutomaticBrightnessControllerTest { } } assertEquals(lux, sensorValues[0], EPSILON); - assertEquals(mClock.now() - AMBIENT_LIGHT_HORIZON_LONG, sensorTimestamps[0]); + assertEquals(mClock.now() - AMBIENT_LIGHT_HORIZON_LONG + ANDROID_SLEEP_TIME, + sensorTimestamps[0]); } @Test @@ -951,25 +970,29 @@ public class AutomaticBrightnessControllerTest { // t = 0 // Initial lux - listener.onSensorChanged(TestUtils.createSensorEvent(mLightSensor, 500)); + listener.onSensorChanged(TestUtils.createSensorEvent(mLightSensor, 500, + (mClock.now() + ANDROID_SLEEP_TIME) * NANO_SECONDS_MULTIPLIER)); assertEquals(500, mController.getAmbientLux(), EPSILON); // t = 1000 // Lux isn't steady yet mClock.fastForward(1000); - listener.onSensorChanged(TestUtils.createSensorEvent(mLightSensor, 1200)); + listener.onSensorChanged(TestUtils.createSensorEvent(mLightSensor, 1200, + (mClock.now() + ANDROID_SLEEP_TIME) * NANO_SECONDS_MULTIPLIER)); assertEquals(500, mController.getAmbientLux(), EPSILON); // t = 1500 // Lux isn't steady yet mClock.fastForward(500); - listener.onSensorChanged(TestUtils.createSensorEvent(mLightSensor, 1200)); + listener.onSensorChanged(TestUtils.createSensorEvent(mLightSensor, 1200, + (mClock.now() + ANDROID_SLEEP_TIME) * NANO_SECONDS_MULTIPLIER)); assertEquals(500, mController.getAmbientLux(), EPSILON); // t = 2500 // Lux is steady now mClock.fastForward(1000); - listener.onSensorChanged(TestUtils.createSensorEvent(mLightSensor, 1200)); + listener.onSensorChanged(TestUtils.createSensorEvent(mLightSensor, 1200, + (mClock.now() + ANDROID_SLEEP_TIME) * NANO_SECONDS_MULTIPLIER)); assertEquals(1200, mController.getAmbientLux(), EPSILON); } @@ -992,25 +1015,29 @@ public class AutomaticBrightnessControllerTest { // t = 0 // Initial lux - listener.onSensorChanged(TestUtils.createSensorEvent(mLightSensor, 1200)); + listener.onSensorChanged(TestUtils.createSensorEvent(mLightSensor, 1200, + (mClock.now() + ANDROID_SLEEP_TIME) * NANO_SECONDS_MULTIPLIER)); assertEquals(1200, mController.getAmbientLux(), EPSILON); // t = 2000 // Lux isn't steady yet mClock.fastForward(2000); - listener.onSensorChanged(TestUtils.createSensorEvent(mLightSensor, 500)); + listener.onSensorChanged(TestUtils.createSensorEvent(mLightSensor, 500, + (mClock.now() + ANDROID_SLEEP_TIME) * NANO_SECONDS_MULTIPLIER)); assertEquals(1200, mController.getAmbientLux(), EPSILON); // t = 2500 // Lux isn't steady yet mClock.fastForward(500); - listener.onSensorChanged(TestUtils.createSensorEvent(mLightSensor, 500)); + listener.onSensorChanged(TestUtils.createSensorEvent(mLightSensor, 500, + (mClock.now() + ANDROID_SLEEP_TIME) * NANO_SECONDS_MULTIPLIER)); assertEquals(1200, mController.getAmbientLux(), EPSILON); // t = 4500 // Lux is steady now mClock.fastForward(2000); - listener.onSensorChanged(TestUtils.createSensorEvent(mLightSensor, 500)); + listener.onSensorChanged(TestUtils.createSensorEvent(mLightSensor, 500, + (mClock.now() + ANDROID_SLEEP_TIME) * NANO_SECONDS_MULTIPLIER)); assertEquals(500, mController.getAmbientLux(), EPSILON); } @@ -1031,19 +1058,22 @@ public class AutomaticBrightnessControllerTest { // t = 0 // Initial lux - listener.onSensorChanged(TestUtils.createSensorEvent(mLightSensor, 500)); + listener.onSensorChanged(TestUtils.createSensorEvent(mLightSensor, 500, + (mClock.now() + ANDROID_SLEEP_TIME) * NANO_SECONDS_MULTIPLIER)); assertEquals(500, mController.getAmbientLux(), EPSILON); // t = 500 // Lux isn't steady yet mClock.fastForward(500); - listener.onSensorChanged(TestUtils.createSensorEvent(mLightSensor, 1200)); + listener.onSensorChanged(TestUtils.createSensorEvent(mLightSensor, 1200, + (mClock.now() + ANDROID_SLEEP_TIME) * NANO_SECONDS_MULTIPLIER)); assertEquals(500, mController.getAmbientLux(), EPSILON); // t = 1500 // Lux is steady now mClock.fastForward(1000); - listener.onSensorChanged(TestUtils.createSensorEvent(mLightSensor, 1200)); + listener.onSensorChanged(TestUtils.createSensorEvent(mLightSensor, 1200, + (mClock.now() + ANDROID_SLEEP_TIME) * NANO_SECONDS_MULTIPLIER)); assertEquals(1200, mController.getAmbientLux(), EPSILON); } @@ -1068,19 +1098,22 @@ public class AutomaticBrightnessControllerTest { // t = 0 // Initial lux - listener.onSensorChanged(TestUtils.createSensorEvent(mLightSensor, 1200)); + listener.onSensorChanged(TestUtils.createSensorEvent(mLightSensor, 1200, + (mClock.now() + ANDROID_SLEEP_TIME) * NANO_SECONDS_MULTIPLIER)); assertEquals(1200, mController.getAmbientLux(), EPSILON); // t = 1000 // Lux isn't steady yet mClock.fastForward(1000); - listener.onSensorChanged(TestUtils.createSensorEvent(mLightSensor, 500)); + listener.onSensorChanged(TestUtils.createSensorEvent(mLightSensor, 500, + (mClock.now() + ANDROID_SLEEP_TIME) * NANO_SECONDS_MULTIPLIER)); assertEquals(1200, mController.getAmbientLux(), EPSILON); // t = 2500 // Lux is steady now mClock.fastForward(1500); - listener.onSensorChanged(TestUtils.createSensorEvent(mLightSensor, 500)); + listener.onSensorChanged(TestUtils.createSensorEvent(mLightSensor, 500, + (mClock.now() + ANDROID_SLEEP_TIME) * NANO_SECONDS_MULTIPLIER)); assertEquals(500, mController.getAmbientLux(), EPSILON); } diff --git a/services/tests/displayservicetests/src/com/android/server/display/DisplayPowerControllerTest.java b/services/tests/displayservicetests/src/com/android/server/display/DisplayPowerControllerTest.java index aed1f9858660..db94958f769e 100644 --- a/services/tests/displayservicetests/src/com/android/server/display/DisplayPowerControllerTest.java +++ b/services/tests/displayservicetests/src/com/android/server/display/DisplayPowerControllerTest.java @@ -1066,7 +1066,6 @@ public final class DisplayPowerControllerTest { com.android.internal.R.bool.config_allowAutoBrightnessWhileDozing, true); mHolder = createDisplayPowerController(DISPLAY_ID, UNIQUE_ID); when(mDisplayManagerFlagsMock.isDisplayOffloadEnabled()).thenReturn(true); - when(mDisplayManagerFlagsMock.offloadControlsDozeAutoBrightness()).thenReturn(true); when(mDisplayOffloadSession.allowAutoBrightnessInDoze()).thenReturn(true); mHolder.dpc.setDisplayOffloadSession(mDisplayOffloadSession); @@ -1172,7 +1171,6 @@ public final class DisplayPowerControllerTest { com.android.internal.R.bool.config_allowAutoBrightnessWhileDozing, true); mHolder = createDisplayPowerController(DISPLAY_ID, UNIQUE_ID); when(mDisplayManagerFlagsMock.isDisplayOffloadEnabled()).thenReturn(true); - when(mDisplayManagerFlagsMock.offloadControlsDozeAutoBrightness()).thenReturn(true); when(mDisplayOffloadSession.allowAutoBrightnessInDoze()).thenReturn(false); mHolder.dpc.setDisplayOffloadSession(mDisplayOffloadSession); diff --git a/services/tests/displayservicetests/src/com/android/server/display/brightness/DisplayBrightnessStrategySelectorTest.java b/services/tests/displayservicetests/src/com/android/server/display/brightness/DisplayBrightnessStrategySelectorTest.java index 2ebb6c2a3ce4..ef39167dbabc 100644 --- a/services/tests/displayservicetests/src/com/android/server/display/brightness/DisplayBrightnessStrategySelectorTest.java +++ b/services/tests/displayservicetests/src/com/android/server/display/brightness/DisplayBrightnessStrategySelectorTest.java @@ -240,7 +240,6 @@ public final class DisplayBrightnessStrategySelectorTest { @Test public void selectStrategyDoesNotSelectDozeStrategyWhenOffloadSessionAutoBrightnessIsEnabled() { - when(mDisplayManagerFlags.offloadControlsDozeAutoBrightness()).thenReturn(true); when(mDisplayManagerFlags.isDisplayOffloadEnabled()).thenReturn(true); when(mDisplayOffloadSession.allowAutoBrightnessInDoze()).thenReturn(true); when(mResources.getBoolean(R.bool.config_allowAutoBrightnessWhileDozing)).thenReturn( @@ -378,7 +377,6 @@ public final class DisplayBrightnessStrategySelectorTest { @Test public void selectStrategy_selectsAutomaticStrategyWhenValid() { when(mDisplayManagerFlags.isRefactorDisplayPowerControllerEnabled()).thenReturn(true); - when(mDisplayManagerFlags.offloadControlsDozeAutoBrightness()).thenReturn(true); when(mDisplayManagerFlags.isDisplayOffloadEnabled()).thenReturn(true); when(mDisplayOffloadSession.allowAutoBrightnessInDoze()).thenReturn(true); when(mResources.getBoolean(R.bool.config_allowAutoBrightnessWhileDozing)).thenReturn( @@ -409,7 +407,6 @@ public final class DisplayBrightnessStrategySelectorTest { @Test public void selectStrategy_doesNotSelectAutomaticStrategyWhenStylusInUse() { when(mDisplayManagerFlags.isRefactorDisplayPowerControllerEnabled()).thenReturn(true); - when(mDisplayManagerFlags.offloadControlsDozeAutoBrightness()).thenReturn(true); when(mDisplayManagerFlags.isDisplayOffloadEnabled()).thenReturn(true); when(mDisplayOffloadSession.allowAutoBrightnessInDoze()).thenReturn(true); when(mResources.getBoolean(R.bool.config_allowAutoBrightnessWhileDozing)).thenReturn( @@ -536,7 +533,6 @@ public final class DisplayBrightnessStrategySelectorTest { @Test public void setAllowAutoBrightnessWhileDozing_enabledWhenConfigAndOffloadSessionAreEnabled() { - when(mDisplayManagerFlags.offloadControlsDozeAutoBrightness()).thenReturn(true); when(mDisplayManagerFlags.isDisplayOffloadEnabled()).thenReturn(true); when(mDisplayOffloadSession.allowAutoBrightnessInDoze()).thenReturn(true); when(mResources.getBoolean(R.bool.config_allowAutoBrightnessWhileDozing)).thenReturn( @@ -550,7 +546,6 @@ public final class DisplayBrightnessStrategySelectorTest { @Test public void setAllowAutoBrightnessWhileDozing_disabledWhenOffloadSessionFlagIsDisabled() { - when(mDisplayManagerFlags.offloadControlsDozeAutoBrightness()).thenReturn(true); when(mDisplayManagerFlags.isDisplayOffloadEnabled()).thenReturn(true); when(mDisplayOffloadSession.allowAutoBrightnessInDoze()).thenReturn(false); when(mResources.getBoolean(R.bool.config_allowAutoBrightnessWhileDozing)).thenReturn( @@ -564,7 +559,6 @@ public final class DisplayBrightnessStrategySelectorTest { @Test public void setAllowAutoBrightnessWhileDozing_disabledWhenABWhileDozingConfigIsDisabled() { - when(mDisplayManagerFlags.offloadControlsDozeAutoBrightness()).thenReturn(true); when(mDisplayManagerFlags.isDisplayOffloadEnabled()).thenReturn(true); when(mDisplayOffloadSession.allowAutoBrightnessInDoze()).thenReturn(true); when(mResources.getBoolean(R.bool.config_allowAutoBrightnessWhileDozing)).thenReturn( @@ -588,7 +582,6 @@ public final class DisplayBrightnessStrategySelectorTest { @Test public void setAllowAutoBrightnessWhileDozing_EnabledWhenFlagsAreDisabled() { - when(mDisplayManagerFlags.offloadControlsDozeAutoBrightness()).thenReturn(true); when(mResources.getBoolean(R.bool.config_allowAutoBrightnessWhileDozing)).thenReturn( true); mDisplayBrightnessStrategySelector = new DisplayBrightnessStrategySelector(mContext, @@ -600,11 +593,5 @@ public final class DisplayBrightnessStrategySelectorTest { mDisplayBrightnessStrategySelector .setAllowAutoBrightnessWhileDozing(mDisplayOffloadSession); assertTrue(mDisplayBrightnessStrategySelector.isAllowAutoBrightnessWhileDozing()); - - when(mDisplayManagerFlags.isDisplayOffloadEnabled()).thenReturn(true); - when(mDisplayManagerFlags.offloadControlsDozeAutoBrightness()).thenReturn(false); - mDisplayBrightnessStrategySelector - .setAllowAutoBrightnessWhileDozing(mDisplayOffloadSession); - assertTrue(mDisplayBrightnessStrategySelector.isAllowAutoBrightnessWhileDozing()); } } diff --git a/services/tests/mockingservicestests/src/com/android/server/am/CachedAppOptimizerTest.java b/services/tests/mockingservicestests/src/com/android/server/am/CachedAppOptimizerTest.java index fa5847560782..4b53f1309337 100644 --- a/services/tests/mockingservicestests/src/com/android/server/am/CachedAppOptimizerTest.java +++ b/services/tests/mockingservicestests/src/com/android/server/am/CachedAppOptimizerTest.java @@ -23,18 +23,11 @@ import static com.android.server.am.ActivityManagerService.Injector; import static com.google.common.truth.Truth.assertThat; import static com.google.common.util.concurrent.MoreExecutors.directExecutor; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertTrue; -import static org.mockito.Mockito.anyInt; import static org.mockito.Mockito.doReturn; -import static org.mockito.Mockito.mock; import static org.mockito.Mockito.spy; -import static org.mockito.Mockito.times; -import static org.mockito.Mockito.verify; -import android.app.ActivityManagerInternal; import android.app.ActivityManagerInternal.FrozenProcessListener; import android.content.ComponentName; import android.content.Context; @@ -44,14 +37,12 @@ import android.os.Handler; import android.os.HandlerThread; import android.os.MessageQueue; import android.os.Process; -import android.os.SystemClock; import android.platform.test.annotations.Presubmit; import android.provider.DeviceConfig; import android.text.TextUtils; import androidx.test.platform.app.InstrumentationRegistry; -import com.android.internal.annotations.GuardedBy; import com.android.modules.utils.testing.ExtendedMockitoRule; import com.android.modules.utils.testing.TestableDeviceConfig; import com.android.server.LocalServices; @@ -68,11 +59,9 @@ import org.mockito.Mock; import java.io.File; import java.io.IOException; -import java.util.ArrayList; import java.util.HashSet; import java.util.Set; import java.util.concurrent.CountDownLatch; -import java.util.concurrent.Executor; import java.util.concurrent.TimeUnit; /** @@ -164,7 +153,7 @@ public final class CachedAppOptimizerTest { app.info.uid = packageUid; // Exact value does not mater, it can be any state for which compaction is allowed. app.mState.setSetProcState(PROCESS_STATE_BOUND_FOREGROUND_SERVICE); - app.mState.setSetAdj(899); + app.mState.setSetAdj(940); app.mState.setCurAdj(940); return app; } diff --git a/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryUsageStatsProviderPerfTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryUsageStatsProviderPerfTest.java index 8fc8c9f677a6..6be9c6d4b80c 100644 --- a/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryUsageStatsProviderPerfTest.java +++ b/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryUsageStatsProviderPerfTest.java @@ -48,6 +48,7 @@ import org.junit.runner.RunWith; import java.io.File; import java.io.FileOutputStream; +import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.nio.file.Files; @@ -121,7 +122,7 @@ public class BatteryUsageStatsProviderPerfTest { } @Test - public void getBatteryUsageStats_accumulated() { + public void getBatteryUsageStats_accumulated() throws IOException { BatteryUsageStatsQuery query = new BatteryUsageStatsQuery.Builder() .setMaxStatsAgeMs(0) .includePowerStateData() @@ -155,6 +156,8 @@ public class BatteryUsageStatsProviderPerfTest { // Verify that all iterations produce the same result assertThat(cpuConsumedPower).isEqualTo(expectedCpuPower); } + stats.close(); + state.resumeTiming(); } } diff --git a/services/tests/powerstatstests/src/com/android/server/power/stats/processor/MultiStateStatsTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/processor/MultiStateStatsTest.java index a232c0c7aec9..3b614bdb38ed 100644 --- a/services/tests/powerstatstests/src/com/android/server/power/stats/processor/MultiStateStatsTest.java +++ b/services/tests/powerstatstests/src/com/android/server/power/stats/processor/MultiStateStatsTest.java @@ -143,6 +143,8 @@ public class MultiStateStatsTest { multiStateStats.increment(new long[]{200, 200}, 5000); + multiStateStats.increment(null, 6000); // No-op + long[] stats = new long[DIMENSION_COUNT]; multiStateStats.getStats(stats, new int[]{0, BatteryConsumer.PROCESS_STATE_FOREGROUND, 0}); // (400 - 100) * 0.5 + (600 - 400) * 0.5 diff --git a/services/tests/wmtests/src/com/android/server/policy/PhoneWindowManagerTests.java b/services/tests/wmtests/src/com/android/server/policy/PhoneWindowManagerTests.java index 32a3b7f2c9cc..22c86eb3a9b8 100644 --- a/services/tests/wmtests/src/com/android/server/policy/PhoneWindowManagerTests.java +++ b/services/tests/wmtests/src/com/android/server/policy/PhoneWindowManagerTests.java @@ -272,6 +272,19 @@ public class PhoneWindowManagerTests { } @Test + public void powerPress_withoutDreamManagerInternal_doesNotCrash() { + when(mDisplayPolicy.isAwake()).thenReturn(true); + mDreamManagerInternal = null; + initPhoneWindowManager(); + + // Power button pressed. + int eventTime = 0; + mPhoneWindowManager.powerPress(eventTime, 1, 0); + + // verify no crash + } + + @Test public void powerPress_hubOrDreamOrSleep_hubAvailableLocks() { when(mDisplayPolicy.isAwake()).thenReturn(true); mContext.getTestablePermissions().setPermission(android.Manifest.permission.DEVICE_POWER, @@ -352,5 +365,10 @@ public class PhoneWindowManagerTests { WindowWakeUpPolicy getWindowWakeUpPolicy() { return mock(WindowWakeUpPolicy.class); } + + @Override + DreamManagerInternal getDreamManagerInternal() { + return mDreamManagerInternal; + } } } diff --git a/tests/Tracing/src/com/android/internal/protolog/ProtoLogCommandHandlerTest.java b/tests/Tracing/src/com/android/internal/protolog/ProtoLogCommandHandlerTest.java index be0c7daebb57..e62a89b17927 100644 --- a/tests/Tracing/src/com/android/internal/protolog/ProtoLogCommandHandlerTest.java +++ b/tests/Tracing/src/com/android/internal/protolog/ProtoLogCommandHandlerTest.java @@ -19,6 +19,7 @@ package com.android.internal.protolog; import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.ArgumentMatchers.contains; import static org.mockito.ArgumentMatchers.endsWith; +import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.atLeast; import static org.mockito.Mockito.times; @@ -157,13 +158,15 @@ public class ProtoLogCommandHandlerTest { cmdHandler.exec(mMockBinder, FileDescriptor.in, FileDescriptor.out, FileDescriptor.err, new String[] { "logcat", "enable", "MY_GROUP" }); - Mockito.verify(mProtoLogConfigurationService).enableProtoLogToLogcat("MY_GROUP"); + Mockito.verify(mProtoLogConfigurationService) + .enableProtoLogToLogcat(Mockito.any(), eq("MY_GROUP")); cmdHandler.exec(mMockBinder, FileDescriptor.in, FileDescriptor.out, FileDescriptor.err, new String[] { "logcat", "enable", "MY_GROUP", "MY_OTHER_GROUP" }); Mockito.verify(mProtoLogConfigurationService) - .enableProtoLogToLogcat("MY_GROUP", "MY_OTHER_GROUP"); + .enableProtoLogToLogcat(Mockito.any(), + eq("MY_GROUP"), eq("MY_OTHER_GROUP")); } @Test @@ -173,13 +176,15 @@ public class ProtoLogCommandHandlerTest { cmdHandler.exec(mMockBinder, FileDescriptor.in, FileDescriptor.out, FileDescriptor.err, new String[] { "logcat", "disable", "MY_GROUP" }); - Mockito.verify(mProtoLogConfigurationService).disableProtoLogToLogcat("MY_GROUP"); + Mockito.verify(mProtoLogConfigurationService) + .disableProtoLogToLogcat(Mockito.any(), eq("MY_GROUP")); cmdHandler.exec(mMockBinder, FileDescriptor.in, FileDescriptor.out, FileDescriptor.err, new String[] { "logcat", "disable", "MY_GROUP", "MY_OTHER_GROUP" }); Mockito.verify(mProtoLogConfigurationService) - .disableProtoLogToLogcat("MY_GROUP", "MY_OTHER_GROUP"); + .disableProtoLogToLogcat(Mockito.any(), + eq("MY_GROUP"), eq("MY_OTHER_GROUP")); } @Test diff --git a/tests/Tracing/src/com/android/internal/protolog/ProtoLogConfigurationServiceTest.java b/tests/Tracing/src/com/android/internal/protolog/ProtoLogConfigurationServiceTest.java index 3be725101252..1f3f91ebf557 100644 --- a/tests/Tracing/src/com/android/internal/protolog/ProtoLogConfigurationServiceTest.java +++ b/tests/Tracing/src/com/android/internal/protolog/ProtoLogConfigurationServiceTest.java @@ -62,6 +62,7 @@ import java.io.File; import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.IOException; +import java.io.PrintWriter; import java.util.List; /** @@ -234,7 +235,7 @@ public class ProtoLogConfigurationServiceTest { service.registerClient(mMockClient, args); Truth.assertThat(service.isLoggingToLogcat(TEST_GROUP)).isFalse(); - service.enableProtoLogToLogcat(TEST_GROUP); + service.enableProtoLogToLogcat(Mockito.mock(PrintWriter.class), TEST_GROUP); Truth.assertThat(service.isLoggingToLogcat(TEST_GROUP)).isTrue(); Mockito.verify(mMockClient).toggleLogcat(eq(true), @@ -251,7 +252,7 @@ public class ProtoLogConfigurationServiceTest { service.registerClient(mMockClient, args); Truth.assertThat(service.isLoggingToLogcat(TEST_GROUP)).isTrue(); - service.disableProtoLogToLogcat(TEST_GROUP); + service.disableProtoLogToLogcat(Mockito.mock(PrintWriter.class), TEST_GROUP); Truth.assertThat(service.isLoggingToLogcat(TEST_GROUP)).isFalse(); Mockito.verify(mMockClient).toggleLogcat(eq(false), @@ -269,7 +270,7 @@ public class ProtoLogConfigurationServiceTest { service.registerClient(mMockClient, args); Truth.assertThat(service.isLoggingToLogcat(TEST_GROUP)).isFalse(); - service.enableProtoLogToLogcat(OTHER_TEST_GROUP); + service.enableProtoLogToLogcat(Mockito.mock(PrintWriter.class), OTHER_TEST_GROUP); Truth.assertThat(service.isLoggingToLogcat(TEST_GROUP)).isFalse(); Mockito.verify(mMockClient, never()).toggleLogcat(anyBoolean(), any()); @@ -280,7 +281,7 @@ public class ProtoLogConfigurationServiceTest { final ProtoLogConfigurationService service = new ProtoLogConfigurationServiceImpl(); Truth.assertThat(service.getGroups()).asList().doesNotContain(TEST_GROUP); - service.enableProtoLogToLogcat(TEST_GROUP); + service.enableProtoLogToLogcat(Mockito.mock(PrintWriter.class), TEST_GROUP); Truth.assertThat(service.isLoggingToLogcat(TEST_GROUP)).isTrue(); final RegisterClientArgs args = new RegisterClientArgs(); diff --git a/tests/testables/src/android/testing/TestableLooper.java b/tests/testables/src/android/testing/TestableLooper.java index 1273826c8ef8..8cd89ce89e83 100644 --- a/tests/testables/src/android/testing/TestableLooper.java +++ b/tests/testables/src/android/testing/TestableLooper.java @@ -273,7 +273,7 @@ public class TestableLooper { messages.add(message); } - // Repost all Messages back to the queuewith a new time. + // Repost all Messages back to the queue with a new time. while (true) { Message message = messages.poll(); if (message == null) { |