diff options
244 files changed, 4746 insertions, 1541 deletions
diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java index 7dbf270672f8..76c1ed619510 100644 --- a/core/java/android/app/ActivityThread.java +++ b/core/java/android/app/ActivityThread.java @@ -355,7 +355,7 @@ public final class ActivityThread extends ClientTransactionHandler private static final String DEFAULT_FULL_BACKUP_AGENT = "android.app.backup.FullBackupAgent"; - private static final long BINDER_CALLBACK_THROTTLE_MS = 10_100L; + private static final long BINDER_CALLBACK_THROTTLE = 10_100L; private long mBinderCallbackLast = -1; /** @@ -7551,13 +7551,12 @@ public final class ActivityThread extends ClientTransactionHandler @Override public void onTransactionError(int pid, int code, int flags, int err) { final long now = SystemClock.uptimeMillis(); - if (now < mBinderCallbackLast + BINDER_CALLBACK_THROTTLE_MS) { + if (now < mBinderCallbackLast + BINDER_CALLBACK_THROTTLE) { Slog.d(TAG, "Too many transaction errors, throttling freezer binder callback."); return; } mBinderCallbackLast = now; try { - Log.wtfStack(TAG, "Binder Transaction Error"); mgr.frozenBinderTransactionDetected(pid, code, flags, err); } catch (RemoteException ex) { throw ex.rethrowFromSystemServer(); diff --git a/core/java/android/app/Notification.java b/core/java/android/app/Notification.java index 329fb00c1d9b..fc3bb0288d67 100644 --- a/core/java/android/app/Notification.java +++ b/core/java/android/app/Notification.java @@ -114,7 +114,7 @@ import com.android.internal.annotations.VisibleForTesting; import com.android.internal.graphics.ColorUtils; import com.android.internal.util.ArrayUtils; import com.android.internal.util.ContrastColorUtil; -import com.android.internal.util.NewlineNormalizer; +import com.android.internal.util.NotificationBigTextNormalizer; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; @@ -3262,12 +3262,12 @@ public class Notification implements Parcelable return cs.toString(); } - private static CharSequence cleanUpNewLines(@Nullable CharSequence charSequence) { + private static CharSequence normalizeBigText(@Nullable CharSequence charSequence) { if (charSequence == null) { return charSequence; } - return NewlineNormalizer.normalizeNewlines(charSequence.toString()); + return NotificationBigTextNormalizer.normalizeBigText(charSequence.toString()); } private static CharSequence removeTextSizeSpans(CharSequence charSequence) { @@ -8566,7 +8566,7 @@ public class Notification implements Parcelable // Replace the text with the big text, but only if the big text is not empty. CharSequence bigTextText = mBuilder.processLegacyText(mBigText); if (Flags.cleanUpSpansAndNewLines()) { - bigTextText = cleanUpNewLines(stripStyling(bigTextText)); + bigTextText = normalizeBigText(stripStyling(bigTextText)); } if (!TextUtils.isEmpty(bigTextText)) { p.text(bigTextText); diff --git a/core/java/android/app/notification.aconfig b/core/java/android/app/notification.aconfig index 6edae0b60fd9..2d783177e0d7 100644 --- a/core/java/android/app/notification.aconfig +++ b/core/java/android/app/notification.aconfig @@ -192,3 +192,10 @@ flag { description: "Removes all custom views" bug: "342602960" } + +flag { + name: "redact_sensitive_content_notifications_on_lockscreen" + namespace: "systemui" + description: "redacts notifications on the lockscreen if they have the 'sensitiveContent' flag" + bug: "343631648" +} diff --git a/core/java/android/companion/virtual/flags/flags.aconfig b/core/java/android/companion/virtual/flags/flags.aconfig index ed5d66227574..1e7815329f3b 100644 --- a/core/java/android/companion/virtual/flags/flags.aconfig +++ b/core/java/android/companion/virtual/flags/flags.aconfig @@ -64,3 +64,13 @@ flag { purpose: PURPOSE_BUGFIX } } + +flag { + namespace: "virtual_devices" + name: "intent_interception_action_matching_fix" + description: "Do not match intents without actions if the filter has actions" + bug: "343805037" + metadata { + purpose: PURPOSE_BUGFIX + } +} diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java index f357ebff9878..793321254a09 100644 --- a/core/java/android/provider/Settings.java +++ b/core/java/android/provider/Settings.java @@ -19930,6 +19930,12 @@ public final class Settings { public static final String NETWORK_LOCATION_OPT_IN = "network_location_opt_in"; /** + * Whether haptics are enabled for Active Unlock on wear. + * @hide + */ + public static final String VIBRATE_FOR_ACTIVE_UNLOCK = "wear_vibrate_for_active_unlock"; + + /** * The custom foreground color. * @hide */ diff --git a/core/java/android/tracing/perfetto/InitArguments.java b/core/java/android/tracing/perfetto/InitArguments.java index da8c273fd14e..b4cb68c242b3 100644 --- a/core/java/android/tracing/perfetto/InitArguments.java +++ b/core/java/android/tracing/perfetto/InitArguments.java @@ -26,6 +26,7 @@ import java.lang.annotation.RetentionPolicy; */ public class InitArguments { public final @PerfettoBackend int backends; + public final int shmemSizeHintKb; /** * @hide @@ -44,11 +45,21 @@ public class InitArguments { // on Linux/Android/Mac uses a named UNIX socket). public static final int PERFETTO_BACKEND_SYSTEM = (1 << 1); - public static InitArguments DEFAULTS = new InitArguments(PERFETTO_BACKEND_SYSTEM); + public static InitArguments DEFAULTS = new InitArguments(PERFETTO_BACKEND_SYSTEM, 0); - public static InitArguments TESTING = new InitArguments(PERFETTO_BACKEND_IN_PROCESS); + public static InitArguments TESTING = new InitArguments(PERFETTO_BACKEND_IN_PROCESS, 0); - public InitArguments(@PerfettoBackend int backends) { + /** + * Perfetto initialization arguments. + * + * @param backends Bitwise-or of backends that should be enabled. + * @param shmemSizeHintKb [Optional] Tune the size of the shared memory buffer between the + * current process and the service backend(s). This is a trade-off between memory footprint and + * the ability to sustain bursts of trace writes. If set, the value must be a multiple of 4KB. + * The value can be ignored if larger than kMaxShmSize (32MB) or not a multiple of 4KB. + */ + public InitArguments(@PerfettoBackend int backends, int shmemSizeHintKb) { this.backends = backends; + this.shmemSizeHintKb = shmemSizeHintKb; } } diff --git a/core/java/android/tracing/perfetto/Producer.java b/core/java/android/tracing/perfetto/Producer.java index a1b3eb754157..13582e8742c3 100644 --- a/core/java/android/tracing/perfetto/Producer.java +++ b/core/java/android/tracing/perfetto/Producer.java @@ -27,8 +27,8 @@ public class Producer { * @param args arguments on how to initialize the Perfetto producer. */ public static void init(InitArguments args) { - nativePerfettoProducerInit(args.backends); + nativePerfettoProducerInit(args.backends, args.shmemSizeHintKb); } - private static native void nativePerfettoProducerInit(int backends); + private static native void nativePerfettoProducerInit(int backends, int shmemSizeHintKb); } diff --git a/core/java/android/view/InsetsController.java b/core/java/android/view/InsetsController.java index a9846fb5751f..eec805b74b07 100644 --- a/core/java/android/view/InsetsController.java +++ b/core/java/android/view/InsetsController.java @@ -303,7 +303,7 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation } /** Not running an animation. */ - @VisibleForTesting(visibility = PACKAGE) + @VisibleForTesting public static final int ANIMATION_TYPE_NONE = -1; /** Running animation will show insets */ @@ -317,7 +317,7 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation public static final int ANIMATION_TYPE_USER = 2; /** Running animation will resize insets */ - @VisibleForTesting(visibility = PACKAGE) + @VisibleForTesting public static final int ANIMATION_TYPE_RESIZE = 3; @Retention(RetentionPolicy.SOURCE) @@ -1712,7 +1712,7 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation mImeSourceConsumer.onWindowFocusLost(); } - @VisibleForTesting(visibility = PACKAGE) + @VisibleForTesting public @AnimationType int getAnimationType(@InsetsType int type) { for (int i = mRunningAnimations.size() - 1; i >= 0; i--) { InsetsAnimationControlRunner control = mRunningAnimations.get(i).runner; diff --git a/core/java/android/view/InsetsSourceConsumer.java b/core/java/android/view/InsetsSourceConsumer.java index 6c670f5d6934..fdb2a6ee1791 100644 --- a/core/java/android/view/InsetsSourceConsumer.java +++ b/core/java/android/view/InsetsSourceConsumer.java @@ -17,7 +17,6 @@ package android.view; import static android.view.InsetsController.ANIMATION_TYPE_NONE; -import static android.view.InsetsController.ANIMATION_TYPE_RESIZE; import static android.view.InsetsController.AnimationType; import static android.view.InsetsController.DEBUG; import static android.view.InsetsSourceConsumerProto.ANIMATION_STATE; @@ -32,7 +31,6 @@ import static com.android.internal.annotations.VisibleForTesting.Visibility.PACK import android.annotation.IntDef; import android.annotation.Nullable; -import android.graphics.Point; import android.graphics.Rect; import android.util.Log; import android.util.proto.ProtoOutputStream; @@ -181,11 +179,10 @@ public class InsetsSourceConsumer { mController.notifyVisibilityChanged(); } - // If there is no animation controlling the leash, make sure the visibility and the - // position is up-to-date. - final int animType = mController.getAnimationType(mType); - if (animType == ANIMATION_TYPE_NONE || animType == ANIMATION_TYPE_RESIZE) { - applyRequestedVisibilityAndPositionToControl(); + // If we have a new leash, make sure visibility is up-to-date, even though we + // didn't want to run an animation above. + if (mController.getAnimationType(mType) == ANIMATION_TYPE_NONE) { + applyRequestedVisibilityToControl(); } // Remove the surface that owned by last control when it lost. @@ -374,27 +371,21 @@ public class InsetsSourceConsumer { if (DEBUG) Log.d(TAG, "updateSource: " + newSource); } - private void applyRequestedVisibilityAndPositionToControl() { - if (mSourceControl == null) { - return; - } - final SurfaceControl leash = mSourceControl.getLeash(); - if (leash == null) { + private void applyRequestedVisibilityToControl() { + if (mSourceControl == null || mSourceControl.getLeash() == null) { return; } final boolean requestedVisible = (mController.getRequestedVisibleTypes() & mType) != 0; - final Point surfacePosition = mSourceControl.getSurfacePosition(); try (Transaction t = mTransactionSupplier.get()) { if (DEBUG) Log.d(TAG, "applyRequestedVisibilityToControl: " + requestedVisible); if (requestedVisible) { - t.show(leash); + t.show(mSourceControl.getLeash()); } else { - t.hide(leash); + t.hide(mSourceControl.getLeash()); } // Ensure the alpha value is aligned with the actual requested visibility. - t.setAlpha(leash, requestedVisible ? 1 : 0); - t.setPosition(leash, surfacePosition.x, surfacePosition.y); + t.setAlpha(mSourceControl.getLeash(), requestedVisible ? 1 : 0); t.apply(); } onPerceptible(requestedVisible); diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java index 95c9d7bb72c5..075ee1b0023f 100644 --- a/core/java/android/view/View.java +++ b/core/java/android/view/View.java @@ -34061,10 +34061,14 @@ public class View implements Drawable.Callback, KeyEvent.Callback, } private float convertVelocityToFrameRate(float velocityPps) { + // From UXR study, premium experience is: + // 1500+ dp/s: 120fps + // 0 - 1500 dp/s: 80fps + // OEMs are likely to modify this to balance battery and user experience for their + // specific device. float density = mAttachInfo.mDensity; float velocityDps = velocityPps / density; - // Choose a frame rate in increments of 10fps - return Math.min(MAX_FRAME_RATE, 60f + (10f * (float) Math.floor(velocityDps / 300f))); + return (velocityDps >= 1500f) ? MAX_FRAME_RATE : 80f; } /** diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java index 496e8992fc41..fd407d6ec3ed 100644 --- a/core/java/android/view/ViewRootImpl.java +++ b/core/java/android/view/ViewRootImpl.java @@ -2790,11 +2790,27 @@ public final class ViewRootImpl implements ViewParent, public void bringChildToFront(View child) { } + // keep in sync with getHostVisibilityReason int getHostVisibility() { return mView != null && (mAppVisible || mForceDecorViewVisibility) ? mView.getVisibility() : View.GONE; } + String getHostVisibilityReason() { + if (mView == null) { + return "mView is null"; + } + if (!mAppVisible && !mForceDecorViewVisibility) { + return "!mAppVisible && !mForceDecorViewVisibility"; + } + switch (mView.getVisibility()) { + case View.VISIBLE: return "View.VISIBLE"; + case View.GONE: return "View.GONE"; + case View.INVISIBLE: return "View.INVISIBLE"; + default: return ""; + } + } + /** * Add LayoutTransition to the list of transitions to be started in the next traversal. * This list will be cleared after the transitions on the list are start()'ed. These @@ -3346,6 +3362,7 @@ public final class ViewRootImpl implements ViewParent, int desiredWindowHeight; final int viewVisibility = getHostVisibility(); + final String viewVisibilityReason = getHostVisibilityReason(); final boolean viewVisibilityChanged = !mFirst && (mViewVisibility != viewVisibility || mNewSurfaceNeeded // Also check for possible double visibility update, which will make current @@ -4220,7 +4237,7 @@ public final class ViewRootImpl implements ViewParent, if (!isViewVisible) { if (mLastTraversalWasVisible) { - logAndTrace("Not drawing due to not visible"); + logAndTrace("Not drawing due to not visible. Reason=" + viewVisibilityReason); } mLastPerformTraversalsSkipDrawReason = "view_not_visible"; if (mPendingTransitions != null && mPendingTransitions.size() > 0) { diff --git a/core/java/android/window/ClientWindowFrames.java b/core/java/android/window/ClientWindowFrames.java index 1bd921b339f6..d5398e6268dc 100644 --- a/core/java/android/window/ClientWindowFrames.java +++ b/core/java/android/window/ClientWindowFrames.java @@ -56,7 +56,16 @@ public class ClientWindowFrames implements Parcelable { public ClientWindowFrames() { } - public ClientWindowFrames(ClientWindowFrames other) { + public ClientWindowFrames(@NonNull ClientWindowFrames other) { + setTo(other); + } + + private ClientWindowFrames(@NonNull Parcel in) { + readFromParcel(in); + } + + /** Updates the current frames to the given frames. */ + public void setTo(@NonNull ClientWindowFrames other) { frame.set(other.frame); displayFrame.set(other.displayFrame); parentFrame.set(other.parentFrame); @@ -67,10 +76,6 @@ public class ClientWindowFrames implements Parcelable { compatScale = other.compatScale; } - private ClientWindowFrames(Parcel in) { - readFromParcel(in); - } - /** Needed for AIDL out parameters. */ public void readFromParcel(Parcel in) { frame.readFromParcel(in); diff --git a/core/java/com/android/internal/util/NotificationBigTextNormalizer.java b/core/java/com/android/internal/util/NotificationBigTextNormalizer.java new file mode 100644 index 000000000000..80d409500ef0 --- /dev/null +++ b/core/java/com/android/internal/util/NotificationBigTextNormalizer.java @@ -0,0 +1,123 @@ +/* + * Copyright (C) 2024 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.internal.util; + + +import android.annotation.NonNull; +import android.os.Trace; + +import java.util.regex.Pattern; + +/** + * Utility class that normalizes BigText style Notification content. + * @hide + */ +public class NotificationBigTextNormalizer { + + private static final Pattern MULTIPLE_NEWLINES = Pattern.compile("\\v(\\s*\\v)?"); + private static final Pattern HORIZONTAL_WHITESPACES = Pattern.compile("\\h+"); + + // Private constructor to prevent instantiation + private NotificationBigTextNormalizer() {} + + /** + * Normalizes the given text by collapsing consecutive new lines into single one and cleaning + * up each line by removing zero-width characters, invisible formatting characters, and + * collapsing consecutive whitespace into single space. + */ + @NonNull + public static String normalizeBigText(@NonNull String text) { + try { + Trace.beginSection("NotifBigTextNormalizer#normalizeBigText"); + text = MULTIPLE_NEWLINES.matcher(text).replaceAll("\n"); + text = HORIZONTAL_WHITESPACES.matcher(text).replaceAll(" "); + text = normalizeLines(text); + return text; + } finally { + Trace.endSection(); + } + } + + /** + * Normalizes lines in a text by removing zero-width characters, invisible formatting + * characters, and collapsing consecutive whitespace into single space. + * + * <p> + * This method processes the input text line by line. It eliminates zero-width + * characters (U+200B to U+200D, U+FEFF, U+034F), invisible formatting + * characters (U+2060 to U+2065, U+206A to U+206F, U+FFF9 to U+FFFB), + * and replaces any sequence of consecutive whitespace characters with a single space. + * </p> + * + * <p> + * Additionally, the method trims trailing whitespace from each line and removes any + * resulting empty lines. + * </p> + */ + @NonNull + private static String normalizeLines(@NonNull String text) { + String[] lines = text.split("\n"); + final StringBuilder textSB = new StringBuilder(text.length()); + for (int i = 0; i < lines.length; i++) { + final String line = lines[i]; + final StringBuilder lineSB = new StringBuilder(line.length()); + boolean spaceSeen = false; + for (int j = 0; j < line.length(); j++) { + final char character = line.charAt(j); + + // Skip ZERO WIDTH characters + if ((character >= '\u200B' && character <= '\u200D') + || character == '\uFEFF' || character == '\u034F') { + continue; + } + // Skip INVISIBLE_FORMATTING_CHARACTERS + if ((character >= '\u2060' && character <= '\u2065') + || (character >= '\u206A' && character <= '\u206F') + || (character >= '\uFFF9' && character <= '\uFFFB')) { + continue; + } + + if (isSpace(character)) { + // eliminate consecutive spaces.... + if (!spaceSeen) { + lineSB.append(" "); + } + spaceSeen = true; + } else { + spaceSeen = false; + lineSB.append(character); + } + } + // trim line. + final String currentLine = lineSB.toString().trim(); + + // don't add empty lines after trim. + if (currentLine.length() > 0) { + if (textSB.length() > 0) { + textSB.append("\n"); + } + textSB.append(currentLine); + } + } + + return textSB.toString(); + } + + private static boolean isSpace(char ch) { + return ch != '\n' && Character.isSpaceChar(ch); + } +} diff --git a/core/jni/android_tracing_PerfettoProducer.cpp b/core/jni/android_tracing_PerfettoProducer.cpp index f8c63c80f769..f55338057718 100644 --- a/core/jni/android_tracing_PerfettoProducer.cpp +++ b/core/jni/android_tracing_PerfettoProducer.cpp @@ -34,15 +34,17 @@ namespace android { -void perfettoProducerInit(JNIEnv* env, jclass clazz, int backends) { +void perfettoProducerInit(JNIEnv* env, jclass clazz, PerfettoBackendTypes backends, + uint32_t shmem_size_hint_kb) { struct PerfettoProducerInitArgs args = PERFETTO_PRODUCER_INIT_ARGS_INIT(); - args.backends = (PerfettoBackendTypes)backends; + args.backends = backends; + args.shmem_size_hint_kb = shmem_size_hint_kb; PerfettoProducerInit(args); } const JNINativeMethod gMethods[] = { /* name, signature, funcPtr */ - {"nativePerfettoProducerInit", "(I)V", (void*)perfettoProducerInit}, + {"nativePerfettoProducerInit", "(II)V", (void*)perfettoProducerInit}, }; int register_android_tracing_PerfettoProducer(JNIEnv* env) { diff --git a/core/jni/android_util_Binder.cpp b/core/jni/android_util_Binder.cpp index 3006e204a9db..2068bd7bc8ea 100644 --- a/core/jni/android_util_Binder.cpp +++ b/core/jni/android_util_Binder.cpp @@ -1411,10 +1411,8 @@ static jboolean android_os_BinderProxy_transact(JNIEnv* env, jobject obj, return JNI_TRUE; } - if (err == FAILED_TRANSACTION) { - env->CallStaticVoidMethod(gBinderOffsets.mClass, gBinderOffsets.mTransactionCallback, - getpid(), code, flags, err); - } + env->CallStaticVoidMethod(gBinderOffsets.mClass, gBinderOffsets.mTransactionCallback, getpid(), + code, flags, err); if (err == UNKNOWN_TRANSACTION) { return JNI_FALSE; diff --git a/core/tests/coretests/src/android/view/ViewFrameRateTest.java b/core/tests/coretests/src/android/view/ViewFrameRateTest.java index 07446e7617aa..abe9c8e337bb 100644 --- a/core/tests/coretests/src/android/view/ViewFrameRateTest.java +++ b/core/tests/coretests/src/android/view/ViewFrameRateTest.java @@ -164,7 +164,7 @@ public class ViewFrameRateTest { mActivityRule.runOnUiThread(() -> { mMovingView.setFrameContentVelocity(1f); mMovingView.invalidate(); - runAfterDraw(() -> assertEquals(60f, mViewRoot.getLastPreferredFrameRate(), 0f)); + runAfterDraw(() -> assertEquals(80f, mViewRoot.getLastPreferredFrameRate(), 0f)); }); waitForAfterDraw(); } @@ -190,7 +190,7 @@ public class ViewFrameRateTest { frameLayout.setFrameContentVelocity(1f); mMovingView.offsetTopAndBottom(100); frameLayout.invalidate(); - runAfterDraw(() -> assertEquals(60f, mViewRoot.getLastPreferredFrameRate(), 0f)); + runAfterDraw(() -> assertEquals(80f, mViewRoot.getLastPreferredFrameRate(), 0f)); }); waitForAfterDraw(); } @@ -435,7 +435,7 @@ public class ViewFrameRateTest { runAfterDraw(() -> { assertEquals(FRAME_RATE_CATEGORY_LOW, mViewRoot.getLastPreferredFrameRateCategory()); - assertEquals(60f, mViewRoot.getLastPreferredFrameRate()); + assertEquals(80f, mViewRoot.getLastPreferredFrameRate()); }); }); waitForAfterDraw(); diff --git a/core/tests/utiltests/src/com/android/internal/util/NewlineNormalizerTest.java b/core/tests/utiltests/src/com/android/internal/util/NewlineNormalizerTest.java deleted file mode 100644 index bcdac610a49d..000000000000 --- a/core/tests/utiltests/src/com/android/internal/util/NewlineNormalizerTest.java +++ /dev/null @@ -1,71 +0,0 @@ -/* - * Copyright (C) 2024 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.internal.util; - -import static junit.framework.Assert.assertEquals; - - -import android.platform.test.annotations.DisabledOnRavenwood; -import android.platform.test.ravenwood.RavenwoodRule; - -import androidx.test.runner.AndroidJUnit4; - -import org.junit.Rule; -import org.junit.Test; -import org.junit.runner.RunWith; - -/** - * Test for {@link NewlineNormalizer} - * @hide - */ -@DisabledOnRavenwood(blockedBy = NewlineNormalizer.class) -@RunWith(AndroidJUnit4.class) -public class NewlineNormalizerTest { - - @Rule - public final RavenwoodRule mRavenwood = new RavenwoodRule(); - - @Test - public void testEmptyInput() { - assertEquals("", NewlineNormalizer.normalizeNewlines("")); - } - - @Test - public void testSingleNewline() { - assertEquals("\n", NewlineNormalizer.normalizeNewlines("\n")); - } - - @Test - public void testMultipleConsecutiveNewlines() { - assertEquals("\n", NewlineNormalizer.normalizeNewlines("\n\n\n\n\n")); - } - - @Test - public void testNewlinesWithSpacesAndTabs() { - String input = "Line 1\n \n \t \n\tLine 2"; - // Adjusted expected output to include the tab character - String expected = "Line 1\n\tLine 2"; - assertEquals(expected, NewlineNormalizer.normalizeNewlines(input)); - } - - @Test - public void testMixedNewlineCharacters() { - String input = "Line 1\r\nLine 2\u000BLine 3\fLine 4\u2028Line 5\u2029Line 6"; - String expected = "Line 1\nLine 2\nLine 3\nLine 4\nLine 5\nLine 6"; - assertEquals(expected, NewlineNormalizer.normalizeNewlines(input)); - } -} diff --git a/core/tests/utiltests/src/com/android/internal/util/NotificationBigTextNormalizerTest.java b/core/tests/utiltests/src/com/android/internal/util/NotificationBigTextNormalizerTest.java new file mode 100644 index 000000000000..1f2e24aa8c68 --- /dev/null +++ b/core/tests/utiltests/src/com/android/internal/util/NotificationBigTextNormalizerTest.java @@ -0,0 +1,148 @@ +/* + * Copyright (C) 2024 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.internal.util; + +import static junit.framework.Assert.assertEquals; + + +import android.platform.test.annotations.DisabledOnRavenwood; +import android.platform.test.ravenwood.RavenwoodRule; + +import androidx.test.runner.AndroidJUnit4; + +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; + +/** + * Test for {@link NotificationBigTextNormalizer} + * @hide + */ +@DisabledOnRavenwood(blockedBy = NotificationBigTextNormalizer.class) +@RunWith(AndroidJUnit4.class) +public class NotificationBigTextNormalizerTest { + + @Rule + public final RavenwoodRule mRavenwood = new RavenwoodRule(); + + + @Test + public void testEmptyInput() { + assertEquals("", NotificationBigTextNormalizer.normalizeBigText("")); + } + + @Test + public void testSingleNewline() { + assertEquals("", NotificationBigTextNormalizer.normalizeBigText("\n")); + } + + @Test + public void testMultipleConsecutiveNewlines() { + assertEquals("", NotificationBigTextNormalizer.normalizeBigText("\n\n\n\n\n")); + } + + @Test + public void testNewlinesWithSpacesAndTabs() { + String input = "Line 1\n \n \t \n\tLine 2"; + // Adjusted expected output to include the tab character + String expected = "Line 1\nLine 2"; + assertEquals(expected, NotificationBigTextNormalizer.normalizeBigText(input)); + } + + @Test + public void testMixedNewlineCharacters() { + String input = "Line 1\r\nLine 2\u000BLine 3\fLine 4\u2028Line 5\u2029Line 6"; + String expected = "Line 1\nLine 2\nLine 3\nLine 4\nLine 5\nLine 6"; + assertEquals(expected, NotificationBigTextNormalizer.normalizeBigText(input)); + } + + @Test + public void testConsecutiveSpaces() { + // Only spaces + assertEquals("This is a test.", NotificationBigTextNormalizer.normalizeBigText("This" + + " is a test.")); + // Zero width characters bw spaces. + assertEquals("This is a test.", NotificationBigTextNormalizer.normalizeBigText("This" + + "\u200B \u200B \u200B \u200B \u200B \u200B \u200B \u200Bis\uFEFF \uFEFF \uFEFF" + + " \uFEFFa \u034F \u034F \u034F \u034F \u034F \u034Ftest.")); + + // Invisible formatting characters bw spaces. + assertEquals("This is a test.", NotificationBigTextNormalizer.normalizeBigText("This" + + "\u2061 \u2061 \u2061 \u2061 \u2061 \u2061 \u2061 \u2061is\u206E \u206E \u206E" + + " \u206Ea \uFFFB \uFFFB \uFFFB \uFFFB \uFFFB \uFFFBtest.")); + // Non breakable spaces + assertEquals("This is a test.", NotificationBigTextNormalizer.normalizeBigText("This" + + "\u00A0\u00A0\u00A0\u00A0\u00A0\u00A0is\u2005 \u2005 \u2005" + + " \u2005a\u2005\u2005\u2005 \u2005\u2005\u2005test.")); + } + + @Test + public void testZeroWidthCharRemoval() { + // Test each character individually + char[] zeroWidthChars = { '\u200B', '\u200C', '\u200D', '\uFEFF', '\u034F' }; + + for (char c : zeroWidthChars) { + String input = "Test" + c + "string"; + String expected = "Teststring"; + assertEquals(expected, NotificationBigTextNormalizer.normalizeBigText(input)); + } + } + + @Test + public void testWhitespaceReplacement() { + assertEquals("This text has horizontal whitespace.", + NotificationBigTextNormalizer.normalizeBigText( + "This\ttext\thas\thorizontal\twhitespace.")); + assertEquals("This text has mixed whitespace.", + NotificationBigTextNormalizer.normalizeBigText( + "This text has \u00A0 mixed\u2009whitespace.")); + assertEquals("This text has leading and trailing whitespace.", + NotificationBigTextNormalizer.normalizeBigText( + "\t This text has leading and trailing whitespace. \n")); + } + + @Test + public void testInvisibleFormattingCharacterRemoval() { + // Test each character individually + char[] invisibleFormattingChars = { + '\u2060', '\u2061', '\u2062', '\u2063', '\u2064', '\u2065', + '\u206A', '\u206B', '\u206C', '\u206D', '\u206E', '\u206F', + '\uFFF9', '\uFFFA', '\uFFFB' + }; + + for (char c : invisibleFormattingChars) { + String input = "Test " + c + "string"; + String expected = "Test string"; + assertEquals(expected, NotificationBigTextNormalizer.normalizeBigText(input)); + } + } + @Test + public void testNonBreakSpaceReplacement() { + // Test each character individually + char[] nonBreakSpaces = { + '\u00A0', '\u1680', '\u2000', '\u2001', '\u2002', + '\u2003', '\u2004', '\u2005', '\u2006', '\u2007', + '\u2008', '\u2009', '\u200A', '\u202F', '\u205F', '\u3000' + }; + + for (char c : nonBreakSpaces) { + String input = "Test" + c + "string"; + String expected = "Test string"; + assertEquals(expected, NotificationBigTextNormalizer.normalizeBigText(input)); + } + } +} diff --git a/keystore/java/android/security/keystore/KeyGenParameterSpec.java b/keystore/java/android/security/keystore/KeyGenParameterSpec.java index d359a9050a0f..3cff91597939 100644 --- a/keystore/java/android/security/keystore/KeyGenParameterSpec.java +++ b/keystore/java/android/security/keystore/KeyGenParameterSpec.java @@ -1149,6 +1149,8 @@ public final class KeyGenParameterSpec implements AlgorithmParameterSpec, UserAu /** * Sets the serial number used for the certificate of the generated key pair. + * To ensure compatibility with devices and certificate parsers, the value + * should be 20 bytes or shorter (see RFC 5280 section 4.1.2.2). * * <p>By default, the serial number is {@code 1}. */ diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/DividerPresenter.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/DividerPresenter.java index 774b2129497d..23dc96c39bde 100644 --- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/DividerPresenter.java +++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/DividerPresenter.java @@ -245,11 +245,9 @@ class DividerPresenter implements View.OnTouchListener { isReversedLayout, parentInfo.getDisplayId(), isDraggableExpandType, - getContainerBackgroundColor( - primaryContainer, DEFAULT_PRIMARY_VEIL_COLOR), - getContainerBackgroundColor( - secondaryContainer, DEFAULT_SECONDARY_VEIL_COLOR) - )); + primaryContainer, + secondaryContainer) + ); } } @@ -965,8 +963,10 @@ class DividerPresenter implements View.OnTouchListener { private final int mDisplayId; private final boolean mIsReversedLayout; private final boolean mIsDraggableExpandType; - private final Color mPrimaryVeilColor; - private final Color mSecondaryVeilColor; + @NonNull + private final TaskFragmentContainer mPrimaryContainer; + @NonNull + private final TaskFragmentContainer mSecondaryContainer; private final int mDividerWidthPx; @VisibleForTesting @@ -979,8 +979,8 @@ class DividerPresenter implements View.OnTouchListener { boolean isReversedLayout, int displayId, boolean isDraggableExpandType, - @NonNull Color primaryVeilColor, - @NonNull Color secondaryVeilColor) { + @NonNull TaskFragmentContainer primaryContainer, + @NonNull TaskFragmentContainer secondaryContainer) { mConfiguration = configuration; mDividerAttributes = dividerAttributes; mDecorSurface = decorSurface; @@ -989,8 +989,8 @@ class DividerPresenter implements View.OnTouchListener { mIsReversedLayout = isReversedLayout; mDisplayId = displayId; mIsDraggableExpandType = isDraggableExpandType; - mPrimaryVeilColor = primaryVeilColor; - mSecondaryVeilColor = secondaryVeilColor; + mPrimaryContainer = primaryContainer; + mSecondaryContainer = secondaryContainer; mDividerWidthPx = getDividerWidthPx(dividerAttributes); } @@ -1014,8 +1014,8 @@ class DividerPresenter implements View.OnTouchListener { && a.mDisplayId == b.mDisplayId && a.mIsReversedLayout == b.mIsReversedLayout && a.mIsDraggableExpandType == b.mIsDraggableExpandType - && a.mPrimaryVeilColor.equals(b.mPrimaryVeilColor) - && a.mSecondaryVeilColor.equals(b.mSecondaryVeilColor); + && a.mPrimaryContainer == b.mPrimaryContainer + && a.mSecondaryContainer == b.mSecondaryContainer; } private static boolean areSameSurfaces( @@ -1328,8 +1328,12 @@ class DividerPresenter implements View.OnTouchListener { } private void showVeils(@NonNull SurfaceControl.Transaction t) { - t.setColor(mPrimaryVeil, colorToFloatArray(mProperties.mPrimaryVeilColor)) - .setColor(mSecondaryVeil, colorToFloatArray(mProperties.mSecondaryVeilColor)) + final Color primaryVeilColor = getContainerBackgroundColor( + mProperties.mPrimaryContainer, DEFAULT_PRIMARY_VEIL_COLOR); + final Color secondaryVeilColor = getContainerBackgroundColor( + mProperties.mSecondaryContainer, DEFAULT_SECONDARY_VEIL_COLOR); + t.setColor(mPrimaryVeil, colorToFloatArray(primaryVeilColor)) + .setColor(mSecondaryVeil, colorToFloatArray(secondaryVeilColor)) .setLayer(mDividerSurface, DIVIDER_LAYER) .setLayer(mPrimaryVeil, VEIL_LAYER) .setLayer(mSecondaryVeil, VEIL_LAYER) diff --git a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/DividerPresenterTest.java b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/DividerPresenterTest.java index 4515187f231e..3f676079f080 100644 --- a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/DividerPresenterTest.java +++ b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/DividerPresenterTest.java @@ -166,8 +166,8 @@ public class DividerPresenterTest { false /* isReversedLayout */, Display.DEFAULT_DISPLAY, false /* isDraggableExpandType */, - Color.valueOf(Color.BLACK), /* primaryVeilColor */ - Color.valueOf(Color.GRAY) /* secondaryVeilColor */ + mockPrimaryContainer, + mockSecondaryContainer ); mDividerPresenter = new DividerPresenter( diff --git a/libs/WindowManager/Shell/aconfig/multitasking.aconfig b/libs/WindowManager/Shell/aconfig/multitasking.aconfig index 08e695b6d6d2..15f8c328bb56 100644 --- a/libs/WindowManager/Shell/aconfig/multitasking.aconfig +++ b/libs/WindowManager/Shell/aconfig/multitasking.aconfig @@ -1,3 +1,5 @@ +# proto-file: build/make/tools/aconfig/aconfig_protos/protos/aconfig.proto + package: "com.android.wm.shell" container: "system" @@ -99,3 +101,13 @@ flag { description: "Enable UI affordances to put other content into a bubble" bug: "342245211" } + +flag { + name: "only_reuse_bubbled_task_when_launched_from_bubble" + namespace: "multitasking" + description: "Allow reusing bubbled tasks for new activities only when launching from bubbles" + bug: "328229865" + metadata { + purpose: PURPOSE_BUGFIX + } +} diff --git a/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/bar/BubbleExpandedViewPinControllerTest.kt b/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/bar/BubbleExpandedViewPinControllerTest.kt index 12d19279111a..ace2c131050c 100644 --- a/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/bar/BubbleExpandedViewPinControllerTest.kt +++ b/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/bar/BubbleExpandedViewPinControllerTest.kt @@ -26,7 +26,7 @@ import androidx.core.animation.AnimatorTestRule import androidx.test.core.app.ApplicationProvider import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest -import androidx.test.platform.app.InstrumentationRegistry +import androidx.test.platform.app.InstrumentationRegistry.getInstrumentation import com.android.internal.protolog.common.ProtoLog import com.android.wm.shell.R import com.android.wm.shell.bubbles.BubblePositioner @@ -35,6 +35,8 @@ import com.android.wm.shell.common.bubbles.BaseBubblePinController import com.android.wm.shell.common.bubbles.BaseBubblePinController.Companion.DROP_TARGET_ALPHA_IN_DURATION import com.android.wm.shell.common.bubbles.BaseBubblePinController.Companion.DROP_TARGET_ALPHA_OUT_DURATION import com.android.wm.shell.common.bubbles.BubbleBarLocation +import com.android.wm.shell.common.bubbles.BubbleBarLocation.LEFT +import com.android.wm.shell.common.bubbles.BubbleBarLocation.RIGHT import com.google.common.truth.Truth.assertThat import org.junit.After import org.junit.Before @@ -63,6 +65,9 @@ class BubbleExpandedViewPinControllerTest { private lateinit var controller: BubbleExpandedViewPinController private lateinit var testListener: TestLocationChangeListener + private val dropTargetView: View? + get() = container.findViewById(R.id.bubble_bar_drop_target) + private val pointOnLeft = PointF(100f, 100f) private val pointOnRight = PointF(1900f, 500f) @@ -92,13 +97,14 @@ class BubbleExpandedViewPinControllerTest { @After fun tearDown() { - runOnMainSync { controller.onDragEnd() } + getInstrumentation().runOnMainSync { controller.onDragEnd() } waitForAnimateOut() } + /** Dragging on same side should not show drop target or trigger location changes */ @Test - fun drag_stayOnSameSide() { - runOnMainSync { + fun drag_stayOnRightSide() { + getInstrumentation().runOnMainSync { controller.onDragStart(initialLocationOnLeft = false) controller.onDragUpdate(pointOnRight.x, pointOnRight.y) controller.onDragEnd() @@ -106,71 +112,124 @@ class BubbleExpandedViewPinControllerTest { waitForAnimateIn() assertThat(dropTargetView).isNull() assertThat(testListener.locationChanges).isEmpty() - assertThat(testListener.locationReleases).containsExactly(BubbleBarLocation.RIGHT) + assertThat(testListener.locationReleases).containsExactly(RIGHT) } + /** Dragging on same side should not show drop target or trigger location changes */ @Test - fun drag_toLeft() { - // Drag to left, but don't finish - runOnMainSync { + fun drag_stayOnLeftSide() { + getInstrumentation().runOnMainSync { + controller.onDragStart(initialLocationOnLeft = true) + controller.onDragUpdate(pointOnLeft.x, pointOnLeft.y) + controller.onDragEnd() + } + waitForAnimateIn() + assertThat(dropTargetView).isNull() + assertThat(testListener.locationChanges).isEmpty() + assertThat(testListener.locationReleases).containsExactly(LEFT) + } + + /** Drag crosses to the other side. Show drop target and trigger a location change. */ + @Test + fun drag_rightToLeft() { + getInstrumentation().runOnMainSync { controller.onDragStart(initialLocationOnLeft = false) + controller.onDragUpdate(pointOnRight.x, pointOnRight.y) controller.onDragUpdate(pointOnLeft.x, pointOnLeft.y) } waitForAnimateIn() assertThat(dropTargetView).isNotNull() assertThat(dropTargetView!!.alpha).isEqualTo(1f) + assertThat(dropTargetView!!.bounds()).isEqualTo(getExpectedDropTargetBoundsOnLeft()) + assertThat(testListener.locationChanges).containsExactly(LEFT) + assertThat(testListener.locationReleases).isEmpty() + } - val expectedDropTargetBounds = getExpectedDropTargetBounds(onLeft = true) - assertThat(dropTargetView!!.layoutParams.width).isEqualTo(expectedDropTargetBounds.width()) - assertThat(dropTargetView!!.layoutParams.height) - .isEqualTo(expectedDropTargetBounds.height()) + /** Drag crosses to the other side. Show drop target and trigger a location change. */ + @Test + fun drag_leftToRight() { + getInstrumentation().runOnMainSync { + controller.onDragStart(initialLocationOnLeft = true) + controller.onDragUpdate(pointOnLeft.x, pointOnLeft.y) + controller.onDragUpdate(pointOnRight.x, pointOnRight.y) + } + waitForAnimateIn() - assertThat(testListener.locationChanges).containsExactly(BubbleBarLocation.LEFT) + assertThat(dropTargetView).isNotNull() + assertThat(dropTargetView!!.alpha).isEqualTo(1f) + assertThat(dropTargetView!!.bounds()).isEqualTo(getExpectedDropTargetBoundsOnRight()) + assertThat(testListener.locationChanges).containsExactly(RIGHT) assertThat(testListener.locationReleases).isEmpty() - - // Finish the drag - runOnMainSync { controller.onDragEnd() } - assertThat(testListener.locationReleases).containsExactly(BubbleBarLocation.LEFT) } + /** + * Drop target does not initially show on the side that the drag starts. Check that it shows up + * after the dragging the view to other side and back to the initial side. + */ @Test - fun drag_toLeftAndBackToRight() { - // Drag to left - runOnMainSync { + fun drag_rightToLeftToRight() { + getInstrumentation().runOnMainSync { controller.onDragStart(initialLocationOnLeft = false) - controller.onDragUpdate(pointOnLeft.x, pointOnLeft.y) + controller.onDragUpdate(pointOnRight.x, pointOnRight.y) } waitForAnimateIn() + assertThat(dropTargetView).isNull() + + getInstrumentation().runOnMainSync { controller.onDragUpdate(pointOnLeft.x, pointOnLeft.y) } + waitForAnimateIn() assertThat(dropTargetView).isNotNull() - // Drag to right - runOnMainSync { controller.onDragUpdate(pointOnRight.x, pointOnRight.y) } - // We have to wait for existing drop target to animate out and new to animate in + getInstrumentation().runOnMainSync { + controller.onDragUpdate(pointOnRight.x, pointOnRight.y) + } waitForAnimateOut() waitForAnimateIn() - assertThat(dropTargetView).isNotNull() assertThat(dropTargetView!!.alpha).isEqualTo(1f) + assertThat(dropTargetView!!.bounds()).isEqualTo(getExpectedDropTargetBoundsOnRight()) + assertThat(testListener.locationChanges).containsExactly(LEFT, RIGHT).inOrder() + assertThat(testListener.locationReleases).isEmpty() + } - val expectedDropTargetBounds = getExpectedDropTargetBounds(onLeft = false) - assertThat(dropTargetView!!.layoutParams.width).isEqualTo(expectedDropTargetBounds.width()) - assertThat(dropTargetView!!.layoutParams.height) - .isEqualTo(expectedDropTargetBounds.height()) + /** + * Drop target does not initially show on the side that the drag starts. Check that it shows up + * after the dragging the view to other side and back to the initial side. + */ + @Test + fun drag_leftToRightToLeft() { + getInstrumentation().runOnMainSync { + controller.onDragStart(initialLocationOnLeft = true) + controller.onDragUpdate(pointOnLeft.x, pointOnLeft.y) + } + waitForAnimateIn() + assertThat(dropTargetView).isNull() - assertThat(testListener.locationChanges) - .containsExactly(BubbleBarLocation.LEFT, BubbleBarLocation.RIGHT) - assertThat(testListener.locationReleases).isEmpty() + getInstrumentation().runOnMainSync { + controller.onDragUpdate(pointOnRight.x, pointOnRight.y) + } + waitForAnimateIn() + assertThat(dropTargetView).isNotNull() - // Release the view - runOnMainSync { controller.onDragEnd() } - assertThat(testListener.locationReleases).containsExactly(BubbleBarLocation.RIGHT) + getInstrumentation().runOnMainSync { controller.onDragUpdate(pointOnLeft.x, pointOnLeft.y) } + waitForAnimateOut() + waitForAnimateIn() + assertThat(dropTargetView).isNotNull() + assertThat(dropTargetView!!.alpha).isEqualTo(1f) + assertThat(dropTargetView!!.bounds()).isEqualTo(getExpectedDropTargetBoundsOnLeft()) + assertThat(testListener.locationChanges).containsExactly(RIGHT, LEFT).inOrder() + assertThat(testListener.locationReleases).isEmpty() } + /** + * Drag from right to left, but stay in exclusion rect around the dismiss view. Drop target + * should not show and location change should not trigger. + */ @Test - fun drag_toLeftInExclusionRect() { - runOnMainSync { + fun drag_rightToLeft_inExclusionRect() { + getInstrumentation().runOnMainSync { controller.onDragStart(initialLocationOnLeft = false) + controller.onDragUpdate(pointOnRight.x, pointOnRight.y) // Exclusion rect is around the bottom center area of the screen controller.onDragUpdate(SCREEN_WIDTH / 2f - 50, SCREEN_HEIGHT - 100f) } @@ -178,85 +237,212 @@ class BubbleExpandedViewPinControllerTest { assertThat(dropTargetView).isNull() assertThat(testListener.locationChanges).isEmpty() assertThat(testListener.locationReleases).isEmpty() + } - runOnMainSync { controller.onDragEnd() } - assertThat(testListener.locationReleases).containsExactly(BubbleBarLocation.RIGHT) + /** + * Drag from left to right, but stay in exclusion rect around the dismiss view. Drop target + * should not show and location change should not trigger. + */ + @Test + fun drag_leftToRight_inExclusionRect() { + getInstrumentation().runOnMainSync { + controller.onDragStart(initialLocationOnLeft = true) + controller.onDragUpdate(pointOnLeft.x, pointOnLeft.y) + // Exclusion rect is around the bottom center area of the screen + controller.onDragUpdate(SCREEN_WIDTH / 2f + 50, SCREEN_HEIGHT - 100f) + } + waitForAnimateIn() + assertThat(dropTargetView).isNull() + assertThat(testListener.locationChanges).isEmpty() + assertThat(testListener.locationReleases).isEmpty() } + /** + * Drag to dismiss target and back to the same side should not cause the drop target to show. + */ @Test - fun toggleSetDropTargetHidden_dropTargetExists() { - runOnMainSync { + fun drag_rightToDismissToRight() { + getInstrumentation().runOnMainSync { controller.onDragStart(initialLocationOnLeft = false) + controller.onDragUpdate(pointOnRight.x, pointOnRight.y) + controller.onStuckToDismissTarget() + controller.onDragUpdate(pointOnRight.x, pointOnRight.y) + } + waitForAnimateIn() + assertThat(dropTargetView).isNull() + assertThat(testListener.locationChanges).isEmpty() + assertThat(testListener.locationReleases).isEmpty() + } + + /** + * Drag to dismiss target and back to the same side should not cause the drop target to show. + */ + @Test + fun drag_leftToDismissToLeft() { + getInstrumentation().runOnMainSync { + controller.onDragStart(initialLocationOnLeft = true) + controller.onDragUpdate(pointOnLeft.x, pointOnLeft.y) + controller.onStuckToDismissTarget() controller.onDragUpdate(pointOnLeft.x, pointOnLeft.y) } waitForAnimateIn() + assertThat(dropTargetView).isNull() + assertThat(testListener.locationChanges).isEmpty() + assertThat(testListener.locationReleases).isEmpty() + } + /** Drag to dismiss target and other side should show drop target on the other side. */ + @Test + fun drag_rightToDismissToLeft() { + getInstrumentation().runOnMainSync { + controller.onDragStart(initialLocationOnLeft = false) + controller.onDragUpdate(pointOnRight.x, pointOnRight.y) + controller.onStuckToDismissTarget() + controller.onDragUpdate(pointOnLeft.x, pointOnLeft.y) + } + waitForAnimateIn() assertThat(dropTargetView).isNotNull() assertThat(dropTargetView!!.alpha).isEqualTo(1f) + assertThat(dropTargetView!!.bounds()).isEqualTo(getExpectedDropTargetBoundsOnLeft()) - runOnMainSync { controller.setDropTargetHidden(true) } - waitForAnimateOut() - assertThat(dropTargetView).isNotNull() - assertThat(dropTargetView!!.alpha).isEqualTo(0f) + assertThat(testListener.locationChanges).containsExactly(LEFT) + assertThat(testListener.locationReleases).isEmpty() + } - runOnMainSync { controller.setDropTargetHidden(false) } + /** Drag to dismiss target and other side should show drop target on the other side. */ + @Test + fun drag_leftToDismissToRight() { + getInstrumentation().runOnMainSync { + controller.onDragStart(initialLocationOnLeft = true) + controller.onDragUpdate(pointOnLeft.x, pointOnLeft.y) + controller.onStuckToDismissTarget() + controller.onDragUpdate(pointOnRight.x, pointOnRight.y) + } waitForAnimateIn() assertThat(dropTargetView).isNotNull() assertThat(dropTargetView!!.alpha).isEqualTo(1f) + assertThat(dropTargetView!!.bounds()).isEqualTo(getExpectedDropTargetBoundsOnRight()) + + assertThat(testListener.locationChanges).containsExactly(RIGHT) + assertThat(testListener.locationReleases).isEmpty() } + /** + * Drag to dismiss should trigger a location change to the initial location, if the current + * location is different. And hide the drop target. + */ @Test - fun toggleSetDropTargetHidden_noDropTarget() { - runOnMainSync { controller.setDropTargetHidden(true) } + fun drag_rightToLeftToDismiss() { + getInstrumentation().runOnMainSync { + controller.onDragStart(initialLocationOnLeft = false) + controller.onDragUpdate(pointOnRight.x, pointOnRight.y) + controller.onDragUpdate(pointOnLeft.x, pointOnLeft.y) + } + waitForAnimateIn() + assertThat(dropTargetView).isNotNull() + assertThat(dropTargetView!!.alpha).isEqualTo(1f) + + getInstrumentation().runOnMainSync { controller.onStuckToDismissTarget() } waitForAnimateOut() - assertThat(dropTargetView).isNull() + assertThat(dropTargetView!!.alpha).isEqualTo(0f) - runOnMainSync { controller.setDropTargetHidden(false) } + assertThat(testListener.locationChanges).containsExactly(LEFT, RIGHT).inOrder() + assertThat(testListener.locationReleases).isEmpty() + } + + /** + * Drag to dismiss should trigger a location change to the initial location, if the current + * location is different. And hide the drop target. + */ + @Test + fun drag_leftToRightToDismiss() { + getInstrumentation().runOnMainSync { + controller.onDragStart(initialLocationOnLeft = true) + controller.onDragUpdate(pointOnLeft.x, pointOnLeft.y) + controller.onDragUpdate(pointOnRight.x, pointOnRight.y) + } waitForAnimateIn() - assertThat(dropTargetView).isNull() + assertThat(dropTargetView).isNotNull() + assertThat(dropTargetView!!.alpha).isEqualTo(1f) + getInstrumentation().runOnMainSync { controller.onStuckToDismissTarget() } + waitForAnimateOut() + assertThat(dropTargetView!!.alpha).isEqualTo(0f) + assertThat(testListener.locationChanges).containsExactly(RIGHT, LEFT).inOrder() + assertThat(testListener.locationReleases).isEmpty() } + /** Finishing drag should remove drop target and send location update. */ @Test - fun onDragEnd_dropTargetExists() { - runOnMainSync { + fun drag_rightToLeftRelease() { + getInstrumentation().runOnMainSync { controller.onDragStart(initialLocationOnLeft = false) + controller.onDragUpdate(pointOnRight.x, pointOnRight.y) controller.onDragUpdate(pointOnLeft.x, pointOnLeft.y) } waitForAnimateIn() assertThat(dropTargetView).isNotNull() - runOnMainSync { controller.onDragEnd() } + getInstrumentation().runOnMainSync { controller.onDragEnd() } waitForAnimateOut() assertThat(dropTargetView).isNull() + assertThat(testListener.locationChanges).containsExactly(LEFT) + assertThat(testListener.locationReleases).containsExactly(LEFT) } + /** Finishing drag should remove drop target and send location update. */ @Test - fun onDragEnd_noDropTarget() { - runOnMainSync { controller.onDragEnd() } + fun drag_leftToRightRelease() { + getInstrumentation().runOnMainSync { + controller.onDragStart(initialLocationOnLeft = true) + controller.onDragUpdate(pointOnLeft.x, pointOnLeft.y) + controller.onDragUpdate(pointOnRight.x, pointOnRight.y) + } + waitForAnimateIn() + assertThat(dropTargetView).isNotNull() + + getInstrumentation().runOnMainSync { controller.onDragEnd() } waitForAnimateOut() assertThat(dropTargetView).isNull() + assertThat(testListener.locationChanges).containsExactly(RIGHT) + assertThat(testListener.locationReleases).containsExactly(RIGHT) } - private val dropTargetView: View? - get() = container.findViewById(R.id.bubble_bar_drop_target) - - private fun getExpectedDropTargetBounds(onLeft: Boolean): Rect = + private fun getExpectedDropTargetBoundsOnLeft(): Rect = Rect().also { - positioner.getBubbleBarExpandedViewBounds(onLeft, false /* isOveflowExpanded */, it) + positioner.getBubbleBarExpandedViewBounds( + true /* onLeft */, + false /* isOverflowExpanded */, + it + ) } - private fun runOnMainSync(runnable: Runnable) { - InstrumentationRegistry.getInstrumentation().runOnMainSync(runnable) - } + private fun getExpectedDropTargetBoundsOnRight(): Rect = + Rect().also { + positioner.getBubbleBarExpandedViewBounds( + false /* onLeft */, + false /* isOverflowExpanded */, + it + ) + } private fun waitForAnimateIn() { // Advance animator for on-device test - runOnMainSync { animatorTestRule.advanceTimeBy(DROP_TARGET_ALPHA_IN_DURATION) } + getInstrumentation().runOnMainSync { + animatorTestRule.advanceTimeBy(DROP_TARGET_ALPHA_IN_DURATION) + } } private fun waitForAnimateOut() { // Advance animator for on-device test - runOnMainSync { animatorTestRule.advanceTimeBy(DROP_TARGET_ALPHA_OUT_DURATION) } + getInstrumentation().runOnMainSync { + animatorTestRule.advanceTimeBy(DROP_TARGET_ALPHA_OUT_DURATION) + } + } + + private fun View.bounds(): Rect { + return Rect(0, 0, layoutParams.width, layoutParams.height).also { rect -> + rect.offsetTo(x.toInt(), y.toInt()) + } } internal class TestLocationChangeListener : BaseBubblePinController.LocationChangeListener { diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarExpandedViewDragController.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarExpandedViewDragController.kt index a51ac633ad86..fa1091c63d00 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarExpandedViewDragController.kt +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarExpandedViewDragController.kt @@ -150,7 +150,7 @@ class BubbleBarExpandedViewDragController( draggedObject: MagnetizedObject<*> ) { isStuckToDismiss = true - pinController.setDropTargetHidden(true) + pinController.onStuckToDismissTarget() } override fun onUnstuckFromTarget( @@ -162,7 +162,6 @@ class BubbleBarExpandedViewDragController( ) { isStuckToDismiss = false animationHelper.animateUnstuckFromDismissView(target) - pinController.setDropTargetHidden(false) } override fun onReleasedInTarget( diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/bubbles/BaseBubblePinController.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/common/bubbles/BaseBubblePinController.kt index e514f9d70599..eec24683db8a 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/bubbles/BaseBubblePinController.kt +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/bubbles/BaseBubblePinController.kt @@ -38,8 +38,10 @@ import com.android.wm.shell.common.bubbles.BubbleBarLocation.RIGHT */ abstract class BaseBubblePinController(private val screenSizeProvider: () -> Point) { + private var initialLocationOnLeft = false private var onLeft = false private var dismissZone: RectF? = null + private var stuckToDismissTarget = false private var screenCenterX = 0 private var listener: LocationChangeListener? = null private var dropTargetAnimator: ObjectAnimator? = null @@ -50,6 +52,7 @@ abstract class BaseBubblePinController(private val screenSizeProvider: () -> Poi * @param initialLocationOnLeft side of the screen where bubble bar is pinned to */ fun onDragStart(initialLocationOnLeft: Boolean) { + this.initialLocationOnLeft = initialLocationOnLeft onLeft = initialLocationOnLeft screenCenterX = screenSizeProvider.invoke().x / 2 dismissZone = getExclusionRect() @@ -59,22 +62,33 @@ abstract class BaseBubblePinController(private val screenSizeProvider: () -> Poi fun onDragUpdate(x: Float, y: Float) { if (dismissZone?.contains(x, y) == true) return - if (onLeft && x > screenCenterX) { - onLeft = false - onLocationChange(RIGHT) - } else if (!onLeft && x < screenCenterX) { - onLeft = true - onLocationChange(LEFT) + val wasOnLeft = onLeft + onLeft = x < screenCenterX + if (wasOnLeft != onLeft) { + onLocationChange(if (onLeft) LEFT else RIGHT) + } else if (stuckToDismissTarget) { + // Moved out of the dismiss view back to initial side, if we have a drop target, show it + getDropTargetView()?.apply { animateIn() } } + // Make sure this gets cleared + stuckToDismissTarget = false } - /** Temporarily hide the drop target view */ - fun setDropTargetHidden(hidden: Boolean) { - val targetView = getDropTargetView() ?: return - if (hidden) { - targetView.animateOut() - } else { - targetView.animateIn() + /** Signal the controller that view has been dragged to dismiss view. */ + fun onStuckToDismissTarget() { + stuckToDismissTarget = true + // Notify that location may be reset + val shouldResetLocation = onLeft != initialLocationOnLeft + if (shouldResetLocation) { + onLeft = initialLocationOnLeft + listener?.onChange(if (onLeft) LEFT else RIGHT) + } + getDropTargetView()?.apply { + animateOut { + if (shouldResetLocation) { + updateLocation(if (onLeft) LEFT else RIGHT) + } + } } } diff --git a/libs/input/Android.bp b/libs/input/Android.bp index 5ce990fdeb82..7b7ccf51aa1a 100644 --- a/libs/input/Android.bp +++ b/libs/input/Android.bp @@ -48,7 +48,6 @@ cc_library_shared { "libgui", "libui", "libinput", - "libnativewindow", ], header_libs: [ diff --git a/libs/input/tests/PointerController_test.cpp b/libs/input/tests/PointerController_test.cpp index cbef68e2eb8f..5b00fca4d857 100644 --- a/libs/input/tests/PointerController_test.cpp +++ b/libs/input/tests/PointerController_test.cpp @@ -162,6 +162,16 @@ public: }; class PointerControllerTest : public Test { +private: + void loopThread(); + + std::atomic<bool> mRunning = true; + class MyLooper : public Looper { + public: + MyLooper() : Looper(false) {} + ~MyLooper() = default; + }; + protected: PointerControllerTest(); ~PointerControllerTest(); @@ -173,26 +183,16 @@ protected: std::unique_ptr<MockSpriteController> mSpriteController; std::shared_ptr<PointerController> mPointerController; sp<android::gui::WindowInfosListener> mRegisteredListener; + sp<MyLooper> mLooper; private: - void loopThread(); - - std::atomic<bool> mRunning = true; - class MyLooper : public Looper { - public: - MyLooper() : Looper(false) {} - ~MyLooper() = default; - }; std::thread mThread; - -protected: - sp<MyLooper> mLooper; }; PointerControllerTest::PointerControllerTest() : mPointerSprite(new NiceMock<MockSprite>), - mThread(&PointerControllerTest::loopThread, this), - mLooper(new MyLooper) { + mLooper(new MyLooper), + mThread(&PointerControllerTest::loopThread, this) { mSpriteController.reset(new NiceMock<MockSpriteController>(mLooper)); mPolicy = new MockPointerControllerPolicyInterface(); diff --git a/nfc/java/android/nfc/cardemulation/PollingFrame.java b/nfc/java/android/nfc/cardemulation/PollingFrame.java index 4c76fb02f7d8..5dcc84ccf8b9 100644 --- a/nfc/java/android/nfc/cardemulation/PollingFrame.java +++ b/nfc/java/android/nfc/cardemulation/PollingFrame.java @@ -16,7 +16,6 @@ package android.nfc.cardemulation; -import android.annotation.DurationMillisLong; import android.annotation.FlaggedApi; import android.annotation.IntDef; import android.annotation.NonNull; @@ -33,13 +32,13 @@ import java.util.List; /** * Polling Frames represent data about individual frames of an NFC polling loop. These frames will - * be deliverd to subclasses of {@link HostApduService} that have registered filters with - * {@link CardEmulation#registerPollingLoopFilterForService(ComponentName, String)} that match a - * given frame in a loop and will be delivered through calls to + * be delivered to subclasses of {@link HostApduService} that have registered filters with + * {@link CardEmulation#registerPollingLoopFilterForService(ComponentName, String, boolean)} that + * match a given frame in a loop and will be delivered through calls to * {@link HostApduService#processPollingFrames(List)}. */ @FlaggedApi(android.nfc.Flags.FLAG_NFC_READ_POLLING_LOOP) -public final class PollingFrame implements Parcelable{ +public final class PollingFrame implements Parcelable { /** * @hide @@ -146,7 +145,6 @@ public final class PollingFrame implements Parcelable{ private final int mType; private final byte[] mData; private final int mGain; - @DurationMillisLong private final long mTimestamp; private boolean mTriggeredAutoTransact; @@ -179,18 +177,18 @@ public final class PollingFrame implements Parcelable{ * @param type the type of the frame * @param data a byte array of the data contained in the frame * @param gain the vendor-specific gain of the field - * @param timestampMillis the timestamp in millisecones + * @param timestampMicros the timestamp in microseconds * @param triggeredAutoTransact whether or not this frame triggered the device to start a * transaction automatically * * @hide */ public PollingFrame(@PollingFrameType int type, @Nullable byte[] data, - int gain, @DurationMillisLong long timestampMillis, boolean triggeredAutoTransact) { + int gain, long timestampMicros, boolean triggeredAutoTransact) { mType = type; mData = data == null ? new byte[0] : data; mGain = gain; - mTimestamp = timestampMillis; + mTimestamp = timestampMicros; mTriggeredAutoTransact = triggeredAutoTransact; } @@ -198,11 +196,11 @@ public final class PollingFrame implements Parcelable{ * Returns the type of frame for this polling loop frame. * The possible return values are: * <ul> - * <li>{@link POLLING_LOOP_TYPE_ON}</li> - * <li>{@link POLLING_LOOP_TYPE_OFF}</li> - * <li>{@link POLLING_LOOP_TYPE_A}</li> - * <li>{@link POLLING_LOOP_TYPE_B}</li> - * <li>{@link POLLING_LOOP_TYPE_F}</li> + * <li>{@link #POLLING_LOOP_TYPE_ON}</li> + * <li>{@link #POLLING_LOOP_TYPE_OFF}</li> + * <li>{@link #POLLING_LOOP_TYPE_A}</li> + * <li>{@link #POLLING_LOOP_TYPE_B}</li> + * <li>{@link #POLLING_LOOP_TYPE_F}</li> * </ul> */ public @PollingFrameType int getType() { @@ -226,12 +224,12 @@ public final class PollingFrame implements Parcelable{ } /** - * Returns the timestamp of when the polling loop frame was observed in milliseconds. These - * timestamps are relative and not absolute and should only be used for comparing the timing of - * frames relative to each other. - * @return the timestamp in milliseconds + * Returns the timestamp of when the polling loop frame was observed, in microseconds. These + * timestamps are relative and should only be used for comparing the timing of frames relative + * to each other. + * @return the timestamp in microseconds */ - public @DurationMillisLong long getTimestamp() { + public long getTimestamp() { return mTimestamp; } diff --git a/packages/CredentialManager/shared/src/com/android/credentialmanager/ktx/CredentialKtx.kt b/packages/CredentialManager/shared/src/com/android/credentialmanager/ktx/CredentialKtx.kt index 9c8ec3b56813..3022fa01cd8d 100644 --- a/packages/CredentialManager/shared/src/com/android/credentialmanager/ktx/CredentialKtx.kt +++ b/packages/CredentialManager/shared/src/com/android/credentialmanager/ktx/CredentialKtx.kt @@ -27,6 +27,7 @@ import android.credentials.selection.AuthenticationEntry import android.credentials.selection.Entry import android.credentials.selection.GetCredentialProviderData import android.graphics.drawable.Drawable +import android.os.Bundle import android.text.TextUtils import android.util.Log import androidx.activity.result.IntentSenderRequest @@ -227,26 +228,31 @@ private fun getCredentialOptionInfoList( * and get flows utilize slice params; includes the final '.' before the name of the type (e.g. * androidx.credentials.provider.credentialEntry.SLICE_HINT_ALLOWED_AUTHENTICATORS must have * 'hintPrefix' up to "androidx.credentials.provider.credentialEntry.") - * // TODO(b/326243754) : Presently, due to dependencies, the opId bit is parsed but is never - * // expected to be used. When it is added, it should be lightly validated. */ fun retrieveEntryBiometricRequest( entry: Entry, - hintPrefix: String, + hintPrefix: String ): BiometricRequestInfo? { - // TODO(b/326243754) : When available, use the official jetpack structured type - val allowedAuthenticators: Int? = entry.slice.items.firstOrNull { - it.hasHint(hintPrefix + "SLICE_HINT_ALLOWED_AUTHENTICATORS") - }?.int + // TODO(b/326243754) : When available, use the official jetpack structured typLo + val biometricPromptDataBundleKey = "SLICE_HINT_BIOMETRIC_PROMPT_DATA" + val biometricPromptDataBundle: Bundle = entry.slice.items.firstOrNull { + it.hasHint(hintPrefix + biometricPromptDataBundleKey) + }?.bundle ?: return null - // This is optional and does not affect validating the biometric flow in any case - val opId: Int? = entry.slice.items.firstOrNull { - it.hasHint(hintPrefix + "SLICE_HINT_CRYPTO_OP_ID") - }?.int - if (allowedAuthenticators != null) { - return BiometricRequestInfo(opId = opId, allowedAuthenticators = allowedAuthenticators) + val allowedAuthConstantKey = "androidx.credentials.provider.BUNDLE_HINT_ALLOWED_AUTHENTICATORS" + val cryptoOpIdKey = "androidx.credentials.provider.BUNDLE_HINT_CRYPTO_OP_ID" + + if (!biometricPromptDataBundle.containsKey(allowedAuthConstantKey)) { + return null } - return null + + val allowedAuthenticators: Int = biometricPromptDataBundle.getInt(allowedAuthConstantKey) + + // This is optional and does not affect validating the biometric flow in any case + val opId: Long? = if (biometricPromptDataBundle.containsKey(cryptoOpIdKey)) + biometricPromptDataBundle.getLong(cryptoOpIdKey) else null + + return BiometricRequestInfo(opId = opId, allowedAuthenticators = allowedAuthenticators) } val Slice.credentialEntry: CredentialEntry? diff --git a/packages/CredentialManager/shared/src/com/android/credentialmanager/model/BiometricRequestInfo.kt b/packages/CredentialManager/shared/src/com/android/credentialmanager/model/BiometricRequestInfo.kt index 486cfe7123dd..fe4beadfa9ec 100644 --- a/packages/CredentialManager/shared/src/com/android/credentialmanager/model/BiometricRequestInfo.kt +++ b/packages/CredentialManager/shared/src/com/android/credentialmanager/model/BiometricRequestInfo.kt @@ -23,6 +23,6 @@ package com.android.credentialmanager.model * null. */ data class BiometricRequestInfo( - val opId: Int? = null, + val opId: Long? = null, val allowedAuthenticators: Int )
\ No newline at end of file diff --git a/packages/CredentialManager/src/com/android/credentialmanager/CredentialSelectorViewModel.kt b/packages/CredentialManager/src/com/android/credentialmanager/CredentialSelectorViewModel.kt index 7bc25ed81089..894d5ef24ebd 100644 --- a/packages/CredentialManager/src/com/android/credentialmanager/CredentialSelectorViewModel.kt +++ b/packages/CredentialManager/src/com/android/credentialmanager/CredentialSelectorViewModel.kt @@ -135,16 +135,18 @@ class CredentialSelectorViewModel( Log.w(Constants.LOG_TAG, "Unexpected biometric result exists when " + "autoSelect is preferred.") } - // TODO(b/333445754) : Decide whether to propagate info on prompt launch + // TODO(b/333445754) : Change the fm option to false in qpr after discussion if (biometricState.biometricResult != null) { entryIntent?.putExtra(Constants.BIOMETRIC_AUTH_RESULT, biometricState.biometricResult.biometricAuthenticationResult .authenticationType) + entryIntent?.putExtra(Constants.BIOMETRIC_FRAMEWORK_OPTION, true) } else if (biometricState.biometricError != null){ entryIntent?.putExtra(Constants.BIOMETRIC_AUTH_ERROR_CODE, biometricState.biometricError.errorCode) entryIntent?.putExtra(Constants.BIOMETRIC_AUTH_ERROR_MESSAGE, biometricState.biometricError.errorMessage) + entryIntent?.putExtra(Constants.BIOMETRIC_FRAMEWORK_OPTION, true) } } val intentSenderRequest = IntentSenderRequest.Builder(pendingIntent) diff --git a/packages/CredentialManager/src/com/android/credentialmanager/common/BiometricHandler.kt b/packages/CredentialManager/src/com/android/credentialmanager/common/BiometricHandler.kt index b43b5f318cf1..c35721c11741 100644 --- a/packages/CredentialManager/src/com/android/credentialmanager/common/BiometricHandler.kt +++ b/packages/CredentialManager/src/com/android/credentialmanager/common/BiometricHandler.kt @@ -38,7 +38,6 @@ import com.android.credentialmanager.model.EntryInfo import com.android.credentialmanager.model.creation.CreateOptionInfo import com.android.credentialmanager.model.get.CredentialEntryInfo import com.android.credentialmanager.model.get.ProviderInfo -import java.lang.Exception /** * Aggregates common display information used for the Biometric Flow. @@ -121,11 +120,11 @@ fun runBiometricFlowForGet( getBiometricCancellationSignal: () -> CancellationSignal, getRequestDisplayInfo: RequestDisplayInfo? = null, getProviderInfoList: List<ProviderInfo>? = null, - getProviderDisplayInfo: ProviderDisplayInfo? = null, -) { + getProviderDisplayInfo: ProviderDisplayInfo? = null +): Boolean { if (getBiometricPromptState() != BiometricPromptState.INACTIVE) { // Screen is already up, do not re-launch - return + return false } onBiometricPromptStateChange(BiometricPromptState.PENDING) val biometricDisplayInfo = validateAndRetrieveBiometricGetDisplayInfo( @@ -137,7 +136,7 @@ fun runBiometricFlowForGet( if (biometricDisplayInfo == null) { onBiometricFailureFallback(BiometricFlowType.GET) - return + return false } val callback: BiometricPrompt.AuthenticationCallback = @@ -146,7 +145,7 @@ fun runBiometricFlowForGet( getBiometricPromptState) Log.d(TAG, "The BiometricPrompt API call begins for Get.") - runBiometricFlow(context, biometricDisplayInfo, callback, openMoreOptionsPage, + return runBiometricFlow(context, biometricDisplayInfo, callback, openMoreOptionsPage, onBiometricFailureFallback, BiometricFlowType.GET, onCancelFlowAndFinish, getBiometricCancellationSignal) } @@ -169,11 +168,11 @@ fun runBiometricFlowForCreate( getBiometricCancellationSignal: () -> CancellationSignal, createRequestDisplayInfo: com.android.credentialmanager.createflow .RequestDisplayInfo? = null, - createProviderInfo: EnabledProviderInfo? = null, -) { + createProviderInfo: EnabledProviderInfo? = null +): Boolean { if (getBiometricPromptState() != BiometricPromptState.INACTIVE) { // Screen is already up, do not re-launch - return + return false } onBiometricPromptStateChange(BiometricPromptState.PENDING) val biometricDisplayInfo = validateAndRetrieveBiometricCreateDisplayInfo( @@ -184,7 +183,7 @@ fun runBiometricFlowForCreate( if (biometricDisplayInfo == null) { onBiometricFailureFallback(BiometricFlowType.CREATE) - return + return false } val callback: BiometricPrompt.AuthenticationCallback = @@ -193,7 +192,7 @@ fun runBiometricFlowForCreate( getBiometricPromptState) Log.d(TAG, "The BiometricPrompt API call begins for Create.") - runBiometricFlow(context, biometricDisplayInfo, callback, openMoreOptionsPage, + return runBiometricFlow(context, biometricDisplayInfo, callback, openMoreOptionsPage, onBiometricFailureFallback, BiometricFlowType.CREATE, onCancelFlowAndFinish, getBiometricCancellationSignal) } @@ -206,19 +205,19 @@ fun runBiometricFlowForCreate( * only device credentials are requested. */ private fun runBiometricFlow( - context: Context, - biometricDisplayInfo: BiometricDisplayInfo, - callback: BiometricPrompt.AuthenticationCallback, - openMoreOptionsPage: () -> Unit, - onBiometricFailureFallback: (BiometricFlowType) -> Unit, - biometricFlowType: BiometricFlowType, - onCancelFlowAndFinish: () -> Unit, - getBiometricCancellationSignal: () -> CancellationSignal, -) { + context: Context, + biometricDisplayInfo: BiometricDisplayInfo, + callback: BiometricPrompt.AuthenticationCallback, + openMoreOptionsPage: () -> Unit, + onBiometricFailureFallback: (BiometricFlowType) -> Unit, + biometricFlowType: BiometricFlowType, + onCancelFlowAndFinish: () -> Unit, + getBiometricCancellationSignal: () -> CancellationSignal +): Boolean { try { if (!canCallBiometricPrompt(biometricDisplayInfo, context)) { onBiometricFailureFallback(biometricFlowType) - return + return false } val biometricPrompt = setupBiometricPrompt(context, biometricDisplayInfo, @@ -231,7 +230,7 @@ private fun runBiometricFlow( val cryptoOpId = getCryptoOpId(biometricDisplayInfo) if (cryptoOpId != null) { biometricPrompt.authenticate( - BiometricPrompt.CryptoObject(cryptoOpId.toLong()), + BiometricPrompt.CryptoObject(cryptoOpId), cancellationSignal, executor, callback) } else { biometricPrompt.authenticate(cancellationSignal, executor, callback) @@ -239,10 +238,12 @@ private fun runBiometricFlow( } catch (e: IllegalArgumentException) { Log.w(TAG, "Calling the biometric prompt API failed with: /n${e.localizedMessage}\n") onBiometricFailureFallback(biometricFlowType) + return false } + return true } -private fun getCryptoOpId(biometricDisplayInfo: BiometricDisplayInfo): Int? { +private fun getCryptoOpId(biometricDisplayInfo: BiometricDisplayInfo): Long? { return biometricDisplayInfo.biometricRequestInfo.opId } diff --git a/packages/CredentialManager/src/com/android/credentialmanager/common/Constants.kt b/packages/CredentialManager/src/com/android/credentialmanager/common/Constants.kt index 3c80113134b1..cb089adf750e 100644 --- a/packages/CredentialManager/src/com/android/credentialmanager/common/Constants.kt +++ b/packages/CredentialManager/src/com/android/credentialmanager/common/Constants.kt @@ -22,9 +22,12 @@ class Constants { const val BUNDLE_KEY_PREFER_IMMEDIATELY_AVAILABLE_CREDENTIALS = "androidx.credentials.BUNDLE_KEY_IS_AUTO_SELECT_ALLOWED" const val IS_AUTO_SELECTED_KEY = "IS_AUTO_SELECTED" - // TODO(b/333445772) : Qualify error codes fully for propagation - const val BIOMETRIC_AUTH_RESULT = "BIOMETRIC_AUTH_RESULT" - const val BIOMETRIC_AUTH_ERROR_CODE = "BIOMETRIC_AUTH_ERROR_CODE" - const val BIOMETRIC_AUTH_ERROR_MESSAGE = "BIOMETRIC_AUTH_ERROR_MESSAGE" + const val BIOMETRIC_AUTH_RESULT = "androidx.credentials.provider.BIOMETRIC_AUTH_RESULT" + const val BIOMETRIC_AUTH_ERROR_CODE = + "androidx.credentials.provider.BIOMETRIC_AUTH_ERROR_CODE" + const val BIOMETRIC_AUTH_ERROR_MESSAGE = + "androidx.credentials.provider.BIOMETRIC_AUTH_ERROR_MESSAGE" + const val BIOMETRIC_FRAMEWORK_OPTION = + "androidx.credentials.provider.BIOMETRIC_FRAMEWORK_OPTION" } } diff --git a/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateCredentialComponents.kt b/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateCredentialComponents.kt index 7d61f73a525b..4993a1fa0672 100644 --- a/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateCredentialComponents.kt +++ b/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateCredentialComponents.kt @@ -123,7 +123,8 @@ fun CreateCredentialScreen( onBiometricPromptStateChange = viewModel::onBiometricPromptStateChange, getBiometricCancellationSignal = - viewModel::getBiometricCancellationSignal + viewModel::getBiometricCancellationSignal, + onLog = { viewModel.logUiEvent(it) }, ) CreateScreenState.MORE_OPTIONS_SELECTION_ONLY -> MoreOptionsSelectionCard( requestDisplayInfo = createCredentialUiState.requestDisplayInfo, @@ -642,12 +643,13 @@ internal fun BiometricSelectionPage( getBiometricPromptState: () -> BiometricPromptState, onBiometricPromptStateChange: (BiometricPromptState) -> Unit, getBiometricCancellationSignal: () -> CancellationSignal, + onLog: @Composable (UiEventEnum) -> Unit ) { if (biometricEntry == null) { fallbackToOriginalFlow(BiometricFlowType.CREATE) return } - runBiometricFlowForCreate( + val biometricFlowCalled = runBiometricFlowForCreate( biometricEntry = biometricEntry, context = LocalContext.current, openMoreOptionsPage = onMoreOptionSelected, @@ -659,6 +661,9 @@ internal fun BiometricSelectionPage( createProviderInfo = enabledProviderInfo, onBiometricFailureFallback = fallbackToOriginalFlow, onIllegalStateAndFinish = onIllegalScreenStateAndFinish, - getBiometricCancellationSignal = getBiometricCancellationSignal, + getBiometricCancellationSignal = getBiometricCancellationSignal ) + if (biometricFlowCalled) { + onLog(CreateCredentialEvent.CREDMAN_CREATE_CRED_BIOMETRIC_FLOW_LAUNCHED) + } } diff --git a/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetCredentialComponents.kt b/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetCredentialComponents.kt index ba61b90fa4dc..517ad0069f85 100644 --- a/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetCredentialComponents.kt +++ b/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetCredentialComponents.kt @@ -166,7 +166,8 @@ fun GetCredentialScreen( onBiometricPromptStateChange = viewModel::onBiometricPromptStateChange, getBiometricCancellationSignal = - viewModel::getBiometricCancellationSignal + viewModel::getBiometricCancellationSignal, + onLog = { viewModel.logUiEvent(it) }, ) } else if (credmanBiometricApiEnabled() && getCredentialUiState.currentScreenState @@ -260,12 +261,13 @@ internal fun BiometricSelectionPage( getBiometricPromptState: () -> BiometricPromptState, onBiometricPromptStateChange: (BiometricPromptState) -> Unit, getBiometricCancellationSignal: () -> CancellationSignal, + onLog: @Composable (UiEventEnum) -> Unit, ) { if (biometricEntry == null) { fallbackToOriginalFlow(BiometricFlowType.GET) return } - runBiometricFlowForGet( + val biometricFlowCalled = runBiometricFlowForGet( biometricEntry = biometricEntry, context = LocalContext.current, openMoreOptionsPage = onMoreOptionSelected, @@ -280,6 +282,9 @@ internal fun BiometricSelectionPage( onBiometricFailureFallback = fallbackToOriginalFlow, getBiometricCancellationSignal = getBiometricCancellationSignal ) + if (biometricFlowCalled) { + onLog(GetCredentialEvent.CREDMAN_GET_CRED_BIOMETRIC_FLOW_LAUNCHED) + } } /** Draws the primary credential selection page, used in Android U. */ diff --git a/packages/CredentialManager/src/com/android/credentialmanager/logging/CreateCredentialEvent.kt b/packages/CredentialManager/src/com/android/credentialmanager/logging/CreateCredentialEvent.kt index daa42be020ce..dac25fa165af 100644 --- a/packages/CredentialManager/src/com/android/credentialmanager/logging/CreateCredentialEvent.kt +++ b/packages/CredentialManager/src/com/android/credentialmanager/logging/CreateCredentialEvent.kt @@ -52,7 +52,10 @@ enum class CreateCredentialEvent(private val id: Int) : UiEventLogger.UiEventEnu CREDMAN_CREATE_CRED_EXTERNAL_ONLY_SELECTION(1327), @UiEvent(doc = "The more about passkeys intro card is visible on screen.") - CREDMAN_CREATE_CRED_MORE_ABOUT_PASSKEYS_INTRO(1328); + CREDMAN_CREATE_CRED_MORE_ABOUT_PASSKEYS_INTRO(1328), + + @UiEvent(doc = "The single tap biometric flow is launched.") + CREDMAN_CREATE_CRED_BIOMETRIC_FLOW_LAUNCHED(1800); override fun getId(): Int { return this.id diff --git a/packages/CredentialManager/src/com/android/credentialmanager/logging/GetCredentialEvent.kt b/packages/CredentialManager/src/com/android/credentialmanager/logging/GetCredentialEvent.kt index 8de8895e8ffc..8870f28b9fcc 100644 --- a/packages/CredentialManager/src/com/android/credentialmanager/logging/GetCredentialEvent.kt +++ b/packages/CredentialManager/src/com/android/credentialmanager/logging/GetCredentialEvent.kt @@ -54,7 +54,10 @@ enum class GetCredentialEvent(private val id: Int) : UiEventLogger.UiEventEnum { CREDMAN_GET_CRED_PRIMARY_SELECTION_CARD(1341), @UiEvent(doc = "The all sign in option card is visible on screen.") - CREDMAN_GET_CRED_ALL_SIGN_IN_OPTION_CARD(1342); + CREDMAN_GET_CRED_ALL_SIGN_IN_OPTION_CARD(1342), + + @UiEvent(doc = "The single tap biometric flow is launched.") + CREDMAN_GET_CRED_BIOMETRIC_FLOW_LAUNCHED(1801); override fun getId(): Int { return this.id diff --git a/packages/SettingsLib/Color/res/values/colors.xml b/packages/SettingsLib/Color/res/values/colors.xml index b0b9b10952b8..709752362153 100644 --- a/packages/SettingsLib/Color/res/values/colors.xml +++ b/packages/SettingsLib/Color/res/values/colors.xml @@ -62,4 +62,5 @@ <color name="settingslib_color_cyan400">#4ecde6</color> <color name="settingslib_color_cyan300">#78d9ec</color> <color name="settingslib_color_cyan100">#cbf0f8</color> + <color name="settingslib_color_charcoal">#171717</color> </resources> diff --git a/packages/SettingsLib/IllustrationPreference/src/com/android/settingslib/widget/LottieColorUtils.java b/packages/SettingsLib/IllustrationPreference/src/com/android/settingslib/widget/LottieColorUtils.java index 0447ef8357eb..cf645f73beb3 100644 --- a/packages/SettingsLib/IllustrationPreference/src/com/android/settingslib/widget/LottieColorUtils.java +++ b/packages/SettingsLib/IllustrationPreference/src/com/android/settingslib/widget/LottieColorUtils.java @@ -41,6 +41,9 @@ public class LottieColorUtils { static { HashMap<String, Integer> map = new HashMap<>(); map.put( + ".grey200", + R.color.settingslib_color_grey800); + map.put( ".grey600", R.color.settingslib_color_grey400); map.put( @@ -67,6 +70,9 @@ public class LottieColorUtils { map.put( ".red200", R.color.settingslib_color_red500); + map.put( + ".cream", + R.color.settingslib_color_charcoal); DARK_TO_LIGHT_THEME_COLOR_MAP = Collections.unmodifiableMap(map); } diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/ui/Lottie.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/ui/Lottie.kt index a6cc3a9a6dd2..ea4480fbe235 100644 --- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/ui/Lottie.kt +++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/ui/Lottie.kt @@ -49,6 +49,7 @@ fun Lottie( object LottieColorUtils { private val DARK_TO_LIGHT_THEME_COLOR_MAP = mapOf( + ".grey200" to R.color.settingslib_color_grey800, ".grey600" to R.color.settingslib_color_grey400, ".grey800" to R.color.settingslib_color_grey300, ".grey900" to R.color.settingslib_color_grey50, @@ -58,6 +59,7 @@ object LottieColorUtils { ".green400" to R.color.settingslib_color_green600, ".green200" to R.color.settingslib_color_green500, ".red200" to R.color.settingslib_color_red500, + ".cream" to R.color.settingslib_color_charcoal ) @Composable diff --git a/packages/SettingsProvider/src/android/provider/settings/validators/GlobalSettingsValidators.java b/packages/SettingsProvider/src/android/provider/settings/validators/GlobalSettingsValidators.java index f83928dff98e..03c2a83519d8 100644 --- a/packages/SettingsProvider/src/android/provider/settings/validators/GlobalSettingsValidators.java +++ b/packages/SettingsProvider/src/android/provider/settings/validators/GlobalSettingsValidators.java @@ -418,6 +418,7 @@ public class GlobalSettingsValidators { VALIDATORS.put(Global.Wearable.CHARGING_SOUNDS_ENABLED, BOOLEAN_VALIDATOR); VALIDATORS.put(Global.Wearable.BEDTIME_MODE, BOOLEAN_VALIDATOR); VALIDATORS.put(Global.Wearable.BEDTIME_HARD_MODE, BOOLEAN_VALIDATOR); + VALIDATORS.put(Global.Wearable.VIBRATE_FOR_ACTIVE_UNLOCK, BOOLEAN_VALIDATOR); VALIDATORS.put(Global.Wearable.DYNAMIC_COLOR_THEME_ENABLED, BOOLEAN_VALIDATOR); VALIDATORS.put(Global.Wearable.SCREENSHOT_ENABLED, BOOLEAN_VALIDATOR); VALIDATORS.put(Global.Wearable.UPGRADE_DATA_MIGRATION_STATUS, diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsState.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsState.java index 04922d6e2c99..c8992c344b2d 100644 --- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsState.java +++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsState.java @@ -64,6 +64,7 @@ import java.io.File; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.FileOutputStream; +import java.io.InputStream; import java.io.IOException; import java.io.PrintWriter; import java.nio.file.Files; @@ -85,13 +86,13 @@ import java.util.concurrent.CountDownLatch; // FOR ACONFIGD TEST MISSION AND ROLLOUT import java.io.DataInputStream; import java.io.DataOutputStream; -import android.net.LocalSocketAddress; -import android.net.LocalSocket; import android.util.proto.ProtoInputStream; import android.aconfigd.Aconfigd.StorageRequestMessage; import android.aconfigd.Aconfigd.StorageRequestMessages; import android.aconfigd.Aconfigd.StorageReturnMessage; import android.aconfigd.Aconfigd.StorageReturnMessages; +import android.aconfigd.AconfigdClientSocket; +import android.aconfigd.AconfigdFlagInfo; import android.aconfigd.AconfigdJavaUtils; import static com.android.aconfig_new_storage.Flags.enableAconfigStorageDaemon; /** @@ -265,6 +266,10 @@ final class SettingsState { @NonNull private Map<String, Map<String, String>> mNamespaceDefaults; + // TOBO(b/312444587): remove the comparison logic after Test Mission 2. + @NonNull + private Map<String, AconfigdFlagInfo> mAconfigDefaultFlags; + public static final int SETTINGS_TYPE_GLOBAL = 0; public static final int SETTINGS_TYPE_SYSTEM = 1; public static final int SETTINGS_TYPE_SECURE = 2; @@ -334,8 +339,13 @@ final class SettingsState { + settingTypeToString(getTypeFromKey(key)) + "]"; } - public SettingsState(Context context, Object lock, File file, int key, - int maxBytesPerAppPackage, Looper looper) { + public SettingsState( + Context context, + Object lock, + File file, + int key, + int maxBytesPerAppPackage, + Looper looper) { // It is important that we use the same lock as the settings provider // to ensure multiple mutations on this state are atomically persisted // as the async persistence should be blocked while we make changes. @@ -353,12 +363,15 @@ final class SettingsState { mPackageToMemoryUsage = null; } - mHistoricalOperations = Build.IS_DEBUGGABLE - ? new ArrayList<>(HISTORICAL_OPERATION_COUNT) : null; + mHistoricalOperations = + Build.IS_DEBUGGABLE ? new ArrayList<>(HISTORICAL_OPERATION_COUNT) : null; mNamespaceDefaults = new HashMap<>(); + mAconfigDefaultFlags = new HashMap<>(); ProtoOutputStream requests = null; + Map<String, AconfigdFlagInfo> aconfigFlagMap = new HashMap<>(); + synchronized (mLock) { readStateSyncLocked(); @@ -375,39 +388,114 @@ final class SettingsState { } } + if (enableAconfigStorageDaemon()) { + if (isConfigSettingsKey(mKey)) { + aconfigFlagMap = getAllAconfigFlagsFromSettings(); + } + } + if (isConfigSettingsKey(mKey)) { - requests = handleBulkSyncToNewStorage(); + requests = handleBulkSyncToNewStorage(aconfigFlagMap); } } - if (requests != null) { - LocalSocket client = new LocalSocket(); - try{ - client.connect(new LocalSocketAddress( - "aconfigd", LocalSocketAddress.Namespace.RESERVED)); - Slog.d(LOG_TAG, "connected to aconfigd socket"); - } catch (IOException ioe) { - Slog.e(LOG_TAG, "failed to connect to aconfigd socket", ioe); - return; + if (enableAconfigStorageDaemon()) { + if (isConfigSettingsKey(mKey)){ + AconfigdClientSocket localSocket = AconfigdJavaUtils.getAconfigdClientSocket(); + if (requests != null) { + InputStream res = localSocket.send(requests.getBytes()); + if (res == null) { + Slog.w(LOG_TAG, "Bulk sync request to acongid failed."); + } + } + // TOBO(b/312444587): remove the comparison logic after Test Mission 2. + if (mSettings.get("aconfigd_marker/bulk_synced").value.equals("true") + && requests == null) { + Map<String, AconfigdFlagInfo> aconfigdFlagMap = + AconfigdJavaUtils.listFlagsValueInNewStorage(localSocket); + compareFlagValueInNewStorage( + aconfigFlagMap, + mAconfigDefaultFlags, + aconfigdFlagMap); + } } - AconfigdJavaUtils.sendAconfigdRequests(client, requests); } } - // TODO(b/341764371): migrate aconfig flag push to GMS core - public static class FlagOverrideToSync { - public String packageName; - public String flagName; - public String flagValue; - public boolean isLocal; + // TOBO(b/312444587): remove the comparison logic after Test Mission 2. + public int compareFlagValueInNewStorage( + Map<String, AconfigdFlagInfo> settingFlagMap, + Map<String, AconfigdFlagInfo> defaultFlagMap, + Map<String, AconfigdFlagInfo> aconfigdFlagMap) { + + // Get all defaults from the default map. The mSettings may not contain + // all flags, since it only contains updated flags. + int diffNum = 0; + for (Map.Entry<String, AconfigdFlagInfo> entry : defaultFlagMap.entrySet()) { + String key = entry.getKey(); + AconfigdFlagInfo flag = entry.getValue(); + if (settingFlagMap.containsKey(key)) { + flag.merge(settingFlagMap.get(key)); + } + + AconfigdFlagInfo aconfigdFlag = aconfigdFlagMap.get(key); + if (aconfigdFlag == null) { + Slog.w(LOG_TAG, String.format("Flag %s is missing from aconfigd", key)); + diffNum++; + continue; + } + String diff = flag.dumpDiff(aconfigdFlag); + if (!diff.isEmpty()) { + Slog.w( + LOG_TAG, + String.format( + "Flag %s is different in Settings and aconfig: %s", key, diff)); + diffNum++; + } + } + + for (String key : aconfigdFlagMap.keySet()) { + if (defaultFlagMap.containsKey(key)) continue; + Slog.w(LOG_TAG, String.format("Flag %s is missing from Settings", key)); + diffNum++; + } + + if (diffNum == 0) { + Slog.i(LOG_TAG, "Settings and new storage have same flags."); + } + return diffNum; + } + + @GuardedBy("mLock") + public Map<String, AconfigdFlagInfo> getAllAconfigFlagsFromSettings() { + Map<String, AconfigdFlagInfo> ret = new HashMap<>(); + int numSettings = mSettings.size(); + int num_requests = 0; + for (int i = 0; i < numSettings; i++) { + String name = mSettings.keyAt(i); + Setting setting = mSettings.valueAt(i); + AconfigdFlagInfo flag = + getFlagOverrideToSync(name, setting.getValue()); + if (flag == null) { + continue; + } + String fullFlagName = flag.getFullFlagName(); + AconfigdFlagInfo prev = ret.putIfAbsent(fullFlagName,flag); + if (prev != null) { + prev.merge(flag); + } + ++num_requests; + } + Slog.i(LOG_TAG, num_requests + " flag override requests created"); + return ret; } // TODO(b/341764371): migrate aconfig flag push to GMS core @VisibleForTesting @GuardedBy("mLock") - public FlagOverrideToSync getFlagOverrideToSync(String name, String value) { + public AconfigdFlagInfo getFlagOverrideToSync(String name, String value) { int slashIdx = name.indexOf("/"); - if (slashIdx <= 0 || slashIdx >= name.length()-1) { + if (slashIdx <= 0 || slashIdx >= name.length() - 1) { Slog.e(LOG_TAG, "invalid flag name " + name); return null; } @@ -430,8 +518,9 @@ final class SettingsState { } String aconfigName = namespace + "/" + fullFlagName; - boolean isAconfig = mNamespaceDefaults.containsKey(namespace) - && mNamespaceDefaults.get(namespace).containsKey(aconfigName); + boolean isAconfig = + mNamespaceDefaults.containsKey(namespace) + && mNamespaceDefaults.get(namespace).containsKey(aconfigName); if (!isAconfig) { return null; } @@ -443,25 +532,30 @@ final class SettingsState { return null; } - FlagOverrideToSync flag = new FlagOverrideToSync(); - flag.packageName = fullFlagName.substring(0, dotIdx); - flag.flagName = fullFlagName.substring(dotIdx + 1); - flag.isLocal = isLocal; - flag.flagValue = value; - return flag; + AconfigdFlagInfo.Builder builder = AconfigdFlagInfo.newBuilder() + .setPackageName(fullFlagName.substring(0, dotIdx)) + .setFlagName(fullFlagName.substring(dotIdx + 1)) + .setDefaultFlagValue(mNamespaceDefaults.get(namespace).get(aconfigName)); + + if (isLocal) { + builder.setHasLocalOverride(isLocal).setBootFlagValue(value).setLocalFlagValue(value); + } else { + builder.setHasServerOverride(true).setServerFlagValue(value).setBootFlagValue(value); + } + return builder.build(); } // TODO(b/341764371): migrate aconfig flag push to GMS core @VisibleForTesting @GuardedBy("mLock") - public ProtoOutputStream handleBulkSyncToNewStorage() { + public ProtoOutputStream handleBulkSyncToNewStorage( + Map<String, AconfigdFlagInfo> aconfigFlagMap) { // get marker or add marker if it does not exist final String bulkSyncMarkerName = new String("aconfigd_marker/bulk_synced"); Setting markerSetting = mSettings.get(bulkSyncMarkerName); if (markerSetting == null) { - markerSetting = new Setting( - bulkSyncMarkerName, "false", false, "aconfig", "aconfig"); + markerSetting = new Setting(bulkSyncMarkerName, "false", false, "aconfig", "aconfig"); mSettings.put(bulkSyncMarkerName, markerSetting); } @@ -479,24 +573,19 @@ final class SettingsState { AconfigdJavaUtils.writeResetStorageRequest(requests); // loop over all settings and add flag override requests - final int numSettings = mSettings.size(); - int num_requests = 0; - for (int i = 0; i < numSettings; i++) { - String name = mSettings.keyAt(i); - Setting setting = mSettings.valueAt(i); - FlagOverrideToSync flag = - getFlagOverrideToSync(name, setting.getValue()); - if (flag == null) { - continue; - } - ++num_requests; + for (AconfigdFlagInfo flag : aconfigFlagMap.values()) { + String value = + flag.getHasLocalOverride() + ? flag.getLocalFlagValue() + : flag.getServerFlagValue(); AconfigdJavaUtils.writeFlagOverrideRequest( - requests, flag.packageName, flag.flagName, flag.flagValue, - flag.isLocal); + requests, + flag.getPackageName(), + flag.getFlagName(), + value, + flag.getHasLocalOverride()); } - Slog.i(LOG_TAG, num_requests + " flag override requests created"); - // mark sync has been done markerSetting.value = "true"; scheduleWriteIfNeededLocked(); @@ -513,14 +602,14 @@ final class SettingsState { return null; } } - } @GuardedBy("mLock") private void loadAconfigDefaultValuesLocked(List<String> filePaths) { for (String fileName : filePaths) { try (FileInputStream inputStream = new FileInputStream(fileName)) { - loadAconfigDefaultValues(inputStream.readAllBytes(), mNamespaceDefaults); + loadAconfigDefaultValues( + inputStream.readAllBytes(), mNamespaceDefaults, mAconfigDefaultFlags); } catch (IOException e) { Slog.e(LOG_TAG, "failed to read protobuf", e); } @@ -566,21 +655,30 @@ final class SettingsState { @VisibleForTesting @GuardedBy("mLock") - public static void loadAconfigDefaultValues(byte[] fileContents, - @NonNull Map<String, Map<String, String>> defaultMap) { + public static void loadAconfigDefaultValues( + byte[] fileContents, + @NonNull Map<String, Map<String, String>> defaultMap, + @NonNull Map<String, AconfigdFlagInfo> flagInfoDefault) { try { - parsed_flags parsedFlags = - parsed_flags.parseFrom(fileContents); + parsed_flags parsedFlags = parsed_flags.parseFrom(fileContents); for (parsed_flag flag : parsedFlags.getParsedFlagList()) { if (!defaultMap.containsKey(flag.getNamespace())) { Map<String, String> defaults = new HashMap<>(); defaultMap.put(flag.getNamespace(), defaults); } - String flagName = flag.getNamespace() - + "/" + flag.getPackage() + "." + flag.getName(); - String flagValue = flag.getState() == flag_state.ENABLED - ? "true" : "false"; + String fullFlagName = flag.getPackage() + "." + flag.getName(); + String flagName = flag.getNamespace() + "/" + fullFlagName; + String flagValue = flag.getState() == flag_state.ENABLED ? "true" : "false"; defaultMap.get(flag.getNamespace()).put(flagName, flagValue); + if (!flagInfoDefault.containsKey(fullFlagName)) { + flagInfoDefault.put( + fullFlagName, + AconfigdFlagInfo.newBuilder() + .setPackageName(flag.getPackage()) + .setFlagName(flag.getName()) + .setDefaultFlagValue(flagValue) + .build()); + } } } catch (IOException e) { Slog.e(LOG_TAG, "failed to parse protobuf", e); @@ -1646,7 +1744,6 @@ final class SettingsState { } } } - mSettings.put(name, new Setting(name, value, defaultValue, packageName, tag, fromSystem, id, isPreservedInRestore)); diff --git a/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java b/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java index 473955f9b679..4ec170dda6f3 100644 --- a/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java +++ b/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java @@ -619,6 +619,7 @@ public class SettingsBackupTest { Settings.Global.Wearable.COOLDOWN_MODE_ON, Settings.Global.Wearable.BEDTIME_MODE, Settings.Global.Wearable.BEDTIME_HARD_MODE, + Settings.Global.Wearable.VIBRATE_FOR_ACTIVE_UNLOCK, Settings.Global.Wearable.LOCK_SCREEN_STATE, Settings.Global.Wearable.DISABLE_AOD_WHILE_PLUGGED, Settings.Global.Wearable.NETWORK_LOCATION_OPT_IN, diff --git a/packages/SettingsProvider/test/src/com/android/providers/settings/SettingsStateTest.java b/packages/SettingsProvider/test/src/com/android/providers/settings/SettingsStateTest.java index 244c8c4d99bc..256b999ca6c5 100644 --- a/packages/SettingsProvider/test/src/com/android/providers/settings/SettingsStateTest.java +++ b/packages/SettingsProvider/test/src/com/android/providers/settings/SettingsStateTest.java @@ -24,13 +24,13 @@ import static junit.framework.Assert.fail; import android.aconfig.Aconfig; import android.aconfig.Aconfig.parsed_flag; import android.aconfig.Aconfig.parsed_flags; +import android.aconfigd.AconfigdFlagInfo; import android.os.Looper; import android.platform.test.annotations.RequiresFlagsEnabled; import android.platform.test.flag.junit.CheckFlagsRule; import android.platform.test.flag.junit.DeviceFlagsValueProvider; import android.util.Xml; import android.util.proto.ProtoOutputStream; -import com.android.providers.settings.SettingsState.FlagOverrideToSync; import androidx.test.InstrumentationRegistry; import androidx.test.runner.AndroidJUnit4; @@ -145,16 +145,32 @@ public class SettingsStateTest { .setState(Aconfig.flag_state.ENABLED) .setPermission(Aconfig.flag_permission.READ_WRITE)) .build(); + + AconfigdFlagInfo flag1 = AconfigdFlagInfo.newBuilder() + .setPackageName("com.android.flags") + .setFlagName("flag1") + .setDefaultFlagValue("false") + .build(); + AconfigdFlagInfo flag2 = AconfigdFlagInfo.newBuilder() + .setPackageName("com.android.flags") + .setFlagName("flag2") + .setDefaultFlagValue("true") + .build(); + Map<String, AconfigdFlagInfo> flagInfoDefault = new HashMap<>(); synchronized (lock) { Map<String, Map<String, String>> defaults = new HashMap<>(); - settingsState.loadAconfigDefaultValues(flags.toByteArray(), defaults); + settingsState.loadAconfigDefaultValues( + flags.toByteArray(), defaults, flagInfoDefault); Map<String, String> namespaceDefaults = defaults.get("test_namespace"); assertEquals(2, namespaceDefaults.keySet().size()); assertEquals("false", namespaceDefaults.get("test_namespace/com.android.flags.flag1")); assertEquals("true", namespaceDefaults.get("test_namespace/com.android.flags.flag2")); } + + assertEquals(flag1, flagInfoDefault.get(flag1.getFullFlagName())); + assertEquals(flag2, flagInfoDefault.get(flag2.getFullFlagName())); } @Test @@ -165,6 +181,8 @@ public class SettingsStateTest { InstrumentationRegistry.getContext(), lock, mSettingsFile, configKey, SettingsState.MAX_BYTES_PER_APP_PACKAGE_UNLIMITED, Looper.getMainLooper()); + Map<String, AconfigdFlagInfo> flagInfoDefault = new HashMap<>(); + parsed_flags flags = parsed_flags .newBuilder() .addParsedFlag(parsed_flag @@ -177,7 +195,8 @@ public class SettingsStateTest { synchronized (lock) { Map<String, Map<String, String>> defaults = new HashMap<>(); - settingsState.loadAconfigDefaultValues(flags.toByteArray(), defaults); + settingsState.loadAconfigDefaultValues( + flags.toByteArray(), defaults, flagInfoDefault); Map<String, String> namespaceDefaults = defaults.get("test_namespace"); assertEquals(null, namespaceDefaults); @@ -204,10 +223,12 @@ public class SettingsStateTest { .setState(Aconfig.flag_state.DISABLED) .setPermission(Aconfig.flag_permission.READ_WRITE)) .build(); + Map<String, AconfigdFlagInfo> flagInfoDefault = new HashMap<>(); synchronized (lock) { Map<String, Map<String, String>> defaults = new HashMap<>(); - settingsState.loadAconfigDefaultValues(flags.toByteArray(), defaults); + settingsState.loadAconfigDefaultValues( + flags.toByteArray(), defaults, flagInfoDefault); settingsState.addAconfigDefaultValuesFromMap(defaults); settingsState.insertSettingLocked("test_namespace/com.android.flags.flag5", @@ -238,8 +259,10 @@ public class SettingsStateTest { @Test public void testInvalidAconfigProtoDoesNotCrash() { Map<String, Map<String, String>> defaults = new HashMap<>(); + Map<String, AconfigdFlagInfo> flagInfoDefault = new HashMap<>(); SettingsState settingsState = getSettingStateObject(); - settingsState.loadAconfigDefaultValues("invalid protobuf".getBytes(), defaults); + settingsState.loadAconfigDefaultValues( + "invalid protobuf".getBytes(), defaults, flagInfoDefault); } @Test @@ -759,6 +782,8 @@ public class SettingsStateTest { Map<String, String> keyValues = Map.of("test_namespace/com.android.flags.flag3", "true"); + Map<String, AconfigdFlagInfo> flagInfoDefault = new HashMap<>(); + parsed_flags flags = parsed_flags .newBuilder() .addParsedFlag(parsed_flag @@ -774,7 +799,8 @@ public class SettingsStateTest { synchronized (mLock) { settingsState.loadAconfigDefaultValues( - flags.toByteArray(), settingsState.getAconfigDefaultValues()); + flags.toByteArray(), + settingsState.getAconfigDefaultValues(), flagInfoDefault); List<String> updates = settingsState.setSettingsLocked("test_namespace/", keyValues, packageName); assertEquals(1, updates.size()); @@ -840,10 +866,13 @@ public class SettingsStateTest { .setState(Aconfig.flag_state.DISABLED) .setPermission(Aconfig.flag_permission.READ_WRITE)) .build(); + Map<String, AconfigdFlagInfo> flagInfoDefault = new HashMap<>(); synchronized (mLock) { settingsState.loadAconfigDefaultValues( - flags.toByteArray(), settingsState.getAconfigDefaultValues()); + flags.toByteArray(), + settingsState.getAconfigDefaultValues(), + flagInfoDefault); List<String> updates = settingsState.setSettingsLocked("test_namespace/", keyValues, packageName); assertEquals(3, updates.size()); @@ -973,10 +1002,12 @@ public class SettingsStateTest { .setState(Aconfig.flag_state.DISABLED) .setPermission(Aconfig.flag_permission.READ_WRITE)) .build(); + Map<String, AconfigdFlagInfo> flagInfoDefault = new HashMap<>(); synchronized (lock) { Map<String, Map<String, String>> defaults = new HashMap<>(); - settingsState.loadAconfigDefaultValues(flags.toByteArray(), defaults); + settingsState.loadAconfigDefaultValues( + flags.toByteArray(), defaults, flagInfoDefault); Map<String, String> namespaceDefaults = defaults.get("test_namespace"); assertEquals(1, namespaceDefaults.keySet().size()); settingsState.addAconfigDefaultValuesFromMap(defaults); @@ -991,22 +1022,28 @@ public class SettingsStateTest { "some_namespace/some_flag", "false") == null); // server override - FlagOverrideToSync flag = settingsState.getFlagOverrideToSync( + AconfigdFlagInfo flag = settingsState.getFlagOverrideToSync( "test_namespace/com.android.flags.flag1", "false"); assertTrue(flag != null); - assertEquals(flag.packageName, "com.android.flags"); - assertEquals(flag.flagName, "flag1"); - assertEquals(flag.flagValue, "false"); - assertEquals(flag.isLocal, false); + assertEquals(flag.getPackageName(), "com.android.flags"); + assertEquals(flag.getFlagName(), "flag1"); + assertEquals("false", flag.getBootFlagValue()); + assertEquals("false", flag.getServerFlagValue()); + assertFalse(flag.getHasLocalOverride()); + assertNull(flag.getLocalFlagValue()); + assertEquals("false", flag.getDefaultFlagValue()); // local override flag = settingsState.getFlagOverrideToSync( "device_config_overrides/test_namespace:com.android.flags.flag1", "false"); assertTrue(flag != null); - assertEquals(flag.packageName, "com.android.flags"); - assertEquals(flag.flagName, "flag1"); - assertEquals(flag.flagValue, "false"); - assertEquals(flag.isLocal, true); + assertEquals(flag.getPackageName(), "com.android.flags"); + assertEquals(flag.getFlagName(), "flag1"); + assertEquals("false", flag.getLocalFlagValue()); + assertEquals("false", flag.getBootFlagValue()); + assertTrue(flag.getHasLocalOverride()); + assertNull(flag.getServerFlagValue()); + assertEquals("false", flag.getDefaultFlagValue()); } @Rule public final SetFlagsRule mSetFlagsRule = new SetFlagsRule(); @@ -1020,18 +1057,25 @@ public class SettingsStateTest { InstrumentationRegistry.getContext(), lock, mSettingsFile, configKey, SettingsState.MAX_BYTES_PER_APP_PACKAGE_UNLIMITED, Looper.getMainLooper()); + Map<String, AconfigdFlagInfo> flags = new HashMap<>(); + AconfigdFlagInfo flag = AconfigdFlagInfo.newBuilder() + .setPackageName("com.android.flags") + .setFlagName("flag1") + .setBootFlagValue("true").build(); + flags.put("com.android.flags/flag1", flag); + synchronized (lock) { settingsState.insertSettingLocked("aconfigd_marker/bulk_synced", "false", null, false, "aconfig"); // first bulk sync - ProtoOutputStream requests = settingsState.handleBulkSyncToNewStorage(); + ProtoOutputStream requests = settingsState.handleBulkSyncToNewStorage(flags); assertTrue(requests != null); String value = settingsState.getSettingLocked("aconfigd_marker/bulk_synced").getValue(); assertEquals("true", value); // send time should no longer bulk sync - requests = settingsState.handleBulkSyncToNewStorage(); + requests = settingsState.handleBulkSyncToNewStorage(flags); assertTrue(requests == null); value = settingsState.getSettingLocked("aconfigd_marker/bulk_synced").getValue(); assertEquals("true", value); @@ -1047,21 +1091,200 @@ public class SettingsStateTest { InstrumentationRegistry.getContext(), lock, mSettingsFile, configKey, SettingsState.MAX_BYTES_PER_APP_PACKAGE_UNLIMITED, Looper.getMainLooper()); + Map<String, AconfigdFlagInfo> flags = new HashMap<>(); synchronized (lock) { settingsState.insertSettingLocked("aconfigd_marker/bulk_synced", "true", null, false, "aconfig"); // when aconfigd is off, should change the marker to false - ProtoOutputStream requests = settingsState.handleBulkSyncToNewStorage(); + ProtoOutputStream requests = settingsState.handleBulkSyncToNewStorage(flags); assertTrue(requests == null); String value = settingsState.getSettingLocked("aconfigd_marker/bulk_synced").getValue(); assertEquals("false", value); // marker started with false value, after call, it should remain false - requests = settingsState.handleBulkSyncToNewStorage(); + requests = settingsState.handleBulkSyncToNewStorage(flags); assertTrue(requests == null); value = settingsState.getSettingLocked("aconfigd_marker/bulk_synced").getValue(); assertEquals("false", value); } } + + @Test + public void testGetAllAconfigFlagsFromSettings() throws Exception { + final Object lock = new Object(); + final PrintStream os = new PrintStream(new FileOutputStream(mSettingsFile)); + os.print( + "<?xml version='1.0' encoding='UTF-8' standalone='yes' ?>" + + "<settings version=\"120\">" + + " <setting id=\"0\" name=\"test_namespace/com.android.flags.flag1\" " + + "value=\"false\" package=\"com.android.flags\" />" + + " <setting id=\"1\" name=\"device_config_overrides/test_namespace:com.android.flags.flag1\" " + + "value=\"true\" package=\"com.android.flags\" />" + + " <setting id=\"2\" name=\"device_config_overrides/test_namespace:com.android.flags.flag2\" " + + "value=\"true\" package=\"com.android.flags\" />" + + " <setting id=\"3\" name=\"test_namespace/com.android.flags.flag3\" " + + "value=\"true\" package=\"com.android.flags\" />" + + "</settings>"); + os.close(); + + int configKey = SettingsState.makeKey(SettingsState.SETTINGS_TYPE_CONFIG, 0); + + SettingsState settingsState = new SettingsState( + InstrumentationRegistry.getContext(), lock, mSettingsFile, configKey, + SettingsState.MAX_BYTES_PER_APP_PACKAGE_UNLIMITED, Looper.getMainLooper()); + + Map<String, AconfigdFlagInfo> ret; + synchronized (lock) { + ret = settingsState.getAllAconfigFlagsFromSettings(); + } + + assertTrue(ret.isEmpty()); + + parsed_flags flags = + parsed_flags + .newBuilder() + .addParsedFlag( + parsed_flag + .newBuilder() + .setPackage("com.android.flags") + .setName("flag1") + .setNamespace("test_namespace") + .setDescription("test flag") + .addBug("12345678") + .setState(Aconfig.flag_state.DISABLED) + .setPermission(Aconfig.flag_permission.READ_WRITE)) + .addParsedFlag( + parsed_flag + .newBuilder() + .setPackage("com.android.flags") + .setName("flag2") + .setNamespace("test_namespace") + .setDescription("test flag") + .addBug("12345678") + .setState(Aconfig.flag_state.DISABLED) + .setPermission(Aconfig.flag_permission.READ_WRITE)) + .addParsedFlag( + parsed_flag + .newBuilder() + .setPackage("com.android.flags") + .setName("flag3") + .setNamespace("test_namespace") + .setDescription("test flag") + .addBug("12345678") + .setState(Aconfig.flag_state.DISABLED) + .setPermission(Aconfig.flag_permission.READ_WRITE)) + .build(); + + Map<String, Map<String, String>> defaults = new HashMap<>(); + Map<String, AconfigdFlagInfo> flagInfoDefault = new HashMap<>(); + synchronized (lock) { + settingsState.loadAconfigDefaultValues( + flags.toByteArray(), defaults, flagInfoDefault); + settingsState.addAconfigDefaultValuesFromMap(defaults); + ret = settingsState.getAllAconfigFlagsFromSettings(); + } + + AconfigdFlagInfo expectedFlag1 = + AconfigdFlagInfo.newBuilder() + .setPackageName("com.android.flags") + .setFlagName("flag1") + .setServerFlagValue("false") + .setLocalFlagValue("true") + .setDefaultFlagValue("false") + .setBootFlagValue("true") + .setHasServerOverride(true) + .setHasLocalOverride(true) + .setIsReadWrite(false) + .build(); + + AconfigdFlagInfo expectedFlag2 = + AconfigdFlagInfo.newBuilder() + .setPackageName("com.android.flags") + .setFlagName("flag2") + .setLocalFlagValue("true") + .setDefaultFlagValue("false") + .setBootFlagValue("true") + .setHasLocalOverride(true) + .setHasServerOverride(false) + .setIsReadWrite(false) + .build(); + + + AconfigdFlagInfo expectedFlag3 = + AconfigdFlagInfo.newBuilder() + .setPackageName("com.android.flags") + .setFlagName("flag3") + .setServerFlagValue("true") + .setBootFlagValue("true") + .setDefaultFlagValue("false") + .setHasServerOverride(true) + .setIsReadWrite(false) + .build(); + + assertEquals(expectedFlag1, ret.get("com.android.flags.flag1")); + assertEquals(expectedFlag2, ret.get("com.android.flags.flag2")); + assertEquals(expectedFlag3, ret.get("com.android.flags.flag3")); + } + + @Test + public void testCompareFlagValueInNewStorage() { + int configKey = SettingsState.makeKey(SettingsState.SETTINGS_TYPE_CONFIG, 0); + Object lock = new Object(); + SettingsState settingsState = + new SettingsState( + InstrumentationRegistry.getContext(), + lock, + mSettingsFile, + configKey, + SettingsState.MAX_BYTES_PER_APP_PACKAGE_UNLIMITED, + Looper.getMainLooper()); + + AconfigdFlagInfo defaultFlag1 = + AconfigdFlagInfo.newBuilder() + .setPackageName("com.android.flags") + .setFlagName("flag1") + .setDefaultFlagValue("false") + .build(); + + AconfigdFlagInfo settingFlag1 = + AconfigdFlagInfo.newBuilder() + .setPackageName("com.android.flags") + .setFlagName("flag1") + .setServerFlagValue("true") + .setHasServerOverride(true) + .build(); + + AconfigdFlagInfo expectedFlag1 = + AconfigdFlagInfo.newBuilder() + .setPackageName("com.android.flags") + .setFlagName("flag1") + .setBootFlagValue("true") + .setServerFlagValue("true") + .setDefaultFlagValue("false") + .setHasServerOverride(true) + .build(); + + Map<String, AconfigdFlagInfo> settingMap = new HashMap<>(); + Map<String, AconfigdFlagInfo> aconfigdMap = new HashMap<>(); + Map<String, AconfigdFlagInfo> defaultMap = new HashMap<>(); + + defaultMap.put("com.android.flags.flag1", defaultFlag1); + settingMap.put("com.android.flags.flag1", settingFlag1); + aconfigdMap.put("com.android.flags.flag1", expectedFlag1); + + int ret = settingsState.compareFlagValueInNewStorage(settingMap, defaultMap, aconfigdMap); + assertEquals(0, ret); + + AconfigdFlagInfo defaultFlag2 = + AconfigdFlagInfo.newBuilder() + .setPackageName("com.android.flags") + .setFlagName("flag2") + .setDefaultFlagValue("false") + .build(); + defaultMap.put("com.android.flags.flag2", defaultFlag2); + + ret = settingsState.compareFlagValueInNewStorage(settingMap, defaultMap, aconfigdMap); + assertEquals(1, ret); + } } diff --git a/packages/SystemUI/Android.bp b/packages/SystemUI/Android.bp index e940674a1cf5..7a098ee34097 100644 --- a/packages/SystemUI/Android.bp +++ b/packages/SystemUI/Android.bp @@ -78,9 +78,42 @@ filegroup { visibility: ["//visibility:private"], } +// Tests where robolectric conversion caused errors in SystemUITests at runtime +filegroup { + name: "SystemUI-tests-broken-robofiles-sysui-run", + srcs: [ + "tests/src/**/systemui/globalactions/GlobalActionsDialogLiteTest.java", + "tests/src/**/systemui/media/controls/domain/pipeline/LegacyMediaDataManagerImplTest.kt", + "tests/src/**/systemui/media/controls/domain/pipeline/MediaDataProcessorTest.kt", + "tests/src/**/systemui/media/dialog/MediaOutputAdapterTest.java", + "tests/src/**/systemui/media/dialog/MediaOutputBaseDialogTest.java", + "tests/src/**/systemui/media/dialog/MediaOutputBroadcastDialogTest.java", + "tests/src/**/systemui/media/dialog/MediaOutputDialogTest.java", + "tests/src/**/systemui/mediaprojection/permission/MediaProjectionPermissionDialogDelegateTest.kt", + ], +} + filegroup { name: "SystemUI-tests-broken-robofiles-run", srcs: [ + "tests/src/**/systemui/keyguard/CustomizationProviderTest.kt", + "tests/src/**/systemui/globalactions/GlobalActionsColumnLayoutTest.java", + "tests/src/**/systemui/globalactions/GlobalActionsDialogLiteTest.java", + "tests/src/**/systemui/globalactions/GlobalActionsImeTest.java", + "tests/src/**/systemui/graphics/ImageLoaderTest.kt", + "tests/src/**/systemui/keyguard/KeyguardViewMediatorTest.java", + "tests/src/**/systemui/keyguard/LifecycleTest.java", + "tests/src/**/systemui/keyguard/data/repository/KeyguardTransitionRepositoryTest.kt", + "tests/src/**/systemui/keyguard/ui/view/layout/sections/ClockSectionTest.kt", + "tests/src/**/systemui/keyguard/data/repository/KeyguardBlueprintRepositoryTest.kt", + "tests/src/**/systemui/keyguard/ui/viewmodel/KeyguardSmartspaceViewModelTest.kt", + "tests/src/**/systemui/lifecycle/RepeatWhenAttachedTest.kt", + "tests/src/**/systemui/log/LogBufferTest.kt", + "tests/src/**/systemui/media/dialog/MediaOutputBaseDialogTest.java", + "tests/src/**/systemui/media/dialog/MediaOutputBroadcastDialogTest.java", + "tests/src/**/systemui/media/dialog/MediaOutputDialogTest.java", + "tests/src/**/systemui/media/controls/domain/resume/MediaResumeListenerTest.kt", + "tests/src/**/systemui/mediaprojection/taskswitcher/ui/TaskSwitcherNotificationCoordinatorTest.kt", "tests/src/**/systemui/util/LifecycleFragmentTest.java", "tests/src/**/systemui/util/TestableAlertDialogTest.kt", "tests/src/**/systemui/util/kotlin/PairwiseFlowTest", @@ -767,6 +800,7 @@ android_robolectric_test { ":SystemUI-tests-robofiles", ], exclude_srcs: [ + ":SystemUI-tests-broken-robofiles-sysui-run", ":SystemUI-tests-broken-robofiles-compile", ":SystemUI-tests-broken-robofiles-run", ], diff --git a/packages/SystemUI/aconfig/systemui.aconfig b/packages/SystemUI/aconfig/systemui.aconfig index 63a52d624ca7..3310cfac6bf3 100644 --- a/packages/SystemUI/aconfig/systemui.aconfig +++ b/packages/SystemUI/aconfig/systemui.aconfig @@ -736,16 +736,6 @@ flag { } flag { - name: "trim_resources_with_background_trim_at_lock" - namespace: "systemui" - description: "Trim fonts and other caches when the device locks to lower memory consumption." - bug: "322143614" - metadata { - purpose: PURPOSE_BUGFIX - } -} - -flag { name: "dedicated_notif_inflation_thread" namespace: "systemui" description: "Create a separate background thread for inflating notifications" @@ -1011,6 +1001,13 @@ flag { } flag { + name: "glanceable_hub_allow_keyguard_when_dreaming" + namespace: "systemui" + description: "Allows users to exit dream to keyguard with glanceable hub enabled" + bug: "343505271" +} + +flag { name: "new_touchpad_gestures_tutorial" namespace: "systemui" description: "Enables new interactive tutorial for learning touchpad gestures" diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/OverlayShade.kt b/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/OverlayShade.kt index a7302061d02f..d776e2ad5fe0 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/OverlayShade.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/OverlayShade.kt @@ -14,17 +14,28 @@ * limitations under the License. */ +@file:OptIn(ExperimentalLayoutApi::class) + package com.android.systemui.shade.ui.composable import androidx.compose.foundation.background import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.ExperimentalLayoutApi +import androidx.compose.foundation.layout.PaddingValues import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.WindowInsets +import androidx.compose.foundation.layout.asPaddingValues +import androidx.compose.foundation.layout.calculateEndPadding +import androidx.compose.foundation.layout.calculateStartPadding +import androidx.compose.foundation.layout.displayCutout import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.systemBarsIgnoringVisibility +import androidx.compose.foundation.layout.waterfall import androidx.compose.foundation.layout.width import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material3.MaterialTheme @@ -35,12 +46,12 @@ import androidx.compose.runtime.getValue import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip import androidx.compose.ui.graphics.Color +import androidx.compose.ui.platform.LocalLayoutDirection import androidx.compose.ui.unit.dp import androidx.lifecycle.compose.collectAsStateWithLifecycle import com.android.compose.animation.scene.ElementKey import com.android.compose.animation.scene.LowestZIndexScenePicker import com.android.compose.animation.scene.SceneScope -import com.android.compose.modifiers.thenIf import com.android.compose.windowsizeclass.LocalWindowSizeClass import com.android.systemui.keyguard.ui.composable.LockscreenContent import com.android.systemui.scene.shared.model.Scenes @@ -59,8 +70,6 @@ fun SceneScope.OverlayShade( content: @Composable () -> Unit, ) { val backgroundScene by viewModel.backgroundScene.collectAsStateWithLifecycle() - val widthSizeClass = LocalWindowSizeClass.current.widthSizeClass - val isPanelFullWidth = widthSizeClass == WindowWidthSizeClass.Compact Box(modifier) { if (backgroundScene == Scenes.Lockscreen) { @@ -73,10 +82,7 @@ fun SceneScope.OverlayShade( Scrim(onClicked = viewModel::onScrimClicked) Row( - modifier = - Modifier.fillMaxSize().thenIf(!isPanelFullWidth) { - Modifier.padding(OverlayShade.Dimensions.ScrimContentPadding) - }, + modifier = Modifier.fillMaxSize().panelPadding(), horizontalArrangement = horizontalArrangement, ) { Panel( @@ -138,6 +144,42 @@ private fun Modifier.panelSize(): Modifier { ) } +@Composable +private fun Modifier.panelPadding(): Modifier { + val widthSizeClass = LocalWindowSizeClass.current.widthSizeClass + val systemBars = WindowInsets.systemBarsIgnoringVisibility + val displayCutout = WindowInsets.displayCutout + val waterfall = WindowInsets.waterfall + val contentPadding = PaddingValues(all = OverlayShade.Dimensions.ScrimContentPadding) + + val combinedPadding = + combinePaddings( + systemBars.asPaddingValues(), + displayCutout.asPaddingValues(), + waterfall.asPaddingValues(), + contentPadding + ) + + return if (widthSizeClass == WindowWidthSizeClass.Compact) { + padding(bottom = combinedPadding.calculateBottomPadding()) + } else { + padding(combinedPadding) + } +} + +/** Creates a union of [paddingValues] by using the max padding of each edge. */ +@Composable +private fun combinePaddings(vararg paddingValues: PaddingValues): PaddingValues { + val layoutDirection = LocalLayoutDirection.current + + return PaddingValues( + start = paddingValues.maxOfOrNull { it.calculateStartPadding(layoutDirection) } ?: 0.dp, + top = paddingValues.maxOfOrNull { it.calculateTopPadding() } ?: 0.dp, + end = paddingValues.maxOfOrNull { it.calculateEndPadding(layoutDirection) } ?: 0.dp, + bottom = paddingValues.maxOfOrNull { it.calculateBottomPadding() } ?: 0.dp + ) +} + object OverlayShade { object Elements { val Scrim = ElementKey("OverlayShadeScrim", scenePicker = LowestZIndexScenePicker) diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/ui/composable/HorizontalVolumePanelContent.kt b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/ui/composable/HorizontalVolumePanelContent.kt index ac5004e16a3b..580aba586ee1 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/ui/composable/HorizontalVolumePanelContent.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/ui/composable/HorizontalVolumePanelContent.kt @@ -16,6 +16,7 @@ package com.android.systemui.volume.panel.ui.composable +import androidx.compose.animation.AnimatedContent import androidx.compose.animation.AnimatedVisibility import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column @@ -56,17 +57,19 @@ fun VolumePanelComposeScope.HorizontalVolumePanelContent( with(component.component as ComposeVolumePanelUiComponent) { Content(Modifier) } } } - Row( - modifier = Modifier.fillMaxWidth(), - horizontalArrangement = Arrangement.spacedBy(spacing), - ) { - for (component in layout.footerComponents) { - AnimatedVisibility( - visible = component.isVisible, - modifier = Modifier.weight(1f), - ) { - with(component.component as ComposeVolumePanelUiComponent) { - Content(Modifier) + AnimatedContent( + targetState = layout.footerComponents, + label = "FooterComponentAnimation", + ) { footerComponents -> + Row( + modifier = Modifier.fillMaxWidth(), + horizontalArrangement = Arrangement.spacedBy(spacing), + ) { + for (component in footerComponents) { + if (component.isVisible) { + with(component.component as ComposeVolumePanelUiComponent) { + Content(Modifier.weight(1f)) + } } } } diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/ui/composable/VerticalVolumePanelContent.kt b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/ui/composable/VerticalVolumePanelContent.kt index 9ea20b9da4b6..6349c1406a12 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/ui/composable/VerticalVolumePanelContent.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/ui/composable/VerticalVolumePanelContent.kt @@ -16,6 +16,7 @@ package com.android.systemui.volume.panel.ui.composable +import androidx.compose.animation.AnimatedContent import androidx.compose.animation.AnimatedVisibility import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column @@ -50,26 +51,27 @@ fun VolumePanelComposeScope.VerticalVolumePanelContent( with(component.component as ComposeVolumePanelUiComponent) { Content(Modifier) } } } - if (layout.footerComponents.isNotEmpty()) { + + AnimatedContent( + targetState = layout.footerComponents, + label = "FooterComponentAnimation", + ) { footerComponents -> Row( modifier = Modifier.fillMaxWidth().wrapContentHeight(), horizontalArrangement = Arrangement.spacedBy(if (isLargeScreen) 28.dp else 20.dp), ) { val visibleComponentsCount = - layout.footerComponents.fastSumBy { if (it.isVisible) 1 else 0 } + footerComponents.fastSumBy { if (it.isVisible) 1 else 0 } // Center footer component if there is only one present if (visibleComponentsCount == 1) { Spacer(modifier = Modifier.weight(0.5f)) } - for (component in layout.footerComponents) { - AnimatedVisibility( - visible = component.isVisible, - modifier = Modifier.weight(1f), - ) { + for (component in footerComponents) { + if (component.isVisible) { with(component.component as ComposeVolumePanelUiComponent) { - Content(Modifier) + Content(Modifier.weight(1f)) } } } diff --git a/packages/SystemUI/customization/src/com/android/systemui/util/Assert.java b/packages/SystemUI/customization/src/com/android/systemui/util/Assert.java index 165e9728b9d7..de9baa59b2b7 100644 --- a/packages/SystemUI/customization/src/com/android/systemui/util/Assert.java +++ b/packages/SystemUI/customization/src/com/android/systemui/util/Assert.java @@ -79,6 +79,21 @@ public class Assert { } } + /** + * Asserts that the current thread is the same as the given thread, or that the current thread + * is the test thread. + * @param expected The looper we expected to be running on + */ + public static void isCurrentThread(Looper expected) { + if (!expected.isCurrentThread() + && (sTestThread == null || sTestThread != Thread.currentThread())) { + throw new IllegalStateException("Called on wrong thread thread." + + " wanted " + expected.getThread().getName() + + " but instead got Thread.currentThread()=" + + Thread.currentThread().getName()); + } + } + public static void isNotMainThread() { if (sMainLooper.isCurrentThread() && (sTestThread == null || sTestThread == Thread.currentThread())) { diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalViewModelTest.kt index 5e19a41f345c..a1bad392799d 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalViewModelTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalViewModelTest.kt @@ -54,6 +54,8 @@ import com.android.systemui.keyguard.data.repository.fakeKeyguardRepository import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository import com.android.systemui.keyguard.domain.interactor.keyguardInteractor import com.android.systemui.keyguard.domain.interactor.keyguardTransitionInteractor +import com.android.systemui.keyguard.shared.model.DozeStateModel +import com.android.systemui.keyguard.shared.model.DozeTransitionModel import com.android.systemui.keyguard.shared.model.KeyguardState import com.android.systemui.keyguard.shared.model.StatusBarState import com.android.systemui.keyguard.shared.model.TransitionState @@ -62,6 +64,8 @@ import com.android.systemui.kosmos.testScope import com.android.systemui.log.logcatLogBuffer import com.android.systemui.media.controls.ui.controller.MediaHierarchyManager import com.android.systemui.media.controls.ui.view.MediaHost +import com.android.systemui.power.domain.interactor.PowerInteractor.Companion.setAwakeForTest +import com.android.systemui.power.domain.interactor.powerInteractor import com.android.systemui.settings.fakeUserTracker import com.android.systemui.shade.ShadeTestUtil import com.android.systemui.shade.domain.interactor.shadeInteractor @@ -71,7 +75,6 @@ import com.android.systemui.smartspace.data.repository.fakeSmartspaceRepository import com.android.systemui.testKosmos import com.android.systemui.user.data.repository.FakeUserRepository import com.android.systemui.user.data.repository.fakeUserRepository -import com.android.systemui.util.mockito.whenever import com.google.common.truth.Truth.assertThat import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.flow.flowOf @@ -85,6 +88,7 @@ import org.mockito.Mock import org.mockito.Mockito import org.mockito.Mockito.verify import org.mockito.MockitoAnnotations +import org.mockito.kotlin.whenever import platform.test.runner.parameterized.ParameterizedAndroidJunit4 import platform.test.runner.parameterized.Parameters @@ -138,6 +142,8 @@ class CommunalViewModelTest(flags: FlagsParameterization) : SysuiTestCase() { ) whenever(providerInfo.profile).thenReturn(UserHandle(MAIN_USER_INFO.id)) + kosmos.powerInteractor.setAwakeForTest() + underTest = CommunalViewModel( testScope, @@ -468,6 +474,229 @@ class CommunalViewModelTest(flags: FlagsParameterization) : SysuiTestCase() { assertThat(isFocusable).isEqualTo(false) } + @Test + fun isCommunalContentFlowFrozen_whenActivityStartedWhileDreaming() = + testScope.runTest { + val isCommunalContentFlowFrozen by + collectLastValue(underTest.isCommunalContentFlowFrozen) + + // 1. When dreaming not dozing + keyguardRepository.setDozeTransitionModel( + DozeTransitionModel(from = DozeStateModel.DOZE, to = DozeStateModel.FINISH) + ) + keyguardRepository.setDreaming(true) + keyguardRepository.setDreamingWithOverlay(true) + advanceTimeBy(60L) + // And keyguard is occluded by dream + keyguardRepository.setKeyguardOccluded(true) + + // And on hub + keyguardTransitionRepository.sendTransitionSteps( + from = KeyguardState.DREAMING, + to = KeyguardState.GLANCEABLE_HUB, + testScope = testScope, + ) + + // Then flow is not frozen + assertThat(isCommunalContentFlowFrozen).isEqualTo(false) + + // 2. When dreaming stopped by the new activity about to show on lock screen + keyguardRepository.setDreamingWithOverlay(false) + advanceTimeBy(60L) + + // Then flow is frozen + assertThat(isCommunalContentFlowFrozen).isEqualTo(true) + + // 3. When transitioned to OCCLUDED and activity shows + keyguardTransitionRepository.sendTransitionSteps( + from = KeyguardState.GLANCEABLE_HUB, + to = KeyguardState.OCCLUDED, + testScope = testScope, + ) + + // Then flow is not frozen + assertThat(isCommunalContentFlowFrozen).isEqualTo(false) + } + + @Test + fun isCommunalContentFlowFrozen_whenActivityStartedInHandheldMode() = + testScope.runTest { + val isCommunalContentFlowFrozen by + collectLastValue(underTest.isCommunalContentFlowFrozen) + + // 1. When on keyguard and not occluded + keyguardRepository.setKeyguardShowing(true) + keyguardRepository.setKeyguardOccluded(false) + + // And transitioned to hub + keyguardTransitionRepository.sendTransitionSteps( + from = KeyguardState.LOCKSCREEN, + to = KeyguardState.GLANCEABLE_HUB, + testScope = testScope, + ) + + // Then flow is not frozen + assertThat(isCommunalContentFlowFrozen).isEqualTo(false) + + // 2. When occluded by a new activity + keyguardRepository.setKeyguardOccluded(true) + runCurrent() + + // And transitioning to occluded + keyguardTransitionRepository.sendTransitionStep( + TransitionStep( + from = KeyguardState.GLANCEABLE_HUB, + to = KeyguardState.OCCLUDED, + transitionState = TransitionState.STARTED, + ) + ) + + keyguardTransitionRepository.sendTransitionStep( + from = KeyguardState.GLANCEABLE_HUB, + to = KeyguardState.OCCLUDED, + transitionState = TransitionState.RUNNING, + value = 0.5f, + ) + + // Then flow is frozen + assertThat(isCommunalContentFlowFrozen).isEqualTo(true) + + // 3. When transition is finished + keyguardTransitionRepository.sendTransitionStep( + from = KeyguardState.GLANCEABLE_HUB, + to = KeyguardState.OCCLUDED, + transitionState = TransitionState.FINISHED, + value = 1f, + ) + + // Then flow is not frozen + assertThat(isCommunalContentFlowFrozen).isEqualTo(false) + } + + @Test + fun communalContent_emitsFrozenContent_whenFrozen() = + testScope.runTest { + val communalContent by collectLastValue(underTest.communalContent) + tutorialRepository.setTutorialSettingState(Settings.Secure.HUB_MODE_TUTORIAL_COMPLETED) + + // When dreaming + keyguardRepository.setDozeTransitionModel( + DozeTransitionModel(from = DozeStateModel.DOZE, to = DozeStateModel.FINISH) + ) + keyguardRepository.setDreaming(true) + keyguardRepository.setDreamingWithOverlay(true) + advanceTimeBy(60L) + keyguardRepository.setKeyguardOccluded(true) + + // And transitioned to hub + keyguardTransitionRepository.sendTransitionSteps( + from = KeyguardState.DREAMING, + to = KeyguardState.GLANCEABLE_HUB, + testScope = testScope, + ) + + // Widgets available + val widgets = + listOf( + CommunalWidgetContentModel.Available( + appWidgetId = 0, + priority = 30, + providerInfo = providerInfo, + ), + CommunalWidgetContentModel.Available( + appWidgetId = 1, + priority = 20, + providerInfo = providerInfo, + ), + ) + widgetRepository.setCommunalWidgets(widgets) + + // Then hub shows widgets and the CTA tile + assertThat(communalContent).hasSize(3) + + // When dreaming stopped by another activity which should freeze flow + keyguardRepository.setDreamingWithOverlay(false) + advanceTimeBy(60L) + + // New timer available + val target = Mockito.mock(SmartspaceTarget::class.java) + whenever<String?>(target.smartspaceTargetId).thenReturn("target") + whenever(target.featureType).thenReturn(SmartspaceTarget.FEATURE_TIMER) + whenever(target.remoteViews).thenReturn(Mockito.mock(RemoteViews::class.java)) + smartspaceRepository.setCommunalSmartspaceTargets(listOf(target)) + runCurrent() + + // Still only emits widgets and the CTA tile + assertThat(communalContent).hasSize(3) + assertThat(communalContent?.get(0)) + .isInstanceOf(CommunalContentModel.WidgetContent::class.java) + assertThat(communalContent?.get(1)) + .isInstanceOf(CommunalContentModel.WidgetContent::class.java) + assertThat(communalContent?.get(2)) + .isInstanceOf(CommunalContentModel.CtaTileInViewMode::class.java) + } + + @Test + fun communalContent_emitsLatestContent_whenNotFrozen() = + testScope.runTest { + val communalContent by collectLastValue(underTest.communalContent) + tutorialRepository.setTutorialSettingState(Settings.Secure.HUB_MODE_TUTORIAL_COMPLETED) + + // When dreaming + keyguardRepository.setDozeTransitionModel( + DozeTransitionModel(from = DozeStateModel.DOZE, to = DozeStateModel.FINISH) + ) + keyguardRepository.setDreaming(true) + keyguardRepository.setDreamingWithOverlay(true) + advanceTimeBy(60L) + keyguardRepository.setKeyguardOccluded(true) + + // Transitioned to Glanceable hub. + keyguardTransitionRepository.sendTransitionSteps( + from = KeyguardState.DREAMING, + to = KeyguardState.GLANCEABLE_HUB, + testScope = testScope, + ) + + // And widgets available + val widgets = + listOf( + CommunalWidgetContentModel.Available( + appWidgetId = 0, + priority = 30, + providerInfo = providerInfo, + ), + CommunalWidgetContentModel.Available( + appWidgetId = 1, + priority = 20, + providerInfo = providerInfo, + ), + ) + widgetRepository.setCommunalWidgets(widgets) + + // Then emits widgets and the CTA tile + assertThat(communalContent).hasSize(3) + + // When new timer available + val target = Mockito.mock(SmartspaceTarget::class.java) + whenever(target.smartspaceTargetId).thenReturn("target") + whenever(target.featureType).thenReturn(SmartspaceTarget.FEATURE_TIMER) + whenever(target.remoteViews).thenReturn(Mockito.mock(RemoteViews::class.java)) + smartspaceRepository.setCommunalSmartspaceTargets(listOf(target)) + runCurrent() + + // Then emits timer, widgets and the CTA tile + assertThat(communalContent).hasSize(4) + assertThat(communalContent?.get(0)) + .isInstanceOf(CommunalContentModel.Smartspace::class.java) + assertThat(communalContent?.get(1)) + .isInstanceOf(CommunalContentModel.WidgetContent::class.java) + assertThat(communalContent?.get(2)) + .isInstanceOf(CommunalContentModel.WidgetContent::class.java) + assertThat(communalContent?.get(3)) + .isInstanceOf(CommunalContentModel.CtaTileInViewMode::class.java) + } + private suspend fun setIsMainUser(isMainUser: Boolean) { whenever(user.isMain).thenReturn(isMainUser) userRepository.setUserInfos(listOf(user)) diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/haptics/qs/QSLongPressEffectTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/haptics/qs/QSLongPressEffectTest.kt index c51413a2cc78..4849e66d37d5 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/haptics/qs/QSLongPressEffectTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/haptics/qs/QSLongPressEffectTest.kt @@ -26,6 +26,7 @@ import com.android.systemui.haptics.vibratorHelper import com.android.systemui.keyguard.data.repository.fakeKeyguardRepository import com.android.systemui.keyguard.domain.interactor.keyguardInteractor import com.android.systemui.kosmos.testScope +import com.android.systemui.qs.qsTileFactory import com.android.systemui.testKosmos import com.google.common.truth.Truth.assertThat import kotlinx.coroutines.test.TestScope @@ -41,6 +42,7 @@ class QSLongPressEffectTest : SysuiTestCase() { private val kosmos = testKosmos() private val vibratorHelper = kosmos.vibratorHelper + private val qsTile = kosmos.qsTileFactory.createTile("Test Tile") private val effectDuration = 400 private val lowTickDuration = 12 @@ -61,6 +63,7 @@ class QSLongPressEffectTest : SysuiTestCase() { vibratorHelper, kosmos.keyguardInteractor, ) + longPressEffect.qsTile = qsTile } @Test @@ -91,8 +94,10 @@ class QSLongPressEffectTest : SysuiTestCase() { // GIVEN an action down event occurs longPressEffect.handleActionDown() - // THEN the effect moves to the TIMEOUT_WAIT state + // THEN the effect moves to the TIMEOUT_WAIT state and starts the wait + val action by collectLastValue(longPressEffect.actionType) assertThat(longPressEffect.state).isEqualTo(QSLongPressEffect.State.TIMEOUT_WAIT) + assertThat(action).isEqualTo(QSLongPressEffect.ActionType.WAIT_TAP_TIMEOUT) } @Test @@ -107,20 +112,6 @@ class QSLongPressEffectTest : SysuiTestCase() { } @Test - fun onActionUp_whileWaiting_performsClick() = - testWhileInState(QSLongPressEffect.State.TIMEOUT_WAIT) { - // GIVEN an action is being collected - val action by collectLastValue(longPressEffect.actionType) - - // GIVEN an action up occurs - longPressEffect.handleActionUp() - - // THEN the action to invoke is the click action and the effect does not start - assertThat(action).isEqualTo(QSLongPressEffect.ActionType.CLICK) - assertEffectDidNotStart() - } - - @Test fun onWaitComplete_whileWaiting_beginsEffect() = testWhileInState(QSLongPressEffect.State.TIMEOUT_WAIT) { // GIVEN the pressed timeout is complete @@ -221,8 +212,10 @@ class QSLongPressEffectTest : SysuiTestCase() { // GIVEN that the animator was cancelled longPressEffect.handleAnimationCancel() - // THEN the state goes to the timeout wait + // THEN the state goes to the timeout wait and the wait is posted + val action by collectLastValue(longPressEffect.actionType) assertThat(longPressEffect.state).isEqualTo(QSLongPressEffect.State.TIMEOUT_WAIT) + assertThat(action).isEqualTo(QSLongPressEffect.ActionType.WAIT_TAP_TIMEOUT) } @Test @@ -238,6 +231,29 @@ class QSLongPressEffectTest : SysuiTestCase() { assertThat(longPressEffect.state).isEqualTo(QSLongPressEffect.State.IDLE) } + @Test + fun onTileClick_whileWaiting_withQSTile_clicks() = + testWhileInState(QSLongPressEffect.State.TIMEOUT_WAIT) { + // GIVEN that a click was detected + val couldClick = longPressEffect.onTileClick() + + // THEN the click is successful + assertThat(couldClick).isTrue() + } + + @Test + fun onTileClick_whileWaiting_withoutQSTile_cannotClick() = + testWhileInState(QSLongPressEffect.State.TIMEOUT_WAIT) { + // GIVEN that no QSTile has been set + longPressEffect.qsTile = null + + // GIVEN that a click was detected + val couldClick = longPressEffect.onTileClick() + + // THEN the click is not successful + assertThat(couldClick).isFalse() + } + private fun testWithScope(initialize: Boolean = true, test: suspend TestScope.() -> Unit) = with(kosmos) { testScope.runTest { diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerTest.java index 02993b8922b0..523a89ad5740 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerTest.java +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerTest.java @@ -99,14 +99,14 @@ import org.mockito.Mock; import org.mockito.Mockito; import org.mockito.MockitoAnnotations; -import platform.test.runner.parameterized.ParameterizedAndroidJunit4; -import platform.test.runner.parameterized.Parameters; - import java.util.ArrayList; import java.util.Collection; import java.util.List; import java.util.concurrent.Executor; +import platform.test.runner.parameterized.ParameterizedAndroidJunit4; +import platform.test.runner.parameterized.Parameters; + @SmallTest @RunWith(ParameterizedAndroidJunit4.class) public class NotificationLockscreenUserManagerTest extends SysuiTestCase { @@ -162,6 +162,7 @@ public class NotificationLockscreenUserManagerTest extends SysuiTestCase { private NotificationEntry mCurrentUserNotif; private NotificationEntry mSecondaryUserNotif; private NotificationEntry mWorkProfileNotif; + private NotificationEntry mSensitiveContentNotif; private final FakeFeatureFlagsClassic mFakeFeatureFlags = new FakeFeatureFlagsClassic(); private final FakeSystemClock mFakeSystemClock = new FakeSystemClock(); private final FakeExecutor mBackgroundExecutor = new FakeExecutor(mFakeSystemClock); @@ -224,6 +225,14 @@ public class NotificationLockscreenUserManagerTest extends SysuiTestCase { mWorkProfileNotif.setRanking(new RankingBuilder(mWorkProfileNotif.getRanking()) .setChannel(channel) .setVisibilityOverride(VISIBILITY_NO_OVERRIDE).build()); + mSensitiveContentNotif = new NotificationEntryBuilder() + .setNotification(notifWithPrivateVisibility) + .setUser(new UserHandle(mCurrentUser.id)) + .build(); + mSensitiveContentNotif.setRanking(new RankingBuilder(mCurrentUserNotif.getRanking()) + .setChannel(channel) + .setSensitiveContent(true) + .setVisibilityOverride(VISIBILITY_NO_OVERRIDE).build()); when(mNotifCollection.getEntry(mWorkProfileNotif.getKey())).thenReturn(mWorkProfileNotif); mLockscreenUserManager = new TestNotificationLockscreenUserManager(mContext); @@ -459,6 +468,17 @@ public class NotificationLockscreenUserManagerTest extends SysuiTestCase { } @Test + public void testHasSensitiveContent_redacted() { + // Allow private notifications for this user + mSettings.putIntForUser(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, 0, + mCurrentUser.id); + changeSetting(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS); + + // Sensitive Content notifications are always redacted + assertTrue(mLockscreenUserManager.needsRedaction(mSensitiveContentNotif)); + } + + @Test public void testUserSwitchedCallsOnUserSwitching() { mLockscreenUserManager.getUserTrackerCallbackForTest().onUserChanging(mSecondaryUser.id, mContext); diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModelTest.kt index 82e2bb719818..c35c165ba761 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModelTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModelTest.kt @@ -41,6 +41,8 @@ import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepos import com.android.systemui.keyguard.domain.interactor.keyguardInteractor import com.android.systemui.keyguard.shared.model.BurnInModel import com.android.systemui.keyguard.shared.model.KeyguardState +import com.android.systemui.keyguard.shared.model.KeyguardState.AOD +import com.android.systemui.keyguard.shared.model.KeyguardState.LOCKSCREEN import com.android.systemui.keyguard.shared.model.StatusBarState import com.android.systemui.keyguard.shared.model.TransitionState import com.android.systemui.keyguard.shared.model.TransitionStep @@ -53,6 +55,7 @@ import com.android.systemui.kosmos.testScope import com.android.systemui.res.R import com.android.systemui.shade.mockLargeScreenHeaderHelper import com.android.systemui.shade.shadeTestUtil +import com.android.systemui.statusbar.notification.NotificationUtils.interpolate import com.android.systemui.statusbar.notification.stack.domain.interactor.sharedNotificationContainerInteractor import com.android.systemui.testKosmos import com.android.systemui.util.mockito.any @@ -794,11 +797,47 @@ class SharedNotificationContainerViewModelTest(flags: FlagsParameterization) : S @DisableSceneContainer fun updateBounds_fromKeyguardRoot() = testScope.runTest { - val bounds by collectLastValue(underTest.bounds) + val startProgress = 0f + val startStep = TransitionStep(LOCKSCREEN, AOD, startProgress, TransitionState.STARTED) + val boundsChangingProgress = 0.2f + val boundsChangingStep = + TransitionStep(LOCKSCREEN, AOD, boundsChangingProgress, TransitionState.RUNNING) + val boundsInterpolatingProgress = 0.6f + val boundsInterpolatingStep = + TransitionStep( + LOCKSCREEN, + AOD, + boundsInterpolatingProgress, + TransitionState.RUNNING + ) + val finishProgress = 1.0f + val finishStep = + TransitionStep(LOCKSCREEN, AOD, finishProgress, TransitionState.FINISHED) + val bounds by collectLastValue(underTest.bounds) val top = 123f val bottom = 456f + + kosmos.fakeKeyguardTransitionRepository.sendTransitionStep(startStep) + runCurrent() + kosmos.fakeKeyguardTransitionRepository.sendTransitionStep(boundsChangingStep) + runCurrent() keyguardRootViewModel.onNotificationContainerBoundsChanged(top, bottom) + + kosmos.fakeKeyguardTransitionRepository.sendTransitionStep(boundsInterpolatingStep) + runCurrent() + val adjustedProgress = + (boundsInterpolatingProgress - boundsChangingProgress) / + (1 - boundsChangingProgress) + val interpolatedTop = interpolate(0f, top, adjustedProgress) + val interpolatedBottom = interpolate(0f, bottom, adjustedProgress) + assertThat(bounds) + .isEqualTo( + NotificationContainerBounds(top = interpolatedTop, bottom = interpolatedBottom) + ) + + kosmos.fakeKeyguardTransitionRepository.sendTransitionStep(finishStep) + runCurrent() assertThat(bounds).isEqualTo(NotificationContainerBounds(top = top, bottom = bottom)) } diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/ui/navigation/VolumeNavigatorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/ui/navigation/VolumeNavigatorTest.kt new file mode 100644 index 000000000000..7934b02126bd --- /dev/null +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/ui/navigation/VolumeNavigatorTest.kt @@ -0,0 +1,95 @@ +/* + * Copyright (C) 2024 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.volume.ui.navigation + +import androidx.test.ext.junit.runners.AndroidJUnit4 +import androidx.test.filters.SmallTest +import com.android.internal.logging.uiEventLoggerFake +import com.android.systemui.SysuiTestCase +import com.android.systemui.coroutines.collectLastValue +import com.android.systemui.kosmos.testDispatcher +import com.android.systemui.kosmos.testScope +import com.android.systemui.plugins.ActivityStarter +import com.android.systemui.plugins.activityStarter +import com.android.systemui.testKosmos +import com.android.systemui.volume.domain.model.VolumePanelRoute +import com.android.systemui.volume.panel.domain.interactor.volumePanelGlobalStateInteractor +import com.android.systemui.volume.panel.ui.viewmodel.volumePanelViewModelFactory +import com.google.common.truth.Truth.assertThat +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.test.runCurrent +import kotlinx.coroutines.test.runTest +import org.junit.Test +import org.junit.runner.RunWith +import org.mockito.ArgumentMatchers.anyBoolean +import org.mockito.ArgumentMatchers.anyInt +import org.mockito.kotlin.any +import org.mockito.kotlin.mock +import org.mockito.kotlin.whenever + +@OptIn(ExperimentalCoroutinesApi::class) +@SmallTest +@RunWith(AndroidJUnit4::class) +class VolumeNavigatorTest : SysuiTestCase() { + + private val kosmos = testKosmos() + + private val underTest: VolumeNavigator = + with(kosmos) { + VolumeNavigator( + testScope.backgroundScope, + testDispatcher, + mock {}, + activityStarter, + volumePanelViewModelFactory, + mock { + on { create(any(), anyInt(), anyBoolean(), any()) }.thenReturn(mock {}) + on { applicationContext }.thenReturn(context) + }, + uiEventLoggerFake, + volumePanelGlobalStateInteractor, + ) + } + + @Test + fun showNewVolumePanel_keyguardLocked_notShown() = + with(kosmos) { + testScope.runTest { + val panelState by collectLastValue(volumePanelGlobalStateInteractor.globalState) + + underTest.openVolumePanel(VolumePanelRoute.COMPOSE_VOLUME_PANEL) + runCurrent() + + assertThat(panelState!!.isVisible).isFalse() + } + } + + @Test + fun showNewVolumePanel_keyguardUnlocked_shown() = + with(kosmos) { + testScope.runTest { + whenever(activityStarter.dismissKeyguardThenExecute(any(), any(), anyBoolean())) + .then { (it.arguments[0] as ActivityStarter.OnDismissAction).onDismiss() } + val panelState by collectLastValue(volumePanelGlobalStateInteractor.globalState) + + underTest.openVolumePanel(VolumePanelRoute.COMPOSE_VOLUME_PANEL) + runCurrent() + + assertThat(panelState!!.isVisible).isTrue() + } + } +} diff --git a/packages/SystemUI/res/layout/activity_keyboard_shortcut_helper.xml b/packages/SystemUI/res/layout/activity_keyboard_shortcut_helper.xml index 292e49610e2a..06d1bf4c01cb 100644 --- a/packages/SystemUI/res/layout/activity_keyboard_shortcut_helper.xml +++ b/packages/SystemUI/res/layout/activity_keyboard_shortcut_helper.xml @@ -5,9 +5,9 @@ android:layout_width="match_parent" android:layout_height="match_parent"> - <LinearLayout + <FrameLayout android:id="@+id/shortcut_helper_sheet" - style="@style/Widget.Material3.BottomSheet" + style="@style/ShortcutHelperBottomSheet" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical" @@ -19,13 +19,9 @@ android:layout_width="match_parent" android:layout_height="wrap_content" /> - <TextView + <androidx.compose.ui.platform.ComposeView + android:id="@+id/shortcut_helper_compose_container" android:layout_width="match_parent" - android:layout_height="0dp" - android:layout_weight="1" - android:gravity="center" - android:textAppearance="?textAppearanceDisplayLarge" - android:background="?colorTertiaryContainer" - android:text="Shortcut Helper Content" /> - </LinearLayout> -</androidx.coordinatorlayout.widget.CoordinatorLayout> + android:layout_height="match_parent" /> + </FrameLayout> +</androidx.coordinatorlayout.widget.CoordinatorLayout>
\ No newline at end of file diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml index c038a8207d43..1226bbf21a0f 100644 --- a/packages/SystemUI/res/values/strings.xml +++ b/packages/SystemUI/res/values/strings.xml @@ -3489,6 +3489,45 @@ <!-- Label for recent app usage of a phone sensor with sub-attribution and proxy label in the privacy dialog [CHAR LIMIT=NONE] --> <string name="privacy_dialog_recent_app_usage_2">Recently used by <xliff:g id="app_name" example="Gmail">%1$s</xliff:g> (<xliff:g id="attribution_label" example="For Wallet">%2$s</xliff:g> \u2022 <xliff:g id="proxy_label" example="Speech services">%3$s</xliff:g>)</string> + <!-- Title of the keyboard shortcut helper category "System". The helper is a component that + shows the user which keyboard shortcuts they can use. The "System" shortcuts are for + example "Take a screenshot" or "Go back". [CHAR LIMIT=NONE] --> + <string name="shortcut_helper_category_system">System</string> + <!-- Title of the keyboard shortcut helper category "Multitasking". The helper is a component + that shows the user which keyboard shortcuts they can use. The "Multitasking" shortcuts are + for example "Enter split screen". [CHAR LIMIT=NONE] --> + <string name="shortcut_helper_category_multitasking">Multitasking</string> + <!-- Title of the keyboard shortcut helper category "Input". The helper is a component + that shows the user which keyboard shortcuts they can use. The "Input" shortcuts are + the ones provided by the keyboard. Examples are "Access emoji" or "Switch to next language" + [CHAR LIMIT=NONE] --> + <string name="shortcut_helper_category_input">Input</string> + <!-- Title of the keyboard shortcut helper category "App shortcuts". The helper is a component + that shows the user which keyboard shortcuts they can use. The "App shortcuts" are + for example "Open browser" or "Open calculator". [CHAR LIMIT=NONE] --> + <string name="shortcut_helper_category_app_shortcuts">App shortcuts</string> + <!-- Title of the keyboard shortcut helper category "Accessibility". The helper is a component + that shows the user which keyboard shortcuts they can use. The "Accessibility" shortcuts + are for example "Turn on talkback". [CHAR LIMIT=NONE] --> + <string name="shortcut_helper_category_a11y">Accessibility</string> + <!-- Title at the top of the keyboard shortcut helper UI. The helper is a component + that shows the user which keyboard shortcuts they can use. [CHAR LIMIT=NONE] --> + <string name="shortcut_helper_title">Keyboard shortcuts</string> + <!-- Placeholder text shown in the search box of the keyboard shortcut helper, when the user + hasn't typed in anything in the search box yet. The helper is a component that shows the + user which keyboard shortcuts they can use. [CHAR LIMIT=NONE] --> + <string name="shortcut_helper_search_placeholder">Search shortcuts</string> + <!-- Content description of the icon that allows to collapse a keyboard shortcut helper category + panel. The helper is a component that shows the user which keyboard shortcuts they can + use. The helper shows shortcuts in categories, which can be collapsed or expanded. + [CHAR LIMIT=NONE] --> + <string name="shortcut_helper_content_description_collapse_icon">Collapse icon</string> + <!-- Content description of the icon that allows to expand a keyboard shortcut helper category + panel. The helper is a component that shows the user which keyboard shortcuts they can + use. The helper shows shortcuts in categories, which can be collapsed or expanded. + [CHAR LIMIT=NONE] --> + <string name="shortcut_helper_content_description_expand_icon">Expand icon</string> + <!-- Content description for keyboard backlight brightness dialog [CHAR LIMIT=NONE] --> <string name="keyboard_backlight_dialog_title">Keyboard backlight</string> <!-- Content description for keyboard backlight brightness value [CHAR LIMIT=NONE] --> diff --git a/packages/SystemUI/res/values/styles.xml b/packages/SystemUI/res/values/styles.xml index 64717fcc8c5d..1e0adec4e84f 100644 --- a/packages/SystemUI/res/values/styles.xml +++ b/packages/SystemUI/res/values/styles.xml @@ -1665,6 +1665,10 @@ <item name="android:colorBackground">@color/transparent</item> </style> + <style name="ShortcutHelperBottomSheet" parent="@style/Widget.Material3.BottomSheet"> + <item name="backgroundTint">?colorSurfaceContainer</item> + </style> + <style name="ShortcutHelperAnimation" parent="@android:style/Animation.Activity"> <item name="android:activityOpenEnterAnimation">@anim/shortcut_helper_launch_anim</item> <item name="android:taskOpenEnterAnimation">@anim/shortcut_helper_launch_anim</item> diff --git a/packages/SystemUI/src/com/android/keyguard/CarrierTextManager.java b/packages/SystemUI/src/com/android/keyguard/CarrierTextManager.java index 3bf3fb3a1ebd..b116e297ccbf 100644 --- a/packages/SystemUI/src/com/android/keyguard/CarrierTextManager.java +++ b/packages/SystemUI/src/com/android/keyguard/CarrierTextManager.java @@ -308,7 +308,13 @@ public class CarrierTextManager { } else { // Don't listen and clear out the text when the device isn't a phone. mMainExecutor.execute(() -> callback.updateCarrierInfo( - new CarrierTextCallbackInfo("", null, false, null) + new CarrierTextCallbackInfo( + /* carrierText= */ "", + /* listOfCarriers= */ null, + /* anySimReady= */ false, + /* isInSatelliteMode= */ false, + /* subscriptionIds= */ null, + /* airplaneMode= */ false) )); } } else { @@ -448,10 +454,12 @@ public class CarrierTextManager { displayText = currentSatelliteText; } + boolean isInSatelliteMode = mSatelliteCarrierText != null; final CarrierTextCallbackInfo info = new CarrierTextCallbackInfo( displayText, carrierNames, !allSimsMissing, + isInSatelliteMode, subsIds, airplaneMode); mLogger.logCallbackSentFromUpdate(info); @@ -757,21 +765,35 @@ public class CarrierTextManager { public final CharSequence carrierText; public final CharSequence[] listOfCarriers; public final boolean anySimReady; + public final boolean isInSatelliteMode; public final int[] subscriptionIds; public boolean airplaneMode; @VisibleForTesting - public CarrierTextCallbackInfo(CharSequence carrierText, CharSequence[] listOfCarriers, - boolean anySimReady, int[] subscriptionIds) { - this(carrierText, listOfCarriers, anySimReady, subscriptionIds, false); + public CarrierTextCallbackInfo( + CharSequence carrierText, + CharSequence[] listOfCarriers, + boolean anySimReady, + int[] subscriptionIds) { + this(carrierText, + listOfCarriers, + anySimReady, + /* isInSatelliteMode= */ false, + subscriptionIds, + /* airplaneMode= */ false); } - @VisibleForTesting - public CarrierTextCallbackInfo(CharSequence carrierText, CharSequence[] listOfCarriers, - boolean anySimReady, int[] subscriptionIds, boolean airplaneMode) { + public CarrierTextCallbackInfo( + CharSequence carrierText, + CharSequence[] listOfCarriers, + boolean anySimReady, + boolean isInSatelliteMode, + int[] subscriptionIds, + boolean airplaneMode) { this.carrierText = carrierText; this.listOfCarriers = listOfCarriers; this.anySimReady = anySimReady; + this.isInSatelliteMode = isInSatelliteMode; this.subscriptionIds = subscriptionIds; this.airplaneMode = airplaneMode; } @@ -782,6 +804,7 @@ public class CarrierTextManager { + "carrierText=" + carrierText + ", listOfCarriers=" + Arrays.toString(listOfCarriers) + ", anySimReady=" + anySimReady + + ", isInSatelliteMode=" + isInSatelliteMode + ", subscriptionIds=" + Arrays.toString(subscriptionIds) + ", airplaneMode=" + airplaneMode + '}'; diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/FingerprintPropertyInteractor.kt b/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/FingerprintPropertyInteractor.kt index a74b0b07299c..b8ff3bb43203 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/FingerprintPropertyInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/FingerprintPropertyInteractor.kt @@ -98,11 +98,11 @@ constructor( ) { unscaledSensorLocation, scale -> val sensorLocation = SensorLocation( - unscaledSensorLocation.sensorLocationX, - unscaledSensorLocation.sensorLocationY, - unscaledSensorLocation.sensorRadius, + naturalCenterX = unscaledSensorLocation.sensorLocationX, + naturalCenterY = unscaledSensorLocation.sensorLocationY, + naturalRadius = unscaledSensorLocation.sensorRadius, + scale = scale ) - sensorLocation.scale = scale sensorLocation } diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/shared/model/SensorLocation.kt b/packages/SystemUI/src/com/android/systemui/biometrics/shared/model/SensorLocation.kt index dddadbd5e036..2f2f3a35dbaa 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/shared/model/SensorLocation.kt +++ b/packages/SystemUI/src/com/android/systemui/biometrics/shared/model/SensorLocation.kt @@ -16,18 +16,18 @@ package com.android.systemui.biometrics.shared.model -/** Provides current sensor location information in the current screen resolution [scale]. */ +/** + * Provides current sensor location information in the current screen resolution [scale]. + * + * @property scale Scale to apply to the sensor location's natural parameters to support different + * screen resolutions. + */ data class SensorLocation( private val naturalCenterX: Int, private val naturalCenterY: Int, - private val naturalRadius: Int + private val naturalRadius: Int, + private val scale: Float = 1f ) { - /** - * Scale to apply to the sensor location's natural parameters to support different screen - * resolutions. - */ - var scale: Float = 1f - val centerX: Float get() { return naturalCenterX * scale diff --git a/packages/SystemUI/src/com/android/systemui/communal/CommunalDreamStartable.kt b/packages/SystemUI/src/com/android/systemui/communal/CommunalDreamStartable.kt index 153b7aa3e522..8993a3be058b 100644 --- a/packages/SystemUI/src/com/android/systemui/communal/CommunalDreamStartable.kt +++ b/packages/SystemUI/src/com/android/systemui/communal/CommunalDreamStartable.kt @@ -19,6 +19,7 @@ package com.android.systemui.communal import android.annotation.SuppressLint import android.app.DreamManager import com.android.systemui.CoreStartable +import com.android.systemui.Flags.glanceableHubAllowKeyguardWhenDreaming import com.android.systemui.Flags.communalHub import com.android.systemui.Flags.restartDreamOnUnocclude import com.android.systemui.communal.domain.interactor.CommunalInteractor @@ -30,11 +31,11 @@ import com.android.systemui.keyguard.shared.model.KeyguardState import com.android.systemui.power.domain.interactor.PowerInteractor import com.android.systemui.util.kotlin.Utils.Companion.sample import com.android.systemui.util.kotlin.sample -import javax.inject.Inject import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.flow.filter import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.onEach +import javax.inject.Inject /** * A [CoreStartable] responsible for automatically starting the dream when the communal hub is @@ -78,6 +79,7 @@ constructor( if ( finishedState == KeyguardState.GLANCEABLE_HUB && !dreaming && + !glanceableHubAllowKeyguardWhenDreaming() && dreamManager.canStartDreaming(isAwake) ) { dreamManager.startDream() diff --git a/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/BaseCommunalViewModel.kt b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/BaseCommunalViewModel.kt index 3d9e8615fb18..8cd5603bdc7f 100644 --- a/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/BaseCommunalViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/BaseCommunalViewModel.kt @@ -91,6 +91,12 @@ abstract class BaseCommunalViewModel( /** A list of all the communal content to be displayed in the communal hub. */ abstract val communalContent: Flow<List<CommunalContentModel>> + /** + * Whether to freeze the emission of the communalContent flow to prevent recomposition. Defaults + * to false, indicating that the flow will emit new update. + */ + open val isCommunalContentFlowFrozen: Flow<Boolean> = flowOf(false) + /** Whether in edit mode for the communal hub. */ open val isEditMode = false diff --git a/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalViewModel.kt b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalViewModel.kt index 7f3a2dcb23dc..ce69ee880ce1 100644 --- a/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalViewModel.kt @@ -38,7 +38,9 @@ import com.android.systemui.media.controls.ui.view.MediaHostState import com.android.systemui.media.dagger.MediaModule import com.android.systemui.res.R import com.android.systemui.shade.domain.interactor.ShadeInteractor +import com.android.systemui.util.kotlin.BooleanFlowOperators.allOf import com.android.systemui.util.kotlin.BooleanFlowOperators.not +import com.android.systemui.utils.coroutines.flow.flatMapLatestConflated import javax.inject.Inject import javax.inject.Named import kotlinx.coroutines.CoroutineScope @@ -76,8 +78,11 @@ constructor( private val logger = Logger(logBuffer, "CommunalViewModel") + /** Communal content saved from the previous emission when the flow is active (not "frozen"). */ + private var frozenCommunalContent: List<CommunalContentModel>? = null + @OptIn(ExperimentalCoroutinesApi::class) - override val communalContent: Flow<List<CommunalContentModel>> = + private val latestCommunalContent: Flow<List<CommunalContentModel>> = tutorialInteractor.isTutorialAvailable .flatMapLatest { isTutorialMode -> if (isTutorialMode) { @@ -93,9 +98,40 @@ constructor( } } .onEach { models -> + frozenCommunalContent = models logger.d({ "Content updated: $str1" }) { str1 = models.joinToString { it.key } } } + /** + * Freeze the content flow, when an activity is about to show, like starting a timer via voice: + * 1) in handheld mode, use the keyguard occluded state; + * 2) in dreaming mode, where keyguard is already occluded by dream, use the dream wakeup + * signal. Since in this case the shell transition info does not include + * KEYGUARD_VISIBILITY_TRANSIT_FLAGS, KeyguardTransitionHandler will not run the + * occludeAnimation on KeyguardViewMediator. + */ + override val isCommunalContentFlowFrozen: Flow<Boolean> = + allOf( + keyguardTransitionInteractor.isFinishedInState(KeyguardState.GLANCEABLE_HUB), + keyguardInteractor.isKeyguardOccluded, + not(keyguardInteractor.isAbleToDream) + ) + .distinctUntilChanged() + .onEach { logger.d("isCommunalContentFlowFrozen: $it") } + + override val communalContent: Flow<List<CommunalContentModel>> = + isCommunalContentFlowFrozen + .flatMapLatestConflated { isFrozen -> + if (isFrozen) { + flowOf(frozenCommunalContent ?: emptyList()) + } else { + latestCommunalContent + } + } + .onEach { models -> + logger.d({ "CommunalContent: $str1" }) { str1 = models.joinToString { it.key } } + } + override val isEmptyState: Flow<Boolean> = communalInteractor.widgetContent .map { it.isEmpty() } diff --git a/packages/SystemUI/src/com/android/systemui/complication/OpenHubComplication.java b/packages/SystemUI/src/com/android/systemui/complication/OpenHubComplication.java index 3cf22b1b55e4..a679bfb15476 100644 --- a/packages/SystemUI/src/com/android/systemui/complication/OpenHubComplication.java +++ b/packages/SystemUI/src/com/android/systemui/complication/OpenHubComplication.java @@ -67,7 +67,7 @@ public class OpenHubComplication implements Complication { @Override public int getRequiredTypeAvailability() { // TODO(b/339667383): create a new complication type if we decide to productionize this - return COMPLICATION_TYPE_HOME_CONTROLS; + return COMPLICATION_TYPE_NONE; } /** diff --git a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayService.java b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayService.java index 96e708fca451..c6c5747973b3 100644 --- a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayService.java +++ b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayService.java @@ -18,6 +18,7 @@ package com.android.systemui.dreams; import static android.service.dreams.Flags.dreamWakeRedirect; +import static com.android.systemui.Flags.glanceableHubAllowKeyguardWhenDreaming; import static com.android.systemui.dreams.dagger.DreamModule.DREAM_OVERLAY_WINDOW_TITLE; import static com.android.systemui.dreams.dagger.DreamModule.DREAM_TOUCH_INSET_MANAGER; import static com.android.systemui.dreams.dagger.DreamModule.HOME_CONTROL_PANEL_DREAM_COMPONENT; @@ -395,7 +396,7 @@ public class DreamOverlayService extends android.service.dreams.DreamOverlayServ return; } - redirectWake(mCommunalAvailable); + redirectWake(mCommunalAvailable && !glanceableHubAllowKeyguardWhenDreaming()); } @Override diff --git a/packages/SystemUI/src/com/android/systemui/dreams/ui/viewmodel/DreamViewModel.kt b/packages/SystemUI/src/com/android/systemui/dreams/ui/viewmodel/DreamViewModel.kt index c5b3c5335fc8..4b07f78eb825 100644 --- a/packages/SystemUI/src/com/android/systemui/dreams/ui/viewmodel/DreamViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/dreams/ui/viewmodel/DreamViewModel.kt @@ -17,6 +17,7 @@ package com.android.systemui.dreams.ui.viewmodel import com.android.keyguard.KeyguardUpdateMonitor +import com.android.systemui.Flags.glanceableHubAllowKeyguardWhenDreaming import com.android.systemui.common.ui.domain.interactor.ConfigurationInteractor import com.android.systemui.communal.domain.interactor.CommunalInteractor import com.android.systemui.communal.shared.model.CommunalScenes @@ -60,7 +61,7 @@ constructor( val showGlanceableHub = communalInteractor.isCommunalEnabled.value && !keyguardUpdateMonitor.isEncryptedOrLockdown(userTracker.userId) - if (showGlanceableHub) { + if (showGlanceableHub && !glanceableHubAllowKeyguardWhenDreaming()) { communalInteractor.changeScene(CommunalScenes.Communal) } else { toLockscreenTransitionViewModel.startTransition() diff --git a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt index f4f8796ebffc..3d3584e29def 100644 --- a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt +++ b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt @@ -60,14 +60,6 @@ object Flags { "notification_drag_to_contents" ) - /** - * This flag controls whether we register a listener for StatsD notification memory reports. - * For statsd to actually call the listener however, a server-side toggle needs to be - * enabled as well. - */ - val NOTIFICATION_MEMORY_LOGGING_ENABLED = - releasedFlag("notification_memory_logging_enabled") - // TODO(b/280783617): Tracking Bug @Keep @JvmField @@ -386,9 +378,6 @@ object Flags { val WARN_ON_BLOCKING_BINDER_TRANSACTIONS = unreleasedFlag("warn_on_blocking_binder_transactions") - // TODO:(b/283203305): Tracking bug - @JvmField val TRIM_FONT_CACHES_AT_UNLOCK = unreleasedFlag("trim_font_caches_on_unlock") - // TODO(b/298380520): Tracking Bug @JvmField val USER_TRACKER_BACKGROUND_CALLBACKS = unreleasedFlag("user_tracker_background_callbacks") diff --git a/packages/SystemUI/src/com/android/systemui/haptics/qs/QSLongPressEffect.kt b/packages/SystemUI/src/com/android/systemui/haptics/qs/QSLongPressEffect.kt index ea8d7d778851..30b958393b60 100644 --- a/packages/SystemUI/src/com/android/systemui/haptics/qs/QSLongPressEffect.kt +++ b/packages/SystemUI/src/com/android/systemui/haptics/qs/QSLongPressEffect.kt @@ -19,7 +19,9 @@ package com.android.systemui.haptics.qs import android.os.VibrationEffect import android.view.View import androidx.annotation.VisibleForTesting +import com.android.systemui.animation.Expandable import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor +import com.android.systemui.plugins.qs.QSTile import com.android.systemui.statusbar.VibratorHelper import javax.inject.Inject import kotlinx.coroutines.flow.Flow @@ -51,6 +53,10 @@ constructor( var state = State.IDLE private set + /** The QSTile and Expandable used to perform a long-click and click actions */ + var qsTile: QSTile? = null + var expandable: Expandable? = null + /** Flow for view control and action */ private val _postedActionType = MutableStateFlow<ActionType?>(null) val actionType: Flow<ActionType?> = @@ -105,6 +111,7 @@ constructor( when (state) { State.IDLE -> { setState(State.TIMEOUT_WAIT) + _postedActionType.value = ActionType.WAIT_TAP_TIMEOUT } State.RUNNING_BACKWARDS -> _postedActionType.value = ActionType.CANCEL_ANIMATOR else -> {} @@ -112,16 +119,9 @@ constructor( } fun handleActionUp() { - when (state) { - State.TIMEOUT_WAIT -> { - _postedActionType.value = ActionType.CLICK - setState(State.IDLE) - } - State.RUNNING_FORWARD -> { - _postedActionType.value = ActionType.REVERSE_ANIMATOR - setState(State.RUNNING_BACKWARDS) - } - else -> {} + if (state == State.RUNNING_FORWARD) { + _postedActionType.value = ActionType.REVERSE_ANIMATOR + setState(State.RUNNING_BACKWARDS) } } @@ -129,6 +129,7 @@ constructor( when (state) { State.TIMEOUT_WAIT -> { setState(State.IDLE) + clearActionType() } State.RUNNING_FORWARD -> { _postedActionType.value = ActionType.REVERSE_ANIMATOR @@ -145,18 +146,23 @@ constructor( /** This function is called both when an animator completes or gets cancelled */ fun handleAnimationComplete() { - if (state == State.RUNNING_FORWARD) { - vibrate(snapEffect) - _postedActionType.value = ActionType.LONG_PRESS - } - if (state != State.TIMEOUT_WAIT) { - // This will happen if the animator did not finish by being cancelled - setState(State.IDLE) + when (state) { + State.RUNNING_FORWARD -> { + setState(State.IDLE) + vibrate(snapEffect) + _postedActionType.value = ActionType.LONG_PRESS + } + State.RUNNING_BACKWARDS -> { + setState(State.IDLE) + clearActionType() + } + else -> {} } } fun handleAnimationCancel() { setState(State.TIMEOUT_WAIT) + _postedActionType.value = ActionType.WAIT_TAP_TIMEOUT } fun handleTimeoutComplete() { @@ -190,9 +196,22 @@ constructor( effectDuration ) setState(State.IDLE) + clearActionType() return true } + fun onTileClick(): Boolean { + if (state == State.TIMEOUT_WAIT) { + setState(State.IDLE) + clearActionType() + qsTile?.let { + it.click(expandable) + return true + } + } + return false + } + enum class State { IDLE, /* The effect is idle waiting for touch input */ TIMEOUT_WAIT, /* The effect is waiting for a [PRESSED_TIMEOUT] period */ @@ -202,7 +221,7 @@ constructor( /* A type of action to perform on the view depending on the effect's state and logic */ enum class ActionType { - CLICK, + WAIT_TAP_TIMEOUT, LONG_PRESS, RESET_AND_LONG_PRESS, START_ANIMATOR, diff --git a/packages/SystemUI/src/com/android/systemui/haptics/qs/QSLongPressEffectViewBinder.kt b/packages/SystemUI/src/com/android/systemui/haptics/qs/QSLongPressEffectViewBinder.kt index 4875f481cce6..92a55ef0e74f 100644 --- a/packages/SystemUI/src/com/android/systemui/haptics/qs/QSLongPressEffectViewBinder.kt +++ b/packages/SystemUI/src/com/android/systemui/haptics/qs/QSLongPressEffectViewBinder.kt @@ -17,8 +17,6 @@ package com.android.systemui.haptics.qs import android.animation.ValueAnimator -import android.annotation.SuppressLint -import android.view.MotionEvent import android.view.ViewConfiguration import android.view.animation.AccelerateDecelerateInterpolator import androidx.core.animation.doOnCancel @@ -30,6 +28,7 @@ import com.android.app.tracing.coroutines.launch import com.android.systemui.lifecycle.repeatWhenAttached import com.android.systemui.qs.tileimpl.QSTileViewImpl import kotlinx.coroutines.DisposableHandle +import kotlinx.coroutines.delay import kotlinx.coroutines.flow.filterNotNull object QSLongPressEffectViewBinder { @@ -41,9 +40,6 @@ object QSLongPressEffectViewBinder { ): DisposableHandle? { if (qsLongPressEffect == null) return null - // Set the touch listener as the long-press effect - setTouchListener(tile, qsLongPressEffect) - return tile.repeatWhenAttached { repeatOnLifecycle(Lifecycle.State.CREATED) { // Action to perform @@ -52,18 +48,18 @@ object QSLongPressEffectViewBinder { qsLongPressEffect.actionType.filterNotNull().collect { action -> when (action) { - QSLongPressEffect.ActionType.CLICK -> { - tile.performClick() - qsLongPressEffect.clearActionType() + QSLongPressEffect.ActionType.WAIT_TAP_TIMEOUT -> { + delay(ViewConfiguration.getTapTimeout().toLong()) + qsLongPressEffect.handleTimeoutComplete() } QSLongPressEffect.ActionType.LONG_PRESS -> { tile.prepareForLaunch() - tile.performLongClick() + qsLongPressEffect.qsTile?.longClick(qsLongPressEffect.expandable) qsLongPressEffect.clearActionType() } QSLongPressEffect.ActionType.RESET_AND_LONG_PRESS -> { tile.resetLongPressEffectProperties() - tile.performLongClick() + qsLongPressEffect.qsTile?.longClick(qsLongPressEffect.expandable) qsLongPressEffect.clearActionType() } QSLongPressEffect.ActionType.START_ANIMATOR -> { @@ -106,22 +102,4 @@ object QSLongPressEffectViewBinder { } } } - - @SuppressLint("ClickableViewAccessibility") - private fun setTouchListener(tile: QSTileViewImpl, longPressEffect: QSLongPressEffect?) { - tile.setOnTouchListener { _, event -> - when (event.actionMasked) { - MotionEvent.ACTION_DOWN -> { - tile.postDelayed( - { longPressEffect?.handleTimeoutComplete() }, - ViewConfiguration.getTapTimeout().toLong(), - ) - longPressEffect?.handleActionDown() - } - MotionEvent.ACTION_UP -> longPressEffect?.handleActionUp() - MotionEvent.ACTION_CANCEL -> longPressEffect?.handleActionCancel() - } - true - } - } } diff --git a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/composable/ShortcutHelper.kt b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/composable/ShortcutHelper.kt new file mode 100644 index 000000000000..52ccc219353e --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/composable/ShortcutHelper.kt @@ -0,0 +1,413 @@ +/* + * Copyright (C) 2024 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.keyboard.shortcut.ui.composable + +import androidx.annotation.StringRes +import androidx.compose.animation.AnimatedVisibility +import androidx.compose.animation.core.animateFloatAsState +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.heightIn +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.layout.width +import androidx.compose.foundation.rememberScrollState +import androidx.compose.foundation.shape.CircleShape +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.foundation.verticalScroll +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.automirrored.filled.OpenInNew +import androidx.compose.material.icons.filled.Accessibility +import androidx.compose.material.icons.filled.Apps +import androidx.compose.material.icons.filled.ExpandMore +import androidx.compose.material.icons.filled.Keyboard +import androidx.compose.material.icons.filled.Search +import androidx.compose.material.icons.filled.Tv +import androidx.compose.material.icons.filled.VerticalSplit +import androidx.compose.material3.CenterAlignedTopAppBar +import androidx.compose.material3.ExperimentalMaterial3Api +import androidx.compose.material3.Icon +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.NavigationDrawerItemColors +import androidx.compose.material3.NavigationDrawerItemDefaults +import androidx.compose.material3.SearchBar +import androidx.compose.material3.SearchBarDefaults +import androidx.compose.material3.Surface +import androidx.compose.material3.Text +import androidx.compose.material3.TopAppBarDefaults +import androidx.compose.material3.windowsizeclass.WindowWidthSizeClass +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.RectangleShape +import androidx.compose.ui.graphics.Shape +import androidx.compose.ui.graphics.graphicsLayer +import androidx.compose.ui.graphics.vector.ImageVector +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.semantics.Role +import androidx.compose.ui.semantics.role +import androidx.compose.ui.semantics.semantics +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp +import androidx.compose.ui.util.fastForEach +import androidx.compose.ui.util.fastForEachIndexed +import com.android.compose.windowsizeclass.LocalWindowSizeClass +import com.android.systemui.res.R + +@Composable +fun ShortcutHelper(modifier: Modifier = Modifier, onKeyboardSettingsClicked: () -> Unit) { + if (shouldUseSinglePane()) { + ShortcutHelperSinglePane(modifier, categories, onKeyboardSettingsClicked) + } else { + ShortcutHelperTwoPane(modifier, categories, onKeyboardSettingsClicked) + } +} + +@Composable +private fun shouldUseSinglePane() = + LocalWindowSizeClass.current.widthSizeClass == WindowWidthSizeClass.Compact + +@Composable +private fun ShortcutHelperSinglePane( + modifier: Modifier = Modifier, + categories: List<ShortcutHelperCategory>, + onKeyboardSettingsClicked: () -> Unit, +) { + Column( + modifier = + modifier + .fillMaxSize() + .verticalScroll(rememberScrollState()) + .padding(start = 16.dp, end = 16.dp, top = 26.dp) + ) { + TitleBar() + Spacer(modifier = Modifier.height(6.dp)) + ShortcutsSearchBar() + Spacer(modifier = Modifier.height(16.dp)) + CategoriesPanelSinglePane(categories) + Spacer(modifier = Modifier.weight(1f)) + KeyboardSettings(onClick = onKeyboardSettingsClicked) + } +} + +@Composable +private fun CategoriesPanelSinglePane( + categories: List<ShortcutHelperCategory>, +) { + var expandedCategory by remember { mutableStateOf<ShortcutHelperCategory?>(null) } + Column(verticalArrangement = Arrangement.spacedBy(2.dp)) { + categories.fastForEachIndexed { index, category -> + val isExpanded = expandedCategory == category + val itemShape = + if (index == 0) { + ShortcutHelper.Shapes.singlePaneFirstCategory + } else if (index == categories.lastIndex) { + ShortcutHelper.Shapes.singlePaneLastCategory + } else { + ShortcutHelper.Shapes.singlePaneCategory + } + CategoryItemSinglePane( + category = category, + isExpanded = isExpanded, + onClick = { + expandedCategory = + if (isExpanded) { + null + } else { + category + } + }, + shape = itemShape, + ) + } + } +} + +@Composable +private fun CategoryItemSinglePane( + category: ShortcutHelperCategory, + isExpanded: Boolean, + onClick: () -> Unit, + shape: Shape, +) { + Surface( + color = MaterialTheme.colorScheme.surfaceBright, + shape = shape, + onClick = onClick, + ) { + Column { + Row( + verticalAlignment = Alignment.CenterVertically, + modifier = Modifier.fillMaxWidth().heightIn(min = 88.dp).padding(horizontal = 16.dp) + ) { + Icon(category.icon, contentDescription = null) + Spacer(modifier = Modifier.width(16.dp)) + Text(stringResource(category.labelResId)) + Spacer(modifier = Modifier.weight(1f)) + RotatingExpandCollapseIcon(isExpanded) + } + AnimatedVisibility(visible = isExpanded) { ShortcutCategoryDetailsSinglePane(category) } + } + } +} + +@Composable +private fun RotatingExpandCollapseIcon(isExpanded: Boolean) { + val expandIconRotationDegrees by + animateFloatAsState( + targetValue = + if (isExpanded) { + 180f + } else { + 0f + }, + label = "Expand icon rotation animation" + ) + Icon( + modifier = + Modifier.background( + color = MaterialTheme.colorScheme.surfaceContainerHigh, + shape = CircleShape + ) + .graphicsLayer { rotationZ = expandIconRotationDegrees }, + imageVector = Icons.Default.ExpandMore, + contentDescription = + if (isExpanded) { + stringResource(R.string.shortcut_helper_content_description_collapse_icon) + } else { + stringResource(R.string.shortcut_helper_content_description_expand_icon) + }, + tint = MaterialTheme.colorScheme.onSurface + ) +} + +@Composable +private fun ShortcutCategoryDetailsSinglePane(category: ShortcutHelperCategory) { + Box(modifier = Modifier.fillMaxWidth().heightIn(min = 300.dp)) { + Text( + modifier = Modifier.align(Alignment.Center), + text = stringResource(category.labelResId), + ) + } +} + +@Composable +private fun ShortcutHelperTwoPane( + modifier: Modifier = Modifier, + categories: List<ShortcutHelperCategory>, + onKeyboardSettingsClicked: () -> Unit, +) { + Column(modifier = modifier.fillMaxSize().padding(start = 24.dp, end = 24.dp, top = 26.dp)) { + TitleBar() + Spacer(modifier = Modifier.height(12.dp)) + Row(Modifier.fillMaxWidth()) { + StartSidePanel( + modifier = Modifier.fillMaxWidth(fraction = 0.32f), + categories = categories, + onKeyboardSettingsClicked = onKeyboardSettingsClicked, + ) + Spacer(modifier = Modifier.width(24.dp)) + EndSidePanel(Modifier.fillMaxSize()) + } + } +} + +@Composable +private fun StartSidePanel( + modifier: Modifier, + categories: List<ShortcutHelperCategory>, + onKeyboardSettingsClicked: () -> Unit, +) { + Column(modifier) { + ShortcutsSearchBar() + Spacer(modifier = Modifier.heightIn(16.dp)) + CategoriesPanelTwoPane(categories) + Spacer(modifier = Modifier.weight(1f)) + KeyboardSettings(onKeyboardSettingsClicked) + } +} + +@Composable +private fun CategoriesPanelTwoPane(categories: List<ShortcutHelperCategory>) { + var selected by remember { mutableStateOf(categories.first()) } + Column { + categories.fastForEach { + CategoryItemTwoPane( + label = stringResource(it.labelResId), + icon = it.icon, + selected = selected == it, + onClick = { selected = it } + ) + } + } +} + +@Composable +private fun CategoryItemTwoPane( + label: String, + icon: ImageVector, + selected: Boolean, + onClick: () -> Unit, + colors: NavigationDrawerItemColors = + NavigationDrawerItemDefaults.colors(unselectedContainerColor = Color.Transparent), +) { + Surface( + selected = selected, + onClick = onClick, + modifier = Modifier.semantics { role = Role.Tab }.heightIn(min = 72.dp).fillMaxWidth(), + shape = RoundedCornerShape(28.dp), + color = colors.containerColor(selected).value, + ) { + Row(Modifier.padding(horizontal = 24.dp), verticalAlignment = Alignment.CenterVertically) { + Icon( + modifier = Modifier.size(24.dp), + imageVector = icon, + contentDescription = null, + tint = colors.iconColor(selected).value + ) + Spacer(Modifier.width(12.dp)) + Box(Modifier.weight(1f)) { + Text( + fontSize = 18.sp, + color = colors.textColor(selected).value, + style = MaterialTheme.typography.headlineSmall, + text = label + ) + } + } + } +} + +@Composable +fun EndSidePanel(modifier: Modifier) { + Surface( + modifier = modifier, + shape = RoundedCornerShape(28.dp), + color = MaterialTheme.colorScheme.surfaceBright + ) {} +} + +@Composable +@OptIn(ExperimentalMaterial3Api::class) +private fun TitleBar() { + CenterAlignedTopAppBar( + colors = TopAppBarDefaults.centerAlignedTopAppBarColors(containerColor = Color.Transparent), + title = { + Text( + text = stringResource(R.string.shortcut_helper_title), + color = MaterialTheme.colorScheme.onSurface, + style = MaterialTheme.typography.headlineSmall + ) + } + ) +} + +@Composable +@OptIn(ExperimentalMaterial3Api::class) +private fun ShortcutsSearchBar() { + var query by remember { mutableStateOf("") } + SearchBar( + colors = SearchBarDefaults.colors(containerColor = MaterialTheme.colorScheme.surfaceBright), + query = query, + active = false, + onActiveChange = {}, + onQueryChange = { query = it }, + onSearch = {}, + leadingIcon = { Icon(Icons.Default.Search, contentDescription = null) }, + placeholder = { Text(text = stringResource(R.string.shortcut_helper_search_placeholder)) }, + content = {} + ) +} + +@Composable +private fun KeyboardSettings(onClick: () -> Unit) { + Surface( + onClick = onClick, + shape = RoundedCornerShape(24.dp), + color = Color.Transparent, + modifier = Modifier.semantics { role = Role.Button }.fillMaxWidth() + ) { + Row( + modifier = Modifier.padding(horizontal = 24.dp, vertical = 16.dp), + verticalAlignment = Alignment.CenterVertically + ) { + Text( + "Keyboard Settings", + color = MaterialTheme.colorScheme.onSurfaceVariant, + fontSize = 16.sp + ) + Spacer(modifier = Modifier.width(8.dp)) + Icon( + imageVector = Icons.AutoMirrored.Default.OpenInNew, + contentDescription = null, + tint = MaterialTheme.colorScheme.onSurfaceVariant + ) + } + } +} + +/** Temporary data class just to populate the UI. */ +private data class ShortcutHelperCategory( + @StringRes val labelResId: Int, + val icon: ImageVector, +) + +// Temporarily populating the categories directly in the UI. +private val categories = + listOf( + ShortcutHelperCategory(R.string.shortcut_helper_category_system, Icons.Default.Tv), + ShortcutHelperCategory( + R.string.shortcut_helper_category_multitasking, + Icons.Default.VerticalSplit + ), + ShortcutHelperCategory(R.string.shortcut_helper_category_input, Icons.Default.Keyboard), + ShortcutHelperCategory(R.string.shortcut_helper_category_app_shortcuts, Icons.Default.Apps), + ShortcutHelperCategory(R.string.shortcut_helper_category_a11y, Icons.Default.Accessibility), + ) + +object ShortcutHelper { + + object Shapes { + val singlePaneFirstCategory = + RoundedCornerShape( + topStart = Dimensions.SinglePaneCategoryCornerRadius, + topEnd = Dimensions.SinglePaneCategoryCornerRadius + ) + val singlePaneLastCategory = + RoundedCornerShape( + bottomStart = Dimensions.SinglePaneCategoryCornerRadius, + bottomEnd = Dimensions.SinglePaneCategoryCornerRadius + ) + val singlePaneCategory = RectangleShape + } + + object Dimensions { + val SinglePaneCategoryCornerRadius = 28.dp + } +} diff --git a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/view/ShortcutHelperActivity.kt b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/view/ShortcutHelperActivity.kt index ef4156da4f7b..1e8d23918964 100644 --- a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/view/ShortcutHelperActivity.kt +++ b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/view/ShortcutHelperActivity.kt @@ -23,9 +23,12 @@ import android.view.WindowInsets import androidx.activity.BackEventCompat import androidx.activity.ComponentActivity import androidx.activity.OnBackPressedCallback +import androidx.compose.ui.platform.ComposeView import androidx.core.view.updatePadding import androidx.lifecycle.flowWithLifecycle import androidx.lifecycle.lifecycleScope +import com.android.compose.theme.PlatformTheme +import com.android.systemui.keyboard.shortcut.ui.composable.ShortcutHelper import com.android.systemui.keyboard.shortcut.ui.viewmodel.ShortcutHelperViewModel import com.android.systemui.res.R import com.google.android.material.bottomsheet.BottomSheetBehavior @@ -58,14 +61,30 @@ constructor( super.onCreate(savedInstanceState) setContentView(R.layout.activity_keyboard_shortcut_helper) setUpBottomSheetWidth() + expandBottomSheet() setUpInsets() setUpPredictiveBack() setUpSheetDismissListener() setUpDismissOnTouchOutside() + setUpComposeView() observeFinishRequired() viewModel.onViewOpened() } + private fun setUpComposeView() { + requireViewById<ComposeView>(R.id.shortcut_helper_compose_container).apply { + setContent { + PlatformTheme { + ShortcutHelper( + onKeyboardSettingsClicked = ::onKeyboardSettingsClicked, + ) + } + } + } + } + + private fun onKeyboardSettingsClicked() {} + override fun onDestroy() { super.onDestroy() if (isFinishing) { @@ -101,7 +120,8 @@ constructor( bottomSheetContainer.setOnApplyWindowInsetsListener { _, insets -> val safeDrawingInsets = insets.safeDrawing // Make sure the bottom sheet is not covered by the status bar. - bottomSheetContainer.updatePadding(top = safeDrawingInsets.top) + bottomSheetBehavior.maxHeight = + resources.displayMetrics.heightPixels - safeDrawingInsets.top // Make sure the contents inside of the bottom sheet are not hidden by system bars, or // cutouts. bottomSheet.updatePadding( @@ -171,7 +191,6 @@ constructor( private val WindowInsets.safeDrawing get() = getInsets(WindowInsets.Type.systemBars()) - .union(getInsets(WindowInsets.Type.ime())) .union(getInsets(WindowInsets.Type.displayCutout())) private fun Insets.union(insets: Insets): Insets = Insets.max(this, insets) diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java index 81c2d92d29e8..f3a1843b6c31 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java +++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java @@ -2380,7 +2380,10 @@ public class KeyguardViewMediator implements CoreStartable, Dumpable, } mCustomMessage = message; mKeyguardViewControllerLazy.get().dismissAndCollapse(); - } else if (callback != null) { + return; + } + Log.w(TAG, "Ignoring request to DISMISS because mShowing=false"); + if (callback != null) { new DismissCallbackWrapper(callback).notifyDismissError(); } } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ResourceTrimmer.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ResourceTrimmer.kt index 3cbcb2cb4a0b..97ea16d7f8c8 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ResourceTrimmer.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ResourceTrimmer.kt @@ -18,21 +18,15 @@ package com.android.systemui.keyguard import android.annotation.WorkerThread import android.content.ComponentCallbacks2 -import android.graphics.HardwareRenderer -import android.os.Trace import android.util.Log import com.android.systemui.CoreStartable import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Application import com.android.systemui.dagger.qualifiers.Background -import com.android.systemui.flags.FeatureFlags -import com.android.systemui.flags.Flags -import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor import com.android.systemui.keyguard.shared.model.Edge import com.android.systemui.keyguard.shared.model.KeyguardState.GONE import com.android.systemui.keyguard.shared.model.TransitionState -import com.android.systemui.power.domain.interactor.PowerInteractor import com.android.systemui.scene.domain.interactor.SceneInteractor import com.android.systemui.scene.shared.flag.SceneContainerFlag import com.android.systemui.scene.shared.model.Scenes @@ -40,10 +34,7 @@ import com.android.systemui.utils.GlobalWindowManager import javax.inject.Inject import kotlinx.coroutines.CoroutineDispatcher import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.flow.combine -import kotlinx.coroutines.flow.distinctUntilChanged import kotlinx.coroutines.flow.filter -import kotlinx.coroutines.flow.map import kotlinx.coroutines.launch /** @@ -57,37 +48,15 @@ import kotlinx.coroutines.launch class ResourceTrimmer @Inject constructor( - private val keyguardInteractor: KeyguardInteractor, - private val powerInteractor: PowerInteractor, private val keyguardTransitionInteractor: KeyguardTransitionInteractor, private val globalWindowManager: GlobalWindowManager, @Application private val applicationScope: CoroutineScope, @Background private val bgDispatcher: CoroutineDispatcher, - private val featureFlags: FeatureFlags, private val sceneInteractor: SceneInteractor, -) : CoreStartable, WakefulnessLifecycle.Observer { +) : CoreStartable { override fun start() { Log.d(LOG_TAG, "Resource trimmer registered.") - if (com.android.systemui.Flags.trimResourcesWithBackgroundTrimAtLock()) { - applicationScope.launch(bgDispatcher) { - // We need to wait for the AoD transition (and animation) to complete. - // This means we're waiting for isDreaming (== implies isDoze) and dozeAmount == 1f - // signal. This is to make sure we don't clear font caches during animation which - // would jank and leave stale data in memory. - val isDozingFully = - keyguardInteractor.dozeAmount.map { it == 1f }.distinctUntilChanged() - combine( - powerInteractor.isAsleep, - keyguardInteractor.isDreaming, - isDozingFully, - ::Triple - ) - .distinctUntilChanged() - .collect { onWakefulnessUpdated(it.first, it.second, it.third) } - } - } - applicationScope.launch(bgDispatcher) { // We drop 1 to avoid triggering on initial collect(). if (SceneContainerFlag.isEnabled) { @@ -110,47 +79,9 @@ constructor( // lockscreen elements, especially clocks. Log.d(LOG_TAG, "Sending TRIM_MEMORY_UI_HIDDEN.") globalWindowManager.trimMemory(ComponentCallbacks2.TRIM_MEMORY_UI_HIDDEN) - if (featureFlags.isEnabled(Flags.TRIM_FONT_CACHES_AT_UNLOCK)) { - if (DEBUG) { - Log.d(LOG_TAG, "Trimming font caches since keyguard went away.") - } - globalWindowManager.trimCaches(HardwareRenderer.CACHE_TRIM_FONT) - } - } - - @WorkerThread - private fun onWakefulnessUpdated( - isAsleep: Boolean, - isDreaming: Boolean, - isDozingFully: Boolean - ) { - if (!com.android.systemui.Flags.trimResourcesWithBackgroundTrimAtLock()) { - return - } - - if (DEBUG) { - Log.d(LOG_TAG, "isAsleep: $isAsleep Dreaming: $isDreaming DozeAmount: $isDozingFully") - } - // There are three scenarios: - // * No dozing and no AoD at all - where we go directly to ASLEEP with isDreaming = false - // and dozeAmount == 0f - // * Dozing without Aod - where we go to ASLEEP with isDreaming = true and dozeAmount jumps - // to 1f - // * AoD - where we go to ASLEEP with iDreaming = true and dozeAmount slowly increases - // to 1f - val dozeDisabledAndScreenOff = isAsleep && !isDreaming - val dozeEnabledAndDozeAnimationCompleted = isAsleep && isDreaming && isDozingFully - if (dozeDisabledAndScreenOff || dozeEnabledAndDozeAnimationCompleted) { - Trace.beginSection("ResourceTrimmer#trimMemory") - Log.d(LOG_TAG, "SysUI asleep, trimming memory.") - globalWindowManager.trimMemory(ComponentCallbacks2.TRIM_MEMORY_UI_HIDDEN) - globalWindowManager.trimCaches(HardwareRenderer.CACHE_TRIM_ALL) - Trace.endSection() - } } companion object { private const val LOG_TAG = "ResourceTrimmer" - private val DEBUG = Log.isLoggable(LOG_TAG, Log.DEBUG) } } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt index c44a40f33857..b142b4469e40 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt @@ -37,6 +37,7 @@ import com.android.systemui.keyguard.shared.model.CameraLaunchSourceModel import com.android.systemui.keyguard.shared.model.DozeStateModel import com.android.systemui.keyguard.shared.model.DozeStateModel.Companion.isDozeOff import com.android.systemui.keyguard.shared.model.DozeTransitionModel +import com.android.systemui.keyguard.shared.model.KeyguardState.AOD import com.android.systemui.keyguard.shared.model.KeyguardState.GONE import com.android.systemui.keyguard.shared.model.KeyguardState.LOCKSCREEN import com.android.systemui.keyguard.shared.model.StatusBarState @@ -47,8 +48,10 @@ import com.android.systemui.scene.shared.flag.SceneContainerFlag import com.android.systemui.scene.shared.model.Scenes import com.android.systemui.shade.data.repository.ShadeRepository import com.android.systemui.statusbar.CommandQueue +import com.android.systemui.statusbar.notification.NotificationUtils.interpolate import com.android.systemui.statusbar.notification.stack.domain.interactor.SharedNotificationContainerInteractor import com.android.systemui.util.kotlin.Utils.Companion.sample as sampleCombine +import com.android.systemui.util.kotlin.pairwise import com.android.systemui.util.kotlin.sample import javax.inject.Inject import javax.inject.Provider @@ -66,7 +69,9 @@ import kotlinx.coroutines.flow.combineTransform import kotlinx.coroutines.flow.debounce import kotlinx.coroutines.flow.distinctUntilChanged import kotlinx.coroutines.flow.filter +import kotlinx.coroutines.flow.first import kotlinx.coroutines.flow.flatMapLatest +import kotlinx.coroutines.flow.flow import kotlinx.coroutines.flow.flowOf import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.merge @@ -96,17 +101,54 @@ constructor( // TODO(b/296118689): move to a repository private val _notificationPlaceholderBounds = MutableStateFlow(NotificationContainerBounds()) + // When going to AOD, we interpolate bounds when receiving the new bounds + // When going back to LS, we'll apply new bounds directly + private val _nonSplitShadeNotifciationPlaceholderBounds = + _notificationPlaceholderBounds.pairwise().flatMapLatest { (oldBounds, newBounds) -> + val lastChangeStep = keyguardTransitionInteractor.transitionState.first() + if (lastChangeStep.to == AOD) { + keyguardTransitionInteractor.transitionState.map { step -> + val startingProgress = lastChangeStep.value + val progress = step.value + if (step.to == AOD && progress >= startingProgress) { + val adjustedProgress = + ((progress - startingProgress) / (1F - startingProgress)).coerceIn( + 0F, + 1F + ) + val top = interpolate(oldBounds.top, newBounds.top, adjustedProgress) + val bottom = + interpolate( + oldBounds.bottom, + newBounds.bottom, + adjustedProgress.coerceIn(0F, 1F) + ) + NotificationContainerBounds(top = top, bottom = bottom) + } else { + newBounds + } + } + } else { + flow { emit(newBounds) } + } + } + /** Bounds of the notification container. */ val notificationContainerBounds: StateFlow<NotificationContainerBounds> by lazy { SceneContainerFlag.assertInLegacyMode() combine( _notificationPlaceholderBounds, + _nonSplitShadeNotifciationPlaceholderBounds, sharedNotificationContainerInteractor.get().configurationBasedDimensions, - ) { bounds, cfg -> + ) { bounds, nonSplitShadeBounds: NotificationContainerBounds, cfg -> // We offset the placeholder bounds by the configured top margin to account for // legacy placement behavior within notifications for splitshade. - if (MigrateClocksToBlueprint.isEnabled && cfg.useSplitShade) { - bounds.copy(bottom = bounds.bottom - cfg.keyguardSplitShadeTopMargin) + if (MigrateClocksToBlueprint.isEnabled) { + if (cfg.useSplitShade) { + bounds.copy(bottom = bounds.bottom - cfg.keyguardSplitShadeTopMargin) + } else { + nonSplitShadeBounds + } } else bounds } .stateIn( diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultDeviceEntrySection.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultDeviceEntrySection.kt index c5fab8f57822..e01f0a152b37 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultDeviceEntrySection.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultDeviceEntrySection.kt @@ -147,8 +147,9 @@ constructor( deviceEntryIconViewModel.get().udfpsLocation.value?.let { udfpsLocation -> Log.d( "DeviceEntrySection", - "udfpsLocation=$udfpsLocation" + - " unusedAuthController=${authController.udfpsLocation}" + "udfpsLocation=$udfpsLocation, " + + "scaledLocation=(${udfpsLocation.centerX},${udfpsLocation.centerY}), " + + "unusedAuthController=${authController.udfpsLocation}" ) centerIcon( Point(udfpsLocation.centerX.toInt(), udfpsLocation.centerY.toInt()), diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardClockViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardClockViewModel.kt index 198e9f26384a..940f42377b42 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardClockViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardClockViewModel.kt @@ -65,7 +65,7 @@ constructor( } .stateIn( scope = applicationScope, - started = SharingStarted.WhileSubscribed(), + started = SharingStarted.Eagerly, initialValue = ClockSize.LARGE, ) @@ -74,7 +74,7 @@ constructor( .map { it == ClockSize.LARGE } .stateIn( scope = applicationScope, - started = SharingStarted.WhileSubscribed(), + started = SharingStarted.Eagerly, initialValue = true, ) diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaCarouselController.kt b/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaCarouselController.kt index 8316b3aba73e..7bacdeb99808 100644 --- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaCarouselController.kt +++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaCarouselController.kt @@ -42,6 +42,7 @@ import com.android.keyguard.KeyguardUpdateMonitor import com.android.keyguard.KeyguardUpdateMonitorCallback import com.android.systemui.Dumpable import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.dagger.qualifiers.Application import com.android.systemui.dagger.qualifiers.Background import com.android.systemui.dagger.qualifiers.Main import com.android.systemui.dump.DumpManager @@ -123,6 +124,7 @@ private val DEBUG = Log.isLoggable(TAG, Log.DEBUG) class MediaCarouselController @Inject constructor( + @Application applicationScope: CoroutineScope, private val context: Context, private val mediaControlPanelFactory: Provider<MediaControlPanel>, private val visualStabilityProvider: VisualStabilityProvider, @@ -387,12 +389,12 @@ constructor( repeatOnLifecycle(Lifecycle.State.STARTED) { listenForAnyStateToGoneKeyguardTransition(this) listenForAnyStateToLockscreenTransition(this) - listenForLockscreenSettingChanges(this) if (!mediaFlags.isSceneContainerEnabled()) return@repeatOnLifecycle listenForMediaItemsChanges(this) } } + listenForLockscreenSettingChanges(applicationScope) // Notifies all active players about animation scale changes. globalSettings.registerContentObserver( diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/BackPanelController.kt b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/BackPanelController.kt index b208434c3218..18358a79cbca 100644 --- a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/BackPanelController.kt +++ b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/BackPanelController.kt @@ -36,11 +36,12 @@ import androidx.dynamicanimation.animation.DynamicAnimation import com.android.internal.jank.Cuj import com.android.internal.jank.InteractionJankMonitor import com.android.internal.util.LatencyTracker -import com.android.systemui.dagger.qualifiers.Main import com.android.systemui.plugins.NavigationEdgeBackPlugin import com.android.systemui.statusbar.VibratorHelper import com.android.systemui.statusbar.policy.ConfigurationController import com.android.systemui.util.ViewController +import com.android.systemui.util.concurrency.BackPanelUiThread +import com.android.systemui.util.concurrency.UiThreadContext import com.android.systemui.util.time.SystemClock import java.io.PrintWriter import javax.inject.Inject @@ -84,11 +85,11 @@ internal constructor( context: Context, private val windowManager: WindowManager, private val viewConfiguration: ViewConfiguration, - @Main private val mainHandler: Handler, + private val mainHandler: Handler, private val systemClock: SystemClock, private val vibratorHelper: VibratorHelper, private val configurationController: ConfigurationController, - private val latencyTracker: LatencyTracker, + latencyTracker: LatencyTracker, private val interactionJankMonitor: InteractionJankMonitor, ) : ViewController<BackPanel>(BackPanel(context, latencyTracker)), NavigationEdgeBackPlugin { @@ -103,7 +104,7 @@ internal constructor( constructor( private val windowManager: WindowManager, private val viewConfiguration: ViewConfiguration, - @Main private val mainHandler: Handler, + @BackPanelUiThread private val uiThreadContext: UiThreadContext, private val systemClock: SystemClock, private val vibratorHelper: VibratorHelper, private val configurationController: ConfigurationController, @@ -112,20 +113,19 @@ internal constructor( ) { /** Construct a [BackPanelController]. */ fun create(context: Context): BackPanelController { - val backPanelController = - BackPanelController( + uiThreadContext.isCurrentThread() + return BackPanelController( context, windowManager, viewConfiguration, - mainHandler, + uiThreadContext.handler, systemClock, vibratorHelper, configurationController, latencyTracker, interactionJankMonitor ) - backPanelController.init() - return backPanelController + .also { it.init() } } } diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java index d0f8412c85b2..41cd2c46b998 100644 --- a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java +++ b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java @@ -44,8 +44,6 @@ import android.graphics.Rect; import android.graphics.Region; import android.hardware.input.InputManager; import android.icu.text.SimpleDateFormat; -import android.os.Handler; -import android.os.Looper; import android.os.RemoteException; import android.os.SystemClock; import android.os.SystemProperties; @@ -55,7 +53,6 @@ import android.util.ArraySet; import android.util.DisplayMetrics; import android.util.Log; import android.util.TypedValue; -import android.view.Choreographer; import android.view.ISystemGestureExclusionListener; import android.view.IWindowManager; import android.view.InputDevice; @@ -75,7 +72,6 @@ import androidx.annotation.DimenRes; import com.android.internal.config.sysui.SystemUiDeviceConfigFlags; import com.android.internal.policy.GestureNavigationSettingsObserver; import com.android.systemui.dagger.qualifiers.Background; -import com.android.systemui.dagger.qualifiers.Main; import com.android.systemui.model.SysUiState; import com.android.systemui.navigationbar.NavigationModeController; import com.android.systemui.plugins.FalsingManager; @@ -94,7 +90,8 @@ import com.android.systemui.shared.system.SysUiStatsLog; import com.android.systemui.shared.system.TaskStackChangeListener; import com.android.systemui.shared.system.TaskStackChangeListeners; import com.android.systemui.statusbar.phone.LightBarController; -import com.android.systemui.util.Assert; +import com.android.systemui.util.concurrency.BackPanelUiThread; +import com.android.systemui.util.concurrency.UiThreadContext; import com.android.wm.shell.back.BackAnimation; import com.android.wm.shell.desktopmode.DesktopMode; import com.android.wm.shell.pip.Pip; @@ -136,7 +133,7 @@ public class EdgeBackGestureHandler implements PluginListener<NavigationEdgeBack public void onSystemGestureExclusionChanged(int displayId, Region systemGestureExclusion, Region unrestrictedOrNull) { if (displayId == mDisplayId) { - mMainExecutor.execute(() -> { + mUiThreadContext.getExecutor().execute(() -> { mExcludeRegion.set(systemGestureExclusion); mUnrestrictedExcludeRegion.set(unrestrictedOrNull != null ? unrestrictedOrNull : systemGestureExclusion); @@ -215,8 +212,7 @@ public class EdgeBackGestureHandler implements PluginListener<NavigationEdgeBack private final Point mDisplaySize = new Point(); private final int mDisplayId; - private final Executor mMainExecutor; - private final Handler mMainHandler; + private final UiThreadContext mUiThreadContext; private final Executor mBackgroundExecutor; private final Rect mPipExcludedBounds = new Rect(); @@ -411,8 +407,7 @@ public class EdgeBackGestureHandler implements PluginListener<NavigationEdgeBack OverviewProxyService overviewProxyService, SysUiState sysUiState, PluginManager pluginManager, - @Main Executor executor, - @Main Handler handler, + @BackPanelUiThread UiThreadContext uiThreadContext, @Background Executor backgroundExecutor, UserTracker userTracker, NavigationModeController navigationModeController, @@ -428,8 +423,7 @@ public class EdgeBackGestureHandler implements PluginListener<NavigationEdgeBack Provider<LightBarController> lightBarControllerProvider) { mContext = context; mDisplayId = context.getDisplayId(); - mMainExecutor = executor; - mMainHandler = handler; + mUiThreadContext = uiThreadContext; mBackgroundExecutor = backgroundExecutor; mUserTracker = userTracker; mOverviewProxyService = overviewProxyService; @@ -478,7 +472,7 @@ public class EdgeBackGestureHandler implements PluginListener<NavigationEdgeBack ViewConfiguration.getLongPressTimeout()); mGestureNavigationSettingsObserver = new GestureNavigationSettingsObserver( - mMainHandler, mContext, this::onNavigationSettingsChanged); + mUiThreadContext.getHandler(), mContext, this::onNavigationSettingsChanged); updateCurrentUserResources(); } @@ -506,11 +500,13 @@ public class EdgeBackGestureHandler implements PluginListener<NavigationEdgeBack final boolean previousForcedVisible = mIsButtonForcedVisible; mIsButtonForcedVisible = mGestureNavigationSettingsObserver.areNavigationButtonForcedVisible(); + // Update this before calling mButtonForcedVisibleCallback since NavigationBar will relayout + // and query isHandlingGestures() as a part of the callback + mIsBackGestureAllowed = !mIsButtonForcedVisible; if (previousForcedVisible != mIsButtonForcedVisible && mButtonForcedVisibleCallback != null) { mButtonForcedVisibleCallback.accept(mIsButtonForcedVisible); } - mIsBackGestureAllowed = !mIsButtonForcedVisible; final DisplayMetrics dm = res.getDisplayMetrics(); final float defaultGestureHeight = res.getDimension( @@ -564,13 +560,15 @@ public class EdgeBackGestureHandler implements PluginListener<NavigationEdgeBack mIsAttached = true; mOverviewProxyService.addCallback(mQuickSwitchListener); mSysUiState.addCallback(mSysUiStateCallback); - mInputManager.registerInputDeviceListener(mInputDeviceListener, mMainHandler); - int [] inputDevices = mInputManager.getInputDeviceIds(); + mInputManager.registerInputDeviceListener( + mInputDeviceListener, + mUiThreadContext.getHandler()); + int[] inputDevices = mInputManager.getInputDeviceIds(); for (int inputDeviceId : inputDevices) { mInputDeviceListener.onInputDeviceAdded(inputDeviceId); } updateIsEnabled(); - mUserTracker.addCallback(mUserChangedCallback, mMainExecutor); + mUserTracker.addCallback(mUserChangedCallback, mUiThreadContext.getExecutor()); } /** @@ -617,6 +615,10 @@ public class EdgeBackGestureHandler implements PluginListener<NavigationEdgeBack } private void updateIsEnabled() { + mUiThreadContext.runWithScissors(this::updateIsEnabledInner); + } + + private void updateIsEnabledInner() { try { Trace.beginSection("EdgeBackGestureHandler#updateIsEnabled"); @@ -661,12 +663,12 @@ public class EdgeBackGestureHandler implements PluginListener<NavigationEdgeBack TaskStackChangeListeners.getInstance().registerTaskStackListener( mTaskStackListener); DeviceConfig.addOnPropertiesChangedListener(DeviceConfig.NAMESPACE_SYSTEMUI, - mMainExecutor::execute, mOnPropertiesChangedListener); + mUiThreadContext.getExecutor()::execute, mOnPropertiesChangedListener); mPipOptional.ifPresent(pip -> pip.setOnIsInPipStateChangedListener( mOnIsInPipStateChangedListener)); mDesktopModeOptional.ifPresent( dm -> dm.addDesktopGestureExclusionRegionListener( - mDesktopCornersChangedListener, mMainExecutor)); + mDesktopCornersChangedListener, mUiThreadContext.getExecutor())); try { mWindowManagerService.registerSystemGestureExclusionListener( @@ -677,8 +679,8 @@ public class EdgeBackGestureHandler implements PluginListener<NavigationEdgeBack // Register input event receiver mInputMonitor = new InputMonitorCompat("edge-swipe", mDisplayId); - mInputEventReceiver = mInputMonitor.getInputReceiver(Looper.getMainLooper(), - Choreographer.getInstance(), this::onInputEvent); + mInputEventReceiver = mInputMonitor.getInputReceiver(mUiThreadContext.getLooper(), + mUiThreadContext.getChoreographer(), this::onInputEvent); // Add a nav bar panel window resetEdgeBackPlugin(); @@ -773,7 +775,7 @@ public class EdgeBackGestureHandler implements PluginListener<NavigationEdgeBack mUseMLModel = newState; if (mUseMLModel) { - Assert.isMainThread(); + mUiThreadContext.isCurrentThread(); if (mMLModelIsLoading) { Log.d(TAG, "Model tried to load while already loading."); return; @@ -804,12 +806,13 @@ public class EdgeBackGestureHandler implements PluginListener<NavigationEdgeBack } BackGestureTfClassifierProvider finalProvider = provider; Map<String, Integer> finalVocab = vocab; - mMainExecutor.execute(() -> onMLModelLoadFinished(finalProvider, finalVocab, threshold)); + mUiThreadContext.getExecutor().execute( + () -> onMLModelLoadFinished(finalProvider, finalVocab, threshold)); } private void onMLModelLoadFinished(BackGestureTfClassifierProvider provider, Map<String, Integer> vocab, float threshold) { - Assert.isMainThread(); + mUiThreadContext.isCurrentThread(); mMLModelIsLoading = false; if (!mUseMLModel) { // This can happen if the user disables Gesture Nav while the model is loading. @@ -1291,7 +1294,7 @@ public class EdgeBackGestureHandler implements PluginListener<NavigationEdgeBack updateBackAnimationThresholds(); if (mLightBarControllerProvider.get() != null) { mBackAnimation.setStatusBarCustomizer((appearance) -> { - mMainExecutor.execute(() -> + mUiThreadContext.getExecutor().execute(() -> mLightBarControllerProvider.get() .customizeStatusBarAppearance(appearance)); }); @@ -1308,8 +1311,7 @@ public class EdgeBackGestureHandler implements PluginListener<NavigationEdgeBack private final OverviewProxyService mOverviewProxyService; private final SysUiState mSysUiState; private final PluginManager mPluginManager; - private final Executor mExecutor; - private final Handler mHandler; + private final UiThreadContext mUiThreadContext; private final Executor mBackgroundExecutor; private final UserTracker mUserTracker; private final NavigationModeController mNavigationModeController; @@ -1327,29 +1329,27 @@ public class EdgeBackGestureHandler implements PluginListener<NavigationEdgeBack @Inject public Factory(OverviewProxyService overviewProxyService, - SysUiState sysUiState, - PluginManager pluginManager, - @Main Executor executor, - @Main Handler handler, - @Background Executor backgroundExecutor, - UserTracker userTracker, - NavigationModeController navigationModeController, - BackPanelController.Factory backPanelControllerFactory, - ViewConfiguration viewConfiguration, - WindowManager windowManager, - IWindowManager windowManagerService, - InputManager inputManager, - Optional<Pip> pipOptional, - Optional<DesktopMode> desktopModeOptional, - FalsingManager falsingManager, - Provider<BackGestureTfClassifierProvider> - backGestureTfClassifierProviderProvider, - Provider<LightBarController> lightBarControllerProvider) { + SysUiState sysUiState, + PluginManager pluginManager, + @BackPanelUiThread UiThreadContext uiThreadContext, + @Background Executor backgroundExecutor, + UserTracker userTracker, + NavigationModeController navigationModeController, + BackPanelController.Factory backPanelControllerFactory, + ViewConfiguration viewConfiguration, + WindowManager windowManager, + IWindowManager windowManagerService, + InputManager inputManager, + Optional<Pip> pipOptional, + Optional<DesktopMode> desktopModeOptional, + FalsingManager falsingManager, + Provider<BackGestureTfClassifierProvider> + backGestureTfClassifierProviderProvider, + Provider<LightBarController> lightBarControllerProvider) { mOverviewProxyService = overviewProxyService; mSysUiState = sysUiState; mPluginManager = pluginManager; - mExecutor = executor; - mHandler = handler; + mUiThreadContext = uiThreadContext; mBackgroundExecutor = backgroundExecutor; mUserTracker = userTracker; mNavigationModeController = navigationModeController; @@ -1367,26 +1367,26 @@ public class EdgeBackGestureHandler implements PluginListener<NavigationEdgeBack /** Construct a {@link EdgeBackGestureHandler}. */ public EdgeBackGestureHandler create(Context context) { - return new EdgeBackGestureHandler( - context, - mOverviewProxyService, - mSysUiState, - mPluginManager, - mExecutor, - mHandler, - mBackgroundExecutor, - mUserTracker, - mNavigationModeController, - mBackPanelControllerFactory, - mViewConfiguration, - mWindowManager, - mWindowManagerService, - mInputManager, - mPipOptional, - mDesktopModeOptional, - mFalsingManager, - mBackGestureTfClassifierProviderProvider, - mLightBarControllerProvider); + return mUiThreadContext.runWithScissors( + () -> new EdgeBackGestureHandler( + context, + mOverviewProxyService, + mSysUiState, + mPluginManager, + mUiThreadContext, + mBackgroundExecutor, + mUserTracker, + mNavigationModeController, + mBackPanelControllerFactory, + mViewConfiguration, + mWindowManager, + mWindowManagerService, + mInputManager, + mPipOptional, + mDesktopModeOptional, + mFalsingManager, + mBackGestureTfClassifierProviderProvider, + mLightBarControllerProvider)); } } diff --git a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileViewImpl.kt b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileViewImpl.kt index 1143c304f594..762dacdfdddb 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileViewImpl.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileViewImpl.kt @@ -19,6 +19,7 @@ package com.android.systemui.qs.tileimpl import android.animation.ArgbEvaluator import android.animation.PropertyValuesHolder import android.animation.ValueAnimator +import android.annotation.SuppressLint import android.content.Context import android.content.res.ColorStateList import android.content.res.Configuration @@ -37,6 +38,7 @@ import android.util.Log import android.util.TypedValue import android.view.Gravity import android.view.LayoutInflater +import android.view.MotionEvent import android.view.View import android.view.ViewGroup import android.view.accessibility.AccessibilityEvent @@ -395,15 +397,22 @@ open class QSTileViewImpl @JvmOverloads constructor( override fun init(tile: QSTile) { val expandable = Expandable.fromView(this) - init( + if (quickSettingsVisualHapticsLongpress()) { + isHapticFeedbackEnabled = false + longPressEffect?.qsTile = tile + longPressEffect?.expandable = expandable + init( + { _: View? -> longPressEffect?.onTileClick() }, + null, // Haptics and long-clicks will be handled by the [QSLongPressEffect] + ) + } else { + init( { _: View? -> tile.click(expandable) }, { _: View? -> tile.longClick(expandable) true - } - ) - if (quickSettingsVisualHapticsLongpress()) { - isHapticFeedbackEnabled = false // Haptics will be handled by the [QSLongPressEffect] + }, + ) } } @@ -541,6 +550,20 @@ open class QSTileViewImpl @JvmOverloads constructor( return sb.toString() } + @SuppressLint("ClickableViewAccessibility") + override fun onTouchEvent(event: MotionEvent?): Boolean { + // let the View run the onTouch logic for click and long-click detection + val result = super.onTouchEvent(event) + if (longPressEffect != null) { + when (event?.actionMasked) { + MotionEvent.ACTION_DOWN -> longPressEffect.handleActionDown() + MotionEvent.ACTION_UP -> longPressEffect.handleActionUp() + MotionEvent.ACTION_CANCEL -> longPressEffect.handleActionCancel() + } + } + return result + } + // HANDLE STATE CHANGES RELATED METHODS protected open fun handleStateChanged(state: QSTile.State) { @@ -675,7 +698,6 @@ open class QSTileViewImpl @JvmOverloads constructor( // Long-press effects might have been enabled before but the new state does not // handle a long-press. In this case, we go back to the behaviour of a regular tile // and clean-up the resources - setOnTouchListener(null) unbindLongPressEffect() showRippleEffect = isClickable initialLongPressProperties = null diff --git a/packages/SystemUI/src/com/android/systemui/shade/carrier/ShadeCarrierGroupController.java b/packages/SystemUI/src/com/android/systemui/shade/carrier/ShadeCarrierGroupController.java index 5e4b1732bd42..a171d33ddb47 100644 --- a/packages/SystemUI/src/com/android/systemui/shade/carrier/ShadeCarrierGroupController.java +++ b/packages/SystemUI/src/com/android/systemui/shade/carrier/ShadeCarrierGroupController.java @@ -381,7 +381,10 @@ public class ShadeCarrierGroupController { mLogger.logHandleUpdateCarrierInfo(info); mNoSimTextView.setVisibility(View.GONE); - if (!info.airplaneMode && info.anySimReady) { + if (info.isInSatelliteMode) { + mLogger.logUsingSatelliteText(info.carrierText); + showSingleText(info.carrierText); + } else if (!info.airplaneMode && info.anySimReady) { boolean[] slotSeen = new boolean[SIM_SLOTS]; if (info.listOfCarriers.length == info.subscriptionIds.length) { mLogger.logUsingSimViews(); @@ -416,22 +419,31 @@ public class ShadeCarrierGroupController { info.listOfCarriers.length, info.subscriptionIds.length); } } else { + // No sims or airplane mode (but not WFC), so just show the main carrier text. mLogger.logUsingNoSimView(info.carrierText); - // No sims or airplane mode (but not WFC). Do not show ShadeCarrierGroup, - // instead just show info.carrierText in a different view. - for (int i = 0; i < SIM_SLOTS; i++) { - mInfos[i] = mInfos[i].changeVisibility(false); - mCarrierGroups[i].setCarrierText(""); - mCarrierGroups[i].setVisibility(View.GONE); - } - mNoSimTextView.setText(info.carrierText); - if (!TextUtils.isEmpty(info.carrierText)) { - mNoSimTextView.setVisibility(View.VISIBLE); - } + showSingleText(info.carrierText); } handleUpdateState(); // handleUpdateCarrierInfo is always called from main thread. } + /** + * Shows only the given text in a single TextView and hides ShadeCarrierGroup (which would show + * individual SIM details). + */ + private void showSingleText(CharSequence text) { + for (int i = 0; i < SIM_SLOTS; i++) { + mInfos[i] = mInfos[i].changeVisibility(false); + mCarrierGroups[i].setCarrierText(""); + mCarrierGroups[i].setVisibility(View.GONE); + } + // TODO(b/341841138): Re-name this view now that it's being used for more than just the + // no-SIM case. + mNoSimTextView.setText(text); + if (!TextUtils.isEmpty(text)) { + mNoSimTextView.setVisibility(View.VISIBLE); + } + } + private static class H extends Handler { private Consumer<CarrierTextManager.CarrierTextCallbackInfo> mUpdateCarrierInfo; private Runnable mUpdateState; diff --git a/packages/SystemUI/src/com/android/systemui/shade/carrier/ShadeCarrierGroupControllerLogger.kt b/packages/SystemUI/src/com/android/systemui/shade/carrier/ShadeCarrierGroupControllerLogger.kt index af06a356002b..b563cd9f0301 100644 --- a/packages/SystemUI/src/com/android/systemui/shade/carrier/ShadeCarrierGroupControllerLogger.kt +++ b/packages/SystemUI/src/com/android/systemui/shade/carrier/ShadeCarrierGroupControllerLogger.kt @@ -65,6 +65,15 @@ constructor(@ShadeCarrierGroupControllerLog val buffer: LogBuffer) { ) } + fun logUsingSatelliteText(text: CharSequence) { + buffer.log( + TAG, + LogLevel.VERBOSE, + { str1 = "$text" }, + { "â”— updating No SIM view with satellite text=$str1" }, + ) + } + fun logUsingSimViews() { buffer.log(TAG, LogLevel.VERBOSE, {}, { "â”— updating SIM views" }) } diff --git a/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeLockscreenInteractorImpl.kt b/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeLockscreenInteractorImpl.kt index 9885fe436e6c..e7fc18ebfc21 100644 --- a/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeLockscreenInteractorImpl.kt +++ b/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeLockscreenInteractorImpl.kt @@ -17,6 +17,7 @@ package com.android.systemui.shade.domain.interactor import com.android.keyguard.LockIconViewController +import com.android.systemui.dagger.qualifiers.Application import com.android.systemui.dagger.qualifiers.Background import com.android.systemui.scene.domain.interactor.SceneInteractor import com.android.systemui.scene.shared.model.Scenes @@ -30,7 +31,8 @@ import kotlinx.coroutines.launch class ShadeLockscreenInteractorImpl @Inject constructor( - @Background private val scope: CoroutineScope, + @Application private val applicationScope: CoroutineScope, + @Background private val backgroundScope: CoroutineScope, private val shadeInteractor: ShadeInteractor, private val sceneInteractor: SceneInteractor, private val lockIconViewController: LockIconViewController, @@ -68,7 +70,7 @@ constructor( // Now handled elsewhere. Do nothing. } override fun transitionToExpandedShade(delay: Long) { - scope.launch { + backgroundScope.launch { delay(delay) changeToShadeScene() } @@ -96,10 +98,12 @@ constructor( } private fun changeToShadeScene() { - val shadeMode = shadeInteractor.shadeMode.value - sceneInteractor.changeScene( - if (shadeMode is ShadeMode.Dual) Scenes.NotificationsShade else Scenes.Shade, - "ShadeLockscreenInteractorImpl.expandToNotifications", - ) + applicationScope.launch { + val shadeMode = shadeInteractor.shadeMode.value + sceneInteractor.changeScene( + if (shadeMode is ShadeMode.Dual) Scenes.NotificationsShade else Scenes.Shade, + "ShadeLockscreenInteractorImpl.expandToNotifications", + ) + } } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/KeyboardShortcuts.java b/packages/SystemUI/src/com/android/systemui/statusbar/KeyboardShortcuts.java index d00916a1c1a8..c742f6413022 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/KeyboardShortcuts.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/KeyboardShortcuts.java @@ -610,7 +610,7 @@ public final class KeyboardShortcuts { keyboardShortcutInfoAppItems.add(new KeyboardShortcutInfo( mContext.getString(R.string.keyboard_shortcut_group_applications_calendar), calendarIcon, - KeyEvent.KEYCODE_L, + KeyEvent.KEYCODE_K, KeyEvent.META_META_ON)); } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerImpl.java index 854ef928847a..bb26f92e510d 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerImpl.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerImpl.java @@ -16,6 +16,7 @@ package com.android.systemui.statusbar; import static android.app.Flags.keyguardPrivateNotifications; +import static android.app.Flags.redactSensitiveContentNotificationsOnLockscreen; import static android.app.StatusBarManager.ACTION_KEYGUARD_PRIVATE_NOTIFICATIONS_CHANGED; import static android.app.StatusBarManager.EXTRA_KM_PRIVATE_NOTIFS_ALLOWED; import static android.app.admin.DevicePolicyManager.ACTION_DEVICE_POLICY_MANAGER_STATE_CHANGED; @@ -654,10 +655,12 @@ public class NotificationLockscreenUserManagerImpl implements !userAllowsPrivateNotificationsInPublic(mCurrentUserId); boolean isNotifForManagedProfile = mCurrentManagedProfiles.contains(userId); boolean isNotifUserRedacted = !userAllowsPrivateNotificationsInPublic(userId); + boolean isNotifSensitive = redactSensitiveContentNotificationsOnLockscreen() + && ent.getRanking() != null && ent.getRanking().hasSensitiveContent(); - // redact notifications if the current user is redacting notifications; however if the - // notification is associated with a managed profile, we rely on the managed profile - // setting to determine whether to redact it + // redact notifications if the current user is redacting notifications or the notification + // contains sensitive content. However if the notification is associated with a managed + // profile, we rely on the managed profile setting to determine whether to redact it. boolean isNotifRedacted = (!isNotifForManagedProfile && isCurrentUserRedactingNotifs) || isNotifUserRedacted; @@ -666,10 +669,11 @@ public class NotificationLockscreenUserManagerImpl implements boolean userForcesRedaction = packageHasVisibilityOverride(ent.getSbn().getKey()); if (keyguardPrivateNotifications()) { - return !mKeyguardAllowingNotifications - || userForcesRedaction || notificationRequestsRedaction && isNotifRedacted; + return !mKeyguardAllowingNotifications || isNotifSensitive + || userForcesRedaction || (notificationRequestsRedaction && isNotifRedacted); } else { - return userForcesRedaction || notificationRequestsRedaction && isNotifRedacted; + return userForcesRedaction || isNotifSensitive + || (notificationRequestsRedaction && isNotifRedacted); } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/ConversationNotifications.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/ConversationNotifications.kt index c643238b7e30..682a9fff2994 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/ConversationNotifications.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/ConversationNotifications.kt @@ -34,8 +34,8 @@ import com.android.systemui.statusbar.notification.collection.inflation.BindEven import com.android.systemui.statusbar.notification.collection.notifcollection.CommonNotifCollection import com.android.systemui.statusbar.notification.collection.notifcollection.NotifCollectionListener import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow -import com.android.systemui.statusbar.notification.row.NotificationContentInflaterLogger import com.android.systemui.statusbar.notification.row.NotificationContentView +import com.android.systemui.statusbar.notification.row.NotificationRowContentBinderLogger import com.android.systemui.statusbar.notification.stack.StackStateAnimator import com.android.systemui.statusbar.policy.HeadsUpManager import com.android.systemui.statusbar.policy.OnHeadsUpChangedListener @@ -44,30 +44,29 @@ import java.util.concurrent.ConcurrentHashMap import javax.inject.Inject /** Populates additional information in conversation notifications */ -class ConversationNotificationProcessor @Inject constructor( +class ConversationNotificationProcessor +@Inject +constructor( private val launcherApps: LauncherApps, private val conversationNotificationManager: ConversationNotificationManager ) { fun processNotification( - entry: NotificationEntry, - recoveredBuilder: Notification.Builder, - logger: NotificationContentInflaterLogger + entry: NotificationEntry, + recoveredBuilder: Notification.Builder, + logger: NotificationRowContentBinderLogger ): Notification.MessagingStyle? { val messagingStyle = recoveredBuilder.style as? Notification.MessagingStyle ?: return null messagingStyle.conversationType = - if (entry.ranking.channel.isImportantConversation) - Notification.MessagingStyle.CONVERSATION_TYPE_IMPORTANT - else - Notification.MessagingStyle.CONVERSATION_TYPE_NORMAL + if (entry.ranking.channel.isImportantConversation) + Notification.MessagingStyle.CONVERSATION_TYPE_IMPORTANT + else Notification.MessagingStyle.CONVERSATION_TYPE_NORMAL entry.ranking.conversationShortcutInfo?.let { shortcutInfo -> logger.logAsyncTaskProgress(entry, "getting shortcut icon") messagingStyle.shortcutIcon = launcherApps.getShortcutIcon(shortcutInfo) - shortcutInfo.label?.let { label -> - messagingStyle.conversationTitle = label - } + shortcutInfo.label?.let { label -> messagingStyle.conversationTitle = label } } messagingStyle.unreadMessageCount = - conversationNotificationManager.getUnreadCount(entry, recoveredBuilder) + conversationNotificationManager.getUnreadCount(entry, recoveredBuilder) return messagingStyle } } @@ -77,7 +76,9 @@ class ConversationNotificationProcessor @Inject constructor( * animations to conserve CPU and memory. */ @SysUISingleton -class AnimatedImageNotificationManager @Inject constructor( +class AnimatedImageNotificationManager +@Inject +constructor( private val notifCollection: CommonNotifCollection, private val bindEventManager: BindEventManager, private val headsUpManager: HeadsUpManager, @@ -88,17 +89,21 @@ class AnimatedImageNotificationManager @Inject constructor( /** Begins listening to state changes and updating animations accordingly. */ fun bind() { - headsUpManager.addListener(object : OnHeadsUpChangedListener { - override fun onHeadsUpStateChanged(entry: NotificationEntry, isHeadsUp: Boolean) { - updateAnimatedImageDrawables(entry) + headsUpManager.addListener( + object : OnHeadsUpChangedListener { + override fun onHeadsUpStateChanged(entry: NotificationEntry, isHeadsUp: Boolean) { + updateAnimatedImageDrawables(entry) + } } - }) - statusBarStateController.addCallback(object : StatusBarStateController.StateListener { - override fun onExpandedChanged(isExpanded: Boolean) { - isStatusBarExpanded = isExpanded - notifCollection.allNotifs.forEach(::updateAnimatedImageDrawables) + ) + statusBarStateController.addCallback( + object : StatusBarStateController.StateListener { + override fun onExpandedChanged(isExpanded: Boolean) { + isStatusBarExpanded = isExpanded + notifCollection.allNotifs.forEach(::updateAnimatedImageDrawables) + } } - }) + ) bindEventManager.addListener(::updateAnimatedImageDrawables) } @@ -108,74 +113,73 @@ class AnimatedImageNotificationManager @Inject constructor( } private fun updateAnimatedImageDrawables(row: ExpandableNotificationRow, animating: Boolean) = - (row.layouts?.asSequence() ?: emptySequence()) - .flatMap { layout -> layout.allViews.asSequence() } - .flatMap { view -> - (view as? ConversationLayout)?.messagingGroups?.asSequence() - ?: (view as? MessagingLayout)?.messagingGroups?.asSequence() - ?: emptySequence() - } - .flatMap { messagingGroup -> messagingGroup.messageContainer.children } - .mapNotNull { view -> - (view as? MessagingImageMessage) - ?.let { imageMessage -> - imageMessage.drawable as? AnimatedImageDrawable - } - } - .forEach { animatedImageDrawable -> - if (animating) animatedImageDrawable.start() - else animatedImageDrawable.stop() - } + (row.layouts?.asSequence() ?: emptySequence()) + .flatMap { layout -> layout.allViews.asSequence() } + .flatMap { view -> + (view as? ConversationLayout)?.messagingGroups?.asSequence() + ?: (view as? MessagingLayout)?.messagingGroups?.asSequence() ?: emptySequence() + } + .flatMap { messagingGroup -> messagingGroup.messageContainer.children } + .mapNotNull { view -> + (view as? MessagingImageMessage)?.let { imageMessage -> + imageMessage.drawable as? AnimatedImageDrawable + } + } + .forEach { animatedImageDrawable -> + if (animating) animatedImageDrawable.start() else animatedImageDrawable.stop() + } } /** * Tracks state related to conversation notifications, and updates the UI of existing notifications * when necessary. + * * TODO(b/214083332) Refactor this class to use the right coordinators and controllers */ @SysUISingleton -class ConversationNotificationManager @Inject constructor( +class ConversationNotificationManager +@Inject +constructor( bindEventManager: BindEventManager, private val context: Context, private val notifCollection: CommonNotifCollection, @Main private val mainHandler: Handler ) { // Need this state to be thread safe, since it's accessed from the ui thread - // (NotificationEntryListener) and a bg thread (NotificationContentInflater) + // (NotificationEntryListener) and a bg thread (NotificationRowContentBinder) private val states = ConcurrentHashMap<String, ConversationState>() private var notifPanelCollapsed = true private fun updateNotificationRanking(rankingMap: RankingMap) { fun getLayouts(view: NotificationContentView) = - sequenceOf(view.contractedChild, view.expandedChild, view.headsUpChild) + sequenceOf(view.contractedChild, view.expandedChild, view.headsUpChild) val ranking = Ranking() - val activeConversationEntries = states.keys.asSequence() - .mapNotNull { notifCollection.getEntry(it) } + val activeConversationEntries = + states.keys.asSequence().mapNotNull { notifCollection.getEntry(it) } for (entry in activeConversationEntries) { if (rankingMap.getRanking(entry.sbn.key, ranking) && ranking.isConversation) { val important = ranking.channel.isImportantConversation var changed = false - entry.row?.layouts?.asSequence() - ?.flatMap(::getLayouts) - ?.mapNotNull { it as? ConversationLayout } - ?.filterNot { it.isImportantConversation == important } - ?.forEach { layout -> - changed = true - if (important && entry.isMarkedForUserTriggeredMovement) { - // delay this so that it doesn't animate in until after - // the notif has been moved in the shade - mainHandler.postDelayed( - { - layout.setIsImportantConversation( - important, - true) - }, - IMPORTANCE_ANIMATION_DELAY.toLong()) - } else { - layout.setIsImportantConversation(important, false) - } + entry.row + ?.layouts + ?.asSequence() + ?.flatMap(::getLayouts) + ?.mapNotNull { it as? ConversationLayout } + ?.filterNot { it.isImportantConversation == important } + ?.forEach { layout -> + changed = true + if (important && entry.isMarkedForUserTriggeredMovement) { + // delay this so that it doesn't animate in until after + // the notif has been moved in the shade + mainHandler.postDelayed( + { layout.setIsImportantConversation(important, true) }, + IMPORTANCE_ANIMATION_DELAY.toLong() + ) + } else { + layout.setIsImportantConversation(important, false) } + } } } } @@ -192,9 +196,7 @@ class ConversationNotificationManager @Inject constructor( } entry.row?.setOnExpansionChangedListener { isExpanded -> if (entry.row?.isShown == true && isExpanded) { - entry.row.performOnIntrinsicHeightReached { - updateCount(isExpanded) - } + entry.row.performOnIntrinsicHeightReached { updateCount(isExpanded) } } else { updateCount(isExpanded) } @@ -203,31 +205,38 @@ class ConversationNotificationManager @Inject constructor( } init { - notifCollection.addCollectionListener(object : NotifCollectionListener { - override fun onRankingUpdate(ranking: RankingMap) = - updateNotificationRanking(ranking) + notifCollection.addCollectionListener( + object : NotifCollectionListener { + override fun onRankingUpdate(ranking: RankingMap) = + updateNotificationRanking(ranking) - override fun onEntryRemoved(entry: NotificationEntry, reason: Int) = - removeTrackedEntry(entry) - }) + override fun onEntryRemoved(entry: NotificationEntry, reason: Int) = + removeTrackedEntry(entry) + } + ) bindEventManager.addListener(::onEntryViewBound) } private fun ConversationState.shouldIncrementUnread(newBuilder: Notification.Builder) = - if (notification.flags and Notification.FLAG_ONLY_ALERT_ONCE != 0) { - false - } else { - val oldBuilder = Notification.Builder.recoverBuilder(context, notification) - Notification.areStyledNotificationsVisiblyDifferent(oldBuilder, newBuilder) - } + if (notification.flags and Notification.FLAG_ONLY_ALERT_ONCE != 0) { + false + } else { + val oldBuilder = Notification.Builder.recoverBuilder(context, notification) + Notification.areStyledNotificationsVisiblyDifferent(oldBuilder, newBuilder) + } fun getUnreadCount(entry: NotificationEntry, recoveredBuilder: Notification.Builder): Int = - states.compute(entry.key) { _, state -> - val newCount = state?.run { - if (shouldIncrementUnread(recoveredBuilder)) unreadCount + 1 else unreadCount - } ?: 1 + states + .compute(entry.key) { _, state -> + val newCount = + state?.run { + if (shouldIncrementUnread(recoveredBuilder)) unreadCount + 1 + else unreadCount + } + ?: 1 ConversationState(newCount, entry.sbn.notification) - }!!.unreadCount + }!! + .unreadCount fun onNotificationPanelExpandStateChanged(isCollapsed: Boolean) { notifPanelCollapsed = isCollapsed @@ -235,18 +244,17 @@ class ConversationNotificationManager @Inject constructor( // When the notification panel is expanded, reset the counters of any expanded // conversations - val expanded = states + val expanded = + states .asSequence() .mapNotNull { (key, _) -> notifCollection.getEntry(key)?.let { entry -> - if (entry.row?.isExpanded == true) key to entry - else null + if (entry.row?.isExpanded == true) key to entry else null } } .toMap() states.replaceAll { key, state -> - if (expanded.contains(key)) state.copy(unreadCount = 0) - else state + if (expanded.contains(key)) state.copy(unreadCount = 0) else state } // Update UI separate from the replaceAll call, since ConcurrentHashMap may re-run the // lambda if threads are in contention. @@ -262,16 +270,16 @@ class ConversationNotificationManager @Inject constructor( } private fun resetBadgeUi(row: ExpandableNotificationRow): Unit = - (row.layouts?.asSequence() ?: emptySequence()) - .flatMap { layout -> layout.allViews.asSequence() } - .mapNotNull { view -> view as? ConversationLayout } - .forEach { convoLayout -> convoLayout.setUnreadCount(0) } + (row.layouts?.asSequence() ?: emptySequence()) + .flatMap { layout -> layout.allViews.asSequence() } + .mapNotNull { view -> view as? ConversationLayout } + .forEach { convoLayout -> convoLayout.setUnreadCount(0) } private data class ConversationState(val unreadCount: Int, val notification: Notification) companion object { private const val IMPORTANCE_ANIMATION_DELAY = - StackStateAnimator.ANIMATION_DURATION_STANDARD + + StackStateAnimator.ANIMATION_DURATION_STANDARD + StackStateAnimator.ANIMATION_DURATION_PRIORITY_CHANGE + 100 } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/DynamicChildBindController.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/DynamicChildBindController.java index 57b41f36e51f..cafe6ffc87a3 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/DynamicChildBindController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/DynamicChildBindController.java @@ -16,8 +16,8 @@ package com.android.systemui.statusbar.notification; -import static com.android.systemui.statusbar.notification.row.NotificationContentInflater.FLAG_CONTENT_VIEW_CONTRACTED; -import static com.android.systemui.statusbar.notification.row.NotificationContentInflater.FLAG_CONTENT_VIEW_EXPANDED; +import static com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.FLAG_CONTENT_VIEW_CONTRACTED; +import static com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.FLAG_CONTENT_VIEW_EXPANDED; import static com.android.systemui.statusbar.notification.stack.NotificationChildrenContainer.NUMBER_OF_CHILDREN_WHEN_CHILDREN_EXPANDED; import com.android.systemui.statusbar.notification.collection.NotifPipeline; diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifInflaterImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifInflaterImpl.java index 98109f940289..fc47dc1ed81a 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifInflaterImpl.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifInflaterImpl.java @@ -23,7 +23,7 @@ import com.android.systemui.statusbar.notification.InflationException; import com.android.systemui.statusbar.notification.collection.inflation.NotifInflater; import com.android.systemui.statusbar.notification.collection.inflation.NotificationRowBinderImpl; import com.android.systemui.statusbar.notification.row.NotifInflationErrorManager; -import com.android.systemui.statusbar.notification.row.NotificationContentInflater; +import com.android.systemui.statusbar.notification.row.NotificationRowContentBinder; import javax.inject.Inject; @@ -100,9 +100,9 @@ public class NotifInflaterImpl implements NotifInflater { requireBinder().releaseViews(entry); } - private NotificationContentInflater.InflationCallback wrapInflationCallback( + private NotificationRowContentBinder.InflationCallback wrapInflationCallback( InflationCallback callback) { - return new NotificationContentInflater.InflationCallback() { + return new NotificationRowContentBinder.InflationCallback() { @Override public void handleInflationException( NotificationEntry entry, diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotificationMemoryMonitor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotificationMemoryMonitor.kt index 0fdf681aac26..1efa56f9bb09 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotificationMemoryMonitor.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotificationMemoryMonitor.kt @@ -20,8 +20,6 @@ package com.android.systemui.statusbar.notification.logging import android.util.Log import com.android.systemui.CoreStartable import com.android.systemui.dagger.SysUISingleton -import com.android.systemui.flags.FeatureFlags -import com.android.systemui.flags.Flags import dagger.Lazy import javax.inject.Inject @@ -30,7 +28,6 @@ import javax.inject.Inject class NotificationMemoryMonitor @Inject constructor( - private val featureFlags: FeatureFlags, private val notificationMemoryDumper: NotificationMemoryDumper, private val notificationMemoryLogger: Lazy<NotificationMemoryLogger>, ) : CoreStartable { @@ -42,9 +39,6 @@ constructor( override fun start() { Log.d(TAG, "NotificationMemoryMonitor initialized.") notificationMemoryDumper.init() - if (featureFlags.isEnabled(Flags.NOTIFICATION_MEMORY_LOGGING_ENABLED)) { - Log.d(TAG, "Notification memory logging enabled.") - notificationMemoryLogger.get().init() - } + notificationMemoryLogger.get().init() } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java index 99ce454126cf..190a2cd269ae 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java @@ -548,7 +548,7 @@ public class ExpandableNotificationRow extends ActivatableNotificationView * and ensuring that the view is freed when it is safe to remove. * * @param inflationFlag flag corresponding to the content view to be freed - * @deprecated By default, {@link NotificationContentInflater#unbindContent} will tell the + * @deprecated By default, {@link NotificationRowContentBinder#unbindContent} will tell the * view hierarchy to only free when the view is safe to remove so this method is no longer * needed. Will remove when all uses are gone. */ diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentInflater.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentInflater.java index 2f038714212b..6ba26d99419e 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentInflater.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentInflater.java @@ -92,7 +92,7 @@ public class NotificationContentInflater implements NotificationRowContentBinder private final NotifLayoutInflaterFactory.Provider mNotifLayoutInflaterFactoryProvider; private final HeadsUpStyleProvider mHeadsUpStyleProvider; - private final NotificationContentInflaterLogger mLogger; + private final NotificationRowContentBinderLogger mLogger; @Inject NotificationContentInflater( @@ -104,7 +104,7 @@ public class NotificationContentInflater implements NotificationRowContentBinder SmartReplyStateInflater smartRepliesInflater, NotifLayoutInflaterFactory.Provider notifLayoutInflaterFactoryProvider, HeadsUpStyleProvider headsUpStyleProvider, - NotificationContentInflaterLogger logger) { + NotificationRowContentBinderLogger logger) { mRemoteViewCache = remoteViewCache; mRemoteInputManager = remoteInputManager; mConversationProcessor = conversationProcessor; @@ -345,7 +345,7 @@ public class NotificationContentInflater implements NotificationRowContentBinder Context packageContext, InflatedSmartReplyState previousSmartReplyState, SmartReplyStateInflater inflater, - NotificationContentInflaterLogger logger) { + NotificationRowContentBinderLogger logger) { boolean inflateContracted = (reInflateFlags & FLAG_CONTENT_VIEW_CONTRACTED) != 0 && result.newContentView != null; boolean inflateExpanded = (reInflateFlags & FLAG_CONTENT_VIEW_EXPANDED) != 0 @@ -377,7 +377,7 @@ public class NotificationContentInflater implements NotificationRowContentBinder ExpandableNotificationRow row, NotifLayoutInflaterFactory.Provider notifLayoutInflaterFactoryProvider, HeadsUpStyleProvider headsUpStyleProvider, - NotificationContentInflaterLogger logger) { + NotificationRowContentBinderLogger logger) { return TraceUtils.trace("NotificationContentInflater.createRemoteViews", () -> { InflationProgress result = new InflationProgress(); final NotificationEntry entryForLogging = row.getEntry(); @@ -465,7 +465,7 @@ public class NotificationContentInflater implements NotificationRowContentBinder ExpandableNotificationRow row, RemoteViews.InteractionHandler remoteViewClickHandler, @Nullable InflationCallback callback, - NotificationContentInflaterLogger logger) { + NotificationRowContentBinderLogger logger) { Trace.beginAsyncSection(APPLY_TRACE_METHOD, System.identityHashCode(row)); NotificationContentView privateLayout = row.getPrivateLayout(); @@ -676,7 +676,7 @@ public class NotificationContentInflater implements NotificationRowContentBinder NotificationViewWrapper existingWrapper, final HashMap<Integer, CancellationSignal> runningInflations, ApplyCallback applyCallback, - NotificationContentInflaterLogger logger) { + NotificationRowContentBinderLogger logger) { RemoteViews newContentView = applyCallback.getRemoteView(); if (inflateSynchronously) { try { @@ -845,7 +845,7 @@ public class NotificationContentInflater implements NotificationRowContentBinder private static void handleInflationError( HashMap<Integer, CancellationSignal> runningInflations, Exception e, NotificationEntry notification, @Nullable InflationCallback callback, - NotificationContentInflaterLogger logger, String logContext) { + NotificationRowContentBinderLogger logger, String logContext) { Assert.isMainThread(); logger.logAsyncTaskException(notification, logContext, e); runningInflations.values().forEach(CancellationSignal::cancel); @@ -864,7 +864,7 @@ public class NotificationContentInflater implements NotificationRowContentBinder @InflationFlag int reInflateFlags, NotifRemoteViewCache remoteViewCache, HashMap<Integer, CancellationSignal> runningInflations, @Nullable InflationCallback endListener, NotificationEntry entry, - ExpandableNotificationRow row, NotificationContentInflaterLogger logger) { + ExpandableNotificationRow row, NotificationRowContentBinderLogger logger) { Assert.isMainThread(); if (!runningInflations.isEmpty()) { return false; @@ -1080,7 +1080,7 @@ public class NotificationContentInflater implements NotificationRowContentBinder private final SmartReplyStateInflater mSmartRepliesInflater; private final NotifLayoutInflaterFactory.Provider mNotifLayoutInflaterFactoryProvider; private final HeadsUpStyleProvider mHeadsUpStyleProvider; - private final NotificationContentInflaterLogger mLogger; + private final NotificationRowContentBinderLogger mLogger; private AsyncInflationTask( Executor inflationExecutor, @@ -1099,7 +1099,7 @@ public class NotificationContentInflater implements NotificationRowContentBinder SmartReplyStateInflater smartRepliesInflater, NotifLayoutInflaterFactory.Provider notifLayoutInflaterFactoryProvider, HeadsUpStyleProvider headsUpStyleProvider, - NotificationContentInflaterLogger logger) { + NotificationRowContentBinderLogger logger) { mEntry = entry; mRow = row; mInflationExecutor = inflationExecutor; diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationRowContentBinder.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationRowContentBinder.java index 33339a7fe025..c1302a0d3e57 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationRowContentBinder.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationRowContentBinder.java @@ -20,6 +20,8 @@ import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.Nullable; +import androidx.annotation.VisibleForTesting; + import com.android.systemui.statusbar.notification.collection.NotificationEntry; import java.lang.annotation.Retention; @@ -72,6 +74,10 @@ public interface NotificationRowContentBinder { @NonNull ExpandableNotificationRow row, @InflationFlag int contentToUnbind); + /** For testing, ensure all inflation is synchronous. */ + @VisibleForTesting + void setInflateSynchronously(boolean inflateSynchronously); + @Retention(RetentionPolicy.SOURCE) @IntDef(flag = true, prefix = {"FLAG_CONTENT_VIEW_"}, diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentInflaterLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationRowContentBinderLogger.kt index 15c705579bf7..a32e1d738fcc 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentInflaterLogger.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationRowContentBinderLogger.kt @@ -32,7 +32,7 @@ import com.android.systemui.statusbar.notification.row.NotificationRowContentBin import com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.InflationFlag import javax.inject.Inject -class NotificationContentInflaterLogger +class NotificationRowContentBinderLogger @Inject constructor(@NotifInflationLog private val buffer: LogBuffer) { fun logNotBindingRowWasRemoved(entry: NotificationEntry) { @@ -158,4 +158,4 @@ constructor(@NotifInflationLog private val buffer: LogBuffer) { } } -private const val TAG = "NotificationContentInflater" +private const val TAG = "NotificationRowContentBinder" diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/RowContentBindParams.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/RowContentBindParams.java index bae89fbf626f..427fb66ca2d0 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/RowContentBindParams.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/RowContentBindParams.java @@ -35,7 +35,7 @@ public final class RowContentBindParams { /** * Content views that are out of date and need to be rebound. * - * TODO: This should go away once {@link NotificationContentInflater} is broken down into + * TODO: This should go away once {@link NotificationRowContentBinder} is broken down into * smaller stages as then the stage itself would be invalidated. */ private @InflationFlag int mDirtyContentViews = mContentViews; diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/SingleLineViewInflater.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/SingleLineViewInflater.kt index 3fce9ce5837d..6fc82c9bcd7e 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/SingleLineViewInflater.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/SingleLineViewInflater.kt @@ -342,7 +342,7 @@ internal object SingleLineViewInflater { reinflateFlags: Int, entry: NotificationEntry, context: Context, - logger: NotificationContentInflaterLogger, + logger: NotificationRowContentBinderLogger, ): HybridNotificationView? { if (AsyncHybridViewInflation.isUnexpectedlyInLegacyMode()) return null if (reinflateFlags and FLAG_CONTENT_VIEW_SINGLE_LINE == 0) { @@ -354,7 +354,7 @@ internal object SingleLineViewInflater { var view: HybridNotificationView? = null - traceSection("NotificationContentInflater#inflateSingleLineView") { + traceSection("SingleLineViewInflater#inflateSingleLineView") { val inflater = LayoutInflater.from(context) val layoutRes: Int = if (isConversation) diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LegacyActivityStarterInternalImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LegacyActivityStarterInternalImpl.kt index b448d85bfd49..fc29eaba4b46 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LegacyActivityStarterInternalImpl.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LegacyActivityStarterInternalImpl.kt @@ -413,6 +413,7 @@ constructor( afterKeyguardGone: Boolean, customMessage: String?, ) { + Log.i(TAG, "Invoking dismissKeyguardThenExecute, afterKeyguardGone: $afterKeyguardGone") if ( !action.willRunAnimationOnKeyguard() && wakefulnessLifecycle.wakefulness == WakefulnessLifecycle.WAKEFULNESS_ASLEEP && diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java index ebb62ec7bcac..db4f0af5ba9f 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java @@ -112,6 +112,7 @@ import kotlinx.coroutines.ExperimentalCoroutinesApi; import kotlinx.coroutines.Job; import java.io.PrintWriter; +import java.io.StringWriter; import java.util.ArrayList; import java.util.HashSet; import java.util.Objects; @@ -899,6 +900,11 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb } finally { Trace.endSection(); } + } else { + Log.w(TAG, "Ignoring request to dismiss, dumping state: "); + StringWriter sw = new StringWriter(); + mKeyguardStateController.dump(new PrintWriter(sw), null); + Log.w(TAG, sw.toString()); } updateStates(); } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryImpl.kt index c32f0e8090ac..261258a58914 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryImpl.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryImpl.kt @@ -40,6 +40,7 @@ import com.android.systemui.broadcast.BroadcastDispatcher import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Application import com.android.systemui.dagger.qualifiers.Background +import com.android.systemui.dagger.qualifiers.Main import com.android.systemui.dump.DumpManager import com.android.systemui.log.table.TableLogBuffer import com.android.systemui.log.table.logDiffsForTable @@ -99,6 +100,7 @@ constructor( private val context: Context, @Background private val bgDispatcher: CoroutineDispatcher, @Application private val scope: CoroutineScope, + @Main private val mainDispatcher: CoroutineDispatcher, airplaneModeRepository: AirplaneModeRepository, // Some "wifi networks" should be rendered as a mobile connection, which is why the wifi // repository is an input to the mobile repository. @@ -315,6 +317,7 @@ constructor( trySend(false) awaitClose { keyguardUpdateMonitor.removeCallback(callback) } } + .flowOn(mainDispatcher) .logDiffsForTable( tableLogger, LOGGING_PREFIX, diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/satellite/data/prod/DeviceBasedSatelliteRepositoryImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/satellite/data/prod/DeviceBasedSatelliteRepositoryImpl.kt index 43258972ea34..1449e535c279 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/satellite/data/prod/DeviceBasedSatelliteRepositoryImpl.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/satellite/data/prod/DeviceBasedSatelliteRepositoryImpl.kt @@ -23,6 +23,7 @@ import android.telephony.satellite.NtnSignalStrengthCallback import android.telephony.satellite.SatelliteManager import android.telephony.satellite.SatelliteManager.SATELLITE_RESULT_SUCCESS import android.telephony.satellite.SatelliteModemStateCallback +import android.telephony.satellite.SatelliteSupportedStateCallback import androidx.annotation.VisibleForTesting import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow import com.android.systemui.dagger.SysUISingleton @@ -35,6 +36,7 @@ import com.android.systemui.log.core.MessagePrinter import com.android.systemui.statusbar.pipeline.dagger.DeviceBasedSatelliteInputLog import com.android.systemui.statusbar.pipeline.dagger.VerboseDeviceBasedSatelliteInputLog import com.android.systemui.statusbar.pipeline.satellite.data.RealDeviceBasedSatelliteRepository +import com.android.systemui.statusbar.pipeline.satellite.data.prod.DeviceBasedSatelliteRepositoryImpl.Companion.POLLING_INTERVAL_MS import com.android.systemui.statusbar.pipeline.satellite.data.prod.SatelliteSupport.Companion.whenSupported import com.android.systemui.statusbar.pipeline.satellite.data.prod.SatelliteSupport.NotSupported import com.android.systemui.statusbar.pipeline.satellite.data.prod.SatelliteSupport.Supported @@ -162,60 +164,6 @@ constructor( @get:VisibleForTesting val satelliteSupport: MutableStateFlow<SatelliteSupport> = MutableStateFlow(Unknown) - init { - satelliteManager = satelliteManagerOpt.getOrNull() - - isSatelliteAllowedForCurrentLocation = MutableStateFlow(false) - - if (satelliteManager != null) { - // First, check that satellite is supported on this device - scope.launch { - val waitTime = ensureMinUptime(systemClock, MIN_UPTIME) - if (waitTime > 0) { - logBuffer.i({ long1 = waitTime }) { - "Waiting $long1 ms before checking for satellite support" - } - delay(waitTime) - } - - satelliteSupport.value = satelliteManager.checkSatelliteSupported() - - logBuffer.i( - { str1 = satelliteSupport.value.toString() }, - { "Checked for system support. support=$str1" }, - ) - - // We only need to check location availability if this mode is supported - if (satelliteSupport.value is Supported) { - isSatelliteAllowedForCurrentLocation.subscriptionCount - .map { it > 0 } - .distinctUntilChanged() - .collectLatest { hasSubscribers -> - if (hasSubscribers) { - /* - * As there is no listener available for checking satellite allowed, - * we must poll. Defaulting to polling at most once every hour while - * active. Subsequent OOS events will restart the job, so a flaky - * connection might cause more frequent checks. - */ - while (true) { - logBuffer.i { - "requestIsCommunicationAllowedForCurrentLocation" - } - checkIsSatelliteAllowed() - delay(POLLING_INTERVAL_MS) - } - } - } - } - } - } else { - logBuffer.i { "Satellite manager is null" } - - satelliteSupport.value = NotSupported - } - } - /** * Note that we are given an "unbound" [TelephonyManager] (meaning it was not created with a * specific `subscriptionId`). Therefore this is the radio power state of the @@ -269,6 +217,134 @@ constructor( } .onStart { emit(Unit) } + init { + satelliteManager = satelliteManagerOpt.getOrNull() + + isSatelliteAllowedForCurrentLocation = MutableStateFlow(false) + + if (satelliteManager != null) { + // Outer scope launch allows us to delay until MIN_UPTIME + scope.launch { + // First, check that satellite is supported on this device + satelliteSupport.value = checkSatelliteSupportAfterMinUptime(satelliteManager) + logBuffer.i( + { str1 = satelliteSupport.value.toString() }, + { "Checked for system support. support=$str1" }, + ) + + // Second, launch a job to poll for service availability based on location + scope.launch { pollForAvailabilityBasedOnLocation() } + + // Third, register a listener to let us know if there are changes to support + scope.launch { listenForChangesToSatelliteSupport(satelliteManager) } + } + } else { + logBuffer.i { "Satellite manager is null" } + satelliteSupport.value = NotSupported + } + } + + private suspend fun checkSatelliteSupportAfterMinUptime( + sm: SatelliteManager + ): SatelliteSupport { + val waitTime = ensureMinUptime(systemClock, MIN_UPTIME) + if (waitTime > 0) { + logBuffer.i({ long1 = waitTime }) { + "Waiting $long1 ms before checking for satellite support" + } + delay(waitTime) + } + + return sm.checkSatelliteSupported() + } + + /* + * As there is no listener available for checking satellite allowed, we must poll the service. + * Defaulting to polling at most once every 20m while active. Subsequent OOS events will restart + * the job, so a flaky connection might cause more frequent checks. + */ + private suspend fun pollForAvailabilityBasedOnLocation() { + satelliteSupport + .whenSupported( + supported = ::isSatelliteAllowedHasListener, + orElse = flowOf(false), + retrySignal = telephonyProcessCrashedEvent, + ) + .collectLatest { hasSubscribers -> + if (hasSubscribers) { + while (true) { + logBuffer.i { "requestIsCommunicationAllowedForCurrentLocation" } + checkIsSatelliteAllowed() + delay(POLLING_INTERVAL_MS) + } + } + } + } + + /** + * Register a callback with [SatelliteManager] to let us know if there is a change in satellite + * support. This job restarts if there is a crash event detected. + * + * Note that the structure of this method looks similar to [whenSupported], but since we want + * this callback registered even when it is [NotSupported], we just mimic the structure here. + */ + private suspend fun listenForChangesToSatelliteSupport(sm: SatelliteManager) { + telephonyProcessCrashedEvent.collectLatest { + satelliteIsSupportedCallback.collect { supported -> + if (supported) { + satelliteSupport.value = Supported(sm) + } else { + satelliteSupport.value = NotSupported + } + } + } + } + + /** + * Callback version of [checkSatelliteSupported]. This flow should be retried on the same + * [telephonyProcessCrashedEvent] signal, but does not require a [SupportedSatelliteManager], + * since it specifically watches for satellite support. + */ + private val satelliteIsSupportedCallback: Flow<Boolean> = + if (satelliteManager == null) { + flowOf(false) + } else { + conflatedCallbackFlow { + val callback = SatelliteSupportedStateCallback { supported -> + logBuffer.i { + "onSatelliteSupportedStateChanged: " + + "${if (supported) "supported" else "not supported"}" + } + trySend(supported) + } + + var registered = false + try { + satelliteManager.registerForSupportedStateChanged( + bgDispatcher.asExecutor(), + callback + ) + registered = true + } catch (e: Exception) { + logBuffer.e("error registering for supported state change", e) + } + + awaitClose { + if (registered) { + satelliteManager.unregisterForSupportedStateChanged(callback) + } + } + } + } + + /** + * Signal that we should start polling [checkIsSatelliteAllowed]. We only need to poll if there + * are active listeners to [isSatelliteAllowedForCurrentLocation] + */ + @SuppressWarnings("unused") + private fun isSatelliteAllowedHasListener(sm: SupportedSatelliteManager): Flow<Boolean> = + isSatelliteAllowedForCurrentLocation.subscriptionCount.map { it > 0 }.distinctUntilChanged() + override val connectionState = satelliteSupport .whenSupported( diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardStateController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardStateController.java index b07aa81de0a2..d210e93e36f1 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardStateController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardStateController.java @@ -18,13 +18,16 @@ package com.android.systemui.statusbar.policy; import android.app.IActivityTaskManager; +import com.android.systemui.Dumpable; import com.android.systemui.statusbar.StatusBarState; import com.android.systemui.statusbar.policy.KeyguardStateController.Callback; +import java.io.PrintWriter; + /** * Source of truth for keyguard state: If locked, occluded, has password, trusted etc. */ -public interface KeyguardStateController extends CallbackController<Callback> { +public interface KeyguardStateController extends CallbackController<Callback>, Dumpable { /** * If the device is locked or unlocked. @@ -40,6 +43,8 @@ public interface KeyguardStateController extends CallbackController<Callback> { return isShowing() && !isOccluded(); } + default void dump(PrintWriter pw, String[] args) { } + /** * If the keyguard is showing. This includes when it's occluded by an activity, and when * the device is asleep or in always on mode, except when the screen timed out and the user diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardStateControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardStateControllerImpl.java index 886010ccbf16..c256e6430af9 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardStateControllerImpl.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardStateControllerImpl.java @@ -36,7 +36,6 @@ import com.android.internal.widget.LockPatternUtils; import com.android.keyguard.KeyguardUpdateMonitor; import com.android.keyguard.KeyguardUpdateMonitorCallback; import com.android.keyguard.logging.KeyguardUpdateMonitorLogger; -import com.android.systemui.Dumpable; import com.android.systemui.dagger.SysUISingleton; import com.android.systemui.dump.DumpManager; import com.android.systemui.flags.FeatureFlags; @@ -57,7 +56,7 @@ import javax.inject.Inject; * */ @SysUISingleton -public class KeyguardStateControllerImpl implements KeyguardStateController, Dumpable { +public class KeyguardStateControllerImpl implements KeyguardStateController { private static final boolean DEBUG_AUTH_WITH_ADB = false; private static final String AUTH_BROADCAST_KEY = "debug_trigger_auth"; diff --git a/packages/SystemUI/src/com/android/systemui/util/concurrency/SysUIConcurrencyModule.kt b/packages/SystemUI/src/com/android/systemui/util/concurrency/SysUIConcurrencyModule.kt index 83e3428bb95f..a7abb6b5f1d3 100644 --- a/packages/SystemUI/src/com/android/systemui/util/concurrency/SysUIConcurrencyModule.kt +++ b/packages/SystemUI/src/com/android/systemui/util/concurrency/SysUIConcurrencyModule.kt @@ -19,6 +19,7 @@ import android.os.Handler import android.os.HandlerThread import android.os.Looper import android.os.Process +import android.view.Choreographer import com.android.systemui.Dependency import com.android.systemui.Flags import com.android.systemui.dagger.SysUISingleton @@ -31,6 +32,12 @@ import dagger.Module import dagger.Provides import java.util.concurrent.Executor import javax.inject.Named +import javax.inject.Qualifier + +@Qualifier +@MustBeDocumented +@Retention(AnnotationRetention.RUNTIME) +annotation class BackPanelUiThread /** Dagger Module for classes found within the concurrent package. */ @Module @@ -106,6 +113,39 @@ object SysUIConcurrencyModule { return looper } + @Provides + @SysUISingleton + @BackPanelUiThread + fun provideBackPanelUiThreadContext( + @Main mainLooper: Looper, + @Main mainHandler: Handler, + @Main mainExecutor: Executor + ): UiThreadContext { + return if (Flags.edgeBackGestureHandlerThread()) { + val thread = + HandlerThread("BackPanelUiThread", Process.THREAD_PRIORITY_DISPLAY).apply { + start() + looper.setSlowLogThresholdMs( + LONG_SLOW_DISPATCH_THRESHOLD, + LONG_SLOW_DELIVERY_THRESHOLD + ) + } + UiThreadContext( + thread.looper, + thread.threadHandler, + thread.threadExecutor, + thread.threadHandler.runWithScissors { Choreographer.getInstance() } + ) + } else { + UiThreadContext( + mainLooper, + mainHandler, + mainExecutor, + mainHandler.runWithScissors { Choreographer.getInstance() } + ) + } + } + /** * Background Handler. * diff --git a/packages/SystemUI/src/com/android/systemui/util/concurrency/UiThreadContext.kt b/packages/SystemUI/src/com/android/systemui/util/concurrency/UiThreadContext.kt new file mode 100644 index 000000000000..8c8c686dddcc --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/util/concurrency/UiThreadContext.kt @@ -0,0 +1,51 @@ +/* + * Copyright (C) 2024 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.util.concurrency + +import android.os.Handler +import android.os.Looper +import android.view.Choreographer +import com.android.systemui.util.Assert +import java.util.concurrent.Executor +import java.util.concurrent.atomic.AtomicReference + +private const val DEFAULT_TIMEOUT = 150L + +class UiThreadContext( + val looper: Looper, + val handler: Handler, + val executor: Executor, + val choreographer: Choreographer +) { + fun isCurrentThread() { + Assert.isCurrentThread(looper) + } + + fun <T> runWithScissors(block: () -> T): T { + return handler.runWithScissors(block) + } + + fun runWithScissors(block: Runnable) { + handler.runWithScissors(block, DEFAULT_TIMEOUT) + } +} + +fun <T> Handler.runWithScissors(block: () -> T): T { + val returnedValue = AtomicReference<T>() + runWithScissors({ returnedValue.set(block()) }, DEFAULT_TIMEOUT) + return returnedValue.get()!! +} diff --git a/packages/SystemUI/src/com/android/systemui/volume/domain/interactor/AudioOutputInteractor.kt b/packages/SystemUI/src/com/android/systemui/volume/domain/interactor/AudioOutputInteractor.kt index c6b0dc542087..e0f64b4f7dc2 100644 --- a/packages/SystemUI/src/com/android/systemui/volume/domain/interactor/AudioOutputInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/volume/domain/interactor/AudioOutputInteractor.kt @@ -18,8 +18,6 @@ package com.android.systemui.volume.domain.interactor import android.bluetooth.BluetoothAdapter import android.media.AudioDeviceInfo -import android.media.AudioDeviceInfo.TYPE_WIRED_HEADPHONES -import android.media.AudioDeviceInfo.TYPE_WIRED_HEADSET import com.android.settingslib.bluetooth.CachedBluetoothDevice import com.android.settingslib.bluetooth.LocalBluetoothManager import com.android.settingslib.media.BluetoothMediaDevice @@ -81,30 +79,32 @@ constructor( val isInAudioSharing: Flow<Boolean> = audioSharingRepository.inAudioSharing private fun AudioDeviceInfo.toAudioOutputDevice(): AudioOutputDevice { - if (type == TYPE_WIRED_HEADPHONES || type == TYPE_WIRED_HEADSET) { - return AudioOutputDevice.Wired( - name = productName.toString(), - icon = deviceIconInteractor.loadIcon(type), - ) - } - val cachedBluetoothDevice: CachedBluetoothDevice? = - if (address.isEmpty() || localBluetoothManager == null || bluetoothAdapter == null) { - null - } else { - val remoteDevice = bluetoothAdapter.getRemoteDevice(address) - localBluetoothManager.cachedDeviceManager.findDevice(remoteDevice) + if ( + BluetoothAdapter.checkBluetoothAddress(address) && + localBluetoothManager != null && + bluetoothAdapter != null + ) { + val remoteDevice = bluetoothAdapter.getRemoteDevice(address) + localBluetoothManager.cachedDeviceManager.findDevice(remoteDevice)?.let { + device: CachedBluetoothDevice -> + return AudioOutputDevice.Bluetooth( + name = device.name, + icon = deviceIconInteractor.loadIcon(device), + cachedBluetoothDevice = device, + ) } - return cachedBluetoothDevice?.let { - AudioOutputDevice.Bluetooth( - name = it.name, - icon = deviceIconInteractor.loadIcon(it), - cachedBluetoothDevice = it, - ) } - ?: AudioOutputDevice.BuiltIn( + // Built-in device has an empty address + if (address.isNotEmpty()) { + return AudioOutputDevice.Wired( name = productName.toString(), icon = deviceIconInteractor.loadIcon(type), ) + } + return AudioOutputDevice.BuiltIn( + name = productName.toString(), + icon = deviceIconInteractor.loadIcon(type), + ) } private fun MediaDevice.toAudioOutputDevice(): AudioOutputDevice { @@ -115,7 +115,8 @@ constructor( icon = icon, cachedBluetoothDevice = cachedDevice, ) - deviceType == MediaDeviceType.TYPE_3POINT5_MM_AUDIO_DEVICE -> + deviceType == MediaDeviceType.TYPE_3POINT5_MM_AUDIO_DEVICE || + deviceType == MediaDeviceType.TYPE_USB_C_AUDIO_DEVICE -> AudioOutputDevice.Wired( name = name, icon = icon, diff --git a/packages/SystemUI/src/com/android/systemui/volume/ui/navigation/VolumeNavigator.kt b/packages/SystemUI/src/com/android/systemui/volume/ui/navigation/VolumeNavigator.kt index 99f956489bc3..3da725b9a51f 100644 --- a/packages/SystemUI/src/com/android/systemui/volume/ui/navigation/VolumeNavigator.kt +++ b/packages/SystemUI/src/com/android/systemui/volume/ui/navigation/VolumeNavigator.kt @@ -98,12 +98,12 @@ constructor( private fun showNewVolumePanel() { activityStarter.dismissKeyguardThenExecute( - { + /* action = */ { volumePanelGlobalStateInteractor.setVisible(true) false }, - {}, - true + /* cancel = */ {}, + /* afterKeyguardGone = */ true, ) } diff --git a/packages/SystemUI/tests/src/com/android/keyguard/CarrierTextManagerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/CarrierTextManagerTest.java index 8c4179d385ba..0a3225eecbe4 100644 --- a/packages/SystemUI/tests/src/com/android/keyguard/CarrierTextManagerTest.java +++ b/packages/SystemUI/tests/src/com/android/keyguard/CarrierTextManagerTest.java @@ -161,8 +161,11 @@ public class CarrierTextManagerTest extends SysuiTestCase { doAnswer(this::checkMainThread).when(mKeyguardUpdateMonitor) .removeCallback(any(KeyguardUpdateMonitorCallback.class)); - mCarrierTextCallbackInfo = new CarrierTextManager.CarrierTextCallbackInfo("", - new CharSequence[]{}, false, new int[]{}); + mCarrierTextCallbackInfo = new CarrierTextManager.CarrierTextCallbackInfo( + /* carrierText= */ "", + /* listOfCarriers= */ new CharSequence[]{}, + /* anySimReady= */ false, + /* subscriptionIds= */ new int[]{}); when(mTelephonyManager.getSupportedModemCount()).thenReturn(3); when(mTelephonyManager.getActiveModemCount()).thenReturn(3); @@ -473,7 +476,7 @@ public class CarrierTextManagerTest extends SysuiTestCase { } @Test - public void carrierText_satelliteTextNull_notUsed() { + public void carrierText_satelliteTextNull_isSatelliteFalse_textNotUsed() { reset(mCarrierTextCallback); List<SubscriptionInfo> list = new ArrayList<>(); list.add(TEST_SUBSCRIPTION); @@ -491,12 +494,38 @@ public class CarrierTextManagerTest extends SysuiTestCase { CarrierTextManager.CarrierTextCallbackInfo.class); FakeExecutor.exhaustExecutors(mMainExecutor, mBgExecutor); - // THEN the default subscription carrier text is used + // THEN satellite mode is false and the default subscription carrier text is used verify(mCarrierTextCallback).updateCarrierInfo(captor.capture()); + assertThat(captor.getValue().isInSatelliteMode).isFalse(); assertThat(captor.getValue().carrierText).isEqualTo(TEST_CARRIER); } @Test + public void carrierText_hasSatelliteText_isSatelliteTrue_textUsed() { + reset(mCarrierTextCallback); + List<SubscriptionInfo> list = new ArrayList<>(); + list.add(TEST_SUBSCRIPTION); + when(mKeyguardUpdateMonitor.getSimState(anyInt())).thenReturn( + TelephonyManager.SIM_STATE_READY); + when(mKeyguardUpdateMonitor.getFilteredSubscriptionInfo()).thenReturn(list); + mKeyguardUpdateMonitor.mServiceStates = new HashMap<>(); + + // WHEN the satellite text is non-null + mSatelliteViewModel.getCarrierText().setValue("Satellite Test Text"); + mTestScope.getTestScheduler().runCurrent(); + + ArgumentCaptor<CarrierTextManager.CarrierTextCallbackInfo> captor = + ArgumentCaptor.forClass( + CarrierTextManager.CarrierTextCallbackInfo.class); + FakeExecutor.exhaustExecutors(mMainExecutor, mBgExecutor); + + // THEN satellite mode is true and the satellite text is used + verify(mCarrierTextCallback).updateCarrierInfo(captor.capture()); + assertThat(captor.getValue().isInSatelliteMode).isTrue(); + assertThat(captor.getValue().carrierText).isEqualTo("Satellite Test Text"); + } + + @Test public void carrierText_satelliteTextUpdates_autoTriggersCallback() { reset(mCarrierTextCallback); List<SubscriptionInfo> list = new ArrayList<>(); @@ -517,6 +546,7 @@ public class CarrierTextManagerTest extends SysuiTestCase { FakeExecutor.exhaustExecutors(mMainExecutor, mBgExecutor); verify(mCarrierTextCallback).updateCarrierInfo(captor.capture()); // AND use the satellite text as the carrier text + assertThat(captor.getValue().isInSatelliteMode).isTrue(); assertThat(captor.getValue().carrierText).isEqualTo("Test satellite text"); // WHEN the satellite text is reset to null @@ -528,6 +558,7 @@ public class CarrierTextManagerTest extends SysuiTestCase { // that doesn't include the satellite info FakeExecutor.exhaustExecutors(mMainExecutor, mBgExecutor); verify(mCarrierTextCallback).updateCarrierInfo(captor.capture()); + assertThat(captor.getValue().isInSatelliteMode).isFalse(); assertThat(captor.getValue().carrierText).isEqualTo(TEST_CARRIER); } @@ -566,10 +597,11 @@ public class CarrierTextManagerTest extends SysuiTestCase { mCarrierTextManager.setListening(mCarrierTextCallback); // THEN we should automatically re-trigger #updateCarrierText and get callback info - // that includes the new satellite text + // that includes the new satellite state and text mTestScope.getTestScheduler().runCurrent(); FakeExecutor.exhaustExecutors(mMainExecutor, mBgExecutor); verify(mCarrierTextCallback).updateCarrierInfo(captor.capture()); + assertThat(captor.getValue().isInSatelliteMode).isTrue(); assertThat(captor.getValue().carrierText).isEqualTo("New satellite text"); } diff --git a/packages/SystemUI/tests/src/com/android/systemui/globalactions/GlobalActionsGridLayoutTest.java b/packages/SystemUI/tests/src/com/android/systemui/globalactions/GlobalActionsGridLayoutTest.java index 84b1c00f29e6..87dd9b229598 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/globalactions/GlobalActionsGridLayoutTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/globalactions/GlobalActionsGridLayoutTest.java @@ -21,11 +21,11 @@ import static junit.framework.Assert.assertEquals; import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.spy; -import android.testing.AndroidTestingRunner; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; +import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.filters.SmallTest; import com.android.systemui.SysuiTestCase; @@ -40,7 +40,7 @@ import org.junit.runner.RunWith; * Tests for {@link ListGridLayout}. */ @SmallTest -@RunWith(AndroidTestingRunner.class) +@RunWith(AndroidJUnit4.class) public class GlobalActionsGridLayoutTest extends SysuiTestCase { private GlobalActionsGridLayout mGridLayout; diff --git a/packages/SystemUI/tests/src/com/android/systemui/globalactions/GlobalActionsLayoutTest.java b/packages/SystemUI/tests/src/com/android/systemui/globalactions/GlobalActionsLayoutTest.java index 16dcd659b3f9..ea0f4d247a47 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/globalactions/GlobalActionsLayoutTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/globalactions/GlobalActionsLayoutTest.java @@ -24,11 +24,11 @@ import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.spy; import android.content.Context; -import android.testing.AndroidTestingRunner; import android.util.AttributeSet; import android.view.View; import android.view.ViewGroup; +import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.filters.SmallTest; import com.android.systemui.MultiListLayout; @@ -44,7 +44,7 @@ import java.util.ArrayList; * Tests for {@link ListGridLayout}. */ @SmallTest -@RunWith(AndroidTestingRunner.class) +@RunWith(AndroidJUnit4.class) public class GlobalActionsLayoutTest extends SysuiTestCase { private TestLayout mLayout; diff --git a/packages/SystemUI/tests/src/com/android/systemui/globalactions/ListGridLayoutTest.java b/packages/SystemUI/tests/src/com/android/systemui/globalactions/ListGridLayoutTest.java index 4c65b904bbc1..a10457f704ec 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/globalactions/ListGridLayoutTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/globalactions/ListGridLayoutTest.java @@ -18,11 +18,11 @@ package com.android.systemui.globalactions; import static junit.framework.Assert.assertEquals; -import android.testing.AndroidTestingRunner; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; +import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.filters.SmallTest; import com.android.systemui.SysuiTestCase; @@ -36,7 +36,7 @@ import org.junit.runner.RunWith; * Tests for {@link ListGridLayout}. */ @SmallTest -@RunWith(AndroidTestingRunner.class) +@RunWith(AndroidJUnit4.class) public class ListGridLayoutTest extends SysuiTestCase { private ListGridLayout mListGridLayout; diff --git a/packages/SystemUI/tests/src/com/android/systemui/globalactions/ShutdownUiTest.java b/packages/SystemUI/tests/src/com/android/systemui/globalactions/ShutdownUiTest.java index 28c01ad3a043..73509e2da520 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/globalactions/ShutdownUiTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/globalactions/ShutdownUiTest.java @@ -26,8 +26,8 @@ import android.net.platform.flags.Flags; import android.os.PowerManager; import android.platform.test.annotations.DisableFlags; import android.platform.test.annotations.EnableFlags; -import android.testing.AndroidTestingRunner; +import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.filters.SmallTest; import com.android.internal.R; @@ -41,7 +41,7 @@ import org.mockito.Mock; import org.mockito.MockitoAnnotations; @SmallTest -@RunWith(AndroidTestingRunner.class) +@RunWith(AndroidJUnit4.class) public class ShutdownUiTest extends SysuiTestCase { ShutdownUi mShutdownUi; diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyboard/backlight/domain/interactor/KeyboardBacklightInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyboard/backlight/domain/interactor/KeyboardBacklightInteractorTest.kt index d7a0d5b5e062..64cd09128373 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyboard/backlight/domain/interactor/KeyboardBacklightInteractorTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/keyboard/backlight/domain/interactor/KeyboardBacklightInteractorTest.kt @@ -17,6 +17,7 @@ package com.android.systemui.keyboard.backlight.domain.interactor +import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.coroutines.collectLastValue @@ -29,11 +30,10 @@ import kotlinx.coroutines.test.runTest import org.junit.Before import org.junit.Test import org.junit.runner.RunWith -import org.junit.runners.JUnit4 @OptIn(ExperimentalCoroutinesApi::class) @SmallTest -@RunWith(JUnit4::class) +@RunWith(AndroidJUnit4::class) class KeyboardBacklightInteractorTest : SysuiTestCase() { private val keyboardRepository = FakeKeyboardRepository() diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyboard/backlight/ui/KeyboardBacklightDialogCoordinatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyboard/backlight/ui/KeyboardBacklightDialogCoordinatorTest.kt index 7207fbf83bd7..47261a935725 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyboard/backlight/ui/KeyboardBacklightDialogCoordinatorTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/keyboard/backlight/ui/KeyboardBacklightDialogCoordinatorTest.kt @@ -17,6 +17,7 @@ package com.android.systemui.keyboard.backlight.ui +import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.keyboard.backlight.domain.interactor.KeyboardBacklightInteractor @@ -37,7 +38,6 @@ import kotlinx.coroutines.test.runTest import org.junit.Before import org.junit.Test import org.junit.runner.RunWith -import org.junit.runners.JUnit4 import org.mockito.Mock import org.mockito.Mockito.never import org.mockito.Mockito.times @@ -46,7 +46,7 @@ import org.mockito.MockitoAnnotations @OptIn(ExperimentalCoroutinesApi::class) @SmallTest -@RunWith(JUnit4::class) +@RunWith(AndroidJUnit4::class) class KeyboardBacklightDialogCoordinatorTest : SysuiTestCase() { @Mock private lateinit var accessibilityManagerWrapper: AccessibilityManagerWrapper diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyboard/data/repository/KeyboardRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyboard/data/repository/KeyboardRepositoryTest.kt index 4410e68e64aa..53bcf865b829 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyboard/data/repository/KeyboardRepositoryTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/keyboard/data/repository/KeyboardRepositoryTest.kt @@ -21,6 +21,7 @@ import android.hardware.input.InputManager import android.hardware.input.InputManager.KeyboardBacklightListener import android.hardware.input.KeyboardBacklightState import android.view.InputDevice +import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.coroutines.FlowValue @@ -44,7 +45,6 @@ import kotlinx.coroutines.test.runTest import org.junit.Before import org.junit.Test import org.junit.runner.RunWith -import org.junit.runners.JUnit4 import org.mockito.ArgumentCaptor import org.mockito.Captor import org.mockito.Mock @@ -53,7 +53,7 @@ import org.mockito.MockitoAnnotations @OptIn(ExperimentalCoroutinesApi::class) @SmallTest -@RunWith(JUnit4::class) +@RunWith(AndroidJUnit4::class) class KeyboardRepositoryTest : SysuiTestCase() { @Captor diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyboard/shortcut/ui/ShortcutHelperActivityStarterTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyboard/shortcut/ui/ShortcutHelperActivityStarterTest.kt index 05a2ca20037b..0757ea156bbf 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyboard/shortcut/ui/ShortcutHelperActivityStarterTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/keyboard/shortcut/ui/ShortcutHelperActivityStarterTest.kt @@ -17,6 +17,7 @@ package com.android.systemui.keyboard.shortcut.ui import android.content.Intent +import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.keyboard.shortcut.fakeShortcutHelperStartActivity @@ -33,11 +34,10 @@ import kotlinx.coroutines.test.UnconfinedTestDispatcher import kotlinx.coroutines.test.runTest import org.junit.Test import org.junit.runner.RunWith -import org.junit.runners.JUnit4 @OptIn(ExperimentalCoroutinesApi::class) @SmallTest -@RunWith(JUnit4::class) +@RunWith(AndroidJUnit4::class) class ShortcutHelperActivityStarterTest : SysuiTestCase() { private val kosmos = diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyboard/shortcut/ui/viewmodel/ShortcutHelperViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyboard/shortcut/ui/viewmodel/ShortcutHelperViewModelTest.kt index 44a8904f50da..34b2aaf8b57a 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyboard/shortcut/ui/viewmodel/ShortcutHelperViewModelTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/keyboard/shortcut/ui/viewmodel/ShortcutHelperViewModelTest.kt @@ -16,6 +16,7 @@ package com.android.systemui.keyboard.shortcut.ui.viewmodel +import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.coroutines.collectLastValue @@ -34,11 +35,10 @@ import kotlinx.coroutines.test.UnconfinedTestDispatcher import kotlinx.coroutines.test.runTest import org.junit.Test import org.junit.runner.RunWith -import org.junit.runners.JUnit4 @OptIn(ExperimentalCoroutinesApi::class) @SmallTest -@RunWith(JUnit4::class) +@RunWith(AndroidJUnit4::class) class ShortcutHelperViewModelTest : SysuiTestCase() { private val kosmos = diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyboard/stickykeys/ui/StickyKeysIndicatorCoordinatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyboard/stickykeys/ui/StickyKeysIndicatorCoordinatorTest.kt index 59d8fc3d3fd0..9a721fa26df3 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyboard/stickykeys/ui/StickyKeysIndicatorCoordinatorTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/keyboard/stickykeys/ui/StickyKeysIndicatorCoordinatorTest.kt @@ -17,6 +17,7 @@ package com.android.systemui.keyboard.stickykeys.ui import android.app.Dialog +import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.keyboard.data.repository.FakeStickyKeysRepository @@ -36,13 +37,12 @@ import kotlinx.coroutines.test.runCurrent import org.junit.Before import org.junit.Test import org.junit.runner.RunWith -import org.junit.runners.JUnit4 import org.mockito.Mockito.verify import org.mockito.Mockito.verifyZeroInteractions @OptIn(ExperimentalCoroutinesApi::class) @SmallTest -@RunWith(JUnit4::class) +@RunWith(AndroidJUnit4::class) class StickyKeysIndicatorCoordinatorTest : SysuiTestCase() { private lateinit var coordinator: StickyKeysIndicatorCoordinator diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyboard/stickykeys/ui/viewmodel/StickyKeysIndicatorViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyboard/stickykeys/ui/viewmodel/StickyKeysIndicatorViewModelTest.kt index d14d72d90a31..9d9e5be62351 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyboard/stickykeys/ui/viewmodel/StickyKeysIndicatorViewModelTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/keyboard/stickykeys/ui/viewmodel/StickyKeysIndicatorViewModelTest.kt @@ -19,6 +19,7 @@ package com.android.systemui.keyboard.stickykeys.ui.viewmodel import android.hardware.input.InputManager import android.hardware.input.StickyModifierState import android.provider.Settings.Secure.ACCESSIBILITY_STICKY_KEYS +import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.coroutines.collectLastValue @@ -47,14 +48,13 @@ import kotlinx.coroutines.test.runTest import org.junit.Before import org.junit.Test import org.junit.runner.RunWith -import org.junit.runners.JUnit4 import org.mockito.ArgumentCaptor import org.mockito.Mockito.verify import org.mockito.Mockito.verifyZeroInteractions @OptIn(ExperimentalCoroutinesApi::class) @SmallTest -@RunWith(JUnit4::class) +@RunWith(AndroidJUnit4::class) class StickyKeysIndicatorViewModelTest : SysuiTestCase() { private val dispatcher = StandardTestDispatcher() diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardIndicationRotateTextViewControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardIndicationRotateTextViewControllerTest.java index 5b836b6b06b9..925ace26871f 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardIndicationRotateTextViewControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardIndicationRotateTextViewControllerTest.java @@ -36,9 +36,9 @@ import static org.mockito.Mockito.when; import android.content.res.ColorStateList; import android.graphics.Color; -import android.testing.AndroidTestingRunner; import android.testing.TestableLooper.RunWithLooper; +import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.filters.SmallTest; import com.android.keyguard.logging.KeyguardLogger; @@ -56,7 +56,7 @@ import org.mockito.Captor; import org.mockito.Mock; import org.mockito.MockitoAnnotations; -@RunWith(AndroidTestingRunner.class) +@RunWith(AndroidJUnit4.class) @RunWithLooper @SmallTest public class KeyguardIndicationRotateTextViewControllerTest extends SysuiTestCase { diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardIndicationTest.java b/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardIndicationTest.java index b44fb8e61c32..325bae15b18e 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardIndicationTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardIndicationTest.java @@ -24,10 +24,10 @@ import android.content.res.ColorStateList; import android.graphics.Canvas; import android.graphics.ColorFilter; import android.graphics.drawable.Drawable; -import android.testing.AndroidTestingRunner; import androidx.annotation.NonNull; import androidx.annotation.Nullable; +import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.filters.SmallTest; import com.android.systemui.SysuiTestCase; @@ -35,7 +35,7 @@ import com.android.systemui.SysuiTestCase; import org.junit.Test; import org.junit.runner.RunWith; -@RunWith(AndroidTestingRunner.class) +@RunWith(AndroidJUnit4.class) @SmallTest public class KeyguardIndicationTest extends SysuiTestCase { diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardSliceProviderTest.java b/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardSliceProviderTest.java index ce8028c8b37a..909acca12551 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardSliceProviderTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardSliceProviderTest.java @@ -34,7 +34,6 @@ import android.media.session.PlaybackState; import android.net.Uri; import android.os.Handler; import android.provider.Settings; -import android.testing.AndroidTestingRunner; import android.testing.TestableLooper; import android.testing.TestableLooper.RunWithLooper; @@ -44,6 +43,7 @@ import androidx.slice.SliceProvider; import androidx.slice.SliceSpecs; import androidx.slice.builders.ListBuilder; import androidx.slice.core.SliceQuery; +import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.filters.SmallTest; import com.android.keyguard.KeyguardUpdateMonitor; @@ -72,7 +72,7 @@ import java.util.HashSet; import java.util.concurrent.TimeUnit; @SmallTest -@RunWith(AndroidTestingRunner.class) +@RunWith(AndroidJUnit4.class) @RunWithLooper public class KeyguardSliceProviderTest extends SysuiTestCase { diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardUnlockAnimationControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardUnlockAnimationControllerTest.kt index 6ebda4db808f..128dd2353042 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardUnlockAnimationControllerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardUnlockAnimationControllerTest.kt @@ -7,7 +7,6 @@ import android.graphics.Point import android.graphics.Rect import android.os.PowerManager import android.platform.test.annotations.DisableFlags -import android.testing.AndroidTestingRunner import android.testing.TestableLooper.RunWithLooper import android.view.RemoteAnimationTarget import android.view.SurfaceControl @@ -15,6 +14,7 @@ import android.view.SyncRtSurfaceTransactionApplier import android.view.View import android.view.ViewRootImpl import android.view.WindowManager +import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.keyguard.KeyguardViewController import com.android.systemui.Flags @@ -46,7 +46,7 @@ import org.mockito.Mockito.verifyNoMoreInteractions import org.mockito.MockitoAnnotations import java.util.function.Predicate -@RunWith(AndroidTestingRunner::class) +@RunWith(AndroidJUnit4::class) @RunWithLooper @SmallTest class KeyguardUnlockAnimationControllerTest : SysuiTestCase() { diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ResourceTrimmerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ResourceTrimmerTest.kt index 977116e812ac..144e634ae519 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ResourceTrimmerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ResourceTrimmerTest.kt @@ -1,20 +1,15 @@ package com.android.systemui.keyguard import android.content.ComponentCallbacks2 -import android.graphics.HardwareRenderer -import android.platform.test.annotations.DisableFlags -import android.platform.test.annotations.EnableFlags import android.platform.test.flag.junit.SetFlagsRule -import android.testing.AndroidTestingRunner +import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.flags.DisableSceneContainer import com.android.systemui.flags.EnableSceneContainer -import com.android.systemui.flags.Flags import com.android.systemui.flags.fakeFeatureFlagsClassic import com.android.systemui.keyguard.data.repository.fakeKeyguardRepository import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository -import com.android.systemui.keyguard.domain.interactor.KeyguardInteractorFactory import com.android.systemui.keyguard.domain.interactor.keyguardTransitionInteractor import com.android.systemui.keyguard.shared.model.KeyguardState import com.android.systemui.kosmos.testDispatcher @@ -26,7 +21,6 @@ import com.android.systemui.scene.data.repository.setSceneTransition import com.android.systemui.scene.domain.interactor.sceneInteractor import com.android.systemui.scene.shared.model.Scenes import com.android.systemui.testKosmos -import com.android.systemui.util.mockito.any import com.android.systemui.utils.GlobalWindowManager import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.test.runCurrent @@ -43,7 +37,7 @@ import org.mockito.Mockito.verifyZeroInteractions import org.mockito.MockitoAnnotations @OptIn(ExperimentalCoroutinesApi::class) -@RunWith(AndroidTestingRunner::class) +@RunWith(AndroidJUnit4::class) @SmallTest class ResourceTrimmerTest : SysuiTestCase() { val kosmos = testKosmos() @@ -62,52 +56,21 @@ class ResourceTrimmerTest : SysuiTestCase() { @Before fun setUp() { MockitoAnnotations.initMocks(this) - featureFlags.set(Flags.TRIM_FONT_CACHES_AT_UNLOCK, true) keyguardRepository.setDozeAmount(0f) keyguardRepository.setKeyguardGoingAway(false) - - val withDeps = - KeyguardInteractorFactory.create( - featureFlags = featureFlags, - repository = keyguardRepository, - ) - val keyguardInteractor = withDeps.keyguardInteractor resourceTrimmer = ResourceTrimmer( - keyguardInteractor, - powerInteractor = powerInteractor, keyguardTransitionInteractor = kosmos.keyguardTransitionInteractor, globalWindowManager = globalWindowManager, applicationScope = testScope.backgroundScope, bgDispatcher = kosmos.testDispatcher, - featureFlags = featureFlags, sceneInteractor = kosmos.sceneInteractor, ) resourceTrimmer.start() } @Test - @EnableFlags(com.android.systemui.Flags.FLAG_TRIM_RESOURCES_WITH_BACKGROUND_TRIM_AT_LOCK) - fun noChange_noOutputChanges() = - testScope.runTest { - testScope.runCurrent() - verifyZeroInteractions(globalWindowManager) - } - - @Test - @EnableFlags(com.android.systemui.Flags.FLAG_TRIM_RESOURCES_WITH_BACKGROUND_TRIM_AT_LOCK) - fun dozeAodDisabled_sleep_trimsMemory() = - testScope.runTest { - powerInteractor.setAsleepForTest() - testScope.runCurrent() - verify(globalWindowManager, times(1)) - .trimMemory(ComponentCallbacks2.TRIM_MEMORY_UI_HIDDEN) - verify(globalWindowManager, times(1)).trimCaches(HardwareRenderer.CACHE_TRIM_ALL) - } - - @Test - @DisableFlags(com.android.systemui.Flags.FLAG_TRIM_RESOURCES_WITH_BACKGROUND_TRIM_AT_LOCK) - fun dozeAodDisabled_flagDisabled_sleep_doesntTrimMemory() = + fun dozeAodDisabled_sleep_doesntTrimMemory() = testScope.runTest { powerInteractor.setAsleepForTest() testScope.runCurrent() @@ -115,8 +78,7 @@ class ResourceTrimmerTest : SysuiTestCase() { } @Test - @DisableFlags(com.android.systemui.Flags.FLAG_TRIM_RESOURCES_WITH_BACKGROUND_TRIM_AT_LOCK) - fun dozeEnabled_flagDisabled_sleepWithFullDozeAmount_doesntTrimMemory() = + fun dozeEnabled_sleepWithFullDozeAmount_doesntTrimMemory() = testScope.runTest { keyguardRepository.setDreaming(true) keyguardRepository.setDozeAmount(1f) @@ -126,20 +88,6 @@ class ResourceTrimmerTest : SysuiTestCase() { } @Test - @EnableFlags(com.android.systemui.Flags.FLAG_TRIM_RESOURCES_WITH_BACKGROUND_TRIM_AT_LOCK) - fun dozeEnabled_sleepWithFullDozeAmount_trimsMemory() = - testScope.runTest { - keyguardRepository.setDreaming(true) - keyguardRepository.setDozeAmount(1f) - powerInteractor.setAsleepForTest() - testScope.runCurrent() - verify(globalWindowManager, times(1)) - .trimMemory(ComponentCallbacks2.TRIM_MEMORY_UI_HIDDEN) - verify(globalWindowManager, times(1)).trimCaches(HardwareRenderer.CACHE_TRIM_ALL) - } - - @Test - @EnableFlags(com.android.systemui.Flags.FLAG_TRIM_RESOURCES_WITH_BACKGROUND_TRIM_AT_LOCK) fun dozeEnabled_sleepWithoutFullDozeAmount_doesntTrimMemory() = testScope.runTest { keyguardRepository.setDreaming(true) @@ -150,34 +98,6 @@ class ResourceTrimmerTest : SysuiTestCase() { } @Test - @EnableFlags(com.android.systemui.Flags.FLAG_TRIM_RESOURCES_WITH_BACKGROUND_TRIM_AT_LOCK) - fun aodEnabled_sleepWithFullDozeAmount_trimsMemoryOnce() { - testScope.runTest { - keyguardRepository.setDreaming(true) - keyguardRepository.setDozeAmount(0f) - powerInteractor.setAsleepForTest() - - testScope.runCurrent() - verifyZeroInteractions(globalWindowManager) - - generateSequence(0f) { it + 0.1f } - .takeWhile { it < 1f } - .forEach { - keyguardRepository.setDozeAmount(it) - testScope.runCurrent() - } - verifyZeroInteractions(globalWindowManager) - - keyguardRepository.setDozeAmount(1f) - testScope.runCurrent() - verify(globalWindowManager, times(1)) - .trimMemory(ComponentCallbacks2.TRIM_MEMORY_UI_HIDDEN) - verify(globalWindowManager, times(1)).trimCaches(HardwareRenderer.CACHE_TRIM_ALL) - } - } - - @Test - @EnableFlags(com.android.systemui.Flags.FLAG_TRIM_RESOURCES_WITH_BACKGROUND_TRIM_AT_LOCK) fun aodEnabled_deviceWakesHalfWayThrough_doesNotTrimMemory() { testScope.runTest { keyguardRepository.setDreaming(true) @@ -209,7 +129,6 @@ class ResourceTrimmerTest : SysuiTestCase() { } @Test - @EnableFlags(com.android.systemui.Flags.FLAG_TRIM_RESOURCES_WITH_BACKGROUND_TRIM_AT_LOCK) @DisableSceneContainer fun keyguardTransitionsToGone_trimsFontCache() = testScope.runTest { @@ -220,12 +139,10 @@ class ResourceTrimmerTest : SysuiTestCase() { ) verify(globalWindowManager, times(1)) .trimMemory(ComponentCallbacks2.TRIM_MEMORY_UI_HIDDEN) - verify(globalWindowManager, times(1)).trimCaches(HardwareRenderer.CACHE_TRIM_FONT) verifyNoMoreInteractions(globalWindowManager) } @Test - @EnableFlags(com.android.systemui.Flags.FLAG_TRIM_RESOURCES_WITH_BACKGROUND_TRIM_AT_LOCK) @EnableSceneContainer fun keyguardTransitionsToGone_trimsFontCache_scene_container() = testScope.runTest { @@ -233,38 +150,6 @@ class ResourceTrimmerTest : SysuiTestCase() { verify(globalWindowManager, times(1)) .trimMemory(ComponentCallbacks2.TRIM_MEMORY_UI_HIDDEN) - verify(globalWindowManager, times(1)).trimCaches(HardwareRenderer.CACHE_TRIM_FONT) verifyNoMoreInteractions(globalWindowManager) } - - @Test - @EnableFlags(com.android.systemui.Flags.FLAG_TRIM_RESOURCES_WITH_BACKGROUND_TRIM_AT_LOCK) - @DisableSceneContainer - fun keyguardTransitionsToGone_flagDisabled_doesNotTrimFontCache() = - testScope.runTest { - featureFlags.set(Flags.TRIM_FONT_CACHES_AT_UNLOCK, false) - keyguardTransitionRepository.sendTransitionSteps( - from = KeyguardState.LOCKSCREEN, - to = KeyguardState.GONE, - testScope - ) - // Memory hidden should still be called. - verify(globalWindowManager, times(1)) - .trimMemory(ComponentCallbacks2.TRIM_MEMORY_UI_HIDDEN) - verify(globalWindowManager, times(0)).trimCaches(any()) - } - - @Test - @EnableFlags(com.android.systemui.Flags.FLAG_TRIM_RESOURCES_WITH_BACKGROUND_TRIM_AT_LOCK) - @EnableSceneContainer - fun keyguardTransitionsToGone_flagDisabled_doesNotTrimFontCache_scene_container() = - testScope.runTest { - featureFlags.set(Flags.TRIM_FONT_CACHES_AT_UNLOCK, false) - kosmos.setSceneTransition(Idle(Scenes.Gone)) - - // Memory hidden should still be called. - verify(globalWindowManager, times(1)) - .trimMemory(ComponentCallbacks2.TRIM_MEMORY_UI_HIDDEN) - verify(globalWindowManager, times(0)).trimCaches(any()) - } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ScreenLifecycleTest.java b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ScreenLifecycleTest.java index 70a0415d2e35..99ff2d405c6f 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ScreenLifecycleTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ScreenLifecycleTest.java @@ -21,8 +21,8 @@ import static org.mockito.Mockito.mock; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verifyNoMoreInteractions; -import android.testing.AndroidTestingRunner; +import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.filters.SmallTest; import com.android.systemui.SysuiTestCase; @@ -35,7 +35,7 @@ import org.junit.runner.RunWith; import java.io.ByteArrayOutputStream; import java.io.PrintWriter; -@RunWith(AndroidTestingRunner.class) +@RunWith(AndroidJUnit4.class) @SmallTest public class ScreenLifecycleTest extends SysuiTestCase { diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/WakefulnessLifecycleTest.java b/packages/SystemUI/tests/src/com/android/systemui/keyguard/WakefulnessLifecycleTest.java index 39a453da7f92..a9f7d0005624 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/WakefulnessLifecycleTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/WakefulnessLifecycleTest.java @@ -24,8 +24,8 @@ import static org.mockito.Mockito.verifyNoMoreInteractions; import android.app.IWallpaperManager; import android.os.PowerManager; -import android.testing.AndroidTestingRunner; +import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.filters.SmallTest; import com.android.systemui.SysuiTestCase; @@ -39,7 +39,7 @@ import org.junit.runner.RunWith; import java.io.ByteArrayOutputStream; import java.io.PrintWriter; -@RunWith(AndroidTestingRunner.class) +@RunWith(AndroidJUnit4.class) @SmallTest public class WakefulnessLifecycleTest extends SysuiTestCase { diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/WorkLockActivityTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/WorkLockActivityTest.kt index c7f1decac670..d435a4708b0a 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/WorkLockActivityTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/WorkLockActivityTest.kt @@ -25,8 +25,8 @@ import android.graphics.drawable.Drawable import android.os.Looper import android.os.UserHandle import android.os.UserManager +import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest -import androidx.test.runner.AndroidJUnit4 import com.android.systemui.SysuiTestCase import com.android.systemui.broadcast.BroadcastDispatcher import com.android.systemui.util.mockito.any diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardQuickAffordanceConfigTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardQuickAffordanceConfigTest.kt index bd4525b28f71..47bf653c699c 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardQuickAffordanceConfigTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardQuickAffordanceConfigTest.kt @@ -18,16 +18,16 @@ package com.android.systemui.keyguard.data.quickaffordance import android.content.Intent import android.net.Uri +import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.res.R import com.android.systemui.SysuiTestCase import com.google.common.truth.Truth.assertThat import org.junit.Test import org.junit.runner.RunWith -import org.junit.runners.JUnit4 @SmallTest -@RunWith(JUnit4::class) +@RunWith(AndroidJUnit4::class) class KeyguardQuickAffordanceConfigTest : SysuiTestCase() { @Test diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/MuteQuickAffordanceCoreStartableTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/MuteQuickAffordanceCoreStartableTest.kt index df1833e2506e..5fa194d95e28 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/MuteQuickAffordanceCoreStartableTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/MuteQuickAffordanceCoreStartableTest.kt @@ -203,4 +203,4 @@ class MuteQuickAffordanceCoreStartableTest : SysuiTestCase() { verify(ringerModeInternal).removeObserver(any()) coroutineContext.cancelChildren() } -}
\ No newline at end of file +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardBlueprintRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardBlueprintRepositoryTest.kt index f5b5261de139..972ca02548ec 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardBlueprintRepositoryTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardBlueprintRepositoryTest.kt @@ -19,6 +19,7 @@ package com.android.systemui.keyguard.data.repository +import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.common.ui.data.repository.ConfigurationRepository @@ -37,11 +38,10 @@ import kotlinx.coroutines.test.runTest import org.junit.Before import org.junit.Test import org.junit.runner.RunWith -import org.junit.runners.JUnit4 import org.mockito.Mock import org.mockito.MockitoAnnotations -@RunWith(JUnit4::class) +@RunWith(AndroidJUnit4::class) @SmallTest class KeyguardBlueprintRepositoryTest : SysuiTestCase() { private lateinit var underTest: KeyguardBlueprintRepository diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardClockRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardClockRepositoryTest.kt index 9aac8e270f92..af5187d03261 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardClockRepositoryTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardClockRepositoryTest.kt @@ -17,6 +17,7 @@ package com.android.systemui.keyguard.data.repository import android.provider.Settings +import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.keyguard.ClockEventController import com.android.systemui.SysuiTestCase @@ -36,11 +37,10 @@ import kotlinx.coroutines.test.TestScope import kotlinx.coroutines.test.runTest import org.junit.Before import org.junit.runner.RunWith -import org.junit.runners.JUnit4 import org.mockito.Mock import org.mockito.MockitoAnnotations -@RunWith(JUnit4::class) +@RunWith(AndroidJUnit4::class) @SmallTest class KeyguardClockRepositoryTest : SysuiTestCase() { diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardSmartspaceRepositoryImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardSmartspaceRepositoryImplTest.kt index a320845be6e4..8b8a6cbf6148 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardSmartspaceRepositoryImplTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardSmartspaceRepositoryImplTest.kt @@ -17,6 +17,7 @@ package com.android.systemui.keyguard.data.repository import android.provider.Settings +import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.coroutines.collectLastValue @@ -31,10 +32,9 @@ import kotlinx.coroutines.test.TestScope import kotlinx.coroutines.test.runTest import org.junit.Before import org.junit.runner.RunWith -import org.junit.runners.JUnit4 import org.mockito.MockitoAnnotations -@RunWith(JUnit4::class) +@RunWith(AndroidJUnit4::class) @SmallTest class KeyguardSmartspaceRepositoryImplTest : SysuiTestCase() { diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/DeviceEntrySideFpsOverlayInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/DeviceEntrySideFpsOverlayInteractorTest.kt index 8be1e7a2b3f1..a8271c1b7786 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/DeviceEntrySideFpsOverlayInteractorTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/DeviceEntrySideFpsOverlayInteractorTest.kt @@ -17,6 +17,7 @@ package com.android.systemui.keyguard.domain.interactor import android.os.Handler +import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.keyguard.KeyguardSecurityModel import com.android.keyguard.KeyguardUpdateMonitor @@ -56,7 +57,6 @@ import org.junit.Before import org.junit.Rule import org.junit.Test import org.junit.runner.RunWith -import org.junit.runners.JUnit4 import org.mockito.Mock import org.mockito.Mockito.mock import org.mockito.junit.MockitoJUnit @@ -64,7 +64,7 @@ import org.mockito.junit.MockitoRule @OptIn(ExperimentalCoroutinesApi::class) @SmallTest -@RunWith(JUnit4::class) +@RunWith(AndroidJUnit4::class) class DeviceEntrySideFpsOverlayInteractorTest : SysuiTestCase() { @JvmField @Rule var mockitoRule: MockitoRule = MockitoJUnit.rule() diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorParameterizedTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorParameterizedTest.kt index 14d954873f0f..d2a9c582d904 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorParameterizedTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorParameterizedTest.kt @@ -63,11 +63,11 @@ import kotlinx.coroutines.test.StandardTestDispatcher import kotlinx.coroutines.test.TestScope import kotlinx.coroutines.test.runTest import org.junit.Before +import platform.test.runner.parameterized.ParameterizedAndroidJunit4 +import platform.test.runner.parameterized.Parameters +import platform.test.runner.parameterized.Parameter import org.junit.Test import org.junit.runner.RunWith -import org.junit.runners.Parameterized -import org.junit.runners.Parameterized.Parameter -import org.junit.runners.Parameterized.Parameters import org.mockito.ArgumentMatchers.anyInt import org.mockito.ArgumentMatchers.anyString import org.mockito.ArgumentMatchers.eq @@ -83,7 +83,7 @@ import org.mockito.MockitoAnnotations detail = "on certain architectures all permutations with startActivity=true is causing failures" ) @SmallTest -@RunWith(Parameterized::class) +@RunWith(ParameterizedAndroidJunit4::class) @DisableSceneContainer class KeyguardQuickAffordanceInteractorParameterizedTest : SysuiTestCase() { diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorSceneContainerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorSceneContainerTest.kt index ced3526f40be..9d06031a3ed5 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorSceneContainerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorSceneContainerTest.kt @@ -63,11 +63,11 @@ import kotlinx.coroutines.test.StandardTestDispatcher import kotlinx.coroutines.test.TestScope import kotlinx.coroutines.test.runTest import org.junit.Before +import platform.test.runner.parameterized.ParameterizedAndroidJunit4 +import platform.test.runner.parameterized.Parameters +import platform.test.runner.parameterized.Parameter import org.junit.Test import org.junit.runner.RunWith -import org.junit.runners.Parameterized -import org.junit.runners.Parameterized.Parameter -import org.junit.runners.Parameterized.Parameters import org.mockito.ArgumentMatchers.anyInt import org.mockito.ArgumentMatchers.anyString import org.mockito.ArgumentMatchers.eq @@ -83,7 +83,7 @@ import org.mockito.MockitoAnnotations detail = "on certain architectures all permutations with startActivity=true is causing failures" ) @SmallTest -@RunWith(Parameterized::class) +@RunWith(ParameterizedAndroidJunit4::class) @EnableSceneContainer class KeyguardQuickAffordanceInteractorSceneContainerTest : SysuiTestCase() { diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/KeyguardBlueprintCommandListenerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/KeyguardBlueprintCommandListenerTest.kt index 8a0613f9b010..baa8ed73d7fb 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/KeyguardBlueprintCommandListenerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/KeyguardBlueprintCommandListenerTest.kt @@ -16,6 +16,7 @@ package com.android.systemui.keyguard.ui.view.layout +import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.keyguard.data.repository.KeyguardBlueprintRepository @@ -28,7 +29,6 @@ import java.io.PrintWriter import org.junit.Before import org.junit.Test import org.junit.runner.RunWith -import org.junit.runners.JUnit4 import org.mockito.ArgumentMatchers.anyString import org.mockito.Mock import org.mockito.Mockito.atLeastOnce @@ -36,7 +36,7 @@ import org.mockito.Mockito.never import org.mockito.Mockito.verify import org.mockito.MockitoAnnotations -@RunWith(JUnit4::class) +@RunWith(AndroidJUnit4::class) @SmallTest class KeyguardBlueprintCommandListenerTest : SysuiTestCase() { private lateinit var keyguardBlueprintCommandListener: KeyguardBlueprintCommandListener diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/blueprints/DefaultKeyguardBlueprintTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/blueprints/DefaultKeyguardBlueprintTest.kt index 344e0fc2bc6a..9fab0d9065b6 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/blueprints/DefaultKeyguardBlueprintTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/blueprints/DefaultKeyguardBlueprintTest.kt @@ -17,10 +17,10 @@ package com.android.systemui.keyguard.ui.view.layout.blueprints -import android.testing.AndroidTestingRunner import android.testing.TestableLooper.RunWithLooper import androidx.constraintlayout.widget.ConstraintLayout import androidx.constraintlayout.widget.ConstraintSet +import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.communal.ui.view.layout.sections.CommunalTutorialIndicatorSection @@ -54,7 +54,7 @@ import org.mockito.Mockito.never import org.mockito.Mockito.verify import org.mockito.MockitoAnnotations -@RunWith(AndroidTestingRunner::class) +@RunWith(AndroidJUnit4::class) @RunWithLooper(setAsMainLooper = true) @ExperimentalCoroutinesApi @SmallTest diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/sections/ClockSectionTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/sections/ClockSectionTest.kt index b3cc5c949cb8..4a39a9b2e801 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/sections/ClockSectionTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/sections/ClockSectionTest.kt @@ -22,6 +22,7 @@ import android.content.res.Resources import android.view.View.GONE import android.view.View.VISIBLE import androidx.constraintlayout.widget.ConstraintSet +import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.coroutines.collectLastValue @@ -52,12 +53,11 @@ import kotlinx.coroutines.test.runTest import org.junit.Before import org.junit.Test import org.junit.runner.RunWith -import org.junit.runners.JUnit4 import org.mockito.ArgumentMatchers.anyInt import org.mockito.ArgumentMatchers.anyString import org.mockito.MockitoAnnotations -@RunWith(JUnit4::class) +@RunWith(AndroidJUnit4::class) @SmallTest class ClockSectionTest : SysuiTestCase() { private lateinit var underTest: ClockSection diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultDeviceEntrySectionTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultDeviceEntrySectionTest.kt index 4f2b690f9fcd..693a87761b27 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultDeviceEntrySectionTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultDeviceEntrySectionTest.kt @@ -21,6 +21,7 @@ import android.graphics.Point import android.view.WindowManager import androidx.constraintlayout.widget.ConstraintLayout import androidx.constraintlayout.widget.ConstraintSet +import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.keyguard.LegacyLockIconViewController import com.android.systemui.Flags as AConfigFlags @@ -44,14 +45,13 @@ import kotlinx.coroutines.test.TestScope import org.junit.Before import org.junit.Test import org.junit.runner.RunWith -import org.junit.runners.JUnit4 import org.mockito.Answers import org.mockito.Mock import org.mockito.Mockito.mock import org.mockito.MockitoAnnotations @ExperimentalCoroutinesApi -@RunWith(JUnit4::class) +@RunWith(AndroidJUnit4::class) @SmallTest class DefaultDeviceEntrySectionTest : SysuiTestCase() { @Mock private lateinit var authController: AuthController diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultIndicationAreaSectionTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultIndicationAreaSectionTest.kt index 711f90f043ee..10f7128af43c 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultIndicationAreaSectionTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultIndicationAreaSectionTest.kt @@ -20,6 +20,7 @@ package com.android.systemui.keyguard.ui.view.layout.sections import android.view.ViewGroup import androidx.constraintlayout.widget.ConstraintLayout import androidx.constraintlayout.widget.ConstraintSet +import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.Flags as AConfigFlags import com.android.systemui.SysuiTestCase @@ -30,11 +31,10 @@ import com.google.common.truth.Truth.assertThat import org.junit.Before import org.junit.Test import org.junit.runner.RunWith -import org.junit.runners.JUnit4 import org.mockito.Mock import org.mockito.MockitoAnnotations -@RunWith(JUnit4::class) +@RunWith(AndroidJUnit4::class) @SmallTest class DefaultIndicationAreaSectionTest : SysuiTestCase() { diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/sections/SmartspaceSectionTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/sections/SmartspaceSectionTest.kt index be10b82193c1..7d4f03453fa7 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/sections/SmartspaceSectionTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/sections/SmartspaceSectionTest.kt @@ -22,6 +22,7 @@ import androidx.constraintlayout.widget.ConstraintLayout import androidx.constraintlayout.widget.ConstraintSet import androidx.constraintlayout.widget.ConstraintSet.GONE import androidx.constraintlayout.widget.ConstraintSet.VISIBLE +import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.Flags import com.android.systemui.SysuiTestCase @@ -41,11 +42,10 @@ import kotlinx.coroutines.flow.MutableStateFlow import org.junit.Before import org.junit.Test import org.junit.runner.RunWith -import org.junit.runners.JUnit4 import org.mockito.Mock import org.mockito.MockitoAnnotations -@RunWith(JUnit4::class) +@RunWith(AndroidJUnit4::class) @SmallTest class SmartspaceSectionTest : SysuiTestCase() { private lateinit var underTest: SmartspaceSection diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerViewModelTest.kt index f1c93c4652c3..e44bc7b43fb1 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerViewModelTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerViewModelTest.kt @@ -17,6 +17,7 @@ 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.coroutines.collectLastValue @@ -35,11 +36,10 @@ import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.test.runTest import org.junit.Test import org.junit.runner.RunWith -import org.junit.runners.JUnit4 import org.mockito.Mockito.verify @ExperimentalCoroutinesApi -@RunWith(JUnit4::class) +@RunWith(AndroidJUnit4::class) @SmallTest class AlternateBouncerViewModelTest : SysuiTestCase() { private val kosmos = testKosmos() diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerWindowViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerWindowViewModelTest.kt index 391831a61579..6398a5af52f1 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerWindowViewModelTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerWindowViewModelTest.kt @@ -17,6 +17,7 @@ package com.android.systemui.keyguard.ui.viewmodel +import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.keyguard.keyguardUpdateMonitor import com.android.systemui.Flags @@ -37,11 +38,10 @@ import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.test.runTest import org.junit.Test import org.junit.runner.RunWith -import org.junit.runners.JUnit4 import org.mockito.kotlin.whenever @ExperimentalCoroutinesApi -@RunWith(JUnit4::class) +@RunWith(AndroidJUnit4::class) @SmallTest class AlternateBouncerWindowViewModelTest : SysuiTestCase() { private val kosmos = testKosmos() diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBlueprintViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBlueprintViewModelTest.kt index ec2a1d305ab3..129752e4f106 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBlueprintViewModelTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBlueprintViewModelTest.kt @@ -18,6 +18,7 @@ package com.android.systemui.keyguard.ui.viewmodel import android.os.fakeExecutorHandler +import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.keyguard.domain.interactor.KeyguardBlueprintInteractor @@ -25,12 +26,11 @@ import com.android.systemui.testKosmos import org.junit.Before import org.junit.Test import org.junit.runner.RunWith -import org.junit.runners.JUnit4 import org.mockito.Mock import org.mockito.Mockito.verify import org.mockito.MockitoAnnotations -@RunWith(JUnit4::class) +@RunWith(AndroidJUnit4::class) @SmallTest class KeyguardBlueprintViewModelTest : SysuiTestCase() { @Mock private lateinit var keyguardBlueprintInteractor: KeyguardBlueprintInteractor diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardQuickAffordancesCombinedViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardQuickAffordancesCombinedViewModelTest.kt index c1e3e84f2bf4..16dd9af023a7 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardQuickAffordancesCombinedViewModelTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardQuickAffordancesCombinedViewModelTest.kt @@ -20,6 +20,7 @@ package com.android.systemui.keyguard.ui.viewmodel import android.app.admin.DevicePolicyManager import android.content.Intent import android.os.UserHandle +import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.internal.widget.LockPatternUtils import com.android.systemui.Flags as AConfigFlags @@ -76,7 +77,6 @@ import kotlinx.coroutines.test.runTest import org.junit.Before import org.junit.Test import org.junit.runner.RunWith -import org.junit.runners.JUnit4 import org.mockito.ArgumentMatchers import org.mockito.Mock import org.mockito.Mockito @@ -84,7 +84,7 @@ import org.mockito.MockitoAnnotations @OptIn(ExperimentalCoroutinesApi::class) @SmallTest -@RunWith(JUnit4::class) +@RunWith(AndroidJUnit4::class) class KeyguardQuickAffordancesCombinedViewModelTest : SysuiTestCase() { @Mock private lateinit var activityStarter: ActivityStarter diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardSmartspaceViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardSmartspaceViewModelTest.kt index 78f4dccd18a3..0c3fcb3ef759 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardSmartspaceViewModelTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardSmartspaceViewModelTest.kt @@ -16,6 +16,7 @@ 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.coroutines.collectLastValue @@ -32,13 +33,12 @@ import kotlinx.coroutines.test.runTest import org.junit.Before import org.junit.Test import org.junit.runner.RunWith -import org.junit.runners.JUnit4 import org.mockito.Answers import org.mockito.Mock import org.mockito.MockitoAnnotations @SmallTest -@RunWith(JUnit4::class) +@RunWith(AndroidJUnit4::class) class KeyguardSmartspaceViewModelTest : SysuiTestCase() { val kosmos = testKosmos() val testScope = kosmos.testScope diff --git a/packages/SystemUI/tests/src/com/android/systemui/log/SessionTrackerTest.java b/packages/SystemUI/tests/src/com/android/systemui/log/SessionTrackerTest.java index 447b333b942f..fbeb6d8d0a6f 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/log/SessionTrackerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/log/SessionTrackerTest.java @@ -32,9 +32,9 @@ import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import android.os.RemoteException; -import android.testing.AndroidTestingRunner; import android.testing.TestableLooper; +import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.filters.SmallTest; import com.android.internal.logging.InstanceId; @@ -54,7 +54,7 @@ import org.mockito.Captor; import org.mockito.Mock; import org.mockito.MockitoAnnotations; -@RunWith(AndroidTestingRunner.class) +@RunWith(AndroidJUnit4.class) @TestableLooper.RunWithLooper @SmallTest public class SessionTrackerTest extends SysuiTestCase { diff --git a/packages/SystemUI/tests/src/com/android/systemui/log/core/LoggerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/log/core/LoggerTest.kt index ab19b3aeceb0..d2e6dad8ed8d 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/log/core/LoggerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/log/core/LoggerTest.kt @@ -1,5 +1,6 @@ package com.android.systemui.log.core +import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.log.LogMessageImpl @@ -16,10 +17,9 @@ import org.mockito.Mockito.anyString import org.mockito.Mockito.isNull import org.mockito.Mockito.verify import org.mockito.MockitoAnnotations -import org.mockito.junit.MockitoJUnitRunner @SmallTest -@RunWith(MockitoJUnitRunner::class) +@RunWith(AndroidJUnit4::class) class LoggerTest : SysuiTestCase() { @Mock private lateinit var buffer: MessageBuffer private lateinit var message: LogMessage diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/controls/domain/pipeline/LegacyMediaDataFilterImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/controls/domain/pipeline/LegacyMediaDataFilterImplTest.kt index 5986f4a9a9aa..e2a2b7a91319 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/media/controls/domain/pipeline/LegacyMediaDataFilterImplTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/media/controls/domain/pipeline/LegacyMediaDataFilterImplTest.kt @@ -18,8 +18,8 @@ package com.android.systemui.media.controls.domain.pipeline import android.app.smartspace.SmartspaceAction import android.os.Bundle -import android.testing.AndroidTestingRunner import android.testing.TestableLooper +import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.internal.logging.InstanceId import com.android.systemui.SysuiTestCase @@ -64,7 +64,7 @@ private const val SMARTSPACE_PACKAGE = "SMARTSPACE_PKG" private val SMARTSPACE_INSTANCE_ID = InstanceId.fakeInstanceId(456)!! @SmallTest -@RunWith(AndroidTestingRunner::class) +@RunWith(AndroidJUnit4::class) @TestableLooper.RunWithLooper class LegacyMediaDataFilterImplTest : SysuiTestCase() { diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/controls/domain/pipeline/MediaDataCombineLatestTest.java b/packages/SystemUI/tests/src/com/android/systemui/media/controls/domain/pipeline/MediaDataCombineLatestTest.java index dd05a0d12429..544350c7e24d 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/media/controls/domain/pipeline/MediaDataCombineLatestTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/media/controls/domain/pipeline/MediaDataCombineLatestTest.java @@ -26,9 +26,9 @@ import static org.mockito.Mockito.never; import static org.mockito.Mockito.reset; import static org.mockito.Mockito.verify; -import android.testing.AndroidTestingRunner; import android.testing.TestableLooper; +import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.filters.SmallTest; import com.android.internal.logging.InstanceId; @@ -48,7 +48,7 @@ import org.mockito.junit.MockitoRule; import java.util.ArrayList; @SmallTest -@RunWith(AndroidTestingRunner.class) +@RunWith(AndroidJUnit4.class) @TestableLooper.RunWithLooper public class MediaDataCombineLatestTest extends SysuiTestCase { diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/controls/domain/pipeline/MediaDataFilterImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/controls/domain/pipeline/MediaDataFilterImplTest.kt index caaa42fc364c..8471fe1ed2d2 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/media/controls/domain/pipeline/MediaDataFilterImplTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/media/controls/domain/pipeline/MediaDataFilterImplTest.kt @@ -18,8 +18,8 @@ package com.android.systemui.media.controls.domain.pipeline import android.app.smartspace.SmartspaceAction import android.os.Bundle -import android.testing.AndroidTestingRunner import android.testing.TestableLooper +import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.internal.logging.InstanceId import com.android.systemui.SysuiTestCase @@ -76,7 +76,7 @@ private val SMARTSPACE_INSTANCE_ID = InstanceId.fakeInstanceId(456)!! @ExperimentalCoroutinesApi @SmallTest -@RunWith(AndroidTestingRunner::class) +@RunWith(AndroidJUnit4::class) @TestableLooper.RunWithLooper class MediaDataFilterImplTest : SysuiTestCase() { val kosmos = testKosmos() diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/controls/domain/pipeline/MediaDeviceManagerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/controls/domain/pipeline/MediaDeviceManagerTest.kt index 16d8819f13fb..42bd46f323b5 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/media/controls/domain/pipeline/MediaDeviceManagerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/media/controls/domain/pipeline/MediaDeviceManagerTest.kt @@ -31,8 +31,8 @@ import android.platform.test.annotations.DisableFlags import android.platform.test.annotations.EnableFlags import android.platform.test.annotations.RequiresFlagsDisabled import android.platform.test.flag.junit.DeviceFlagsValueProvider -import android.testing.AndroidTestingRunner import android.testing.TestableLooper +import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.settingslib.bluetooth.LocalBluetoothLeBroadcast import com.android.settingslib.bluetooth.LocalBluetoothManager @@ -86,7 +86,7 @@ private const val BROADCAST_APP_NAME = "BROADCAST_APP_NAME" private const val NORMAL_APP_NAME = "NORMAL_APP_NAME" @SmallTest -@RunWith(AndroidTestingRunner::class) +@RunWith(AndroidJUnit4::class) @TestableLooper.RunWithLooper public class MediaDeviceManagerTest : SysuiTestCase() { @get:Rule val checkFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule() diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/controls/domain/pipeline/MediaSessionBasedFilterTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/controls/domain/pipeline/MediaSessionBasedFilterTest.kt index 31a243591b60..efe4413cfb0f 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/media/controls/domain/pipeline/MediaSessionBasedFilterTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/media/controls/domain/pipeline/MediaSessionBasedFilterTest.kt @@ -20,8 +20,8 @@ import android.media.session.MediaController import android.media.session.MediaController.PlaybackInfo import android.media.session.MediaSession import android.media.session.MediaSessionManager -import android.testing.AndroidTestingRunner import android.testing.TestableLooper +import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.media.controls.MediaTestUtils @@ -53,7 +53,7 @@ private val info = MediaTestUtils.emptyMediaData.copy(packageName = PACKAGE, notificationKey = NOTIF_KEY) @SmallTest -@RunWith(AndroidTestingRunner::class) +@RunWith(AndroidJUnit4::class) @TestableLooper.RunWithLooper public class MediaSessionBasedFilterTest : SysuiTestCase() { diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/controls/domain/pipeline/MediaTimeoutListenerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/controls/domain/pipeline/MediaTimeoutListenerTest.kt index 6ca0bef17404..c1bba4d4d60c 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/media/controls/domain/pipeline/MediaTimeoutListenerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/media/controls/domain/pipeline/MediaTimeoutListenerTest.kt @@ -20,7 +20,7 @@ import android.media.MediaMetadata import android.media.session.MediaController import android.media.session.MediaSession import android.media.session.PlaybackState -import android.testing.AndroidTestingRunner +import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.media.controls.MediaTestUtils @@ -65,7 +65,7 @@ private fun <T> anyObject(): T { } @SmallTest -@RunWith(AndroidTestingRunner::class) +@RunWith(AndroidJUnit4::class) class MediaTimeoutListenerTest : SysuiTestCase() { @Mock private lateinit var mediaControllerFactory: MediaControllerFactory diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/controls/domain/resume/MediaResumeListenerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/controls/domain/resume/MediaResumeListenerTest.kt index 55ff231ab6b6..02d741385cf9 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/media/controls/domain/resume/MediaResumeListenerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/media/controls/domain/resume/MediaResumeListenerTest.kt @@ -27,8 +27,8 @@ import android.content.pm.ServiceInfo import android.media.MediaDescription import android.media.session.MediaSession import android.provider.Settings -import android.testing.AndroidTestingRunner import android.testing.TestableLooper +import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.broadcast.BroadcastDispatcher @@ -76,7 +76,7 @@ private fun <T> eq(value: T): T = Mockito.eq(value) ?: value private fun <T> any(): T = Mockito.any<T>() @SmallTest -@RunWith(AndroidTestingRunner::class) +@RunWith(AndroidJUnit4::class) @TestableLooper.RunWithLooper class MediaResumeListenerTest : SysuiTestCase() { diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/controls/domain/resume/ResumeMediaBrowserTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/controls/domain/resume/ResumeMediaBrowserTest.kt index 8dfa5b8e640c..dca19690fd14 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/media/controls/domain/resume/ResumeMediaBrowserTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/media/controls/domain/resume/ResumeMediaBrowserTest.kt @@ -23,8 +23,8 @@ import android.media.browse.MediaBrowser import android.media.session.MediaController import android.media.session.MediaSession import android.service.media.MediaBrowserService -import android.testing.AndroidTestingRunner import android.testing.TestableLooper +import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.util.mockito.mock @@ -53,7 +53,7 @@ private fun <T> eq(value: T): T = Mockito.eq(value) ?: value private fun <T> any(): T = Mockito.any<T>() @SmallTest -@RunWith(AndroidTestingRunner::class) +@RunWith(AndroidJUnit4::class) @TestableLooper.RunWithLooper public class ResumeMediaBrowserTest : SysuiTestCase() { diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaPlayerDataTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaPlayerDataTest.kt index b509e779a8c3..addf008619c9 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaPlayerDataTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaPlayerDataTest.kt @@ -16,7 +16,7 @@ package com.android.systemui.media.controls.ui -import android.testing.AndroidTestingRunner +import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.media.controls.MediaTestUtils @@ -34,7 +34,7 @@ import org.mockito.Mockito.mock import org.mockito.junit.MockitoJUnit @SmallTest -@RunWith(AndroidTestingRunner::class) +@RunWith(AndroidJUnit4::class) public class MediaPlayerDataTest : SysuiTestCase() { @Mock private lateinit var playerIsPlaying: MediaControlPanel diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/animation/AnimationBindHandlerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/animation/AnimationBindHandlerTest.kt index 4fcd3bb7600a..cdcb143fb00c 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/animation/AnimationBindHandlerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/animation/AnimationBindHandlerTest.kt @@ -18,8 +18,8 @@ package com.android.systemui.media.controls.ui.animation import android.graphics.drawable.Animatable2 import android.graphics.drawable.Drawable -import android.testing.AndroidTestingRunner import android.testing.TestableLooper +import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import junit.framework.Assert.assertFalse @@ -37,7 +37,7 @@ import org.mockito.Mockito.`when` as whenever import org.mockito.junit.MockitoJUnit @SmallTest -@RunWith(AndroidTestingRunner::class) +@RunWith(AndroidJUnit4::class) @TestableLooper.RunWithLooper(setAsMainLooper = true) class AnimationBindHandlerTest : SysuiTestCase() { diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/animation/ColorSchemeTransitionTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/animation/ColorSchemeTransitionTest.kt index aa297b537c49..4d0605fd5190 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/animation/ColorSchemeTransitionTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/animation/ColorSchemeTransitionTest.kt @@ -18,8 +18,8 @@ package com.android.systemui.media.controls.ui.animation import android.animation.ValueAnimator import android.graphics.Color -import android.testing.AndroidTestingRunner import android.testing.TestableLooper +import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.media.controls.ui.view.GutsViewHolder @@ -44,7 +44,7 @@ private const val DEFAULT_COLOR = Color.RED private const val TARGET_COLOR = Color.BLUE @SmallTest -@RunWith(AndroidTestingRunner::class) +@RunWith(AndroidJUnit4::class) @TestableLooper.RunWithLooper(setAsMainLooper = true) class ColorSchemeTransitionTest : SysuiTestCase() { diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/animation/MetadataAnimationHandlerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/animation/MetadataAnimationHandlerTest.kt index bb95ba356fef..2499c9ce7791 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/animation/MetadataAnimationHandlerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/animation/MetadataAnimationHandlerTest.kt @@ -17,8 +17,8 @@ package com.android.systemui.media.controls.ui.animation import android.animation.Animator -import android.testing.AndroidTestingRunner import android.testing.TestableLooper +import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import junit.framework.Assert.fail @@ -36,7 +36,7 @@ import org.mockito.Mockito.`when` as whenever import org.mockito.junit.MockitoJUnit @SmallTest -@RunWith(AndroidTestingRunner::class) +@RunWith(AndroidJUnit4::class) @TestableLooper.RunWithLooper(setAsMainLooper = true) class MetadataAnimationHandlerTest : SysuiTestCase() { diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/binder/SeekBarObserverTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/binder/SeekBarObserverTest.kt index 8a6b2722b1a2..4e14fec8408f 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/binder/SeekBarObserverTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/binder/SeekBarObserverTest.kt @@ -18,11 +18,11 @@ package com.android.systemui.media.controls.ui.binder import android.animation.Animator import android.animation.ObjectAnimator -import android.testing.AndroidTestingRunner import android.testing.TestableLooper import android.view.View import android.widget.SeekBar import android.widget.TextView +import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.media.controls.ui.drawable.SquigglyProgress @@ -40,7 +40,7 @@ import org.mockito.Mockito.`when` as whenever import org.mockito.junit.MockitoJUnit @SmallTest -@RunWith(AndroidTestingRunner::class) +@RunWith(AndroidJUnit4::class) @TestableLooper.RunWithLooper class SeekBarObserverTest : SysuiTestCase() { diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/controller/KeyguardMediaControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/controller/KeyguardMediaControllerTest.kt index 791563a839b1..2f95936a576f 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/controller/KeyguardMediaControllerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/controller/KeyguardMediaControllerTest.kt @@ -17,11 +17,11 @@ package com.android.systemui.media.controls.ui.controller import android.provider.Settings -import android.testing.AndroidTestingRunner import android.testing.TestableLooper import android.view.View.GONE import android.view.View.VISIBLE import android.widget.FrameLayout +import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.dump.DumpManager @@ -50,7 +50,7 @@ import org.mockito.Mockito.verify import org.mockito.junit.MockitoJUnit @SmallTest -@RunWith(AndroidTestingRunner::class) +@RunWith(AndroidJUnit4::class) @TestableLooper.RunWithLooper class KeyguardMediaControllerTest : SysuiTestCase() { diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/controller/MediaCarouselControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/controller/MediaCarouselControllerTest.kt index a89139b18bed..3ca5b421a7a4 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/controller/MediaCarouselControllerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/controller/MediaCarouselControllerTest.kt @@ -22,10 +22,10 @@ import android.content.res.Configuration import android.database.ContentObserver import android.os.LocaleList import android.provider.Settings -import android.testing.AndroidTestingRunner import android.testing.TestableLooper import android.util.MathUtils.abs import android.view.View +import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.internal.logging.InstanceId import com.android.keyguard.KeyguardUpdateMonitor @@ -40,6 +40,7 @@ import com.android.systemui.flags.fakeFeatureFlagsClassic import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository import com.android.systemui.keyguard.domain.interactor.keyguardTransitionInteractor import com.android.systemui.keyguard.shared.model.KeyguardState +import com.android.systemui.kosmos.applicationCoroutineScope import com.android.systemui.kosmos.testDispatcher import com.android.systemui.kosmos.testScope import com.android.systemui.media.controls.MediaTestUtils @@ -107,7 +108,7 @@ private const val PLAYING_LOCAL = "playing local" @SmallTest @TestableLooper.RunWithLooper(setAsMainLooper = true) -@RunWith(AndroidTestingRunner::class) +@RunWith(AndroidJUnit4::class) class MediaCarouselControllerTest : SysuiTestCase() { val kosmos = testKosmos() @@ -158,6 +159,7 @@ class MediaCarouselControllerTest : SysuiTestCase() { testDispatcher = UnconfinedTestDispatcher() mediaCarouselController = MediaCarouselController( + applicationScope = kosmos.applicationCoroutineScope, context = context, mediaControlPanelFactory = mediaControlPanelFactory, visualStabilityProvider = visualStabilityProvider, @@ -893,7 +895,10 @@ class MediaCarouselControllerTest : SysuiTestCase() { mediaCarouselController.updateHostVisibility = { updatedVisibility = true } mediaCarouselController.mediaCarousel = mediaCarousel - val settingsJob = mediaCarouselController.listenForLockscreenSettingChanges(this) + val settingsJob = + mediaCarouselController.listenForLockscreenSettingChanges( + kosmos.applicationCoroutineScope + ) secureSettings.putBool(Settings.Secure.MEDIA_CONTROLS_LOCK_SCREEN, false) val keyguardJob = mediaCarouselController.listenForAnyStateToLockscreenTransition(this) @@ -920,7 +925,10 @@ class MediaCarouselControllerTest : SysuiTestCase() { mediaCarouselController.updateHostVisibility = { updatedVisibility = true } mediaCarouselController.mediaCarousel = mediaCarousel - val settingsJob = mediaCarouselController.listenForLockscreenSettingChanges(this) + val settingsJob = + mediaCarouselController.listenForLockscreenSettingChanges( + kosmos.applicationCoroutineScope + ) secureSettings.putBool(Settings.Secure.MEDIA_CONTROLS_LOCK_SCREEN, true) val keyguardJob = mediaCarouselController.listenForAnyStateToLockscreenTransition(this) diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/controller/MediaControlPanelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/controller/MediaControlPanelTest.kt index 6d7976e6e51d..ecc456cb3888 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/controller/MediaControlPanelTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/controller/MediaControlPanelTest.kt @@ -45,7 +45,6 @@ import android.platform.test.annotations.RequiresFlagsEnabled import android.platform.test.flag.junit.DeviceFlagsValueProvider import android.provider.Settings import android.provider.Settings.ACTION_MEDIA_CONTROLS_SETTINGS -import android.testing.AndroidTestingRunner import android.testing.TestableLooper import android.util.TypedValue import android.view.View @@ -60,6 +59,7 @@ import androidx.constraintlayout.widget.Barrier import androidx.constraintlayout.widget.ConstraintSet import androidx.lifecycle.LiveData import androidx.media.utils.MediaConstants +import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.internal.logging.InstanceId import com.android.internal.widget.CachingIconView @@ -137,7 +137,7 @@ private const val REC_APP_NAME = "REC APP NAME" private const val APP_NAME = "APP_NAME" @SmallTest -@RunWith(AndroidTestingRunner::class) +@RunWith(AndroidJUnit4::class) @TestableLooper.RunWithLooper(setAsMainLooper = true) public class MediaControlPanelTest : SysuiTestCase() { @get:Rule val checkFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule() diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/controller/MediaHierarchyManagerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/controller/MediaHierarchyManagerTest.kt index 20fb7014b146..bba01bd0b78b 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/controller/MediaHierarchyManagerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/controller/MediaHierarchyManagerTest.kt @@ -18,10 +18,10 @@ package com.android.systemui.media.controls.ui.controller import android.graphics.Rect import android.provider.Settings -import android.testing.AndroidTestingRunner import android.testing.TestableLooper import android.view.ViewGroup import android.widget.FrameLayout +import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.keyguard.KeyguardViewController import com.android.systemui.SysuiTestCase @@ -81,7 +81,7 @@ import org.mockito.kotlin.anyOrNull @OptIn(ExperimentalCoroutinesApi::class) @SmallTest -@RunWith(AndroidTestingRunner::class) +@RunWith(AndroidJUnit4::class) @TestableLooper.RunWithLooper(setAsMainLooper = true) class MediaHierarchyManagerTest : SysuiTestCase() { diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/controller/MediaViewControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/controller/MediaViewControllerTest.kt index 80ebe56453ca..00b9a46f340a 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/controller/MediaViewControllerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/controller/MediaViewControllerTest.kt @@ -23,7 +23,6 @@ import android.content.res.Configuration.ORIENTATION_LANDSCAPE import android.graphics.drawable.Drawable import android.graphics.drawable.GradientDrawable import android.graphics.drawable.RippleDrawable -import android.testing.AndroidTestingRunner import android.testing.TestableLooper import android.view.View import android.view.ViewGroup @@ -35,6 +34,7 @@ import android.widget.SeekBar import android.widget.TextView import androidx.constraintlayout.widget.ConstraintSet import androidx.lifecycle.LiveData +import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.internal.widget.CachingIconView import com.android.systemui.SysuiTestCase @@ -72,7 +72,7 @@ import org.mockito.MockitoAnnotations @SmallTest @TestableLooper.RunWithLooper(setAsMainLooper = true) -@RunWith(AndroidTestingRunner::class) +@RunWith(AndroidJUnit4::class) class MediaViewControllerTest : SysuiTestCase() { private val mediaHostStateHolder = MediaHost.MediaHostStateHolder() private val mediaHostStatesManager = MediaHostStatesManager() diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/drawable/SquigglyProgressTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/drawable/SquigglyProgressTest.kt index 0319aaaedd27..e87f17601c47 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/drawable/SquigglyProgressTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/drawable/SquigglyProgressTest.kt @@ -21,8 +21,8 @@ import android.graphics.Color import android.graphics.LightingColorFilter import android.graphics.Paint import android.graphics.Rect -import android.testing.AndroidTestingRunner import android.testing.TestableLooper +import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.internal.graphics.ColorUtils import com.android.systemui.SysuiTestCase @@ -40,7 +40,7 @@ import org.mockito.Mockito.verify import org.mockito.junit.MockitoJUnit @SmallTest -@RunWith(AndroidTestingRunner::class) +@RunWith(AndroidJUnit4::class) @TestableLooper.RunWithLooper class SquigglyProgressTest : SysuiTestCase() { diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/view/MediaCarouselScrollHandlerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/view/MediaCarouselScrollHandlerTest.kt index 120836959fdc..d073cf1ac9db 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/view/MediaCarouselScrollHandlerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/view/MediaCarouselScrollHandlerTest.kt @@ -16,9 +16,9 @@ package com.android.systemui.media.controls.ui.view -import android.testing.AndroidTestingRunner import android.testing.TestableLooper import android.view.MotionEvent +import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.media.controls.util.MediaUiEventLogger @@ -38,7 +38,7 @@ import org.mockito.MockitoAnnotations @SmallTest @TestableLooper.RunWithLooper(setAsMainLooper = true) -@RunWith(AndroidTestingRunner::class) +@RunWith(AndroidJUnit4::class) class MediaCarouselScrollHandlerTest : SysuiTestCase() { private val carouselWidth = 1038 diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/view/MediaViewHolderTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/view/MediaViewHolderTest.kt index d3c703ccce46..cdb060cee645 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/view/MediaViewHolderTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/view/MediaViewHolderTest.kt @@ -16,17 +16,17 @@ package com.android.systemui.media.controls.ui.view -import android.testing.AndroidTestingRunner import android.testing.TestableLooper import android.view.LayoutInflater import android.widget.FrameLayout +import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import org.junit.Test import org.junit.runner.RunWith @SmallTest -@RunWith(AndroidTestingRunner::class) +@RunWith(AndroidJUnit4::class) @TestableLooper.RunWithLooper class MediaViewHolderTest : SysuiTestCase() { diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/viewmodel/SeekBarViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/viewmodel/SeekBarViewModelTest.kt index e1c2d3f115ed..4da7b2ac3700 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/viewmodel/SeekBarViewModelTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/viewmodel/SeekBarViewModelTest.kt @@ -20,12 +20,12 @@ import android.media.MediaMetadata import android.media.session.MediaController import android.media.session.MediaSession import android.media.session.PlaybackState -import android.testing.AndroidTestingRunner import android.testing.TestableLooper import android.view.MotionEvent import android.widget.SeekBar import androidx.arch.core.executor.ArchTaskExecutor import androidx.arch.core.executor.TaskExecutor +import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.classifier.Classifier @@ -53,7 +53,7 @@ import org.mockito.Mockito.`when` as whenever import org.mockito.junit.MockitoJUnit @SmallTest -@RunWith(AndroidTestingRunner::class) +@RunWith(AndroidJUnit4::class) @TestableLooper.RunWithLooper(setAsMainLooper = true) public class SeekBarViewModelTest : SysuiTestCase() { diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/controls/util/MediaDataUtilsTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/controls/util/MediaDataUtilsTest.kt index 86f3062bca35..bb9d20f88aa3 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/media/controls/util/MediaDataUtilsTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/media/controls/util/MediaDataUtilsTest.kt @@ -16,8 +16,8 @@ package com.android.systemui.media.controls.util -import android.testing.AndroidTestingRunner import android.util.Pair as APair +import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.google.common.truth.Truth.assertThat @@ -25,7 +25,7 @@ import org.junit.Test import org.junit.runner.RunWith @SmallTest -@RunWith(AndroidTestingRunner::class) +@RunWith(AndroidJUnit4::class) class MediaDataUtilsTest : SysuiTestCase() { @Test diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputControllerTest.java index 45ae50623612..856bc0b78215 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputControllerTest.java @@ -59,12 +59,12 @@ import android.os.PowerExemptionManager; import android.os.RemoteException; import android.os.UserHandle; import android.service.notification.StatusBarNotification; -import android.testing.AndroidTestingRunner; import android.testing.TestableLooper; import android.text.TextUtils; import android.view.View; import androidx.core.graphics.drawable.IconCompat; +import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.filters.SmallTest; import com.android.settingslib.bluetooth.CachedBluetoothDeviceManager; @@ -95,7 +95,7 @@ import java.util.ArrayList; import java.util.List; @SmallTest -@RunWith(AndroidTestingRunner.class) +@RunWith(AndroidJUnit4.class) @TestableLooper.RunWithLooper(setAsMainLooper = true) public class MediaOutputControllerTest extends SysuiTestCase { private static final String TEST_DEVICE_1_ID = "test_device_1_id"; diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputDialogReceiverTest.java b/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputDialogReceiverTest.java index 1e8fbeac05bd..50ae25ccfc00 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputDialogReceiverTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputDialogReceiverTest.java @@ -27,8 +27,8 @@ import static org.mockito.Mockito.verify; import android.content.Intent; import android.platform.test.annotations.DisableFlags; import android.platform.test.annotations.EnableFlags; -import android.testing.AndroidTestingRunner; +import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.filters.SmallTest; import com.android.settingslib.flags.Flags; @@ -40,7 +40,7 @@ import org.junit.Test; import org.junit.runner.RunWith; @SmallTest -@RunWith(AndroidTestingRunner.class) +@RunWith(AndroidJUnit4.class) public class MediaOutputDialogReceiverTest extends SysuiTestCase { private MediaOutputDialogReceiver mMediaOutputDialogReceiver; diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/dream/MediaComplicationViewControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/media/dream/MediaComplicationViewControllerTest.java index a82884377602..8e9a1f9d52f5 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/media/dream/MediaComplicationViewControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/media/dream/MediaComplicationViewControllerTest.java @@ -19,9 +19,9 @@ package com.android.systemui.media.dream; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.verify; -import android.testing.AndroidTestingRunner; import android.widget.FrameLayout; +import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.filters.SmallTest; import com.android.systemui.SysuiTestCase; @@ -35,7 +35,7 @@ import org.mockito.Mock; import org.mockito.MockitoAnnotations; @SmallTest -@RunWith(AndroidTestingRunner.class) +@RunWith(AndroidJUnit4.class) public class MediaComplicationViewControllerTest extends SysuiTestCase { @Mock private MediaHost mMediaHost; diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/dream/MediaDreamSentinelTest.java b/packages/SystemUI/tests/src/com/android/systemui/media/dream/MediaDreamSentinelTest.java index 8f8630e90694..a49819e5e7cc 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/media/dream/MediaDreamSentinelTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/media/dream/MediaDreamSentinelTest.java @@ -25,8 +25,8 @@ import static org.mockito.Mockito.never; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; -import android.testing.AndroidTestingRunner; +import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.filters.SmallTest; import com.android.systemui.SysuiTestCase; @@ -44,7 +44,7 @@ import org.mockito.Mock; import org.mockito.MockitoAnnotations; @SmallTest -@RunWith(AndroidTestingRunner.class) +@RunWith(AndroidJUnit4.class) public class MediaDreamSentinelTest extends SysuiTestCase { @Mock MediaDataManager mMediaDataManager; diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/receiver/MediaTttChipControllerReceiverTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/receiver/MediaTttChipControllerReceiverTest.kt index 27f59d292927..f1d833f7fa15 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/receiver/MediaTttChipControllerReceiverTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/receiver/MediaTttChipControllerReceiverTest.kt @@ -23,13 +23,13 @@ import android.graphics.drawable.Drawable import android.media.MediaRoute2Info import android.os.Handler import android.os.PowerManager -import android.testing.AndroidTestingRunner import android.testing.TestableLooper import android.view.View import android.view.ViewGroup import android.view.WindowManager import android.view.accessibility.AccessibilityManager import android.widget.ImageView +import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.internal.logging.InstanceId import com.android.internal.logging.testing.UiEventLoggerFake @@ -59,7 +59,7 @@ import org.mockito.Mockito.`when` as whenever import org.mockito.MockitoAnnotations @SmallTest -@RunWith(AndroidTestingRunner::class) +@RunWith(AndroidJUnit4::class) @TestableLooper.RunWithLooper class MediaTttChipControllerReceiverTest : SysuiTestCase() { private lateinit var controllerReceiver: MediaTttChipControllerReceiver diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/sender/MediaTttSenderCoordinatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/sender/MediaTttSenderCoordinatorTest.kt index 3be50b1baedf..111b8d4b1b30 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/sender/MediaTttSenderCoordinatorTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/sender/MediaTttSenderCoordinatorTest.kt @@ -24,7 +24,6 @@ import android.media.MediaRoute2Info import android.os.PowerManager import android.os.VibrationAttributes import android.os.VibrationEffect -import android.testing.AndroidTestingRunner import android.testing.TestableLooper import android.view.View import android.view.ViewGroup @@ -32,6 +31,7 @@ import android.view.WindowManager import android.view.accessibility.AccessibilityManager import android.widget.ImageView import android.widget.TextView +import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.internal.logging.testing.UiEventLoggerFake import com.android.internal.statusbar.IUndoMediaTransferCallback @@ -74,7 +74,7 @@ import org.mockito.Mockito.`when` as whenever import org.mockito.MockitoAnnotations @SmallTest -@RunWith(AndroidTestingRunner::class) +@RunWith(AndroidJUnit4::class) @TestableLooper.RunWithLooper class MediaTttSenderCoordinatorTest : SysuiTestCase() { diff --git a/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/MediaProjectionMetricsLoggerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/MediaProjectionMetricsLoggerTest.kt index da448aa20289..62382570a2b1 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/MediaProjectionMetricsLoggerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/MediaProjectionMetricsLoggerTest.kt @@ -1,7 +1,7 @@ package com.android.systemui.mediaprojection import android.media.projection.IMediaProjectionManager -import android.testing.AndroidTestingRunner +import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.internal.util.FrameworkStatsLog.MEDIA_PROJECTION_STATE_CHANGED__CREATION_SOURCE__CREATION_SOURCE_APP as METRICS_CREATION_SOURCE_APP import com.android.internal.util.FrameworkStatsLog.MEDIA_PROJECTION_STATE_CHANGED__CREATION_SOURCE__CREATION_SOURCE_CAST as METRICS_CREATION_SOURCE_CAST @@ -13,7 +13,7 @@ import org.junit.Test import org.junit.runner.RunWith import org.mockito.Mockito.verify -@RunWith(AndroidTestingRunner::class) +@RunWith(AndroidJUnit4::class) @SmallTest class MediaProjectionMetricsLoggerTest : SysuiTestCase() { diff --git a/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/appselector/MediaProjectionAppSelectorControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/appselector/MediaProjectionAppSelectorControllerTest.kt index 253607846e0f..22bdfe8f3cb3 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/appselector/MediaProjectionAppSelectorControllerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/appselector/MediaProjectionAppSelectorControllerTest.kt @@ -2,7 +2,7 @@ package com.android.systemui.mediaprojection.appselector import android.content.ComponentName import android.os.UserHandle -import android.testing.AndroidTestingRunner +import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.mediaprojection.MediaProjectionMetricsLogger @@ -24,7 +24,7 @@ import org.junit.runner.RunWith import org.mockito.Mockito.never import org.mockito.Mockito.verify -@RunWith(AndroidTestingRunner::class) +@RunWith(AndroidJUnit4::class) @SmallTest class MediaProjectionAppSelectorControllerTest : SysuiTestCase() { diff --git a/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/appselector/data/ActivityTaskManagerThumbnailLoaderTest.kt b/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/appselector/data/ActivityTaskManagerThumbnailLoaderTest.kt index db36131b825e..a73df0767dbc 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/appselector/data/ActivityTaskManagerThumbnailLoaderTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/appselector/data/ActivityTaskManagerThumbnailLoaderTest.kt @@ -7,9 +7,9 @@ import android.graphics.ColorSpace import android.graphics.Point import android.graphics.Rect import android.hardware.HardwareBuffer -import android.testing.AndroidTestingRunner import android.view.Surface import android.window.TaskSnapshot +import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.shared.recents.model.ThumbnailData @@ -24,7 +24,7 @@ import kotlinx.coroutines.test.runTest import org.junit.Test import org.junit.runner.RunWith -@RunWith(AndroidTestingRunner::class) +@RunWith(AndroidJUnit4::class) @SmallTest @OptIn(ExperimentalCoroutinesApi::class) class ActivityTaskManagerThumbnailLoaderTest : SysuiTestCase() { diff --git a/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/appselector/data/BasicPackageManagerAppIconLoaderTest.kt b/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/appselector/data/BasicPackageManagerAppIconLoaderTest.kt index fa1c8f8ea2c7..a0cd835b4ec1 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/appselector/data/BasicPackageManagerAppIconLoaderTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/appselector/data/BasicPackageManagerAppIconLoaderTest.kt @@ -20,6 +20,7 @@ import android.content.ComponentName import android.content.pm.ActivityInfo import android.content.pm.PackageManager import android.graphics.Bitmap +import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.launcher3.icons.FastBitmapDrawable import com.android.systemui.SysuiTestCase @@ -31,10 +32,9 @@ import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.runBlocking import org.junit.Test import org.junit.runner.RunWith -import org.junit.runners.JUnit4 @SmallTest -@RunWith(JUnit4::class) +@RunWith(AndroidJUnit4::class) class BasicPackageManagerAppIconLoaderTest : SysuiTestCase() { private val packageManagerWrapper: PackageManagerWrapper = mock() diff --git a/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/appselector/data/ShellRecentTaskListProviderTest.kt b/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/appselector/data/ShellRecentTaskListProviderTest.kt index 6ac86f58517d..2f61579d53c3 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/appselector/data/ShellRecentTaskListProviderTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/appselector/data/ShellRecentTaskListProviderTest.kt @@ -3,7 +3,7 @@ package com.android.systemui.mediaprojection.appselector.data import android.app.ActivityManager.RecentTaskInfo import android.content.pm.UserInfo import android.os.UserManager -import android.testing.AndroidTestingRunner +import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.mediaprojection.appselector.data.RecentTask.UserType.CLONED @@ -25,7 +25,7 @@ import org.junit.Test import org.junit.runner.RunWith import org.mockito.ArgumentMatchers.anyInt -@RunWith(AndroidTestingRunner::class) +@RunWith(AndroidJUnit4::class) @SmallTest class ShellRecentTaskListProviderTest : SysuiTestCase() { diff --git a/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/data/repository/MediaProjectionManagerRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/data/repository/MediaProjectionManagerRepositoryTest.kt index e4877808f133..b7fefc0a202f 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/data/repository/MediaProjectionManagerRepositoryTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/data/repository/MediaProjectionManagerRepositoryTest.kt @@ -17,8 +17,8 @@ package com.android.systemui.mediaprojection.data.repository import android.os.Binder -import android.testing.AndroidTestingRunner import android.view.ContentRecordingSession +import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.coroutines.collectLastValue @@ -34,7 +34,7 @@ import kotlinx.coroutines.test.runTest import org.junit.Test import org.junit.runner.RunWith -@RunWith(AndroidTestingRunner::class) +@RunWith(AndroidJUnit4::class) @SmallTest class MediaProjectionManagerRepositoryTest : SysuiTestCase() { diff --git a/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/devicepolicy/ScreenCaptureDevicePolicyResolverTest.kt b/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/devicepolicy/ScreenCaptureDevicePolicyResolverTest.kt index c63efa1ba38a..ea5603d71e13 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/devicepolicy/ScreenCaptureDevicePolicyResolverTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/devicepolicy/ScreenCaptureDevicePolicyResolverTest.kt @@ -25,10 +25,11 @@ import com.android.systemui.util.mockito.mock import com.android.systemui.util.mockito.whenever import com.google.common.truth.Truth.assertWithMessage import org.junit.Before +import platform.test.runner.parameterized.ParameterizedAndroidJunit4 +import platform.test.runner.parameterized.Parameters +import platform.test.runner.parameterized.Parameter import org.junit.Test import org.junit.runner.RunWith -import org.junit.runners.Parameterized -import org.junit.runners.Parameterized.Parameters import org.mockito.ArgumentMatchers.any abstract class BaseScreenCaptureDevicePolicyResolverTest(private val precondition: Preconditions) : @@ -81,7 +82,7 @@ abstract class BaseScreenCaptureDevicePolicyResolverTest(private val preconditio } } -@RunWith(Parameterized::class) +@RunWith(ParameterizedAndroidJunit4::class) @SmallTest class IsAllowedScreenCaptureDevicePolicyResolverTest( private val test: IsScreenCaptureAllowedTestCase @@ -468,7 +469,7 @@ class IsAllowedScreenCaptureDevicePolicyResolverTest( } } -@RunWith(Parameterized::class) +@RunWith(ParameterizedAndroidJunit4::class) @SmallTest class IsCompletelyNotAllowedScreenCaptureDevicePolicyResolverTest( private val test: IsScreenCaptureCompletelyDisabledTestCase diff --git a/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/taskswitcher/MediaProjectionTaskSwitcherCoreStartableTest.kt b/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/taskswitcher/MediaProjectionTaskSwitcherCoreStartableTest.kt index 16c92ecc2181..8fe8878c6918 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/taskswitcher/MediaProjectionTaskSwitcherCoreStartableTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/taskswitcher/MediaProjectionTaskSwitcherCoreStartableTest.kt @@ -16,7 +16,7 @@ package com.android.systemui.mediaprojection.taskswitcher -import android.testing.AndroidTestingRunner +import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.Flags.FLAG_PSS_TASK_SWITCHER import com.android.systemui.SysuiTestCase @@ -27,7 +27,7 @@ import org.junit.runner.RunWith import org.mockito.Mockito.verify import org.mockito.Mockito.verifyZeroInteractions -@RunWith(AndroidTestingRunner::class) +@RunWith(AndroidJUnit4::class) @SmallTest class MediaProjectionTaskSwitcherCoreStartableTest : SysuiTestCase() { diff --git a/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/taskswitcher/data/repository/ActivityTaskManagerTasksRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/taskswitcher/data/repository/ActivityTaskManagerTasksRepositoryTest.kt index bda0e1ed5c46..d3ce871994f6 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/taskswitcher/data/repository/ActivityTaskManagerTasksRepositoryTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/taskswitcher/data/repository/ActivityTaskManagerTasksRepositoryTest.kt @@ -17,7 +17,7 @@ package com.android.systemui.mediaprojection.taskswitcher.data.repository import android.os.Binder -import android.testing.AndroidTestingRunner +import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.coroutines.collectLastValue @@ -32,7 +32,7 @@ import kotlinx.coroutines.test.runTest import org.junit.Test import org.junit.runner.RunWith -@RunWith(AndroidTestingRunner::class) +@RunWith(AndroidJUnit4::class) @SmallTest class ActivityTaskManagerTasksRepositoryTest : SysuiTestCase() { diff --git a/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/taskswitcher/domain/interactor/TaskSwitchInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/taskswitcher/domain/interactor/TaskSwitchInteractorTest.kt index 33e65f26716a..7fe55b85fcd8 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/taskswitcher/domain/interactor/TaskSwitchInteractorTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/taskswitcher/domain/interactor/TaskSwitchInteractorTest.kt @@ -17,7 +17,7 @@ package com.android.systemui.mediaprojection.taskswitcher.domain.interactor import android.content.Intent -import android.testing.AndroidTestingRunner +import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.coroutines.collectLastValue @@ -35,7 +35,7 @@ import kotlinx.coroutines.test.runTest import org.junit.Test import org.junit.runner.RunWith -@RunWith(AndroidTestingRunner::class) +@RunWith(AndroidJUnit4::class) @SmallTest class TaskSwitchInteractorTest : SysuiTestCase() { diff --git a/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/taskswitcher/ui/TaskSwitcherNotificationCoordinatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/taskswitcher/ui/TaskSwitcherNotificationCoordinatorTest.kt index ad18099fefb9..7417dac5ceec 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/taskswitcher/ui/TaskSwitcherNotificationCoordinatorTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/taskswitcher/ui/TaskSwitcherNotificationCoordinatorTest.kt @@ -18,7 +18,7 @@ package com.android.systemui.mediaprojection.taskswitcher.ui import android.app.Notification import android.app.NotificationManager -import android.testing.AndroidTestingRunner +import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.kosmos.testScope @@ -43,7 +43,7 @@ import org.mockito.Mockito.never import org.mockito.Mockito.reset import org.mockito.Mockito.verify -@RunWith(AndroidTestingRunner::class) +@RunWith(AndroidJUnit4::class) @SmallTest class TaskSwitcherNotificationCoordinatorTest : SysuiTestCase() { diff --git a/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/taskswitcher/ui/viewmodel/TaskSwitcherNotificationViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/taskswitcher/ui/viewmodel/TaskSwitcherNotificationViewModelTest.kt index a468953b971e..5bedc134a1ee 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/taskswitcher/ui/viewmodel/TaskSwitcherNotificationViewModelTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/taskswitcher/ui/viewmodel/TaskSwitcherNotificationViewModelTest.kt @@ -17,7 +17,7 @@ package com.android.systemui.mediaprojection.taskswitcher.ui.viewmodel import android.content.Intent -import android.testing.AndroidTestingRunner +import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.coroutines.collectLastValue @@ -37,7 +37,7 @@ import kotlinx.coroutines.test.runTest import org.junit.Test import org.junit.runner.RunWith -@RunWith(AndroidTestingRunner::class) +@RunWith(AndroidJUnit4::class) @SmallTest class TaskSwitcherNotificationViewModelTest : SysuiTestCase() { diff --git a/packages/SystemUI/tests/src/com/android/systemui/model/SysUiStateExtTest.kt b/packages/SystemUI/tests/src/com/android/systemui/model/SysUiStateExtTest.kt index c06a28e3d840..a3be9e35b912 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/model/SysUiStateExtTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/model/SysUiStateExtTest.kt @@ -17,6 +17,7 @@ package com.android.systemui.model import android.view.Display +import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.settings.FakeDisplayTracker @@ -24,10 +25,9 @@ import com.android.systemui.testKosmos import com.google.common.truth.Truth.assertThat import org.junit.Test import org.junit.runner.RunWith -import org.junit.runners.JUnit4 @SmallTest -@RunWith(JUnit4::class) +@RunWith(AndroidJUnit4::class) class SysUiStateExtTest : SysuiTestCase() { private val kosmos = testKosmos() diff --git a/packages/SystemUI/tests/src/com/android/systemui/model/SysUiStateTest.java b/packages/SystemUI/tests/src/com/android/systemui/model/SysUiStateTest.java index f03f4f7375f5..9a78bd93f424 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/model/SysUiStateTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/model/SysUiStateTest.java @@ -23,6 +23,7 @@ import static org.mockito.Mockito.mock; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; +import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.filters.SmallTest; import com.android.systemui.SysuiTestCase; @@ -32,10 +33,9 @@ import com.android.systemui.settings.FakeDisplayTracker; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; -import org.mockito.junit.MockitoJUnitRunner; @SmallTest -@RunWith(MockitoJUnitRunner.class) +@RunWith(AndroidJUnit4.class) public class SysUiStateTest extends SysuiTestCase { private static final int FLAG_1 = 1; private static final int FLAG_2 = 1 << 1; diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tileimpl/QSTileViewImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/tileimpl/QSTileViewImplTest.kt index db11c3e89160..196f654a205a 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/tileimpl/QSTileViewImplTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tileimpl/QSTileViewImplTest.kt @@ -19,6 +19,8 @@ package com.android.systemui.qs.tileimpl import android.content.Context import android.graphics.Rect import android.graphics.drawable.Drawable +import android.platform.test.annotations.DisableFlags +import android.platform.test.annotations.EnableFlags import android.service.quicksettings.Tile import android.testing.AndroidTestingRunner import android.testing.TestableLooper @@ -28,11 +30,13 @@ import android.view.View import android.view.accessibility.AccessibilityNodeInfo import android.widget.TextView import androidx.test.filters.SmallTest +import com.android.systemui.Flags.FLAG_QUICK_SETTINGS_VISUAL_HAPTICS_LONGPRESS import com.android.systemui.res.R import com.android.systemui.SysuiTestCase import com.android.systemui.haptics.qs.QSLongPressEffect import com.android.systemui.haptics.qs.qsLongPressEffect import com.android.systemui.plugins.qs.QSTile +import com.android.systemui.qs.qsTileFactory import com.android.systemui.testKosmos import com.google.common.truth.Truth.assertThat import org.junit.Before @@ -536,10 +540,30 @@ class QSTileViewImplTest : SysuiTestCase() { assertThat(tileView.haveLongPressPropertiesBeenReset).isTrue() } + @Test + @EnableFlags(FLAG_QUICK_SETTINGS_VISUAL_HAPTICS_LONGPRESS) + fun onInit_withLongPressEffect_longPressEffectHasTileAndExpandable() { + val tile = kosmos.qsTileFactory.createTile("Test Tile") + tileView.init(tile) + + assertThat(tileView.isTileAddedToLongPress).isTrue() + assertThat(tileView.isExpandableAddedToLongPress).isTrue() + } + + @Test + @DisableFlags(FLAG_QUICK_SETTINGS_VISUAL_HAPTICS_LONGPRESS) + fun onInit_withoutLongPressEffect_longPressEffectDoesNotHaveTileAndExpandable() { + val tile = kosmos.qsTileFactory.createTile("Test Tile") + tileView.init(tile) + + assertThat(tileView.isTileAddedToLongPress).isFalse() + assertThat(tileView.isExpandableAddedToLongPress).isFalse() + } + class FakeTileView( context: Context, collapsed: Boolean, - longPressEffect: QSLongPressEffect?, + private val longPressEffect: QSLongPressEffect?, ) : QSTileViewImpl( ContextThemeWrapper(context, R.style.Theme_SystemUI_QuickSettings), collapsed, @@ -547,6 +571,11 @@ class QSTileViewImplTest : SysuiTestCase() { ) { var constantLongPressEffectDuration = 500 + val isTileAddedToLongPress: Boolean + get() = longPressEffect?.qsTile != null + val isExpandableAddedToLongPress: Boolean + get() = longPressEffect?.expandable != null + override fun getLongPressEffectDuration(): Int = constantLongPressEffectDuration fun changeState(state: QSTile.State) { handleStateChanged(state) diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/carrier/ShadeCarrierGroupControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/shade/carrier/ShadeCarrierGroupControllerTest.java index 5363c57c1bad..308b3708e407 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/shade/carrier/ShadeCarrierGroupControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/shade/carrier/ShadeCarrierGroupControllerTest.java @@ -312,9 +312,10 @@ public class ShadeCarrierGroupControllerTest extends LeakCheckedTest { info = new CarrierTextManager.CarrierTextCallbackInfo( "", new CharSequence[]{""}, - true, + /* anySimReady= */ true, + /* isInSatelliteMode= */ false, new int[]{0}, - true /* airplaneMode */); + /* airplaneMode= */ true); mCallback.updateCarrierInfo(info); mTestableLooper.processAllMessages(); assertEquals(View.GONE, mShadeCarrierGroup.getNoSimTextView().getVisibility()); @@ -326,15 +327,59 @@ public class ShadeCarrierGroupControllerTest extends LeakCheckedTest { info = new CarrierTextManager.CarrierTextCallbackInfo( "", new CharSequence[]{FIRST_CARRIER_NAME, ""}, - true, + /* anySimReady= */ true, + /* isInSatelliteMode= */ false, new int[]{0, 1}, - false /* airplaneMode */); + /* airplaneMode= */ false); mCallback.updateCarrierInfo(info); mTestableLooper.processAllMessages(); assertEquals(View.VISIBLE, mShadeCarrierGroupController.getShadeCarrierVisibility(0)); } @Test + public void isInSatelliteMode_true_noSimViewShownWithText() { + CarrierTextManager.CarrierTextCallbackInfo + info = new CarrierTextManager.CarrierTextCallbackInfo( + "Satellite Mode Test", + new CharSequence[]{FIRST_CARRIER_NAME}, + /* anySimReady= */ true, + /* isInSatelliteMode= */ true, + new int[]{1}, + /* airplaneMode= */ false); + + mCallback.updateCarrierInfo(info); + mTestableLooper.processAllMessages(); + + assertThat(mShadeCarrierGroup.getNoSimTextView().getVisibility()).isEqualTo(View.VISIBLE); + assertThat(mShadeCarrierGroup.getNoSimTextView().getText()).isEqualTo( + "Satellite Mode Test"); + + verify(mShadeCarrier1).setVisibility(View.GONE); + verify(mShadeCarrier2).setVisibility(View.GONE); + verify(mShadeCarrier3).setVisibility(View.GONE); + } + + @Test + public void isInSatelliteMode_false_normalSimViewsShown() { + CarrierTextManager.CarrierTextCallbackInfo + info = new CarrierTextManager.CarrierTextCallbackInfo( + "Satellite Mode Test", + new CharSequence[]{FIRST_CARRIER_NAME, SECOND_CARRIER_NAME}, + /* anySimReady= */ true, + /* isInSatelliteMode= */ false, + new int[]{0, 1}, + /* airplaneMode= */ false); + + mCallback.updateCarrierInfo(info); + mTestableLooper.processAllMessages(); + + assertThat(mShadeCarrierGroup.getNoSimTextView().getVisibility()).isEqualTo(View.GONE); + + verify(mShadeCarrier1).setVisibility(View.VISIBLE); + verify(mShadeCarrier2).setVisibility(View.VISIBLE); + } + + @Test public void testListenerNotCalledOnRegistreation() { mShadeCarrierGroupController .setOnSingleCarrierChangedListener(mOnSingleCarrierChangedListener); @@ -350,8 +395,7 @@ public class ShadeCarrierGroupControllerTest extends LeakCheckedTest { SINGLE_CARRIER_TEXT, new CharSequence[]{SINGLE_CARRIER_TEXT}, true, - new int[]{0}, - false /* airplaneMode */); + new int[]{0}); mCallback.updateCarrierInfo(info); mTestableLooper.processAllMessages(); @@ -369,8 +413,7 @@ public class ShadeCarrierGroupControllerTest extends LeakCheckedTest { MULTI_CARRIER_TEXT, new CharSequence[]{FIRST_CARRIER_NAME, SECOND_CARRIER_NAME}, true, - new int[]{0, 1}, - false /* airplaneMode */); + new int[]{0, 1}); mCallback.updateCarrierInfo(info); mTestableLooper.processAllMessages(); @@ -387,16 +430,14 @@ public class ShadeCarrierGroupControllerTest extends LeakCheckedTest { SINGLE_CARRIER_TEXT, new CharSequence[]{FIRST_CARRIER_NAME}, true, - new int[]{0}, - false /* airplaneMode */); + new int[]{0}); CarrierTextManager.CarrierTextCallbackInfo multiCarrierInfo = new CarrierTextManager.CarrierTextCallbackInfo( MULTI_CARRIER_TEXT, new CharSequence[]{FIRST_CARRIER_NAME, SECOND_CARRIER_NAME}, true, - new int[]{0, 1}, - false /* airplaneMode */); + new int[]{0, 1}); mCallback.updateCarrierInfo(singleCarrierInfo); mTestableLooper.processAllMessages(); @@ -421,8 +462,7 @@ public class ShadeCarrierGroupControllerTest extends LeakCheckedTest { SINGLE_CARRIER_TEXT, new CharSequence[]{FIRST_CARRIER_NAME}, true, - new int[]{0}, - false /* airplaneMode */); + new int[]{0}); mCallback.updateCarrierInfo(singleCarrierInfo); mTestableLooper.processAllMessages(); @@ -443,8 +483,7 @@ public class ShadeCarrierGroupControllerTest extends LeakCheckedTest { MULTI_CARRIER_TEXT, new CharSequence[]{FIRST_CARRIER_NAME, SECOND_CARRIER_NAME}, true, - new int[]{0, 1}, - false /* airplaneMode */); + new int[]{0, 1}); mCallback.updateCarrierInfo(multiCarrierInfo); mTestableLooper.processAllMessages(); diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/lockscreen/LockscreenSmartspaceControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/lockscreen/LockscreenSmartspaceControllerTest.kt index 63ce2330698a..068e166de5bb 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/lockscreen/LockscreenSmartspaceControllerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/lockscreen/LockscreenSmartspaceControllerTest.kt @@ -32,6 +32,7 @@ import android.os.Handler import android.os.UserHandle import android.platform.test.annotations.DisableFlags import android.provider.Settings +import android.testing.TestableLooper.RunWithLooper import android.view.View import android.widget.FrameLayout import androidx.test.filters.SmallTest @@ -84,6 +85,7 @@ import java.util.Optional import java.util.concurrent.Executor @SmallTest +@RunWithLooper(setAsMainLooper = true) class LockscreenSmartspaceControllerTest : SysuiTestCase() { companion object { const val SMARTSPACE_TIME_TOO_EARLY = 1000L @@ -778,6 +780,7 @@ class LockscreenSmartspaceControllerTest : SysuiTestCase() { } @Test + @RunWithLooper(setAsMainLooper = false) fun testConnectAttemptBeforeInitializationShouldNotCreateSession() { // GIVEN an uninitalized smartspaceView // WHEN the device is provisioned diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationContentInflaterTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationContentInflaterTest.java index a355cd16ee15..62bffdda6fea 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationContentInflaterTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationContentInflaterTest.java @@ -140,7 +140,7 @@ public class NotificationContentInflaterTest extends SysuiTestCase { mSmartReplyStateInflater, mNotifLayoutInflaterFactoryProvider, mHeadsUpStyleProvider, - mock(NotificationContentInflaterLogger.class)); + mock(NotificationRowContentBinderLogger.class)); } @Test @@ -265,7 +265,7 @@ public class NotificationContentInflaterTest extends SysuiTestCase { R.layout.custom_view_dark); } }, - mock(NotificationContentInflaterLogger.class)); + mock(NotificationRowContentBinderLogger.class)); assertTrue(countDownLatch.await(500, TimeUnit.MILLISECONDS)); } diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationTestHelper.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationTestHelper.java index 16618602e2c3..65941adf280e 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationTestHelper.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationTestHelper.java @@ -192,7 +192,7 @@ public class NotificationTestHelper { mBgCoroutineContext, mMainCoroutineContext); - NotificationContentInflater contentBinder = new NotificationContentInflater( + NotificationRowContentBinder contentBinder = new NotificationContentInflater( mock(NotifRemoteViewCache.class), mock(NotificationRemoteInputManager.class), mock(ConversationNotificationProcessor.class), @@ -201,7 +201,7 @@ public class NotificationTestHelper { new MockSmartReplyInflater(), mock(NotifLayoutInflaterFactory.Provider.class), mock(HeadsUpStyleProvider.class), - mock(NotificationContentInflaterLogger.class)); + mock(NotificationRowContentBinderLogger.class)); contentBinder.setInflateSynchronously(true); mBindStage = new RowContentBindStage(contentBinder, mock(NotifInflationErrorManager.class), diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/SingleLineViewInflaterTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/SingleLineViewInflaterTest.kt index e025d3d36ab1..d3666321c8e4 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/SingleLineViewInflaterTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/SingleLineViewInflaterTest.kt @@ -372,7 +372,7 @@ class SingleLineViewInflaterTest : SysuiTestCase() { } // Inflate the SingleLineViewModel - // Mock the behavior of NotificationContentInflater.doInBackground + // Mock the behavior of NotificationRowContentBinder.doInBackground val messagingStyle = builder.getMessagingStyle() val isConversation = type is OneToOneConversation || type is GroupConversation return SingleLineViewInflater.inflateSingleLineViewModel( diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileRepositorySwitcherTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileRepositorySwitcherTest.kt index eb2538ec032e..7d586cde2222 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileRepositorySwitcherTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileRepositorySwitcherTest.kt @@ -131,8 +131,9 @@ class MobileRepositorySwitcherTest : SysuiTestCase() { mobileMappings, fakeBroadcastDispatcher, context, - IMMEDIATE, + /* bgDispatcher = */ IMMEDIATE, scope, + /* mainDispatcher = */ IMMEDIATE, FakeAirplaneModeRepository(), wifiRepository, mock(), diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryTest.kt index 96e599f8f4d0..76982ae12516 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryTest.kt @@ -141,8 +141,8 @@ class MobileConnectionsRepositoryTest : SysuiTestCase() { private val wifiPickerTrackerCallback = argumentCaptor<WifiPickerTracker.WifiPickerTrackerCallback>() - private val dispatcher = StandardTestDispatcher() - private val testScope = TestScope(dispatcher) + private val testDispatcher = StandardTestDispatcher() + private val testScope = TestScope(testDispatcher) private lateinit var underTest: MobileConnectionsRepositoryImpl @@ -194,7 +194,7 @@ class MobileConnectionsRepositoryTest : SysuiTestCase() { flags, testScope.backgroundScope, mainExecutor, - dispatcher, + testDispatcher, wifiPickerTrackerFactory, wifiManager, wifiLogBuffer, @@ -216,7 +216,7 @@ class MobileConnectionsRepositoryTest : SysuiTestCase() { fakeBroadcastDispatcher, connectivityManager, telephonyManager = telephonyManager, - bgDispatcher = dispatcher, + bgDispatcher = testDispatcher, logger = logger, mobileMappingsProxy = mobileMappings, scope = testScope.backgroundScope, @@ -249,8 +249,9 @@ class MobileConnectionsRepositoryTest : SysuiTestCase() { mobileMappings, fakeBroadcastDispatcher, context, - dispatcher, + /* bgDispatcher = */ testDispatcher, testScope.backgroundScope, + /* mainDispatcher = */ testDispatcher, airplaneModeRepository, wifiRepository, fullConnectionFactory, @@ -1225,8 +1226,9 @@ class MobileConnectionsRepositoryTest : SysuiTestCase() { mobileMappings, fakeBroadcastDispatcher, context, - dispatcher, + testDispatcher, testScope.backgroundScope, + testDispatcher, airplaneModeRepository, wifiRepository, fullConnectionFactory, diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/satellite/data/prod/DeviceBasedSatelliteRepositoryImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/satellite/data/prod/DeviceBasedSatelliteRepositoryImplTest.kt index 02f53b6846e8..d24d87c6f57a 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/satellite/data/prod/DeviceBasedSatelliteRepositoryImplTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/satellite/data/prod/DeviceBasedSatelliteRepositoryImplTest.kt @@ -34,6 +34,7 @@ import android.telephony.satellite.SatelliteManager.SATELLITE_MODEM_STATE_UNAVAI import android.telephony.satellite.SatelliteManager.SATELLITE_MODEM_STATE_UNKNOWN import android.telephony.satellite.SatelliteManager.SatelliteException import android.telephony.satellite.SatelliteModemStateCallback +import android.telephony.satellite.SatelliteSupportedStateCallback import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.coroutines.collectLastValue @@ -327,7 +328,6 @@ class DeviceBasedSatelliteRepositoryImplTest : SysuiTestCase() { @Test fun satelliteNotSupported_listenersAreNotRegistered() = testScope.runTest { - setupDefaultRepo() // GIVEN satellite is not supported setUpRepo( uptime = MIN_UPTIME, @@ -345,6 +345,110 @@ class DeviceBasedSatelliteRepositoryImplTest : SysuiTestCase() { } @Test + fun satelliteSupported_registersCallbackForStateChanges() = + testScope.runTest { + // GIVEN a supported satellite manager. + setupDefaultRepo() + runCurrent() + + // THEN the repo registers for state changes of satellite support + verify(satelliteManager, times(1)).registerForSupportedStateChanged(any(), any()) + } + + @Test + fun satelliteNotSupported_registersCallbackForStateChanges() = + testScope.runTest { + // GIVEN satellite is not supported + setUpRepo( + uptime = MIN_UPTIME, + satMan = satelliteManager, + satelliteSupported = false, + ) + + runCurrent() + // THEN the repo registers for state changes of satellite support + verify(satelliteManager, times(1)).registerForSupportedStateChanged(any(), any()) + } + + @Test + fun satelliteSupportedStateChangedCallbackThrows_doesNotCrash() = + testScope.runTest { + // GIVEN, satellite manager throws when registering for supported state changes + whenever(satelliteManager.registerForSupportedStateChanged(any(), any())) + .thenThrow(IllegalStateException()) + + // GIVEN a supported satellite manager. + setupDefaultRepo() + runCurrent() + + // THEN a listener for satellite supported changed can attempt to register, + // with no crash + verify(satelliteManager).registerForSupportedStateChanged(any(), any()) + } + + @Test + fun satelliteSupported_supportIsLost_unregistersListeners() = + testScope.runTest { + // GIVEN a supported satellite manager. + setupDefaultRepo() + runCurrent() + + val callback = + withArgCaptor<SatelliteSupportedStateCallback> { + verify(satelliteManager).registerForSupportedStateChanged(any(), capture()) + } + + // WHEN data is requested from the repo + val connectionState by collectLastValue(underTest.connectionState) + val signalStrength by collectLastValue(underTest.signalStrength) + + // THEN the listeners are registered + verify(satelliteManager, times(1)).registerForModemStateChanged(any(), any()) + verify(satelliteManager, times(1)).registerForNtnSignalStrengthChanged(any(), any()) + + // WHEN satellite support turns off + callback.onSatelliteSupportedStateChanged(false) + runCurrent() + + // THEN listeners are unregistered + verify(satelliteManager, times(1)).unregisterForModemStateChanged(any()) + verify(satelliteManager, times(1)).unregisterForNtnSignalStrengthChanged(any()) + } + + @Test + fun satelliteNotSupported_supportShowsUp_registersListeners() = + testScope.runTest { + // GIVEN satellite is not supported + setUpRepo( + uptime = MIN_UPTIME, + satMan = satelliteManager, + satelliteSupported = false, + ) + runCurrent() + + val callback = + withArgCaptor<SatelliteSupportedStateCallback> { + verify(satelliteManager).registerForSupportedStateChanged(any(), capture()) + } + + // WHEN data is requested from the repo + val connectionState by collectLastValue(underTest.connectionState) + val signalStrength by collectLastValue(underTest.signalStrength) + + // THEN the listeners are not yet registered + verify(satelliteManager, times(0)).registerForModemStateChanged(any(), any()) + verify(satelliteManager, times(0)).registerForNtnSignalStrengthChanged(any(), any()) + + // WHEN satellite support turns on + callback.onSatelliteSupportedStateChanged(true) + runCurrent() + + // THEN listeners are registered + verify(satelliteManager, times(1)).registerForModemStateChanged(any(), any()) + verify(satelliteManager, times(1)).registerForNtnSignalStrengthChanged(any(), any()) + } + + @Test fun repoDoesNotCheckForSupportUntilMinUptime() = testScope.runTest { // GIVEN we init 100ms after sysui starts up diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/shade/domain/interactor/ShadeLockscreenInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/shade/domain/interactor/ShadeLockscreenInteractorKosmos.kt index 297d1d8c2c0a..0a3a2ee91773 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/shade/domain/interactor/ShadeLockscreenInteractorKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/shade/domain/interactor/ShadeLockscreenInteractorKosmos.kt @@ -17,7 +17,7 @@ package com.android.systemui.shade.domain.interactor import com.android.systemui.kosmos.Kosmos -import com.android.systemui.kosmos.testScope +import com.android.systemui.kosmos.applicationCoroutineScope import com.android.systemui.scene.domain.interactor.sceneInteractor import com.android.systemui.shade.data.repository.shadeRepository import com.android.systemui.util.mockito.mock @@ -25,7 +25,8 @@ import com.android.systemui.util.mockito.mock val Kosmos.shadeLockscreenInteractor by Kosmos.Fixture { ShadeLockscreenInteractorImpl( - scope = testScope, + applicationScope = applicationCoroutineScope, + backgroundScope = applicationCoroutineScope, shadeInteractor = shadeInteractorImpl, sceneInteractor = sceneInteractor, lockIconViewController = mock(), diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/volume/data/repository/TestAudioDevicesFactory.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/data/repository/TestAudioDevicesFactory.kt index 3ac712918100..15ef26d58ece 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/volume/data/repository/TestAudioDevicesFactory.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/data/repository/TestAudioDevicesFactory.kt @@ -33,19 +33,22 @@ object TestAudioDevicesFactory { ) } - fun wiredDevice(deviceName: String = "wired"): AudioDeviceInfo { + fun wiredDevice( + deviceName: String = "wired", + deviceAddress: String = "card=1;device=0", + ): AudioDeviceInfo { return AudioDeviceInfo( AudioDevicePort.createForTesting( AudioDeviceInfo.TYPE_WIRED_HEADPHONES, deviceName, - "", + deviceAddress, ) ) } fun bluetoothDevice( deviceName: String = "bt", - deviceAddress: String = "test_address", + deviceAddress: String = "00:43:A8:23:10:F0", ): AudioDeviceInfo { return AudioDeviceInfo( AudioDevicePort.createForTesting( diff --git a/packages/services/CameraExtensionsProxy/src/com/android/cameraextensions/CameraExtensionsProxyService.java b/packages/services/CameraExtensionsProxy/src/com/android/cameraextensions/CameraExtensionsProxyService.java index 004f37c16757..2c4bc7cb0d47 100644 --- a/packages/services/CameraExtensionsProxy/src/com/android/cameraextensions/CameraExtensionsProxyService.java +++ b/packages/services/CameraExtensionsProxy/src/com/android/cameraextensions/CameraExtensionsProxyService.java @@ -2514,7 +2514,7 @@ public class CameraExtensionsProxyService extends Service { public Plane[] getPlanes() { throwISEIfImageIsInvalid(); if (mPlanes == null) { - int fenceFd = mParcelImage.fence != null ? mParcelImage.fence.getFd() : -1; + int fenceFd = mParcelImage.fence != null ? mParcelImage.fence.detachFd() : -1; mGraphicBuffer = GraphicBuffer.createFromHardwareBuffer(mParcelImage.buffer); mPlanes = ImageReader.initializeImagePlanes(mParcelImage.planeCount, mGraphicBuffer, fenceFd, mParcelImage.format, mParcelImage.timestamp, diff --git a/services/accessibility/java/com/android/server/accessibility/AbstractAccessibilityServiceConnection.java b/services/accessibility/java/com/android/server/accessibility/AbstractAccessibilityServiceConnection.java index 77decb6a52fa..7342b0003ed5 100644 --- a/services/accessibility/java/com/android/server/accessibility/AbstractAccessibilityServiceConnection.java +++ b/services/accessibility/java/com/android/server/accessibility/AbstractAccessibilityServiceConnection.java @@ -1748,9 +1748,7 @@ abstract class AbstractAccessibilityServiceConnection extends IAccessibilityServ } public void resetLocked() { - if (Flags.resettableDynamicProperties()) { - mAccessibilityServiceInfo.resetDynamicallyConfigurableProperties(); - } + mAccessibilityServiceInfo.resetDynamicallyConfigurableProperties(); mSystemSupport.getKeyEventDispatcher().flush(this); try { // Clear the proxy in the other process so this diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java index fe083389fa77..d09cb3ee103a 100644 --- a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java +++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java @@ -5272,13 +5272,9 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub //Clip to the window bounds. Rect windowBounds = mTempRect1; - if (Flags.focusClickPointWindowBoundsFromA11yWindowInfo()) { - AccessibilityWindowInfo window = focus.getWindow(); - if (window != null) { - window.getBoundsInScreen(windowBounds); - } - } else { - getWindowBounds(focus.getWindowId(), windowBounds); + AccessibilityWindowInfo window = focus.getWindow(); + if (window != null) { + window.getBoundsInScreen(windowBounds); } if (!boundsInScreenBeforeMagnification.intersect(windowBounds)) { return false; diff --git a/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java b/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java index 0d0c21dcf49c..30e4a3eb77e6 100644 --- a/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java +++ b/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java @@ -34,6 +34,7 @@ import static com.android.internal.util.function.pooled.PooledLambda.obtainMessa import static com.android.server.companion.utils.PackageUtils.enforceUsesCompanionDeviceFeature; import static com.android.server.companion.utils.PackageUtils.getPackageInfo; import static com.android.server.companion.utils.PackageUtils.isRestrictedSettingsAllowed; +import static com.android.server.companion.utils.PermissionsUtils.checkCallerCanManageCompanionDevice; import static com.android.server.companion.utils.PermissionsUtils.enforceCallerCanManageAssociationsForPackage; import static com.android.server.companion.utils.PermissionsUtils.enforceCallerIsSystemOr; import static com.android.server.companion.utils.PermissionsUtils.enforceCallerIsSystemOrCanInteractWithUserId; @@ -334,6 +335,12 @@ public class CompanionDeviceManagerService extends SystemService { enforceCallerCanManageAssociationsForPackage(getContext(), userId, packageName, "get associations"); + if (!checkCallerCanManageCompanionDevice(getContext())) { + // If the caller neither is system nor holds MANAGE_COMPANION_DEVICES: it needs to + // request the feature (also: the caller is the app itself). + enforceUsesCompanionDeviceFeature(getContext(), userId, packageName); + } + return mAssociationStore.getActiveAssociationsByPackage(userId, packageName); } diff --git a/services/companion/java/com/android/server/companion/association/AssociationRequestsProcessor.java b/services/companion/java/com/android/server/companion/association/AssociationRequestsProcessor.java index 3fbd8560b82c..d09d7e672f9d 100644 --- a/services/companion/java/com/android/server/companion/association/AssociationRequestsProcessor.java +++ b/services/companion/java/com/android/server/companion/association/AssociationRequestsProcessor.java @@ -347,8 +347,6 @@ public class AssociationRequestsProcessor { * Set association tag. */ public void setAssociationTag(int associationId, String tag) { - Slog.i(TAG, "Setting association tag=[" + tag + "] to id=[" + associationId + "]..."); - AssociationInfo association = mAssociationStore.getAssociationWithCallerChecks( associationId); association = (new AssociationInfo.Builder(association)).setTag(tag).build(); diff --git a/services/companion/java/com/android/server/companion/association/AssociationStore.java b/services/companion/java/com/android/server/companion/association/AssociationStore.java index 757abd927ac8..29e8095f8680 100644 --- a/services/companion/java/com/android/server/companion/association/AssociationStore.java +++ b/services/companion/java/com/android/server/companion/association/AssociationStore.java @@ -18,7 +18,7 @@ package com.android.server.companion.association; import static com.android.server.companion.utils.MetricUtils.logCreateAssociation; import static com.android.server.companion.utils.MetricUtils.logRemoveAssociation; -import static com.android.server.companion.utils.PermissionsUtils.enforceCallerCanManageAssociationsForPackage; +import static com.android.server.companion.utils.PermissionsUtils.checkCallerCanManageAssociationsForPackage; import android.annotation.IntDef; import android.annotation.NonNull; @@ -457,10 +457,6 @@ public class AssociationStore { /** * Get association by id with caller checks. - * - * If the association is not found, an IllegalArgumentException would be thrown. - * - * If the caller can't access the association, a SecurityException would be thrown. */ @NonNull public AssociationInfo getAssociationWithCallerChecks(int associationId) { @@ -470,9 +466,13 @@ public class AssociationStore { "getAssociationWithCallerChecks() Association id=[" + associationId + "] doesn't exist."); } - enforceCallerCanManageAssociationsForPackage(mContext, association.getUserId(), - association.getPackageName(), null); - return association; + if (checkCallerCanManageAssociationsForPackage(mContext, association.getUserId(), + association.getPackageName())) { + return association; + } + + throw new IllegalArgumentException( + "The caller can't interact with the association id=[" + associationId + "]."); } /** diff --git a/services/companion/java/com/android/server/companion/association/DisassociationProcessor.java b/services/companion/java/com/android/server/companion/association/DisassociationProcessor.java index 6f0baef019b3..8c1116b7a612 100644 --- a/services/companion/java/com/android/server/companion/association/DisassociationProcessor.java +++ b/services/companion/java/com/android/server/companion/association/DisassociationProcessor.java @@ -98,6 +98,7 @@ public class DisassociationProcessor { Slog.i(TAG, "Disassociating id=[" + id + "]..."); final AssociationInfo association = mAssociationStore.getAssociationWithCallerChecks(id); + final int userId = association.getUserId(); final String packageName = association.getPackageName(); final String deviceProfile = association.getDeviceProfile(); diff --git a/services/companion/java/com/android/server/companion/datatransfer/SystemDataTransferProcessor.java b/services/companion/java/com/android/server/companion/datatransfer/SystemDataTransferProcessor.java index 00e049c1e1ff..026d29c9f821 100644 --- a/services/companion/java/com/android/server/companion/datatransfer/SystemDataTransferProcessor.java +++ b/services/companion/java/com/android/server/companion/datatransfer/SystemDataTransferProcessor.java @@ -122,6 +122,7 @@ public class SystemDataTransferProcessor { */ public boolean isPermissionTransferUserConsented(int associationId) { mAssociationStore.getAssociationWithCallerChecks(associationId); + PermissionSyncRequest request = getPermissionSyncRequest(associationId); if (request == null) { return false; @@ -146,12 +147,12 @@ public class SystemDataTransferProcessor { return null; } - Slog.i(LOG_TAG, "Creating permission sync intent for userId [" + userId - + "] associationId [" + associationId + "]"); - final AssociationInfo association = mAssociationStore.getAssociationWithCallerChecks( associationId); + Slog.i(LOG_TAG, "Creating permission sync intent for userId [" + userId + + "] associationId [" + associationId + "]"); + // Create an internal intent to launch the user consent dialog final Bundle extras = new Bundle(); PermissionSyncRequest request = new PermissionSyncRequest(associationId); @@ -219,9 +220,7 @@ public class SystemDataTransferProcessor { * Enable perm sync for the association */ public void enablePermissionsSync(int associationId) { - AssociationInfo association = mAssociationStore.getAssociationWithCallerChecks( - associationId); - int userId = association.getUserId(); + int userId = mAssociationStore.getAssociationWithCallerChecks(associationId).getUserId(); PermissionSyncRequest request = new PermissionSyncRequest(associationId); request.setUserConsented(true); mSystemDataTransferRequestStore.writeRequest(userId, request); @@ -231,9 +230,7 @@ public class SystemDataTransferProcessor { * Disable perm sync for the association */ public void disablePermissionsSync(int associationId) { - AssociationInfo association = mAssociationStore.getAssociationWithCallerChecks( - associationId); - int userId = association.getUserId(); + int userId = mAssociationStore.getAssociationWithCallerChecks(associationId).getUserId(); PermissionSyncRequest request = new PermissionSyncRequest(associationId); request.setUserConsented(false); mSystemDataTransferRequestStore.writeRequest(userId, request); @@ -244,9 +241,8 @@ public class SystemDataTransferProcessor { */ @Nullable public PermissionSyncRequest getPermissionSyncRequest(int associationId) { - AssociationInfo association = mAssociationStore.getAssociationWithCallerChecks( - associationId); - int userId = association.getUserId(); + int userId = mAssociationStore.getAssociationWithCallerChecks(associationId) + .getUserId(); List<SystemDataTransferRequest> requests = mSystemDataTransferRequestStore.readRequestsByAssociationId(userId, associationId); @@ -263,9 +259,7 @@ public class SystemDataTransferProcessor { */ public void removePermissionSyncRequest(int associationId) { Binder.withCleanCallingIdentity(() -> { - AssociationInfo association = mAssociationStore.getAssociationWithCallerChecks( - associationId); - int userId = association.getUserId(); + int userId = mAssociationStore.getAssociationById(associationId).getUserId(); mSystemDataTransferRequestStore.removeRequestsByAssociationId(userId, associationId); }); } diff --git a/services/companion/java/com/android/server/companion/utils/PermissionsUtils.java b/services/companion/java/com/android/server/companion/utils/PermissionsUtils.java index 796d2851760f..f397814f7ad7 100644 --- a/services/companion/java/com/android/server/companion/utils/PermissionsUtils.java +++ b/services/companion/java/com/android/server/companion/utils/PermissionsUtils.java @@ -149,6 +149,21 @@ public final class PermissionsUtils { } /** + * Check if the caller is system UID or the provided user. + */ + public static boolean checkCallerIsSystemOr(@UserIdInt int userId, + @NonNull String packageName) { + final int callingUid = getCallingUid(); + if (callingUid == SYSTEM_UID) return true; + + if (getCallingUserId() != userId) return false; + + if (!checkPackage(callingUid, packageName)) return false; + + return true; + } + + /** * Check if the calling user id matches the userId, and if the package belongs to * the calling uid. */ @@ -169,30 +184,21 @@ public final class PermissionsUtils { } /** + * Check if the caller holds the necessary permission to manage companion devices. + */ + public static boolean checkCallerCanManageCompanionDevice(@NonNull Context context) { + if (getCallingUid() == SYSTEM_UID) return true; + + return context.checkCallingPermission(MANAGE_COMPANION_DEVICES) == PERMISSION_GRANTED; + } + + /** * Require the caller to be able to manage the associations for the package. */ public static void enforceCallerCanManageAssociationsForPackage(@NonNull Context context, @UserIdInt int userId, @NonNull String packageName, @Nullable String actionDescription) { - final int callingUid = getCallingUid(); - - // If the caller is the system - if (callingUid == SYSTEM_UID) { - return; - } - - // If caller can manage the package or has the permissions to manage companion devices - boolean canInteractAcrossUsers = context.checkCallingPermission(INTERACT_ACROSS_USERS) - == PERMISSION_GRANTED; - boolean canManageCompanionDevices = context.checkCallingPermission(MANAGE_COMPANION_DEVICES) - == PERMISSION_GRANTED; - if (getCallingUserId() == userId) { - if (checkPackage(callingUid, packageName) || canManageCompanionDevices) { - return; - } - } else if (canInteractAcrossUsers && canManageCompanionDevices) { - return; - } + if (checkCallerCanManageAssociationsForPackage(context, userId, packageName)) return; throw new SecurityException("Caller (uid=" + getCallingUid() + ") does not have " + "permissions to " @@ -213,6 +219,25 @@ public final class PermissionsUtils { } } + /** + * Check if the caller is either: + * <ul> + * <li> the package itself + * <li> the System ({@link android.os.Process#SYSTEM_UID}) + * <li> holds {@link Manifest.permission#MANAGE_COMPANION_DEVICES} and, if belongs to a + * different user, also holds {@link Manifest.permission#INTERACT_ACROSS_USERS}. + * </ul> + * @return whether the caller is one of the above. + */ + public static boolean checkCallerCanManageAssociationsForPackage(@NonNull Context context, + @UserIdInt int userId, @NonNull String packageName) { + if (checkCallerIsSystemOr(userId, packageName)) return true; + + if (!checkCallerCanInteractWithUserId(context, userId)) return false; + + return checkCallerCanManageCompanionDevice(context); + } + private static boolean checkPackage(@UserIdInt int uid, @NonNull String packageName) { try { return getAppOpsService().checkPackage(uid, packageName) == MODE_ALLOWED; diff --git a/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java b/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java index 215f6402fa76..4a9900763a94 100644 --- a/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java +++ b/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java @@ -29,6 +29,7 @@ import static android.companion.virtual.VirtualDeviceParams.POLICY_TYPE_CLIPBOAR import static android.companion.virtual.VirtualDeviceParams.POLICY_TYPE_RECENTS; import static android.content.pm.PackageManager.ACTION_REQUEST_PERMISSIONS; import static android.companion.virtualdevice.flags.Flags.virtualCameraServiceDiscovery; +import static android.companion.virtualdevice.flags.Flags.intentInterceptionActionMatchingFix; import android.annotation.EnforcePermission; import android.annotation.NonNull; @@ -1478,7 +1479,13 @@ final class VirtualDeviceImpl extends IVirtualDevice.Stub synchronized (mVirtualDeviceLock) { boolean hasInterceptedIntent = false; for (Map.Entry<IBinder, IntentFilter> interceptor : mIntentInterceptors.entrySet()) { - if (interceptor.getValue().match( + IntentFilter intentFilter = interceptor.getValue(); + // Explicitly match the actions because the intent filter will match any intent + // without an explicit action. If the intent has no action, then require that there + // are no actions specified in the filter either. + boolean explicitActionMatch = !intentInterceptionActionMatchingFix() + || intent.getAction() != null || intentFilter.countActions() == 0; + if (explicitActionMatch && intentFilter.match( intent.getAction(), intent.getType(), intent.getScheme(), intent.getData(), intent.getCategories(), TAG) >= 0) { try { diff --git a/services/core/java/com/android/server/PackageWatchdog.java b/services/core/java/com/android/server/PackageWatchdog.java index 966478e33c73..a61925732256 100644 --- a/services/core/java/com/android/server/PackageWatchdog.java +++ b/services/core/java/com/android/server/PackageWatchdog.java @@ -138,12 +138,6 @@ public class PackageWatchdog { static final long DEFAULT_BOOT_LOOP_TRIGGER_WINDOW_MS = TimeUnit.MINUTES.toMillis(10); - // Time needed to apply mitigation - private static final String MITIGATION_WINDOW_MS = - "persist.device_config.configuration.mitigation_window_ms"; - @VisibleForTesting - static final long DEFAULT_MITIGATION_WINDOW_MS = TimeUnit.SECONDS.toMillis(5); - // Threshold level at which or above user might experience significant disruption. private static final String MAJOR_USER_IMPACT_LEVEL_THRESHOLD = "persist.device_config.configuration.major_user_impact_level_threshold"; @@ -216,9 +210,6 @@ public class PackageWatchdog { @GuardedBy("mLock") private boolean mSyncRequired = false; - @GuardedBy("mLock") - private long mLastMitigation = -1000000; - @FunctionalInterface @VisibleForTesting interface SystemClock { @@ -409,16 +400,6 @@ public class PackageWatchdog { Slog.w(TAG, "Could not resolve a list of failing packages"); return; } - synchronized (mLock) { - final long now = mSystemClock.uptimeMillis(); - if (Flags.recoverabilityDetection()) { - if (now >= mLastMitigation - && (now - mLastMitigation) < getMitigationWindowMs()) { - Slog.i(TAG, "Skipping onPackageFailure mitigation"); - return; - } - } - } mLongTaskHandler.post(() -> { synchronized (mLock) { if (mAllObservers.isEmpty()) { @@ -519,17 +500,10 @@ public class PackageWatchdog { int currentObserverImpact, int mitigationCount) { if (currentObserverImpact < getUserImpactLevelLimit()) { - synchronized (mLock) { - mLastMitigation = mSystemClock.uptimeMillis(); - } currentObserverToNotify.execute(versionedPackage, failureReason, mitigationCount); } } - private long getMitigationWindowMs() { - return SystemProperties.getLong(MITIGATION_WINDOW_MS, DEFAULT_MITIGATION_WINDOW_MS); - } - /** * Called when the system server boots. If the system server is detected to be in a boot loop, diff --git a/services/core/java/com/android/server/am/CachedAppOptimizer.java b/services/core/java/com/android/server/am/CachedAppOptimizer.java index db4840dc76c5..211f952551d9 100644 --- a/services/core/java/com/android/server/am/CachedAppOptimizer.java +++ b/services/core/java/com/android/server/am/CachedAppOptimizer.java @@ -83,8 +83,6 @@ import com.android.internal.os.ProcLocksReader; import com.android.internal.util.FrameworkStatsLog; import com.android.server.ServiceThread; -import dalvik.annotation.optimization.NeverCompile; - import java.io.FileReader; import java.io.IOException; import java.io.PrintWriter; @@ -100,6 +98,8 @@ import java.util.Map; import java.util.Random; import java.util.Set; +import dalvik.annotation.optimization.NeverCompile; + public final class CachedAppOptimizer { // Flags stored in the DeviceConfig API. @@ -2633,7 +2633,7 @@ public final class CachedAppOptimizer { public void binderError(int debugPid, ProcessRecord app, int code, int flags, int err) { Slog.w(TAG_AM, "pid " + debugPid + " " + (app == null ? "null" : app.processName) + " sent binder code " + code + " with flags " + flags - + " and got error " + err); + + " to frozen apps and got error " + err); // Do nothing if the binder error callback is not enabled. // That means the frozen apps in a wrong state will be killed when they are unfrozen later. diff --git a/services/core/java/com/android/server/inputmethod/HandwritingModeController.java b/services/core/java/com/android/server/inputmethod/HandwritingModeController.java index 79f1a9c90f53..c19cb030ef37 100644 --- a/services/core/java/com/android/server/inputmethod/HandwritingModeController.java +++ b/services/core/java/com/android/server/inputmethod/HandwritingModeController.java @@ -55,7 +55,6 @@ import java.util.ArrayList; import java.util.List; import java.util.Objects; import java.util.OptionalInt; -import java.util.function.IntConsumer; // TODO(b/210039666): See if we can make this class thread-safe. final class HandwritingModeController { @@ -91,7 +90,6 @@ final class HandwritingModeController { private boolean mDelegationConnectionlessFlow; private Runnable mDelegationIdleTimeoutRunnable; private Handler mDelegationIdleTimeoutHandler; - private IntConsumer mPointerToolTypeConsumer; private final Runnable mDiscardDelegationTextRunnable; private HandwritingEventReceiverSurface mHandwritingSurface; @@ -99,7 +97,7 @@ final class HandwritingModeController { @AnyThread HandwritingModeController(Context context, Looper uiThreadLooper, - Runnable inkWindowInitRunnable, IntConsumer toolTypeConsumer, + Runnable inkWindowInitRunnable, Runnable discardDelegationTextRunnable) { mContext = context; mLooper = uiThreadLooper; @@ -109,7 +107,6 @@ final class HandwritingModeController { mPackageManagerInternal = LocalServices.getService(PackageManagerInternal.class); mCurrentRequestId = 0; mInkWindowInitRunnable = inkWindowInitRunnable; - mPointerToolTypeConsumer = toolTypeConsumer; mDiscardDelegationTextRunnable = discardDelegationTextRunnable; } @@ -389,16 +386,10 @@ final class HandwritingModeController { "Input Event should not be processed when IME has the spy channel."); } - if (!(ev instanceof MotionEvent)) { + if (!(ev instanceof MotionEvent event)) { Slog.wtf(TAG, "Received non-motion event in stylus monitor."); return false; } - final MotionEvent event = (MotionEvent) ev; - if (mPointerToolTypeConsumer != null && event.getAction() == MotionEvent.ACTION_DOWN) { - int toolType = event.getToolType(event.getActionIndex()); - // notify IME of change in tool type. - mPointerToolTypeConsumer.accept(toolType); - } if (!event.isStylusPointer()) { return false; } diff --git a/services/core/java/com/android/server/inputmethod/InputMethodManagerInternal.java b/services/core/java/com/android/server/inputmethod/InputMethodManagerInternal.java index dace67f2c462..f61ca61c1e04 100644 --- a/services/core/java/com/android/server/inputmethod/InputMethodManagerInternal.java +++ b/services/core/java/com/android/server/inputmethod/InputMethodManagerInternal.java @@ -16,6 +16,8 @@ package com.android.server.inputmethod; +import static java.lang.annotation.RetentionPolicy.SOURCE; + import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.UserIdInt; @@ -30,6 +32,9 @@ import com.android.internal.inputmethod.InlineSuggestionsRequestInfo; import com.android.internal.inputmethod.SoftInputShowHideReason; import com.android.server.LocalServices; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.Target; import java.util.Collections; import java.util.List; @@ -38,6 +43,16 @@ import java.util.List; */ public abstract class InputMethodManagerInternal { /** + * Indicates that the method is guaranteed to not require {@link ImfLock}. + * + * <p>You can call this method without worrying about system_server lock layering.</p> + */ + @Retention(SOURCE) + @Target({ElementType.METHOD}) + public @interface ImfLockFree { + } + + /** * Listener for input method list changed events. */ public interface InputMethodListListener { @@ -53,6 +68,7 @@ public abstract class InputMethodManagerInternal { * * @param interactive the interactive mode parameter */ + @ImfLockFree public abstract void setInteractive(boolean interactive); /** @@ -61,6 +77,7 @@ public abstract class InputMethodManagerInternal { * @param reason the reason for hiding the current input method * @param originatingDisplayId the display ID the request is originated */ + @ImfLockFree public abstract void hideAllInputMethods(@SoftInputShowHideReason int reason, int originatingDisplayId); @@ -143,6 +160,7 @@ public abstract class InputMethodManagerInternal { * * @param listener the listener to add */ + @ImfLockFree public abstract void registerInputMethodListListener(InputMethodListListener listener); /** @@ -178,6 +196,7 @@ public abstract class InputMethodManagerInternal { * * @param displayId the display hosting the IME window */ + @ImfLockFree public abstract void removeImeSurface(int displayId); /** @@ -188,12 +207,14 @@ public abstract class InputMethodManagerInternal { * @param disableImeIcon indicates whether IME icon should be enabled or not * @param displayId the display for which to update the IME window status */ + @ImfLockFree public abstract void updateImeWindowStatus(boolean disableImeIcon, int displayId); /** * Finish stylus handwriting by calling {@link InputMethodService#finishStylusHandwriting()} if * there is an ongoing handwriting session. */ + @ImfLockFree public abstract void maybeFinishStylusHandwriting(); /** @@ -240,10 +261,12 @@ public abstract class InputMethodManagerInternal { */ private static final InputMethodManagerInternal NOP = new InputMethodManagerInternal() { + @ImfLockFree @Override public void setInteractive(boolean interactive) { } + @ImfLockFree @Override public void hideAllInputMethods(@SoftInputShowHideReason int reason, int originatingDisplayId) { @@ -282,6 +305,7 @@ public abstract class InputMethodManagerInternal { int deviceId, @Nullable String imeId) { } + @ImfLockFree @Override public void registerInputMethodListListener(InputMethodListListener listener) { } @@ -300,10 +324,12 @@ public abstract class InputMethodManagerInternal { public void onImeParentChanged(int displayId) { } + @ImfLockFree @Override public void removeImeSurface(int displayId) { } + @ImfLockFree @Override public void updateImeWindowStatus(boolean disableImeIcon, int displayId) { } @@ -318,6 +344,7 @@ public abstract class InputMethodManagerInternal { @UserIdInt int userId) { } + @ImfLockFree @Override public void maybeFinishStylusHandwriting() { } diff --git a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java index 7513c40a1f90..d22438fe32ab 100644 --- a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java +++ b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java @@ -205,7 +205,6 @@ import java.util.concurrent.CopyOnWriteArrayList; import java.util.concurrent.Future; import java.util.concurrent.TimeUnit; import java.util.function.Consumer; -import java.util.function.IntConsumer; import java.util.function.IntFunction; /** @@ -1305,12 +1304,9 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. com.android.internal.R.bool.config_preventImeStartupUnlessTextEditor); mNonPreemptibleInputMethods = mRes.getStringArray( com.android.internal.R.array.config_nonPreemptibleInputMethods); - IntConsumer toolTypeConsumer = - Flags.useHandwritingListenerForTooltype() - ? toolType -> onUpdateEditorToolType(toolType) : null; Runnable discardDelegationTextRunnable = () -> discardHandwritingDelegationText(); mHwController = new HandwritingModeController(mContext, thread.getLooper(), - new InkWindowInitializer(), toolTypeConsumer, discardDelegationTextRunnable); + new InkWindowInitializer(), discardDelegationTextRunnable); registerDeviceListenerAndCheckStylusSupport(); } } @@ -3416,8 +3412,10 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. ImeTracker.forLogging().onProgress(statsToken, ImeTracker.PHASE_SERVER_HAS_IME); mCurStatsToken = null; - if (lastClickToolType != MotionEvent.TOOL_TYPE_UNKNOWN) { - curMethod.updateEditorToolType(lastClickToolType); + if (Flags.useHandwritingListenerForTooltype()) { + maybeReportToolType(); + } else if (lastClickToolType != MotionEvent.TOOL_TYPE_UNKNOWN) { + onUpdateEditorToolType(lastClickToolType); } mVisibilityApplier.performShowIme(windowToken, statsToken, mVisibilityStateComputer.getShowFlagsForInputMethodServiceOnly(), @@ -3431,6 +3429,29 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. return false; } + @GuardedBy("ImfLock.class") + private void maybeReportToolType() { + int lastDeviceId = mInputManagerInternal.getLastUsedInputDeviceId(); + final InputManager im = mContext.getSystemService(InputManager.class); + if (im == null) { + return; + } + InputDevice device = im.getInputDevice(lastDeviceId); + if (device == null) { + return; + } + int toolType; + if (isStylusDevice(device)) { + toolType = MotionEvent.TOOL_TYPE_STYLUS; + } else if (isFingerDevice(device)) { + toolType = MotionEvent.TOOL_TYPE_FINGER; + } else { + // other toolTypes are irrelevant and reported as unknown. + toolType = MotionEvent.TOOL_TYPE_UNKNOWN; + } + onUpdateEditorToolType(toolType); + } + @Override public boolean hideSoftInput(IInputMethodClient client, IBinder windowToken, @NonNull ImeTracker.Token statsToken, @InputMethodManager.HideFlags int flags, @@ -4285,6 +4306,10 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. || inputDevice.supportsSource(InputDevice.SOURCE_BLUETOOTH_STYLUS); } + private static boolean isFingerDevice(InputDevice inputDevice) { + return inputDevice.supportsSource(InputDevice.SOURCE_TOUCHSCREEN); + } + @GuardedBy("ImfLock.class") private boolean hasSupportedStylusLocked() { return mStylusIds != null && mStylusIds.size() != 0; @@ -5475,12 +5500,14 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. private final class LocalServiceImpl extends InputMethodManagerInternal { + @ImfLockFree @Override public void setInteractive(boolean interactive) { // Do everything in handler so as not to block the caller. mHandler.obtainMessage(MSG_SET_INTERACTIVE, interactive ? 1 : 0, 0).sendToTarget(); } + @ImfLockFree @Override public void hideAllInputMethods(@SoftInputShowHideReason int reason, int originatingDisplayId) { @@ -5566,6 +5593,7 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. } } + @ImfLockFree @Override public void registerInputMethodListListener(InputMethodListListener listener) { mInputMethodListListeners.addIfAbsent(listener); @@ -5613,11 +5641,13 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. } } + @ImfLockFree @Override public void removeImeSurface(int displayId) { mHandler.obtainMessage(MSG_REMOVE_IME_SURFACE).sendToTarget(); } + @ImfLockFree @Override public void updateImeWindowStatus(boolean disableImeIcon, int displayId) { mHandler.obtainMessage(MSG_UPDATE_IME_WINDOW_STATUS, disableImeIcon ? 1 : 0, 0) @@ -5695,6 +5725,7 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. } } + @ImfLockFree @Override public void maybeFinishStylusHandwriting() { mHandler.removeMessages(MSG_FINISH_HANDWRITING); diff --git a/services/core/java/com/android/server/locksettings/LockSettingsService.java b/services/core/java/com/android/server/locksettings/LockSettingsService.java index b3ab229927fe..ae3d36acdb7f 100644 --- a/services/core/java/com/android/server/locksettings/LockSettingsService.java +++ b/services/core/java/com/android/server/locksettings/LockSettingsService.java @@ -593,7 +593,7 @@ public class LockSettingsService extends ILockSettings.Stub { public RebootEscrowManager getRebootEscrowManager(RebootEscrowManager.Callbacks callbacks, LockSettingsStorage storage) { return new RebootEscrowManager(mContext, callbacks, storage, - getHandler(getServiceThread())); + getHandler(getServiceThread()), getUserManagerInternal()); } public int binderGetCallingUid() { diff --git a/services/core/java/com/android/server/locksettings/RebootEscrowManager.java b/services/core/java/com/android/server/locksettings/RebootEscrowManager.java index e1cd2c585146..d0b8990e37c4 100644 --- a/services/core/java/com/android/server/locksettings/RebootEscrowManager.java +++ b/services/core/java/com/android/server/locksettings/RebootEscrowManager.java @@ -51,16 +51,20 @@ import com.android.internal.annotations.VisibleForTesting; import com.android.internal.util.FrameworkStatsLog; import com.android.internal.util.IndentingPrintWriter; import com.android.internal.widget.RebootEscrowListener; +import com.android.server.pm.UserManagerInternal; import java.io.IOException; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.text.SimpleDateFormat; import java.util.ArrayList; +import java.util.Collections; import java.util.Date; +import java.util.HashSet; import java.util.List; import java.util.Locale; import java.util.Objects; +import java.util.Set; import javax.crypto.SecretKey; @@ -138,6 +142,7 @@ class RebootEscrowManager { ERROR_KEYSTORE_FAILURE, ERROR_NO_NETWORK, ERROR_TIMEOUT_EXHAUSTED, + ERROR_NO_REBOOT_ESCROW_DATA, }) @Retention(RetentionPolicy.SOURCE) @interface RebootEscrowErrorCode { @@ -153,6 +158,7 @@ class RebootEscrowManager { static final int ERROR_KEYSTORE_FAILURE = 7; static final int ERROR_NO_NETWORK = 8; static final int ERROR_TIMEOUT_EXHAUSTED = 9; + static final int ERROR_NO_REBOOT_ESCROW_DATA = 10; private @RebootEscrowErrorCode int mLoadEscrowDataErrorCode = ERROR_NONE; @@ -222,11 +228,16 @@ class RebootEscrowManager { private final RebootEscrowKeyStoreManager mKeyStoreManager; private final LockSettingsStorage mStorage; private RebootEscrowProviderInterface mRebootEscrowProvider; + private final UserManagerInternal mUserManagerInternal; - Injector(Context context, LockSettingsStorage storage) { + Injector( + Context context, + LockSettingsStorage storage, + UserManagerInternal userManagerInternal) { mContext = context; mStorage = storage; mKeyStoreManager = new RebootEscrowKeyStoreManager(); + mUserManagerInternal = userManagerInternal; } private RebootEscrowProviderInterface createRebootEscrowProvider() { @@ -326,6 +337,10 @@ class RebootEscrowManager { return (UserManager) mContext.getSystemService(Context.USER_SERVICE); } + public UserManagerInternal getUserManagerInternal() { + return mUserManagerInternal; + } + public RebootEscrowKeyStoreManager getKeyStoreManager() { return mKeyStoreManager; } @@ -402,8 +417,8 @@ class RebootEscrowManager { } RebootEscrowManager(Context context, Callbacks callbacks, LockSettingsStorage storage, - Handler handler) { - this(new Injector(context, storage), callbacks, storage, handler); + Handler handler, UserManagerInternal userManagerInternal) { + this(new Injector(context, storage, userManagerInternal), callbacks, storage, handler); } @VisibleForTesting @@ -451,18 +466,50 @@ class RebootEscrowManager { onEscrowRestoreComplete(false, attemptCount, retryHandler); } - void loadRebootEscrowDataIfAvailable(Handler retryHandler) { - List<UserInfo> users = mUserManager.getUsers(); + private List<UserInfo> getUsersToUnlock(List<UserInfo> users) { + // System user must be unlocked to unlock any other user + if (mCallbacks.isUserSecure(USER_SYSTEM) && !mStorage.hasRebootEscrow(USER_SYSTEM)) { + Slog.i(TAG, "No reboot escrow data found for system user"); + return Collections.emptyList(); + } + + Set<Integer> noEscrowDataUsers = new HashSet<>(); + for (UserInfo user : users) { + if (mCallbacks.isUserSecure(user.id) + && !mStorage.hasRebootEscrow(user.id)) { + Slog.d(TAG, "No reboot escrow data found for user " + user); + noEscrowDataUsers.add(user.id); + } + } + List<UserInfo> rebootEscrowUsers = new ArrayList<>(); for (UserInfo user : users) { - if (mCallbacks.isUserSecure(user.id) && mStorage.hasRebootEscrow(user.id)) { - rebootEscrowUsers.add(user); + // No lskf, no need to unlock. + if (!mCallbacks.isUserSecure(user.id)) { + continue; + } + // Don't unlock if user or user's parent does not have reboot data + int userId = user.id; + if (noEscrowDataUsers.contains(userId) + || noEscrowDataUsers.contains( + mInjector.getUserManagerInternal().getProfileParentId(userId))) { + continue; } + rebootEscrowUsers.add(user); } + return rebootEscrowUsers; + } + + void loadRebootEscrowDataIfAvailable(Handler retryHandler) { + List<UserInfo> users = mUserManager.getUsers(); + List<UserInfo> rebootEscrowUsers = getUsersToUnlock(users); if (rebootEscrowUsers.isEmpty()) { Slog.i(TAG, "No reboot escrow data found for users," + " skipping loading escrow data"); + setLoadEscrowDataErrorCode(ERROR_NO_REBOOT_ESCROW_DATA, retryHandler); + reportMetricOnRestoreComplete( + /* success= */ false, /* attemptCount= */ 1, retryHandler); clearMetricsStorage(); return; } diff --git a/services/core/java/com/android/server/media/MediaRoute2Provider.java b/services/core/java/com/android/server/media/MediaRoute2Provider.java index 363684f618cc..09605fefe80e 100644 --- a/services/core/java/com/android/server/media/MediaRoute2Provider.java +++ b/services/core/java/com/android/server/media/MediaRoute2Provider.java @@ -59,7 +59,7 @@ abstract class MediaRoute2Provider { public abstract void requestCreateSession( long requestId, String packageName, - String routeId, + String routeOriginalId, @Nullable Bundle sessionHints, @RoutingSessionInfo.TransferReason int transferReason, @NonNull UserHandle transferInitiatorUserHandle, @@ -77,13 +77,15 @@ abstract class MediaRoute2Provider { long requestId, @NonNull UserHandle transferInitiatorUserHandle, @NonNull String transferInitiatorPackageName, - String sessionId, - String routeId, + String sessionOriginalId, + String routeOriginalId, @RoutingSessionInfo.TransferReason int transferReason); - public abstract void setRouteVolume(long requestId, String routeId, int volume); - public abstract void setSessionVolume(long requestId, String sessionId, int volume); - public abstract void prepareReleaseSession(@NonNull String sessionId); + public abstract void setRouteVolume(long requestId, String routeOriginalId, int volume); + + public abstract void setSessionVolume(long requestId, String sessionOriginalId, int volume); + + public abstract void prepareReleaseSession(@NonNull String sessionUniqueId); @NonNull public String getUniqueId() { @@ -197,8 +199,8 @@ abstract class MediaRoute2Provider { */ public final long mRequestId; - /** The {@link MediaRoute2Info#getId() id} of the target route. */ - @NonNull public final String mTargetRouteId; + /** The {@link MediaRoute2Info#getOriginalId()} original id} of the target route. */ + @NonNull public final String mTargetOriginalRouteId; @RoutingSessionInfo.TransferReason public final int mTransferReason; @@ -209,23 +211,23 @@ abstract class MediaRoute2Provider { SessionCreationOrTransferRequest( long requestId, - @NonNull String routeId, + @NonNull String targetOriginalRouteId, @RoutingSessionInfo.TransferReason int transferReason, @NonNull UserHandle transferInitiatorUserHandle, @NonNull String transferInitiatorPackageName) { mRequestId = requestId; - mTargetRouteId = routeId; + mTargetOriginalRouteId = targetOriginalRouteId; mTransferReason = transferReason; mTransferInitiatorUserHandle = transferInitiatorUserHandle; mTransferInitiatorPackageName = transferInitiatorPackageName; } public boolean isTargetRoute(@Nullable MediaRoute2Info route2Info) { - return route2Info != null && mTargetRouteId.equals(route2Info.getId()); + return route2Info != null && mTargetOriginalRouteId.equals(route2Info.getOriginalId()); } - public boolean isTargetRouteIdInList(@NonNull List<String> routesList) { - return routesList.stream().anyMatch(mTargetRouteId::equals); + public boolean isTargetRouteIdInList(@NonNull List<String> routeOriginalIdList) { + return routeOriginalIdList.stream().anyMatch(mTargetOriginalRouteId::equals); } } } diff --git a/services/core/java/com/android/server/media/MediaRoute2ProviderServiceProxy.java b/services/core/java/com/android/server/media/MediaRoute2ProviderServiceProxy.java index 386657e99e36..71cbcb91100f 100644 --- a/services/core/java/com/android/server/media/MediaRoute2ProviderServiceProxy.java +++ b/services/core/java/com/android/server/media/MediaRoute2ProviderServiceProxy.java @@ -103,13 +103,14 @@ final class MediaRoute2ProviderServiceProxy extends MediaRoute2Provider public void requestCreateSession( long requestId, String packageName, - String routeId, + String routeOriginalId, Bundle sessionHints, @RoutingSessionInfo.TransferReason int transferReason, @NonNull UserHandle transferInitiatorUserHandle, @NonNull String transferInitiatorPackageName) { if (mConnectionReady) { - mActiveConnection.requestCreateSession(requestId, packageName, routeId, sessionHints); + mActiveConnection.requestCreateSession( + requestId, packageName, routeOriginalId, sessionHints); updateBinding(); } } @@ -153,35 +154,35 @@ final class MediaRoute2ProviderServiceProxy extends MediaRoute2Provider long requestId, @NonNull UserHandle transferInitiatorUserHandle, @NonNull String transferInitiatorPackageName, - String sessionId, - String routeId, + String sessionOriginalId, + String routeOriginalId, @RoutingSessionInfo.TransferReason int transferReason) { if (mConnectionReady) { - mActiveConnection.transferToRoute(requestId, sessionId, routeId); + mActiveConnection.transferToRoute(requestId, sessionOriginalId, routeOriginalId); } } @Override - public void setRouteVolume(long requestId, String routeId, int volume) { + public void setRouteVolume(long requestId, String routeOriginalId, int volume) { if (mConnectionReady) { - mActiveConnection.setRouteVolume(requestId, routeId, volume); + mActiveConnection.setRouteVolume(requestId, routeOriginalId, volume); updateBinding(); } } @Override - public void setSessionVolume(long requestId, String sessionId, int volume) { + public void setSessionVolume(long requestId, String sessionOriginalId, int volume) { if (mConnectionReady) { - mActiveConnection.setSessionVolume(requestId, sessionId, volume); + mActiveConnection.setSessionVolume(requestId, sessionOriginalId, volume); updateBinding(); } } @Override - public void prepareReleaseSession(@NonNull String sessionId) { + public void prepareReleaseSession(@NonNull String sessionUniqueId) { synchronized (mLock) { for (RoutingSessionInfo session : mSessionInfos) { - if (TextUtils.equals(session.getId(), sessionId)) { + if (TextUtils.equals(session.getId(), sessionUniqueId)) { mSessionInfos.remove(session); mReleasingSessions.add(session); break; diff --git a/services/core/java/com/android/server/media/SystemMediaRoute2Provider.java b/services/core/java/com/android/server/media/SystemMediaRoute2Provider.java index 76930a003e46..6b409ee6f482 100644 --- a/services/core/java/com/android/server/media/SystemMediaRoute2Provider.java +++ b/services/core/java/com/android/server/media/SystemMediaRoute2Provider.java @@ -158,20 +158,20 @@ class SystemMediaRoute2Provider extends MediaRoute2Provider { public void requestCreateSession( long requestId, String packageName, - String routeId, + String routeOriginalId, Bundle sessionHints, @RoutingSessionInfo.TransferReason int transferReason, @NonNull UserHandle transferInitiatorUserHandle, @NonNull String transferInitiatorPackageName) { // Assume a router without MODIFY_AUDIO_ROUTING permission can't request with // a route ID different from the default route ID. The service should've filtered. - if (TextUtils.equals(routeId, MediaRoute2Info.ROUTE_ID_DEFAULT)) { + if (TextUtils.equals(routeOriginalId, MediaRoute2Info.ROUTE_ID_DEFAULT)) { mCallback.onSessionCreated(this, requestId, mDefaultSessionInfo); return; } if (!Flags.enableBuiltInSpeakerRouteSuitabilityStatuses()) { - if (TextUtils.equals(routeId, mSelectedRouteId)) { + if (TextUtils.equals(routeOriginalId, mSelectedRouteId)) { RoutingSessionInfo currentSessionInfo; synchronized (mLock) { currentSessionInfo = mSessionInfos.get(0); @@ -192,7 +192,7 @@ class SystemMediaRoute2Provider extends MediaRoute2Provider { mPendingSessionCreationOrTransferRequest = new SessionCreationOrTransferRequest( requestId, - routeId, + routeOriginalId, RoutingSessionInfo.TRANSFER_REASON_FALLBACK, transferInitiatorUserHandle, transferInitiatorPackageName); @@ -204,7 +204,7 @@ class SystemMediaRoute2Provider extends MediaRoute2Provider { transferInitiatorUserHandle, transferInitiatorPackageName, SYSTEM_SESSION_ID, - routeId, + routeOriginalId, transferReason); } @@ -234,15 +234,15 @@ class SystemMediaRoute2Provider extends MediaRoute2Provider { long requestId, @NonNull UserHandle transferInitiatorUserHandle, @NonNull String transferInitiatorPackageName, - String sessionId, - String routeId, + String sessionOriginalId, + String routeOriginalId, @RoutingSessionInfo.TransferReason int transferReason) { String selectedDeviceRouteId = mDeviceRouteController.getSelectedRoute().getId(); - if (TextUtils.equals(routeId, MediaRoute2Info.ROUTE_ID_DEFAULT)) { + if (TextUtils.equals(routeOriginalId, MediaRoute2Info.ROUTE_ID_DEFAULT)) { if (Flags.enableBuiltInSpeakerRouteSuitabilityStatuses()) { // Transfer to the default route (which is the selected route). We replace the id to // be the selected route id so that the transfer reason gets updated. - routeId = selectedDeviceRouteId; + routeOriginalId = selectedDeviceRouteId; } else { Log.w(TAG, "Ignoring transfer to " + MediaRoute2Info.ROUTE_ID_DEFAULT); return; @@ -254,18 +254,18 @@ class SystemMediaRoute2Provider extends MediaRoute2Provider { mPendingTransferRequest = new SessionCreationOrTransferRequest( requestId, - routeId, + routeOriginalId, transferReason, transferInitiatorUserHandle, transferInitiatorPackageName); } } - String finalRouteId = routeId; // Make a final copy to use it in the lambda. + String finalRouteId = routeOriginalId; // Make a final copy to use it in the lambda. boolean isAvailableDeviceRoute = mDeviceRouteController.getAvailableRoutes().stream() .anyMatch(it -> it.getId().equals(finalRouteId)); - boolean isSelectedDeviceRoute = TextUtils.equals(routeId, selectedDeviceRouteId); + boolean isSelectedDeviceRoute = TextUtils.equals(routeOriginalId, selectedDeviceRouteId); if (isSelectedDeviceRoute || isAvailableDeviceRoute) { // The requested route is managed by the device route controller. Note that the selected @@ -273,12 +273,12 @@ class SystemMediaRoute2Provider extends MediaRoute2Provider { // of the routing session). If the selected device route is transferred to, we need to // make the bluetooth routes inactive so that the device route becomes the selected // route of the routing session. - mDeviceRouteController.transferTo(routeId); + mDeviceRouteController.transferTo(routeOriginalId); mBluetoothRouteController.transferTo(null); } else { // The requested route is managed by the bluetooth route controller. mDeviceRouteController.transferTo(null); - mBluetoothRouteController.transferTo(routeId); + mBluetoothRouteController.transferTo(routeOriginalId); } if (Flags.enableBuiltInSpeakerRouteSuitabilityStatuses() @@ -288,20 +288,20 @@ class SystemMediaRoute2Provider extends MediaRoute2Provider { } @Override - public void setRouteVolume(long requestId, String routeId, int volume) { - if (!TextUtils.equals(routeId, mSelectedRouteId)) { + public void setRouteVolume(long requestId, String routeOriginalId, int volume) { + if (!TextUtils.equals(routeOriginalId, mSelectedRouteId)) { return; } mAudioManager.setStreamVolume(AudioManager.STREAM_MUSIC, volume, 0); } @Override - public void setSessionVolume(long requestId, String sessionId, int volume) { + public void setSessionVolume(long requestId, String sessionOriginalId, int volume) { // Do nothing since we don't support grouping volume yet. } @Override - public void prepareReleaseSession(String sessionId) { + public void prepareReleaseSession(String sessionUniqueId) { // Do nothing since the system session persists. } @@ -503,12 +503,13 @@ class SystemMediaRoute2Provider extends MediaRoute2Provider { } long pendingRequestId = mPendingSessionCreationOrTransferRequest.mRequestId; - if (mPendingSessionCreationOrTransferRequest.mTargetRouteId.equals(mSelectedRouteId)) { + if (mPendingSessionCreationOrTransferRequest.mTargetOriginalRouteId.equals( + mSelectedRouteId)) { if (DEBUG) { Slog.w( TAG, "Session creation success to route " - + mPendingSessionCreationOrTransferRequest.mTargetRouteId); + + mPendingSessionCreationOrTransferRequest.mTargetOriginalRouteId); } mPendingSessionCreationOrTransferRequest = null; mCallback.onSessionCreated(this, pendingRequestId, newSessionInfo); @@ -520,7 +521,8 @@ class SystemMediaRoute2Provider extends MediaRoute2Provider { Slog.w( TAG, "Session creation failed to route " - + mPendingSessionCreationOrTransferRequest.mTargetRouteId); + + mPendingSessionCreationOrTransferRequest + .mTargetOriginalRouteId); } mPendingSessionCreationOrTransferRequest = null; mCallback.onRequestFailed( @@ -529,7 +531,7 @@ class SystemMediaRoute2Provider extends MediaRoute2Provider { Slog.w( TAG, "Session creation waiting state to route " - + mPendingSessionCreationOrTransferRequest.mTargetRouteId); + + mPendingSessionCreationOrTransferRequest.mTargetOriginalRouteId); } } } @@ -541,7 +543,8 @@ class SystemMediaRoute2Provider extends MediaRoute2Provider { // See b/307723189 for context for (MediaRoute2Info btRoute : mBluetoothRouteController.getAllBluetoothRoutes()) { if (TextUtils.equals( - btRoute.getId(), mPendingSessionCreationOrTransferRequest.mTargetRouteId)) { + btRoute.getId(), + mPendingSessionCreationOrTransferRequest.mTargetOriginalRouteId)) { return true; } } diff --git a/services/core/java/com/android/server/pm/KillAppBlocker.java b/services/core/java/com/android/server/pm/KillAppBlocker.java new file mode 100644 index 000000000000..e2901c39e612 --- /dev/null +++ b/services/core/java/com/android/server/pm/KillAppBlocker.java @@ -0,0 +1,114 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.server.pm; + +import static android.app.ActivityManager.PROCESS_STATE_NONEXISTENT; +import static android.content.pm.PackageManager.MATCH_ALL; +import static android.os.Process.INVALID_UID; + +import android.app.ActivityManager; +import android.app.ActivityManagerInternal; +import android.app.IActivityManager; +import android.app.IUidObserver; +import android.app.UidObserver; +import android.os.Process; +import android.os.RemoteException; + +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; + +/** + * Use to monitor UIDs are really killed by the {@link IUidObserver} + */ +final class KillAppBlocker { + private static final int MAX_WAIT_TIMEOUT_MS = 1000; + private CountDownLatch mUidsGoneCountDownLatch = new CountDownLatch(1); + private List mActiveUids = new ArrayList(); + private boolean mRegistered = false; + + private final IUidObserver mUidObserver = new UidObserver() { + @Override + public void onUidGone(int uid, boolean disabled) { + synchronized (this) { + mActiveUids.remove((Integer) uid); + + if (mActiveUids.size() == 0) { + mUidsGoneCountDownLatch.countDown(); + } + } + } + }; + + void register() { + if (!mRegistered) { + IActivityManager am = ActivityManager.getService(); + if (am != null) { + try { + am.registerUidObserver(mUidObserver, ActivityManager.UID_OBSERVER_GONE, + ActivityManager.PROCESS_STATE_UNKNOWN, "pm"); + mRegistered = true; + } catch (RemoteException e) { + // no-op + } + } + } + } + + void unregister() { + if (mRegistered) { + IActivityManager am = ActivityManager.getService(); + if (am != null) { + try { + mRegistered = false; + am.unregisterUidObserver(mUidObserver); + } catch (RemoteException e) { + // no-op + } + } + } + } + + void waitAppProcessGone(ActivityManagerInternal mAmi, Computer snapshot, + UserManagerService userManager, String packageName) { + if (!mRegistered) { + return; + } + synchronized (this) { + if (mAmi != null) { + int[] users = userManager.getUserIds(); + + for (int i = 0; i < users.length; i++) { + final int userId = users[i]; + final int uid = snapshot.getPackageUidInternal( + packageName, MATCH_ALL, userId, Process.SYSTEM_UID); + if (uid != INVALID_UID) { + if (mAmi.getUidProcessState(uid) != PROCESS_STATE_NONEXISTENT) { + mActiveUids.add(uid); + } + } + } + } + } + + try { + mUidsGoneCountDownLatch.await(MAX_WAIT_TIMEOUT_MS, TimeUnit.MILLISECONDS); + } catch (InterruptedException e) { + // no-op + } + } +} diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java index f8fceda0582c..679ab65380af 100644 --- a/services/core/java/com/android/server/pm/PackageManagerService.java +++ b/services/core/java/com/android/server/pm/PackageManagerService.java @@ -3136,13 +3136,18 @@ public class PackageManagerService implements PackageSender, TestUtilityService void killApplicationSync(String pkgName, @AppIdInt int appId, @UserIdInt int userId, String reason, int exitInfoReason) { ActivityManagerInternal mAmi = LocalServices.getService(ActivityManagerInternal.class); - if (mAmi != null) { - if (Thread.holdsLock(mLock)) { - // holds PM's lock, go back killApplication to avoid it run into watchdog reset. - Slog.e(TAG, "Holds PM's locker, unable kill application synchronized"); - killApplication(pkgName, appId, userId, reason, exitInfoReason); - } else { + if (Thread.holdsLock(mLock) || mAmi == null) { + // holds PM's lock, go back killApplication to avoid it run into watchdog reset. + Slog.e(TAG, "Holds PM's lock, unable kill application synchronized"); + killApplication(pkgName, appId, userId, reason, exitInfoReason); + } else { + KillAppBlocker blocker = new KillAppBlocker(); + try { + blocker.register(); mAmi.killApplicationSync(pkgName, appId, userId, reason, exitInfoReason); + blocker.waitAppProcessGone(mAmi, snapshotComputer(), mUserManager, pkgName); + } finally { + blocker.unregister(); } } } @@ -4052,9 +4057,6 @@ public class PackageManagerService implements PackageSender, TestUtilityService return; } - // Log the metrics when the component state is changed. - PackageMetrics.reportComponentStateChanged(computer, componentStateMetricsList, userId); - if (isSynchronous) { flushPackageRestrictionsAsUserInternalLocked(userId); } else { @@ -4074,6 +4076,10 @@ public class PackageManagerService implements PackageSender, TestUtilityService } } + // Log the metrics when the component state is changed. + PackageMetrics.reportComponentStateChanged(snapshotComputer(), componentStateMetricsList, + userId); + final long callingId = Binder.clearCallingIdentity(); try { final Computer newSnapshot = snapshotComputer(); diff --git a/services/core/java/com/android/server/rollback/RollbackManagerServiceImpl.java b/services/core/java/com/android/server/rollback/RollbackManagerServiceImpl.java index c8bcc5128c3a..e753ce845ddc 100644 --- a/services/core/java/com/android/server/rollback/RollbackManagerServiceImpl.java +++ b/services/core/java/com/android/server/rollback/RollbackManagerServiceImpl.java @@ -926,7 +926,9 @@ class RollbackManagerServiceImpl extends IRollbackManager.Stub implements Rollba } Slog.i(TAG, "Enabling rollback for install of " + packageName + ", session:" + session.sessionId - + ", rollbackDataPolicy=" + rollbackDataPolicy); + + ", rollbackDataPolicy=" + rollbackDataPolicy + + ", rollbackId:" + rollback.info.getRollbackId() + + ", originalSessionId:" + rollback.getOriginalSessionId()); final String installerPackageName = session.getInstallerPackageName(); if (!enableRollbackAllowed(installerPackageName, packageName)) { diff --git a/services/core/java/com/android/server/trust/TrustManagerService.java b/services/core/java/com/android/server/trust/TrustManagerService.java index f4b61e7b2d4a..04db3e83ac71 100644 --- a/services/core/java/com/android/server/trust/TrustManagerService.java +++ b/services/core/java/com/android/server/trust/TrustManagerService.java @@ -275,6 +275,10 @@ public class TrustManagerService extends SystemService { return KeyStoreAuthorization.getInstance(); } + AlarmManager getAlarmManager() { + return mContext.getSystemService(AlarmManager.class); + } + Looper getLooper() { return Looper.myLooper(); } @@ -293,7 +297,7 @@ public class TrustManagerService extends SystemService { mLockPatternUtils = injector.getLockPatternUtils(); mKeyStoreAuthorization = injector.getKeyStoreAuthorization(); mStrongAuthTracker = new StrongAuthTracker(context, injector.getLooper()); - mAlarmManager = (AlarmManager) mContext.getSystemService(Context.ALARM_SERVICE); + mAlarmManager = injector.getAlarmManager(); } @Override diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java index 7aa579102622..2d2a88a866ba 100644 --- a/services/core/java/com/android/server/wm/ActivityRecord.java +++ b/services/core/java/com/android/server/wm/ActivityRecord.java @@ -3964,7 +3964,7 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A } if (isCurrentVisible) { - if (isNextNotYetVisible || delayRemoval) { + if (isNextNotYetVisible || delayRemoval || (next != null && isInTransition())) { // Add this activity to the list of stopping activities. It will be processed and // destroyed when the next activity reports idle. addToStopping(false /* scheduleIdle */, false /* idleDelayed */, @@ -9261,6 +9261,19 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A @NonNull CompatDisplayInsets compatDisplayInsets) { final Configuration resolvedConfig = getResolvedOverrideConfiguration(); final Rect resolvedBounds = resolvedConfig.windowConfiguration.getBounds(); + final Insets insets; + if (mResolveConfigHint.mUseOverrideInsetsForConfig) { + // TODO(b/343197837): Add test to verify SCM behaviour with new bound configuration + // Insets are decoupled from configuration by default from V+, use legacy + // compatibility behaviour for apps targeting SDK earlier than 35 + // (see applySizeOverrideIfNeeded). + insets = Insets.of(mDisplayContent.getDisplayPolicy() + .getDecorInsetsInfo(mDisplayContent.mDisplayFrames.mRotation, + mDisplayContent.mDisplayFrames.mWidth, + mDisplayContent.mDisplayFrames.mHeight).mOverrideNonDecorInsets); + } else { + insets = Insets.NONE; + } // When an activity needs to be letterboxed because of fixed orientation, use fixed // orientation bounds (stored in resolved bounds) instead of parent bounds since the @@ -9271,9 +9284,12 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A final Rect containerBounds = useResolvedBounds ? new Rect(resolvedBounds) : newParentConfiguration.windowConfiguration.getBounds(); + final Rect parentAppBounds = + newParentConfiguration.windowConfiguration.getAppBounds(); + parentAppBounds.inset(insets); final Rect containerAppBounds = useResolvedBounds ? new Rect(resolvedConfig.windowConfiguration.getAppBounds()) - : newParentConfiguration.windowConfiguration.getAppBounds(); + : parentAppBounds; final int requestedOrientation = getRequestedConfigurationOrientation(); final boolean orientationRequested = requestedOrientation != ORIENTATION_UNDEFINED; diff --git a/services/core/java/com/android/server/wm/BackgroundActivityStartController.java b/services/core/java/com/android/server/wm/BackgroundActivityStartController.java index 207707efb51d..ac2c886d1b66 100644 --- a/services/core/java/com/android/server/wm/BackgroundActivityStartController.java +++ b/services/core/java/com/android/server/wm/BackgroundActivityStartController.java @@ -28,6 +28,7 @@ import static android.content.pm.PackageManager.PERMISSION_GRANTED; import static android.os.Build.VERSION_CODES.UPSIDE_DOWN_CAKE; import static android.os.Process.INVALID_PID; import static android.os.Process.INVALID_UID; +import static android.os.Process.ROOT_UID; import static android.os.Process.SYSTEM_UID; import static android.provider.DeviceConfig.NAMESPACE_WINDOW_MANAGER; @@ -385,6 +386,10 @@ public class BackgroundActivityStartController { return BackgroundStartPrivileges.NONE; case MODE_BACKGROUND_ACTIVITY_START_SYSTEM_DEFINED: // no explicit choice by the app - let us decide what to do + if (callingUid == ROOT_UID || callingUid == SYSTEM_UID) { + // root and system must always opt in explicitly + return BackgroundStartPrivileges.NONE; + } if (callingPackage != null) { // determine based on the calling/creating package boolean changeEnabled = CompatChanges.isChangeEnabled( diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java index 94a22394cf41..a00b6fc47de7 100644 --- a/services/core/java/com/android/server/wm/WindowManagerService.java +++ b/services/core/java/com/android/server/wm/WindowManagerService.java @@ -1927,7 +1927,7 @@ public class WindowManagerService extends IWindowManager.Stub displayContent.getInsetsStateController().updateAboveInsetsState( false /* notifyInsetsChanged */); - outInsetsState.set(win.getCompatInsetsState(), true /* copySources */); + win.fillInsetsState(outInsetsState, true /* copySources */); getInsetsSourceControls(win, outActiveControls); if (win.mLayoutAttached) { @@ -2680,7 +2680,7 @@ public class WindowManagerService extends IWindowManager.Stub } if (outInsetsState != null) { - outInsetsState.set(win.getCompatInsetsState(), true /* copySources */); + win.fillInsetsState(outInsetsState, true /* copySources */); } ProtoLog.v(WM_DEBUG_FOCUS, "Relayout of %s: focusMayChange=%b", @@ -2743,12 +2743,10 @@ public class WindowManagerService extends IWindowManager.Stub } private void getInsetsSourceControls(WindowState win, InsetsSourceControl.Array outArray) { - final InsetsSourceControl[] controls = - win.getDisplayContent().getInsetsStateController().getControlsForDispatch(win); // We will leave the critical section before returning the leash to the client, // so we need to copy the leash to prevent others release the one that we are // about to return. - outArray.set(controls, true /* copyControls */); + win.fillInsetsSourceControls(outArray, true /* copyControls */); // This source control is an extra copy if the client is not local. By setting // PARCELABLE_WRITE_RETURN_VALUE, the leash will be released at the end of // SurfaceControl.writeToParcel. diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java index d7c49ac81a6c..d1efc27d9661 100644 --- a/services/core/java/com/android/server/wm/WindowState.java +++ b/services/core/java/com/android/server/wm/WindowState.java @@ -434,7 +434,10 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP /** @see #isLastConfigReportedToClient() */ private boolean mLastConfigReportedToClient; - // TODO(b/339380439): Ensure to use the same object for IWindowSession#relayout + private final ClientWindowFrames mLastReportedFrames = new ClientWindowFrames(); + + private final InsetsState mLastReportedInsetsState = new InsetsState(); + private final InsetsSourceControl.Array mLastReportedActiveControls = new InsetsSourceControl.Array(); @@ -495,8 +498,6 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP private final WindowFrames mWindowFrames = new WindowFrames(); - private final ClientWindowFrames mClientWindowFrames = new ClientWindowFrames(); - /** * List of rects where system gestures should be ignored. * @@ -3650,8 +3651,10 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP outFrames.attachedFrame.scale(mInvGlobalScale); } } - outFrames.compatScale = getCompatScaleForClient(); + if (mLastReportedFrames != outFrames) { + mLastReportedFrames.setTo(outFrames); + } // Note: in the cases where the window is tied to an activity, we should not send a // configuration update when the window has requested to be hidden. Doing so can lead to @@ -3678,6 +3681,25 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP mLastConfigReportedToClient = true; } + void fillInsetsState(@NonNull InsetsState outInsetsState, boolean copySources) { + outInsetsState.set(getCompatInsetsState(), copySources); + if (outInsetsState != mLastReportedInsetsState) { + // No need to copy for the recorded. + mLastReportedInsetsState.set(outInsetsState, false /* copySources */); + } + } + + void fillInsetsSourceControls(@NonNull InsetsSourceControl.Array outArray, + boolean copyControls) { + final InsetsSourceControl[] controls = + getDisplayContent().getInsetsStateController().getControlsForDispatch(this); + outArray.set(controls, copyControls); + if (outArray != mLastReportedActiveControls) { + // No need to copy for the recorded. + mLastReportedActiveControls.setTo(outArray, false /* copyControls */); + } + } + void reportResized() { // If the activity is scheduled to relaunch, skip sending the resized to ViewRootImpl now // since it will be destroyed anyway. This also prevents the client from receiving @@ -3712,9 +3734,10 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP final int prevRotation = mLastReportedConfiguration .getMergedConfiguration().windowConfiguration.getRotation(); - fillClientWindowFramesAndConfiguration(mClientWindowFrames, mLastReportedConfiguration, + fillClientWindowFramesAndConfiguration(mLastReportedFrames, mLastReportedConfiguration, mLastReportedActivityWindowInfo, true /* useLatestConfig */, false /* relayoutVisible */); + fillInsetsState(mLastReportedInsetsState, false /* copySources */); final boolean syncRedraw = shouldSendRedrawForSync(); final boolean syncWithBuffers = syncRedraw && shouldSyncWithBuffers(); final boolean reportDraw = syncRedraw || drawPending; @@ -3734,8 +3757,8 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP if (Flags.bundleClientTransactionFlag()) { getProcess().scheduleClientTransactionItem( - WindowStateResizeItem.obtain(mClient, mClientWindowFrames, reportDraw, - mLastReportedConfiguration, getCompatInsetsState(), forceRelayout, + WindowStateResizeItem.obtain(mClient, mLastReportedFrames, reportDraw, + mLastReportedConfiguration, mLastReportedInsetsState, forceRelayout, alwaysConsumeSystemBars, displayId, syncWithBuffers ? mSyncSeqId : -1, isDragResizing, mLastReportedActivityWindowInfo)); @@ -3743,8 +3766,8 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP } else { // TODO(b/301870955): cleanup after launch try { - mClient.resized(mClientWindowFrames, reportDraw, mLastReportedConfiguration, - getCompatInsetsState(), forceRelayout, alwaysConsumeSystemBars, displayId, + mClient.resized(mLastReportedFrames, reportDraw, mLastReportedConfiguration, + mLastReportedInsetsState, forceRelayout, alwaysConsumeSystemBars, displayId, syncWithBuffers ? mSyncSeqId : -1, isDragResizing, mLastReportedActivityWindowInfo); onResizePostDispatched(drawPending, prevRotation, displayId); @@ -3817,17 +3840,14 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP if (mRemoved) { return; } - final InsetsStateController stateController = - getDisplayContent().getInsetsStateController(); - final InsetsState insetsState = getCompatInsetsState(); - mLastReportedActiveControls.set(stateController.getControlsForDispatch(this), - false /* copyControls */); + fillInsetsState(mLastReportedInsetsState, false /* copySources */); + fillInsetsSourceControls(mLastReportedActiveControls, false /* copyControls */); if (Flags.insetsControlChangedItem()) { getProcess().scheduleClientTransactionItem(WindowStateInsetsControlChangeItem.obtain( - mClient, insetsState, mLastReportedActiveControls)); + mClient, mLastReportedInsetsState, mLastReportedActiveControls)); } else { try { - mClient.insetsControlChanged(insetsState, mLastReportedActiveControls); + mClient.insetsControlChanged(mLastReportedInsetsState, mLastReportedActiveControls); } catch (RemoteException e) { Slog.w(TAG, "Failed to deliver inset control state change to w=" + this, e); } diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java index 927df8bb23e7..cfe4e17eb1be 100644 --- a/services/java/com/android/server/SystemServer.java +++ b/services/java/com/android/server/SystemServer.java @@ -761,9 +761,6 @@ public final class SystemServer implements Dumpable { } } - private static final long BINDER_CALLBACK_THROTTLE_MS = 10_100L; - private long mBinderCallbackLast = -1; - private void run() { TimingsTraceAndSlog t = new TimingsTraceAndSlog(); try { @@ -968,14 +965,6 @@ public final class SystemServer implements Dumpable { Binder.setTransactionCallback(new IBinderCallback() { @Override public void onTransactionError(int pid, int code, int flags, int err) { - - final long now = SystemClock.uptimeMillis(); - if (now < mBinderCallbackLast + BINDER_CALLBACK_THROTTLE_MS) { - Slog.d(TAG, "Too many transaction errors, throttling freezer binder callback."); - return; - } - mBinderCallbackLast = now; - Slog.wtfStack(TAG, "Binder Transaction Error"); mActivityManagerService.frozenBinderTransactionDetected(pid, code, flags, err); } }); diff --git a/services/permission/java/com/android/server/permission/access/appop/AppOpService.kt b/services/permission/java/com/android/server/permission/access/appop/AppOpService.kt index b0c7073d792b..3bdcd9b0fdcd 100644 --- a/services/permission/java/com/android/server/permission/access/appop/AppOpService.kt +++ b/services/permission/java/com/android/server/permission/access/appop/AppOpService.kt @@ -18,6 +18,7 @@ package com.android.server.permission.access.appop import android.app.AppOpsManager import android.companion.virtual.VirtualDeviceManager +import android.os.Binder import android.os.Handler import android.os.UserHandle import android.permission.PermissionManager @@ -250,7 +251,9 @@ class AppOpService(private val service: AccessCheckingService) : AppOpsCheckingS ) { Slog.w( LOG_TAG, - "Cannot set UID mode for runtime permission app op, uid = $uid," + + "Cannot set UID mode for runtime permission app op, " + + " callingUid = ${Binder.getCallingUid()}, " + + " uid = $uid," + " code = ${AppOpsManager.opToName(code)}," + " mode = ${AppOpsManager.modeToName(mode)}", RuntimeException() diff --git a/services/tests/mockingservicestests/src/com/android/server/trust/TrustManagerServiceTest.java b/services/tests/mockingservicestests/src/com/android/server/trust/TrustManagerServiceTest.java index 0532e04257d4..2a670294a470 100644 --- a/services/tests/mockingservicestests/src/com/android/server/trust/TrustManagerServiceTest.java +++ b/services/tests/mockingservicestests/src/com/android/server/trust/TrustManagerServiceTest.java @@ -16,6 +16,8 @@ package com.android.server.trust; +import static android.service.trust.TrustAgentService.FLAG_GRANT_TRUST_TEMPORARY_AND_RENEWABLE; + import static com.android.dx.mockito.inline.extended.ExtendedMockito.any; import static com.android.dx.mockito.inline.extended.ExtendedMockito.anyBoolean; import static com.android.dx.mockito.inline.extended.ExtendedMockito.anyInt; @@ -26,12 +28,22 @@ import static com.android.dx.mockito.inline.extended.ExtendedMockito.eq; import static com.android.dx.mockito.inline.extended.ExtendedMockito.mock; import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify; import static com.android.dx.mockito.inline.extended.ExtendedMockito.when; +import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_NOT_REQUIRED; +import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_USER_LOCKDOWN; import static com.google.common.truth.Truth.assertThat; +import static org.mockito.ArgumentMatchers.anyLong; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.times; + +import static java.util.Collections.singleton; + import android.Manifest; import android.annotation.Nullable; import android.app.ActivityManager; +import android.app.AlarmManager; import android.app.IActivityManager; import android.app.admin.DevicePolicyManager; import android.app.trust.ITrustListener; @@ -65,15 +77,22 @@ import android.platform.test.flag.junit.CheckFlagsRule; import android.platform.test.flag.junit.DeviceFlagsValueProvider; import android.provider.Settings; import android.security.KeyStoreAuthorization; +import android.service.trust.GrantTrustResult; +import android.service.trust.ITrustAgentService; +import android.service.trust.ITrustAgentServiceCallback; import android.service.trust.TrustAgentService; import android.testing.TestableContext; +import android.util.Log; import android.view.IWindowManager; import android.view.WindowManagerGlobal; import androidx.test.core.app.ApplicationProvider; +import com.android.internal.infra.AndroidFuture; import com.android.internal.widget.LockPatternUtils; import com.android.internal.widget.LockPatternUtils.StrongAuthTracker; +import com.android.internal.widget.LockPatternUtils.StrongAuthTracker.StrongAuthFlags; +import com.android.internal.widget.LockSettingsInternal; import com.android.modules.utils.testing.ExtendedMockitoRule; import com.android.server.LocalServices; import com.android.server.SystemService; @@ -81,15 +100,19 @@ import com.android.server.SystemServiceManager; import org.junit.After; import org.junit.Before; +import org.junit.Ignore; import org.junit.Rule; import org.junit.Test; import org.mockito.ArgumentCaptor; import org.mockito.ArgumentMatcher; import org.mockito.Mock; +import org.mockito.Mockito; import java.util.ArrayList; import java.util.Collection; +import java.util.HashMap; import java.util.List; +import java.util.Map; public class TrustManagerServiceTest { @@ -115,21 +138,28 @@ public class TrustManagerServiceTest { private static final int PROFILE_USER_ID = 70; private static final long[] PARENT_BIOMETRIC_SIDS = new long[] { 600L, 601L }; private static final long[] PROFILE_BIOMETRIC_SIDS = new long[] { 700L, 701L }; + private static final long RENEWABLE_TRUST_DURATION = 10000L; + private static final String GRANT_TRUST_MESSAGE = "granted"; + private static final String TAG = "TrustManagerServiceTest"; private final ArrayList<ResolveInfo> mTrustAgentResolveInfoList = new ArrayList<>(); private final ArrayList<ComponentName> mKnownTrustAgents = new ArrayList<>(); private final ArrayList<ComponentName> mEnabledTrustAgents = new ArrayList<>(); + private final Map<ComponentName, ITrustAgentService.Stub> mMockTrustAgents = new HashMap<>(); private @Mock ActivityManager mActivityManager; + private @Mock AlarmManager mAlarmManager; private @Mock BiometricManager mBiometricManager; private @Mock DevicePolicyManager mDevicePolicyManager; private @Mock FaceManager mFaceManager; private @Mock FingerprintManager mFingerprintManager; private @Mock KeyStoreAuthorization mKeyStoreAuthorization; private @Mock LockPatternUtils mLockPatternUtils; + private @Mock LockSettingsInternal mLockSettingsInternal; private @Mock PackageManager mPackageManager; private @Mock UserManager mUserManager; private @Mock IWindowManager mWindowManager; + private @Mock ITrustListener mTrustListener; private HandlerThread mHandlerThread; private TrustManagerService mService; @@ -158,6 +188,9 @@ public class TrustManagerServiceTest { return null; }).when(mLockPatternUtils).setEnabledTrustAgents(any(), eq(TEST_USER_ID)); + LocalServices.removeServiceForTest(LockSettingsInternal.class); + LocalServices.addService(LockSettingsInternal.class, mLockSettingsInternal); + ArgumentMatcher<Intent> trustAgentIntentMatcher = new ArgumentMatcher<Intent>() { @Override public boolean matches(Intent argument) { @@ -176,6 +209,7 @@ public class TrustManagerServiceTest { when(mWindowManager.isKeyguardLocked()).thenReturn(true); mMockContext.addMockSystemService(ActivityManager.class, mActivityManager); + mMockContext.addMockSystemService(AlarmManager.class, mAlarmManager); mMockContext.addMockSystemService(BiometricManager.class, mBiometricManager); mMockContext.addMockSystemService(FaceManager.class, mFaceManager); mMockContext.addMockSystemService(FingerprintManager.class, mFingerprintManager); @@ -197,6 +231,7 @@ public class TrustManagerServiceTest { verify(() -> ServiceManager.addService(eq(Context.TRUST_SERVICE), binderArgumentCaptor.capture(), anyBoolean(), anyInt())); mTrustManager = ITrustManager.Stub.asInterface(binderArgumentCaptor.getValue()); + mTrustManager.registerTrustListener(mTrustListener); } private class MockInjector extends TrustManagerService.Injector { @@ -215,6 +250,11 @@ public class TrustManagerServiceTest { } @Override + AlarmManager getAlarmManager() { + return mAlarmManager; + } + + @Override Looper getLooper() { return mHandlerThread.getLooper(); } @@ -367,12 +407,10 @@ public class TrustManagerServiceTest { @Test public void reportEnabledTrustAgentsChangedInformsListener() throws RemoteException { - final ITrustListener trustListener = mock(ITrustListener.class); - mTrustManager.registerTrustListener(trustListener); mService.waitForIdle(); mTrustManager.reportEnabledTrustAgentsChanged(TEST_USER_ID); mService.waitForIdle(); - verify(trustListener).onEnabledTrustAgentsChanged(TEST_USER_ID); + verify(mTrustListener).onEnabledTrustAgentsChanged(TEST_USER_ID); } // Tests that when the device is locked for a managed profile with a *unified* challenge, the @@ -416,6 +454,169 @@ public class TrustManagerServiceTest { } @Test + public void testSuccessfulUnlock_bindsTrustAgent() throws Exception { + when(mUserManager.getAliveUsers()) + .thenReturn(List.of(new UserInfo(TEST_USER_ID, "test", UserInfo.FLAG_FULL))); + ComponentName trustAgentName = + ComponentName.unflattenFromString("com.android/.SystemTrustAgent"); + ITrustAgentService trustAgentService = + setUpTrustAgentWithStrongAuthRequired( + trustAgentName, STRONG_AUTH_REQUIRED_AFTER_USER_LOCKDOWN); + + attemptSuccessfulUnlock(TEST_USER_ID); + mService.waitForIdle(); + + assertThat(getCallback(trustAgentService)).isNotNull(); + } + + @Test + public void testSuccessfulUnlock_notifiesTrustAgent() throws Exception { + ComponentName trustAgentName = + ComponentName.unflattenFromString("com.android/.SystemTrustAgent"); + ITrustAgentService trustAgentService = + setUpTrustAgentWithStrongAuthRequired( + trustAgentName, STRONG_AUTH_REQUIRED_AFTER_USER_LOCKDOWN); + + attemptSuccessfulUnlock(TEST_USER_ID); + mService.waitForIdle(); + + verify(trustAgentService).onUnlockAttempt(/* successful= */ true); + } + + @Test + public void testSuccessfulUnlock_notifiesTrustListenerOfChangeInManagedTrust() + throws Exception { + ComponentName trustAgentName = + ComponentName.unflattenFromString("com.android/.SystemTrustAgent"); + setUpTrustAgentWithStrongAuthRequired(trustAgentName, STRONG_AUTH_NOT_REQUIRED); + mService.waitForIdle(); + Mockito.reset(mTrustListener); + + attemptSuccessfulUnlock(TEST_USER_ID); + mService.waitForIdle(); + + verify(mTrustListener).onTrustManagedChanged(false, TEST_USER_ID); + } + + @Test + @Ignore("TODO: b/340891566 - Trustagent always refreshes trustable timer for user 0 on unlock") + public void testSuccessfulUnlock_refreshesTrustableTimers() throws Exception { + ComponentName trustAgentName = + ComponentName.unflattenFromString("com.android/.SystemTrustAgent"); + ITrustAgentService trustAgent = + setUpTrustAgentWithStrongAuthRequired(trustAgentName, STRONG_AUTH_NOT_REQUIRED); + setUpRenewableTrust(trustAgent); + + attemptSuccessfulUnlock(TEST_USER_ID); + mService.waitForIdle(); + + // Idle and hard timeout alarms for first renewable trust granted + // Idle timeout alarm refresh for second renewable trust granted + // Idle and hard timeout alarms refresh for last report + verify(mAlarmManager, times(3)) + .setExact( + eq(AlarmManager.ELAPSED_REALTIME_WAKEUP), + anyLong(), + anyString(), + any(AlarmManager.OnAlarmListener.class), + any(Handler.class)); + } + + @Test + public void testFailedUnlock_doesNotBindTrustAgent() throws Exception { + ComponentName trustAgentName = + ComponentName.unflattenFromString("com.android/.SystemTrustAgent"); + ITrustAgentService trustAgentService = + setUpTrustAgentWithStrongAuthRequired( + trustAgentName, STRONG_AUTH_REQUIRED_AFTER_USER_LOCKDOWN); + + attemptFailedUnlock(TEST_USER_ID); + mService.waitForIdle(); + + verify(trustAgentService, never()).setCallback(any()); + } + + @Test + public void testFailedUnlock_notifiesTrustAgent() throws Exception { + ComponentName trustAgentName = + ComponentName.unflattenFromString("com.android/.SystemTrustAgent"); + ITrustAgentService trustAgentService = + setUpTrustAgentWithStrongAuthRequired(trustAgentName, STRONG_AUTH_NOT_REQUIRED); + + attemptFailedUnlock(TEST_USER_ID); + mService.waitForIdle(); + + verify(trustAgentService).onUnlockAttempt(/* successful= */ false); + } + + @Test + public void testFailedUnlock_doesNotNotifyTrustListenerOfChangeInManagedTrust() + throws Exception { + ComponentName trustAgentName = + ComponentName.unflattenFromString("com.android/.SystemTrustAgent"); + setUpTrustAgentWithStrongAuthRequired(trustAgentName, STRONG_AUTH_NOT_REQUIRED); + Mockito.reset(mTrustListener); + + attemptFailedUnlock(TEST_USER_ID); + mService.waitForIdle(); + + verify(mTrustListener, never()).onTrustManagedChanged(anyBoolean(), anyInt()); + } + + private void setUpRenewableTrust(ITrustAgentService trustAgent) throws RemoteException { + ITrustAgentServiceCallback callback = getCallback(trustAgent); + callback.setManagingTrust(true); + mService.waitForIdle(); + attemptSuccessfulUnlock(TEST_USER_ID); + mService.waitForIdle(); + when(mWindowManager.isKeyguardLocked()).thenReturn(false); + grantRenewableTrust(callback); + } + + private ITrustAgentService setUpTrustAgentWithStrongAuthRequired( + ComponentName agentName, @StrongAuthFlags int strongAuthFlags) throws Exception { + doReturn(true).when(mUserManager).isUserUnlockingOrUnlocked(TEST_USER_ID); + addTrustAgent(agentName, true); + mLockPatternUtils.setKnownTrustAgents(singleton(agentName), TEST_USER_ID); + mLockPatternUtils.setEnabledTrustAgents(singleton(agentName), TEST_USER_ID); + when(mUserManager.isUserUnlockingOrUnlocked(TEST_USER_ID)).thenReturn(true); + setupStrongAuthTracker(strongAuthFlags, false); + mService.waitForIdle(); + return getOrCreateMockTrustAgent(agentName); + } + + private void attemptSuccessfulUnlock(int userId) throws RemoteException { + mTrustManager.reportUnlockAttempt(/* successful= */ true, userId); + } + + private void attemptFailedUnlock(int userId) throws RemoteException { + mTrustManager.reportUnlockAttempt(/* successful= */ false, userId); + } + + private void grantRenewableTrust(ITrustAgentServiceCallback callback) throws RemoteException { + Log.i(TAG, "Granting trust"); + AndroidFuture<GrantTrustResult> future = new AndroidFuture<>(); + callback.grantTrust( + GRANT_TRUST_MESSAGE, + RENEWABLE_TRUST_DURATION, + FLAG_GRANT_TRUST_TEMPORARY_AND_RENEWABLE, + future); + mService.waitForIdle(); + } + + /** + * Retrieve the ITrustAgentServiceCallback attached to a TrustAgentService after it has been + * bound to by the TrustManagerService. Will fail if no binding was established. + */ + private ITrustAgentServiceCallback getCallback(ITrustAgentService trustAgentService) + throws RemoteException { + ArgumentCaptor<ITrustAgentServiceCallback> callbackCaptor = + ArgumentCaptor.forClass(ITrustAgentServiceCallback.class); + verify(trustAgentService).setCallback(callbackCaptor.capture()); + return callbackCaptor.getValue(); + } + + @Test @RequiresFlagsEnabled(android.security.Flags.FLAG_FIX_UNLOCKED_DEVICE_REQUIRED_KEYS_V2) public void testKeystoreWeakUnlockEnabled_whenWeakFingerprintIsSetupAndAllowed() throws Exception { @@ -637,6 +838,20 @@ public class TrustManagerServiceTest { ResolveInfo resolveInfo = new ResolveInfo(); resolveInfo.serviceInfo = serviceInfo; mTrustAgentResolveInfoList.add(resolveInfo); + ITrustAgentService.Stub mockService = getOrCreateMockTrustAgent(agentComponentName); + mMockContext.addMockService(agentComponentName, mockService); + mMockTrustAgents.put(agentComponentName, mockService); + } + + private ITrustAgentService.Stub getOrCreateMockTrustAgent(ComponentName agentComponentName) { + return mMockTrustAgents.computeIfAbsent( + agentComponentName, + (componentName) -> { + ITrustAgentService.Stub mockTrustAgent = mock(ITrustAgentService.Stub.class); + when(mockTrustAgent.queryLocalInterface(anyString())) + .thenReturn(mockTrustAgent); + return mockTrustAgent; + }); } private void bootService() { diff --git a/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityServiceConnectionTest.java b/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityServiceConnectionTest.java index c4946f0b221e..8914696d55da 100644 --- a/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityServiceConnectionTest.java +++ b/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityServiceConnectionTest.java @@ -432,7 +432,6 @@ public class AccessibilityServiceConnectionTest { } @Test - @RequiresFlagsEnabled(Flags.FLAG_RESETTABLE_DYNAMIC_PROPERTIES) public void binderDied_resetA11yServiceInfo() { final int flag = AccessibilityServiceInfo.FLAG_REPORT_VIEW_IDS; setServiceBinding(COMPONENT_NAME); diff --git a/services/tests/servicestests/src/com/android/server/locksettings/RebootEscrowManagerTests.java b/services/tests/servicestests/src/com/android/server/locksettings/RebootEscrowManagerTests.java index 64e62369f955..17b499e112bc 100644 --- a/services/tests/servicestests/src/com/android/server/locksettings/RebootEscrowManagerTests.java +++ b/services/tests/servicestests/src/com/android/server/locksettings/RebootEscrowManagerTests.java @@ -19,6 +19,7 @@ package com.android.server.locksettings; import static android.content.pm.UserInfo.FLAG_FULL; import static android.content.pm.UserInfo.FLAG_PRIMARY; import static android.content.pm.UserInfo.FLAG_PROFILE; +import static android.content.pm.UserInfo.NO_PROFILE_GROUP_ID; import static android.os.UserHandle.USER_SYSTEM; import static com.android.internal.widget.LockSettingsInternal.ARM_REBOOT_ERROR_ESCROW_NOT_READY; @@ -32,6 +33,7 @@ import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertTrue; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyBoolean; +import static org.mockito.ArgumentMatchers.anyByte; import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.ArgumentMatchers.anyLong; import static org.mockito.ArgumentMatchers.eq; @@ -65,6 +67,7 @@ import androidx.test.runner.AndroidJUnit4; import com.android.internal.widget.RebootEscrowListener; import com.android.server.locksettings.ResumeOnRebootServiceProvider.ResumeOnRebootServiceConnection; +import com.android.server.pm.UserManagerInternal; import org.junit.Before; import org.junit.Test; @@ -107,6 +110,7 @@ public class RebootEscrowManagerTests { private Context mContext; private UserManager mUserManager; + private UserManagerInternal mUserManagerInternal; private RebootEscrowManager.Callbacks mCallbacks; private IRebootEscrow mRebootEscrow; private ResumeOnRebootServiceConnection mServiceConnection; @@ -126,13 +130,15 @@ public class RebootEscrowManagerTests { long getCurrentTimeMillis(); void reportMetric(boolean success, int errorCode, int serviceType, int attemptCount, - int escrowDurationInSeconds, int vbmetaDigestStatus, int durationSinceBootComplete); + int escrowDurationInSeconds, int vbmetaDigestStatus, + int durationSinceBootComplete); } static class MockInjector extends RebootEscrowManager.Injector { private final IRebootEscrow mRebootEscrow; private final RebootEscrowProviderInterface mDefaultRebootEscrowProvider; private final UserManager mUserManager; + private final UserManagerInternal mUserManagerInternal; private final MockableRebootEscrowInjected mInjected; private final RebootEscrowKeyStoreManager mKeyStoreManager; private boolean mServerBased; @@ -141,12 +147,16 @@ public class RebootEscrowManagerTests { private Consumer<ConnectivityManager.NetworkCallback> mNetworkConsumer; private boolean mWaitForInternet; - MockInjector(Context context, UserManager userManager, + MockInjector( + Context context, + UserManager userManager, + UserManagerInternal userManagerInternal, IRebootEscrow rebootEscrow, RebootEscrowKeyStoreManager keyStoreManager, LockSettingsStorageTestable storage, MockableRebootEscrowInjected injected) { - super(context, storage); + // TODO: change this + super(context, storage, userManagerInternal); mRebootEscrow = rebootEscrow; mServerBased = false; mWaitForInternet = false; @@ -159,16 +169,20 @@ public class RebootEscrowManagerTests { }; mDefaultRebootEscrowProvider = new RebootEscrowProviderHalImpl(halInjector); mUserManager = userManager; + mUserManagerInternal = userManagerInternal; mKeyStoreManager = keyStoreManager; mInjected = injected; } - MockInjector(Context context, UserManager userManager, + MockInjector( + Context context, + UserManager userManager, + UserManagerInternal userManagerInternal, ResumeOnRebootServiceConnection serviceConnection, RebootEscrowKeyStoreManager keyStoreManager, LockSettingsStorageTestable storage, MockableRebootEscrowInjected injected) { - super(context, storage); + super(context, storage, userManagerInternal); mRebootEscrow = null; mServerBased = true; mWaitForInternet = false; @@ -187,6 +201,7 @@ public class RebootEscrowManagerTests { mDefaultRebootEscrowProvider = new RebootEscrowProviderServerBasedImpl( storage, injector); mUserManager = userManager; + mUserManagerInternal = userManagerInternal; mKeyStoreManager = keyStoreManager; mInjected = injected; } @@ -202,6 +217,11 @@ public class RebootEscrowManagerTests { } @Override + public UserManagerInternal getUserManagerInternal() { + return mUserManagerInternal; + } + + @Override public boolean serverBasedResumeOnReboot() { return mServerBased; } @@ -289,8 +309,8 @@ public class RebootEscrowManagerTests { @Override public void reportMetric(boolean success, int errorCode, int serviceType, int attemptCount, - int escrowDurationInSeconds, int vbmetaDigestStatus, - int durationSinceBootComplete) { + int escrowDurationInSeconds, int vbmetaDigestStatus, + int durationSinceBootComplete) { mInjected.reportMetric(success, errorCode, serviceType, attemptCount, escrowDurationInSeconds, vbmetaDigestStatus, durationSinceBootComplete); @@ -301,6 +321,7 @@ public class RebootEscrowManagerTests { public void setUp_baseServices() throws Exception { mContext = new ContextWrapper(InstrumentationRegistry.getContext()); mUserManager = mock(UserManager.class); + mUserManagerInternal = mock(UserManagerInternal.class); mCallbacks = mock(RebootEscrowManager.Callbacks.class); mRebootEscrow = mock(IRebootEscrow.class); mServiceConnection = mock(ResumeOnRebootServiceConnection.class); @@ -314,28 +335,43 @@ public class RebootEscrowManagerTests { new File(InstrumentationRegistry.getContext().getFilesDir(), "locksettings")); ArrayList<UserInfo> users = new ArrayList<>(); - users.add(new UserInfo(PRIMARY_USER_ID, "primary", FLAG_PRIMARY)); - users.add(new UserInfo(WORK_PROFILE_USER_ID, "work", FLAG_PROFILE)); - users.add(new UserInfo(NONSECURE_SECONDARY_USER_ID, "non-secure", FLAG_FULL)); - users.add(new UserInfo(SECURE_SECONDARY_USER_ID, "secure", FLAG_FULL)); + users.add(createUser(PRIMARY_USER_ID, "primary", FLAG_PRIMARY, PRIMARY_USER_ID)); + users.add(createUser(WORK_PROFILE_USER_ID, "work", FLAG_PROFILE, PRIMARY_USER_ID)); + users.add( + createUser( + NONSECURE_SECONDARY_USER_ID, "non-secure", FLAG_FULL, NO_PROFILE_GROUP_ID)); + users.add(createUser(SECURE_SECONDARY_USER_ID, "secure", FLAG_FULL, NO_PROFILE_GROUP_ID)); when(mUserManager.getUsers()).thenReturn(users); when(mCallbacks.isUserSecure(PRIMARY_USER_ID)).thenReturn(true); when(mCallbacks.isUserSecure(WORK_PROFILE_USER_ID)).thenReturn(true); when(mCallbacks.isUserSecure(NONSECURE_SECONDARY_USER_ID)).thenReturn(false); when(mCallbacks.isUserSecure(SECURE_SECONDARY_USER_ID)).thenReturn(true); mInjected = mock(MockableRebootEscrowInjected.class); - mMockInjector = new MockInjector(mContext, mUserManager, mRebootEscrow, - mKeyStoreManager, mStorage, mInjected); + mMockInjector = + new MockInjector( + mContext, + mUserManager, + mUserManagerInternal, + mRebootEscrow, + mKeyStoreManager, + mStorage, + mInjected); HandlerThread thread = new HandlerThread("RebootEscrowManagerTest"); thread.start(); mHandler = new Handler(thread.getLooper()); mService = new RebootEscrowManager(mMockInjector, mCallbacks, mStorage, mHandler); - } private void setServerBasedRebootEscrowProvider() throws Exception { - mMockInjector = new MockInjector(mContext, mUserManager, mServiceConnection, - mKeyStoreManager, mStorage, mInjected); + mMockInjector = + new MockInjector( + mContext, + mUserManager, + mUserManagerInternal, + mServiceConnection, + mKeyStoreManager, + mStorage, + mInjected); mService = new RebootEscrowManager(mMockInjector, mCallbacks, mStorage, mHandler); } @@ -352,6 +388,12 @@ public class RebootEscrowManagerTests { waitForHandler(); } + private UserInfo createUser(int id, String name, int flag, int profileGroupId) { + UserInfo user = new UserInfo(id, name, flag); + when(mUserManagerInternal.getProfileParentId(eq(id))).thenReturn(profileGroupId); + return user; + } + @Test public void prepareRebootEscrow_Success() throws Exception { RebootEscrowListener mockListener = mock(RebootEscrowListener.class); @@ -559,6 +601,172 @@ public class RebootEscrowManagerTests { } @Test + public void loadRebootEscrowDataIfAvailable_noDataPrimaryUser_Failure() throws Exception { + setServerBasedRebootEscrowProvider(); + RebootEscrowListener mockListener = mock(RebootEscrowListener.class); + mService.setRebootEscrowListener(mockListener); + mService.prepareRebootEscrow(); + + clearInvocations(mServiceConnection); + + // escrow secondary user, don't escrow primary user + callToRebootEscrowIfNeededAndWait(SECURE_SECONDARY_USER_ID); + verify(mockListener).onPreparedForReboot(eq(true)); + verify(mServiceConnection, never()).wrapBlob(any(), anyLong(), anyLong()); + + when(mServiceConnection.wrapBlob(any(), anyLong(), anyLong())) + .thenAnswer(invocation -> invocation.getArgument(0)); + assertEquals(ARM_REBOOT_ERROR_NONE, mService.armRebootEscrowIfNeeded()); + verify(mServiceConnection).wrapBlob(any(), anyLong(), anyLong()); + + assertTrue(mStorage.hasRebootEscrow(SECURE_SECONDARY_USER_ID)); + assertFalse(mStorage.hasRebootEscrow(PRIMARY_USER_ID)); + assertTrue(mStorage.hasRebootEscrowServerBlob()); + + // pretend reboot happens here + when(mInjected.getBootCount()).thenReturn(1); + ArgumentCaptor<Boolean> metricsSuccessCaptor = ArgumentCaptor.forClass(Boolean.class); + ArgumentCaptor<Integer> metricsErrorCodeCaptor = ArgumentCaptor.forClass(Integer.class); + doNothing() + .when(mInjected) + .reportMetric( + metricsSuccessCaptor.capture(), + metricsErrorCodeCaptor.capture(), + eq(2) /* Server based */, + eq(1) /* attempt count */, + anyInt(), + eq(0) /* vbmeta status */, + anyInt()); + mService.loadRebootEscrowDataIfAvailable(null); + verify(mServiceConnection, never()).unwrap(any(), anyLong()); + verify(mCallbacks, never()).onRebootEscrowRestored(anyByte(), any(), anyInt()); + assertFalse(metricsSuccessCaptor.getValue()); + assertEquals( + Integer.valueOf(RebootEscrowManager.ERROR_NO_REBOOT_ESCROW_DATA), + metricsErrorCodeCaptor.getValue()); + } + + @Test + public void loadRebootEscrowDataIfAvailable_noDataSecondaryUser_Success() throws Exception { + setServerBasedRebootEscrowProvider(); + RebootEscrowListener mockListener = mock(RebootEscrowListener.class); + mService.setRebootEscrowListener(mockListener); + mService.prepareRebootEscrow(); + + clearInvocations(mServiceConnection); + + // Setup work profile with secondary user as parent. + ArrayList<UserInfo> users = new ArrayList<>(); + users.add(createUser(PRIMARY_USER_ID, "primary", FLAG_PRIMARY, NO_PROFILE_GROUP_ID)); + users.add(createUser(WORK_PROFILE_USER_ID, "work", FLAG_PROFILE, SECURE_SECONDARY_USER_ID)); + users.add( + createUser( + SECURE_SECONDARY_USER_ID, "secure", FLAG_FULL, SECURE_SECONDARY_USER_ID)); + when(mUserManager.getUsers()).thenReturn(users); + + // escrow primary user and work profile, don't escrow secondary user + callToRebootEscrowIfNeededAndWait(PRIMARY_USER_ID); + verify(mockListener).onPreparedForReboot(eq(true)); + verify(mServiceConnection, never()).wrapBlob(any(), anyLong(), anyLong()); + callToRebootEscrowIfNeededAndWait(WORK_PROFILE_USER_ID); + verify(mockListener).onPreparedForReboot(eq(true)); + verify(mServiceConnection, never()).wrapBlob(any(), anyLong(), anyLong()); + + when(mServiceConnection.wrapBlob(any(), anyLong(), anyLong())) + .thenAnswer(invocation -> invocation.getArgument(0)); + assertEquals(ARM_REBOOT_ERROR_NONE, mService.armRebootEscrowIfNeeded()); + verify(mServiceConnection).wrapBlob(any(), anyLong(), anyLong()); + + assertTrue(mStorage.hasRebootEscrow(PRIMARY_USER_ID)); + assertFalse(mStorage.hasRebootEscrow(SECURE_SECONDARY_USER_ID)); + assertTrue(mStorage.hasRebootEscrow(WORK_PROFILE_USER_ID)); + assertTrue(mStorage.hasRebootEscrowServerBlob()); + + // pretend reboot happens here + when(mInjected.getBootCount()).thenReturn(1); + ArgumentCaptor<Boolean> metricsSuccessCaptor = ArgumentCaptor.forClass(Boolean.class); + doNothing() + .when(mInjected) + .reportMetric( + metricsSuccessCaptor.capture(), + eq(0) /* error code */, + eq(2) /* Server based */, + eq(1) /* attempt count */, + anyInt(), + eq(0) /* vbmeta status */, + anyInt()); + when(mServiceConnection.unwrap(any(), anyLong())) + .thenAnswer(invocation -> invocation.getArgument(0)); + + mService.loadRebootEscrowDataIfAvailable(null); + + verify(mServiceConnection).unwrap(any(), anyLong()); + verify(mCallbacks).onRebootEscrowRestored(anyByte(), any(), eq(PRIMARY_USER_ID)); + verify(mCallbacks, never()) + .onRebootEscrowRestored(anyByte(), any(), eq(SECURE_SECONDARY_USER_ID)); + verify(mCallbacks, never()) + .onRebootEscrowRestored(anyByte(), any(), eq(WORK_PROFILE_USER_ID)); + verify(mCallbacks, never()) + .onRebootEscrowRestored(anyByte(), any(), eq(NONSECURE_SECONDARY_USER_ID)); + assertTrue(metricsSuccessCaptor.getValue()); + } + + @Test + public void loadRebootEscrowDataIfAvailable_noDataWorkProfile_Success() throws Exception { + setServerBasedRebootEscrowProvider(); + RebootEscrowListener mockListener = mock(RebootEscrowListener.class); + mService.setRebootEscrowListener(mockListener); + mService.prepareRebootEscrow(); + + clearInvocations(mServiceConnection); + + // escrow primary user and secondary user, don't escrow work profile + callToRebootEscrowIfNeededAndWait(PRIMARY_USER_ID); + verify(mockListener).onPreparedForReboot(eq(true)); + verify(mServiceConnection, never()).wrapBlob(any(), anyLong(), anyLong()); + callToRebootEscrowIfNeededAndWait(SECURE_SECONDARY_USER_ID); + verify(mockListener).onPreparedForReboot(eq(true)); + verify(mServiceConnection, never()).wrapBlob(any(), anyLong(), anyLong()); + + when(mServiceConnection.wrapBlob(any(), anyLong(), anyLong())) + .thenAnswer(invocation -> invocation.getArgument(0)); + assertEquals(ARM_REBOOT_ERROR_NONE, mService.armRebootEscrowIfNeeded()); + verify(mServiceConnection).wrapBlob(any(), anyLong(), anyLong()); + + assertTrue(mStorage.hasRebootEscrow(PRIMARY_USER_ID)); + assertTrue(mStorage.hasRebootEscrow(SECURE_SECONDARY_USER_ID)); + assertFalse(mStorage.hasRebootEscrow(WORK_PROFILE_USER_ID)); + assertTrue(mStorage.hasRebootEscrowServerBlob()); + + // pretend reboot happens here + when(mInjected.getBootCount()).thenReturn(1); + ArgumentCaptor<Boolean> metricsSuccessCaptor = ArgumentCaptor.forClass(Boolean.class); + doNothing() + .when(mInjected) + .reportMetric( + metricsSuccessCaptor.capture(), + eq(0) /* error code */, + eq(2) /* Server based */, + eq(1) /* attempt count */, + anyInt(), + eq(0) /* vbmeta status */, + anyInt()); + when(mServiceConnection.unwrap(any(), anyLong())) + .thenAnswer(invocation -> invocation.getArgument(0)); + + mService.loadRebootEscrowDataIfAvailable(null); + + verify(mServiceConnection).unwrap(any(), anyLong()); + verify(mCallbacks).onRebootEscrowRestored(anyByte(), any(), eq(PRIMARY_USER_ID)); + verify(mCallbacks).onRebootEscrowRestored(anyByte(), any(), eq(SECURE_SECONDARY_USER_ID)); + verify(mCallbacks, never()) + .onRebootEscrowRestored(anyByte(), any(), eq(WORK_PROFILE_USER_ID)); + verify(mCallbacks, never()) + .onRebootEscrowRestored(anyByte(), any(), eq(NONSECURE_SECONDARY_USER_ID)); + assertTrue(metricsSuccessCaptor.getValue()); + } + + @Test public void loadRebootEscrowDataIfAvailable_ServerBased_Success() throws Exception { setServerBasedRebootEscrowProvider(); diff --git a/services/tests/uiservicestests/src/com/android/server/notification/GroupHelperTest.java b/services/tests/uiservicestests/src/com/android/server/notification/GroupHelperTest.java index c7c97e40b424..8a7d276dbecd 100644 --- a/services/tests/uiservicestests/src/com/android/server/notification/GroupHelperTest.java +++ b/services/tests/uiservicestests/src/com/android/server/notification/GroupHelperTest.java @@ -57,12 +57,12 @@ import android.graphics.drawable.Icon; import android.os.UserHandle; import android.platform.test.annotations.DisableFlags; import android.platform.test.annotations.EnableFlags; +import android.platform.test.flag.junit.FlagsParameterization; import android.platform.test.flag.junit.SetFlagsRule; import android.service.notification.StatusBarNotification; import android.util.ArrayMap; import androidx.test.filters.SmallTest; -import androidx.test.runner.AndroidJUnit4; import com.android.internal.R; import com.android.server.UiServiceTestCase; @@ -79,9 +79,12 @@ import org.mockito.MockitoAnnotations; import java.util.ArrayList; import java.util.List; +import platform.test.runner.parameterized.ParameterizedAndroidJunit4; +import platform.test.runner.parameterized.Parameters; + @SmallTest @SuppressLint("GuardedBy") // It's ok for this test to access guarded methods from the class. -@RunWith(AndroidJUnit4.class) +@RunWith(ParameterizedAndroidJunit4.class) public class GroupHelperTest extends UiServiceTestCase { @Rule public final SetFlagsRule mSetFlagsRule = new SetFlagsRule(DEVICE_DEFAULT); @@ -95,6 +98,16 @@ public class GroupHelperTest extends UiServiceTestCase { private GroupHelper mGroupHelper; private @Mock Icon mSmallIcon; + @Parameters(name = "{0}") + public static List<FlagsParameterization> getParams() { + return FlagsParameterization.allCombinationsOf( + android.app.Flags.FLAG_CHECK_AUTOGROUP_BEFORE_POST); + } + + public GroupHelperTest(FlagsParameterization flags) { + mSetFlagsRule.setFlagsParameterization(flags); + } + @Before public void setUp() { MockitoAnnotations.initMocks(this); @@ -708,7 +721,8 @@ public class GroupHelperTest extends UiServiceTestCase { } @Test - public void testDropToZeroRemoveGroup() { + @DisableFlags(android.app.Flags.FLAG_CHECK_AUTOGROUP_BEFORE_POST) + public void testDropToZeroRemoveGroup_disableFlag() { final String pkg = "package"; List<StatusBarNotification> posted = new ArrayList<>(); for (int i = 0; i < AUTOGROUP_AT_COUNT; i++) { @@ -736,7 +750,37 @@ public class GroupHelperTest extends UiServiceTestCase { } @Test - public void testAppStartsGrouping() { + @EnableFlags(android.app.Flags.FLAG_CHECK_AUTOGROUP_BEFORE_POST) + public void testDropToZeroRemoveGroup() { + final String pkg = "package"; + List<StatusBarNotification> posted = new ArrayList<>(); + for (int i = 0; i < AUTOGROUP_AT_COUNT; i++) { + final StatusBarNotification sbn = getSbn(pkg, i, String.valueOf(i), UserHandle.SYSTEM); + posted.add(sbn); + mGroupHelper.onNotificationPosted(sbn, false); + } + verify(mCallback, times(1)).addAutoGroupSummary( + anyInt(), eq(pkg), anyString(), eq(getNotificationAttributes(BASE_FLAGS))); + verify(mCallback, times(AUTOGROUP_AT_COUNT - 1)).addAutoGroup(anyString(), anyBoolean()); + verify(mCallback, never()).removeAutoGroup(anyString()); + verify(mCallback, never()).removeAutoGroupSummary(anyInt(), anyString()); + Mockito.reset(mCallback); + + for (int i = 0; i < AUTOGROUP_AT_COUNT - 1; i++) { + mGroupHelper.onNotificationRemoved(posted.remove(0)); + } + verify(mCallback, never()).removeAutoGroup(anyString()); + verify(mCallback, never()).removeAutoGroupSummary(anyInt(), anyString()); + Mockito.reset(mCallback); + + mGroupHelper.onNotificationRemoved(posted.remove(0)); + verify(mCallback, never()).removeAutoGroup(anyString()); + verify(mCallback, times(1)).removeAutoGroupSummary(anyInt(), anyString()); + } + + @Test + @DisableFlags(android.app.Flags.FLAG_CHECK_AUTOGROUP_BEFORE_POST) + public void testAppStartsGrouping_disableFlag() { final String pkg = "package"; List<StatusBarNotification> posted = new ArrayList<>(); for (int i = 0; i < AUTOGROUP_AT_COUNT; i++) { @@ -765,6 +809,36 @@ public class GroupHelperTest extends UiServiceTestCase { } @Test + @EnableFlags(android.app.Flags.FLAG_CHECK_AUTOGROUP_BEFORE_POST) + public void testAppStartsGrouping() { + final String pkg = "package"; + List<StatusBarNotification> posted = new ArrayList<>(); + for (int i = 0; i < AUTOGROUP_AT_COUNT; i++) { + final StatusBarNotification sbn = getSbn(pkg, i, String.valueOf(i), UserHandle.SYSTEM); + posted.add(sbn); + mGroupHelper.onNotificationPosted(sbn, false); + } + verify(mCallback, times(1)).addAutoGroupSummary( + anyInt(), eq(pkg), anyString(), eq(getNotificationAttributes(BASE_FLAGS))); + verify(mCallback, times(AUTOGROUP_AT_COUNT - 1)).addAutoGroup(anyString(), anyBoolean()); + verify(mCallback, never()).removeAutoGroup(anyString()); + verify(mCallback, never()).removeAutoGroupSummary(anyInt(), anyString()); + Mockito.reset(mCallback); + + for (int i = 0; i < AUTOGROUP_AT_COUNT; i++) { + final StatusBarNotification sbn = + getSbn(pkg, i, String.valueOf(i), UserHandle.SYSTEM, "app group"); + sbn.setOverrideGroupKey("autogrouped"); + mGroupHelper.onNotificationPosted(sbn, true); + verify(mCallback, times(1)).removeAutoGroup(sbn.getKey()); + if (i < AUTOGROUP_AT_COUNT - 1) { + verify(mCallback, never()).removeAutoGroupSummary(anyInt(), anyString()); + } + } + verify(mCallback, times(1)).removeAutoGroupSummary(anyInt(), anyString()); + } + + @Test @DisableFlags(android.app.Flags.FLAG_CHECK_AUTOGROUP_BEFORE_POST) public void testNewNotificationsAddedToAutogroup_ifOriginalNotificationsCanceled_alwaysGroup() { final String pkg = "package"; @@ -915,8 +989,9 @@ public class GroupHelperTest extends UiServiceTestCase { } @Test + @DisableFlags(android.app.Flags.FLAG_CHECK_AUTOGROUP_BEFORE_POST) @EnableFlags(Flags.FLAG_AUTOGROUP_SUMMARY_ICON_UPDATE) - public void testAddSummary_diffIcon_diffColor() { + public void testAddSummary_diffIcon_diffColor_disableFlag() { final String pkg = "package"; final Icon initialIcon = mock(Icon.class); when(initialIcon.sameAs(initialIcon)).thenReturn(true); @@ -959,6 +1034,51 @@ public class GroupHelperTest extends UiServiceTestCase { } @Test + @EnableFlags({Flags.FLAG_AUTOGROUP_SUMMARY_ICON_UPDATE, + android.app.Flags.FLAG_CHECK_AUTOGROUP_BEFORE_POST}) + public void testAddSummary_diffIcon_diffColor() { + final String pkg = "package"; + final Icon initialIcon = mock(Icon.class); + when(initialIcon.sameAs(initialIcon)).thenReturn(true); + final int initialIconColor = Color.BLUE; + + // Spy GroupHelper for getMonochromeAppIcon + final Icon monochromeIcon = mock(Icon.class); + when(monochromeIcon.sameAs(monochromeIcon)).thenReturn(true); + GroupHelper groupHelper = spy(mGroupHelper); + doReturn(monochromeIcon).when(groupHelper).getMonochromeAppIcon(eq(pkg)); + + final NotificationAttributes initialAttr = new NotificationAttributes(BASE_FLAGS, + initialIcon, initialIconColor, DEFAULT_VISIBILITY); + + // Add notifications with same icon and color + for (int i = 0; i < AUTOGROUP_AT_COUNT; i++) { + StatusBarNotification sbn = getSbn(pkg, i, String.valueOf(i), UserHandle.SYSTEM, null, + initialIcon, initialIconColor); + groupHelper.onNotificationPosted(sbn, false); + } + // Check that the summary would have the same icon and color + verify(mCallback, times(1)).addAutoGroupSummary( + anyInt(), eq(pkg), anyString(), eq(initialAttr)); + verify(mCallback, times(AUTOGROUP_AT_COUNT - 1)).addAutoGroup(anyString(), anyBoolean()); + verify(mCallback, never()).removeAutoGroup(anyString()); + verify(mCallback, never()).removeAutoGroupSummary(anyInt(), anyString()); + + // After auto-grouping, add new notification with a different color + final Icon newIcon = mock(Icon.class); + final int newIconColor = Color.YELLOW; + StatusBarNotification sbn = getSbn(pkg, AUTOGROUP_AT_COUNT, + String.valueOf(AUTOGROUP_AT_COUNT), UserHandle.SYSTEM, null, newIcon, + newIconColor); + groupHelper.onNotificationPosted(sbn, true); + + // Summary should be updated to the default color and the icon to the monochrome icon + NotificationAttributes newAttr = new NotificationAttributes(BASE_FLAGS, monochromeIcon, + COLOR_DEFAULT, DEFAULT_VISIBILITY); + verify(mCallback, times(1)).updateAutogroupSummary(anyInt(), anyString(), eq(newAttr)); + } + + @Test @DisableFlags(android.app.Flags.FLAG_CHECK_AUTOGROUP_BEFORE_POST) @EnableFlags(Flags.FLAG_AUTOGROUP_SUMMARY_ICON_UPDATE) public void testAddSummary_diffVisibility_alwaysAutogroup() { diff --git a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java index a7dbecbb5255..1d014201cf46 100644 --- a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java +++ b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java @@ -794,8 +794,9 @@ public class VoiceInteractionManagerService extends SystemService { if (curService != null && !curService.isEmpty()) { try { serviceComponent = ComponentName.unflattenFromString(curService); - serviceInfo = getValidVoiceInteractionServiceInfo(serviceComponent); - } catch (RuntimeException e) { + serviceInfo = AppGlobals.getPackageManager() + .getServiceInfo(serviceComponent, 0, mCurUser); + } catch (RuntimeException | RemoteException e) { Slog.wtf(TAG, "Bad voice interaction service name " + curService, e); serviceComponent = null; serviceInfo = null; @@ -833,27 +834,6 @@ public class VoiceInteractionManagerService extends SystemService { } } - @Nullable - private ServiceInfo getValidVoiceInteractionServiceInfo( - @Nullable ComponentName serviceComponent) { - if (serviceComponent == null) { - return null; - } - List<ResolveInfo> services = queryInteractorServices( - mCurUser, serviceComponent.getPackageName()); - for (int i = 0; i < services.size(); i++) { - ResolveInfo service = services.get(i); - VoiceInteractionServiceInfo info = new VoiceInteractionServiceInfo( - mContext.getPackageManager(), service.serviceInfo); - ServiceInfo candidateInfo = info.getServiceInfo(); - if (candidateInfo != null - && candidateInfo.getComponentName().equals(serviceComponent)) { - return candidateInfo; - } - } - return null; - } - private List<ResolveInfo> queryInteractorServices( @UserIdInt int user, @Nullable String packageName) { diff --git a/telephony/java/android/telephony/satellite/SystemSelectionSpecifier.aidl b/telephony/java/android/telephony/satellite/SystemSelectionSpecifier.aidl new file mode 100644 index 000000000000..ecd248cbf924 --- /dev/null +++ b/telephony/java/android/telephony/satellite/SystemSelectionSpecifier.aidl @@ -0,0 +1,19 @@ +/* + * Copyright 2024, 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.telephony.satellite; + + parcelable SystemSelectionSpecifier; diff --git a/telephony/java/android/telephony/satellite/SystemSelectionSpecifier.java b/telephony/java/android/telephony/satellite/SystemSelectionSpecifier.java new file mode 100644 index 000000000000..8a5e7f22888a --- /dev/null +++ b/telephony/java/android/telephony/satellite/SystemSelectionSpecifier.java @@ -0,0 +1,175 @@ +/* + * Copyright (C) 2024 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.telephony.satellite; + +import android.annotation.NonNull; +import android.os.Parcel; +import android.os.Parcelable; +import android.text.TextUtils; +import android.util.IntArray; + +import java.util.Objects; + +/** + * @hide + */ +public final class SystemSelectionSpecifier implements Parcelable { + + /** Network plmn associated with channel information. */ + @NonNull private String mMccMnc; + + /** The frequency bands to scan. Maximum length of the vector is 8. */ + @NonNull private IntArray mBands; + + /** + * The radio channels to scan as defined in 3GPP TS 25.101 and 36.101. + * Maximum length of the vector is 32. + */ + @NonNull private IntArray mEarfcs; + + /** + * @hide + */ + public SystemSelectionSpecifier(@NonNull String mccmnc, @NonNull IntArray bands, + @NonNull IntArray earfcs) { + mMccMnc = mccmnc; + mBands = bands; + mEarfcs = earfcs; + } + + private SystemSelectionSpecifier(Parcel in) { + readFromParcel(in); + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(@NonNull Parcel out, int flags) { + mMccMnc = TextUtils.emptyIfNull(mMccMnc); + out.writeString8(mMccMnc); + + if (mBands != null && mBands.size() > 0) { + out.writeInt(mBands.size()); + for (int i = 0; i < mBands.size(); i++) { + out.writeInt(mBands.get(i)); + } + } else { + out.writeInt(0); + } + + if (mEarfcs != null && mEarfcs.size() > 0) { + out.writeInt(mEarfcs.size()); + for (int i = 0; i < mEarfcs.size(); i++) { + out.writeInt(mEarfcs.get(i)); + } + } else { + out.writeInt(0); + } + } + + @NonNull public static final Parcelable.Creator<SystemSelectionSpecifier> CREATOR = + new Creator<>() { + @Override + public SystemSelectionSpecifier createFromParcel(Parcel in) { + return new SystemSelectionSpecifier(in); + } + + @Override + public SystemSelectionSpecifier[] newArray(int size) { + return new SystemSelectionSpecifier[size]; + } + }; + + @Override + @NonNull public String toString() { + StringBuilder sb = new StringBuilder(); + sb.append("mccmnc:"); + sb.append(mMccMnc); + sb.append(","); + + sb.append("bands:"); + if (mBands != null && mBands.size() > 0) { + for (int i = 0; i < mBands.size(); i++) { + sb.append(mBands.get(i)); + sb.append(","); + } + } else { + sb.append("none,"); + } + + sb.append("earfcs:"); + if (mEarfcs != null && mEarfcs.size() > 0) { + for (int i = 0; i < mEarfcs.size(); i++) { + sb.append(mEarfcs.get(i)); + sb.append(","); + } + } else { + sb.append("none"); + } + return sb.toString(); + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + SystemSelectionSpecifier that = (SystemSelectionSpecifier) o; + return Objects.equals(mMccMnc, that.mMccMnc) + && Objects.equals(mBands, that.mBands) + && Objects.equals(mEarfcs, that.mEarfcs); + } + + @Override + public int hashCode() { + return Objects.hash(mMccMnc, mBands, mEarfcs); + } + + @NonNull public String getMccMnc() { + return mMccMnc; + } + + @NonNull public IntArray getBands() { + return mBands; + } + + @NonNull public IntArray getEarfcs() { + return mEarfcs; + } + + private void readFromParcel(Parcel in) { + mMccMnc = in.readString(); + + mBands = new IntArray(); + int numBands = in.readInt(); + if (numBands > 0) { + for (int i = 0; i < numBands; i++) { + mBands.add(in.readInt()); + } + } + + mEarfcs = new IntArray(); + int numEarfcs = in.readInt(); + if (numEarfcs > 0) { + for (int i = 0; i < numEarfcs; i++) { + mEarfcs.add(in.readInt()); + } + } + } +} diff --git a/telephony/java/android/telephony/satellite/stub/ISatellite.aidl b/telephony/java/android/telephony/satellite/stub/ISatellite.aidl index 16983a0dbca1..b82396e710fd 100644 --- a/telephony/java/android/telephony/satellite/stub/ISatellite.aidl +++ b/telephony/java/android/telephony/satellite/stub/ISatellite.aidl @@ -23,6 +23,7 @@ import android.telephony.satellite.stub.INtnSignalStrengthConsumer; import android.telephony.satellite.stub.ISatelliteCapabilitiesConsumer; import android.telephony.satellite.stub.ISatelliteListener; import android.telephony.satellite.stub.SatelliteDatagram; +import android.telephony.satellite.stub.SystemSelectionSpecifier; /** * {@hide} @@ -500,4 +501,21 @@ oneway interface ISatellite { * SatelliteResult:SATELLITE_RESULT_REQUEST_NOT_SUPPORTED */ void abortSendingSatelliteDatagrams(in IIntegerConsumer resultCallback); + + /** + * Request to update the satellite subscription to be used for Non-Terrestrial network. + * + * @param iccId The ICCID of the subscription + * @param resultCallback The callback to receive the error code result of the operation. + */ + void updateSatelliteSubscription(in String iccId, in IIntegerConsumer resultCallback); + + /** + * Request to update system selection channels + * + * @param systemSelectionSpecifiers list of system selection specifiers + * @param resultCallback The callback to receive the error code result of the operation. + */ + void updateSystemSelectionChannels(in List<SystemSelectionSpecifier> systemSelectionSpecifiers, + in IIntegerConsumer resultCallback); } diff --git a/telephony/java/android/telephony/satellite/stub/SatelliteImplBase.java b/telephony/java/android/telephony/satellite/stub/SatelliteImplBase.java index a62363335fb9..d8b4974f23b9 100644 --- a/telephony/java/android/telephony/satellite/stub/SatelliteImplBase.java +++ b/telephony/java/android/telephony/satellite/stub/SatelliteImplBase.java @@ -262,6 +262,22 @@ public class SatelliteImplBase extends SatelliteService { "abortSendingSatelliteDatagrams"); } + @Override + public void updateSatelliteSubscription(String iccId, IIntegerConsumer resultCallback) + throws RemoteException { + executeMethodAsync(() -> SatelliteImplBase.this.updateSatelliteSubscription( + iccId, resultCallback), "updateSatelliteSubscription"); + } + + @Override + public void updateSystemSelectionChannels( + List<SystemSelectionSpecifier> systemSelectionSpecifiers, + IIntegerConsumer resultCallback) throws RemoteException { + executeMethodAsync(() -> SatelliteImplBase.this.updateSystemSelectionChannels( + systemSelectionSpecifiers, resultCallback), + "updateSystemSelectionChannels"); + } + // Call the methods with a clean calling identity on the executor and wait indefinitely for // the future to return. private void executeMethodAsync(Runnable r, String errorLogName) throws RemoteException { @@ -768,4 +784,27 @@ public class SatelliteImplBase extends SatelliteService { public void abortSendingSatelliteDatagrams(@NonNull IIntegerConsumer resultCallback){ // stub implementation } + + /** + * Request to update the satellite subscription to be used for Non-Terrestrial network. + * + * @param iccId The ICCID of the subscription + * @param resultCallback The callback to receive the error code result of the operation. + */ + public void updateSatelliteSubscription(String iccId, + @NonNull IIntegerConsumer resultCallback) { + // stub implementation + } + + /** + * Request to update system selection channels + * + * @param systemSelectionSpecifiers list of system selection specifiers + * @param resultCallback The callback to receive the error code result of the operation. + */ + public void updateSystemSelectionChannels( + @NonNull List<SystemSelectionSpecifier> systemSelectionSpecifiers, + @NonNull IIntegerConsumer resultCallback) { + // stub implementation + } } diff --git a/core/java/com/android/internal/util/NewlineNormalizer.java b/telephony/java/android/telephony/satellite/stub/SystemSelectionSpecifier.aidl index 0104d1f56f83..22240f6324c9 100644 --- a/core/java/com/android/internal/util/NewlineNormalizer.java +++ b/telephony/java/android/telephony/satellite/stub/SystemSelectionSpecifier.aidl @@ -14,26 +14,25 @@ * limitations under the License. */ -package com.android.internal.util; - - -import java.util.regex.Pattern; +package android.telephony.satellite.stub; /** - * Utility class that replaces consecutive empty lines with single new line. - * @hide + * {@hide} */ -public class NewlineNormalizer { - - private static final Pattern MULTIPLE_NEWLINES = Pattern.compile("\\v(\\s*\\v)?"); +parcelable SystemSelectionSpecifier { + /** Network plmn associated with channel information. */ + String mMccMnc; - // Private constructor to prevent instantiation - private NewlineNormalizer() {} + /** + * The frequency bands to scan. Bands and earfcns won't overlap. + * Bands will be filled only if the whole band is needed. + * Maximum length of the vector is 8. + */ + int[] mBands; /** - * Replaces consecutive newlines with a single newline in the input text. + * The radio channels to scan as defined in 3GPP TS 25.101 and 36.101. + * Maximum length of the vector is 32. */ - public static String normalizeNewlines(String text) { - return MULTIPLE_NEWLINES.matcher(text).replaceAll("\n"); - } + int[] mEarfcs; } diff --git a/tests/Input/Android.bp b/tests/Input/Android.bp index a85d809257cd..c0cbdc3f96f8 100644 --- a/tests/Input/Android.bp +++ b/tests/Input/Android.bp @@ -31,6 +31,7 @@ android_test { "androidx.test.runner", "androidx.test.uiautomator_uiautomator", "compatibility-device-util-axt", + "cts-input-lib", "flag-junit", "frameworks-base-testutils", "hamcrest-library", diff --git a/tests/Input/src/com/android/test/input/AnrTest.kt b/tests/Input/src/com/android/test/input/AnrTest.kt index 4893d14ad79b..6b95f5c10a1e 100644 --- a/tests/Input/src/com/android/test/input/AnrTest.kt +++ b/tests/Input/src/com/android/test/input/AnrTest.kt @@ -21,21 +21,23 @@ import androidx.test.filters.MediumTest import android.app.ActivityManager import android.app.ApplicationExitInfo +import android.content.Context import android.graphics.Rect +import android.hardware.display.DisplayManager import android.os.Build import android.os.IInputConstants.UNMULTIPLIED_DEFAULT_DISPATCHING_TIMEOUT_MILLIS import android.os.SystemClock import android.provider.Settings import android.provider.Settings.Global.HIDE_ERROR_DIALOGS import android.testing.PollingCheck -import android.view.InputDevice -import android.view.MotionEvent import androidx.test.uiautomator.By import androidx.test.uiautomator.UiDevice import androidx.test.uiautomator.UiObject2 import androidx.test.uiautomator.Until +import com.android.cts.input.UinputTouchScreen + import org.junit.After import org.junit.Assert.assertEquals import org.junit.Assert.assertTrue @@ -150,6 +152,18 @@ class AnrTest { assertEquals(ApplicationExitInfo.REASON_ANR, reasons[0].reason) } + private fun clickOnObject(obj: UiObject2) { + val displayManager = + instrumentation.context.getSystemService(Context.DISPLAY_SERVICE) as DisplayManager + val display = displayManager.getDisplay(obj.getDisplayId()) + val touchScreen = UinputTouchScreen(instrumentation, display) + + val rect: Rect = obj.visibleBounds + val pointer = touchScreen.touchDown(rect.centerX(), rect.centerY()) + pointer.lift() + touchScreen.close() + } + private fun triggerAnr() { startUnresponsiveActivity() val uiDevice: UiDevice = UiDevice.getInstance(instrumentation) @@ -160,13 +174,7 @@ class AnrTest { return } - val rect: Rect = obj.visibleBounds - val downTime = SystemClock.uptimeMillis() - val downEvent = MotionEvent.obtain(downTime, downTime, - MotionEvent.ACTION_DOWN, rect.left.toFloat(), rect.top.toFloat(), 0 /* metaState */) - downEvent.source = InputDevice.SOURCE_TOUCHSCREEN - - instrumentation.uiAutomation.injectInputEvent(downEvent, false /* sync*/) + clickOnObject(obj) SystemClock.sleep(DISPATCHING_TIMEOUT.toLong()) // default ANR timeout for gesture monitors } diff --git a/tests/PackageWatchdog/src/com/android/server/PackageWatchdogTest.java b/tests/PackageWatchdog/src/com/android/server/PackageWatchdogTest.java index a8b383cd4274..093923f3ed53 100644 --- a/tests/PackageWatchdog/src/com/android/server/PackageWatchdogTest.java +++ b/tests/PackageWatchdog/src/com/android/server/PackageWatchdogTest.java @@ -101,8 +101,8 @@ public class PackageWatchdogTest { private static final String OBSERVER_NAME_2 = "observer2"; private static final String OBSERVER_NAME_3 = "observer3"; private static final String OBSERVER_NAME_4 = "observer4"; - private static final long SHORT_DURATION = TimeUnit.SECONDS.toMillis(10); - private static final long LONG_DURATION = TimeUnit.SECONDS.toMillis(50); + private static final long SHORT_DURATION = TimeUnit.SECONDS.toMillis(1); + private static final long LONG_DURATION = TimeUnit.SECONDS.toMillis(5); @Rule public final SetFlagsRule mSetFlagsRule = new SetFlagsRule(); @@ -1453,8 +1453,7 @@ public class PackageWatchdogTest { raiseFatalFailureAndDispatch(watchdog, Arrays.asList(new VersionedPackage(APP_A, VERSION_CODE)), PackageWatchdog.FAILURE_REASON_UNKNOWN); - moveTimeForwardAndDispatch(PackageWatchdog.DEFAULT_DEESCALATION_WINDOW_MS - - TimeUnit.MINUTES.toMillis(1)); + moveTimeForwardAndDispatch(PackageWatchdog.DEFAULT_DEESCALATION_WINDOW_MS); // The first failure will be outside the threshold. raiseFatalFailureAndDispatch(watchdog, Arrays.asList(new VersionedPackage(APP_A, @@ -1713,9 +1712,6 @@ public class PackageWatchdogTest { watchdog.onPackageFailure(packages, failureReason); } mTestLooper.dispatchAll(); - if (Flags.recoverabilityDetection()) { - moveTimeForwardAndDispatch(watchdog.DEFAULT_MITIGATION_WINDOW_MS); - } } private PackageWatchdog createWatchdog() { |