diff options
153 files changed, 2944 insertions, 1493 deletions
diff --git a/apex/jobscheduler/service/java/com/android/server/job/controllers/QuotaController.java b/apex/jobscheduler/service/java/com/android/server/job/controllers/QuotaController.java index 175c8d1cc4f3..07958dd0fef5 100644 --- a/apex/jobscheduler/service/java/com/android/server/job/controllers/QuotaController.java +++ b/apex/jobscheduler/service/java/com/android/server/job/controllers/QuotaController.java @@ -3153,7 +3153,8 @@ public final class QuotaController extends StateController { private static final int DEFAULT_MAX_SESSION_COUNT_PER_RATE_LIMITING_WINDOW = 20; private static final long DEFAULT_TIMING_SESSION_COALESCING_DURATION_MS = 5000; // 5 seconds private static final long DEFAULT_MIN_QUOTA_CHECK_DELAY_MS = MINUTE_IN_MILLIS; - private static final long DEFAULT_EJ_LIMIT_EXEMPTED_MS = 45 * MINUTE_IN_MILLIS; + // TODO(267949143): set a different limit for headless system apps + private static final long DEFAULT_EJ_LIMIT_EXEMPTED_MS = 60 * MINUTE_IN_MILLIS; private static final long DEFAULT_EJ_LIMIT_ACTIVE_MS = 30 * MINUTE_IN_MILLIS; private static final long DEFAULT_EJ_LIMIT_WORKING_MS = DEFAULT_EJ_LIMIT_ACTIVE_MS; private static final long DEFAULT_EJ_LIMIT_FREQUENT_MS = 10 * MINUTE_IN_MILLIS; diff --git a/cmds/screencap/screencap.cpp b/cmds/screencap/screencap.cpp index d7222d248911..863efffe3807 100644 --- a/cmds/screencap/screencap.cpp +++ b/cmds/screencap/screencap.cpp @@ -30,6 +30,8 @@ #include <binder/ProcessState.h> +#include <ftl/concat.h> +#include <ftl/optional.h> #include <gui/ISurfaceComposer.h> #include <gui/SurfaceComposerClient.h> #include <gui/SyncScreenCaptureListener.h> @@ -45,14 +47,7 @@ using namespace android; #define COLORSPACE_SRGB 1 #define COLORSPACE_DISPLAY_P3 2 -static void usage(const char* pname, std::optional<PhysicalDisplayId> displayId) -{ - std::string defaultDisplayStr = ""; - if (!displayId) { - defaultDisplayStr = ""; - } else { - defaultDisplayStr = " (default: " + to_string(*displayId) + ")"; - } +void usage(const char* pname, ftl::Optional<DisplayId> displayIdOpt) { fprintf(stderr, "usage: %s [-hp] [-d display-id] [FILENAME]\n" " -h: this message\n" @@ -61,7 +56,13 @@ static void usage(const char* pname, std::optional<PhysicalDisplayId> displayId) " see \"dumpsys SurfaceFlinger --display-id\" for valid display IDs.\n" "If FILENAME ends with .png it will be saved as a png.\n" "If FILENAME is not given, the results will be printed to stdout.\n", - pname, defaultDisplayStr.c_str()); + pname, + displayIdOpt + .transform([](DisplayId id) { + return std::string(ftl::Concat(" (default: ", id.value, ')').str()); + }) + .value_or(std::string()) + .c_str()); } static int32_t flinger2bitmapFormat(PixelFormat f) @@ -132,7 +133,7 @@ int main(int argc, char** argv) fprintf(stderr, "Failed to get ID for any displays.\n"); return 1; } - std::optional<PhysicalDisplayId> displayId; + std::optional<DisplayId> displayIdOpt; const char* pname = argv[0]; bool png = false; int c; @@ -142,8 +143,8 @@ int main(int argc, char** argv) png = true; break; case 'd': - displayId = DisplayId::fromValue<PhysicalDisplayId>(atoll(optarg)); - if (!displayId) { + displayIdOpt = DisplayId::fromValue(atoll(optarg)); + if (!displayIdOpt) { fprintf(stderr, "Invalid display ID: %s\n", optarg); return 1; } @@ -151,15 +152,15 @@ int main(int argc, char** argv) case '?': case 'h': if (ids.size() == 1) { - displayId = ids.front(); - } - usage(pname, displayId); + displayIdOpt = ids.front(); + } + usage(pname, displayIdOpt); return 1; } } - if (!displayId) { // no diplsay id is specified - displayId = ids.front(); + if (!displayIdOpt) { + displayIdOpt = ids.front(); if (ids.size() > 1) { fprintf(stderr, "[Warning] Multiple displays were found, but no display id was specified! " @@ -191,7 +192,7 @@ int main(int argc, char** argv) } if (fd == -1) { - usage(pname, displayId); + usage(pname, displayIdOpt); return 1; } @@ -208,7 +209,7 @@ int main(int argc, char** argv) ProcessState::self()->startThreadPool(); sp<SyncScreenCaptureListener> captureListener = new SyncScreenCaptureListener(); - status_t result = ScreenshotClient::captureDisplay(*displayId, captureListener); + status_t result = ScreenshotClient::captureDisplay(*displayIdOpt, captureListener); if (result != NO_ERROR) { close(fd); return 1; diff --git a/core/java/Android.bp b/core/java/Android.bp index 02b14ad7a757..f8f4cc3523f2 100644 --- a/core/java/Android.bp +++ b/core/java/Android.bp @@ -510,6 +510,17 @@ filegroup { ], } +// common protolog sources without classes that rely on Android SDK +filegroup { + name: "protolog-common-no-android-src", + srcs: [ + ":protolog-common-src", + ], + exclude_srcs: [ + "com/android/internal/protolog/common/ProtoLog.java", + ], +} + java_library { name: "protolog-lib", platform_apis: true, diff --git a/core/java/android/view/InsetsController.java b/core/java/android/view/InsetsController.java index f570c6d15672..7e4e4022f00f 100644 --- a/core/java/android/view/InsetsController.java +++ b/core/java/android/view/InsetsController.java @@ -1374,7 +1374,7 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation // The requested visibilities should be delayed as well. Otherwise, we might override // the insets visibility before playing animation. - setRequestedVisibleTypes(mReportedRequestedVisibleTypes, typesReady); + setRequestedVisibleTypes(mReportedRequestedVisibleTypes, types); Trace.asyncTraceEnd(TRACE_TAG_VIEW, "IC.showRequestFromApi", 0); if (!fromIme) { diff --git a/core/java/android/view/inputmethod/InputMethodManager.java b/core/java/android/view/inputmethod/InputMethodManager.java index 41ef44e1ac1f..40b060ad0bbf 100644 --- a/core/java/android/view/inputmethod/InputMethodManager.java +++ b/core/java/android/view/inputmethod/InputMethodManager.java @@ -4361,15 +4361,14 @@ public final class InputMethodManager { * @param icProto {@link InputConnection} call data in proto format. * @hide */ - @GuardedBy("mH") public void dumpDebug(ProtoOutputStream proto, @Nullable byte[] icProto) { - if (!isImeSessionAvailableLocked()) { - return; - } - - proto.write(DISPLAY_ID, mDisplayId); - final long token = proto.start(INPUT_METHOD_MANAGER); synchronized (mH) { + if (!isImeSessionAvailableLocked()) { + return; + } + + proto.write(DISPLAY_ID, mDisplayId); + final long token = proto.start(INPUT_METHOD_MANAGER); proto.write(CUR_ID, mCurBindState.mImeId); proto.write(FULLSCREEN_MODE, mFullscreenMode); proto.write(ACTIVE, mActive); diff --git a/core/java/android/view/inputmethod/TextAppearanceInfo.java b/core/java/android/view/inputmethod/TextAppearanceInfo.java index 05717dd5d930..7eee33f7f617 100644 --- a/core/java/android/view/inputmethod/TextAppearanceInfo.java +++ b/core/java/android/view/inputmethod/TextAppearanceInfo.java @@ -238,7 +238,10 @@ public final class TextAppearanceInfo implements Parcelable { .setFontFeatureSettings(textPaint.getFontFeatureSettings()) .setFontVariationSettings(textPaint.getFontVariationSettings()) .setTextScaleX(textPaint.getTextScaleX()) - .setTextColor(textPaint.getColor()) + // When there is a hint text (text length is 0), the text color should be the normal + // text color rather than hint text color. + .setTextColor(text.length() == 0 + ? textView.getCurrentTextColor() : textPaint.getColor()) .setLinkTextColor(textPaint.linkColor) .setAllCaps(textView.isAllCaps()) .setFallbackLineSpacing(textView.isFallbackLineSpacing()) diff --git a/core/java/com/android/internal/protolog/common/ProtoLog.java b/core/java/com/android/internal/protolog/common/ProtoLog.java index 93765cdf0890..8870096f3db7 100644 --- a/core/java/com/android/internal/protolog/common/ProtoLog.java +++ b/core/java/com/android/internal/protolog/common/ProtoLog.java @@ -16,6 +16,8 @@ package com.android.internal.protolog.common; +import android.util.Log; + /** * ProtoLog API - exposes static logging methods. Usage of this API is similar * to {@code android.utils.Log} class. Instead of plain text log messages each call consists of @@ -53,6 +55,9 @@ public class ProtoLog { throw new UnsupportedOperationException( "ProtoLog calls MUST be processed with ProtoLogTool"); } + if (group.isLogToLogcat()) { + Log.d(group.getTag(), String.format(messageString, args)); + } } /** @@ -68,6 +73,9 @@ public class ProtoLog { throw new UnsupportedOperationException( "ProtoLog calls MUST be processed with ProtoLogTool"); } + if (group.isLogToLogcat()) { + Log.v(group.getTag(), String.format(messageString, args)); + } } /** @@ -83,6 +91,9 @@ public class ProtoLog { throw new UnsupportedOperationException( "ProtoLog calls MUST be processed with ProtoLogTool"); } + if (group.isLogToLogcat()) { + Log.i(group.getTag(), String.format(messageString, args)); + } } /** @@ -98,6 +109,9 @@ public class ProtoLog { throw new UnsupportedOperationException( "ProtoLog calls MUST be processed with ProtoLogTool"); } + if (group.isLogToLogcat()) { + Log.w(group.getTag(), String.format(messageString, args)); + } } /** @@ -113,6 +127,9 @@ public class ProtoLog { throw new UnsupportedOperationException( "ProtoLog calls MUST be processed with ProtoLogTool"); } + if (group.isLogToLogcat()) { + Log.e(group.getTag(), String.format(messageString, args)); + } } /** @@ -128,5 +145,8 @@ public class ProtoLog { throw new UnsupportedOperationException( "ProtoLog calls MUST be processed with ProtoLogTool"); } + if (group.isLogToLogcat()) { + Log.wtf(group.getTag(), String.format(messageString, args)); + } } } diff --git a/core/tests/coretests/src/android/view/inputmethod/TextAppearanceInfoTest.java b/core/tests/coretests/src/android/view/inputmethod/TextAppearanceInfoTest.java index f93cd18a8521..0750cf1a64ab 100644 --- a/core/tests/coretests/src/android/view/inputmethod/TextAppearanceInfoTest.java +++ b/core/tests/coretests/src/android/view/inputmethod/TextAppearanceInfoTest.java @@ -37,6 +37,7 @@ import android.text.style.ForegroundColorSpan; import android.text.style.ScaleXSpan; import android.text.style.StyleSpan; import android.text.style.TypefaceSpan; +import android.text.util.Linkify; import android.view.ViewGroup; import android.widget.EditText; @@ -53,7 +54,7 @@ import org.junit.runner.RunWith; @RunWith(AndroidJUnit4.class) public class TextAppearanceInfoTest { private static final float EPSILON = 0.0000001f; - private static final String TEST_TEXT = "Happy birthday!"; + private static final String TEST_TEXT = "Hello: google.com"; private static final float TEXT_SIZE = 16.5f; private static final LocaleList TEXT_LOCALES = LocaleList.forLanguageTags("en,ja"); private static final String FONT_FAMILY_NAME = "sans-serif"; @@ -84,39 +85,7 @@ public class TextAppearanceInfoTest { @Before public void setUp() { - mEditText.setText(mSpannableText); - mEditText.getPaint().setTextSize(TEXT_SIZE); - mEditText.setTextLocales(TEXT_LOCALES); - Typeface family = Typeface.create(FONT_FAMILY_NAME, Typeface.NORMAL); - mEditText.setTypeface( - Typeface.create(family, TEXT_WEIGHT, (TEXT_STYLE & Typeface.ITALIC) != 0)); - mEditText.setAllCaps(ALL_CAPS); - mEditText.setShadowLayer(SHADOW_RADIUS, SHADOW_DX, SHADOW_DY, SHADOW_COLOR); - mEditText.setElegantTextHeight(ELEGANT_TEXT_HEIGHT); - mEditText.setFallbackLineSpacing(FALLBACK_LINE_SPACING); - mEditText.setLetterSpacing(LETTER_SPACING); - mEditText.setFontFeatureSettings(FONT_FEATURE_SETTINGS); - mEditText.setFontVariationSettings(FONT_VARIATION_SETTINGS); - mEditText.setLineBreakStyle(LINE_BREAK_STYLE); - mEditText.setLineBreakWordStyle(LINE_BREAK_WORD_STYLE); - mEditText.setTextScaleX(TEXT_SCALEX); - mEditText.setHighlightColor(HIGHLIGHT_TEXT_COLOR); - mEditText.setTextColor(TEXT_COLOR); - mEditText.setHintTextColor(HINT_TEXT_COLOR); - mEditText.setLinkTextColor(LINK_TEXT_COLOR); - ViewGroup.LayoutParams params = - new ViewGroup.LayoutParams( - ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT); - mEditText.setLayoutParams(params); - mEditText.measure(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT); - Bitmap bitmap = - Bitmap.createBitmap( - Math.max(1, mEditText.getMeasuredWidth()), - Math.max(1, mEditText.getMeasuredHeight()), - Bitmap.Config.ARGB_8888); - mEditText.layout(0, 0, mEditText.getMeasuredWidth(), mEditText.getMeasuredHeight()); - mCanvas = new Canvas(bitmap); - mEditText.draw(mCanvas); + initEditText(mSpannableText); } @Test @@ -233,6 +202,43 @@ public class TextAppearanceInfoTest { assertEquals(info1.getSystemFontFamilyName(), FONT_FAMILY_NAME); } + private void initEditText(CharSequence text) { + mEditText.setText(text); + mEditText.getPaint().setTextSize(TEXT_SIZE); + mEditText.setTextLocales(TEXT_LOCALES); + Typeface family = Typeface.create(FONT_FAMILY_NAME, Typeface.NORMAL); + mEditText.setTypeface( + Typeface.create(family, TEXT_WEIGHT, (TEXT_STYLE & Typeface.ITALIC) != 0)); + mEditText.setAllCaps(ALL_CAPS); + mEditText.setShadowLayer(SHADOW_RADIUS, SHADOW_DX, SHADOW_DY, SHADOW_COLOR); + mEditText.setElegantTextHeight(ELEGANT_TEXT_HEIGHT); + mEditText.setFallbackLineSpacing(FALLBACK_LINE_SPACING); + mEditText.setLetterSpacing(LETTER_SPACING); + mEditText.setFontFeatureSettings(FONT_FEATURE_SETTINGS); + mEditText.setFontVariationSettings(FONT_VARIATION_SETTINGS); + mEditText.setLineBreakStyle(LINE_BREAK_STYLE); + mEditText.setLineBreakWordStyle(LINE_BREAK_WORD_STYLE); + mEditText.setTextScaleX(TEXT_SCALEX); + mEditText.setHighlightColor(HIGHLIGHT_TEXT_COLOR); + mEditText.setTextColor(TEXT_COLOR); + mEditText.setHintTextColor(HINT_TEXT_COLOR); + mEditText.setHint("Hint text"); + mEditText.setLinkTextColor(LINK_TEXT_COLOR); + mEditText.setAutoLinkMask(Linkify.WEB_URLS); + ViewGroup.LayoutParams params = + new ViewGroup.LayoutParams( + ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT); + mEditText.setLayoutParams(params); + mEditText.measure(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT); + Bitmap bitmap = + Bitmap.createBitmap( + Math.max(1, mEditText.getMeasuredWidth()), + Math.max(1, mEditText.getMeasuredHeight()), + Bitmap.Config.ARGB_8888); + mEditText.layout(0, 0, mEditText.getMeasuredWidth(), mEditText.getMeasuredHeight()); + mCanvas = new Canvas(bitmap); + mEditText.draw(mCanvas); + } private void assertTextAppearanceInfoContentsEqual(TextAppearanceInfo textAppearanceInfo) { assertEquals(textAppearanceInfo.getTextSize(), TEXT_SIZE, EPSILON); assertEquals(textAppearanceInfo.getTextLocales(), TEXT_LOCALES); @@ -258,6 +264,15 @@ public class TextAppearanceInfoTest { assertEquals(textAppearanceInfo.getLinkTextColor(), LINK_TEXT_COLOR); } + @Test + public void testCreateFromTextView_withHintText() { + // Make hint text display + initEditText(""); + + // The text color should not be hint color + assertTextAppearanceInfoContentsEqual(TextAppearanceInfo.createFromTextView(mEditText)); + } + static class CustomForegroundColorSpan extends ForegroundColorSpan { @Nullable public TextPaint lastTextPaint = null; diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransition.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransition.java index dc0a2943d394..f9d615ad0cf6 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransition.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransition.java @@ -1079,10 +1079,28 @@ public class PipTransition extends PipTransitionController { } final float alphaStart = show ? 0 : 1; final float alphaEnd = show ? 1 : 0; + final PipAnimationController.PipTransactionHandler transactionHandler = + new PipAnimationController.PipTransactionHandler() { + @Override + public boolean handlePipTransaction(SurfaceControl leash, + SurfaceControl.Transaction tx, Rect destinationBounds, float alpha) { + if (alpha == 0) { + if (show) { + tx.setPosition(leash, destinationBounds.left, destinationBounds.top); + } else { + // Put PiP out of the display so it won't block touch when it is hidden. + final Rect displayBounds = mPipDisplayLayoutState.getDisplayBounds(); + final int max = Math.max(displayBounds.width(), displayBounds.height()); + tx.setPosition(leash, max, max); + } + } + return false; + } + }; mPipAnimationController .getAnimator(taskInfo, leash, mPipBoundsState.getBounds(), alphaStart, alphaEnd) .setTransitionDirection(TRANSITION_DIRECTION_SAME) - .setPipAnimationCallback(mPipAnimationCallback) + .setPipTransactionHandler(transactionHandler) .setDuration(mEnterExitAnimationDuration) .start(); mHasFadeOut = !show; diff --git a/libs/WindowManager/Shell/tests/flicker/AndroidTestTemplate.xml b/libs/WindowManager/Shell/tests/flicker/AndroidTestTemplate.xml index ffe6d6a2e7af..991d7b5480c4 100644 --- a/libs/WindowManager/Shell/tests/flicker/AndroidTestTemplate.xml +++ b/libs/WindowManager/Shell/tests/flicker/AndroidTestTemplate.xml @@ -15,46 +15,47 @@ ~ limitations under the License. --> <configuration description="Runs WindowManager Shell Flicker Tests {MODULE}"> - <option name="test-tag" value="FlickerTests" /> + <option name="test-tag" value="FlickerTests"/> <!-- Needed for storing the perfetto trace files in the sdcard/test_results--> - <option name="isolated-storage" value="false" /> + <option name="isolated-storage" value="false"/> <target_preparer class="com.android.tradefed.targetprep.DeviceSetup"> <!-- keeps the screen on during tests --> - <option name="screen-always-on" value="on" /> + <option name="screen-always-on" value="on"/> <!-- prevents the phone from restarting --> - <option name="force-skip-system-props" value="true" /> + <option name="force-skip-system-props" value="true"/> <!-- set WM tracing verbose level to all --> - <option name="run-command" value="cmd window tracing level all" /> + <option name="run-command" value="cmd window tracing level all"/> <!-- set WM tracing to frame (avoid incomplete states) --> - <option name="run-command" value="cmd window tracing frame" /> + <option name="run-command" value="cmd window tracing frame"/> <!-- disable betterbug as it's log collection dialogues cause flakes in e2e tests --> - <option name="run-command" value="pm disable com.google.android.internal.betterbug" /> + <option name="run-command" value="pm disable com.google.android.internal.betterbug"/> <!-- ensure lock screen mode is swipe --> - <option name="run-command" value="locksettings set-disabled false" /> + <option name="run-command" value="locksettings set-disabled false"/> <!-- restart launcher to activate TAPL --> - <option name="run-command" value="setprop ro.test_harness 1 ; am force-stop com.google.android.apps.nexuslauncher" /> - <!-- Ensure output directory is empty at the start --> - <option name="run-command" value="rm -rf /sdcard/flicker" /> + <option name="run-command" + value="setprop ro.test_harness 1 ; am force-stop com.google.android.apps.nexuslauncher"/> <!-- Increase trace size: 20mb for WM and 80mb for SF --> - <option name="run-command" value="cmd window tracing size 20480" /> - <option name="run-command" value="su root service call SurfaceFlinger 1029 i32 81920" /> + <option name="run-command" value="cmd window tracing size 20480"/> + <option name="run-command" value="su root service call SurfaceFlinger 1029 i32 81920"/> </target_preparer> <target_preparer class="com.android.tradefed.targetprep.RunCommandTargetPreparer"> - <option name="run-command" value="settings put secure show_ime_with_hard_keyboard 1" /> - <option name="run-command" value="settings put system show_touches 1" /> - <option name="run-command" value="settings put system pointer_location 1" /> + <option name="test-user-token" value="%TEST_USER%"/> + <option name="run-command" value="rm -rf /data/user/%TEST_USER%/files/*"/> + <option name="run-command" value="settings put secure show_ime_with_hard_keyboard 1"/> + <option name="run-command" value="settings put system show_touches 1"/> + <option name="run-command" value="settings put system pointer_location 1"/> <option name="teardown-command" - value="settings delete secure show_ime_with_hard_keyboard" /> - <option name="teardown-command" value="settings delete system show_touches" /> - <option name="teardown-command" value="settings delete system pointer_location" /> + value="settings delete secure show_ime_with_hard_keyboard"/> + <option name="teardown-command" value="settings delete system show_touches"/> + <option name="teardown-command" value="settings delete system pointer_location"/> <option name="teardown-command" - value="cmd overlay enable com.android.internal.systemui.navbar.gestural" /> + value="cmd overlay enable com.android.internal.systemui.navbar.gestural"/> </target_preparer> <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller"> <option name="cleanup-apks" value="true"/> <option name="test-file-name" value="{MODULE}.apk"/> - <option name="test-file-name" value="FlickerTestApp.apk" /> + <option name="test-file-name" value="FlickerTestApp.apk"/> </target_preparer> <!-- Needed for pushing the trace config file --> <target_preparer class="com.android.tradefed.targetprep.RootTargetPreparer"/> @@ -65,35 +66,34 @@ /> <!--Install the content provider automatically when we push some file in sdcard folder.--> <!--Needed to avoid the installation during the test suite.--> - <option name="push-file" key="trace_config.textproto" value="/sdcard/sample.textproto" /> + <option name="push-file" key="trace_config.textproto" value="/sdcard/sample.textproto"/> </target_preparer> <test class="com.android.tradefed.testtype.AndroidJUnitTest"> <option name="package" value="{PACKAGE}"/> - <option name="shell-timeout" value="6600s" /> - <option name="test-timeout" value="6000s" /> - <option name="hidden-api-checks" value="false" /> - <option name="device-listeners" value="android.device.collectors.PerfettoListener" /> + <option name="shell-timeout" value="6600s"/> + <option name="test-timeout" value="6000s"/> + <option name="hidden-api-checks" value="false"/> + <option name="device-listeners" value="android.device.collectors.PerfettoListener"/> <!-- PerfettoListener related arguments --> - <option name="instrumentation-arg" key="perfetto_config_text_proto" value="true" /> + <option name="instrumentation-arg" key="perfetto_config_text_proto" value="true"/> <option name="instrumentation-arg" key="perfetto_config_file" value="trace_config.textproto" /> - <option name="instrumentation-arg" key="per_run" value="true" /> + <option name="instrumentation-arg" key="per_run" value="true"/> </test> <!-- Needed for pulling the collected trace config on to the host --> <metrics_collector class="com.android.tradefed.device.metric.FilePullerLogCollector"> - <option name="pull-pattern-keys" value="perfetto_file_path" /> - </metrics_collector> - <metrics_collector class="com.android.tradefed.device.metric.FilePullerLogCollector"> - <option name="pull-pattern-keys" value="(\w)+\.winscope" /> - <option name="pull-pattern-keys" value="(\w)+\.mp4" /> - <option name="collect-on-run-ended-only" value="false" /> - <option name="clean-up" value="true" /> - </metrics_collector> - <metrics_collector class="com.android.tradefed.device.metric.FilePullerLogCollector"> - <option name="directory-keys" value="/sdcard/flicker" /> - <option name="collect-on-run-ended-only" value="true" /> - <option name="clean-up" value="true" /> + <option name="pull-pattern-keys" value="perfetto_file_path"/> + <option name="directory-keys" + value="/data/user/0/com.android.wm.shell.flicker/files"/> + <option name="directory-keys" + value="/data/user/0/com.android.wm.shell.flicker.bubbles/files"/> + <option name="directory-keys" + value="/data/user/0/com.android.server.wm.flicker.pip/files"/> + <option name="directory-keys" + value="/data/user/0/com.android.server.wm.flicker.splitscreen/files"/> + <option name="collect-on-run-ended-only" value="true"/> + <option name="clean-up" value="true"/> </metrics_collector> </configuration> diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothBroadcastUtils.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothBroadcastUtils.java index 2bca7cf9762e..1ff2befd19ed 100644 --- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothBroadcastUtils.java +++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothBroadcastUtils.java @@ -43,5 +43,5 @@ public final class BluetoothBroadcastUtils { /** * Bluetooth scheme. */ - public static final String SCHEME_BT_BROADCAST_METADATA = "BT:BluetoothLeBroadcastMetadata:"; + public static final String SCHEME_BT_BROADCAST_METADATA = "BT:"; } diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothLeBroadcastMetadataExt.kt b/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothLeBroadcastMetadataExt.kt index b54b115213d5..9bb11f8da645 100644 --- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothLeBroadcastMetadataExt.kt +++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothLeBroadcastMetadataExt.kt @@ -15,23 +15,92 @@ */ package com.android.settingslib.bluetooth +import android.annotation.TargetApi +import android.bluetooth.BluetoothAdapter +import android.bluetooth.BluetoothDevice +import android.bluetooth.BluetoothLeAudioCodecConfigMetadata +import android.bluetooth.BluetoothLeAudioContentMetadata +import android.bluetooth.BluetoothLeBroadcastChannel import android.bluetooth.BluetoothLeBroadcastMetadata -import android.os.Parcel -import android.os.Parcelable +import android.bluetooth.BluetoothLeBroadcastSubgroup +import android.os.Build import android.util.Base64 import android.util.Log import com.android.settingslib.bluetooth.BluetoothBroadcastUtils.SCHEME_BT_BROADCAST_METADATA object BluetoothLeBroadcastMetadataExt { - private const val TAG = "BluetoothLeBroadcastMetadataExt" + private const val TAG = "BtLeBroadcastMetadataExt" + + // BluetoothLeBroadcastMetadata + private const val KEY_BT_QR_VER = "R" + private const val KEY_BT_ADDRESS_TYPE = "T" + private const val KEY_BT_DEVICE = "D" + private const val KEY_BT_ADVERTISING_SID = "AS" + private const val KEY_BT_BROADCAST_ID = "B" + private const val KEY_BT_BROADCAST_NAME = "BN" + private const val KEY_BT_PUBLIC_BROADCAST_DATA = "PM" + private const val KEY_BT_SYNC_INTERVAL = "SI" + private const val KEY_BT_BROADCAST_CODE = "C" + private const val KEY_BT_SUBGROUPS = "SG" + private const val KEY_BT_VENDOR_SPECIFIC = "V" + private const val KEY_BT_ANDROID_VERSION = "VN" + + // Subgroup data + private const val KEY_BTSG_BIS_SYNC = "BS" + private const val KEY_BTSG_BIS_MASK = "BM" + private const val KEY_BTSG_AUDIO_CONTENT = "AC" + + // Vendor specific data + private const val KEY_BTVSD_COMPANY_ID = "VI" + private const val KEY_BTVSD_VENDOR_DATA = "VD" + + private const val DELIMITER_KEY_VALUE = ":" + private const val DELIMITER_BT_LEVEL_1 = ";" + private const val DELIMITER_BT_LEVEL_2 = "," + + private const val SUFFIX_QR_CODE = ";;" + + private const val ANDROID_VER = "U" + private const val QR_CODE_VER = 0x010000 + + // BT constants + private const val BIS_SYNC_MAX_CHANNEL = 32 + private const val BIS_SYNC_NO_PREFERENCE = 0xFFFFFFFFu + private const val SUBGROUP_LC3_CODEC_ID = 0x6L /** * Converts [BluetoothLeBroadcastMetadata] to QR code string. * - * QR code string will prefix with "BT:BluetoothLeBroadcastMetadata:". + * QR code string will prefix with "BT:". */ - fun BluetoothLeBroadcastMetadata.toQrCodeString(): String = - SCHEME_BT_BROADCAST_METADATA + Base64.encodeToString(toBytes(this), Base64.NO_WRAP) + fun BluetoothLeBroadcastMetadata.toQrCodeString(): String { + val entries = mutableListOf<Pair<String, String>>() + entries.add(Pair(KEY_BT_QR_VER, QR_CODE_VER.toString())) + entries.add(Pair(KEY_BT_ADDRESS_TYPE, this.sourceAddressType.toString())) + entries.add(Pair(KEY_BT_DEVICE, this.sourceDevice.address.replace(":", "-"))) + entries.add(Pair(KEY_BT_ADVERTISING_SID, this.sourceAdvertisingSid.toString())) + entries.add(Pair(KEY_BT_BROADCAST_ID, this.broadcastId.toString())) + if (this.broadcastName != null) { + entries.add(Pair(KEY_BT_BROADCAST_NAME, Base64.encodeToString( + this.broadcastName?.toByteArray(Charsets.UTF_8), Base64.NO_WRAP))) + } + if (this.publicBroadcastMetadata != null) { + entries.add(Pair(KEY_BT_PUBLIC_BROADCAST_DATA, Base64.encodeToString( + this.publicBroadcastMetadata?.rawMetadata, Base64.NO_WRAP))) + } + entries.add(Pair(KEY_BT_SYNC_INTERVAL, this.paSyncInterval.toString())) + if (this.broadcastCode != null) { + entries.add(Pair(KEY_BT_BROADCAST_CODE, + Base64.encodeToString(this.broadcastCode, Base64.NO_WRAP))) + } + this.subgroups.forEach { + subgroup -> entries.add(Pair(KEY_BT_SUBGROUPS, subgroup.toQrCodeString())) } + entries.add(Pair(KEY_BT_ANDROID_VERSION, ANDROID_VER)) + val qrCodeString = SCHEME_BT_BROADCAST_METADATA + + entries.toQrCodeString(DELIMITER_BT_LEVEL_1) + SUFFIX_QR_CODE + Log.d(TAG, "Generated QR string : $qrCodeString") + return qrCodeString + } /** * Converts QR code string to [BluetoothLeBroadcastMetadata]. @@ -39,32 +108,255 @@ object BluetoothLeBroadcastMetadataExt { * QR code string should prefix with "BT:BluetoothLeBroadcastMetadata:". */ fun convertToBroadcastMetadata(qrCodeString: String): BluetoothLeBroadcastMetadata? { - if (!qrCodeString.startsWith(SCHEME_BT_BROADCAST_METADATA)) return null + if (!qrCodeString.startsWith(SCHEME_BT_BROADCAST_METADATA)) { + Log.e(TAG, "String \"$qrCodeString\" does not begin with " + + "\"$SCHEME_BT_BROADCAST_METADATA\"") + return null + } return try { - val encodedString = qrCodeString.removePrefix(SCHEME_BT_BROADCAST_METADATA) - val bytes = Base64.decode(encodedString, Base64.NO_WRAP) - createFromBytes(BluetoothLeBroadcastMetadata.CREATOR, bytes) + Log.d(TAG, "Parsing QR string: $qrCodeString") + val strippedString = + qrCodeString.removePrefix(SCHEME_BT_BROADCAST_METADATA) + .removeSuffix(SUFFIX_QR_CODE) + Log.d(TAG, "Stripped to: $strippedString") + parseQrCodeToMetadata(strippedString) } catch (e: Exception) { - Log.w(TAG, "Cannot convert QR code string to BluetoothLeBroadcastMetadata", e) + Log.w(TAG, "Cannot parse: $qrCodeString", e) null } } - private fun toBytes(parcelable: Parcelable): ByteArray = - Parcel.obtain().run { - parcelable.writeToParcel(this, 0) - setDataPosition(0) - val bytes = marshall() - recycle() - bytes + private fun BluetoothLeBroadcastSubgroup.toQrCodeString(): String { + val entries = mutableListOf<Pair<String, String>>() + entries.add(Pair(KEY_BTSG_BIS_SYNC, getBisSyncFromChannels(this.channels).toString())) + entries.add(Pair(KEY_BTSG_BIS_MASK, getBisMaskFromChannels(this.channels).toString())) + entries.add(Pair(KEY_BTSG_AUDIO_CONTENT, + Base64.encodeToString(this.contentMetadata.rawMetadata, Base64.NO_WRAP))) + return entries.toQrCodeString(DELIMITER_BT_LEVEL_2) + } + + private fun List<Pair<String, String>>.toQrCodeString(delimiter: String): String { + val entryStrings = this.map{ it.first + DELIMITER_KEY_VALUE + it.second } + return entryStrings.joinToString(separator = delimiter) + } + + @TargetApi(Build.VERSION_CODES.TIRAMISU) + private fun parseQrCodeToMetadata(input: String): BluetoothLeBroadcastMetadata { + // Split into a list of list + val level1Fields = input.split(DELIMITER_BT_LEVEL_1) + .map{it.split(DELIMITER_KEY_VALUE, limit = 2)} + var qrCodeVersion = -1 + var sourceAddrType = BluetoothDevice.ADDRESS_TYPE_UNKNOWN + var sourceAddrString: String? = null + var sourceAdvertiserSid = -1 + var broadcastId = -1 + var broadcastName: String? = null + var publicBroadcastMetadata: BluetoothLeAudioContentMetadata? = null + var paSyncInterval = -1 + var broadcastCode: ByteArray? = null + // List of VendorID -> Data Pairs + var vendorDataList = mutableListOf<Pair<Int, ByteArray?>>() + var androidVersion: String? = null + val builder = BluetoothLeBroadcastMetadata.Builder() + + for (field: List<String> in level1Fields) { + if (field.isEmpty()) { + continue + } + val key = field[0] + // Ignore 3rd value and after + val value = if (field.size > 1) field[1] else "" + when (key) { + KEY_BT_QR_VER -> { + require(qrCodeVersion == -1) { "Duplicate qrCodeVersion: $input" } + qrCodeVersion = value.toInt() + } + KEY_BT_ADDRESS_TYPE -> { + require(sourceAddrType == BluetoothDevice.ADDRESS_TYPE_UNKNOWN) { + "Duplicate sourceAddrType: $input" + } + sourceAddrType = value.toInt() + } + KEY_BT_DEVICE -> { + require(sourceAddrString == null) { "Duplicate sourceAddr: $input" } + sourceAddrString = value.replace("-", ":") + } + KEY_BT_ADVERTISING_SID -> { + require(sourceAdvertiserSid == -1) { "Duplicate sourceAdvertiserSid: $input" } + sourceAdvertiserSid = value.toInt() + } + KEY_BT_BROADCAST_ID -> { + require(broadcastId == -1) { "Duplicate broadcastId: $input" } + broadcastId = value.toInt() + } + KEY_BT_BROADCAST_NAME -> { + require(broadcastName == null) { "Duplicate broadcastName: $input" } + broadcastName = String(Base64.decode(value, Base64.NO_WRAP)) + } + KEY_BT_PUBLIC_BROADCAST_DATA -> { + require(publicBroadcastMetadata == null) { + "Duplicate publicBroadcastMetadata $input" + } + publicBroadcastMetadata = BluetoothLeAudioContentMetadata + .fromRawBytes(Base64.decode(value, Base64.NO_WRAP)) + } + KEY_BT_SYNC_INTERVAL -> { + require(paSyncInterval == -1) { "Duplicate paSyncInterval: $input" } + paSyncInterval = value.toInt() + } + KEY_BT_BROADCAST_CODE -> { + require(broadcastCode == null) { "Duplicate broadcastCode: $input" } + broadcastCode = Base64.decode(value, Base64.NO_WRAP) + } + KEY_BT_ANDROID_VERSION -> { + require(androidVersion == null) { "Duplicate androidVersion: $input" } + androidVersion = value + Log.i(TAG, "QR code Android version: $androidVersion") + } + // Repeatable + KEY_BT_SUBGROUPS -> { + builder.addSubgroup(parseSubgroupData(value)) + } + // Repeatable + KEY_BT_VENDOR_SPECIFIC -> { + vendorDataList.add(parseVendorData(value)) + } + } + } + Log.d(TAG, "parseQrCodeToMetadata: sourceAddrType=$sourceAddrType, " + + "sourceAddr=$sourceAddrString, sourceAdvertiserSid=$sourceAdvertiserSid, " + + "broadcastId=$broadcastId, broadcastName=$broadcastName, " + + "publicBroadcastMetadata=${publicBroadcastMetadata != null}, " + + "paSyncInterval=$paSyncInterval, " + + "broadcastCode=${broadcastCode?.toString(Charsets.UTF_8)}") + Log.d(TAG, "Not used in current code, but part of the specification: " + + "qrCodeVersion=$qrCodeVersion, androidVersion=$androidVersion, " + + "vendorDataListSize=${vendorDataList.size}") + val adapter = BluetoothAdapter.getDefaultAdapter() + // add source device and set broadcast code + val device = adapter.getRemoteLeDevice(requireNotNull(sourceAddrString), sourceAddrType) + builder.apply { + setSourceDevice(device, sourceAddrType) + setSourceAdvertisingSid(sourceAdvertiserSid) + setBroadcastId(broadcastId) + setBroadcastName(broadcastName) + setPublicBroadcast(publicBroadcastMetadata != null) + setPublicBroadcastMetadata(publicBroadcastMetadata) + setPaSyncInterval(paSyncInterval) + setEncrypted(broadcastCode != null) + setBroadcastCode(broadcastCode) + // Presentation delay is unknown and not useful when adding source + // Broadcast sink needs to sync to the Broadcast source to get presentation delay + setPresentationDelayMicros(0) + } + return builder.build() + } + + private fun parseSubgroupData(input: String): BluetoothLeBroadcastSubgroup { + Log.d(TAG, "parseSubgroupData: $input") + val fields = input.split(DELIMITER_BT_LEVEL_2) + var bisSync: UInt? = null + var bisMask: UInt? = null + var metadata: ByteArray? = null + + fields.forEach { field -> + val(key, value) = field.split(DELIMITER_KEY_VALUE) + when (key) { + KEY_BTSG_BIS_SYNC -> { + require(bisSync == null) { "Duplicate bisSync: $input" } + bisSync = value.toUInt() + } + KEY_BTSG_BIS_MASK -> { + require(bisMask == null) { "Duplicate bisMask: $input" } + bisMask = value.toUInt() + } + KEY_BTSG_AUDIO_CONTENT -> { + require(metadata == null) { "Duplicate metadata: $input" } + metadata = Base64.decode(value, Base64.NO_WRAP) + } + } } + val channels = convertToChannels(requireNotNull(bisSync), requireNotNull(bisMask)) + val audioCodecConfigMetadata = BluetoothLeAudioCodecConfigMetadata.Builder() + .setAudioLocation(0).build() + return BluetoothLeBroadcastSubgroup.Builder().apply { + setCodecId(SUBGROUP_LC3_CODEC_ID) + setCodecSpecificConfig(audioCodecConfigMetadata) + setContentMetadata( + BluetoothLeAudioContentMetadata.fromRawBytes(metadata ?: ByteArray(0))) + channels.forEach(::addChannel) + }.build() + } + + private fun parseVendorData(input: String): Pair<Int, ByteArray?> { + var companyId = -1 + var data: ByteArray? = null + val fields = input.split(DELIMITER_BT_LEVEL_2) + fields.forEach { field -> + val(key, value) = field.split(DELIMITER_KEY_VALUE) + when (key) { + KEY_BTVSD_COMPANY_ID -> { + require(companyId == -1) { "Duplicate companyId: $input" } + companyId = value.toInt() + } + KEY_BTVSD_VENDOR_DATA -> { + require(data == null) { "Duplicate data: $input" } + data = Base64.decode(value, Base64.NO_WRAP) + } + } + } + return Pair(companyId, data) + } - private fun <T> createFromBytes(creator: Parcelable.Creator<T>, bytes: ByteArray): T = - Parcel.obtain().run { - unmarshall(bytes, 0, bytes.size) - setDataPosition(0) - val created = creator.createFromParcel(this) - recycle() - created + private fun getBisSyncFromChannels(channels: List<BluetoothLeBroadcastChannel>): UInt { + var bisSync = 0u + // channel index starts from 1 + channels.forEach { channel -> + if (channel.isSelected && channel.channelIndex > 0) { + bisSync = bisSync or (1u shl (channel.channelIndex - 1)) + } } + // No channel is selected means no preference on Android platform + return if (bisSync == 0u) BIS_SYNC_NO_PREFERENCE else bisSync + } + + private fun getBisMaskFromChannels(channels: List<BluetoothLeBroadcastChannel>): UInt { + var bisMask = 0u + // channel index starts from 1 + channels.forEach { channel -> + if (channel.channelIndex > 0) { + bisMask = bisMask or (1u shl (channel.channelIndex - 1)) + } + } + return bisMask + } + + private fun convertToChannels(bisSync: UInt, bisMask: UInt): + List<BluetoothLeBroadcastChannel> { + Log.d(TAG, "convertToChannels: bisSync=$bisSync, bisMask=$bisMask") + var selectionMask = bisSync + if (bisSync != BIS_SYNC_NO_PREFERENCE) { + require(bisMask == (bisMask or bisSync)) { + "bisSync($bisSync) must select a subset of bisMask($bisMask) if it has preferences" + } + } else { + // No channel preference means no channel is selected + selectionMask = 0u + } + val channels = mutableListOf<BluetoothLeBroadcastChannel>() + val audioCodecConfigMetadata = BluetoothLeAudioCodecConfigMetadata.Builder() + .setAudioLocation(0).build() + for (i in 0 until BIS_SYNC_MAX_CHANNEL) { + val channelMask = 1u shl i + if ((bisMask and channelMask) != 0u) { + val channel = BluetoothLeBroadcastChannel.Builder().apply { + setSelected((selectionMask and channelMask) != 0u) + setChannelIndex(i + 1) + setCodecMetadata(audioCodecConfigMetadata) + } + channels.add(channel.build()) + } + } + return channels + } }
\ No newline at end of file diff --git a/packages/SettingsLib/tests/unit/src/com/android/settingslib/bluetooth/BluetoothLeBroadcastMetadataExtTest.kt b/packages/SettingsLib/tests/unit/src/com/android/settingslib/bluetooth/BluetoothLeBroadcastMetadataExtTest.kt index 0e3590d96a14..27d7078774d5 100644 --- a/packages/SettingsLib/tests/unit/src/com/android/settingslib/bluetooth/BluetoothLeBroadcastMetadataExtTest.kt +++ b/packages/SettingsLib/tests/unit/src/com/android/settingslib/bluetooth/BluetoothLeBroadcastMetadataExtTest.kt @@ -34,24 +34,33 @@ class BluetoothLeBroadcastMetadataExtTest { @Test fun toQrCodeString() { val subgroup = BluetoothLeBroadcastSubgroup.Builder().apply { - setCodecId(100) + setCodecId(0x6) val audioCodecConfigMetadata = BluetoothLeAudioCodecConfigMetadata.Builder().build() setCodecSpecificConfig(audioCodecConfigMetadata) - setContentMetadata(BluetoothLeAudioContentMetadata.Builder().build()) + setContentMetadata(BluetoothLeAudioContentMetadata.Builder() + .setProgramInfo("Test").setLanguage("eng").build()) addChannel(BluetoothLeBroadcastChannel.Builder().apply { - setChannelIndex(1000) + setSelected(true) + setChannelIndex(2) + setCodecMetadata(audioCodecConfigMetadata) + }.build()) + addChannel(BluetoothLeBroadcastChannel.Builder().apply { + setSelected(true) + setChannelIndex(1) setCodecMetadata(audioCodecConfigMetadata) }.build()) }.build() val metadata = BluetoothLeBroadcastMetadata.Builder().apply { - setSourceDevice(Device, 0) + setSourceDevice(Device, BluetoothDevice.ADDRESS_TYPE_RANDOM) setSourceAdvertisingSid(1) - setBroadcastId(2) - setPaSyncInterval(3) + setBroadcastId(123456) + setBroadcastName("Test") + setPublicBroadcastMetadata(BluetoothLeAudioContentMetadata.Builder() + .setProgramInfo("pTest").build()) + setPaSyncInterval(160) setEncrypted(true) - setBroadcastCode(byteArrayOf(10, 11, 12, 13)) - setPresentationDelayMicros(4) + setBroadcastCode("TestCode".toByteArray(Charsets.UTF_8)) addSubgroup(subgroup) }.build() @@ -61,6 +70,108 @@ class BluetoothLeBroadcastMetadataExtTest { } @Test + fun toQrCodeString_NoChannelSelected() { + val subgroup = BluetoothLeBroadcastSubgroup.Builder().apply { + setCodecId(0x6) + val audioCodecConfigMetadata = BluetoothLeAudioCodecConfigMetadata.Builder().build() + setCodecSpecificConfig(audioCodecConfigMetadata) + setContentMetadata(BluetoothLeAudioContentMetadata.Builder() + .setProgramInfo("Test").setLanguage("eng").build()) + addChannel(BluetoothLeBroadcastChannel.Builder().apply { + setSelected(false) + setChannelIndex(2) + setCodecMetadata(audioCodecConfigMetadata) + }.build()) + addChannel(BluetoothLeBroadcastChannel.Builder().apply { + setSelected(false) + setChannelIndex(1) + setCodecMetadata(audioCodecConfigMetadata) + }.build()) + }.build() + + val metadata = BluetoothLeBroadcastMetadata.Builder().apply { + setSourceDevice(Device, BluetoothDevice.ADDRESS_TYPE_RANDOM) + setSourceAdvertisingSid(1) + setBroadcastId(123456) + setBroadcastName("Test") + setPublicBroadcastMetadata(BluetoothLeAudioContentMetadata.Builder() + .setProgramInfo("pTest").build()) + setPaSyncInterval(160) + setEncrypted(true) + setBroadcastCode("TestCode".toByteArray(Charsets.UTF_8)) + addSubgroup(subgroup) + }.build() + + val qrCodeString = metadata.toQrCodeString() + + val parsedMetadata = + BluetoothLeBroadcastMetadataExt.convertToBroadcastMetadata(qrCodeString)!! + + assertThat(parsedMetadata).isNotNull() + assertThat(parsedMetadata.subgroups).isNotNull() + assertThat(parsedMetadata.subgroups.size).isEqualTo(1) + assertThat(parsedMetadata.subgroups[0].channels).isNotNull() + assertThat(parsedMetadata.subgroups[0].channels.size).isEqualTo(2) + assertThat(parsedMetadata.subgroups[0].hasChannelPreference()).isFalse() + // Input order does not matter due to parsing through bisMask + assertThat(parsedMetadata.subgroups[0].channels[0].channelIndex).isEqualTo(1) + assertThat(parsedMetadata.subgroups[0].channels[0].isSelected).isFalse() + assertThat(parsedMetadata.subgroups[0].channels[1].channelIndex).isEqualTo(2) + assertThat(parsedMetadata.subgroups[0].channels[1].isSelected).isFalse() + } + + @Test + fun toQrCodeString_OneChannelSelected() { + val subgroup = BluetoothLeBroadcastSubgroup.Builder().apply { + setCodecId(0x6) + val audioCodecConfigMetadata = BluetoothLeAudioCodecConfigMetadata.Builder().build() + setCodecSpecificConfig(audioCodecConfigMetadata) + setContentMetadata(BluetoothLeAudioContentMetadata.Builder() + .setProgramInfo("Test").setLanguage("eng").build()) + addChannel(BluetoothLeBroadcastChannel.Builder().apply { + setSelected(false) + setChannelIndex(1) + setCodecMetadata(audioCodecConfigMetadata) + }.build()) + addChannel(BluetoothLeBroadcastChannel.Builder().apply { + setSelected(true) + setChannelIndex(2) + setCodecMetadata(audioCodecConfigMetadata) + }.build()) + }.build() + + val metadata = BluetoothLeBroadcastMetadata.Builder().apply { + setSourceDevice(Device, BluetoothDevice.ADDRESS_TYPE_RANDOM) + setSourceAdvertisingSid(1) + setBroadcastId(123456) + setBroadcastName("Test") + setPublicBroadcastMetadata(BluetoothLeAudioContentMetadata.Builder() + .setProgramInfo("pTest").build()) + setPaSyncInterval(160) + setEncrypted(true) + setBroadcastCode("TestCode".toByteArray(Charsets.UTF_8)) + addSubgroup(subgroup) + }.build() + + val qrCodeString = metadata.toQrCodeString() + + val parsedMetadata = + BluetoothLeBroadcastMetadataExt.convertToBroadcastMetadata(qrCodeString)!! + + assertThat(parsedMetadata).isNotNull() + assertThat(parsedMetadata.subgroups).isNotNull() + assertThat(parsedMetadata.subgroups.size).isEqualTo(1) + assertThat(parsedMetadata.subgroups[0].channels).isNotNull() + // Only selected channel can be recovered + assertThat(parsedMetadata.subgroups[0].channels.size).isEqualTo(2) + assertThat(parsedMetadata.subgroups[0].hasChannelPreference()).isTrue() + assertThat(parsedMetadata.subgroups[0].channels[0].channelIndex).isEqualTo(1) + assertThat(parsedMetadata.subgroups[0].channels[0].isSelected).isFalse() + assertThat(parsedMetadata.subgroups[0].channels[1].channelIndex).isEqualTo(2) + assertThat(parsedMetadata.subgroups[0].channels[1].isSelected).isTrue() + } + + @Test fun decodeAndEncodeAgain_sameString() { val metadata = BluetoothLeBroadcastMetadataExt.convertToBroadcastMetadata(QR_CODE_STRING)!! @@ -73,13 +184,12 @@ class BluetoothLeBroadcastMetadataExtTest { const val TEST_DEVICE_ADDRESS = "00:A1:A1:A1:A1:A1" val Device: BluetoothDevice = - BluetoothAdapter.getDefaultAdapter().getRemoteDevice(TEST_DEVICE_ADDRESS) + BluetoothAdapter.getDefaultAdapter().getRemoteLeDevice(TEST_DEVICE_ADDRESS, + BluetoothDevice.ADDRESS_TYPE_RANDOM) const val QR_CODE_STRING = - "BT:BluetoothLeBroadcastMetadata:AAAAAAEAAAABAAAAEQAAADAAMAA6AEEAMQA6AEEAMQA6AEEAMQA6" + - "AEEAMQA6AEEAMQAAAAAAAAABAAAAAgAAAAMAAAABAAAABAAAAAQAAAAKCwwNBAAAAAEAAAABAAAAZAAA" + - "AAAAAAABAAAAAAAAAAAAAAAGAAAABgAAAAUDAAAAAAAAAAAAAAAAAAAAAAAAAQAAAP//////////AAAA" + - "AAAAAAABAAAAAQAAAAAAAADoAwAAAQAAAAAAAAAAAAAABgAAAAYAAAAFAwAAAAAAAAAAAAAAAAAAAAAA" + - "AAAAAAD/////AAAAAAAAAAA=" + "BT:R:65536;T:1;D:00-A1-A1-A1-A1-A1;AS:1;B:123456;BN:VGVzdA==;" + + "PM:BgNwVGVzdA==;SI:160;C:VGVzdENvZGU=;SG:BS:3,BM:3,AC:BQNUZXN0BARlbmc=;" + + "VN:U;;" } }
\ No newline at end of file diff --git a/packages/SystemUI/compose/core/src/com/android/compose/animation/Easings.kt b/packages/SystemUI/compose/core/src/com/android/compose/animation/Easings.kt index 5d09de167dc5..4fe9f89830f6 100644 --- a/packages/SystemUI/compose/core/src/com/android/compose/animation/Easings.kt +++ b/packages/SystemUI/compose/core/src/com/android/compose/animation/Easings.kt @@ -59,5 +59,20 @@ object Easings { /** The linear interpolator. */ val Linear = fromInterpolator(InterpolatorsAndroidX.LINEAR) + /** The default legacy interpolator as defined in Material 1. Also known as FAST_OUT_SLOW_IN. */ + val Legacy = fromInterpolator(InterpolatorsAndroidX.LEGACY) + + /** + * The default legacy accelerating interpolator as defined in Material 1. Also known as + * FAST_OUT_LINEAR_IN. + */ + val LegacyAccelerate = fromInterpolator(InterpolatorsAndroidX.LEGACY_ACCELERATE) + + /** + * T The default legacy decelerating interpolator as defined in Material 1. Also known as + * LINEAR_OUT_SLOW_IN. + */ + val LegacyDecelerate = fromInterpolator(InterpolatorsAndroidX.LEGACY_DECELERATE) + private fun fromInterpolator(source: Interpolator) = Easing { x -> source.getInterpolation(x) } } diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/PinBouncer.kt b/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/PinBouncer.kt index 20b859a6a242..f80143499928 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/PinBouncer.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/PinBouncer.kt @@ -19,23 +19,19 @@ package com.android.systemui.bouncer.ui.composable import android.view.HapticFeedbackConstants -import androidx.compose.animation.AnimatedVisibility import androidx.compose.animation.ExperimentalAnimationApi import androidx.compose.animation.animateColorAsState -import androidx.compose.animation.animateContentSize import androidx.compose.animation.core.AnimationSpec -import androidx.compose.animation.core.LinearEasing +import androidx.compose.animation.core.MutableTransitionState +import androidx.compose.animation.core.Transition +import androidx.compose.animation.core.animateDp import androidx.compose.animation.core.animateDpAsState +import androidx.compose.animation.core.keyframes +import androidx.compose.animation.core.snap import androidx.compose.animation.core.tween -import androidx.compose.animation.fadeIn -import androidx.compose.animation.fadeOut -import androidx.compose.animation.scaleIn -import androidx.compose.animation.scaleOut -import androidx.compose.animation.slideInHorizontally -import androidx.compose.animation.slideOutHorizontally -import androidx.compose.foundation.background +import androidx.compose.animation.core.updateTransition +import androidx.compose.foundation.Canvas import androidx.compose.foundation.gestures.detectTapGestures -import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row @@ -43,35 +39,40 @@ import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.heightIn import androidx.compose.foundation.layout.size -import androidx.compose.foundation.shape.CircleShape +import androidx.compose.foundation.layout.wrapContentSize import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.collectAsState import androidx.compose.runtime.getValue +import androidx.compose.runtime.key import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.runtime.setValue +import androidx.compose.runtime.snapshotFlow +import androidx.compose.runtime.snapshots.SnapshotStateList import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.drawBehind import androidx.compose.ui.geometry.CornerRadius import androidx.compose.ui.graphics.Color import androidx.compose.ui.input.pointer.pointerInput +import androidx.compose.ui.layout.Layout import androidx.compose.ui.platform.LocalView +import androidx.compose.ui.unit.Constraints import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.dp import com.android.compose.animation.Easings import com.android.compose.grid.VerticalGrid import com.android.systemui.R +import com.android.systemui.bouncer.ui.viewmodel.EnteredKey import com.android.systemui.bouncer.ui.viewmodel.PinBouncerViewModel import com.android.systemui.common.shared.model.ContentDescription import com.android.systemui.common.shared.model.Icon import com.android.systemui.common.ui.compose.Icon import com.android.systemui.compose.modifiers.thenIf -import kotlin.math.max import kotlin.time.Duration.Companion.milliseconds import kotlin.time.DurationUnit import kotlinx.coroutines.async @@ -86,8 +87,6 @@ internal fun PinBouncer( // Report that the UI is shown to let the view-model run some logic. LaunchedEffect(Unit) { viewModel.onShown() } - // The length of the PIN input received so far, so we know how many dots to render. - val pinLength: Pair<Int, Int> by viewModel.pinLengths.collectAsState() val isInputEnabled: Boolean by viewModel.isInputEnabled.collectAsState() val animateFailure: Boolean by viewModel.animateFailure.collectAsState() @@ -103,30 +102,7 @@ internal fun PinBouncer( horizontalAlignment = Alignment.CenterHorizontally, modifier = modifier, ) { - Row( - horizontalArrangement = Arrangement.spacedBy(12.dp), - modifier = Modifier.heightIn(min = 16.dp).animateContentSize(), - ) { - // TODO(b/281871687): add support for dot shapes. - val (previousPinLength, currentPinLength) = pinLength - val dotCount = max(previousPinLength, currentPinLength) + 1 - repeat(dotCount) { index -> - AnimatedVisibility( - visible = index < currentPinLength, - enter = fadeIn() + scaleIn() + slideInHorizontally(), - exit = fadeOut() + scaleOut() + slideOutHorizontally(), - ) { - Box( - modifier = - Modifier.size(16.dp) - .background( - MaterialTheme.colorScheme.onSurfaceVariant, - CircleShape, - ) - ) - } - } - } + PinInputDisplay(viewModel) Spacer(Modifier.height(100.dp)) @@ -187,6 +163,148 @@ internal fun PinBouncer( } @Composable +private fun PinInputDisplay(viewModel: PinBouncerViewModel) { + val currentPinEntries: List<EnteredKey> by viewModel.pinEntries.collectAsState() + + // visiblePinEntries keeps pins removed from currentPinEntries in the composition until their + // disappear-animation completed. The list is sorted by the natural ordering of EnteredKey, + // which is guaranteed to produce the original edit order, since the model only modifies entries + // at the end. + val visiblePinEntries = remember { SnapshotStateList<EnteredKey>() } + currentPinEntries.forEach { + val index = visiblePinEntries.binarySearch(it) + if (index < 0) { + val insertionPoint = -(index + 1) + visiblePinEntries.add(insertionPoint, it) + } + } + + Row( + modifier = + Modifier.heightIn(min = entryShapeSize) + // Pins overflowing horizontally should still be shown as scrolling. + .wrapContentSize(unbounded = true), + ) { + visiblePinEntries.forEachIndexed { index, entry -> + key(entry) { + val visibility = remember { + MutableTransitionState<EntryVisibility>(EntryVisibility.Hidden) + } + visibility.targetState = + when { + currentPinEntries.isEmpty() && visiblePinEntries.size > 1 -> + EntryVisibility.BulkHidden(index, visiblePinEntries.size) + currentPinEntries.contains(entry) -> EntryVisibility.Shown + else -> EntryVisibility.Hidden + } + + ObscuredInputEntry(updateTransition(visibility, label = "Pin Entry $entry")) + + LaunchedEffect(entry) { + // Remove entry from visiblePinEntries once the hide transition completed. + snapshotFlow { + visibility.currentState == visibility.targetState && + visibility.targetState != EntryVisibility.Shown + } + .collect { isRemoved -> + if (isRemoved) { + visiblePinEntries.remove(entry) + } + } + } + } + } + } +} + +private sealed class EntryVisibility { + object Shown : EntryVisibility() + + object Hidden : EntryVisibility() + + /** + * Same as [Hidden], but applies when multiple entries are hidden simultaneously, without + * collapsing during the hide. + */ + data class BulkHidden(val staggerIndex: Int, val totalEntryCount: Int) : EntryVisibility() +} + +@Composable +private fun ObscuredInputEntry(transition: Transition<EntryVisibility>) { + // spec: http://shortn/_DEhE3Xl2bi + val shapePadding = 6.dp + val shapeOvershootSize = 22.dp + val dismissStaggerDelayMs = 33 + val dismissDurationMs = 450 + val expansionDurationMs = 250 + val shapeExpandDurationMs = 83 + val shapeRetractDurationMs = 167 + val shapeCollapseDurationMs = 200 + + val animatedEntryWidth by + transition.animateDp( + transitionSpec = { + when (val target = targetState) { + is EntryVisibility.BulkHidden -> + // only collapse horizontal space once all entries are removed + snap(dismissDurationMs + dismissStaggerDelayMs * target.totalEntryCount) + else -> tween(expansionDurationMs, easing = Easings.Standard) + } + }, + label = "entry space" + ) { state -> + if (state == EntryVisibility.Shown) entryShapeSize + (shapePadding * 2) else 0.dp + } + + val animatedShapeSize by + transition.animateDp( + transitionSpec = { + when { + EntryVisibility.Hidden isTransitioningTo EntryVisibility.Shown -> + keyframes { + durationMillis = shapeExpandDurationMs + shapeRetractDurationMs + 0.dp at 0 with Easings.Linear + shapeOvershootSize at shapeExpandDurationMs with Easings.Legacy + } + targetState is EntryVisibility.BulkHidden -> { + val target = targetState as EntryVisibility.BulkHidden + tween( + dismissDurationMs, + delayMillis = target.staggerIndex * dismissStaggerDelayMs, + easing = Easings.Legacy, + ) + } + else -> tween(shapeCollapseDurationMs, easing = Easings.StandardDecelerate) + } + }, + label = "shape size" + ) { state -> + when (state) { + EntryVisibility.Shown -> entryShapeSize + else -> 0.dp + } + } + + val dotColor = MaterialTheme.colorScheme.onSurfaceVariant + Layout( + content = { + // TODO(b/282730134): add support for dot shapes. + Canvas(Modifier) { drawCircle(dotColor) } + } + ) { measurables, _ -> + val shapeSizePx = animatedShapeSize.roundToPx() + val placeable = measurables.single().measure(Constraints.fixed(shapeSizePx, shapeSizePx)) + + layout(animatedEntryWidth.roundToPx(), entryShapeSize.roundToPx()) { + placeable.place( + ((animatedEntryWidth - animatedShapeSize) / 2f).roundToPx(), + ((entryShapeSize - animatedShapeSize) / 2f).roundToPx() + ) + } + } +} + +@Composable private fun PinDigit( digit: Int, contentColor: Color, @@ -310,11 +428,13 @@ private fun showFailureAnimation() { // TODO(b/282730134): implement. } +private val entryShapeSize = 16.dp + private val pinButtonSize = 84.dp // Pin button motion spec: http://shortn/_9TTIG6SoEa private val pinButtonPressedDuration = 100.milliseconds -private val pinButtonPressedEasing = LinearEasing +private val pinButtonPressedEasing = Easings.Linear private val pinButtonHoldTime = 33.milliseconds private val pinButtonReleasedDuration = 420.milliseconds private val pinButtonReleasedEasing = Easings.Standard diff --git a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/ClockRegistry.kt b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/ClockRegistry.kt index 7c76281e4bdd..0e20444347fd 100644 --- a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/ClockRegistry.kt +++ b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/ClockRegistry.kt @@ -45,6 +45,7 @@ import java.util.concurrent.atomic.AtomicBoolean import kotlinx.coroutines.CoroutineDispatcher import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.launch +import kotlinx.coroutines.withContext private val KEY_TIMESTAMP = "appliedTimestamp" @@ -320,20 +321,20 @@ open class ClockRegistry( } } - public fun mutateSetting(mutator: (ClockSettings) -> ClockSettings) { - scope.launch(bgDispatcher) { applySettings(mutator(settings ?: ClockSettings())) } + public suspend fun mutateSetting(mutator: (ClockSettings) -> ClockSettings) { + withContext(bgDispatcher) { applySettings(mutator(settings ?: ClockSettings())) } } var currentClockId: ClockId get() = settings?.clockId ?: fallbackClockId set(value) { - mutateSetting { it.copy(clockId = value) } + scope.launch(bgDispatcher) { mutateSetting { it.copy(clockId = value) } } } var seedColor: Int? get() = settings?.seedColor set(value) { - mutateSetting { it.copy(seedColor = value) } + scope.launch(bgDispatcher) { mutateSetting { it.copy(seedColor = value) } } } init { @@ -501,11 +502,25 @@ open class ClockRegistry( fun createExampleClock(clockId: ClockId): ClockController? = createClock(clockId) - fun registerClockChangeListener(listener: ClockChangeListener) = + /** + * Adds [listener] to receive future clock changes. + * + * Calling from main thread to make sure the access is thread safe. + */ + fun registerClockChangeListener(listener: ClockChangeListener) { + assertMainThread() clockChangeListeners.add(listener) + } - fun unregisterClockChangeListener(listener: ClockChangeListener) = + /** + * Removes [listener] from future clock changes. + * + * Calling from main thread to make sure the access is thread safe. + */ + fun unregisterClockChangeListener(listener: ClockChangeListener) { + assertMainThread() clockChangeListeners.remove(listener) + } fun createCurrentClock(): ClockController { val clockId = currentClockId diff --git a/packages/SystemUI/customization/src/com/android/systemui/shared/quickaffordance/shared/model/KeyguardPreviewConstants.kt b/packages/SystemUI/customization/src/com/android/systemui/shared/quickaffordance/shared/model/KeyguardPreviewConstants.kt index 6f363a4ffa26..08ee60204178 100644 --- a/packages/SystemUI/customization/src/com/android/systemui/shared/quickaffordance/shared/model/KeyguardPreviewConstants.kt +++ b/packages/SystemUI/customization/src/com/android/systemui/shared/quickaffordance/shared/model/KeyguardPreviewConstants.kt @@ -20,8 +20,6 @@ package com.android.systemui.shared.quickaffordance.shared.model object KeyguardPreviewConstants { const val MESSAGE_ID_HIDE_SMART_SPACE = 1111 const val KEY_HIDE_SMART_SPACE = "hide_smart_space" - const val MESSAGE_ID_COLOR_OVERRIDE = 1234 - const val KEY_COLOR_OVERRIDE = "color_override" // ColorInt Encoded as string const val MESSAGE_ID_SLOT_SELECTED = 1337 const val KEY_SLOT_ID = "slot_id" const val KEY_INITIALLY_SELECTED_SLOT_ID = "initially_selected_slot_id" diff --git a/packages/SystemUI/monet/src/com/android/systemui/monet/dynamiccolor/MaterialDynamicColors.java b/packages/SystemUI/monet/src/com/android/systemui/monet/dynamiccolor/MaterialDynamicColors.java index 21218a2dc8e2..9f075e521126 100644 --- a/packages/SystemUI/monet/src/com/android/systemui/monet/dynamiccolor/MaterialDynamicColors.java +++ b/packages/SystemUI/monet/src/com/android/systemui/monet/dynamiccolor/MaterialDynamicColors.java @@ -16,13 +16,15 @@ package com.android.systemui.monet.dynamiccolor; +import android.annotation.NonNull; + import com.android.systemui.monet.dislike.DislikeAnalyzer; import com.android.systemui.monet.hct.Hct; import com.android.systemui.monet.hct.ViewingConditions; import com.android.systemui.monet.scheme.DynamicScheme; import com.android.systemui.monet.scheme.Variant; -/** Named colors, otherwise known as tokens, or roles, in the Material Design system.*/ +/** Named colors, otherwise known as tokens, or roles, in the Material Design system. */ // Prevent lint for Function.apply not being available on Android before API level 14 (4.0.1). // "AndroidJdkLibsChecker" for Function, "NewApi" for Function.apply(). // A java_library Bazel rule with an Android constraint cannot skip these warnings without this @@ -32,190 +34,148 @@ import com.android.systemui.monet.scheme.Variant; public final class MaterialDynamicColors { private static final double CONTAINER_ACCENT_TONE_DELTA = 15.0; - - public MaterialDynamicColors() { - } - - /** - * These colors were present in Android framework before Android U, and used by MDC controls. - * They - * should be avoided, if possible. It's unclear if they're used on multiple backgrounds, and if - * they are, they can't be adjusted for contrast.* For now, they will be set with no background, - * and those won't adjust for contrast, avoiding issues. - * - * <p>* For example, if the same color is on a white background _and_ black background, - * there's no - * way to increase contrast with either without losing contrast with the other. - */ - // colorControlActivated documented as colorAccent in M3 & GM3. - // colorAccent documented as colorSecondary in M3 and colorPrimary in GM3. - // Android used Material's Container as Primary/Secondary/Tertiary at launch. - // Therefore, this is a duplicated version of Primary Container. - public static DynamicColor controlActivated() { - return DynamicColor.fromPalette((s) -> s.primaryPalette, (s) -> s.isDark ? 30.0 : 90.0, null); - } + public MaterialDynamicColors() {} // Compatibility Keys Colors for Android - public static DynamicColor primaryPaletteKeyColor() { + public DynamicColor primaryPaletteKeyColor() { return DynamicColor.fromPalette( (s) -> s.primaryPalette, (s) -> s.primaryPalette.getKeyColor().getTone()); } - public static DynamicColor secondaryPaletteKeyColor() { + public DynamicColor secondaryPaletteKeyColor() { return DynamicColor.fromPalette( (s) -> s.secondaryPalette, (s) -> s.secondaryPalette.getKeyColor().getTone()); } - public static DynamicColor tertiaryPaletteKeyColor() { + public DynamicColor tertiaryPaletteKeyColor() { return DynamicColor.fromPalette( (s) -> s.tertiaryPalette, (s) -> s.tertiaryPalette.getKeyColor().getTone()); } - public static DynamicColor neutralPaletteKeyColor() { + public DynamicColor neutralPaletteKeyColor() { return DynamicColor.fromPalette( (s) -> s.neutralPalette, (s) -> s.neutralPalette.getKeyColor().getTone()); } - public static DynamicColor neutralVariantPaletteKeyColor() { + public DynamicColor neutralVariantPaletteKeyColor() { return DynamicColor.fromPalette( (s) -> s.neutralVariantPalette, (s) -> s.neutralVariantPalette.getKeyColor().getTone()); } - private static ViewingConditions viewingConditionsForAlbers(DynamicScheme scheme) { - return ViewingConditions.defaultWithBackgroundLstar(scheme.isDark ? 30.0 : 80.0); - } - - private static boolean isFidelity(DynamicScheme scheme) { - return scheme.variant == Variant.FIDELITY || scheme.variant == Variant.CONTENT; - } - - private static boolean isMonochrome(DynamicScheme scheme) { - return scheme.variant == Variant.MONOCHROME; - } - - static double findDesiredChromaByTone( - double hue, double chroma, double tone, boolean byDecreasingTone) { - double answer = tone; - - Hct closestToChroma = Hct.from(hue, chroma, tone); - if (closestToChroma.getChroma() < chroma) { - double chromaPeak = closestToChroma.getChroma(); - while (closestToChroma.getChroma() < chroma) { - answer += byDecreasingTone ? -1.0 : 1.0; - Hct potentialSolution = Hct.from(hue, chroma, answer); - if (chromaPeak > potentialSolution.getChroma()) { - break; - } - if (Math.abs(potentialSolution.getChroma() - chroma) < 0.4) { - break; - } - - double potentialDelta = Math.abs(potentialSolution.getChroma() - chroma); - double currentDelta = Math.abs(closestToChroma.getChroma() - chroma); - if (potentialDelta < currentDelta) { - closestToChroma = potentialSolution; - } - chromaPeak = Math.max(chromaPeak, potentialSolution.getChroma()); - } - } - - return answer; - } - - static double performAlbers(Hct prealbers, DynamicScheme scheme) { - final Hct albersd = prealbers.inViewingConditions(viewingConditionsForAlbers(scheme)); - if (DynamicColor.tonePrefersLightForeground(prealbers.getTone()) - && !DynamicColor.toneAllowsLightForeground(albersd.getTone())) { - return DynamicColor.enableLightForeground(prealbers.getTone()); - } else { - return DynamicColor.enableLightForeground(albersd.getTone()); - } - } - - public static DynamicColor highestSurface(DynamicScheme s) { + @NonNull + public DynamicColor highestSurface(@NonNull DynamicScheme s) { return s.isDark ? surfaceBright() : surfaceDim(); } - public static DynamicColor background() { + @NonNull + public DynamicColor background() { return DynamicColor.fromPalette((s) -> s.neutralPalette, (s) -> s.isDark ? 6.0 : 98.0); } - public static DynamicColor onBackground() { + @NonNull + public DynamicColor onBackground() { return DynamicColor.fromPalette( (s) -> s.neutralPalette, (s) -> s.isDark ? 90.0 : 10.0, (s) -> background()); } - public static DynamicColor surface() { + @NonNull + public DynamicColor surface() { return DynamicColor.fromPalette((s) -> s.neutralPalette, (s) -> s.isDark ? 6.0 : 98.0); } - public static DynamicColor inverseSurface() { + @NonNull + public DynamicColor inverseSurface() { return DynamicColor.fromPalette((s) -> s.neutralPalette, (s) -> s.isDark ? 90.0 : 20.0); } - public static DynamicColor surfaceBright() { + @NonNull + public DynamicColor surfaceBright() { return DynamicColor.fromPalette((s) -> s.neutralPalette, (s) -> s.isDark ? 24.0 : 98.0); } - public static DynamicColor surfaceDim() { + @NonNull + public DynamicColor surfaceDim() { return DynamicColor.fromPalette((s) -> s.neutralPalette, (s) -> s.isDark ? 6.0 : 87.0); } - public static DynamicColor surfaceContainerLowest() { + @NonNull + public DynamicColor surfaceContainerLowest() { return DynamicColor.fromPalette((s) -> s.neutralPalette, (s) -> s.isDark ? 4.0 : 100.0); } - public static DynamicColor surfaceContainerLow() { + @NonNull + public DynamicColor surfaceContainerLow() { return DynamicColor.fromPalette((s) -> s.neutralPalette, (s) -> s.isDark ? 10.0 : 96.0); } - public static DynamicColor surfaceContainer() { + @NonNull + public DynamicColor surfaceContainer() { return DynamicColor.fromPalette((s) -> s.neutralPalette, (s) -> s.isDark ? 12.0 : 94.0); } - public static DynamicColor surfaceContainerHigh() { + @NonNull + public DynamicColor surfaceContainerHigh() { return DynamicColor.fromPalette((s) -> s.neutralPalette, (s) -> s.isDark ? 17.0 : 92.0); } - public static DynamicColor surfaceContainerHighest() { + @NonNull + public DynamicColor surfaceContainerHighest() { return DynamicColor.fromPalette((s) -> s.neutralPalette, (s) -> s.isDark ? 22.0 : 90.0); } - public static DynamicColor onSurface() { + @NonNull + public DynamicColor onSurface() { return DynamicColor.fromPalette( - (s) -> s.neutralPalette, (s) -> s.isDark ? 90.0 : 10.0, - MaterialDynamicColors::highestSurface); + (s) -> s.neutralPalette, (s) -> s.isDark ? 90.0 : 10.0, this::highestSurface); } - public static DynamicColor inverseOnSurface() { + @NonNull + public DynamicColor inverseOnSurface() { return DynamicColor.fromPalette( (s) -> s.neutralPalette, (s) -> s.isDark ? 20.0 : 95.0, (s) -> inverseSurface()); } - public static DynamicColor surfaceVariant() { - return DynamicColor.fromPalette((s) -> s.neutralVariantPalette, - (s) -> s.isDark ? 30.0 : 90.0); + @NonNull + public DynamicColor surfaceVariant() { + return DynamicColor.fromPalette((s) -> s.neutralVariantPalette, (s) -> s.isDark ? 30.0 : 90.0); } - public static DynamicColor onSurfaceVariant() { + @NonNull + public DynamicColor onSurfaceVariant() { return DynamicColor.fromPalette( - (s) -> s.neutralVariantPalette, (s) -> s.isDark ? 80.0 : 30.0, - (s) -> surfaceVariant()); + (s) -> s.neutralVariantPalette, (s) -> s.isDark ? 80.0 : 30.0, (s) -> surfaceVariant()); } - public static DynamicColor outline() { + @NonNull + public DynamicColor outline() { return DynamicColor.fromPalette( - (s) -> s.neutralVariantPalette, (s) -> 50.0, MaterialDynamicColors::highestSurface); + (s) -> s.neutralVariantPalette, (s) -> s.isDark ? 60.0 : 50.0, this::highestSurface); } - public static DynamicColor outlineVariant() { + @NonNull + public DynamicColor outlineVariant() { return DynamicColor.fromPalette( - (s) -> s.neutralVariantPalette, (s) -> s.isDark ? 30.0 : 80.0, - MaterialDynamicColors::highestSurface); + (s) -> s.neutralVariantPalette, (s) -> s.isDark ? 30.0 : 80.0, this::highestSurface); + } + + @NonNull + public DynamicColor shadow() { + return DynamicColor.fromPalette((s) -> s.neutralPalette, (s) -> 0.0); } - public static DynamicColor primaryContainer() { + @NonNull + public DynamicColor scrim() { + return DynamicColor.fromPalette((s) -> s.neutralPalette, (s) -> 0.0); + } + + @NonNull + public DynamicColor surfaceTint() { + return DynamicColor.fromPalette((s) -> s.primaryPalette, (s) -> s.isDark ? 80.0 : 40.0); + } + + @NonNull + public DynamicColor primaryContainer() { return DynamicColor.fromPalette( (s) -> s.primaryPalette, (s) -> { @@ -227,10 +187,11 @@ public final class MaterialDynamicColors { } return s.isDark ? 30.0 : 90.0; }, - MaterialDynamicColors::highestSurface); + this::highestSurface); } - public static DynamicColor onPrimaryContainer() { + @NonNull + public DynamicColor onPrimaryContainer() { return DynamicColor.fromPalette( (s) -> s.primaryPalette, (s) -> { @@ -246,7 +207,8 @@ public final class MaterialDynamicColors { null); } - public static DynamicColor primary() { + @NonNull + public DynamicColor primary() { return DynamicColor.fromPalette( (s) -> s.primaryPalette, (s) -> { @@ -255,7 +217,7 @@ public final class MaterialDynamicColors { } return s.isDark ? 80.0 : 40.0; }, - MaterialDynamicColors::highestSurface, + this::highestSurface, (s) -> new ToneDeltaConstraint( CONTAINER_ACCENT_TONE_DELTA, @@ -263,12 +225,14 @@ public final class MaterialDynamicColors { s.isDark ? TonePolarity.DARKER : TonePolarity.LIGHTER)); } - public static DynamicColor inversePrimary() { + @NonNull + public DynamicColor inversePrimary() { return DynamicColor.fromPalette( (s) -> s.primaryPalette, (s) -> s.isDark ? 40.0 : 80.0, (s) -> inverseSurface()); } - public static DynamicColor onPrimary() { + @NonNull + public DynamicColor onPrimary() { return DynamicColor.fromPalette( (s) -> s.primaryPalette, (s) -> { @@ -280,7 +244,8 @@ public final class MaterialDynamicColors { (s) -> primary()); } - public static DynamicColor secondaryContainer() { + @NonNull + public DynamicColor secondaryContainer() { return DynamicColor.fromPalette( (s) -> s.secondaryPalette, (s) -> { @@ -300,10 +265,11 @@ public final class MaterialDynamicColors { answer = performAlbers(s.secondaryPalette.getHct(answer), s); return answer; }, - MaterialDynamicColors::highestSurface); + this::highestSurface); } - public static DynamicColor onSecondaryContainer() { + @NonNull + public DynamicColor onSecondaryContainer() { return DynamicColor.fromPalette( (s) -> s.secondaryPalette, (s) -> { @@ -315,11 +281,12 @@ public final class MaterialDynamicColors { (s) -> secondaryContainer()); } - public static DynamicColor secondary() { + @NonNull + public DynamicColor secondary() { return DynamicColor.fromPalette( (s) -> s.secondaryPalette, (s) -> s.isDark ? 80.0 : 40.0, - MaterialDynamicColors::highestSurface, + this::highestSurface, (s) -> new ToneDeltaConstraint( CONTAINER_ACCENT_TONE_DELTA, @@ -327,7 +294,8 @@ public final class MaterialDynamicColors { s.isDark ? TonePolarity.DARKER : TonePolarity.LIGHTER)); } - public static DynamicColor onSecondary() { + @NonNull + public DynamicColor onSecondary() { return DynamicColor.fromPalette( (s) -> s.secondaryPalette, (s) -> { @@ -339,7 +307,8 @@ public final class MaterialDynamicColors { (s) -> secondary()); } - public static DynamicColor tertiaryContainer() { + @NonNull + public DynamicColor tertiaryContainer() { return DynamicColor.fromPalette( (s) -> s.tertiaryPalette, (s) -> { @@ -354,10 +323,11 @@ public final class MaterialDynamicColors { final Hct proposedHct = s.tertiaryPalette.getHct(albersTone); return DislikeAnalyzer.fixIfDisliked(proposedHct).getTone(); }, - MaterialDynamicColors::highestSurface); + this::highestSurface); } - public static DynamicColor onTertiaryContainer() { + @NonNull + public DynamicColor onTertiaryContainer() { return DynamicColor.fromPalette( (s) -> s.tertiaryPalette, (s) -> { @@ -372,7 +342,8 @@ public final class MaterialDynamicColors { (s) -> tertiaryContainer()); } - public static DynamicColor tertiary() { + @NonNull + public DynamicColor tertiary() { return DynamicColor.fromPalette( (s) -> s.tertiaryPalette, (s) -> { @@ -381,7 +352,7 @@ public final class MaterialDynamicColors { } return s.isDark ? 80.0 : 40.0; }, - MaterialDynamicColors::highestSurface, + this::highestSurface, (s) -> new ToneDeltaConstraint( CONTAINER_ACCENT_TONE_DELTA, @@ -389,7 +360,8 @@ public final class MaterialDynamicColors { s.isDark ? TonePolarity.DARKER : TonePolarity.LIGHTER)); } - public static DynamicColor onTertiary() { + @NonNull + public DynamicColor onTertiary() { return DynamicColor.fromPalette( (s) -> s.tertiaryPalette, (s) -> { @@ -401,22 +373,24 @@ public final class MaterialDynamicColors { (s) -> tertiary()); } - public static DynamicColor errorContainer() { + @NonNull + public DynamicColor errorContainer() { return DynamicColor.fromPalette( - (s) -> s.errorPalette, (s) -> s.isDark ? 30.0 : 90.0, - MaterialDynamicColors::highestSurface); + (s) -> s.errorPalette, (s) -> s.isDark ? 30.0 : 90.0, this::highestSurface); } - public static DynamicColor onErrorContainer() { + @NonNull + public DynamicColor onErrorContainer() { return DynamicColor.fromPalette( (s) -> s.errorPalette, (s) -> s.isDark ? 90.0 : 10.0, (s) -> errorContainer()); } - public static DynamicColor error() { + @NonNull + public DynamicColor error() { return DynamicColor.fromPalette( (s) -> s.errorPalette, (s) -> s.isDark ? 80.0 : 40.0, - MaterialDynamicColors::highestSurface, + this::highestSurface, (s) -> new ToneDeltaConstraint( CONTAINER_ACCENT_TONE_DELTA, @@ -424,113 +398,138 @@ public final class MaterialDynamicColors { s.isDark ? TonePolarity.DARKER : TonePolarity.LIGHTER)); } - public static DynamicColor onError() { + @NonNull + public DynamicColor onError() { return DynamicColor.fromPalette( (s) -> s.errorPalette, (s) -> s.isDark ? 20.0 : 100.0, (s) -> error()); } - public static DynamicColor primaryFixed() { + @NonNull + public DynamicColor primaryFixed() { return DynamicColor.fromPalette( (s) -> s.primaryPalette, (s) -> { if (isMonochrome(s)) { - return s.isDark ? 100.0 : 10.0; + return 40.0; } return 90.0; }, - MaterialDynamicColors::highestSurface); + this::highestSurface); } - public static DynamicColor primaryFixedDim() { + @NonNull + public DynamicColor primaryFixedDim() { return DynamicColor.fromPalette( (s) -> s.primaryPalette, (s) -> { if (isMonochrome(s)) { - return s.isDark ? 90.0 : 20.0; + return 30.0; } return 80.0; }, - MaterialDynamicColors::highestSurface); + this::highestSurface); } - public static DynamicColor onPrimaryFixed() { + @NonNull + public DynamicColor onPrimaryFixed() { return DynamicColor.fromPalette( (s) -> s.primaryPalette, (s) -> { if (isMonochrome(s)) { - return s.isDark ? 10.0 : 90.0; + return 100.0; } return 10.0; }, (s) -> primaryFixedDim()); } - public static DynamicColor onPrimaryFixedVariant() { + @NonNull + public DynamicColor onPrimaryFixedVariant() { return DynamicColor.fromPalette( (s) -> s.primaryPalette, (s) -> { if (isMonochrome(s)) { - return s.isDark ? 30.0 : 70.0; + return 90.0; } return 30.0; }, (s) -> primaryFixedDim()); } - public static DynamicColor secondaryFixed() { + @NonNull + public DynamicColor secondaryFixed() { return DynamicColor.fromPalette( - (s) -> s.secondaryPalette, (s) -> isMonochrome(s) ? 80.0 : 90.0, - MaterialDynamicColors::highestSurface); + (s) -> s.secondaryPalette, (s) -> isMonochrome(s) ? 80.0 : 90.0, this::highestSurface); } - public static DynamicColor secondaryFixedDim() { + @NonNull + public DynamicColor secondaryFixedDim() { return DynamicColor.fromPalette( - (s) -> s.secondaryPalette, (s) -> isMonochrome(s) ? 70.0 : 80.0, - MaterialDynamicColors::highestSurface); + (s) -> s.secondaryPalette, (s) -> isMonochrome(s) ? 70.0 : 80.0, this::highestSurface); } - public static DynamicColor onSecondaryFixed() { + @NonNull + public DynamicColor onSecondaryFixed() { return DynamicColor.fromPalette( (s) -> s.secondaryPalette, (s) -> 10.0, (s) -> secondaryFixedDim()); } - public static DynamicColor onSecondaryFixedVariant() { + @NonNull + public DynamicColor onSecondaryFixedVariant() { return DynamicColor.fromPalette( (s) -> s.secondaryPalette, (s) -> isMonochrome(s) ? 25.0 : 30.0, (s) -> secondaryFixedDim()); } - public static DynamicColor tertiaryFixed() { + @NonNull + public DynamicColor tertiaryFixed() { return DynamicColor.fromPalette( - (s) -> s.tertiaryPalette, (s) -> isMonochrome(s) ? 40.0 : 90.0, - MaterialDynamicColors::highestSurface); + (s) -> s.tertiaryPalette, (s) -> isMonochrome(s) ? 40.0 : 90.0, this::highestSurface); } - public static DynamicColor tertiaryFixedDim() { + @NonNull + public DynamicColor tertiaryFixedDim() { return DynamicColor.fromPalette( - (s) -> s.tertiaryPalette, (s) -> isMonochrome(s) ? 30.0 : 80.0, - MaterialDynamicColors::highestSurface); + (s) -> s.tertiaryPalette, (s) -> isMonochrome(s) ? 30.0 : 80.0, this::highestSurface); } - public static DynamicColor onTertiaryFixed() { + @NonNull + public DynamicColor onTertiaryFixed() { return DynamicColor.fromPalette( - (s) -> s.tertiaryPalette, (s) -> isMonochrome(s) ? 90.0 : 10.0, - (s) -> tertiaryFixedDim()); + (s) -> s.tertiaryPalette, (s) -> isMonochrome(s) ? 100.0 : 10.0, (s) -> tertiaryFixedDim()); } - public static DynamicColor onTertiaryFixedVariant() { + @NonNull + public DynamicColor onTertiaryFixedVariant() { return DynamicColor.fromPalette( - (s) -> s.tertiaryPalette, (s) -> isMonochrome(s) ? 70.0 : 30.0, - (s) -> tertiaryFixedDim()); + (s) -> s.tertiaryPalette, (s) -> isMonochrome(s) ? 90.0 : 30.0, (s) -> tertiaryFixedDim()); + } + + /** + * These colors were present in Android framework before Android U, and used by MDC controls. They + * should be avoided, if possible. It's unclear if they're used on multiple backgrounds, and if + * they are, they can't be adjusted for contrast.* For now, they will be set with no background, + * and those won't adjust for contrast, avoiding issues. + * + * <p>* For example, if the same color is on a white background _and_ black background, there's no + * way to increase contrast with either without losing contrast with the other. + */ + // colorControlActivated documented as colorAccent in M3 & GM3. + // colorAccent documented as colorSecondary in M3 and colorPrimary in GM3. + // Android used Material's Container as Primary/Secondary/Tertiary at launch. + // Therefore, this is a duplicated version of Primary Container. + @NonNull + public DynamicColor controlActivated() { + return DynamicColor.fromPalette((s) -> s.primaryPalette, (s) -> s.isDark ? 30.0 : 90.0, null); } // colorControlNormal documented as textColorSecondary in M3 & GM3. // In Material, textColorSecondary points to onSurfaceVariant in the non-disabled state, // which is Neutral Variant T30/80 in light/dark. - public static DynamicColor controlNormal() { - return DynamicColor.fromPalette((s) -> s.neutralVariantPalette, - (s) -> s.isDark ? 80.0 : 30.0); + @NonNull + public DynamicColor controlNormal() { + return DynamicColor.fromPalette((s) -> s.neutralVariantPalette, (s) -> s.isDark ? 80.0 : 30.0); } // colorControlHighlight documented, in both M3 & GM3: @@ -541,7 +540,8 @@ public final class MaterialDynamicColors { // DynamicColors do not support alpha currently, and _may_ not need it for this use case, // depending on how MDC resolved alpha for the other cases. // Returning black in dark mode, white in light mode. - public static DynamicColor controlHighlight() { + @NonNull + public DynamicColor controlHighlight() { return new DynamicColor( s -> 0.0, s -> 0.0, @@ -549,40 +549,92 @@ public final class MaterialDynamicColors { s -> s.isDark ? 0.20 : 0.12, null, scheme -> - - DynamicColor.toneMinContrastDefault((s) -> s.isDark ? 100.0 : 0.0, null, - scheme, null), + DynamicColor.toneMinContrastDefault((s) -> s.isDark ? 100.0 : 0.0, null, scheme, null), scheme -> - DynamicColor.toneMaxContrastDefault((s) -> s.isDark ? 100.0 : 0.0, null, - scheme, null), + DynamicColor.toneMaxContrastDefault((s) -> s.isDark ? 100.0 : 0.0, null, scheme, null), null); } // textColorPrimaryInverse documented, in both M3 & GM3, documented as N10/N90. - public static DynamicColor textPrimaryInverse() { + @NonNull + public DynamicColor textPrimaryInverse() { return DynamicColor.fromPalette((s) -> s.neutralPalette, (s) -> s.isDark ? 10.0 : 90.0); } // textColorSecondaryInverse and textColorTertiaryInverse both documented, in both M3 & GM3, as // NV30/NV80 - public static DynamicColor textSecondaryAndTertiaryInverse() { - return DynamicColor.fromPalette((s) -> s.neutralVariantPalette, - (s) -> s.isDark ? 30.0 : 80.0); + @NonNull + public DynamicColor textSecondaryAndTertiaryInverse() { + return DynamicColor.fromPalette((s) -> s.neutralVariantPalette, (s) -> s.isDark ? 30.0 : 80.0); } // textColorPrimaryInverseDisableOnly documented, in both M3 & GM3, as N10/N90 - public static DynamicColor textPrimaryInverseDisableOnly() { + @NonNull + public DynamicColor textPrimaryInverseDisableOnly() { return DynamicColor.fromPalette((s) -> s.neutralPalette, (s) -> s.isDark ? 10.0 : 90.0); } // textColorSecondaryInverse and textColorTertiaryInverse in disabled state both documented, // in both M3 & GM3, as N10/N90 - public static DynamicColor textSecondaryAndTertiaryInverseDisabled() { + @NonNull + public DynamicColor textSecondaryAndTertiaryInverseDisabled() { return DynamicColor.fromPalette((s) -> s.neutralPalette, (s) -> s.isDark ? 10.0 : 90.0); } // textColorHintInverse documented, in both M3 & GM3, as N10/N90 - public static DynamicColor textHintInverse() { + @NonNull + public DynamicColor textHintInverse() { return DynamicColor.fromPalette((s) -> s.neutralPalette, (s) -> s.isDark ? 10.0 : 90.0); } -} + + private static ViewingConditions viewingConditionsForAlbers(DynamicScheme scheme) { + return ViewingConditions.defaultWithBackgroundLstar(scheme.isDark ? 30.0 : 80.0); + } + + private static boolean isFidelity(DynamicScheme scheme) { + return scheme.variant == Variant.FIDELITY || scheme.variant == Variant.CONTENT; + } + + private static boolean isMonochrome(DynamicScheme scheme) { + return scheme.variant == Variant.MONOCHROME; + } + + static double findDesiredChromaByTone( + double hue, double chroma, double tone, boolean byDecreasingTone) { + double answer = tone; + + Hct closestToChroma = Hct.from(hue, chroma, tone); + if (closestToChroma.getChroma() < chroma) { + double chromaPeak = closestToChroma.getChroma(); + while (closestToChroma.getChroma() < chroma) { + answer += byDecreasingTone ? -1.0 : 1.0; + Hct potentialSolution = Hct.from(hue, chroma, answer); + if (chromaPeak > potentialSolution.getChroma()) { + break; + } + if (Math.abs(potentialSolution.getChroma() - chroma) < 0.4) { + break; + } + + double potentialDelta = Math.abs(potentialSolution.getChroma() - chroma); + double currentDelta = Math.abs(closestToChroma.getChroma() - chroma); + if (potentialDelta < currentDelta) { + closestToChroma = potentialSolution; + } + chromaPeak = Math.max(chromaPeak, potentialSolution.getChroma()); + } + } + + return answer; + } + + static double performAlbers(Hct prealbers, DynamicScheme scheme) { + final Hct albersd = prealbers.inViewingConditions(viewingConditionsForAlbers(scheme)); + if (DynamicColor.tonePrefersLightForeground(prealbers.getTone()) + && !DynamicColor.toneAllowsLightForeground(albersd.getTone())) { + return DynamicColor.enableLightForeground(prealbers.getTone()); + } else { + return DynamicColor.enableLightForeground(albersd.getTone()); + } + } +}
\ No newline at end of file diff --git a/packages/SystemUI/proguard_common.flags b/packages/SystemUI/proguard_common.flags index 02d55104c288..1f47e724e48c 100644 --- a/packages/SystemUI/proguard_common.flags +++ b/packages/SystemUI/proguard_common.flags @@ -64,15 +64,16 @@ # The plugins, log & common subpackages act as shared libraries that might be referenced in # dynamically-loaded plugin APKs. --keep class com.android.systemui.plugins.** { - *; -} --keep class com.android.systemui.log.** { - *; -} --keep class com.android.systemui.common.** { - *; -} +-keep class com.android.systemui.plugins.** { *; } +-keep class com.android.systemui.log.ConstantStringsLoggerImpl { *; } +-keep class com.android.systemui.log.ConstantStringsLogger { *; } +-keep class com.android.systemui.log.LogBuffer { *; } +-keep class com.android.systemui.log.LogcatEchoTrackerDebug { *; } +-keep class com.android.systemui.log.LogcatEchoTracker { *; } +-keep class com.android.systemui.log.LogcatEchoTrackerProd { *; } +-keep class com.android.systemui.log.LogLevel { *; } +-keep class com.android.systemui.log.LogMessageImpl { *; } +-keep class com.android.systemui.log.LogMessage { *; } -keep class com.android.systemui.fragments.FragmentService$FragmentCreator { *; } diff --git a/packages/SystemUI/res/layout/keyguard_bottom_area.xml b/packages/SystemUI/res/layout/keyguard_bottom_area.xml index a3a7135dabad..66c57fc2a9ac 100644 --- a/packages/SystemUI/res/layout/keyguard_bottom_area.xml +++ b/packages/SystemUI/res/layout/keyguard_bottom_area.xml @@ -23,7 +23,7 @@ android:outlineProvider="none" > <LinearLayout - android:id="@+id/keyguard_indication_area" + android:id="@id/keyguard_indication_area" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_marginBottom="@dimen/keyguard_indication_margin_bottom" @@ -31,7 +31,7 @@ android:orientation="vertical"> <com.android.systemui.statusbar.phone.KeyguardIndicationTextView - android:id="@+id/keyguard_indication_text" + android:id="@id/keyguard_indication_text" android:layout_width="match_parent" android:layout_height="wrap_content" android:gravity="center" @@ -41,13 +41,12 @@ android:accessibilityLiveRegion="polite"/> <com.android.systemui.statusbar.phone.KeyguardIndicationTextView - android:id="@+id/keyguard_indication_text_bottom" + android:id="@id/keyguard_indication_text_bottom" android:layout_width="wrap_content" android:layout_height="wrap_content" android:gravity="center" - android:minHeight="48dp" + android:minHeight="@dimen/keyguard_indication_text_min_height" android:layout_gravity="center_horizontal" - android:layout_centerHorizontal="true" android:paddingStart="@dimen/keyguard_indication_text_padding" android:paddingEnd="@dimen/keyguard_indication_text_padding" android:textAppearance="@style/TextAppearance.Keyguard.BottomArea" diff --git a/packages/SystemUI/res/layout/super_notification_shade.xml b/packages/SystemUI/res/layout/super_notification_shade.xml index 01465d759ab0..6601e63fa034 100644 --- a/packages/SystemUI/res/layout/super_notification_shade.xml +++ b/packages/SystemUI/res/layout/super_notification_shade.xml @@ -70,6 +70,12 @@ android:layout_height="match_parent" android:visibility="invisible" /> + <!-- Root for all keyguard content. It was previously located within the shade. --> + <com.android.systemui.keyguard.ui.view.KeyguardRootView + android:id="@id/keyguard_root_view" + android:layout_width="match_parent" + android:layout_height="match_parent" /> + <include layout="@layout/brightness_mirror_container" /> <com.android.systemui.scrim.ScrimView diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml index 8d3ba364da06..50d33c6a733b 100644 --- a/packages/SystemUI/res/values/dimens.xml +++ b/packages/SystemUI/res/values/dimens.xml @@ -275,6 +275,9 @@ <!-- The padding at start and end of indication text shown on AOD --> <dimen name="keyguard_indication_text_padding">16dp</dimen> + <!-- The min height on the indication text shown on AOD --> + <dimen name="keyguard_indication_text_min_height">48dp</dimen> + <!-- Shadows under the clock, date and other keyguard text fields --> <dimen name="keyguard_shadow_radius">5</dimen> diff --git a/packages/SystemUI/res/values/ids.xml b/packages/SystemUI/res/values/ids.xml index e5c946156e46..d651a2159721 100644 --- a/packages/SystemUI/res/values/ids.xml +++ b/packages/SystemUI/res/values/ids.xml @@ -206,5 +206,9 @@ <!-- keyboard backlight indicator--> <item type="id" name="backlight_icon" /> -</resources> + <item type="id" name="keyguard_root_view" /> + <item type="id" name="keyguard_indication_area" /> + <item type="id" name="keyguard_indication_text" /> + <item type="id" name="keyguard_indication_text_bottom" /> +</resources> diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/regionsampling/RegionSampler.kt b/packages/SystemUI/shared/src/com/android/systemui/shared/regionsampling/RegionSampler.kt index 9a0044761504..0ca2f7a36311 100644 --- a/packages/SystemUI/shared/src/com/android/systemui/shared/regionsampling/RegionSampler.kt +++ b/packages/SystemUI/shared/src/com/android/systemui/shared/regionsampling/RegionSampler.kt @@ -263,6 +263,8 @@ constructor( (colors?.colorHints?.and(WallpaperColors.HINT_SUPPORTS_DARK_TEXT)) != WallpaperColors.HINT_SUPPORTS_DARK_TEXT ) + if (DEBUG) + Log.d(TAG, "onColorsChanged() | region darkness = $regionDarkness for region $area") updateForegroundColor() } diff --git a/packages/SystemUI/src/com/android/keyguard/ClockEventController.kt b/packages/SystemUI/src/com/android/keyguard/ClockEventController.kt index 8ea4c31a5ac9..84a2c25999a0 100644 --- a/packages/SystemUI/src/com/android/keyguard/ClockEventController.kt +++ b/packages/SystemUI/src/com/android/keyguard/ClockEventController.kt @@ -15,18 +15,18 @@ */ package com.android.keyguard -import android.app.WallpaperManager import android.content.BroadcastReceiver import android.content.Context import android.content.Intent import android.content.IntentFilter import android.content.res.Resources import android.text.format.DateFormat -import android.util.TypedValue import android.util.Log +import android.util.TypedValue import android.view.View import android.view.View.OnAttachStateChangeListener import android.view.ViewTreeObserver +import android.widget.FrameLayout import androidx.annotation.VisibleForTesting import androidx.lifecycle.Lifecycle import androidx.lifecycle.repeatOnLifecycle @@ -99,6 +99,28 @@ constructor( if (!regionSamplingEnabled) { updateColors() + } else { + clock?.let { + smallRegionSampler = createRegionSampler( + it.smallClock.view, + mainExecutor, + bgExecutor, + regionSamplingEnabled, + isLockscreen = true, + ::updateColors + )?.apply { startRegionSampler() } + + largeRegionSampler = createRegionSampler( + it.largeClock.view, + mainExecutor, + bgExecutor, + regionSamplingEnabled, + isLockscreen = true, + ::updateColors + )?.apply { startRegionSampler() } + + updateColors() + } } updateFontSizes() updateTimeListeners() @@ -110,8 +132,25 @@ constructor( } value.smallClock.view.addOnAttachStateChangeListener( object : OnAttachStateChangeListener { - override fun onViewAttachedToWindow(p0: View?) { + var pastVisibility: Int? = null + override fun onViewAttachedToWindow(view: View?) { value.events.onTimeFormatChanged(DateFormat.is24HourFormat(context)) + if (view != null) { + val smallClockFrame = view.parent as FrameLayout + pastVisibility = smallClockFrame.visibility + smallClockFrame.viewTreeObserver.addOnGlobalLayoutListener( + ViewTreeObserver.OnGlobalLayoutListener { + val currentVisibility = smallClockFrame.visibility + if (pastVisibility != currentVisibility) { + pastVisibility = currentVisibility + // when small clock visible, recalculate bounds and sample + if (currentVisibility == View.VISIBLE) { + smallRegionSampler?.stopRegionSampler() + smallRegionSampler?.startRegionSampler() + } + } + }) + } } override fun onViewDetachedFromWindow(p0: View?) { @@ -141,21 +180,19 @@ constructor( private fun updateColors() { - val wallpaperManager = WallpaperManager.getInstance(context) if (regionSamplingEnabled) { - regionSampler?.let { regionSampler -> - clock?.let { clock -> - if (regionSampler.sampledView == clock.smallClock.view) { - smallClockIsDark = regionSampler.currentRegionDarkness().isDark - clock.smallClock.events.onRegionDarknessChanged(smallClockIsDark) - return@updateColors - } else if (regionSampler.sampledView == clock.largeClock.view) { - largeClockIsDark = regionSampler.currentRegionDarkness().isDark - clock.largeClock.events.onRegionDarknessChanged(largeClockIsDark) - return@updateColors - } + clock?.let { clock -> + smallRegionSampler?.let { + smallClockIsDark = it.currentRegionDarkness().isDark + clock.smallClock.events.onRegionDarknessChanged(smallClockIsDark) + } + + largeRegionSampler?.let { + largeClockIsDark = it.currentRegionDarkness().isDark + clock.largeClock.events.onRegionDarknessChanged(largeClockIsDark) } } + return } val isLightTheme = TypedValue() @@ -168,23 +205,6 @@ constructor( largeClock.events.onRegionDarknessChanged(largeClockIsDark) } } - - private fun updateRegionSampler(sampledRegion: View) { - regionSampler?.stopRegionSampler() - regionSampler = - createRegionSampler( - sampledRegion, - mainExecutor, - bgExecutor, - regionSamplingEnabled, - isLockscreen = true, - ::updateColors - ) - ?.apply { startRegionSampler() } - - updateColors() - } - protected open fun createRegionSampler( sampledView: View, mainExecutor: Executor?, @@ -202,7 +222,10 @@ constructor( ) { updateColors() } } - var regionSampler: RegionSampler? = null + var smallRegionSampler: RegionSampler? = null + private set + var largeRegionSampler: RegionSampler? = null + private set var smallTimeListener: TimeListener? = null var largeTimeListener: TimeListener? = null val shouldTimeListenerRun: Boolean @@ -319,7 +342,8 @@ constructor( configurationController.removeCallback(configListener) batteryController.removeCallback(batteryCallback) keyguardUpdateMonitor.removeCallback(keyguardUpdateMonitorCallback) - regionSampler?.stopRegionSampler() + smallRegionSampler?.stopRegionSampler() + largeRegionSampler?.stopRegionSampler() smallTimeListener?.stop() largeTimeListener?.stop() } diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java index 8f17edd23f55..25ad3af52d04 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java @@ -531,9 +531,13 @@ public class KeyguardClockSwitchController extends ViewController<KeyguardClockS if (clock != null) { clock.dump(pw); } - final RegionSampler regionSampler = mClockEventController.getRegionSampler(); - if (regionSampler != null) { - regionSampler.dump(pw); + final RegionSampler smallRegionSampler = mClockEventController.getSmallRegionSampler(); + if (smallRegionSampler != null) { + smallRegionSampler.dump(pw); + } + final RegionSampler largeRegionSampler = mClockEventController.getLargeRegionSampler(); + if (largeRegionSampler != null) { + largeRegionSampler.dump(pw); } } diff --git a/packages/SystemUI/src/com/android/systemui/authentication/data/repository/AuthenticationRepository.kt b/packages/SystemUI/src/com/android/systemui/authentication/data/repository/AuthenticationRepository.kt index c684dc54c6fd..c4ebee2a9197 100644 --- a/packages/SystemUI/src/com/android/systemui/authentication/data/repository/AuthenticationRepository.kt +++ b/packages/SystemUI/src/com/android/systemui/authentication/data/repository/AuthenticationRepository.kt @@ -77,7 +77,7 @@ class AuthenticationRepositoryImpl @Inject constructor() : AuthenticationReposit override val isUnlocked: StateFlow<Boolean> = _isUnlocked.asStateFlow() private val _authenticationMethod = - MutableStateFlow<AuthenticationMethodModel>(AuthenticationMethodModel.PIN(1234)) + MutableStateFlow<AuthenticationMethodModel>(AuthenticationMethodModel.Pin(1234)) override val authenticationMethod: StateFlow<AuthenticationMethodModel> = _authenticationMethod.asStateFlow() diff --git a/packages/SystemUI/src/com/android/systemui/authentication/domain/interactor/AuthenticationInteractor.kt b/packages/SystemUI/src/com/android/systemui/authentication/domain/interactor/AuthenticationInteractor.kt index 3984627a181d..dd9dcbedd6fc 100644 --- a/packages/SystemUI/src/com/android/systemui/authentication/domain/interactor/AuthenticationInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/authentication/domain/interactor/AuthenticationInteractor.kt @@ -16,6 +16,7 @@ package com.android.systemui.authentication.domain.interactor +import android.app.admin.DevicePolicyManager import com.android.systemui.authentication.data.repository.AuthenticationRepository import com.android.systemui.authentication.shared.model.AuthenticationMethodModel import com.android.systemui.dagger.SysUISingleton @@ -129,7 +130,7 @@ constructor( fun authenticate(input: List<Any>): Boolean { val isSuccessful = when (val authMethod = this.authenticationMethod.value) { - is AuthenticationMethodModel.PIN -> input.asCode() == authMethod.code + is AuthenticationMethodModel.Pin -> input.asCode() == authMethod.code is AuthenticationMethodModel.Password -> input.asPassword() == authMethod.password is AuthenticationMethodModel.Pattern -> input.asPattern() == authMethod.coordinates else -> true @@ -177,15 +178,21 @@ constructor( /** * Returns a PIN code from the given list. It's assumed the given list elements are all - * [Int]. + * [Int] in the range [0-9]. */ - private fun List<Any>.asCode(): Int? { - if (isEmpty()) { + private fun List<Any>.asCode(): Long? { + if (isEmpty() || size > DevicePolicyManager.MAX_PASSWORD_LENGTH) { return null } - var code = 0 - map { it as Int }.forEach { integer -> code = code * 10 + integer } + var code = 0L + map { + require(it is Int && it in 0..9) { + "Pin is required to be Int in range [0..9], but got $it" + } + it + } + .forEach { integer -> code = code * 10 + integer } return code } diff --git a/packages/SystemUI/src/com/android/systemui/authentication/shared/model/AuthenticationMethodModel.kt b/packages/SystemUI/src/com/android/systemui/authentication/shared/model/AuthenticationMethodModel.kt index 6f008c3017b9..e4fbf9af35ea 100644 --- a/packages/SystemUI/src/com/android/systemui/authentication/shared/model/AuthenticationMethodModel.kt +++ b/packages/SystemUI/src/com/android/systemui/authentication/shared/model/AuthenticationMethodModel.kt @@ -32,7 +32,13 @@ sealed class AuthenticationMethodModel( /** The most basic authentication method. The lock screen can be swiped away when displayed. */ object Swipe : AuthenticationMethodModel(isSecure = false) - data class PIN(val code: Int) : AuthenticationMethodModel(isSecure = true) + /** + * Authentication method using a PIN. + * + * In practice, a pin is restricted to 16 decimal digits , see + * [android.app.admin.DevicePolicyManager.MAX_PASSWORD_LENGTH] + */ + data class Pin(val code: Long) : AuthenticationMethodModel(isSecure = true) data class Password(val password: String) : AuthenticationMethodModel(isSecure = true) diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/BouncerInteractor.kt b/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/BouncerInteractor.kt index 1d2fce7d8b05..a24a421ff2e1 100644 --- a/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/BouncerInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/BouncerInteractor.kt @@ -197,7 +197,7 @@ constructor( private fun promptMessage(authMethod: AuthenticationMethodModel): String { return when (authMethod) { - is AuthenticationMethodModel.PIN -> + is AuthenticationMethodModel.Pin -> applicationContext.getString(R.string.keyguard_enter_your_pin) is AuthenticationMethodModel.Password -> applicationContext.getString(R.string.keyguard_enter_your_password) @@ -209,7 +209,7 @@ constructor( private fun errorMessage(authMethod: AuthenticationMethodModel): String { return when (authMethod) { - is AuthenticationMethodModel.PIN -> applicationContext.getString(R.string.kg_wrong_pin) + is AuthenticationMethodModel.Pin -> applicationContext.getString(R.string.kg_wrong_pin) is AuthenticationMethodModel.Password -> applicationContext.getString(R.string.kg_wrong_password) is AuthenticationMethodModel.Pattern -> diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/BouncerViewModel.kt b/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/BouncerViewModel.kt index 984d9ab1c1be..527fe6ec847d 100644 --- a/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/BouncerViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/BouncerViewModel.kt @@ -126,7 +126,7 @@ constructor( .map { model -> model?.let { when (interactor.authenticationMethod.value) { - is AuthenticationMethodModel.PIN -> + is AuthenticationMethodModel.Pin -> R.string.kg_too_many_failed_pin_attempts_dialog_message is AuthenticationMethodModel.Password -> R.string.kg_too_many_failed_password_attempts_dialog_message @@ -165,7 +165,7 @@ constructor( authMethod: AuthenticationMethodModel, ): AuthMethodBouncerViewModel? { return when (authMethod) { - is AuthenticationMethodModel.PIN -> pin + is AuthenticationMethodModel.Pin -> pin is AuthenticationMethodModel.Password -> password is AuthenticationMethodModel.Pattern -> pattern else -> null diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/PinBouncerViewModel.kt b/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/PinBouncerViewModel.kt index 5c0fd92e7299..94d3d193e9cc 100644 --- a/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/PinBouncerViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/PinBouncerViewModel.kt @@ -16,18 +16,10 @@ package com.android.systemui.bouncer.ui.viewmodel -import androidx.annotation.VisibleForTesting import com.android.systemui.bouncer.domain.interactor.BouncerInteractor -import com.android.systemui.util.kotlin.pairwise import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.Job -import kotlinx.coroutines.delay import kotlinx.coroutines.flow.MutableStateFlow -import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.StateFlow -import kotlinx.coroutines.flow.map -import kotlinx.coroutines.flow.stateIn -import kotlinx.coroutines.launch /** Holds UI state and handles user input for the PIN code bouncer UI. */ class PinBouncerViewModel( @@ -39,21 +31,8 @@ class PinBouncerViewModel( isInputEnabled = isInputEnabled, ) { - private val entered = MutableStateFlow<List<Int>>(emptyList()) - /** - * The length of the PIN digits that were input so far, two values are supplied the previous and - * the current. - */ - val pinLengths: StateFlow<Pair<Int, Int>> = - entered - .pairwise() - .map { it.previousValue.size to it.newValue.size } - .stateIn( - scope = applicationScope, - started = SharingStarted.WhileSubscribed(), - initialValue = 0 to 0, - ) - private var resetPinJob: Job? = null + private val mutablePinEntries = MutableStateFlow<List<EnteredKey>>(emptyList()) + val pinEntries: StateFlow<List<EnteredKey>> = mutablePinEntries /** Notifies that the UI has been shown to the user. */ fun onShown() { @@ -62,47 +41,48 @@ class PinBouncerViewModel( /** Notifies that the user clicked on a PIN button with the given digit value. */ fun onPinButtonClicked(input: Int) { - resetPinJob?.cancel() - resetPinJob = null - - if (entered.value.isEmpty()) { + if (mutablePinEntries.value.isEmpty()) { interactor.clearMessage() } - entered.value += input + mutablePinEntries.value += EnteredKey(input) } /** Notifies that the user clicked the backspace button. */ fun onBackspaceButtonClicked() { - if (entered.value.isEmpty()) { + if (mutablePinEntries.value.isEmpty()) { return } - - entered.value = entered.value.toMutableList().apply { removeLast() } + mutablePinEntries.value = mutablePinEntries.value.toMutableList().apply { removeLast() } } /** Notifies that the user long-pressed the backspace button. */ fun onBackspaceButtonLongPressed() { - resetPinJob?.cancel() - resetPinJob = - applicationScope.launch { - while (entered.value.isNotEmpty()) { - onBackspaceButtonClicked() - delay(BACKSPACE_LONG_PRESS_DELAY_MS) - } - } + mutablePinEntries.value = emptyList() } /** Notifies that the user clicked the "enter" button. */ fun onAuthenticateButtonClicked() { - if (!interactor.authenticate(entered.value)) { + if (!interactor.authenticate(mutablePinEntries.value.map { it.input })) { showFailureAnimation() } - entered.value = emptyList() + mutablePinEntries.value = emptyList() } +} - companion object { - @VisibleForTesting const val BACKSPACE_LONG_PRESS_DELAY_MS = 80L - } +private var nextSequenceNumber = 1 + +/** + * The pin bouncer [input] as digits 0-9, together with a [sequenceNumber] to indicate the ordering. + * + * Since the model only allows appending/removing [EnteredKey]s from the end, the [sequenceNumber] + * is strictly increasing in input order of the pin, but not guaranteed to be monotonic or start at + * a specific number. + */ +data class EnteredKey +internal constructor(val input: Int, val sequenceNumber: Int = nextSequenceNumber++) : + Comparable<EnteredKey> { + override fun compareTo(other: EnteredKey): Int = + compareValuesBy(this, other, EnteredKey::sequenceNumber) } diff --git a/packages/SystemUI/src/com/android/systemui/controls/management/ControlsProviderSelectorActivity.kt b/packages/SystemUI/src/com/android/systemui/controls/management/ControlsProviderSelectorActivity.kt index e60063248fb0..fb19ac99d19e 100644 --- a/packages/SystemUI/src/com/android/systemui/controls/management/ControlsProviderSelectorActivity.kt +++ b/packages/SystemUI/src/com/android/systemui/controls/management/ControlsProviderSelectorActivity.kt @@ -217,7 +217,6 @@ open class ControlsProviderSelectorActivity @Inject constructor( ) } startActivity(intent, ActivityOptions.makeSceneTransitionAnimation(this).toBundle()) - animateExitAndFinish() } } } diff --git a/packages/SystemUI/src/com/android/systemui/controls/ui/DetailDialog.kt b/packages/SystemUI/src/com/android/systemui/controls/ui/DetailDialog.kt index 7cbd1f53612b..be50a1468f07 100644 --- a/packages/SystemUI/src/com/android/systemui/controls/ui/DetailDialog.kt +++ b/packages/SystemUI/src/com/android/systemui/controls/ui/DetailDialog.kt @@ -162,7 +162,11 @@ class DetailDialog( broadcastSender.closeSystemDialogs() // not sent as interactive, lest the higher-importance activity launch // be impacted - pendingIntent.send() + val options = ActivityOptions.makeBasic() + .setPendingIntentBackgroundActivityStartMode( + ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOWED) + .toBundle() + pendingIntent.send(options) false } if (keyguardStateController.isUnlocked()) { diff --git a/packages/SystemUI/src/com/android/systemui/controls/ui/StatusBehavior.kt b/packages/SystemUI/src/com/android/systemui/controls/ui/StatusBehavior.kt index 116f3ca898c0..84cda5a541e3 100644 --- a/packages/SystemUI/src/com/android/systemui/controls/ui/StatusBehavior.kt +++ b/packages/SystemUI/src/com/android/systemui/controls/ui/StatusBehavior.kt @@ -16,6 +16,7 @@ package com.android.systemui.controls.ui +import android.app.ActivityOptions import android.app.AlertDialog import android.app.PendingIntent import android.content.DialogInterface @@ -74,7 +75,11 @@ class StatusBehavior : Behavior { R.string.controls_open_app, DialogInterface.OnClickListener { dialog, _ -> try { - cws.control?.getAppIntent()?.send() + val options = ActivityOptions.makeBasic() + .setPendingIntentBackgroundActivityStartMode( + ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOWED) + .toBundle() + cws.control?.getAppIntent()?.send(options) context.sendBroadcast(Intent(Intent.ACTION_CLOSE_SYSTEM_DIALOGS)) } catch (e: PendingIntent.CanceledException) { cvh.setErrorStatus() diff --git a/packages/SystemUI/src/com/android/systemui/dagger/SystemUICoreStartableModule.kt b/packages/SystemUI/src/com/android/systemui/dagger/SystemUICoreStartableModule.kt index 25634f009fc7..76002d3f9693 100644 --- a/packages/SystemUI/src/com/android/systemui/dagger/SystemUICoreStartableModule.kt +++ b/packages/SystemUI/src/com/android/systemui/dagger/SystemUICoreStartableModule.kt @@ -34,6 +34,7 @@ import com.android.systemui.globalactions.GlobalActionsComponent import com.android.systemui.keyboard.KeyboardUI import com.android.systemui.keyboard.PhysicalKeyboardCoreStartable import com.android.systemui.keyguard.KeyguardViewMediator +import com.android.systemui.keyguard.KeyguardViewConfigurator import com.android.systemui.keyguard.data.quickaffordance.MuteQuickAffordanceCoreStartable import com.android.systemui.log.SessionTracker import com.android.systemui.media.RingtonePlayer @@ -295,4 +296,9 @@ abstract class SystemUICoreStartableModule { @IntoMap @ClassKey(AssistantAttentionMonitor::class) abstract fun bindAssistantAttentionMonitor(sysui: AssistantAttentionMonitor): CoreStartable + + @Binds + @IntoMap + @ClassKey(KeyguardViewConfigurator::class) + abstract fun bindKeyguardViewConfigurator(impl: KeyguardViewConfigurator): CoreStartable } diff --git a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt index 870fff75ed3a..432ec8aef5ed 100644 --- a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt +++ b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt @@ -134,7 +134,7 @@ object Flags { // TODO(b/275694445): Tracking Bug @JvmField - val LOCKSCREEN_WITHOUT_SECURE_LOCK_WHEN_DREAMING = unreleasedFlag(208, + val LOCKSCREEN_WITHOUT_SECURE_LOCK_WHEN_DREAMING = releasedFlag(208, "lockscreen_without_secure_lock_when_dreaming") /** diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewConfigurator.kt b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewConfigurator.kt new file mode 100644 index 000000000000..05c23aefe974 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewConfigurator.kt @@ -0,0 +1,77 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package com.android.systemui.keyguard + +import android.view.View +import android.view.ViewGroup +import com.android.systemui.CoreStartable +import com.android.systemui.R +import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.flags.FeatureFlags +import com.android.systemui.flags.Flags +import com.android.systemui.keyguard.ui.binder.KeyguardIndicationAreaBinder +import com.android.systemui.keyguard.ui.view.KeyguardRootView +import com.android.systemui.keyguard.ui.viewmodel.KeyguardIndicationAreaViewModel +import com.android.systemui.shade.NotificationShadeWindowView +import com.android.systemui.statusbar.KeyguardIndicationController +import javax.inject.Inject +import kotlinx.coroutines.DisposableHandle + +/** Binds keyguard views on startup, and also exposes methods to allow rebinding if views change */ +@SysUISingleton +class KeyguardViewConfigurator +@Inject +constructor( + private val keyguardRootView: KeyguardRootView, + private val keyguardIndicationAreaViewModel: KeyguardIndicationAreaViewModel, + private val notificationShadeWindowView: NotificationShadeWindowView, + private val featureFlags: FeatureFlags, + private val indicationController: KeyguardIndicationController, +) : CoreStartable { + + private var indicationAreaHandle: DisposableHandle? = null + + override fun start() { + bindIndicationArea( + notificationShadeWindowView.requireViewById(R.id.notification_panel) as ViewGroup + ) + } + + fun bindIndicationArea(legacyParent: ViewGroup) { + indicationAreaHandle?.dispose() + + // At startup, 2 views with the ID `R.id.keyguard_indication_area` will be available. + // Disable one of them + if (featureFlags.isEnabled(Flags.MIGRATE_INDICATION_AREA)) { + legacyParent.requireViewById<View>(R.id.keyguard_indication_area).let { + legacyParent.removeView(it) + } + } else { + keyguardRootView.findViewById<View?>(R.id.keyguard_indication_area)?.let { + keyguardRootView.removeView(it) + } + } + + indicationAreaHandle = + KeyguardIndicationAreaBinder.bind( + notificationShadeWindowView, + keyguardIndicationAreaViewModel, + indicationController + ) + } +} 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 3cf9a9ec5a9c..f99b8a2458f2 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 @@ -21,6 +21,7 @@ import android.app.StatusBarManager import android.graphics.Point import com.android.systemui.common.coroutine.ChannelExt.trySendWithFailureLogging import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow +import com.android.systemui.common.ui.data.repository.ConfigurationRepository import com.android.systemui.dagger.SysUISingleton import com.android.systemui.flags.FeatureFlags import com.android.systemui.flags.Flags @@ -59,6 +60,7 @@ constructor( private val commandQueue: CommandQueue, featureFlags: FeatureFlags, bouncerRepository: KeyguardBouncerRepository, + configurationRepository: ConfigurationRepository, ) { /** * The amount of doze the system is in, where `1.0` is fully dozing and `0.0` is not dozing at @@ -172,6 +174,9 @@ constructor( /** The approximate location on the screen of the face unlock sensor, if one is available. */ val faceSensorLocation: Flow<Point?> = repository.faceSensorLocation + /** Notifies when a new configuration is set */ + val configurationChange: Flow<Unit> = configurationRepository.onAnyConfigurationChange + fun dozeTransitionTo(vararg states: DozeStateModel): Flow<DozeTransitionModel> { return dozeTransitionModel.filter { states.contains(it.to) } } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBottomAreaViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBottomAreaViewBinder.kt index a8d662c96284..7d14198bdb17 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBottomAreaViewBinder.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBottomAreaViewBinder.kt @@ -21,12 +21,10 @@ import android.content.Intent import android.graphics.Rect import android.graphics.drawable.Animatable2 import android.util.Size -import android.util.TypedValue import android.view.View import android.view.ViewGroup import android.view.ViewPropertyAnimator import android.widget.ImageView -import android.widget.TextView import androidx.core.view.isInvisible import androidx.core.view.isVisible import androidx.core.view.updateLayoutParams @@ -108,14 +106,10 @@ object KeyguardBottomAreaViewBinder { activityStarter: ActivityStarter?, messageDisplayer: (Int) -> Unit, ): Binding { - val indicationArea: View = view.requireViewById(R.id.keyguard_indication_area) val ambientIndicationArea: View? = view.findViewById(R.id.ambient_indication_container) val startButton: ImageView = view.requireViewById(R.id.start_button) val endButton: ImageView = view.requireViewById(R.id.end_button) val overlayContainer: View = view.requireViewById(R.id.overlay_container) - val indicationText: TextView = view.requireViewById(R.id.keyguard_indication_text) - val indicationTextBottom: TextView = - view.requireViewById(R.id.keyguard_indication_text_bottom) val settingsMenu: LaunchableLinearLayout = view.requireViewById(R.id.keyguard_settings_button) @@ -183,7 +177,6 @@ object KeyguardBottomAreaViewBinder { } ambientIndicationArea?.alpha = alpha - indicationArea.alpha = alpha } } @@ -205,50 +198,23 @@ object KeyguardBottomAreaViewBinder { launch { viewModel.indicationAreaTranslationX.collect { translationX -> - indicationArea.translationX = translationX ambientIndicationArea?.translationX = translationX } } launch { - combine( - viewModel.isIndicationAreaPadded, - configurationBasedDimensions.map { it.indicationAreaPaddingPx }, - ) { isPadded, paddingIfPaddedPx -> - if (isPadded) { - paddingIfPaddedPx - } else { - 0 - } - } - .collect { paddingPx -> - indicationArea.setPadding(paddingPx, 0, paddingPx, 0) - } - } - - launch { configurationBasedDimensions .map { it.defaultBurnInPreventionYOffsetPx } .flatMapLatest { defaultBurnInOffsetY -> viewModel.indicationAreaTranslationY(defaultBurnInOffsetY) } .collect { translationY -> - indicationArea.translationY = translationY ambientIndicationArea?.translationY = translationY } } launch { configurationBasedDimensions.collect { dimensions -> - indicationText.setTextSize( - TypedValue.COMPLEX_UNIT_PX, - dimensions.indicationTextSizePx.toFloat(), - ) - indicationTextBottom.setTextSize( - TypedValue.COMPLEX_UNIT_PX, - dimensions.indicationTextSizePx.toFloat(), - ) - startButton.updateLayoutParams<ViewGroup.LayoutParams> { width = dimensions.buttonSizePx.width height = dimensions.buttonSizePx.height @@ -305,7 +271,7 @@ object KeyguardBottomAreaViewBinder { return object : Binding { override fun getIndicationAreaAnimators(): List<ViewPropertyAnimator> { - return listOf(indicationArea, ambientIndicationArea).mapNotNull { it?.animate() } + return listOf(ambientIndicationArea).mapNotNull { it?.animate() } } override fun onConfigurationChanged() { @@ -517,12 +483,6 @@ object KeyguardBottomAreaViewBinder { return ConfigurationBasedDimensions( defaultBurnInPreventionYOffsetPx = view.resources.getDimensionPixelOffset(R.dimen.default_burn_in_prevention_offset), - indicationAreaPaddingPx = - view.resources.getDimensionPixelOffset(R.dimen.keyguard_indication_area_padding), - indicationTextSizePx = - view.resources.getDimensionPixelSize( - com.android.internal.R.dimen.text_size_small_material, - ), buttonSizePx = Size( view.resources.getDimensionPixelSize(R.dimen.keyguard_affordance_fixed_width), @@ -552,8 +512,6 @@ object KeyguardBottomAreaViewBinder { private data class ConfigurationBasedDimensions( val defaultBurnInPreventionYOffsetPx: Int, - val indicationAreaPaddingPx: Int, - val indicationTextSizePx: Int, val buttonSizePx: Size, ) } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardIndicationAreaBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardIndicationAreaBinder.kt new file mode 100644 index 000000000000..02e6765fa1d3 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardIndicationAreaBinder.kt @@ -0,0 +1,153 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.keyguard.ui.binder + +import android.util.TypedValue +import android.view.View +import android.view.ViewGroup +import android.widget.TextView +import androidx.lifecycle.Lifecycle +import androidx.lifecycle.repeatOnLifecycle +import com.android.systemui.R +import com.android.systemui.keyguard.ui.viewmodel.KeyguardIndicationAreaViewModel +import com.android.systemui.lifecycle.repeatWhenAttached +import com.android.systemui.statusbar.KeyguardIndicationController +import kotlinx.coroutines.DisposableHandle +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.combine +import kotlinx.coroutines.flow.flatMapLatest +import kotlinx.coroutines.flow.map +import kotlinx.coroutines.launch + +/** + * Binds a keyguard indication area view to its view-model. + * + * To use this properly, users should maintain a one-to-one relationship between the [View] and the + * view-binding, binding each view only once. It is okay and expected for the same instance of the + * view-model to be reused for multiple view/view-binder bindings. + */ +@OptIn(ExperimentalCoroutinesApi::class) +object KeyguardIndicationAreaBinder { + + /** Binds the view to the view-model, continuing to update the former based on the latter. */ + @JvmStatic + fun bind( + view: ViewGroup, + viewModel: KeyguardIndicationAreaViewModel, + indicationController: KeyguardIndicationController, + ): DisposableHandle { + val indicationArea: ViewGroup = view.requireViewById(R.id.keyguard_indication_area) + indicationController.setIndicationArea(indicationArea) + + val indicationText: TextView = indicationArea.requireViewById(R.id.keyguard_indication_text) + val indicationTextBottom: TextView = + indicationArea.requireViewById(R.id.keyguard_indication_text_bottom) + + view.clipChildren = false + view.clipToPadding = false + + val configurationBasedDimensions = MutableStateFlow(loadFromResources(view)) + val disposableHandle = + view.repeatWhenAttached { + repeatOnLifecycle(Lifecycle.State.STARTED) { + launch { + viewModel.alpha.collect { alpha -> + view.importantForAccessibility = + if (alpha == 0f) { + View.IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS + } else { + View.IMPORTANT_FOR_ACCESSIBILITY_AUTO + } + + indicationArea.alpha = alpha + } + } + + launch { + viewModel.indicationAreaTranslationX.collect { translationX -> + indicationArea.translationX = translationX + } + } + + launch { + combine( + viewModel.isIndicationAreaPadded, + configurationBasedDimensions.map { it.indicationAreaPaddingPx }, + ) { isPadded, paddingIfPaddedPx -> + if (isPadded) { + paddingIfPaddedPx + } else { + 0 + } + } + .collect { paddingPx -> + indicationArea.setPadding(paddingPx, 0, paddingPx, 0) + } + } + + launch { + configurationBasedDimensions + .map { it.defaultBurnInPreventionYOffsetPx } + .flatMapLatest { defaultBurnInOffsetY -> + viewModel.indicationAreaTranslationY(defaultBurnInOffsetY) + } + .collect { translationY -> indicationArea.translationY = translationY } + } + + launch { + configurationBasedDimensions.collect { dimensions -> + indicationText.setTextSize( + TypedValue.COMPLEX_UNIT_PX, + dimensions.indicationTextSizePx.toFloat(), + ) + indicationTextBottom.setTextSize( + TypedValue.COMPLEX_UNIT_PX, + dimensions.indicationTextSizePx.toFloat(), + ) + } + } + + launch { + viewModel.configurationChange.collect { + configurationBasedDimensions.value = loadFromResources(view) + } + } + } + } + return disposableHandle + } + + private fun loadFromResources(view: View): ConfigurationBasedDimensions { + return ConfigurationBasedDimensions( + defaultBurnInPreventionYOffsetPx = + view.resources.getDimensionPixelOffset(R.dimen.default_burn_in_prevention_offset), + indicationAreaPaddingPx = + view.resources.getDimensionPixelOffset(R.dimen.keyguard_indication_area_padding), + indicationTextSizePx = + view.resources.getDimensionPixelSize( + com.android.internal.R.dimen.text_size_small_material, + ), + ) + } + + private data class ConfigurationBasedDimensions( + val defaultBurnInPreventionYOffsetPx: Int, + val indicationAreaPaddingPx: Int, + val indicationTextSizePx: Int, + ) +} diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/preview/KeyguardPreviewRenderer.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/preview/KeyguardPreviewRenderer.kt index fe62bf4388e9..db231092d7a5 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/preview/KeyguardPreviewRenderer.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/preview/KeyguardPreviewRenderer.kt @@ -17,7 +17,7 @@ package com.android.systemui.keyguard.ui.preview -import android.annotation.ColorInt +import android.app.WallpaperColors import android.content.BroadcastReceiver import android.content.Context import android.content.Intent @@ -47,6 +47,7 @@ import com.android.systemui.keyguard.ui.binder.KeyguardPreviewSmartspaceViewBind import com.android.systemui.keyguard.ui.viewmodel.KeyguardBottomAreaViewModel import com.android.systemui.keyguard.ui.viewmodel.KeyguardPreviewClockViewModel import com.android.systemui.keyguard.ui.viewmodel.KeyguardPreviewSmartspaceViewModel +import com.android.systemui.monet.ColorScheme import com.android.systemui.plugins.ClockController import com.android.systemui.shared.clocks.ClockRegistry import com.android.systemui.shared.clocks.DefaultClockController @@ -91,6 +92,7 @@ constructor( /** [shouldHideClock] here means that we never create and bind the clock views */ private val shouldHideClock: Boolean = bundle.getBoolean(ClockPreviewConstants.KEY_HIDE_CLOCK, false) + private val wallpaperColors: WallpaperColors? = bundle.getParcelable(KEY_COLORS) private var host: SurfaceControlViewHost @@ -100,7 +102,6 @@ constructor( private lateinit var largeClockHostView: FrameLayout private lateinit var smallClockHostView: FrameLayout private var smartSpaceView: View? = null - private var colorOverride: Int? = null private val disposables = mutableSetOf<DisposableHandle>() private var isDestroyed = false @@ -172,6 +173,10 @@ constructor( rootView.translationX = (width - scale * rootView.width) / 2 rootView.translationY = (height - scale * rootView.height) / 2 + if (isDestroyed) { + return@post + } + host.setView(rootView, rootView.measuredWidth, rootView.measuredHeight) } } @@ -195,14 +200,6 @@ constructor( mainHandler.post { smartSpaceView?.visibility = if (hide) View.INVISIBLE else View.VISIBLE } } - /** Sets the clock's color to the overridden seed color. */ - fun onColorOverridden(@ColorInt color: Int?) { - mainHandler.post { - colorOverride = color - clockController.clock?.run { events.onSeedColorChanged(color) } - } - } - /** * This sets up and shows a non-interactive smart space * @@ -395,7 +392,24 @@ constructor( val clock = clockRegistry.createCurrentClock() clockController.clock = clock - colorOverride?.let { clock.events.onSeedColorChanged(it) } + if (clockRegistry.seedColor == null) { + // Seed color null means users do override any color on the clock. The default color + // will need to use wallpaper's extracted color and consider if the wallpaper's color + // is dark or a light. + // TODO(b/277832214) we can potentially simplify this code by checking for + // wallpaperColors being null in the if clause above and removing the many ?. + val wallpaperColorScheme = + wallpaperColors?.let { ColorScheme(it, /* darkTheme= */ false) } + val lightClockColor = wallpaperColorScheme?.accent1?.s100 + val darkClockColor = wallpaperColorScheme?.accent2?.s600 + /** Note that when [wallpaperColors] is null, isWallpaperDark is true. */ + val isWallpaperDark: Boolean = + (wallpaperColors?.colorHints?.and(WallpaperColors.HINT_SUPPORTS_DARK_TEXT)) != + WallpaperColors.HINT_SUPPORTS_DARK_TEXT + clock.events.onSeedColorChanged( + if (isWallpaperDark) lightClockColor else darkClockColor + ) + } updateLargeClock(clock) updateSmallClock(clock) @@ -428,6 +442,7 @@ constructor( private const val KEY_VIEW_WIDTH = "width" private const val KEY_VIEW_HEIGHT = "height" private const val KEY_DISPLAY_ID = "display_id" + private const val KEY_COLORS = "wallpaper_colors" private const val DIM_ALPHA = 0.3f } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/preview/KeyguardRemotePreviewManager.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/preview/KeyguardRemotePreviewManager.kt index 79712f9c6be5..dafeacef60bb 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/preview/KeyguardRemotePreviewManager.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/preview/KeyguardRemotePreviewManager.kt @@ -124,13 +124,6 @@ constructor( message.data.getBoolean(KeyguardPreviewConstants.KEY_HIDE_SMART_SPACE) ) } - KeyguardPreviewConstants.MESSAGE_ID_COLOR_OVERRIDE -> { - renderer.onColorOverridden( - message.data - .getString(KeyguardPreviewConstants.KEY_COLOR_OVERRIDE) - ?.toIntOrNull() - ) - } else -> requestDestruction(this) } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/KeyguardIndicationArea.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/KeyguardIndicationArea.kt new file mode 100644 index 000000000000..890d565b03a5 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/KeyguardIndicationArea.kt @@ -0,0 +1,86 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package com.android.systemui.keyguard.ui.view + +import android.content.Context +import android.text.TextUtils +import android.util.AttributeSet +import android.view.Gravity +import android.view.View +import android.view.ViewGroup.LayoutParams.MATCH_PARENT +import android.view.ViewGroup.LayoutParams.WRAP_CONTENT +import android.widget.LinearLayout +import com.android.systemui.R +import com.android.systemui.statusbar.phone.KeyguardIndicationTextView + +class KeyguardIndicationArea( + context: Context, + private val attrs: AttributeSet?, +) : + LinearLayout( + context, + attrs, + ) { + + init { + setId(R.id.keyguard_indication_area) + orientation = LinearLayout.VERTICAL + + addView(indicationTopRow(), LinearLayout.LayoutParams(MATCH_PARENT, WRAP_CONTENT)) + addView( + indicationBottomRow(), + LinearLayout.LayoutParams(WRAP_CONTENT, WRAP_CONTENT).apply { + gravity = Gravity.CENTER_HORIZONTAL + } + ) + } + + private fun indicationTopRow(): KeyguardIndicationTextView { + return KeyguardIndicationTextView(context, attrs).apply { + id = R.id.keyguard_indication_text + gravity = Gravity.CENTER + accessibilityLiveRegion = View.ACCESSIBILITY_LIVE_REGION_POLITE + setTextAppearance(R.style.TextAppearance_Keyguard_BottomArea) + + val padding = R.dimen.keyguard_indication_text_padding.dp() + setPaddingRelative(padding, 0, padding, 0) + } + } + + private fun indicationBottomRow(): KeyguardIndicationTextView { + return KeyguardIndicationTextView(context, attrs).apply { + id = R.id.keyguard_indication_text_bottom + gravity = Gravity.CENTER + accessibilityLiveRegion = View.ACCESSIBILITY_LIVE_REGION_POLITE + + setTextAppearance(R.style.TextAppearance_Keyguard_BottomArea) + setEllipsize(TextUtils.TruncateAt.END) + setAlpha(0.8f) + setMinHeight(R.dimen.keyguard_indication_text_min_height.dp()) + setMaxLines(2) + setVisibility(View.GONE) + + val padding = R.dimen.keyguard_indication_text_padding.dp() + setPaddingRelative(padding, 0, padding, 0) + } + } + + private fun Int.dp(): Int { + return context.resources.getDimensionPixelSize(this) + } +} diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/KeyguardRootView.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/KeyguardRootView.kt new file mode 100644 index 000000000000..abf0e80bd87a --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/KeyguardRootView.kt @@ -0,0 +1,60 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package com.android.systemui.keyguard.ui.view + +import android.content.Context +import android.util.AttributeSet +import android.view.Gravity +import android.view.ViewGroup.LayoutParams.MATCH_PARENT +import android.view.ViewGroup.LayoutParams.WRAP_CONTENT +import android.widget.FrameLayout +import com.android.systemui.R + +/** Provides a container for all keyguard ui content. */ +class KeyguardRootView( + context: Context, + private val attrs: AttributeSet?, +) : + FrameLayout( + context, + attrs, + ) { + + init { + addIndicationTextArea() + } + + private fun addIndicationTextArea() { + val view = KeyguardIndicationArea(context, attrs) + addView( + view, + FrameLayout.LayoutParams( + MATCH_PARENT, + WRAP_CONTENT, + ) + .apply { + gravity = Gravity.BOTTOM or Gravity.CENTER_HORIZONTAL + bottomMargin = R.dimen.keyguard_indication_margin_bottom.dp() + } + ) + } + + private fun Int.dp(): Int { + return context.resources.getDimensionPixelSize(this) + } +} diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBottomAreaViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBottomAreaViewModel.kt index 62a1a9e16fc6..3e6f8e68891a 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBottomAreaViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBottomAreaViewModel.kt @@ -57,7 +57,7 @@ constructor( * in the wallpaper picker application. This should _always_ be `false` for the real lock screen * experience. */ - private val previewMode = MutableStateFlow(PreviewMode()) + val previewMode = MutableStateFlow(PreviewMode()) /** * ID of the slot that's currently selected in the preview that renders exclusively in the @@ -101,12 +101,6 @@ constructor( bottomAreaInteractor.alpha.distinctUntilChanged() } } - /** An observable for whether the indication area should be padded. */ - val isIndicationAreaPadded: Flow<Boolean> = - combine(startButton, endButton) { startButtonModel, endButtonModel -> - startButtonModel.isVisible || endButtonModel.isVisible - } - .distinctUntilChanged() /** An observable for the x-offset by which the indication area should be translated. */ val indicationAreaTranslationX: Flow<Float> = bottomAreaInteractor.clockPosition.map { it.x.toFloat() }.distinctUntilChanged() diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardIndicationAreaViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardIndicationAreaViewModel.kt new file mode 100644 index 000000000000..389cf76c47ac --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardIndicationAreaViewModel.kt @@ -0,0 +1,70 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.keyguard.ui.viewmodel + +import com.android.systemui.doze.util.BurnInHelperWrapper +import com.android.systemui.keyguard.domain.interactor.KeyguardBottomAreaInteractor +import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor +import javax.inject.Inject +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.combine +import kotlinx.coroutines.flow.distinctUntilChanged +import kotlinx.coroutines.flow.map + +/** View-model for the keyguard indication area view */ +@OptIn(ExperimentalCoroutinesApi::class) +class KeyguardIndicationAreaViewModel +@Inject +constructor( + private val keyguardInteractor: KeyguardInteractor, + private val bottomAreaInteractor: KeyguardBottomAreaInteractor, + private val keyguardBottomAreaViewModel: KeyguardBottomAreaViewModel, + private val burnInHelperWrapper: BurnInHelperWrapper, +) { + + /** Notifies when a new configuration is set */ + val configurationChange: Flow<Unit> = keyguardInteractor.configurationChange + + /** An observable for the alpha level for the entire bottom area. */ + val alpha: Flow<Float> = keyguardBottomAreaViewModel.alpha + + /** An observable for whether the indication area should be padded. */ + val isIndicationAreaPadded: Flow<Boolean> = + combine(keyguardBottomAreaViewModel.startButton, keyguardBottomAreaViewModel.endButton) { + startButtonModel, + endButtonModel -> + startButtonModel.isVisible || endButtonModel.isVisible + } + .distinctUntilChanged() + /** An observable for the x-offset by which the indication area should be translated. */ + val indicationAreaTranslationX: Flow<Float> = + bottomAreaInteractor.clockPosition.map { it.x.toFloat() }.distinctUntilChanged() + + /** Returns an observable for the y-offset by which the indication area should be translated. */ + fun indicationAreaTranslationY(defaultBurnInOffset: Int): Flow<Float> { + return keyguardInteractor.dozeAmount + .map { dozeAmount -> + dozeAmount * + (burnInHelperWrapper.burnInOffset( + /* amplitude = */ defaultBurnInOffset * 2, + /* xAxis= */ false, + ) - defaultBurnInOffset) + } + .distinctUntilChanged() + } +} diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java index 790df0246bc1..1174d3d932ae 100644 --- a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java +++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java @@ -125,6 +125,7 @@ import com.android.systemui.flags.FeatureFlags; import com.android.systemui.flags.Flags; import com.android.systemui.fragments.FragmentService; import com.android.systemui.keyguard.KeyguardUnlockAnimationController; +import com.android.systemui.keyguard.KeyguardViewConfigurator; import com.android.systemui.keyguard.domain.interactor.AlternateBouncerInteractor; import com.android.systemui.keyguard.domain.interactor.KeyguardBottomAreaInteractor; import com.android.systemui.keyguard.domain.interactor.KeyguardFaceAuthInteractor; @@ -598,6 +599,7 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump private final KeyguardTransitionInteractor mKeyguardTransitionInteractor; private final KeyguardInteractor mKeyguardInteractor; + private final KeyguardViewConfigurator mKeyguardViewConfigurator; private final @Nullable MultiShadeInteractor mMultiShadeInteractor; private final CoroutineDispatcher mMainDispatcher; private boolean mIsAnyMultiShadeExpanded; @@ -740,6 +742,7 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump KeyguardLongPressViewModel keyguardLongPressViewModel, KeyguardInteractor keyguardInteractor, ActivityStarter activityStarter, + KeyguardViewConfigurator keyguardViewConfigurator, KeyguardFaceAuthInteractor keyguardFaceAuthInteractor) { mInteractionJankMonitor = interactionJankMonitor; keyguardStateController.addCallback(new KeyguardStateController.Callback() { @@ -762,6 +765,7 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump mLockscreenToOccludedTransitionViewModel = lockscreenToOccludedTransitionViewModel; mKeyguardTransitionInteractor = keyguardTransitionInteractor; mKeyguardInteractor = keyguardInteractor; + mKeyguardViewConfigurator = keyguardViewConfigurator; mView.addOnAttachStateChangeListener(new View.OnAttachStateChangeListener() { @Override public void onViewAttachedToWindow(View v) { @@ -1319,7 +1323,6 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump mKeyguardBottomArea.initFrom(oldBottomArea); mView.addView(mKeyguardBottomArea, index); initBottomArea(); - mKeyguardIndicationController.setIndicationArea(mKeyguardBottomArea); mStatusBarStateListener.onDozeAmountChanged(mStatusBarStateController.getDozeAmount(), mStatusBarStateController.getInterpolatedDozeAmount()); @@ -1361,6 +1364,9 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump mKeyguardIndicationController.showTransientIndication(stringResourceId), mVibratorHelper, mActivityStarter); + + // Rebind (for now), as a new bottom area and indication area may have been created + mKeyguardViewConfigurator.bindIndicationArea(mKeyguardBottomArea); } @VisibleForTesting @@ -1398,7 +1404,6 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump private void setKeyguardBottomArea(KeyguardBottomAreaView keyguardBottomArea) { mKeyguardBottomArea = keyguardBottomArea; - mKeyguardIndicationController.setIndicationArea(mKeyguardBottomArea); } void setOpenCloseListener(OpenCloseListener openCloseListener) { @@ -3023,12 +3028,6 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump mKeyguardStatusViewController.setStatusAccessibilityImportance(mode); } - //TODO(b/254875405): this should be removed. - @Override - public KeyguardBottomAreaView getKeyguardBottomAreaView() { - return mKeyguardBottomArea; - } - @Override public void applyLaunchAnimationProgress(float linearProgress) { boolean hideIcons = LaunchAnimator.getProgress(ActivityLaunchAnimator.TIMINGS, diff --git a/packages/SystemUI/src/com/android/systemui/shade/ShadeModule.kt b/packages/SystemUI/src/com/android/systemui/shade/ShadeModule.kt index 44c732d8082f..1752ff6d531f 100644 --- a/packages/SystemUI/src/com/android/systemui/shade/ShadeModule.kt +++ b/packages/SystemUI/src/com/android/systemui/shade/ShadeModule.kt @@ -32,6 +32,7 @@ import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Main import com.android.systemui.flags.FeatureFlags import com.android.systemui.flags.Flags +import com.android.systemui.keyguard.ui.view.KeyguardRootView import com.android.systemui.privacy.OngoingPrivacyChip import com.android.systemui.scene.ui.view.WindowRootView import com.android.systemui.settings.UserTracker @@ -71,9 +72,9 @@ abstract class ShadeModule { layoutInflater.inflate(R.layout.scene_window_root, null) } else { layoutInflater.inflate(R.layout.super_notification_shade, null) - } as WindowRootView? ?: throw IllegalStateException( - "Window root view could not be properly inflated" - ) + } + as WindowRootView? + ?: throw IllegalStateException("Window root view could not be properly inflated") } @Provides @@ -89,9 +90,7 @@ abstract class ShadeModule { return root.findViewById(R.id.legacy_window_root) } return root as NotificationShadeWindowView? - ?: throw IllegalStateException( - "root view not a NotificationShadeWindowView" - ) + ?: throw IllegalStateException("root view not a NotificationShadeWindowView") } // TODO(b/277762009): Only allow this view's controller to inject the view. See above. @@ -120,6 +119,14 @@ abstract class ShadeModule { return notificationShadeWindowView.findViewById(R.id.light_reveal_scrim) } + @Provides + @SysUISingleton + fun providesKeyguardRootView( + notificationShadeWindowView: NotificationShadeWindowView, + ): KeyguardRootView { + return notificationShadeWindowView.findViewById(R.id.keyguard_root_view) + } + // TODO(b/277762009): Only allow this view's controller to inject the view. See above. @Provides @SysUISingleton diff --git a/packages/SystemUI/src/com/android/systemui/shade/ShadeViewController.kt b/packages/SystemUI/src/com/android/systemui/shade/ShadeViewController.kt index f75047c2072a..203355e71021 100644 --- a/packages/SystemUI/src/com/android/systemui/shade/ShadeViewController.kt +++ b/packages/SystemUI/src/com/android/systemui/shade/ShadeViewController.kt @@ -21,7 +21,6 @@ import com.android.systemui.statusbar.RemoteInputController import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController import com.android.systemui.statusbar.phone.HeadsUpAppearanceController -import com.android.systemui.statusbar.phone.KeyguardBottomAreaView import com.android.systemui.statusbar.phone.KeyguardStatusBarView import com.android.systemui.statusbar.phone.KeyguardStatusBarViewController import java.util.function.Consumer @@ -120,13 +119,6 @@ interface ShadeViewController { /** Returns the StatusBarState. */ val barState: Int - /** - * Returns the bottom part of the keyguard, which contains quick affordances. - * - * TODO(b/275550429): this should be removed. - */ - val keyguardBottomAreaView: KeyguardBottomAreaView? - /** Returns the NSSL controller. */ val notificationStackScrollLayoutController: NotificationStackScrollLayoutController diff --git a/packages/SystemUI/src/com/android/systemui/smartspace/dagger/SmartspaceViewComponent.kt b/packages/SystemUI/src/com/android/systemui/smartspace/dagger/SmartspaceViewComponent.kt index ba9d13d57a74..6d951bf4d83b 100644 --- a/packages/SystemUI/src/com/android/systemui/smartspace/dagger/SmartspaceViewComponent.kt +++ b/packages/SystemUI/src/com/android/systemui/smartspace/dagger/SmartspaceViewComponent.kt @@ -15,6 +15,7 @@ */ package com.android.systemui.smartspace.dagger +import android.app.ActivityOptions import android.app.PendingIntent import android.content.Intent import android.view.View @@ -81,7 +82,11 @@ interface SmartspaceViewComponent { showOnLockscreen: Boolean ) { if (showOnLockscreen) { - pi.send() + val options = ActivityOptions.makeBasic() + .setPendingIntentBackgroundActivityStartMode( + ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOWED) + .toBundle() + pi.send(options) } else { activityStarter.startPendingIntentDismissingKeyguard(pi) } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/lockscreen/LockscreenSmartspaceController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/lockscreen/LockscreenSmartspaceController.kt index 7cc917f3b0b8..518825cea5e0 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/lockscreen/LockscreenSmartspaceController.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/lockscreen/LockscreenSmartspaceController.kt @@ -16,6 +16,7 @@ package com.android.systemui.statusbar.lockscreen +import android.app.ActivityOptions import android.app.PendingIntent import android.app.smartspace.SmartspaceConfig import android.app.smartspace.SmartspaceManager @@ -358,7 +359,11 @@ constructor( showOnLockscreen: Boolean ) { if (showOnLockscreen) { - pi.send() + val options = ActivityOptions.makeBasic() + .setPendingIntentBackgroundActivityStartMode( + ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOWED) + .toBundle() + pi.send(options) } else { activityStarter.postStartActivityDismissingKeyguard(pi) } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ActivityStarterImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ActivityStarterImpl.kt index 0159d1274e85..5ad5d841fad5 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ActivityStarterImpl.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ActivityStarterImpl.kt @@ -574,6 +574,9 @@ constructor( // TODO b/221255671: restrict this to only be set for // notifications options.isEligibleForLegacyPermissionPrompt = true + options.setPendingIntentBackgroundActivityStartMode( + ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOWED + ) return intent.sendAndReturnResult( null, 0, diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfaces.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfaces.java index d759e58f073e..0b02f33daeae 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfaces.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfaces.java @@ -31,7 +31,6 @@ import android.view.KeyEvent; import android.view.MotionEvent; import android.view.RemoteAnimationAdapter; import android.view.View; -import android.view.ViewGroup; import android.window.RemoteTransition; import android.window.SplashScreen; @@ -40,7 +39,6 @@ import androidx.lifecycle.Lifecycle; import androidx.lifecycle.LifecycleOwner; import com.android.internal.annotations.VisibleForTesting; -import com.android.internal.statusbar.RegisterStatusBarResult; import com.android.keyguard.AuthKeyguardMessageArea; import com.android.systemui.Dumpable; import com.android.systemui.animation.ActivityLaunchAnimator; @@ -50,7 +48,6 @@ import com.android.systemui.plugins.statusbar.NotificationSwipeActionHelper; import com.android.systemui.qs.QSPanelController; import com.android.systemui.shade.ShadeViewController; import com.android.systemui.shared.system.RemoteAnimationRunnerCompat; -import com.android.systemui.statusbar.LightRevealScrim; import com.android.systemui.statusbar.NotificationPresenter; import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow; import com.android.systemui.util.Compile; @@ -68,14 +65,11 @@ public interface CentralSurfaces extends Dumpable, LifecycleOwner { String TAG = "CentralSurfaces"; boolean DEBUG = false; boolean SPEW = false; - boolean DUMPTRUCK = true; // extra dumpsys info boolean DEBUG_GESTURES = false; boolean DEBUG_MEDIA_FAKE_ARTWORK = false; boolean DEBUG_CAMERA_LIFT = false; boolean DEBUG_WINDOW_STATE = false; boolean DEBUG_WAKEUP_DELAY = Compile.IS_DEBUG; - // additional instrumentation for testing purposes; intended to be left on during development - boolean CHATTY = DEBUG; boolean SHOW_LOCKSCREEN_MEDIA_ARTWORK = true; String ACTION_FAKE_ARTWORK = "fake_artwork"; int FADE_KEYGUARD_START_DELAY = 100; @@ -234,8 +228,6 @@ public interface CentralSurfaces extends Dumpable, LifecycleOwner { void onKeyguardViewManagerStatesUpdated(); - ViewGroup getNotificationScrollLayout(); - boolean isPulsing(); boolean isOccluded(); @@ -297,8 +289,6 @@ public interface CentralSurfaces extends Dumpable, LifecycleOwner { @Override void dump(PrintWriter pwOriginal, String[] args); - void createAndAddWindows(@Nullable RegisterStatusBarResult result); - float getDisplayWidth(); float getDisplayHeight(); @@ -348,8 +338,6 @@ public interface CentralSurfaces extends Dumpable, LifecycleOwner { void showBouncerWithDimissAndCancelIfKeyguard(OnDismissAction performAction, Runnable cancelAction); - LightRevealScrim getLightRevealScrim(); - // TODO: Figure out way to remove these. NavigationBarView getNavigationBarView(); @@ -463,8 +451,6 @@ public interface CentralSurfaces extends Dumpable, LifecycleOwner { void extendDozePulse(); - boolean shouldDelayWakeUpAnimation(); - public static class KeyboardShortcutsMessage { final int mDeviceId; diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java index 021ef918243e..fde08bde5d17 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java @@ -1816,11 +1816,6 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces { } @Override - public ViewGroup getNotificationScrollLayout() { - return mStackScroller; - } - - @Override public boolean isPulsing() { return mDozeServiceHost.isPulsing(); } @@ -2244,8 +2239,7 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces { + CameraIntents.getOverrideCameraPackage(mContext)); } - @Override - public void createAndAddWindows(@Nullable RegisterStatusBarResult result) { + private void createAndAddWindows(@Nullable RegisterStatusBarResult result) { makeStatusBarView(result); mNotificationShadeWindowController.attach(); mStatusBarWindowController.attach(); @@ -3026,11 +3020,6 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces { } } - @Override - public LightRevealScrim getLightRevealScrim() { - return mLightRevealScrim; - } - // TODO: Figure out way to remove these. @Override public NavigationBarView getNavigationBarView() { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenter.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenter.java index dfaee4c674ad..5624e28204cc 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenter.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenter.java @@ -40,7 +40,6 @@ import com.android.systemui.shade.NotificationShadeWindowView; import com.android.systemui.shade.QuickSettingsController; import com.android.systemui.shade.ShadeViewController; import com.android.systemui.statusbar.CommandQueue; -import com.android.systemui.statusbar.KeyguardIndicationController; import com.android.systemui.statusbar.LockscreenShadeTransitionController; import com.android.systemui.statusbar.NotificationLockscreenUserManager; import com.android.systemui.statusbar.NotificationMediaManager; @@ -86,7 +85,6 @@ class StatusBarNotificationPresenter implements NotificationPresenter, private final HeadsUpManagerPhone mHeadsUpManager; private final AboveShelfObserver mAboveShelfObserver; private final DozeScrimController mDozeScrimController; - private final KeyguardIndicationController mKeyguardIndicationController; private final CentralSurfaces mCentralSurfaces; private final LockscreenShadeTransitionController mShadeTransitionController; private final CommandQueue mCommandQueue; @@ -115,7 +113,6 @@ class StatusBarNotificationPresenter implements NotificationPresenter, NotificationShadeWindowController notificationShadeWindowController, DynamicPrivacyController dynamicPrivacyController, KeyguardStateController keyguardStateController, - KeyguardIndicationController keyguardIndicationController, CentralSurfaces centralSurfaces, LockscreenShadeTransitionController shadeTransitionController, CommandQueue commandQueue, @@ -137,7 +134,6 @@ class StatusBarNotificationPresenter implements NotificationPresenter, mQsController = quickSettingsController; mHeadsUpManager = headsUp; mDynamicPrivacyController = dynamicPrivacyController; - mKeyguardIndicationController = keyguardIndicationController; // TODO: use KeyguardStateController#isOccluded to remove this dependency mCentralSurfaces = centralSurfaces; mShadeTransitionController = shadeTransitionController; @@ -173,7 +169,6 @@ class StatusBarNotificationPresenter implements NotificationPresenter, mNotificationPanel.getShadeNotificationPresenter().createRemoteInputDelegate()); initController.addPostInitTask(() -> { - mKeyguardIndicationController.init(); mNotifShadeEventSource.setShadeEmptiedCallback(this::maybeClosePanelForShadeEmptied); mNotifShadeEventSource.setNotifRemovedByUserCallback(this::maybeEndAmbientPulse); notificationInterruptStateProvider.addSuppressor(mInterruptSuppressor); diff --git a/packages/SystemUI/src/com/android/systemui/theme/DynamicColors.kt b/packages/SystemUI/src/com/android/systemui/theme/DynamicColors.kt index 57b9f914d4d6..ae4820837ce5 100644 --- a/packages/SystemUI/src/com/android/systemui/theme/DynamicColors.kt +++ b/packages/SystemUI/src/com/android/systemui/theme/DynamicColors.kt @@ -18,10 +18,11 @@ package com.android.systemui.theme import android.util.Pair import com.android.systemui.monet.dynamiccolor.DynamicColor -import com.android.systemui.monet.dynamiccolor.MaterialDynamicColors as MDC +import com.android.systemui.monet.dynamiccolor.MaterialDynamicColors class DynamicColors { companion object { + private val MDC = MaterialDynamicColors() @JvmField val ALL_DYNAMIC_COLORS_MAPPED: List<Pair<String, DynamicColor>> = arrayListOf( diff --git a/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayController.java b/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayController.java index c1999b284553..b78329cfa5aa 100644 --- a/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayController.java +++ b/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayController.java @@ -659,6 +659,7 @@ public class ThemeOverlayController implements CoreStartable, Dumpable { Resources res = userHandle.isSystem() ? mResources : mContext.createContextAsUser(userHandle, 0).getResources(); Resources.Theme theme = mContext.getTheme(); + MaterialDynamicColors dynamicColors = new MaterialDynamicColors(); if (!(res.getColor(android.R.color.system_accent1_500, theme) == mColorScheme.getAccent1().getS500() && res.getColor(android.R.color.system_accent2_500, theme) @@ -670,15 +671,15 @@ public class ThemeOverlayController implements CoreStartable, Dumpable { && res.getColor(android.R.color.system_neutral2_500, theme) == mColorScheme.getNeutral2().getS500() && res.getColor(android.R.color.system_outline_variant_dark, theme) - == MaterialDynamicColors.outlineVariant().getArgb(mDynamicSchemeDark) + == dynamicColors.outlineVariant().getArgb(mDynamicSchemeDark) && res.getColor(android.R.color.system_outline_variant_light, theme) - == MaterialDynamicColors.outlineVariant().getArgb(mDynamicSchemeLight) + == dynamicColors.outlineVariant().getArgb(mDynamicSchemeLight) && res.getColor(android.R.color.system_primary_container_dark, theme) - == MaterialDynamicColors.primaryContainer().getArgb(mDynamicSchemeDark) + == dynamicColors.primaryContainer().getArgb(mDynamicSchemeDark) && res.getColor(android.R.color.system_primary_container_light, theme) - == MaterialDynamicColors.primaryContainer().getArgb(mDynamicSchemeLight) + == dynamicColors.primaryContainer().getArgb(mDynamicSchemeLight) && res.getColor(android.R.color.system_primary_fixed, theme) - == MaterialDynamicColors.primaryFixed().getArgb(mDynamicSchemeLight))) { + == dynamicColors.primaryFixed().getArgb(mDynamicSchemeLight))) { return false; } } diff --git a/packages/SystemUI/src/com/android/systemui/wallet/ui/WalletView.java b/packages/SystemUI/src/com/android/systemui/wallet/ui/WalletView.java index dfc6392404a4..cab47a3c4b4b 100644 --- a/packages/SystemUI/src/com/android/systemui/wallet/ui/WalletView.java +++ b/packages/SystemUI/src/com/android/systemui/wallet/ui/WalletView.java @@ -307,6 +307,8 @@ public class WalletView extends FrameLayout implements WalletCardCarousel.OnCard BroadcastOptions options = BroadcastOptions.makeBasic(); options.setInteractive(true); + options.setPendingIntentBackgroundActivityStartMode( + BroadcastOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOWED); walletCard.getPendingIntent().send(options.toBundle()); } catch (PendingIntent.CanceledException e) { Log.w(TAG, "Error sending pending intent for wallet card."); diff --git a/packages/SystemUI/tests/src/com/android/keyguard/ClockEventControllerTest.kt b/packages/SystemUI/tests/src/com/android/keyguard/ClockEventControllerTest.kt index 3f1560bc5fce..8c71392caeec 100644 --- a/packages/SystemUI/tests/src/com/android/keyguard/ClockEventControllerTest.kt +++ b/packages/SystemUI/tests/src/com/android/keyguard/ClockEventControllerTest.kt @@ -53,6 +53,7 @@ import org.junit.Before import org.junit.Rule import org.junit.Test import org.junit.runner.RunWith +import org.mockito.ArgumentMatchers.anyBoolean import org.mockito.ArgumentMatchers.anyFloat import org.mockito.ArgumentMatchers.anyInt import org.mockito.Mock @@ -62,6 +63,7 @@ import org.mockito.Mockito.verify import org.mockito.junit.MockitoJUnit import java.util.TimeZone import java.util.concurrent.Executor +import com.android.systemui.common.ui.data.repository.FakeConfigurationRepository import org.mockito.Mockito.`when` as whenever @RunWith(AndroidTestingRunner::class) @@ -117,6 +119,7 @@ class ClockEventControllerTest : SysuiTestCase() { commandQueue = commandQueue, featureFlags = featureFlags, bouncerRepository = bouncerRepository, + configurationRepository = FakeConfigurationRepository(), ), KeyguardTransitionInteractor(repository = transitionRepository), broadcastDispatcher, @@ -155,9 +158,8 @@ class ClockEventControllerTest : SysuiTestCase() { @Test fun themeChanged_verifyClockPaletteUpdated() = runBlocking(IMMEDIATE) { - // TODO(b/266103601): delete this test and add more coverage for updateColors() - // verify(smallClockEvents).onRegionDarknessChanged(anyBoolean()) - // verify(largeClockEvents).onRegionDarknessChanged(anyBoolean()) + verify(smallClockEvents).onRegionDarknessChanged(anyBoolean()) + verify(largeClockEvents).onRegionDarknessChanged(anyBoolean()) val captor = argumentCaptor<ConfigurationController.ConfigurationListener>() verify(configurationController).addCallback(capture(captor)) diff --git a/packages/SystemUI/tests/src/com/android/keyguard/LockIconViewControllerBaseTest.java b/packages/SystemUI/tests/src/com/android/keyguard/LockIconViewControllerBaseTest.java index 456702b65091..bdf6bee8ecff 100644 --- a/packages/SystemUI/tests/src/com/android/keyguard/LockIconViewControllerBaseTest.java +++ b/packages/SystemUI/tests/src/com/android/keyguard/LockIconViewControllerBaseTest.java @@ -18,6 +18,7 @@ package com.android.keyguard; import static com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession; import static com.android.systemui.flags.Flags.DOZING_MIGRATION_1; +import static com.android.systemui.flags.Flags.FACE_AUTH_REFACTOR; import static org.mockito.Mockito.any; import static org.mockito.Mockito.anyInt; @@ -42,15 +43,12 @@ import com.android.systemui.biometrics.AuthController; import com.android.systemui.biometrics.AuthRippleController; import com.android.systemui.doze.util.BurnInHelperKt; import com.android.systemui.dump.DumpManager; -import com.android.systemui.flags.FeatureFlags; -import com.android.systemui.keyguard.data.repository.FakeKeyguardBouncerRepository; -import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository; +import com.android.systemui.flags.FakeFeatureFlags; import com.android.systemui.keyguard.data.repository.KeyguardTransitionRepository; -import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor; +import com.android.systemui.keyguard.domain.interactor.KeyguardInteractorFactory; import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor; import com.android.systemui.plugins.FalsingManager; import com.android.systemui.plugins.statusbar.StatusBarStateController; -import com.android.systemui.statusbar.CommandQueue; import com.android.systemui.statusbar.StatusBarState; import com.android.systemui.statusbar.VibratorHelper; import com.android.systemui.statusbar.policy.ConfigurationController; @@ -91,10 +89,9 @@ public class LockIconViewControllerBaseTest extends SysuiTestCase { protected @Mock ConfigurationController mConfigurationController; protected @Mock VibratorHelper mVibrator; protected @Mock AuthRippleController mAuthRippleController; - protected @Mock FeatureFlags mFeatureFlags; protected @Mock KeyguardTransitionRepository mTransitionRepository; - protected @Mock CommandQueue mCommandQueue; protected FakeExecutor mDelayableExecutor = new FakeExecutor(new FakeSystemClock()); + protected FakeFeatureFlags mFeatureFlags; protected LockIconViewController mUnderTest; @@ -144,6 +141,8 @@ public class LockIconViewControllerBaseTest extends SysuiTestCase { when(mStatusBarStateController.isDozing()).thenReturn(false); when(mStatusBarStateController.getState()).thenReturn(StatusBarState.KEYGUARD); + mFeatureFlags = new FakeFeatureFlags(); + mFeatureFlags.set(FACE_AUTH_REFACTOR, false); mUnderTest = new LockIconViewController( mLockIconView, mStatusBarStateController, @@ -160,12 +159,7 @@ public class LockIconViewControllerBaseTest extends SysuiTestCase { mAuthRippleController, mResources, new KeyguardTransitionInteractor(mTransitionRepository), - new KeyguardInteractor( - new FakeKeyguardRepository(), - mCommandQueue, - mFeatureFlags, - new FakeKeyguardBouncerRepository() - ), + KeyguardInteractorFactory.create(mFeatureFlags).getKeyguardInteractor(), mFeatureFlags ); } @@ -226,7 +220,7 @@ public class LockIconViewControllerBaseTest extends SysuiTestCase { } protected void init(boolean useMigrationFlag) { - when(mFeatureFlags.isEnabled(DOZING_MIGRATION_1)).thenReturn(useMigrationFlag); + mFeatureFlags.set(DOZING_MIGRATION_1, useMigrationFlag); mUnderTest.init(); verify(mLockIconView, atLeast(1)).addOnAttachStateChangeListener(mAttachCaptor.capture()); diff --git a/packages/SystemUI/tests/src/com/android/systemui/authentication/domain/interactor/AuthenticationInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/authentication/domain/interactor/AuthenticationInteractorTest.kt index 1990c8f644b4..3a93e7744d00 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/authentication/domain/interactor/AuthenticationInteractorTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/authentication/domain/interactor/AuthenticationInteractorTest.kt @@ -16,6 +16,7 @@ package com.android.systemui.authentication.domain.interactor +import android.app.admin.DevicePolicyManager import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.authentication.data.repository.AuthenticationRepository @@ -48,7 +49,7 @@ class AuthenticationInteractorTest : SysuiTestCase() { fun authMethod() = testScope.runTest { val authMethod by collectLastValue(underTest.authenticationMethod) - assertThat(authMethod).isEqualTo(AuthenticationMethodModel.PIN(1234)) + assertThat(authMethod).isEqualTo(AuthenticationMethodModel.Pin(1234)) underTest.setAuthenticationMethod(AuthenticationMethodModel.Password("password")) assertThat(authMethod).isEqualTo(AuthenticationMethodModel.Password("password")) @@ -147,7 +148,7 @@ class AuthenticationInteractorTest : SysuiTestCase() { testScope.runTest { val failedAttemptCount by collectLastValue(underTest.failedAuthenticationAttempts) val isUnlocked by collectLastValue(underTest.isUnlocked) - underTest.setAuthenticationMethod(AuthenticationMethodModel.PIN(1234)) + underTest.setAuthenticationMethod(AuthenticationMethodModel.Pin(1234)) assertThat(isUnlocked).isFalse() assertThat(underTest.authenticate(listOf(1, 2, 3, 4))).isTrue() @@ -160,7 +161,7 @@ class AuthenticationInteractorTest : SysuiTestCase() { testScope.runTest { val failedAttemptCount by collectLastValue(underTest.failedAuthenticationAttempts) val isUnlocked by collectLastValue(underTest.isUnlocked) - underTest.setAuthenticationMethod(AuthenticationMethodModel.PIN(1234)) + underTest.setAuthenticationMethod(AuthenticationMethodModel.Pin(1234)) assertThat(isUnlocked).isFalse() assertThat(underTest.authenticate(listOf(9, 8, 7))).isFalse() @@ -169,6 +170,51 @@ class AuthenticationInteractorTest : SysuiTestCase() { } @Test + fun authenticate_withEmptyPin_returnsFalseAndDoesNotUnlockDevice() = + testScope.runTest { + val failedAttemptCount by collectLastValue(underTest.failedAuthenticationAttempts) + val isUnlocked by collectLastValue(underTest.isUnlocked) + underTest.setAuthenticationMethod(AuthenticationMethodModel.Pin(1234)) + assertThat(isUnlocked).isFalse() + + assertThat(underTest.authenticate(listOf())).isFalse() + assertThat(isUnlocked).isFalse() + assertThat(failedAttemptCount).isEqualTo(1) + } + + @Test + fun authenticate_withCorrectMaxLengthPin_returnsTrueAndUnlocksDevice() = + testScope.runTest { + val failedAttemptCount by collectLastValue(underTest.failedAuthenticationAttempts) + val isUnlocked by collectLastValue(underTest.isUnlocked) + underTest.setAuthenticationMethod(AuthenticationMethodModel.Pin(9999999999999999)) + assertThat(isUnlocked).isFalse() + + assertThat(underTest.authenticate(List(16) { 9 })).isTrue() + assertThat(isUnlocked).isTrue() + assertThat(failedAttemptCount).isEqualTo(0) + } + + @Test + fun authenticate_withCorrectTooLongPin_returnsFalseAndDoesNotUnlockDevice() = + testScope.runTest { + // Max pin length is 16 digits. To avoid issues with overflows, this test ensures + // that all pins > 16 decimal digits are rejected. + + // If the policy changes, there is work to do in SysUI. + assertThat(DevicePolicyManager.MAX_PASSWORD_LENGTH).isLessThan(17) + + val failedAttemptCount by collectLastValue(underTest.failedAuthenticationAttempts) + val isUnlocked by collectLastValue(underTest.isUnlocked) + underTest.setAuthenticationMethod(AuthenticationMethodModel.Pin(99999999999999999)) + assertThat(isUnlocked).isFalse() + + assertThat(underTest.authenticate(List(17) { 9 })).isFalse() + assertThat(isUnlocked).isFalse() + assertThat(failedAttemptCount).isEqualTo(1) + } + + @Test fun authenticate_withCorrectPassword_returnsTrueAndUnlocksDevice() = testScope.runTest { val failedAttemptCount by collectLastValue(underTest.failedAuthenticationAttempts) diff --git a/packages/SystemUI/tests/src/com/android/systemui/bouncer/domain/interactor/BouncerInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/bouncer/domain/interactor/BouncerInteractorTest.kt index 374c28d6dce8..6a63c32f2c40 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/bouncer/domain/interactor/BouncerInteractorTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/bouncer/domain/interactor/BouncerInteractorTest.kt @@ -69,7 +69,7 @@ class BouncerInteractorTest : SysuiTestCase() { val currentScene by collectLastValue(sceneInteractor.currentScene("container1")) val message by collectLastValue(underTest.message) - authenticationInteractor.setAuthenticationMethod(AuthenticationMethodModel.PIN(1234)) + authenticationInteractor.setAuthenticationMethod(AuthenticationMethodModel.Pin(1234)) authenticationInteractor.lockDevice() underTest.showOrUnlockDevice("container1") assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer)) @@ -167,7 +167,7 @@ class BouncerInteractorTest : SysuiTestCase() { fun showOrUnlockDevice_notLocked_switchesToGoneScene() = testScope.runTest { val currentScene by collectLastValue(sceneInteractor.currentScene("container1")) - authenticationInteractor.setAuthenticationMethod(AuthenticationMethodModel.PIN(1234)) + authenticationInteractor.setAuthenticationMethod(AuthenticationMethodModel.Pin(1234)) authenticationInteractor.unlockDevice() runCurrent() @@ -211,7 +211,7 @@ class BouncerInteractorTest : SysuiTestCase() { val throttling by collectLastValue(underTest.throttling) val message by collectLastValue(underTest.message) val isUnlocked by collectLastValue(authenticationInteractor.isUnlocked) - authenticationInteractor.setAuthenticationMethod(AuthenticationMethodModel.PIN(1234)) + authenticationInteractor.setAuthenticationMethod(AuthenticationMethodModel.Pin(1234)) assertThat(throttling).isNull() assertThat(message).isEqualTo("") assertThat(isUnlocked).isFalse() diff --git a/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/AuthMethodBouncerViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/AuthMethodBouncerViewModelTest.kt index 1642410e5a3f..b53e03419df3 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/AuthMethodBouncerViewModelTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/AuthMethodBouncerViewModelTest.kt @@ -55,7 +55,7 @@ class AuthMethodBouncerViewModelTest : SysuiTestCase() { @Test fun animateFailure() = testScope.runTest { - authenticationInteractor.setAuthenticationMethod(AuthenticationMethodModel.PIN(1234)) + authenticationInteractor.setAuthenticationMethod(AuthenticationMethodModel.Pin(1234)) val animateFailure by collectLastValue(underTest.animateFailure) assertThat(animateFailure).isFalse() diff --git a/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/BouncerViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/BouncerViewModelTest.kt index e8c946cdd59d..c6074962ee8e 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/BouncerViewModelTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/BouncerViewModelTest.kt @@ -97,7 +97,7 @@ class BouncerViewModelTest : SysuiTestCase() { testScope.runTest { val message by collectLastValue(underTest.message) val throttling by collectLastValue(bouncerInteractor.throttling) - authenticationInteractor.setAuthenticationMethod(AuthenticationMethodModel.PIN(1234)) + authenticationInteractor.setAuthenticationMethod(AuthenticationMethodModel.Pin(1234)) assertThat(message?.isUpdateAnimated).isTrue() repeat(BouncerInteractor.THROTTLE_EVERY) { @@ -120,7 +120,7 @@ class BouncerViewModelTest : SysuiTestCase() { } ) val throttling by collectLastValue(bouncerInteractor.throttling) - authenticationInteractor.setAuthenticationMethod(AuthenticationMethodModel.PIN(1234)) + authenticationInteractor.setAuthenticationMethod(AuthenticationMethodModel.Pin(1234)) assertThat(isInputEnabled).isTrue() repeat(BouncerInteractor.THROTTLE_EVERY) { @@ -137,7 +137,7 @@ class BouncerViewModelTest : SysuiTestCase() { fun throttlingDialogMessage() = testScope.runTest { val throttlingDialogMessage by collectLastValue(underTest.throttlingDialogMessage) - authenticationInteractor.setAuthenticationMethod(AuthenticationMethodModel.PIN(1234)) + authenticationInteractor.setAuthenticationMethod(AuthenticationMethodModel.Pin(1234)) repeat(BouncerInteractor.THROTTLE_EVERY) { // Wrong PIN. @@ -154,7 +154,7 @@ class BouncerViewModelTest : SysuiTestCase() { return listOf( AuthenticationMethodModel.None, AuthenticationMethodModel.Swipe, - AuthenticationMethodModel.PIN(1234), + AuthenticationMethodModel.Pin(1234), AuthenticationMethodModel.Password("password"), AuthenticationMethodModel.Pattern( listOf(AuthenticationMethodModel.Pattern.PatternCoordinate(1, 1)) diff --git a/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/PinBouncerViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/PinBouncerViewModelTest.kt index 3bdaf0590888..7b6bb37459a7 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/PinBouncerViewModelTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/PinBouncerViewModelTest.kt @@ -25,12 +25,12 @@ import com.android.systemui.coroutines.collectLastValue import com.android.systemui.scene.SceneTestUtils import com.android.systemui.scene.shared.model.SceneKey import com.android.systemui.scene.shared.model.SceneModel +import com.google.common.truth.Correspondence import com.google.common.truth.Truth.assertThat import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.test.TestScope -import kotlinx.coroutines.test.advanceTimeBy import kotlinx.coroutines.test.runTest import org.junit.Before import org.junit.Test @@ -85,8 +85,8 @@ class PinBouncerViewModelTest : SysuiTestCase() { val isUnlocked by collectLastValue(authenticationInteractor.isUnlocked) val currentScene by collectLastValue(sceneInteractor.currentScene(CONTAINER_NAME)) val message by collectLastValue(bouncerViewModel.message) - val pinLengths by collectLastValue(underTest.pinLengths) - authenticationInteractor.setAuthenticationMethod(AuthenticationMethodModel.PIN(1234)) + val entries by collectLastValue(underTest.pinEntries) + authenticationInteractor.setAuthenticationMethod(AuthenticationMethodModel.Pin(1234)) authenticationInteractor.lockDevice() sceneInteractor.setCurrentScene(CONTAINER_NAME, SceneModel(SceneKey.Bouncer)) assertThat(isUnlocked).isFalse() @@ -95,7 +95,7 @@ class PinBouncerViewModelTest : SysuiTestCase() { underTest.onShown() assertThat(message?.text).isEqualTo(ENTER_YOUR_PIN) - assertThat(pinLengths).isEqualTo(0 to 0) + assertThat(entries).hasSize(0) assertThat(isUnlocked).isFalse() assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer)) } @@ -106,8 +106,8 @@ class PinBouncerViewModelTest : SysuiTestCase() { val isUnlocked by collectLastValue(authenticationInteractor.isUnlocked) val currentScene by collectLastValue(sceneInteractor.currentScene(CONTAINER_NAME)) val message by collectLastValue(bouncerViewModel.message) - val pinLengths by collectLastValue(underTest.pinLengths) - authenticationInteractor.setAuthenticationMethod(AuthenticationMethodModel.PIN(1234)) + val entries by collectLastValue(underTest.pinEntries) + authenticationInteractor.setAuthenticationMethod(AuthenticationMethodModel.Pin(1234)) authenticationInteractor.lockDevice() sceneInteractor.setCurrentScene(CONTAINER_NAME, SceneModel(SceneKey.Bouncer)) assertThat(isUnlocked).isFalse() @@ -117,7 +117,8 @@ class PinBouncerViewModelTest : SysuiTestCase() { underTest.onPinButtonClicked(1) assertThat(message?.text).isEmpty() - assertThat(pinLengths).isEqualTo(0 to 1) + assertThat(entries).hasSize(1) + assertThat(entries?.map { it.input }).containsExactly(1) assertThat(isUnlocked).isFalse() assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer)) } @@ -128,32 +129,59 @@ class PinBouncerViewModelTest : SysuiTestCase() { val isUnlocked by collectLastValue(authenticationInteractor.isUnlocked) val currentScene by collectLastValue(sceneInteractor.currentScene(CONTAINER_NAME)) val message by collectLastValue(bouncerViewModel.message) - val pinLengths by collectLastValue(underTest.pinLengths) - authenticationInteractor.setAuthenticationMethod(AuthenticationMethodModel.PIN(1234)) + val entries by collectLastValue(underTest.pinEntries) + authenticationInteractor.setAuthenticationMethod(AuthenticationMethodModel.Pin(1234)) authenticationInteractor.lockDevice() sceneInteractor.setCurrentScene(CONTAINER_NAME, SceneModel(SceneKey.Bouncer)) assertThat(isUnlocked).isFalse() assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer)) underTest.onShown() underTest.onPinButtonClicked(1) - assertThat(pinLengths).isEqualTo(0 to 1) + assertThat(entries).hasSize(1) underTest.onBackspaceButtonClicked() assertThat(message?.text).isEmpty() - assertThat(pinLengths).isEqualTo(1 to 0) + assertThat(entries).hasSize(0) assertThat(isUnlocked).isFalse() assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer)) } @Test + fun onPinEdit() = + testScope.runTest { + val isUnlocked by collectLastValue(authenticationInteractor.isUnlocked) + val currentScene by collectLastValue(sceneInteractor.currentScene(CONTAINER_NAME)) + val message by collectLastValue(bouncerViewModel.message) + val entries by collectLastValue(underTest.pinEntries) + authenticationInteractor.setAuthenticationMethod(AuthenticationMethodModel.Pin(1234)) + authenticationInteractor.lockDevice() + sceneInteractor.setCurrentScene(CONTAINER_NAME, SceneModel(SceneKey.Bouncer)) + assertThat(isUnlocked).isFalse() + assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer)) + underTest.onShown() + + underTest.onPinButtonClicked(1) + underTest.onPinButtonClicked(2) + underTest.onPinButtonClicked(3) + underTest.onBackspaceButtonClicked() + underTest.onBackspaceButtonClicked() + underTest.onPinButtonClicked(4) + underTest.onPinButtonClicked(5) + + assertThat(entries).hasSize(3) + assertThat(entries?.map { it.input }).containsExactly(1, 4, 5).inOrder() + assertThat(entries?.map { it.sequenceNumber }).isInStrictOrder() + } + + @Test fun onBackspaceButtonLongPressed() = testScope.runTest { val isUnlocked by collectLastValue(authenticationInteractor.isUnlocked) val currentScene by collectLastValue(sceneInteractor.currentScene(CONTAINER_NAME)) val message by collectLastValue(bouncerViewModel.message) - val pinLengths by collectLastValue(underTest.pinLengths) - authenticationInteractor.setAuthenticationMethod(AuthenticationMethodModel.PIN(1234)) + val entries by collectLastValue(underTest.pinEntries) + authenticationInteractor.setAuthenticationMethod(AuthenticationMethodModel.Pin(1234)) authenticationInteractor.lockDevice() sceneInteractor.setCurrentScene(CONTAINER_NAME, SceneModel(SceneKey.Bouncer)) assertThat(isUnlocked).isFalse() @@ -165,13 +193,9 @@ class PinBouncerViewModelTest : SysuiTestCase() { underTest.onPinButtonClicked(4) underTest.onBackspaceButtonLongPressed() - repeat(4) { index -> - assertThat(pinLengths).isEqualTo(4 - index to 3 - index) - advanceTimeBy(PinBouncerViewModel.BACKSPACE_LONG_PRESS_DELAY_MS) - } assertThat(message?.text).isEmpty() - assertThat(pinLengths).isEqualTo(1 to 0) + assertThat(entries).hasSize(0) assertThat(isUnlocked).isFalse() assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer)) } @@ -181,7 +205,7 @@ class PinBouncerViewModelTest : SysuiTestCase() { testScope.runTest { val isUnlocked by collectLastValue(authenticationInteractor.isUnlocked) val currentScene by collectLastValue(sceneInteractor.currentScene(CONTAINER_NAME)) - authenticationInteractor.setAuthenticationMethod(AuthenticationMethodModel.PIN(1234)) + authenticationInteractor.setAuthenticationMethod(AuthenticationMethodModel.Pin(1234)) authenticationInteractor.lockDevice() sceneInteractor.setCurrentScene(CONTAINER_NAME, SceneModel(SceneKey.Bouncer)) assertThat(isUnlocked).isFalse() @@ -204,8 +228,8 @@ class PinBouncerViewModelTest : SysuiTestCase() { val isUnlocked by collectLastValue(authenticationInteractor.isUnlocked) val currentScene by collectLastValue(sceneInteractor.currentScene(CONTAINER_NAME)) val message by collectLastValue(bouncerViewModel.message) - val pinLengths by collectLastValue(underTest.pinLengths) - authenticationInteractor.setAuthenticationMethod(AuthenticationMethodModel.PIN(1234)) + val entries by collectLastValue(underTest.pinEntries) + authenticationInteractor.setAuthenticationMethod(AuthenticationMethodModel.Pin(1234)) authenticationInteractor.lockDevice() sceneInteractor.setCurrentScene(CONTAINER_NAME, SceneModel(SceneKey.Bouncer)) assertThat(isUnlocked).isFalse() @@ -219,7 +243,7 @@ class PinBouncerViewModelTest : SysuiTestCase() { underTest.onAuthenticateButtonClicked() - assertThat(pinLengths).isEqualTo(0 to 0) + assertThat(entries).hasSize(0) assertThat(message?.text).isEqualTo(WRONG_PIN) assertThat(isUnlocked).isFalse() assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer)) @@ -231,8 +255,8 @@ class PinBouncerViewModelTest : SysuiTestCase() { val isUnlocked by collectLastValue(authenticationInteractor.isUnlocked) val currentScene by collectLastValue(sceneInteractor.currentScene(CONTAINER_NAME)) val message by collectLastValue(bouncerViewModel.message) - val pinLengths by collectLastValue(underTest.pinLengths) - authenticationInteractor.setAuthenticationMethod(AuthenticationMethodModel.PIN(1234)) + val entries by collectLastValue(underTest.pinEntries) + authenticationInteractor.setAuthenticationMethod(AuthenticationMethodModel.Pin(1234)) authenticationInteractor.lockDevice() sceneInteractor.setCurrentScene(CONTAINER_NAME, SceneModel(SceneKey.Bouncer)) assertThat(isUnlocked).isFalse() @@ -245,7 +269,7 @@ class PinBouncerViewModelTest : SysuiTestCase() { underTest.onPinButtonClicked(5) // PIN is now wrong! underTest.onAuthenticateButtonClicked() assertThat(message?.text).isEqualTo(WRONG_PIN) - assertThat(pinLengths).isEqualTo(0 to 0) + assertThat(entries).hasSize(0) assertThat(isUnlocked).isFalse() assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer)) @@ -266,5 +290,11 @@ class PinBouncerViewModelTest : SysuiTestCase() { private const val CONTAINER_NAME = "container1" private const val ENTER_YOUR_PIN = "Enter your pin" private const val WRONG_PIN = "Wrong pin" + + val KEY_CODE = + Correspondence.transforming<EnteredKey, Int>( + { it?.input }, + "has a eventId of", + ) } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/controls/management/ControlsProviderSelectorActivityTest.kt b/packages/SystemUI/tests/src/com/android/systemui/controls/management/ControlsProviderSelectorActivityTest.kt index b2e37ccb045b..4ba67182c32c 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/controls/management/ControlsProviderSelectorActivityTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/controls/management/ControlsProviderSelectorActivityTest.kt @@ -145,7 +145,7 @@ class ControlsProviderSelectorActivityTest : SysuiTestCase() { assertThat(activityRule.activity.lastStartedActivity?.component?.className) .isEqualTo(ControlsFavoritingActivity::class.java.name) - assertThat(activityRule.activity.triedToFinish).isTrue() + assertThat(activityRule.activity.triedToFinish).isFalse() } @Test diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/CustomizationProviderTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/CustomizationProviderTest.kt index 8ee7d3e86265..c9470bb41ba5 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/CustomizationProviderTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/CustomizationProviderTest.kt @@ -34,6 +34,7 @@ import com.android.systemui.R import com.android.systemui.SystemUIAppComponentFactoryBase import com.android.systemui.SysuiTestCase import com.android.systemui.animation.DialogLaunchAnimator +import com.android.systemui.common.ui.data.repository.FakeConfigurationRepository import com.android.systemui.dock.DockManagerFake import com.android.systemui.flags.FakeFeatureFlags import com.android.systemui.flags.Flags @@ -185,6 +186,7 @@ class CustomizationProviderTest : SysuiTestCase() { commandQueue = commandQueue, featureFlags = featureFlags, bouncerRepository = FakeKeyguardBouncerRepository(), + configurationRepository = FakeConfigurationRepository(), ), registry = mock(), lockPatternUtils = lockPatternUtils, 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 78a65a8473db..eb970221388b 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ResourceTrimmerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ResourceTrimmerTest.kt @@ -7,11 +7,9 @@ import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.flags.FakeFeatureFlags import com.android.systemui.flags.Flags -import com.android.systemui.keyguard.data.repository.FakeCommandQueue -import com.android.systemui.keyguard.data.repository.FakeKeyguardBouncerRepository 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.KeyguardInteractorFactory import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor import com.android.systemui.keyguard.shared.model.KeyguardState import com.android.systemui.keyguard.shared.model.TransitionStep @@ -60,13 +58,12 @@ class ResourceTrimmerTest : SysuiTestCase() { keyguardRepository.setDozeAmount(0f) keyguardRepository.setKeyguardGoingAway(false) - val keyguardInteractor = - KeyguardInteractor( - keyguardRepository, - FakeCommandQueue(), - featureFlags, - FakeKeyguardBouncerRepository() + val withDeps = + KeyguardInteractorFactory.create( + repository = keyguardRepository, + featureFlags = featureFlags, ) + val keyguardInteractor = withDeps.keyguardInteractor resourceTrimmer = ResourceTrimmer( keyguardInteractor, diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/DeviceEntryFaceAuthRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/DeviceEntryFaceAuthRepositoryTest.kt index e61620beeff3..3ea74e582229 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/DeviceEntryFaceAuthRepositoryTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/DeviceEntryFaceAuthRepositoryTest.kt @@ -47,6 +47,7 @@ import com.android.systemui.flags.FakeFeatureFlags import com.android.systemui.flags.Flags.FACE_AUTH_REFACTOR import com.android.systemui.keyguard.domain.interactor.AlternateBouncerInteractor import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor +import com.android.systemui.keyguard.domain.interactor.KeyguardInteractorFactory import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor import com.android.systemui.keyguard.shared.model.AuthenticationStatus import com.android.systemui.keyguard.shared.model.DetectionStatus @@ -159,17 +160,16 @@ class DeviceEntryFaceAuthRepositoryTest : SysuiTestCase() { biometricSettingsRepository = FakeBiometricSettingsRepository() deviceEntryFingerprintAuthRepository = FakeDeviceEntryFingerprintAuthRepository() trustRepository = FakeTrustRepository() - keyguardRepository = FakeKeyguardRepository() - bouncerRepository = FakeKeyguardBouncerRepository() featureFlags = FakeFeatureFlags().apply { set(FACE_AUTH_REFACTOR, true) } - fakeCommandQueue = FakeCommandQueue() - keyguardInteractor = - KeyguardInteractor( - keyguardRepository, - fakeCommandQueue, - featureFlags, - bouncerRepository + val withDeps = + KeyguardInteractorFactory.create( + featureFlags = featureFlags, ) + keyguardInteractor = withDeps.keyguardInteractor + keyguardRepository = withDeps.repository + bouncerRepository = withDeps.bouncerRepository + fakeCommandQueue = withDeps.commandQueue + alternateBouncerInteractor = AlternateBouncerInteractor( bouncerRepository = bouncerRepository, diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractorTest.kt index 0d695aa231cc..a4f19b415061 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractorTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractorTest.kt @@ -22,6 +22,7 @@ import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.RoboPilotTest import com.android.systemui.SysuiTestCase +import com.android.systemui.common.ui.data.repository.FakeConfigurationRepository import com.android.systemui.coroutines.collectLastValue import com.android.systemui.flags.FakeFeatureFlags import com.android.systemui.flags.Flags.FACE_AUTH_REFACTOR @@ -50,6 +51,7 @@ class KeyguardInteractorTest : SysuiTestCase() { private lateinit var underTest: KeyguardInteractor private lateinit var repository: FakeKeyguardRepository private lateinit var bouncerRepository: FakeKeyguardBouncerRepository + private lateinit var configurationRepository: FakeConfigurationRepository @Before fun setUp() { @@ -59,12 +61,14 @@ class KeyguardInteractorTest : SysuiTestCase() { testScope = TestScope() repository = FakeKeyguardRepository() bouncerRepository = FakeKeyguardBouncerRepository() + configurationRepository = FakeConfigurationRepository() underTest = KeyguardInteractor( repository, commandQueue, featureFlags, bouncerRepository, + configurationRepository, ) } 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 fb21847cd4d1..8f6bbc9d48d7 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 @@ -38,8 +38,6 @@ import com.android.systemui.keyguard.data.quickaffordance.KeyguardQuickAffordanc import com.android.systemui.keyguard.data.quickaffordance.KeyguardQuickAffordanceLegacySettingSyncer import com.android.systemui.keyguard.data.quickaffordance.KeyguardQuickAffordanceLocalUserSelectionManager import com.android.systemui.keyguard.data.quickaffordance.KeyguardQuickAffordanceRemoteUserSelectionManager -import com.android.systemui.keyguard.data.repository.FakeKeyguardBouncerRepository -import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository import com.android.systemui.keyguard.data.repository.KeyguardQuickAffordanceRepository import com.android.systemui.keyguard.domain.quickaffordance.FakeKeyguardQuickAffordanceRegistry import com.android.systemui.keyguard.shared.quickaffordance.KeyguardQuickAffordancePosition @@ -48,7 +46,6 @@ import com.android.systemui.plugins.ActivityStarter import com.android.systemui.settings.FakeUserTracker import com.android.systemui.settings.UserFileManager import com.android.systemui.settings.UserTracker -import com.android.systemui.statusbar.CommandQueue import com.android.systemui.statusbar.policy.KeyguardStateController import com.android.systemui.util.FakeSharedPreferences import com.android.systemui.util.mockito.any @@ -225,7 +222,6 @@ class KeyguardQuickAffordanceInteractorParameterizedTest : SysuiTestCase() { @Mock private lateinit var animationController: ActivityLaunchAnimator.Controller @Mock private lateinit var expandable: Expandable @Mock private lateinit var launchAnimator: DialogLaunchAnimator - @Mock private lateinit var commandQueue: CommandQueue @Mock private lateinit var devicePolicyManager: DevicePolicyManager @Mock private lateinit var logger: KeyguardQuickAffordancesMetricsLogger @@ -309,12 +305,10 @@ class KeyguardQuickAffordanceInteractorParameterizedTest : SysuiTestCase() { underTest = KeyguardQuickAffordanceInteractor( keyguardInteractor = - KeyguardInteractor( - repository = FakeKeyguardRepository(), - commandQueue = commandQueue, - featureFlags = featureFlags, - bouncerRepository = FakeKeyguardBouncerRepository(), - ), + KeyguardInteractorFactory.create( + featureFlags = featureFlags, + ) + .keyguardInteractor, registry = FakeKeyguardQuickAffordanceRegistry( mapOf( @@ -368,11 +362,11 @@ class KeyguardQuickAffordanceInteractorParameterizedTest : SysuiTestCase() { KeyguardQuickAffordanceConfig.OnTriggeredResult.Handled } - underTest.onQuickAffordanceTriggered( - configKey = BuiltInKeyguardQuickAffordanceKeys.HOME_CONTROLS, - expandable = expandable, - slotId = "", - ) + underTest.onQuickAffordanceTriggered( + configKey = BuiltInKeyguardQuickAffordanceKeys.HOME_CONTROLS, + expandable = expandable, + slotId = "", + ) if (startActivity) { if (needsToUnlockFirst) { diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorTest.kt index 895c1cd5a1a1..1c0b06cd85b9 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorTest.kt @@ -40,7 +40,6 @@ import com.android.systemui.keyguard.data.quickaffordance.KeyguardQuickAffordanc import com.android.systemui.keyguard.data.quickaffordance.KeyguardQuickAffordanceLegacySettingSyncer import com.android.systemui.keyguard.data.quickaffordance.KeyguardQuickAffordanceLocalUserSelectionManager import com.android.systemui.keyguard.data.quickaffordance.KeyguardQuickAffordanceRemoteUserSelectionManager -import com.android.systemui.keyguard.data.repository.FakeKeyguardBouncerRepository import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository import com.android.systemui.keyguard.data.repository.KeyguardQuickAffordanceRepository import com.android.systemui.keyguard.domain.model.KeyguardQuickAffordanceModel @@ -53,7 +52,6 @@ import com.android.systemui.plugins.ActivityStarter import com.android.systemui.settings.UserFileManager import com.android.systemui.settings.UserTracker import com.android.systemui.shared.keyguard.shared.model.KeyguardQuickAffordanceSlots -import com.android.systemui.statusbar.CommandQueue import com.android.systemui.statusbar.policy.KeyguardStateController import com.android.systemui.util.FakeSharedPreferences import com.android.systemui.util.mockito.mock @@ -84,7 +82,6 @@ class KeyguardQuickAffordanceInteractorTest : SysuiTestCase() { @Mock private lateinit var userTracker: UserTracker @Mock private lateinit var activityStarter: ActivityStarter @Mock private lateinit var launchAnimator: DialogLaunchAnimator - @Mock private lateinit var commandQueue: CommandQueue @Mock private lateinit var devicePolicyManager: DevicePolicyManager @Mock private lateinit var logger: KeyguardQuickAffordancesMetricsLogger @@ -168,15 +165,14 @@ class KeyguardQuickAffordanceInteractorTest : SysuiTestCase() { set(Flags.FACE_AUTH_REFACTOR, true) } + val withDeps = + KeyguardInteractorFactory.create( + featureFlags = featureFlags, + repository = repository, + ) underTest = KeyguardQuickAffordanceInteractor( - keyguardInteractor = - KeyguardInteractor( - repository = repository, - commandQueue = commandQueue, - featureFlags = featureFlags, - bouncerRepository = FakeKeyguardBouncerRepository(), - ), + keyguardInteractor = withDeps.keyguardInteractor, registry = FakeKeyguardQuickAffordanceRegistry( mapOf( diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionScenariosTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionScenariosTest.kt index 603f199b468b..c53d4305f226 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionScenariosTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionScenariosTest.kt @@ -21,7 +21,6 @@ import com.android.keyguard.KeyguardSecurityModel import com.android.keyguard.KeyguardSecurityModel.SecurityMode.PIN import com.android.systemui.SysuiTestCase import com.android.systemui.flags.FakeFeatureFlags -import com.android.systemui.flags.FeatureFlags import com.android.systemui.flags.Flags import com.android.systemui.keyguard.data.repository.FakeKeyguardBouncerRepository import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository @@ -39,7 +38,6 @@ import com.android.systemui.keyguard.shared.model.WakefulnessModel import com.android.systemui.keyguard.shared.model.WakefulnessState import com.android.systemui.shade.data.repository.FakeShadeRepository import com.android.systemui.shade.data.repository.ShadeRepository -import com.android.systemui.statusbar.CommandQueue import com.android.systemui.util.mockito.any import com.android.systemui.util.mockito.whenever import com.android.systemui.util.mockito.withArgCaptor @@ -74,10 +72,10 @@ class KeyguardTransitionScenariosTest : SysuiTestCase() { private lateinit var bouncerRepository: FakeKeyguardBouncerRepository private lateinit var shadeRepository: ShadeRepository private lateinit var transitionRepository: FakeKeyguardTransitionRepository + private lateinit var featureFlags: FakeFeatureFlags // Used to verify transition requests for test output @Mock private lateinit var mockTransitionRepository: KeyguardTransitionRepository - @Mock private lateinit var commandQueue: CommandQueue @Mock private lateinit var keyguardSecurityModel: KeyguardSecurityModel private lateinit var fromLockscreenTransitionInteractor: FromLockscreenTransitionInteractor @@ -103,11 +101,11 @@ class KeyguardTransitionScenariosTest : SysuiTestCase() { whenever(keyguardSecurityModel.getSecurityMode(anyInt())).thenReturn(PIN) - val featureFlags = FakeFeatureFlags().apply { set(Flags.FACE_AUTH_REFACTOR, true) } + featureFlags = FakeFeatureFlags().apply { set(Flags.FACE_AUTH_REFACTOR, true) } fromLockscreenTransitionInteractor = FromLockscreenTransitionInteractor( scope = testScope, - keyguardInteractor = createKeyguardInteractor(featureFlags), + keyguardInteractor = createKeyguardInteractor(), shadeRepository = shadeRepository, keyguardTransitionRepository = mockTransitionRepository, keyguardTransitionInteractor = KeyguardTransitionInteractor(transitionRepository), @@ -117,7 +115,7 @@ class KeyguardTransitionScenariosTest : SysuiTestCase() { fromDreamingTransitionInteractor = FromDreamingTransitionInteractor( scope = testScope, - keyguardInteractor = createKeyguardInteractor(featureFlags), + keyguardInteractor = createKeyguardInteractor(), keyguardTransitionRepository = mockTransitionRepository, keyguardTransitionInteractor = KeyguardTransitionInteractor(transitionRepository), ) @@ -126,7 +124,7 @@ class KeyguardTransitionScenariosTest : SysuiTestCase() { fromAodTransitionInteractor = FromAodTransitionInteractor( scope = testScope, - keyguardInteractor = createKeyguardInteractor(featureFlags), + keyguardInteractor = createKeyguardInteractor(), keyguardTransitionRepository = mockTransitionRepository, keyguardTransitionInteractor = KeyguardTransitionInteractor(transitionRepository), ) @@ -135,7 +133,7 @@ class KeyguardTransitionScenariosTest : SysuiTestCase() { fromGoneTransitionInteractor = FromGoneTransitionInteractor( scope = testScope, - keyguardInteractor = createKeyguardInteractor(featureFlags), + keyguardInteractor = createKeyguardInteractor(), keyguardTransitionRepository = mockTransitionRepository, keyguardTransitionInteractor = KeyguardTransitionInteractor(transitionRepository), ) @@ -144,7 +142,7 @@ class KeyguardTransitionScenariosTest : SysuiTestCase() { fromDozingTransitionInteractor = FromDozingTransitionInteractor( scope = testScope, - keyguardInteractor = createKeyguardInteractor(featureFlags), + keyguardInteractor = createKeyguardInteractor(), keyguardTransitionRepository = mockTransitionRepository, keyguardTransitionInteractor = KeyguardTransitionInteractor(transitionRepository), ) @@ -153,7 +151,7 @@ class KeyguardTransitionScenariosTest : SysuiTestCase() { fromOccludedTransitionInteractor = FromOccludedTransitionInteractor( scope = testScope, - keyguardInteractor = createKeyguardInteractor(featureFlags), + keyguardInteractor = createKeyguardInteractor(), keyguardTransitionRepository = mockTransitionRepository, keyguardTransitionInteractor = KeyguardTransitionInteractor(transitionRepository), ) @@ -162,7 +160,7 @@ class KeyguardTransitionScenariosTest : SysuiTestCase() { fromAlternateBouncerTransitionInteractor = FromAlternateBouncerTransitionInteractor( scope = testScope, - keyguardInteractor = createKeyguardInteractor(featureFlags), + keyguardInteractor = createKeyguardInteractor(), keyguardTransitionRepository = mockTransitionRepository, keyguardTransitionInteractor = KeyguardTransitionInteractor(transitionRepository), ) @@ -171,7 +169,7 @@ class KeyguardTransitionScenariosTest : SysuiTestCase() { fromPrimaryBouncerTransitionInteractor = FromPrimaryBouncerTransitionInteractor( scope = testScope, - keyguardInteractor = createKeyguardInteractor(featureFlags), + keyguardInteractor = createKeyguardInteractor(), keyguardTransitionRepository = mockTransitionRepository, keyguardTransitionInteractor = KeyguardTransitionInteractor(transitionRepository), keyguardSecurityModel = keyguardSecurityModel, @@ -882,13 +880,13 @@ class KeyguardTransitionScenariosTest : SysuiTestCase() { WakeSleepReason.OTHER ) - private fun createKeyguardInteractor(featureFlags: FeatureFlags): KeyguardInteractor { - return KeyguardInteractor( - keyguardRepository, - commandQueue, - featureFlags, - bouncerRepository, - ) + private fun createKeyguardInteractor(): KeyguardInteractor { + return KeyguardInteractorFactory.create( + featureFlags = featureFlags, + repository = keyguardRepository, + bouncerRepository = bouncerRepository, + ) + .keyguardInteractor } private suspend fun TestScope.runTransition(from: KeyguardState, to: KeyguardState) { diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/LockscreenSceneInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/LockscreenSceneInteractorTest.kt index d622f1c30816..65781c497944 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/LockscreenSceneInteractorTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/LockscreenSceneInteractorTest.kt @@ -95,7 +95,7 @@ class LockscreenSceneInteractorTest : SysuiTestCase() { testScope.runTest { val currentScene by collectLastValue(sceneInteractor.currentScene(CONTAINER_1)) authenticationInteractor.lockDevice() - authenticationInteractor.setAuthenticationMethod(AuthenticationMethodModel.PIN(1234)) + authenticationInteractor.setAuthenticationMethod(AuthenticationMethodModel.Pin(1234)) assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Lockscreen)) underTest.dismissLockscreen() @@ -108,7 +108,7 @@ class LockscreenSceneInteractorTest : SysuiTestCase() { testScope.runTest { val currentScene by collectLastValue(sceneInteractor.currentScene(CONTAINER_1)) authenticationInteractor.unlockDevice() - authenticationInteractor.setAuthenticationMethod(AuthenticationMethodModel.PIN(1234)) + authenticationInteractor.setAuthenticationMethod(AuthenticationMethodModel.Pin(1234)) assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Lockscreen)) underTest.dismissLockscreen() @@ -195,7 +195,7 @@ class LockscreenSceneInteractorTest : SysuiTestCase() { testScope.runTest { val isUnlocked by collectLastValue(authenticationInteractor.isUnlocked) sceneInteractor.setCurrentScene(CONTAINER_1, SceneModel(SceneKey.Lockscreen)) - authenticationInteractor.setAuthenticationMethod(AuthenticationMethodModel.PIN(1234)) + authenticationInteractor.setAuthenticationMethod(AuthenticationMethodModel.Pin(1234)) assertThat(isUnlocked).isFalse() sceneInteractor.setCurrentScene(CONTAINER_1, SceneModel(SceneKey.Gone)) diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBottomAreaViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBottomAreaViewModelTest.kt index 8a36dbc86e81..a493b1ddb93d 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBottomAreaViewModelTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBottomAreaViewModelTest.kt @@ -39,12 +39,11 @@ import com.android.systemui.keyguard.data.quickaffordance.KeyguardQuickAffordanc import com.android.systemui.keyguard.data.quickaffordance.KeyguardQuickAffordanceLegacySettingSyncer import com.android.systemui.keyguard.data.quickaffordance.KeyguardQuickAffordanceLocalUserSelectionManager import com.android.systemui.keyguard.data.quickaffordance.KeyguardQuickAffordanceRemoteUserSelectionManager -import com.android.systemui.keyguard.data.repository.FakeKeyguardBouncerRepository import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository import com.android.systemui.keyguard.data.repository.KeyguardQuickAffordanceRepository import com.android.systemui.keyguard.domain.interactor.KeyguardBottomAreaInteractor -import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor +import com.android.systemui.keyguard.domain.interactor.KeyguardInteractorFactory import com.android.systemui.keyguard.domain.interactor.KeyguardLongPressInteractor import com.android.systemui.keyguard.domain.interactor.KeyguardQuickAffordanceInteractor import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor @@ -56,7 +55,6 @@ import com.android.systemui.plugins.ActivityStarter import com.android.systemui.settings.UserFileManager import com.android.systemui.settings.UserTracker import com.android.systemui.shared.keyguard.shared.model.KeyguardQuickAffordanceSlots -import com.android.systemui.statusbar.CommandQueue import com.android.systemui.statusbar.policy.AccessibilityManagerWrapper import com.android.systemui.statusbar.policy.KeyguardStateController import com.android.systemui.util.FakeSharedPreferences @@ -95,7 +93,6 @@ class KeyguardBottomAreaViewModelTest : SysuiTestCase() { @Mock private lateinit var userTracker: UserTracker @Mock private lateinit var activityStarter: ActivityStarter @Mock private lateinit var launchAnimator: DialogLaunchAnimator - @Mock private lateinit var commandQueue: CommandQueue @Mock private lateinit var devicePolicyManager: DevicePolicyManager @Mock private lateinit var logger: KeyguardQuickAffordancesMetricsLogger @Mock private lateinit var broadcastDispatcher: BroadcastDispatcher @@ -140,7 +137,6 @@ class KeyguardBottomAreaViewModelTest : SysuiTestCase() { ), ), ) - repository = FakeKeyguardRepository() val featureFlags = FakeFeatureFlags().apply { set(Flags.CUSTOMIZABLE_LOCK_SCREEN_QUICK_AFFORDANCES, false) @@ -149,13 +145,10 @@ class KeyguardBottomAreaViewModelTest : SysuiTestCase() { set(Flags.LOCK_SCREEN_LONG_PRESS_DIRECT_TO_WPP, false) } - val keyguardInteractor = - KeyguardInteractor( - repository = repository, - commandQueue = commandQueue, - featureFlags = featureFlags, - bouncerRepository = FakeKeyguardBouncerRepository(), - ) + val withDeps = KeyguardInteractorFactory.create(featureFlags = featureFlags) + val keyguardInteractor = withDeps.keyguardInteractor + repository = withDeps.repository + whenever(userTracker.userHandle).thenReturn(mock()) whenever(userTracker.userId).thenReturn(10) whenever(lockPatternUtils.getStrongAuthForUser(anyInt())) @@ -550,91 +543,6 @@ class KeyguardBottomAreaViewModelTest : SysuiTestCase() { } @Test - fun isIndicationAreaPadded() = - testScope.runTest { - repository.setKeyguardShowing(true) - val value = collectLastValue(underTest.isIndicationAreaPadded) - - assertThat(value()).isFalse() - setUpQuickAffordanceModel( - position = KeyguardQuickAffordancePosition.BOTTOM_START, - testConfig = - TestConfig( - isVisible = true, - isClickable = true, - icon = mock(), - canShowWhileLocked = true, - slotId = KeyguardQuickAffordancePosition.BOTTOM_START.toSlotId(), - ) - ) - assertThat(value()).isTrue() - setUpQuickAffordanceModel( - position = KeyguardQuickAffordancePosition.BOTTOM_END, - testConfig = - TestConfig( - isVisible = true, - isClickable = true, - icon = mock(), - canShowWhileLocked = false, - slotId = KeyguardQuickAffordancePosition.BOTTOM_END.toSlotId(), - ) - ) - assertThat(value()).isTrue() - setUpQuickAffordanceModel( - position = KeyguardQuickAffordancePosition.BOTTOM_START, - testConfig = - TestConfig( - isVisible = false, - slotId = KeyguardQuickAffordancePosition.BOTTOM_START.toSlotId(), - ) - ) - assertThat(value()).isTrue() - setUpQuickAffordanceModel( - position = KeyguardQuickAffordancePosition.BOTTOM_END, - testConfig = - TestConfig( - isVisible = false, - slotId = KeyguardQuickAffordancePosition.BOTTOM_END.toSlotId(), - ) - ) - assertThat(value()).isFalse() - } - - @Test - fun indicationAreaTranslationX() = - testScope.runTest { - val value = collectLastValue(underTest.indicationAreaTranslationX) - - assertThat(value()).isEqualTo(0f) - repository.setClockPosition(100, 100) - assertThat(value()).isEqualTo(100f) - repository.setClockPosition(200, 100) - assertThat(value()).isEqualTo(200f) - repository.setClockPosition(200, 200) - assertThat(value()).isEqualTo(200f) - repository.setClockPosition(300, 100) - assertThat(value()).isEqualTo(300f) - } - - @Test - fun indicationAreaTranslationY() = - testScope.runTest { - val value = - collectLastValue(underTest.indicationAreaTranslationY(DEFAULT_BURN_IN_OFFSET)) - - // Negative 0 - apparently there's a difference in floating point arithmetic - FML - assertThat(value()).isEqualTo(-0f) - val expected1 = setDozeAmountAndCalculateExpectedTranslationY(0.1f) - assertThat(value()).isEqualTo(expected1) - val expected2 = setDozeAmountAndCalculateExpectedTranslationY(0.2f) - assertThat(value()).isEqualTo(expected2) - val expected3 = setDozeAmountAndCalculateExpectedTranslationY(0.5f) - assertThat(value()).isEqualTo(expected3) - val expected4 = setDozeAmountAndCalculateExpectedTranslationY(1f) - assertThat(value()).isEqualTo(expected4) - } - - @Test fun isClickable_trueWhenAlphaAtThreshold() = testScope.runTest { repository.setKeyguardShowing(true) @@ -757,11 +665,6 @@ class KeyguardBottomAreaViewModelTest : SysuiTestCase() { ) } - private fun setDozeAmountAndCalculateExpectedTranslationY(dozeAmount: Float): Float { - repository.setDozeAmount(dozeAmount) - return dozeAmount * (RETURNED_BURN_IN_OFFSET - DEFAULT_BURN_IN_OFFSET) - } - private suspend fun setUpQuickAffordanceModel( position: KeyguardQuickAffordancePosition, testConfig: TestConfig, diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardIndicationAreaViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardIndicationAreaViewModelTest.kt new file mode 100644 index 000000000000..dff0f2909126 --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardIndicationAreaViewModelTest.kt @@ -0,0 +1,160 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.keyguard.ui.viewmodel + +import androidx.test.filters.SmallTest +import com.android.systemui.SysuiTestCase +import com.android.systemui.coroutines.collectLastValue +import com.android.systemui.doze.util.BurnInHelperWrapper +import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository +import com.android.systemui.keyguard.domain.interactor.KeyguardBottomAreaInteractor +import com.android.systemui.keyguard.domain.interactor.KeyguardInteractorFactory +import com.android.systemui.keyguard.shared.quickaffordance.KeyguardQuickAffordancePosition +import com.android.systemui.util.mockito.any +import com.android.systemui.util.mockito.mock +import com.android.systemui.util.mockito.whenever +import com.google.common.truth.Truth.assertThat +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.flow.MutableStateFlow +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.Mock +import org.mockito.MockitoAnnotations + +@OptIn(ExperimentalCoroutinesApi::class) +@SmallTest +@RunWith(JUnit4::class) +class KeyguardIndicationAreaViewModelTest : SysuiTestCase() { + + @Mock private lateinit var burnInHelperWrapper: BurnInHelperWrapper + + private lateinit var underTest: KeyguardIndicationAreaViewModel + private lateinit var repository: FakeKeyguardRepository + + private val startButtonFlow = + MutableStateFlow<KeyguardQuickAffordanceViewModel>( + KeyguardQuickAffordanceViewModel( + slotId = KeyguardQuickAffordancePosition.BOTTOM_START.toSlotId() + ) + ) + private val endButtonFlow = + MutableStateFlow<KeyguardQuickAffordanceViewModel>( + KeyguardQuickAffordanceViewModel( + slotId = KeyguardQuickAffordancePosition.BOTTOM_END.toSlotId() + ) + ) + private val alphaFlow = MutableStateFlow<Float>(1f) + + @Before + fun setUp() { + MockitoAnnotations.initMocks(this) + whenever(burnInHelperWrapper.burnInOffset(anyInt(), any())) + .thenReturn(RETURNED_BURN_IN_OFFSET) + + val withDeps = KeyguardInteractorFactory.create() + val keyguardInteractor = withDeps.keyguardInteractor + repository = withDeps.repository + + val bottomAreaViewModel: KeyguardBottomAreaViewModel = mock() + whenever(bottomAreaViewModel.startButton).thenReturn(startButtonFlow) + whenever(bottomAreaViewModel.endButton).thenReturn(endButtonFlow) + whenever(bottomAreaViewModel.alpha).thenReturn(alphaFlow) + underTest = + KeyguardIndicationAreaViewModel( + keyguardInteractor = keyguardInteractor, + bottomAreaInteractor = KeyguardBottomAreaInteractor(repository = repository), + keyguardBottomAreaViewModel = bottomAreaViewModel, + burnInHelperWrapper = burnInHelperWrapper, + ) + } + + @Test + fun alpha() = runTest { + val value = collectLastValue(underTest.alpha) + + assertThat(value()).isEqualTo(1f) + alphaFlow.value = 0.1f + assertThat(value()).isEqualTo(0.1f) + alphaFlow.value = 0.5f + assertThat(value()).isEqualTo(0.5f) + alphaFlow.value = 0.2f + assertThat(value()).isEqualTo(0.2f) + alphaFlow.value = 0f + assertThat(value()).isEqualTo(0f) + } + + @Test + fun isIndicationAreaPadded() = runTest { + repository.setKeyguardShowing(true) + val value = collectLastValue(underTest.isIndicationAreaPadded) + + assertThat(value()).isFalse() + startButtonFlow.value = startButtonFlow.value.copy(isVisible = true) + assertThat(value()).isTrue() + endButtonFlow.value = endButtonFlow.value.copy(isVisible = true) + assertThat(value()).isTrue() + startButtonFlow.value = startButtonFlow.value.copy(isVisible = false) + assertThat(value()).isTrue() + endButtonFlow.value = endButtonFlow.value.copy(isVisible = false) + assertThat(value()).isFalse() + } + + @Test + fun indicationAreaTranslationX() = runTest { + val value = collectLastValue(underTest.indicationAreaTranslationX) + + assertThat(value()).isEqualTo(0f) + repository.setClockPosition(100, 100) + assertThat(value()).isEqualTo(100f) + repository.setClockPosition(200, 100) + assertThat(value()).isEqualTo(200f) + repository.setClockPosition(200, 200) + assertThat(value()).isEqualTo(200f) + repository.setClockPosition(300, 100) + assertThat(value()).isEqualTo(300f) + } + + @Test + fun indicationAreaTranslationY() = runTest { + val value = collectLastValue(underTest.indicationAreaTranslationY(DEFAULT_BURN_IN_OFFSET)) + + // Negative 0 - apparently there's a difference in floating point arithmetic - FML + assertThat(value()).isEqualTo(-0f) + val expected1 = setDozeAmountAndCalculateExpectedTranslationY(0.1f) + assertThat(value()).isEqualTo(expected1) + val expected2 = setDozeAmountAndCalculateExpectedTranslationY(0.2f) + assertThat(value()).isEqualTo(expected2) + val expected3 = setDozeAmountAndCalculateExpectedTranslationY(0.5f) + assertThat(value()).isEqualTo(expected3) + val expected4 = setDozeAmountAndCalculateExpectedTranslationY(1f) + assertThat(value()).isEqualTo(expected4) + } + + private fun setDozeAmountAndCalculateExpectedTranslationY(dozeAmount: Float): Float { + repository.setDozeAmount(dozeAmount) + return dozeAmount * (RETURNED_BURN_IN_OFFSET - DEFAULT_BURN_IN_OFFSET) + } + + companion object { + private const val DEFAULT_BURN_IN_OFFSET = 5 + private const val RETURNED_BURN_IN_OFFSET = 3 + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenSceneViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenSceneViewModelTest.kt index 8ba3f0f57eed..f0ea0077596a 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenSceneViewModelTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenSceneViewModelTest.kt @@ -109,7 +109,7 @@ class LockscreenSceneViewModelTest : SysuiTestCase() { fun upTransitionSceneKey_swipeToUnlockedNotEnabled_bouncer() = testScope.runTest { val upTransitionSceneKey by collectLastValue(underTest.upDestinationSceneKey) - authenticationInteractor.setAuthenticationMethod(AuthenticationMethodModel.PIN(1234)) + authenticationInteractor.setAuthenticationMethod(AuthenticationMethodModel.Pin(1234)) authenticationInteractor.lockDevice() assertThat(upTransitionSceneKey).isEqualTo(SceneKey.Bouncer) @@ -119,7 +119,7 @@ class LockscreenSceneViewModelTest : SysuiTestCase() { fun onLockButtonClicked_deviceLockedSecurely_switchesToBouncer() = testScope.runTest { val currentScene by collectLastValue(sceneInteractor.currentScene(CONTAINER_1)) - authenticationInteractor.setAuthenticationMethod(AuthenticationMethodModel.PIN(1234)) + authenticationInteractor.setAuthenticationMethod(AuthenticationMethodModel.Pin(1234)) authenticationInteractor.lockDevice() runCurrent() @@ -132,7 +132,7 @@ class LockscreenSceneViewModelTest : SysuiTestCase() { fun onContentClicked_deviceUnlocked_switchesToGone() = testScope.runTest { val currentScene by collectLastValue(sceneInteractor.currentScene(CONTAINER_1)) - authenticationInteractor.setAuthenticationMethod(AuthenticationMethodModel.PIN(1234)) + authenticationInteractor.setAuthenticationMethod(AuthenticationMethodModel.Pin(1234)) authenticationInteractor.unlockDevice() runCurrent() @@ -145,7 +145,7 @@ class LockscreenSceneViewModelTest : SysuiTestCase() { fun onContentClicked_deviceLockedSecurely_switchesToBouncer() = testScope.runTest { val currentScene by collectLastValue(sceneInteractor.currentScene(CONTAINER_1)) - authenticationInteractor.setAuthenticationMethod(AuthenticationMethodModel.PIN(1234)) + authenticationInteractor.setAuthenticationMethod(AuthenticationMethodModel.Pin(1234)) authenticationInteractor.lockDevice() runCurrent() @@ -158,7 +158,7 @@ class LockscreenSceneViewModelTest : SysuiTestCase() { fun onLockButtonClicked_deviceUnlocked_switchesToGone() = testScope.runTest { val currentScene by collectLastValue(sceneInteractor.currentScene(CONTAINER_1)) - authenticationInteractor.setAuthenticationMethod(AuthenticationMethodModel.PIN(1234)) + authenticationInteractor.setAuthenticationMethod(AuthenticationMethodModel.Pin(1234)) authenticationInteractor.unlockDevice() runCurrent() diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsSceneViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsSceneViewModelTest.kt index 105387d49bd4..05a16994e021 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsSceneViewModelTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsSceneViewModelTest.kt @@ -70,7 +70,7 @@ class QuickSettingsSceneViewModelTest : SysuiTestCase() { fun onContentClicked_deviceUnlocked_switchesToGone() = testScope.runTest { val currentScene by collectLastValue(sceneInteractor.currentScene(CONTAINER_1)) - authenticationInteractor.setAuthenticationMethod(AuthenticationMethodModel.PIN(1234)) + authenticationInteractor.setAuthenticationMethod(AuthenticationMethodModel.Pin(1234)) authenticationInteractor.unlockDevice() runCurrent() @@ -83,7 +83,7 @@ class QuickSettingsSceneViewModelTest : SysuiTestCase() { fun onContentClicked_deviceLockedSecurely_switchesToBouncer() = testScope.runTest { val currentScene by collectLastValue(sceneInteractor.currentScene(CONTAINER_1)) - authenticationInteractor.setAuthenticationMethod(AuthenticationMethodModel.PIN(1234)) + authenticationInteractor.setAuthenticationMethod(AuthenticationMethodModel.Pin(1234)) authenticationInteractor.lockDevice() runCurrent() diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java index fe89a143e880..41351e53e0c8 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java @@ -89,12 +89,13 @@ import com.android.systemui.flags.FeatureFlags; import com.android.systemui.fragments.FragmentHostManager; import com.android.systemui.fragments.FragmentService; import com.android.systemui.keyguard.KeyguardUnlockAnimationController; -import com.android.systemui.keyguard.data.repository.FakeKeyguardBouncerRepository; +import com.android.systemui.keyguard.KeyguardViewConfigurator; import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository; import com.android.systemui.keyguard.domain.interactor.AlternateBouncerInteractor; import com.android.systemui.keyguard.domain.interactor.KeyguardBottomAreaInteractor; import com.android.systemui.keyguard.domain.interactor.KeyguardFaceAuthInteractor; import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor; +import com.android.systemui.keyguard.domain.interactor.KeyguardInteractorFactory; import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor; import com.android.systemui.keyguard.ui.viewmodel.DreamingToLockscreenTransitionViewModel; import com.android.systemui.keyguard.ui.viewmodel.GoneToDreamingTransitionViewModel; @@ -253,6 +254,7 @@ public class NotificationPanelViewControllerBaseTest extends SysuiTestCase { @Mock protected UserManager mUserManager; @Mock protected UiEventLogger mUiEventLogger; @Mock protected LockIconViewController mLockIconViewController; + @Mock protected KeyguardViewConfigurator mKeyguardViewConfigurator; @Mock protected KeyguardMediaController mKeyguardMediaController; @Mock protected NavigationModeController mNavigationModeController; @Mock protected NavigationBarController mNavigationBarController; @@ -342,8 +344,8 @@ public class NotificationPanelViewControllerBaseTest extends SysuiTestCase { mMainDispatcher = getMainDispatcher(); mKeyguardBottomAreaInteractor = new KeyguardBottomAreaInteractor( new FakeKeyguardRepository()); - mKeyguardInteractor = new KeyguardInteractor(new FakeKeyguardRepository(), mCommandQueue, - mFeatureFlags, new FakeKeyguardBouncerRepository()); + + mKeyguardInteractor = KeyguardInteractorFactory.create().getKeyguardInteractor(); SystemClock systemClock = new FakeSystemClock(); mStatusBarStateController = new StatusBarStateControllerImpl(mUiEventLogger, mDumpManager, mInteractionJankMonitor, mShadeExpansionStateManager); @@ -611,6 +613,7 @@ public class NotificationPanelViewControllerBaseTest extends SysuiTestCase { mKeyuardLongPressViewModel, mKeyguardInteractor, mActivityStarter, + mKeyguardViewConfigurator, mKeyguardFaceAuthInteractor); mNotificationPanelViewController.initDependencies( mCentralSurfaces, diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneViewModelTest.kt index 69d03d9b0e4c..f8e1a9d12657 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneViewModelTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneViewModelTest.kt @@ -71,7 +71,7 @@ class ShadeSceneViewModelTest : SysuiTestCase() { fun upTransitionSceneKey_deviceLocked_lockScreen() = testScope.runTest { val upTransitionSceneKey by collectLastValue(underTest.upDestinationSceneKey) - authenticationInteractor.setAuthenticationMethod(AuthenticationMethodModel.PIN(1234)) + authenticationInteractor.setAuthenticationMethod(AuthenticationMethodModel.Pin(1234)) authenticationInteractor.lockDevice() assertThat(upTransitionSceneKey).isEqualTo(SceneKey.Lockscreen) @@ -81,7 +81,7 @@ class ShadeSceneViewModelTest : SysuiTestCase() { fun upTransitionSceneKey_deviceUnlocked_gone() = testScope.runTest { val upTransitionSceneKey by collectLastValue(underTest.upDestinationSceneKey) - authenticationInteractor.setAuthenticationMethod(AuthenticationMethodModel.PIN(1234)) + authenticationInteractor.setAuthenticationMethod(AuthenticationMethodModel.Pin(1234)) authenticationInteractor.unlockDevice() assertThat(upTransitionSceneKey).isEqualTo(SceneKey.Gone) @@ -91,7 +91,7 @@ class ShadeSceneViewModelTest : SysuiTestCase() { fun onContentClicked_deviceUnlocked_switchesToGone() = testScope.runTest { val currentScene by collectLastValue(sceneInteractor.currentScene(CONTAINER_1)) - authenticationInteractor.setAuthenticationMethod(AuthenticationMethodModel.PIN(1234)) + authenticationInteractor.setAuthenticationMethod(AuthenticationMethodModel.Pin(1234)) authenticationInteractor.unlockDevice() runCurrent() @@ -104,7 +104,7 @@ class ShadeSceneViewModelTest : SysuiTestCase() { fun onContentClicked_deviceLockedSecurely_switchesToBouncer() = testScope.runTest { val currentScene by collectLastValue(sceneInteractor.currentScene(CONTAINER_1)) - authenticationInteractor.setAuthenticationMethod(AuthenticationMethodModel.PIN(1234)) + authenticationInteractor.setAuthenticationMethod(AuthenticationMethodModel.Pin(1234)) authenticationInteractor.lockDevice() runCurrent() diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenterTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenterTest.java index fdfe028765ef..1724f27f63b8 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenterTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenterTest.java @@ -44,7 +44,6 @@ import com.android.systemui.shade.ShadeController; import com.android.systemui.shade.ShadeNotificationPresenter; import com.android.systemui.shade.ShadeViewController; import com.android.systemui.statusbar.CommandQueue; -import com.android.systemui.statusbar.KeyguardIndicationController; import com.android.systemui.statusbar.LockscreenShadeTransitionController; import com.android.systemui.statusbar.NotificationLockscreenUserManager; import com.android.systemui.statusbar.NotificationMediaManager; @@ -122,7 +121,6 @@ public class StatusBarNotificationPresenterTest extends SysuiTestCase { mock(NotificationShadeWindowController.class), mock(DynamicPrivacyController.class), mKeyguardStateController, - mock(KeyguardIndicationController.class), mCentralSurfaces, mock(LockscreenShadeTransitionController.class), mCommandQueue, diff --git a/packages/SystemUI/tests/src/com/android/systemui/unfold/FoldAodAnimationControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/unfold/FoldAodAnimationControllerTest.kt index d9ee08157c84..813597a8b576 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/unfold/FoldAodAnimationControllerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/unfold/FoldAodAnimationControllerTest.kt @@ -28,12 +28,10 @@ import com.android.systemui.SysuiTestCase import com.android.systemui.flags.FakeFeatureFlags import com.android.systemui.flags.Flags.FACE_AUTH_REFACTOR import com.android.systemui.keyguard.WakefulnessLifecycle -import com.android.systemui.keyguard.data.repository.FakeKeyguardBouncerRepository import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository -import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor +import com.android.systemui.keyguard.domain.interactor.KeyguardInteractorFactory import com.android.systemui.shade.ShadeFoldAnimator import com.android.systemui.shade.ShadeViewController -import com.android.systemui.statusbar.CommandQueue import com.android.systemui.statusbar.LightRevealScrim import com.android.systemui.statusbar.phone.CentralSurfaces import com.android.systemui.unfold.util.FoldableDeviceStates @@ -80,8 +78,6 @@ class FoldAodAnimationControllerTest : SysuiTestCase() { @Mock lateinit var viewTreeObserver: ViewTreeObserver - @Mock private lateinit var commandQueue: CommandQueue - @Mock lateinit var shadeFoldAnimator: ShadeFoldAnimator @Captor private lateinit var foldStateListenerCaptor: ArgumentCaptor<FoldStateListener> @@ -111,15 +107,10 @@ class FoldAodAnimationControllerTest : SysuiTestCase() { onActionStarted.run() } - keyguardRepository = FakeKeyguardRepository() val featureFlags = FakeFeatureFlags().apply { set(FACE_AUTH_REFACTOR, true) } - val keyguardInteractor = - KeyguardInteractor( - repository = keyguardRepository, - commandQueue = commandQueue, - featureFlags = featureFlags, - bouncerRepository = FakeKeyguardBouncerRepository(), - ) + val withDeps = KeyguardInteractorFactory.create(featureFlags = featureFlags) + val keyguardInteractor = withDeps.keyguardInteractor + keyguardRepository = withDeps.repository // Needs to be run on the main thread runBlocking(IMMEDIATE) { diff --git a/packages/SystemUI/tests/src/com/android/systemui/user/domain/interactor/UserInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/user/domain/interactor/UserInteractorTest.kt index ca83d49b19ca..3fbbeda8f74d 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/user/domain/interactor/UserInteractorTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/user/domain/interactor/UserInteractorTest.kt @@ -39,12 +39,10 @@ import com.android.systemui.common.shared.model.Text import com.android.systemui.coroutines.collectLastValue import com.android.systemui.flags.FakeFeatureFlags import com.android.systemui.flags.Flags -import com.android.systemui.keyguard.data.repository.FakeKeyguardBouncerRepository import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository -import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor +import com.android.systemui.keyguard.domain.interactor.KeyguardInteractorFactory import com.android.systemui.plugins.ActivityStarter import com.android.systemui.qs.user.UserSwitchDialogController -import com.android.systemui.statusbar.CommandQueue import com.android.systemui.statusbar.policy.DeviceProvisionedController import com.android.systemui.telephony.data.repository.FakeTelephonyRepository import com.android.systemui.telephony.domain.interactor.TelephonyInteractor @@ -97,7 +95,6 @@ class UserInteractorTest : SysuiTestCase() { @Mock private lateinit var dialogShower: UserSwitchDialogController.DialogShower @Mock private lateinit var resumeSessionReceiver: GuestResumeSessionReceiver @Mock private lateinit var resetOrExitSessionReceiver: GuestResetOrExitSessionReceiver - @Mock private lateinit var commandQueue: CommandQueue @Mock private lateinit var keyguardUpdateMonitor: KeyguardUpdateMonitor private lateinit var underTest: UserInteractor @@ -126,8 +123,9 @@ class UserInteractorTest : SysuiTestCase() { set(Flags.FULL_SCREEN_USER_SWITCHER, false) set(Flags.FACE_AUTH_REFACTOR, true) } + val reply = KeyguardInteractorFactory.create(featureFlags = featureFlags) + keyguardRepository = reply.repository userRepository = FakeUserRepository() - keyguardRepository = FakeKeyguardRepository() telephonyRepository = FakeTelephonyRepository() val testDispatcher = StandardTestDispatcher() testScope = TestScope(testDispatcher) @@ -142,13 +140,7 @@ class UserInteractorTest : SysuiTestCase() { applicationContext = context, repository = userRepository, activityStarter = activityStarter, - keyguardInteractor = - KeyguardInteractor( - repository = keyguardRepository, - commandQueue = commandQueue, - featureFlags = featureFlags, - bouncerRepository = FakeKeyguardBouncerRepository(), - ), + keyguardInteractor = reply.keyguardInteractor, manager = manager, headlessSystemUserMode = headlessSystemUserMode, applicationScope = testScope.backgroundScope, diff --git a/packages/SystemUI/tests/src/com/android/systemui/user/ui/viewmodel/StatusBarUserChipViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/user/ui/viewmodel/StatusBarUserChipViewModelTest.kt index fd8c6c76720b..9cb26e05887d 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/user/ui/viewmodel/StatusBarUserChipViewModelTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/user/ui/viewmodel/StatusBarUserChipViewModelTest.kt @@ -32,11 +32,8 @@ import com.android.systemui.SysuiTestCase import com.android.systemui.common.shared.model.Text import com.android.systemui.flags.FakeFeatureFlags import com.android.systemui.flags.Flags -import com.android.systemui.keyguard.data.repository.FakeKeyguardBouncerRepository -import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository -import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor +import com.android.systemui.keyguard.domain.interactor.KeyguardInteractorFactory import com.android.systemui.plugins.ActivityStarter -import com.android.systemui.statusbar.CommandQueue import com.android.systemui.statusbar.policy.DeviceProvisionedController import com.android.systemui.telephony.data.repository.FakeTelephonyRepository import com.android.systemui.telephony.domain.interactor.TelephonyInteractor @@ -80,13 +77,11 @@ class StatusBarUserChipViewModelTest : SysuiTestCase() { @Mock private lateinit var uiEventLogger: UiEventLogger @Mock private lateinit var resumeSessionReceiver: GuestResumeSessionReceiver @Mock private lateinit var resetOrExitSessionReceiver: GuestResetOrExitSessionReceiver - @Mock private lateinit var commandQueue: CommandQueue @Mock private lateinit var keyguardUpdateMonitor: KeyguardUpdateMonitor private lateinit var underTest: StatusBarUserChipViewModel private val userRepository = FakeUserRepository() - private val keyguardRepository = FakeKeyguardRepository() private lateinit var guestUserInteractor: GuestUserInteractor private lateinit var refreshUsersScheduler: RefreshUsersScheduler @@ -250,12 +245,8 @@ class StatusBarUserChipViewModelTest : SysuiTestCase() { repository = userRepository, activityStarter = activityStarter, keyguardInteractor = - KeyguardInteractor( - repository = keyguardRepository, - commandQueue = commandQueue, - featureFlags = featureFlags, - bouncerRepository = FakeKeyguardBouncerRepository(), - ), + KeyguardInteractorFactory.create(featureFlags = featureFlags) + .keyguardInteractor, featureFlags = featureFlags, manager = manager, headlessSystemUserMode = headlessSystemUserMode, diff --git a/packages/SystemUI/tests/src/com/android/systemui/user/ui/viewmodel/UserSwitcherViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/user/ui/viewmodel/UserSwitcherViewModelTest.kt index 91550844ceca..e3f9fac27815 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/user/ui/viewmodel/UserSwitcherViewModelTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/user/ui/viewmodel/UserSwitcherViewModelTest.kt @@ -30,11 +30,9 @@ import com.android.systemui.SysuiTestCase import com.android.systemui.common.shared.model.Text import com.android.systemui.flags.FakeFeatureFlags import com.android.systemui.flags.Flags -import com.android.systemui.keyguard.data.repository.FakeKeyguardBouncerRepository import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository -import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor +import com.android.systemui.keyguard.domain.interactor.KeyguardInteractorFactory import com.android.systemui.plugins.ActivityStarter -import com.android.systemui.statusbar.CommandQueue import com.android.systemui.statusbar.policy.DeviceProvisionedController import com.android.systemui.telephony.data.repository.FakeTelephonyRepository import com.android.systemui.telephony.domain.interactor.TelephonyInteractor @@ -79,7 +77,6 @@ class UserSwitcherViewModelTest : SysuiTestCase() { @Mock private lateinit var uiEventLogger: UiEventLogger @Mock private lateinit var resumeSessionReceiver: GuestResumeSessionReceiver @Mock private lateinit var resetOrExitSessionReceiver: GuestResetOrExitSessionReceiver - @Mock private lateinit var commandQueue: CommandQueue @Mock private lateinit var keyguardUpdateMonitor: KeyguardUpdateMonitor private lateinit var underTest: UserSwitcherViewModel @@ -112,7 +109,6 @@ class UserSwitcherViewModelTest : SysuiTestCase() { ) } - keyguardRepository = FakeKeyguardRepository() val refreshUsersScheduler = RefreshUsersScheduler( applicationScope = testScope.backgroundScope, @@ -140,38 +136,35 @@ class UserSwitcherViewModelTest : SysuiTestCase() { set(Flags.FULL_SCREEN_USER_SWITCHER, false) set(Flags.FACE_AUTH_REFACTOR, true) } + val reply = KeyguardInteractorFactory.create(featureFlags = featureFlags) + keyguardRepository = reply.repository + underTest = UserSwitcherViewModel( - userInteractor = - UserInteractor( - applicationContext = context, - repository = userRepository, - activityStarter = activityStarter, - keyguardInteractor = - KeyguardInteractor( - repository = keyguardRepository, - commandQueue = commandQueue, - featureFlags = featureFlags, - bouncerRepository = FakeKeyguardBouncerRepository(), - ), - featureFlags = featureFlags, - manager = manager, - headlessSystemUserMode = headlessSystemUserMode, - applicationScope = testScope.backgroundScope, - telephonyInteractor = - TelephonyInteractor( - repository = FakeTelephonyRepository(), - ), - broadcastDispatcher = fakeBroadcastDispatcher, - keyguardUpdateMonitor = keyguardUpdateMonitor, - backgroundDispatcher = testDispatcher, - activityManager = activityManager, - refreshUsersScheduler = refreshUsersScheduler, - guestUserInteractor = guestUserInteractor, - uiEventLogger = uiEventLogger, - ), - guestUserInteractor = guestUserInteractor, - ) + userInteractor = + UserInteractor( + applicationContext = context, + repository = userRepository, + activityStarter = activityStarter, + keyguardInteractor = reply.keyguardInteractor, + featureFlags = featureFlags, + manager = manager, + headlessSystemUserMode = headlessSystemUserMode, + applicationScope = testScope.backgroundScope, + telephonyInteractor = + TelephonyInteractor( + repository = FakeTelephonyRepository(), + ), + broadcastDispatcher = fakeBroadcastDispatcher, + keyguardUpdateMonitor = keyguardUpdateMonitor, + backgroundDispatcher = testDispatcher, + activityManager = activityManager, + refreshUsersScheduler = refreshUsersScheduler, + guestUserInteractor = guestUserInteractor, + uiEventLogger = uiEventLogger, + ), + guestUserInteractor = guestUserInteractor, + ) } @Test @@ -323,7 +316,7 @@ class UserSwitcherViewModelTest : SysuiTestCase() { setUsers(count = 2) val isFinishRequested = mutableListOf<Boolean>() val job = - launch(testDispatcher) { underTest.isFinishRequested.toList(isFinishRequested) } + launch(testDispatcher) { underTest.isFinishRequested.toList(isFinishRequested) } assertThat(isFinishRequested.last()).isFalse() underTest.onCancelButtonClicked() diff --git a/packages/SystemUI/tests/src/com/android/systemui/common/ui/data/repository/FakeConfigurationRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/common/ui/data/repository/FakeConfigurationRepository.kt index b2a1668df7aa..b2a1668df7aa 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/common/ui/data/repository/FakeConfigurationRepository.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/common/ui/data/repository/FakeConfigurationRepository.kt diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractorFactory.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractorFactory.kt new file mode 100644 index 000000000000..c7ca7ceb516e --- /dev/null +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractorFactory.kt @@ -0,0 +1,71 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package com.android.systemui.keyguard.domain.interactor + +import com.android.systemui.common.ui.data.repository.FakeConfigurationRepository +import com.android.systemui.flags.FakeFeatureFlags +import com.android.systemui.flags.Flags +import com.android.systemui.keyguard.data.repository.FakeCommandQueue +import com.android.systemui.keyguard.data.repository.FakeKeyguardBouncerRepository +import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository + +/** + * Simply put, I got tired of adding a constructor argument and then having to tweak dozens of + * files. This should alleviate some of the burden by providing defaults for testing. + */ +object KeyguardInteractorFactory { + + @JvmOverloads + @JvmStatic + fun create( + featureFlags: FakeFeatureFlags = createFakeFeatureFlags(), + repository: FakeKeyguardRepository = FakeKeyguardRepository(), + commandQueue: FakeCommandQueue = FakeCommandQueue(), + bouncerRepository: FakeKeyguardBouncerRepository = FakeKeyguardBouncerRepository(), + configurationRepository: FakeConfigurationRepository = FakeConfigurationRepository(), + ): WithDependencies { + return WithDependencies( + repository = repository, + commandQueue = commandQueue, + featureFlags = featureFlags, + bouncerRepository = bouncerRepository, + configurationRepository = configurationRepository, + KeyguardInteractor( + repository = repository, + commandQueue = commandQueue, + featureFlags = featureFlags, + bouncerRepository = bouncerRepository, + configurationRepository = configurationRepository, + ) + ) + } + + /** Provide defaults, otherwise tests will throw an error */ + fun createFakeFeatureFlags(): FakeFeatureFlags { + return FakeFeatureFlags().apply { set(Flags.FACE_AUTH_REFACTOR, false) } + } + + data class WithDependencies( + val repository: FakeKeyguardRepository, + val commandQueue: FakeCommandQueue, + val featureFlags: FakeFeatureFlags, + val bouncerRepository: FakeKeyguardBouncerRepository, + val configurationRepository: FakeConfigurationRepository, + val keyguardInteractor: KeyguardInteractor, + ) +} diff --git a/services/core/java/com/android/server/am/ActiveServices.java b/services/core/java/com/android/server/am/ActiveServices.java index 00dbb97191fb..6db8ea7e536e 100644 --- a/services/core/java/com/android/server/am/ActiveServices.java +++ b/services/core/java/com/android/server/am/ActiveServices.java @@ -2156,7 +2156,6 @@ public final class ActiveServices { } } - final boolean enableFgsWhileInUseFix = mAm.mConstants.mEnableFgsWhileInUseFix; final boolean fgsTypeChangingFromShortFgs = r.isForeground && isOldTypeShortFgs; if (fgsTypeChangingFromShortFgs) { @@ -2214,19 +2213,7 @@ public final class ActiveServices { } } - boolean resetNeededForLogging = false; - - // Re-evaluate mAllowWhileInUsePermissionInFgs and mAllowStartForeground - // (i.e. while-in-use and BFSL flags) if needed. - // - // Consider the below if-else section to be in the else of the above - // `if (fgsTypeChangingFromShortFgs)`. - // Using an else would increase the indent further, so we don't use it here - // and instead just add !fgsTypeChangingFromShortFgs to all if's. - // - // The first if's are for the original while-in-use logic. - if (!fgsTypeChangingFromShortFgs && !enableFgsWhileInUseFix - && r.mStartForegroundCount == 0) { + if (!fgsTypeChangingFromShortFgs && r.mStartForegroundCount == 0) { /* If the service was started with startService(), not startForegroundService(), and if startForeground() isn't called within @@ -2257,8 +2244,7 @@ public final class ActiveServices { r.mLoggedInfoAllowStartForeground = false; } } - } else if (!fgsTypeChangingFromShortFgs && !enableFgsWhileInUseFix - && r.mStartForegroundCount >= 1) { + } else if (!fgsTypeChangingFromShortFgs && r.mStartForegroundCount >= 1) { // We get here if startForeground() is called multiple times // on the same service after it's created, regardless of whether // stopForeground() has been called or not. @@ -2269,100 +2255,6 @@ public final class ActiveServices { r.appInfo.uid, r.intent.getIntent(), r, r.userId, BackgroundStartPrivileges.NONE, false /* isBindService */, false /* isStartService */); - } else if (!fgsTypeChangingFromShortFgs && enableFgsWhileInUseFix) { - // The new while-in-use logic. - // - // When startForeground() is called, we _always_ call - // setFgsRestrictionLocked() to set the restrictions according to the - // current state of the app. - // (So if the app is now in TOP, for example, the service will now always - // get while-in-use permissions.) - // - // Note, setFgsRestrictionLocked() will never disallow - // mAllowWhileInUsePermissionInFgs nor mAllowStartForeground - // (i.e. while-in-use and BFSL flags) once they're set to "allowed". - // - // HOWEVER, if these flags were set to "allowed" in Context.startService() - // (as opposed to startForegroundService()), when the service wasn't yet - // a foreground service, then we may not always - // want to trust them -- for example, if the service has been running as a - // BG service or a bound service for a long time when the app is not longer - // in the foreground, then we shouldn't grant while-in-user nor BFSL. - // So in that case, we need to reset it first. - - final long delayMs = - (r.mLastUntrustedSetFgsRestrictionAllowedTime == 0) ? 0 - : (SystemClock.elapsedRealtime() - - r.mLastUntrustedSetFgsRestrictionAllowedTime); - final boolean resetNeeded = - !r.isForeground - && delayMs > mAm.mConstants.mFgsStartForegroundTimeoutMs; - if (resetNeeded) { - // We don't want to reset mDebugWhileInUseReasonInBindService here -- - // we'll instead reset it in the following code, using the simulated - // legacy logic. - resetFgsRestrictionLocked(r, - /*resetDebugWhileInUseReasonInBindService=*/ false); - } - - // Simulate the reset flow in the legacy logic to reset - // mDebugWhileInUseReasonInBindService. - // (Which is only used to compare to the old logic.) - final long legacyDelayMs = SystemClock.elapsedRealtime() - r.createRealTime; - if ((r.mStartForegroundCount == 0) - && (legacyDelayMs > mAm.mConstants.mFgsStartForegroundTimeoutMs)) { - r.mDebugWhileInUseReasonInBindService = REASON_DENIED; - } - - setFgsRestrictionLocked(r.serviceInfo.packageName, r.app.getPid(), - r.appInfo.uid, r.intent.getIntent(), r, r.userId, - BackgroundStartPrivileges.NONE, - false /* isBindService */, false /* isStartService */); - - final String temp = "startForegroundDelayMs:" + delayMs - + "; started: " + r.startRequested - + "; num_bindings: " + r.getConnections().size() - + "; wasForeground: " + r.isForeground - + "; resetNeeded:" + resetNeeded; - if (r.mInfoAllowStartForeground != null) { - r.mInfoAllowStartForeground += "; " + temp; - } else { - r.mInfoAllowStartForeground = temp; - } - r.mLoggedInfoAllowStartForeground = false; - - resetNeededForLogging = resetNeeded; - } - - // If the service has any bindings and it's not yet a FGS - // we compare the new and old while-in-use logics. - // (If it's not the first startForeground() call, we already reset the - // while-in-use and BFSL flags, so the logic change wouldn't matter.) - // - // Note, mDebugWhileInUseReasonInBindService does *not* fully simulate the - // legacy logic, because we'll only set it in bindService(), but the actual - // mAllowWhileInUsePermissionInFgsReason can change afterwards, in a subsequent - // Service.startForeground(). This check will only provide "rough" check. - // But if mDebugWhileInUseReasonInBindService is _not_ DENIED, and - // mDebugWhileInUseReasonInStartForeground _is_ DENIED, then that means we'd - // now detected a behavior change. - // OTOH, if it's changing from non-DENIED to another non-DENIED, that may - // not be a problem. - if (enableFgsWhileInUseFix - && !r.isForeground - && (r.getConnections().size() > 0) - && (r.mDebugWhileInUseReasonInBindService - != r.mDebugWhileInUseReasonInStartForeground)) { - logWhileInUseChangeWtf("FGS while-in-use changed (b/276963716): old=" - + reasonCodeToString(r.mDebugWhileInUseReasonInBindService) - + " new=" - + reasonCodeToString(r.mDebugWhileInUseReasonInStartForeground) - + " startForegroundCount=" + r.mStartForegroundCount - + " started=" + r.startRequested - + " num_bindings=" + r.getConnections().size() - + " resetNeeded=" + resetNeededForLogging - + " " - + r.shortInstanceName); } // If the foreground service is not started from TOP process, do not allow it to @@ -2471,10 +2363,6 @@ public final class ActiveServices { } r.isForeground = true; - // Once the service becomes a foreground service, - // the FGS restriction information always becomes "trustable". - r.mLastUntrustedSetFgsRestrictionAllowedTime = 0; - // The logging of FOREGROUND_SERVICE_STATE_CHANGED__STATE__ENTER event could // be deferred, make a copy of mAllowStartForeground and // mAllowWhileInUsePermissionInFgs. @@ -2620,13 +2508,6 @@ public final class ActiveServices { } } - /** - * It just does a wtf, but extracted to a method, so we can do a signature search on pitot. - */ - private void logWhileInUseChangeWtf(String message) { - Slog.wtf(TAG, message); - } - private boolean withinFgsDeferRateLimit(ServiceRecord sr, final long now) { // If we're still within the service's deferral period, then by definition // deferral is not rate limited. @@ -3839,25 +3720,9 @@ public final class ActiveServices { return 0; } } - if (!mAm.mConstants.mEnableFgsWhileInUseFix) { - // Old while-in-use logic. - setFgsRestrictionLocked(callingPackage, callingPid, callingUid, service, s, userId, - BackgroundStartPrivileges.NONE, true /* isBindService */, - false /* isStartService */); - } else { - // New logic will not call setFgsRestrictionLocked() here, but we still - // keep track of the allow reason from the old logic here, so we can compare to - // the new logic. - // Once we're confident enough in the new logic, we should remove it. - if (s.mDebugWhileInUseReasonInBindService == REASON_DENIED) { - s.mDebugWhileInUseReasonInBindService = - shouldAllowFgsWhileInUsePermissionLocked( - callingPackage, callingPid, callingUid, s.app, - BackgroundStartPrivileges.NONE, - true /* isBindService */, - false /* DO NOT enableFgsWhileInUseFix; use the old logic */); - } - } + setFgsRestrictionLocked(callingPackage, callingPid, callingUid, service, s, userId, + BackgroundStartPrivileges.NONE, true /* isBindService */, + false /* isStartService */); if (s.app != null) { ProcessServiceRecord servicePsr = s.app.mServices; @@ -7554,15 +7419,13 @@ public final class ActiveServices { * @param r the service to start. * @param isStartService True if it's called from Context.startService(). * False if it's called from Context.startForegroundService() or - * Service.startService(). + * Service.startForeground(). * @return true if allow, false otherwise. */ private void setFgsRestrictionLocked(String callingPackage, int callingPid, int callingUid, Intent intent, ServiceRecord r, int userId, BackgroundStartPrivileges backgroundStartPrivileges, boolean isBindService, boolean isStartService) { - final long now = SystemClock.elapsedRealtime(); - // Check DeviceConfig flag. if (!mAm.mConstants.mFlagBackgroundFgsStartRestrictionEnabled) { r.mAllowWhileInUsePermissionInFgs = true; @@ -7580,60 +7443,30 @@ public final class ActiveServices { isBindService); // We store them to compare the old and new while-in-use logics to each other. // (They're not used for any other purposes.) - if (isBindService) { - r.mDebugWhileInUseReasonInBindService = allowWhileInUse; - } else { - r.mDebugWhileInUseReasonInStartForeground = allowWhileInUse; - } if (!r.mAllowWhileInUsePermissionInFgs) { r.mAllowWhileInUsePermissionInFgs = (allowWhileInUse != REASON_DENIED); - newlyAllowed |= r.mAllowWhileInUsePermissionInFgs; } if (r.mAllowStartForeground == REASON_DENIED) { r.mAllowStartForeground = shouldAllowFgsStartForegroundWithBindingCheckLocked( allowWhileInUse, callingPackage, callingPid, callingUid, intent, r, backgroundStartPrivileges, isBindService); - newlyAllowed |= r.mAllowStartForeground != REASON_DENIED; } } else { allowWhileInUse = REASON_UNKNOWN; } r.mAllowWhileInUsePermissionInFgsReason = allowWhileInUse; - - if (isStartService && !r.isForeground && newlyAllowed) { - // If it's called by Context.startService() (not by startForegroundService()), - // and we're setting "allowed", then we can't fully trust it yet -- we'll need to reset - // the restrictions if startForeground() is called after the grace period. - r.mLastUntrustedSetFgsRestrictionAllowedTime = now; - } } /** * Reset various while-in-use and BFSL related information. */ void resetFgsRestrictionLocked(ServiceRecord r) { - resetFgsRestrictionLocked(r, /*resetDebugWhileInUseReasonInBindService=*/ true); - } - - /** - * Reset various while-in-use and BFSL related information. - */ - void resetFgsRestrictionLocked(ServiceRecord r, - boolean resetDebugWhileInUseReasonInBindService) { r.mAllowWhileInUsePermissionInFgs = false; r.mAllowWhileInUsePermissionInFgsReason = REASON_DENIED; - r.mDebugWhileInUseReasonInStartForeground = REASON_DENIED; - - // In Service.startForeground(), we reset this field using a legacy logic, - // so resetting this field is optional. - if (resetDebugWhileInUseReasonInBindService) { - r.mDebugWhileInUseReasonInBindService = REASON_DENIED; - } r.mAllowStartForeground = REASON_DENIED; r.mInfoAllowStartForeground = null; r.mInfoTempFgsAllowListReason = null; r.mLoggedInfoAllowStartForeground = false; - r.mLastUntrustedSetFgsRestrictionAllowedTime = 0; r.updateAllowUiJobScheduling(r.mAllowWhileInUsePermissionInFgs); } @@ -7668,22 +7501,11 @@ public final class ActiveServices { private @ReasonCode int shouldAllowFgsWhileInUsePermissionLocked(String callingPackage, int callingPid, int callingUid, @Nullable ProcessRecord targetProcess, BackgroundStartPrivileges backgroundStartPrivileges, boolean isBindService) { - return shouldAllowFgsWhileInUsePermissionLocked(callingPackage, - callingPid, callingUid, targetProcess, backgroundStartPrivileges, isBindService, - /* enableFgsWhileInUseFix =*/ mAm.mConstants.mEnableFgsWhileInUseFix); - } - - private @ReasonCode int shouldAllowFgsWhileInUsePermissionLocked(String callingPackage, - int callingPid, int callingUid, @Nullable ProcessRecord targetProcess, - BackgroundStartPrivileges backgroundStartPrivileges, boolean isBindService, - boolean enableFgsWhileInUseFix) { int ret = REASON_DENIED; - // Define some local variables for better readability... - final boolean useOldLogic = !enableFgsWhileInUseFix; final boolean forStartForeground = !isBindService; - if (useOldLogic || forStartForeground) { + if (forStartForeground) { final int uidState = mAm.getUidStateLocked(callingUid); if (ret == REASON_DENIED) { // Allow FGS while-in-use if the caller's process state is PROCESS_STATE_PERSISTENT, @@ -7733,10 +7555,6 @@ public final class ActiveServices { } } - if (enableFgsWhileInUseFix && ret == REASON_DENIED) { - ret = shouldAllowFgsWhileInUsePermissionByBindingsLocked(callingUid); - } - if (ret == REASON_DENIED) { // Allow FGS while-in-use if the WindowManager allows background activity start. // This is mainly to get the 10 seconds grace period if any activity in the caller has diff --git a/services/core/java/com/android/server/am/ActivityManagerConstants.java b/services/core/java/com/android/server/am/ActivityManagerConstants.java index 3b446338298d..44e198b53761 100644 --- a/services/core/java/com/android/server/am/ActivityManagerConstants.java +++ b/services/core/java/com/android/server/am/ActivityManagerConstants.java @@ -1058,13 +1058,6 @@ final class ActivityManagerConstants extends ContentObserver { /** @see #KEY_USE_MODERN_TRIM */ public boolean USE_MODERN_TRIM = DEFAULT_USE_MODERN_TRIM; - private static final String KEY_ENABLE_FGS_WHILE_IN_USE_FIX = - "key_enable_fgs_while_in_use_fix"; - - private static final boolean DEFAULT_ENABLE_FGS_WHILE_IN_USE_FIX = false; - - public volatile boolean mEnableFgsWhileInUseFix = DEFAULT_ENABLE_FGS_WHILE_IN_USE_FIX; - private final OnPropertiesChangedListener mOnDeviceConfigChangedListener = new OnPropertiesChangedListener() { @Override @@ -1233,9 +1226,6 @@ final class ActivityManagerConstants extends ContentObserver { case KEY_ENABLE_WAIT_FOR_FINISH_ATTACH_APPLICATION: updateEnableWaitForFinishAttachApplication(); break; - case KEY_ENABLE_FGS_WHILE_IN_USE_FIX: - updateEnableFgsWhileInUseFix(); - break; case KEY_MAX_PREVIOUS_TIME: updateMaxPreviousTime(); break; @@ -2005,12 +1995,6 @@ final class ActivityManagerConstants extends ContentObserver { DEFAULT_ENABLE_WAIT_FOR_FINISH_ATTACH_APPLICATION); } - private void updateEnableFgsWhileInUseFix() { - mEnableFgsWhileInUseFix = DeviceConfig.getBoolean( - DeviceConfig.NAMESPACE_ACTIVITY_MANAGER, - KEY_ENABLE_FGS_WHILE_IN_USE_FIX, - DEFAULT_ENABLE_FGS_WHILE_IN_USE_FIX); - } private void updateUseTieredCachedAdj() { USE_TIERED_CACHED_ADJ = DeviceConfig.getBoolean( DeviceConfig.NAMESPACE_ACTIVITY_MANAGER, @@ -2211,9 +2195,6 @@ final class ActivityManagerConstants extends ContentObserver { pw.print(" "); pw.print(KEY_SYSTEM_EXEMPT_POWER_RESTRICTIONS_ENABLED); pw.print("="); pw.println(mFlagSystemExemptPowerRestrictionsEnabled); - pw.print(" "); pw.print(KEY_ENABLE_FGS_WHILE_IN_USE_FIX); - pw.print("="); pw.println(mEnableFgsWhileInUseFix); - pw.print(" "); pw.print(KEY_SHORT_FGS_TIMEOUT_DURATION); pw.print("="); pw.println(mShortFgsTimeoutDuration); pw.print(" "); pw.print(KEY_SHORT_FGS_PROC_STATE_EXTRA_WAIT_DURATION); diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java index 460ce4484a4b..1bd0675e95b6 100644 --- a/services/core/java/com/android/server/am/ActivityManagerService.java +++ b/services/core/java/com/android/server/am/ActivityManagerService.java @@ -4854,7 +4854,7 @@ public class ActivityManagerService extends IActivityManager.Stub } checkTime(startTime, "finishAttachApplicationInner: " + "after dispatching broadcasts"); - } catch (Exception e) { + } catch (BroadcastDeliveryFailedException e) { // If the app died trying to launch the receiver we declare it 'bad' Slog.wtf(TAG, "Exception thrown dispatching broadcasts in " + app, e); badApp = true; diff --git a/services/core/java/com/android/server/am/BroadcastDeliveryFailedException.java b/services/core/java/com/android/server/am/BroadcastDeliveryFailedException.java new file mode 100644 index 000000000000..9c9281635cf7 --- /dev/null +++ b/services/core/java/com/android/server/am/BroadcastDeliveryFailedException.java @@ -0,0 +1,32 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.am; + +import android.util.AndroidException; + +/** + * Exception to represent that broadcast could not be delivered. + */ +public class BroadcastDeliveryFailedException extends AndroidException { + public BroadcastDeliveryFailedException(String name) { + super(name); + } + + public BroadcastDeliveryFailedException(Exception cause) { + super(cause); + } +} diff --git a/services/core/java/com/android/server/am/BroadcastProcessQueue.java b/services/core/java/com/android/server/am/BroadcastProcessQueue.java index 9b53af2e9b5c..0b5b1cb2902e 100644 --- a/services/core/java/com/android/server/am/BroadcastProcessQueue.java +++ b/services/core/java/com/android/server/am/BroadcastProcessQueue.java @@ -280,6 +280,25 @@ class BroadcastProcessQueue { } /** + * Re-enqueue the active broadcast so that it can be made active and delivered again. In order + * to keep its previous position same to avoid issues with reordering, insert it at the head + * of the queue. + * + * Callers are responsible for clearing the active broadcast by calling + * {@link #makeActiveIdle()} after re-enqueuing it. + */ + public void reEnqueueActiveBroadcast() { + final BroadcastRecord record = getActive(); + final int recordIndex = getActiveIndex(); + + final SomeArgs broadcastArgs = SomeArgs.obtain(); + broadcastArgs.arg1 = record; + broadcastArgs.argi1 = recordIndex; + getQueueForBroadcast(record).addFirst(broadcastArgs); + onBroadcastEnqueued(record, recordIndex); + } + + /** * Searches from newest to oldest in the pending broadcast queues, and at the first matching * pending broadcast it finds, replaces it in-place and returns -- does not attempt to handle * "duplicate" broadcasts in the queue. diff --git a/services/core/java/com/android/server/am/BroadcastQueue.java b/services/core/java/com/android/server/am/BroadcastQueue.java index 8e76e5b5cf48..e38a2eefcd92 100644 --- a/services/core/java/com/android/server/am/BroadcastQueue.java +++ b/services/core/java/com/android/server/am/BroadcastQueue.java @@ -148,7 +148,8 @@ public abstract class BroadcastQueue { * dispatching a pending broadcast */ @GuardedBy("mService") - public abstract boolean onApplicationAttachedLocked(@NonNull ProcessRecord app); + public abstract boolean onApplicationAttachedLocked(@NonNull ProcessRecord app) + throws BroadcastDeliveryFailedException; /** * Signal from OS internals that the given process has timed out during diff --git a/services/core/java/com/android/server/am/BroadcastQueueImpl.java b/services/core/java/com/android/server/am/BroadcastQueueImpl.java index e389821595a0..4eedfe259633 100644 --- a/services/core/java/com/android/server/am/BroadcastQueueImpl.java +++ b/services/core/java/com/android/server/am/BroadcastQueueImpl.java @@ -438,7 +438,8 @@ public class BroadcastQueueImpl extends BroadcastQueue { scheduleBroadcastsLocked(); } - public boolean onApplicationAttachedLocked(ProcessRecord app) { + public boolean onApplicationAttachedLocked(ProcessRecord app) + throws BroadcastDeliveryFailedException { updateUidReadyForBootCompletedBroadcastLocked(app.uid); if (mPendingBroadcast != null && mPendingBroadcast.curApp == app) { @@ -460,7 +461,8 @@ public class BroadcastQueueImpl extends BroadcastQueue { skipCurrentOrPendingReceiverLocked(app); } - public boolean sendPendingBroadcastsLocked(ProcessRecord app) { + public boolean sendPendingBroadcastsLocked(ProcessRecord app) + throws BroadcastDeliveryFailedException { boolean didSomething = false; final BroadcastRecord br = mPendingBroadcast; if (br != null && br.curApp.getPid() > 0 && br.curApp.getPid() == app.getPid()) { @@ -483,7 +485,7 @@ public class BroadcastQueueImpl extends BroadcastQueue { scheduleBroadcastsLocked(); // We need to reset the state if we failed to start the receiver. br.state = BroadcastRecord.IDLE; - throw new RuntimeException(e.getMessage()); + throw new BroadcastDeliveryFailedException(e); } } return didSomething; diff --git a/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java b/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java index 22c0855ec54f..bb5fcbec7010 100644 --- a/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java +++ b/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java @@ -469,10 +469,15 @@ class BroadcastQueueModernImpl extends BroadcastQueue { if (DEBUG_BROADCAST) logv("Promoting " + queue + " from runnable to running; process is " + queue.app); promoteToRunningLocked(queue); - final boolean completed; + boolean completed; if (processWarm) { updateOomAdj |= queue.runningOomAdjusted; - completed = scheduleReceiverWarmLocked(queue); + try { + completed = scheduleReceiverWarmLocked(queue); + } catch (BroadcastDeliveryFailedException e) { + reEnqueueActiveBroadcast(queue); + completed = true; + } } else { completed = scheduleReceiverColdLocked(queue); } @@ -513,7 +518,9 @@ class BroadcastQueueModernImpl extends BroadcastQueue { private void clearInvalidPendingColdStart() { logw("Clearing invalid pending cold start: " + mRunningColdStart); - onApplicationCleanupLocked(mRunningColdStart.app); + mRunningColdStart.reEnqueueActiveBroadcast(); + demoteFromRunningLocked(mRunningColdStart); + clearRunningColdStart(); } private void checkPendingColdStartValidity() { @@ -535,8 +542,19 @@ class BroadcastQueueModernImpl extends BroadcastQueue { } } + private void reEnqueueActiveBroadcast(@NonNull BroadcastProcessQueue queue) { + checkState(queue.isActive(), "isActive"); + + final BroadcastRecord record = queue.getActive(); + final int index = queue.getActiveIndex(); + setDeliveryState(queue, queue.app, record, index, record.receivers.get(index), + BroadcastRecord.DELIVERY_PENDING, "reEnqueueActiveBroadcast"); + queue.reEnqueueActiveBroadcast(); + } + @Override - public boolean onApplicationAttachedLocked(@NonNull ProcessRecord app) { + public boolean onApplicationAttachedLocked(@NonNull ProcessRecord app) + throws BroadcastDeliveryFailedException { // Process records can be recycled, so always start by looking up the // relevant per-process queue final BroadcastProcessQueue queue = getProcessQueue(app); @@ -557,8 +575,14 @@ class BroadcastQueueModernImpl extends BroadcastQueue { queue.traceProcessEnd(); queue.traceProcessRunningBegin(); - if (scheduleReceiverWarmLocked(queue)) { + try { + if (scheduleReceiverWarmLocked(queue)) { + demoteFromRunningLocked(queue); + } + } catch (BroadcastDeliveryFailedException e) { + reEnqueueActiveBroadcast(queue); demoteFromRunningLocked(queue); + throw e; } // We might be willing to kick off another cold start @@ -588,14 +612,7 @@ class BroadcastQueueModernImpl extends BroadcastQueue { } if ((mRunningColdStart != null) && (mRunningColdStart == queue)) { - // We've been waiting for this app to cold start, and it had - // trouble; clear the slot and fail delivery below - mRunningColdStart = null; - - queue.traceProcessEnd(); - - // We might be willing to kick off another cold start - enqueueUpdateRunningList(); + clearRunningColdStart(); } if (queue != null) { @@ -618,6 +635,17 @@ class BroadcastQueueModernImpl extends BroadcastQueue { } } + private void clearRunningColdStart() { + mRunningColdStart.traceProcessEnd(); + + // We've been waiting for this app to cold start, and it had + // trouble; clear the slot and fail delivery below + mRunningColdStart = null; + + // We might be willing to kick off another cold start + enqueueUpdateRunningList(); + } + @Override public int getPreferredSchedulingGroupLocked(@NonNull ProcessRecord app) { final BroadcastProcessQueue queue = getProcessQueue(app); @@ -836,7 +864,8 @@ class BroadcastQueueModernImpl extends BroadcastQueue { */ @CheckResult @GuardedBy("mService") - private boolean scheduleReceiverWarmLocked(@NonNull BroadcastProcessQueue queue) { + private boolean scheduleReceiverWarmLocked(@NonNull BroadcastProcessQueue queue) + throws BroadcastDeliveryFailedException { checkState(queue.isActive(), "isActive"); final int cookie = traceBegin("scheduleReceiverWarmLocked"); @@ -918,7 +947,7 @@ class BroadcastQueueModernImpl extends BroadcastQueue { */ @CheckResult private boolean dispatchReceivers(@NonNull BroadcastProcessQueue queue, - @NonNull BroadcastRecord r, int index) { + @NonNull BroadcastRecord r, int index) throws BroadcastDeliveryFailedException { final ProcessRecord app = queue.app; final Object receiver = r.receivers.get(index); @@ -1005,6 +1034,11 @@ class BroadcastQueueModernImpl extends BroadcastQueue { logw(msg); app.killLocked("Can't deliver broadcast", ApplicationExitInfo.REASON_OTHER, ApplicationExitInfo.SUBREASON_UNDELIVERED_BROADCAST, true); + // If we were trying to deliver a manifest broadcast, throw the error as we need + // to try redelivering the broadcast to this receiver. + if (receiver instanceof ResolveInfo) { + throw new BroadcastDeliveryFailedException(e); + } finishReceiverActiveLocked(queue, BroadcastRecord.DELIVERY_FAILURE, "remote app"); return false; @@ -1092,7 +1126,7 @@ class BroadcastQueueModernImpl extends BroadcastQueue { boolean waitForServices) { final BroadcastProcessQueue queue = getProcessQueue(app); if ((queue == null) || !queue.isActive()) { - logw("Ignoring finish; no active broadcast for " + queue); + logw("Ignoring finishReceiverLocked; no active broadcast for " + queue); return false; } @@ -1129,7 +1163,13 @@ class BroadcastQueueModernImpl extends BroadcastQueue { // We're on a roll; move onto the next broadcast for this process queue.makeActiveNextPending(); - if (scheduleReceiverWarmLocked(queue)) { + try { + if (scheduleReceiverWarmLocked(queue)) { + demoteFromRunningLocked(queue); + return true; + } + } catch (BroadcastDeliveryFailedException e) { + reEnqueueActiveBroadcast(queue); demoteFromRunningLocked(queue); return true; } @@ -1166,7 +1206,7 @@ class BroadcastQueueModernImpl extends BroadcastQueue { private void finishReceiverActiveLocked(@NonNull BroadcastProcessQueue queue, @DeliveryState int deliveryState, @NonNull String reason) { if (!queue.isActive()) { - logw("Ignoring finish; no active broadcast for " + queue); + logw("Ignoring finishReceiverActiveLocked; no active broadcast for " + queue); return; } diff --git a/services/core/java/com/android/server/am/BroadcastRecord.java b/services/core/java/com/android/server/am/BroadcastRecord.java index 564b1fe8de34..863dd63a2922 100644 --- a/services/core/java/com/android/server/am/BroadcastRecord.java +++ b/services/core/java/com/android/server/am/BroadcastRecord.java @@ -699,6 +699,9 @@ final class BroadcastRecord extends Binder { break; } switch (newDeliveryState) { + case DELIVERY_PENDING: + scheduledTime[index] = 0; + break; case DELIVERY_SCHEDULED: scheduledTime[index] = SystemClock.uptimeMillis(); break; diff --git a/services/core/java/com/android/server/am/ServiceRecord.java b/services/core/java/com/android/server/am/ServiceRecord.java index 1f39d1b5d7f8..dccbb0accadd 100644 --- a/services/core/java/com/android/server/am/ServiceRecord.java +++ b/services/core/java/com/android/server/am/ServiceRecord.java @@ -177,12 +177,6 @@ final class ServiceRecord extends Binder implements ComponentName.WithComponentN @PowerExemptionManager.ReasonCode int mAllowWhileInUsePermissionInFgsReason = REASON_DENIED; - // Integer version of mAllowWhileInUsePermissionInFgs that we keep track to compare - // the old and new logics. - // TODO: Remove them once we're confident in the new logic. - int mDebugWhileInUseReasonInStartForeground = REASON_DENIED; - int mDebugWhileInUseReasonInBindService = REASON_DENIED; - // A copy of mAllowWhileInUsePermissionInFgs's value when the service is entering FGS state. boolean mAllowWhileInUsePermissionInFgsAtEntering; /** Allow scheduling user-initiated jobs from the background. */ @@ -224,13 +218,6 @@ final class ServiceRecord extends Binder implements ComponentName.WithComponentN // is called. int mStartForegroundCount; - // Last time mAllowWhileInUsePermissionInFgs or mAllowStartForeground was set to "allowed" - // from "disallowed" when the service was _not_ already a foreground service. - // this means they're set in startService(). (not startForegroundService) - // In startForeground(), if this timestamp is too old, we can't trust these flags, so - // we need to reset them. - long mLastUntrustedSetFgsRestrictionAllowedTime; - // This is a service record of a FGS delegate (not a service record of a real service) boolean mIsFgsDelegate; @Nullable ForegroundServiceDelegation mFgsDelegation; @@ -624,12 +611,6 @@ final class ServiceRecord extends Binder implements ComponentName.WithComponentN pw.print(prefix); pw.print("mAllowWhileInUsePermissionInFgsReason="); pw.println(PowerExemptionManager.reasonCodeToString(mAllowWhileInUsePermissionInFgsReason)); - pw.print(prefix); pw.print("debugWhileInUseReasonInStartForeground="); - pw.println(PowerExemptionManager.reasonCodeToString( - mDebugWhileInUseReasonInStartForeground)); - pw.print(prefix); pw.print("debugWhileInUseReasonInBindService="); - pw.println(PowerExemptionManager.reasonCodeToString(mDebugWhileInUseReasonInBindService)); - pw.print(prefix); pw.print("allowUiJobScheduling="); pw.println(mAllowUiJobScheduling); pw.print(prefix); pw.print("recentCallingPackage="); pw.println(mRecentCallingPackage); @@ -642,10 +623,6 @@ final class ServiceRecord extends Binder implements ComponentName.WithComponentN pw.print(prefix); pw.print("infoAllowStartForeground="); pw.println(mInfoAllowStartForeground); - pw.print(prefix); pw.print("lastUntrustedSetFgsRestrictionAllowedTime="); - TimeUtils.formatDuration(mLastUntrustedSetFgsRestrictionAllowedTime, now, pw); - pw.println(); - if (delayed) { pw.print(prefix); pw.print("delayed="); pw.println(delayed); } diff --git a/services/core/java/com/android/server/display/BrightnessSetting.java b/services/core/java/com/android/server/display/BrightnessSetting.java index de42370e6d84..651828b6b9e2 100644 --- a/services/core/java/com/android/server/display/BrightnessSetting.java +++ b/services/core/java/com/android/server/display/BrightnessSetting.java @@ -40,6 +40,7 @@ public class BrightnessSetting { private final LogicalDisplay mLogicalDisplay; + private int mUserSerial; private final Handler mHandler = new Handler(Looper.getMainLooper()) { @Override public void handleMessage(Message msg) { @@ -56,13 +57,15 @@ public class BrightnessSetting { @GuardedBy("mSyncRoot") private float mBrightness; - BrightnessSetting(@NonNull PersistentDataStore persistentDataStore, + BrightnessSetting(int userSerial, + @NonNull PersistentDataStore persistentDataStore, @NonNull LogicalDisplay logicalDisplay, DisplayManagerService.SyncRoot syncRoot) { mPersistentDataStore = persistentDataStore; mLogicalDisplay = logicalDisplay; + mUserSerial = userSerial; mBrightness = mPersistentDataStore.getBrightness( - mLogicalDisplay.getPrimaryDisplayDeviceLocked()); + mLogicalDisplay.getPrimaryDisplayDeviceLocked(), userSerial); mSyncRoot = syncRoot; } @@ -96,8 +99,13 @@ public class BrightnessSetting { mListeners.remove(l); } + /** Sets the user serial for the brightness setting */ + public void setUserSerial(int userSerial) { + mUserSerial = userSerial; + } + /** - * Sets the brigtness and broadcasts the change to the listeners. + * Sets the brightness and broadcasts the change to the listeners. * @param brightness The value to which the brightness is to be set. */ public void setBrightness(float brightness) { @@ -112,7 +120,8 @@ public class BrightnessSetting { // changed. if (brightness != mBrightness) { mPersistentDataStore.setBrightness(mLogicalDisplay.getPrimaryDisplayDeviceLocked(), - brightness); + brightness, mUserSerial + ); } mBrightness = brightness; int toSend = Float.floatToIntBits(mBrightness); diff --git a/services/core/java/com/android/server/display/DisplayDeviceConfig.java b/services/core/java/com/android/server/display/DisplayDeviceConfig.java index ca482dc41ce5..7a797dd2250c 100644 --- a/services/core/java/com/android/server/display/DisplayDeviceConfig.java +++ b/services/core/java/com/android/server/display/DisplayDeviceConfig.java @@ -889,6 +889,13 @@ public class DisplayDeviceConfig { } /** + * @return true if there is sdrHdrRatioMap, false otherwise. + */ + public boolean hasSdrToHdrRatioSpline() { + return mSdrToHdrRatioSpline != null; + } + + /** * Calculate the HDR brightness for the specified SDR brightenss, restricted by the * maxDesiredHdrSdrRatio (the ratio between the HDR luminance and SDR luminance) * diff --git a/services/core/java/com/android/server/display/DisplayManagerService.java b/services/core/java/com/android/server/display/DisplayManagerService.java index 38445295b0f1..be65c531b913 100644 --- a/services/core/java/com/android/server/display/DisplayManagerService.java +++ b/services/core/java/com/android/server/display/DisplayManagerService.java @@ -652,6 +652,12 @@ public final class DisplayManagerService extends SystemService { logicalDisplay.getPrimaryDisplayDeviceLocked().getUniqueId(), userSerial); dpc.setBrightnessConfiguration(config, /* shouldResetShortTermModel= */ true); + // change the brightness value according to the selected user. + final DisplayDevice device = logicalDisplay.getPrimaryDisplayDeviceLocked(); + if (device != null) { + dpc.setBrightness( + mPersistentDataStore.getBrightness(device, userSerial), userSerial); + } } dpc.onSwitchUser(newUserId); }); @@ -3134,8 +3140,9 @@ public final class DisplayManagerService extends SystemService { mBrightnessTracker = new BrightnessTracker(mContext, null); } - final BrightnessSetting brightnessSetting = new BrightnessSetting(mPersistentDataStore, - display, mSyncRoot); + final int userSerial = getUserManager().getUserSerialNumber(mContext.getUserId()); + final BrightnessSetting brightnessSetting = new BrightnessSetting(userSerial, + mPersistentDataStore, display, mSyncRoot); final DisplayPowerControllerInterface displayPowerController; // If display is internal and has a HighBrightnessModeMetadata mapping, use that. diff --git a/services/core/java/com/android/server/display/DisplayPowerController.java b/services/core/java/com/android/server/display/DisplayPowerController.java index 2b5523ae52b0..7d8bde9feabf 100644 --- a/services/core/java/com/android/server/display/DisplayPowerController.java +++ b/services/core/java/com/android/server/display/DisplayPowerController.java @@ -2664,9 +2664,10 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call } @Override - public void setBrightness(float brightnessValue) { + public void setBrightness(float brightnessValue, int userSerial) { // Update the setting, which will eventually call back into DPC to have us actually update // the display with the new value. + mBrightnessSetting.setUserSerial(userSerial); mBrightnessSetting.setBrightness(brightnessValue); if (mDisplayId == Display.DEFAULT_DISPLAY && mPersistBrightnessNitsForDefaultDisplay) { float nits = convertToNits(brightnessValue); diff --git a/services/core/java/com/android/server/display/DisplayPowerController2.java b/services/core/java/com/android/server/display/DisplayPowerController2.java index cc9f5394df9b..040ceccbc7c9 100644 --- a/services/core/java/com/android/server/display/DisplayPowerController2.java +++ b/services/core/java/com/android/server/display/DisplayPowerController2.java @@ -2172,8 +2172,8 @@ final class DisplayPowerController2 implements AutomaticBrightnessController.Cal } @Override - public void setBrightness(float brightnessValue) { - mDisplayBrightnessController.setBrightness(brightnessValue); + public void setBrightness(float brightnessValue, int userSerial) { + mDisplayBrightnessController.setBrightness(brightnessValue, userSerial); } @Override diff --git a/services/core/java/com/android/server/display/DisplayPowerControllerInterface.java b/services/core/java/com/android/server/display/DisplayPowerControllerInterface.java index 73edb970ff95..5fbbcbd2959f 100644 --- a/services/core/java/com/android/server/display/DisplayPowerControllerInterface.java +++ b/services/core/java/com/android/server/display/DisplayPowerControllerInterface.java @@ -29,7 +29,7 @@ import java.io.PrintWriter; * An interface to manage the display's power state and brightness */ public interface DisplayPowerControllerInterface { - + int DEFAULT_USER_SERIAL = -1; /** * Notified when the display is changed. * @@ -98,7 +98,17 @@ public interface DisplayPowerControllerInterface { * Set the screen brightness of the associated display * @param brightness The value to which the brightness is to be set */ - void setBrightness(float brightness); + default void setBrightness(float brightness) { + setBrightness(brightness, DEFAULT_USER_SERIAL); + } + + /** + * Set the screen brightness of the associated display + * @param brightness The value to which the brightness is to be set + * @param userSerial The user for which the brightness value is to be set. Use userSerial = -1, + * if brightness needs to be updated for the current user. + */ + void setBrightness(float brightness, int userSerial); /** * Checks if the proximity sensor is available diff --git a/services/core/java/com/android/server/display/LocalDisplayAdapter.java b/services/core/java/com/android/server/display/LocalDisplayAdapter.java index 79984c9b5355..c7c0fab6140d 100644 --- a/services/core/java/com/android/server/display/LocalDisplayAdapter.java +++ b/services/core/java/com/android/server/display/LocalDisplayAdapter.java @@ -888,7 +888,9 @@ final class LocalDisplayAdapter extends DisplayAdapter { BrightnessSynchronizer.brightnessFloatToInt( sdrBrightnessState)); - handleHdrSdrNitsChanged(nits, sdrNits); + if (getDisplayDeviceConfig().hasSdrToHdrRatioSpline()) { + handleHdrSdrNitsChanged(nits, sdrNits); + } } finally { Trace.traceEnd(Trace.TRACE_TAG_POWER); diff --git a/services/core/java/com/android/server/display/PersistentDataStore.java b/services/core/java/com/android/server/display/PersistentDataStore.java index 6d6ed726161b..2d7792d01c53 100644 --- a/services/core/java/com/android/server/display/PersistentDataStore.java +++ b/services/core/java/com/android/server/display/PersistentDataStore.java @@ -133,6 +133,7 @@ final class PersistentDataStore { private static final String TAG_BRIGHTNESS_NITS_FOR_DEFAULT_DISPLAY = "brightness-nits-for-default-display"; + public static final int DEFAULT_USER_ID = -1; // Remembered Wifi display devices. private ArrayList<WifiDisplay> mRememberedWifiDisplays = new ArrayList<WifiDisplay>(); @@ -294,7 +295,7 @@ final class PersistentDataStore { return false; } - public float getBrightness(DisplayDevice device) { + public float getBrightness(DisplayDevice device, int userSerial) { if (device == null || !device.hasStableUniqueId()) { return Float.NaN; } @@ -302,10 +303,10 @@ final class PersistentDataStore { if (state == null) { return Float.NaN; } - return state.getBrightness(); + return state.getBrightness(userSerial); } - public boolean setBrightness(DisplayDevice displayDevice, float brightness) { + public boolean setBrightness(DisplayDevice displayDevice, float brightness, int userSerial) { if (displayDevice == null || !displayDevice.hasStableUniqueId()) { return false; } @@ -314,7 +315,7 @@ final class PersistentDataStore { return false; } final DisplayState state = getDisplayState(displayDeviceUniqueId, true); - if (state.setBrightness(brightness)) { + if (state.setBrightness(brightness, userSerial)) { setDirty(); return true; } @@ -611,6 +612,7 @@ final class PersistentDataStore { state.saveToXml(serializer); serializer.endTag(null, TAG_DISPLAY); } + serializer.endTag(null, TAG_DISPLAY_STATES); serializer.startTag(null, TAG_STABLE_DEVICE_VALUES); mStableDeviceValues.saveToXml(serializer); @@ -649,7 +651,8 @@ final class PersistentDataStore { private static final class DisplayState { private int mColorMode; - private float mBrightness = Float.NaN; + + private SparseArray<Float> mPerUserBrightness = new SparseArray<>(); private int mWidth; private int mHeight; private float mRefreshRate; @@ -670,16 +673,25 @@ final class PersistentDataStore { return mColorMode; } - public boolean setBrightness(float brightness) { - if (brightness == mBrightness) { + public boolean setBrightness(float brightness, int userSerial) { + // Remove the default user brightness, before setting a new user-specific value. + // This is a one-time operation, required to restructure the config after user-specific + // brightness was introduced. + mPerUserBrightness.remove(DEFAULT_USER_ID); + + if (getBrightness(userSerial) == brightness) { return false; } - mBrightness = brightness; + mPerUserBrightness.set(userSerial, brightness); return true; } - public float getBrightness() { - return mBrightness; + public float getBrightness(int userSerial) { + float brightness = mPerUserBrightness.get(userSerial, Float.NaN); + if (Float.isNaN(brightness)) { + brightness = mPerUserBrightness.get(DEFAULT_USER_ID, Float.NaN); + } + return brightness; } public boolean setBrightnessConfiguration(BrightnessConfiguration configuration, @@ -729,12 +741,7 @@ final class PersistentDataStore { mColorMode = Integer.parseInt(value); break; case TAG_BRIGHTNESS_VALUE: - String brightness = parser.nextText(); - try { - mBrightness = Float.parseFloat(brightness); - } catch (NumberFormatException e) { - mBrightness = Float.NaN; - } + loadBrightnessFromXml(parser); break; case TAG_BRIGHTNESS_CONFIGURATIONS: mDisplayBrightnessConfigurations.loadFromXml(parser); @@ -760,11 +767,12 @@ final class PersistentDataStore { serializer.text(Integer.toString(mColorMode)); serializer.endTag(null, TAG_COLOR_MODE); - serializer.startTag(null, TAG_BRIGHTNESS_VALUE); - if (!Float.isNaN(mBrightness)) { - serializer.text(Float.toString(mBrightness)); + for (int i = 0; i < mPerUserBrightness.size(); i++) { + serializer.startTag(null, TAG_BRIGHTNESS_VALUE); + serializer.attributeInt(null, ATTR_USER_SERIAL, mPerUserBrightness.keyAt(i)); + serializer.text(Float.toString(mPerUserBrightness.valueAt(i))); + serializer.endTag(null, TAG_BRIGHTNESS_VALUE); } - serializer.endTag(null, TAG_BRIGHTNESS_VALUE); serializer.startTag(null, TAG_BRIGHTNESS_CONFIGURATIONS); mDisplayBrightnessConfigurations.saveToXml(serializer); @@ -785,12 +793,33 @@ final class PersistentDataStore { public void dump(final PrintWriter pw, final String prefix) { pw.println(prefix + "ColorMode=" + mColorMode); - pw.println(prefix + "BrightnessValue=" + mBrightness); + pw.println(prefix + "BrightnessValues: "); + for (int i = 0; i < mPerUserBrightness.size(); i++) { + pw.println("User: " + mPerUserBrightness.keyAt(i) + + " Value: " + mPerUserBrightness.valueAt(i)); + } pw.println(prefix + "DisplayBrightnessConfigurations: "); mDisplayBrightnessConfigurations.dump(pw, prefix); pw.println(prefix + "Resolution=" + mWidth + " " + mHeight); pw.println(prefix + "RefreshRate=" + mRefreshRate); } + + private void loadBrightnessFromXml(TypedXmlPullParser parser) + throws IOException, XmlPullParserException { + int userSerial; + try { + userSerial = parser.getAttributeInt(null, ATTR_USER_SERIAL); + } catch (NumberFormatException | XmlPullParserException e) { + userSerial = DEFAULT_USER_ID; + Slog.e(TAG, "Failed to read user serial", e); + } + String brightness = parser.nextText(); + try { + mPerUserBrightness.set(userSerial, Float.parseFloat(brightness)); + } catch (NumberFormatException nfe) { + Slog.e(TAG, "Failed to read brightness", nfe); + } + } } private static final class StableDeviceValues { diff --git a/services/core/java/com/android/server/display/brightness/DisplayBrightnessController.java b/services/core/java/com/android/server/display/brightness/DisplayBrightnessController.java index 7574de841440..2f52b708dfb5 100644 --- a/services/core/java/com/android/server/display/brightness/DisplayBrightnessController.java +++ b/services/core/java/com/android/server/display/brightness/DisplayBrightnessController.java @@ -38,6 +38,8 @@ import java.io.PrintWriter; * display. Applies the chosen brightness. */ public final class DisplayBrightnessController { + private static final int DEFAULT_USER_SERIAL = -1; + // The ID of the display tied to this DisplayBrightnessController private final int mDisplayId; @@ -274,8 +276,16 @@ public final class DisplayBrightnessController { * Notifies the brightnessSetting to persist the supplied brightness value. */ public void setBrightness(float brightnessValue) { + setBrightness(brightnessValue, DEFAULT_USER_SERIAL); + } + + /** + * Notifies the brightnessSetting to persist the supplied brightness value for a user. + */ + public void setBrightness(float brightnessValue, int userSerial) { // Update the setting, which will eventually call back into DPC to have us actually // update the display with the new value. + mBrightnessSetting.setUserSerial(userSerial); mBrightnessSetting.setBrightness(brightnessValue); if (mDisplayId == Display.DEFAULT_DISPLAY && mPersistBrightnessNitsForDefaultDisplay) { float nits = convertToNits(brightnessValue); diff --git a/services/core/java/com/android/server/hdmi/HdmiEarcLocalDeviceTx.java b/services/core/java/com/android/server/hdmi/HdmiEarcLocalDeviceTx.java index 873d5fc92601..4bc20a555dec 100644 --- a/services/core/java/com/android/server/hdmi/HdmiEarcLocalDeviceTx.java +++ b/services/core/java/com/android/server/hdmi/HdmiEarcLocalDeviceTx.java @@ -42,6 +42,12 @@ public class HdmiEarcLocalDeviceTx extends HdmiEarcLocalDevice { // How long to wait for the audio system to report its capabilities after eARC was connected static final long REPORT_CAPS_MAX_DELAY_MS = 2_000; + // Array containing the names of the eARC states. The integer value of the eARC state + // corresponds to the index in the array. + private static final String earcStatusNames[] = {"HDMI_EARC_STATUS_IDLE", + "HDMI_EARC_STATUS_EARC_PENDING", "HDMI_EARC_STATUS_ARC_PENDING", + "HDMI_EARC_STATUS_EARC_CONNECTED"}; + // eARC Capability Data Structure parameters private static final int EARC_CAPS_PAYLOAD_LENGTH = 0x02; private static final int EARC_CAPS_DATA_START = 0x03; @@ -75,11 +81,17 @@ public class HdmiEarcLocalDeviceTx extends HdmiEarcLocalDevice { mReportCapsRunnable = new ReportCapsRunnable(); } + private String earcStatusToString(int status) { + return earcStatusNames[status]; + } + protected void handleEarcStateChange(@Constants.EarcStatus int status) { int oldEarcStatus; + synchronized (mLock) { - HdmiLogger.debug("eARC state change [old:%b new %b]", mEarcStatus, - status); + HdmiLogger.debug("eARC state change [old: %s(%d) new: %s(%d)]", + earcStatusToString(mEarcStatus), mEarcStatus, + earcStatusToString(status), status); oldEarcStatus = mEarcStatus; mEarcStatus = status; } diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java index e56eba659d37..c496857bb63b 100755 --- a/services/core/java/com/android/server/notification/NotificationManagerService.java +++ b/services/core/java/com/android/server/notification/NotificationManagerService.java @@ -6746,6 +6746,8 @@ public class NotificationManagerService extends SystemService { return false; } + mUsageStats.registerEnqueuedByAppAndAccepted(pkg); + if (info != null) { // Cache the shortcut synchronously after the associated notification is posted in case // the app unpublishes this shortcut immediately after posting the notification. If the @@ -7184,11 +7186,12 @@ public class NotificationManagerService extends SystemService { + " cannot create notifications"); } - // rate limit updates that aren't completed progress notifications - if (mNotificationsByKey.get(r.getSbn().getKey()) != null - && !r.getNotification().hasCompletedProgress() - && !isAutogroup) { - + // Rate limit updates that aren't completed progress notifications + // Search for the original one in the posted and not-yet-posted (enqueued) lists. + boolean isUpdate = mNotificationsByKey.get(r.getSbn().getKey()) != null + || findNotificationByListLocked(mEnqueuedNotifications, r.getSbn().getKey()) + != null; + if (isUpdate && !r.getNotification().hasCompletedProgress() && !isAutogroup) { final float appEnqueueRate = mUsageStats.getAppEnqueueRate(pkg); if (appEnqueueRate > mMaxPackageEnqueueRate) { mUsageStats.registerOverRateQuota(pkg); @@ -7813,15 +7816,8 @@ public class NotificationManagerService extends SystemService { boolean posted = false; synchronized (mNotificationLock) { try { - NotificationRecord r = null; - int N = mEnqueuedNotifications.size(); - for (int i = 0; i < N; i++) { - final NotificationRecord enqueued = mEnqueuedNotifications.get(i); - if (Objects.equals(key, enqueued.getKey())) { - r = enqueued; - break; - } - } + NotificationRecord r = findNotificationByListLocked(mEnqueuedNotifications, + key); if (r == null) { Slog.i(TAG, "Cannot find enqueued record for key: " + key); return false; @@ -9486,7 +9482,7 @@ public class NotificationManagerService extends SystemService { * Determine whether the userId applies to the notification in question, either because * they match exactly, or one of them is USER_ALL (which is treated as a wildcard). */ - private boolean notificationMatchesUserId(NotificationRecord r, int userId) { + private static boolean notificationMatchesUserId(NotificationRecord r, int userId) { return // looking for USER_ALL notifications? match everything userId == UserHandle.USER_ALL @@ -9845,9 +9841,9 @@ public class NotificationManagerService extends SystemService { return null; } - @GuardedBy("mNotificationLock") - private NotificationRecord findNotificationByListLocked(ArrayList<NotificationRecord> list, - String pkg, String tag, int id, int userId) { + @Nullable + private static NotificationRecord findNotificationByListLocked( + ArrayList<NotificationRecord> list, String pkg, String tag, int id, int userId) { final int len = list.size(); for (int i = 0; i < len; i++) { NotificationRecord r = list.get(i); @@ -9860,8 +9856,7 @@ public class NotificationManagerService extends SystemService { return null; } - @GuardedBy("mNotificationLock") - private List<NotificationRecord> findNotificationsByListLocked( + private static List<NotificationRecord> findNotificationsByListLocked( ArrayList<NotificationRecord> list, String pkg, String tag, int id, int userId) { List<NotificationRecord> matching = new ArrayList<>(); final int len = list.size(); @@ -9876,9 +9871,9 @@ public class NotificationManagerService extends SystemService { return matching; } - @GuardedBy("mNotificationLock") - private NotificationRecord findNotificationByListLocked(ArrayList<NotificationRecord> list, - String key) { + @Nullable + private static NotificationRecord findNotificationByListLocked( + ArrayList<NotificationRecord> list, String key) { final int N = list.size(); for (int i = 0; i < N; i++) { if (key.equals(list.get(i).getKey())) { @@ -9888,29 +9883,6 @@ public class NotificationManagerService extends SystemService { return null; } - /** - * There may be multiple records that match your criteria. For instance if there have been - * multiple notifications posted which are enqueued for the same pkg, tag, id, userId. This - * method will find all of them in the given list - * @return - */ - @GuardedBy("mNotificationLock") - private List<NotificationRecord> findEnqueuedNotificationsForCriteria( - String pkg, String tag, int id, int userId) { - final ArrayList<NotificationRecord> records = new ArrayList<>(); - final int n = mEnqueuedNotifications.size(); - for (int i = 0; i < n; i++) { - NotificationRecord r = mEnqueuedNotifications.get(i); - if (notificationMatchesUserId(r, userId) - && r.getSbn().getId() == id - && TextUtils.equals(r.getSbn().getTag(), tag) - && r.getSbn().getPackageName().equals(pkg)) { - records.add(r); - } - } - return records; - } - @GuardedBy("mNotificationLock") int indexOfNotificationLocked(String key) { final int N = mNotificationList.size(); diff --git a/services/core/java/com/android/server/notification/NotificationUsageStats.java b/services/core/java/com/android/server/notification/NotificationUsageStats.java index ffe33a832ce4..e960f4ba11fd 100644 --- a/services/core/java/com/android/server/notification/NotificationUsageStats.java +++ b/services/core/java/com/android/server/notification/NotificationUsageStats.java @@ -16,23 +16,16 @@ package com.android.server.notification; -import static android.app.NotificationManager.IMPORTANCE_HIGH; - import android.app.Notification; -import android.content.ContentValues; import android.content.Context; -import android.database.Cursor; -import android.database.sqlite.SQLiteDatabase; -import android.database.sqlite.SQLiteFullException; -import android.database.sqlite.SQLiteOpenHelper; import android.os.Handler; -import android.os.HandlerThread; import android.os.Message; import android.os.SystemClock; import android.text.TextUtils; import android.util.ArraySet; import android.util.Log; +import com.android.internal.annotations.GuardedBy; import com.android.internal.logging.MetricsLogger; import com.android.server.notification.NotificationManagerService.DumpFilter; @@ -42,8 +35,6 @@ import org.json.JSONObject; import java.io.PrintWriter; import java.util.ArrayDeque; -import java.util.Calendar; -import java.util.GregorianCalendar; import java.util.HashMap; import java.util.Map; import java.util.Set; @@ -63,7 +54,6 @@ public class NotificationUsageStats { private static final String TAG = "NotificationUsageStats"; private static final boolean ENABLE_AGGREGATED_IN_MEMORY_STATS = true; - private static final boolean ENABLE_SQLITE_LOG = true; private static final AggregatedStats[] EMPTY_AGGREGATED_STATS = new AggregatedStats[0]; private static final String DEVICE_GLOBAL_STATS = "__global"; // packages start with letters private static final int MSG_EMIT = 1; @@ -73,12 +63,15 @@ public class NotificationUsageStats { public static final int FOUR_HOURS = 1000 * 60 * 60 * 4; private static final long EMIT_PERIOD = DEBUG ? TEN_SECONDS : FOUR_HOURS; - // Guarded by synchronized(this). + @GuardedBy("this") private final Map<String, AggregatedStats> mStats = new HashMap<>(); + @GuardedBy("this") private final ArrayDeque<AggregatedStats[]> mStatsArrays = new ArrayDeque<>(); + @GuardedBy("this") private ArraySet<String> mStatExpiredkeys = new ArraySet<>(); private final Context mContext; private final Handler mHandler; + @GuardedBy("this") private long mLastEmitTime; public NotificationUsageStats(Context context) { @@ -105,11 +98,7 @@ public class NotificationUsageStats { */ public synchronized float getAppEnqueueRate(String packageName) { AggregatedStats stats = getOrCreateAggregatedStatsLocked(packageName); - if (stats != null) { - return stats.getEnqueueRate(SystemClock.elapsedRealtime()); - } else { - return 0f; - } + return stats.getEnqueueRate(SystemClock.elapsedRealtime()); } /** @@ -117,11 +106,7 @@ public class NotificationUsageStats { */ public synchronized boolean isAlertRateLimited(String packageName) { AggregatedStats stats = getOrCreateAggregatedStatsLocked(packageName); - if (stats != null) { - return stats.isAlertRateLimited(); - } else { - return false; - } + return stats.isAlertRateLimited(); } /** @@ -136,16 +121,32 @@ public class NotificationUsageStats { } /** + * Called when a notification that was enqueued by an app is effectively enqueued to be + * posted. This is after rate checking, to update the rate. + * + * <p>Note that if we updated the arrival estimate <em>before</em> checking it, then an app + * enqueueing at slightly above the acceptable rate would never get their notifications + * accepted; updating afterwards allows the rate to dip below the threshold and thus lets + * through some of them. + */ + public synchronized void registerEnqueuedByAppAndAccepted(String packageName) { + final long now = SystemClock.elapsedRealtime(); + AggregatedStats[] aggregatedStatsArray = getAggregatedStatsLocked(packageName); + for (AggregatedStats stats : aggregatedStatsArray) { + stats.updateInterarrivalEstimate(now); + } + releaseAggregatedStatsLocked(aggregatedStatsArray); + } + + /** * Called when a notification has been posted. */ public synchronized void registerPostedByApp(NotificationRecord notification) { - final long now = SystemClock.elapsedRealtime(); - notification.stats.posttimeElapsedMs = now; + notification.stats.posttimeElapsedMs = SystemClock.elapsedRealtime(); AggregatedStats[] aggregatedStatsArray = getAggregatedStatsLocked(notification); for (AggregatedStats stats : aggregatedStatsArray) { stats.numPostedByApp++; - stats.updateInterarrivalEstimate(now); stats.countApiUse(notification); stats.numUndecoratedRemoteViews += (notification.hasUndecoratedRemoteView() ? 1 : 0); } @@ -161,7 +162,6 @@ public class NotificationUsageStats { AggregatedStats[] aggregatedStatsArray = getAggregatedStatsLocked(notification); for (AggregatedStats stats : aggregatedStatsArray) { stats.numUpdatedByApp++; - stats.updateInterarrivalEstimate(SystemClock.elapsedRealtime()); stats.countApiUse(notification); } releaseAggregatedStatsLocked(aggregatedStatsArray); @@ -257,12 +257,12 @@ public class NotificationUsageStats { } } - // Locked by this. + @GuardedBy("this") private AggregatedStats[] getAggregatedStatsLocked(NotificationRecord record) { return getAggregatedStatsLocked(record.getSbn().getPackageName()); } - // Locked by this. + @GuardedBy("this") private AggregatedStats[] getAggregatedStatsLocked(String packageName) { if (!ENABLE_AGGREGATED_IN_MEMORY_STATS) { return EMPTY_AGGREGATED_STATS; @@ -277,7 +277,7 @@ public class NotificationUsageStats { return array; } - // Locked by this. + @GuardedBy("this") private void releaseAggregatedStatsLocked(AggregatedStats[] array) { for(int i = 0; i < array.length; i++) { array[i] = null; @@ -285,7 +285,7 @@ public class NotificationUsageStats { mStatsArrays.offer(array); } - // Locked by this. + @GuardedBy("this") private AggregatedStats getOrCreateAggregatedStatsLocked(String key) { AggregatedStats result = mStats.get(key); if (result == null) { diff --git a/services/core/java/com/android/server/notification/RateEstimator.java b/services/core/java/com/android/server/notification/RateEstimator.java index a2f93dce2bca..19768a286fd9 100644 --- a/services/core/java/com/android/server/notification/RateEstimator.java +++ b/services/core/java/com/android/server/notification/RateEstimator.java @@ -22,9 +22,10 @@ package com.android.server.notification; * * {@hide} */ -public class RateEstimator { - private static final double RATE_ALPHA = 0.8; +class RateEstimator { + private static final double RATE_ALPHA = 0.5; private static final double MINIMUM_DT = 0.0005; + private Long mLastEventTime; private double mInterarrivalTime; @@ -34,18 +35,12 @@ public class RateEstimator { } /** Update the estimate to account for an event that just happened. */ - public float update(long now) { - float rate; - if (mLastEventTime == null) { - // No last event time, rate is zero. - rate = 0f; - } else { + public void update(long now) { + if (mLastEventTime != null) { // Calculate the new inter-arrival time based on last event time. mInterarrivalTime = getInterarrivalEstimate(now); - rate = (float) (1.0 / mInterarrivalTime); } mLastEventTime = now; - return rate; } /** @return the estimated rate if there were a new event right now. */ diff --git a/services/core/java/com/android/server/policy/PhoneWindowManager.java b/services/core/java/com/android/server/policy/PhoneWindowManager.java index b1a289f046bd..2806a1176e1c 100644 --- a/services/core/java/com/android/server/policy/PhoneWindowManager.java +++ b/services/core/java/com/android/server/policy/PhoneWindowManager.java @@ -6053,12 +6053,6 @@ public class PhoneWindowManager implements WindowManagerPolicy { } @Override - @WindowManagerFuncs.LidState - public int getLidState() { - return mDefaultDisplayPolicy.getLidState(); - } - - @Override public void dumpDebug(ProtoOutputStream proto, long fieldId) { final long token = proto.start(fieldId); proto.write(ROTATION_MODE, mDefaultDisplayRotation.getUserRotationMode()); diff --git a/services/core/java/com/android/server/policy/WindowManagerPolicy.java b/services/core/java/com/android/server/policy/WindowManagerPolicy.java index 3da78123016b..887f9461bdce 100644 --- a/services/core/java/com/android/server/policy/WindowManagerPolicy.java +++ b/services/core/java/com/android/server/policy/WindowManagerPolicy.java @@ -218,14 +218,6 @@ public interface WindowManagerPolicy extends WindowManagerPolicyConstants { * between it and the policy. */ public interface WindowManagerFuncs { - @IntDef(prefix = { "LID_" }, value = { - LID_ABSENT, - LID_CLOSED, - LID_OPEN, - }) - @Retention(RetentionPolicy.SOURCE) - @interface LidState{} - public static final int LID_ABSENT = -1; public static final int LID_CLOSED = 0; public static final int LID_OPEN = 1; @@ -239,9 +231,8 @@ public interface WindowManagerPolicy extends WindowManagerPolicyConstants { public static final int CAMERA_LENS_COVERED = 1; /** - * Returns a {@link LidState} that describes the current state of the lid switch. + * Returns a code that describes the current state of the lid switch. */ - @LidState public int getLidState(); /** @@ -291,7 +282,7 @@ public interface WindowManagerPolicy extends WindowManagerPolicyConstants { /** * Convert the lid state to a human readable format. */ - static String lidStateToString(@LidState int lid) { + static String lidStateToString(int lid) { switch (lid) { case LID_ABSENT: return "LID_ABSENT"; @@ -1250,11 +1241,4 @@ public interface WindowManagerPolicy extends WindowManagerPolicyConstants { * @return {@code true} if the key will be handled globally. */ boolean isGlobalKey(int keyCode); - - /** - * Returns a {@link WindowManagerFuncs.LidState} that describes the current state of - * the lid switch. - */ - @WindowManagerFuncs.LidState - int getLidState(); } diff --git a/services/core/java/com/android/server/power/PowerManagerService.java b/services/core/java/com/android/server/power/PowerManagerService.java index 712be36e51c6..b8c5b3f5524a 100644 --- a/services/core/java/com/android/server/power/PowerManagerService.java +++ b/services/core/java/com/android/server/power/PowerManagerService.java @@ -33,7 +33,6 @@ import static android.os.PowerManagerInternal.isInteractive; import static android.os.PowerManagerInternal.wakefulnessToString; import static com.android.internal.util.LatencyTracker.ACTION_TURN_ON_SCREEN; -import static com.android.server.policy.WindowManagerPolicy.WindowManagerFuncs.LID_CLOSED; import android.annotation.IntDef; import android.annotation.NonNull; @@ -5734,11 +5733,6 @@ public final class PowerManagerService extends SystemService @Override // Binder call public void wakeUp(long eventTime, @WakeReason int reason, String details, String opPackageName) { - if (mPolicy.getLidState() == LID_CLOSED) { - Slog.d(TAG, "Ignoring wake up call due to the lid being closed"); - return; - } - final long now = mClock.uptimeMillis(); if (eventTime > now) { Slog.e(TAG, "Event time " + eventTime + " cannot be newer than " + now); diff --git a/services/core/java/com/android/server/stats/pull/StatsPullAtomService.java b/services/core/java/com/android/server/stats/pull/StatsPullAtomService.java index e82521584731..9128974fa9d3 100644 --- a/services/core/java/com/android/server/stats/pull/StatsPullAtomService.java +++ b/services/core/java/com/android/server/stats/pull/StatsPullAtomService.java @@ -1004,7 +1004,8 @@ public class StatsPullAtomService extends SystemService { } private void initAndRegisterDeferredPullers() { - mUwbManager = mContext.getSystemService(UwbManager.class); + mUwbManager = mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_UWB) + ? mContext.getSystemService(UwbManager.class) : null; registerUwbActivityInfo(); } @@ -2172,6 +2173,9 @@ public class StatsPullAtomService extends SystemService { } private void registerUwbActivityInfo() { + if (mUwbManager == null) { + return; + } int tagId = FrameworkStatsLog.UWB_ACTIVITY_INFO; mStatsManager.setPullAtomCallback( tagId, diff --git a/services/core/java/com/android/server/wm/DisplayPolicy.java b/services/core/java/com/android/server/wm/DisplayPolicy.java index 8dcb042b3733..dd62b21bc7d1 100644 --- a/services/core/java/com/android/server/wm/DisplayPolicy.java +++ b/services/core/java/com/android/server/wm/DisplayPolicy.java @@ -225,7 +225,6 @@ public class DisplayPolicy { /** Currently it can only be non-null when physical display switch happens. */ private DecorInsets.Cache mCachedDecorInsets; - @WindowManagerFuncs.LidState private volatile int mLidState = LID_ABSENT; private volatile int mDockMode = Intent.EXTRA_DOCK_STATE_UNDOCKED; private volatile boolean mHdmiPlugged; @@ -753,11 +752,10 @@ public class DisplayPolicy { return mNavigationBarCanMove; } - public void setLidState(@WindowManagerFuncs.LidState int lidState) { + public void setLidState(int lidState) { mLidState = lidState; } - @WindowManagerFuncs.LidState public int getLidState() { return mLidState; } @@ -2532,7 +2530,10 @@ public class DisplayPolicy { mNavBarBackgroundWindowCandidate, mDisplayContent.mInputMethodWindow, mNavigationBarPosition); - final boolean drawBackground = navBackgroundWin != null; + final boolean drawBackground = navBackgroundWin != null + // There is no app window showing underneath nav bar. (e.g., The screen is locked.) + // Let system windows (ex: notification shade) draw nav bar background. + || mNavBarBackgroundWindowCandidate == null; if (mNavBarOpacityMode == NAV_BAR_FORCE_TRANSPARENT) { if (drawBackground) { diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyEngine.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyEngine.java index 79c03497223a..5ba22830eec9 100644 --- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyEngine.java +++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyEngine.java @@ -454,8 +454,8 @@ final class DevicePolicyEngine { onGlobalPolicyChanged(policyDefinition, enforcingAdmin); } - applyGlobalPolicyOnUsersWithLocalPoliciesLocked( - policyDefinition, enforcingAdmin, /* value= */ null, /* enforcePolicy= */ true); + applyGlobalPolicyOnUsersWithLocalPoliciesLocked(policyDefinition, enforcingAdmin, + /* value= */ null, /* skipEnforcePolicy= */ false); sendPolicyResultToAdmin( enforcingAdmin, diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java index 7464045ad696..edb8e0c5aa64 100644 --- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java +++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java @@ -14906,8 +14906,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { policy = new LockTaskPolicy(currentPolicy); policy.setPackages(Set.of(packages)); } - if (policy.getPackages().isEmpty() - && policy.getFlags() == DevicePolicyManager.LOCK_TASK_FEATURE_NONE) { + if (policy.getPackages().isEmpty()) { mDevicePolicyEngine.removeLocalPolicy( PolicyDefinition.LOCK_TASK, enforcingAdmin, @@ -20689,7 +20688,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { private void addUserControlDisabledPackages(CallerIdentity caller, EnforcingAdmin enforcingAdmin, Set<String> packages) { - if (isCallerDeviceOwner(caller)) { + if (isDeviceOwner(caller)) { mDevicePolicyEngine.setGlobalPolicy( PolicyDefinition.USER_CONTROLLED_DISABLED_PACKAGES, enforcingAdmin, @@ -20705,7 +20704,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { private void removeUserControlDisabledPackages(CallerIdentity caller, EnforcingAdmin enforcingAdmin) { - if (isCallerDeviceOwner(caller)) { + if (isDeviceOwner(caller)) { mDevicePolicyEngine.removeGlobalPolicy( PolicyDefinition.USER_CONTROLLED_DISABLED_PACKAGES, enforcingAdmin); @@ -20717,12 +20716,6 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { } } - private boolean isCallerDeviceOwner(CallerIdentity caller) { - synchronized (getLockObject()) { - return getDeviceOwnerUserIdUncheckedLocked() == caller.getUserId(); - } - } - @Override public List<String> getUserControlDisabledPackages(ComponentName who, String callerPackageName) { diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/PolicyEnforcerCallbacks.java b/services/devicepolicy/java/com/android/server/devicepolicy/PolicyEnforcerCallbacks.java index 454337fcf141..3b048b250075 100644 --- a/services/devicepolicy/java/com/android/server/devicepolicy/PolicyEnforcerCallbacks.java +++ b/services/devicepolicy/java/com/android/server/devicepolicy/PolicyEnforcerCallbacks.java @@ -167,7 +167,7 @@ final class PolicyEnforcerCallbacks { packages == null ? null : packages.stream().toList()); LocalServices.getService(UsageStatsManagerInternal.class) .setAdminProtectedPackages( - packages == null ? null : new ArraySet(packages), userId); + packages == null ? null : new ArraySet<>(packages), userId); }); return true; } diff --git a/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueTest.java b/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueTest.java index 636576492362..4989f841a275 100644 --- a/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueTest.java +++ b/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueTest.java @@ -276,7 +276,11 @@ public class BroadcastQueueTest { switch (behavior) { case SUCCESS: case SUCCESS_PREDECESSOR: - mQueue.onApplicationAttachedLocked(deliverRes); + try { + mQueue.onApplicationAttachedLocked(deliverRes); + } catch (BroadcastDeliveryFailedException e) { + Log.v(TAG, "Error while invoking onApplicationAttachedLocked", e); + } break; case FAIL_TIMEOUT: case FAIL_TIMEOUT_PREDECESSOR: @@ -1120,6 +1124,7 @@ public class BroadcastQueueTest { final ProcessRecord restartedReceiverApp = mAms.getProcessRecordLocked(PACKAGE_GREEN, getUidForPackage(PACKAGE_GREEN)); assertNotEquals(receiverApp, restartedReceiverApp); + verifyScheduleReceiver(restartedReceiverApp, airplane); verifyScheduleReceiver(restartedReceiverApp, timezone); } @@ -1304,12 +1309,7 @@ public class BroadcastQueueTest { final ProcessRecord receiverOrangeApp = mAms.getProcessRecordLocked(PACKAGE_ORANGE, getUidForPackage(PACKAGE_ORANGE)); - if (mImpl == Impl.MODERN) { - // Modern queue does not retry sending a broadcast once any broadcast delivery fails. - assertNull(receiverGreenApp); - } else { - verifyScheduleReceiver(times(1), receiverGreenApp, airplane); - } + verifyScheduleReceiver(times(1), receiverGreenApp, airplane); verifyScheduleRegisteredReceiver(times(1), receiverBlueApp, airplane); verifyScheduleReceiver(times(1), receiverYellowApp, airplane); verifyScheduleReceiver(times(1), receiverOrangeApp, timezone); diff --git a/services/tests/mockingservicestests/src/com/android/server/display/LocalDisplayAdapterTest.java b/services/tests/mockingservicestests/src/com/android/server/display/LocalDisplayAdapterTest.java index 5f82ec1dde02..b7dbaf93b9e2 100644 --- a/services/tests/mockingservicestests/src/com/android/server/display/LocalDisplayAdapterTest.java +++ b/services/tests/mockingservicestests/src/com/android/server/display/LocalDisplayAdapterTest.java @@ -27,6 +27,7 @@ import static org.junit.Assert.assertArrayEquals; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertTrue; +import static org.junit.Assume.assumeTrue; import static org.mockito.ArgumentMatchers.anyFloat; import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.Mockito.mock; @@ -981,6 +982,7 @@ public class LocalDisplayAdapterTest { DisplayDevice displayDevice = mListener.addedDisplays.get(0); // Turn on / initialize + assumeTrue(displayDevice.getDisplayDeviceConfig().hasSdrToHdrRatioSpline()); Runnable changeStateRunnable = displayDevice.requestDisplayStateLocked(Display.STATE_ON, 0, 0); changeStateRunnable.run(); diff --git a/services/tests/servicestests/src/com/android/server/display/PersistentDataStoreTest.java b/services/tests/servicestests/src/com/android/server/display/PersistentDataStoreTest.java index 817b245a78bf..642f54c25a46 100644 --- a/services/tests/servicestests/src/com/android/server/display/PersistentDataStoreTest.java +++ b/services/tests/servicestests/src/com/android/server/display/PersistentDataStoreTest.java @@ -60,6 +60,114 @@ public class PersistentDataStoreTest { } @Test + public void testLoadBrightness() { + final String uniqueDisplayId = "test:123"; + final DisplayDevice testDisplayDevice = new DisplayDevice( + null, null, uniqueDisplayId, null) { + @Override + public boolean hasStableUniqueId() { + return true; + } + + @Override + public DisplayDeviceInfo getDisplayDeviceInfoLocked() { + return null; + } + }; + + String contents = "<?xml version='1.0' encoding='utf-8' standalone='yes' ?>\n" + + "<display-manager-state>\n" + + " <display-states>\n" + + " <display unique-id=\"test:123\">\n" + + " <brightness-value user-serial=\"1\">0.1</brightness-value>\n" + + " <brightness-value user-serial=\"2\">0.2</brightness-value>\n" + + " </display>\n" + + " </display-states>\n" + + "</display-manager-state>\n"; + + InputStream is = new ByteArrayInputStream(contents.getBytes(StandardCharsets.UTF_8)); + mInjector.setReadStream(is); + mDataStore.loadIfNeeded(); + + float brightness = mDataStore.getBrightness(testDisplayDevice, 1); + assertEquals(0.1, brightness, 0.01); + + brightness = mDataStore.getBrightness(testDisplayDevice, 2); + assertEquals(0.2, brightness, 0.01); + } + + @Test + public void testSetBrightness_brightnessTagWithNoUserId_updatesToBrightnessTagWithUserId() { + final String uniqueDisplayId = "test:123"; + final DisplayDevice testDisplayDevice = + new DisplayDevice(null, null, uniqueDisplayId, null) { + @Override + public boolean hasStableUniqueId() { + return true; + } + + @Override + public DisplayDeviceInfo getDisplayDeviceInfoLocked() { + return null; + } + }; + + String contents = "<?xml version='1.0' encoding='utf-8' standalone='yes' ?>\n" + + "<display-manager-state>\n" + + " <display-states>\n" + + " <color-mode>0</color-mode>\n" + + " <display unique-id=\"test:123\">\n" + + " <brightness-value>0.5</brightness-value>\n" + + " </display>\n" + + " </display-states>\n" + + "</display-manager-state>\n"; + + InputStream is = new ByteArrayInputStream(contents.getBytes(StandardCharsets.UTF_8)); + mInjector.setReadStream(is); + mDataStore.loadIfNeeded(); + + float user1Brightness = mDataStore.getBrightness(testDisplayDevice, 1 /* userSerial */); + float user2Brightness = mDataStore.getBrightness(testDisplayDevice, 2 /* userSerial */); + assertEquals(0.5, user1Brightness, 0.01); + assertEquals(0.5, user2Brightness, 0.01); + + // Override the value for user 2. Default user must have been removed. + mDataStore.setBrightness(testDisplayDevice, 0.2f, 2 /* userSerial */ /* brightness*/); + + user1Brightness = mDataStore.getBrightness(testDisplayDevice, 1 /* userSerial */); + user2Brightness = mDataStore.getBrightness(testDisplayDevice, 2 /* userSerial */); + assertTrue(Float.isNaN(user1Brightness)); + assertEquals(0.2f, user2Brightness, 0.01); + + // Override the value for user 1. User-specific brightness values should co-exist. + mDataStore.setBrightness(testDisplayDevice, 0.1f, 1 /* userSerial */ /* brightness*/); + user1Brightness = mDataStore.getBrightness(testDisplayDevice, 1 /* userSerial */); + user2Brightness = mDataStore.getBrightness(testDisplayDevice, 2 /* userSerial */); + assertEquals(0.1f, user1Brightness, 0.01); + assertEquals(0.2f, user2Brightness, 0.01); + + // Validate saveIfNeeded writes user-specific brightnes. + final ByteArrayOutputStream baos = new ByteArrayOutputStream(); + mInjector.setWriteStream(baos); + mDataStore.saveIfNeeded(); + mTestLooper.dispatchAll(); + assertTrue(mInjector.wasWriteSuccessful()); + TestInjector newInjector = new TestInjector(); + PersistentDataStore newDataStore = new PersistentDataStore(newInjector); + ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray()); + newInjector.setReadStream(bais); + newDataStore.loadIfNeeded(); + + user1Brightness = newDataStore.getBrightness(testDisplayDevice, 1 /* userSerial */); + user2Brightness = newDataStore.getBrightness(testDisplayDevice, 2 /* userSerial */); + float unknownUserBrightness = + newDataStore.getBrightness(testDisplayDevice, 999 /* userSerial */); + assertEquals(0.1f, user1Brightness, 0.01); + assertEquals(0.2f, user2Brightness, 0.01); + assertTrue(Float.isNaN(unknownUserBrightness)); + } + + @Test public void testLoadingBrightnessConfigurations() { String contents = "<?xml version='1.0' encoding='utf-8' standalone='yes' ?>\n" + "<display-manager-state>\n" @@ -374,7 +482,7 @@ public class PersistentDataStoreTest { ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray()); newInjector.setReadStream(bais); newDataStore.loadIfNeeded(); - assertTrue(Float.isNaN(mDataStore.getBrightness(testDisplayDevice))); + assertTrue(Float.isNaN(mDataStore.getBrightness(testDisplayDevice, 1 /* userSerial */))); } @Test diff --git a/services/tests/servicestests/src/com/android/server/display/brightness/DisplayBrightnessControllerTest.java b/services/tests/servicestests/src/com/android/server/display/brightness/DisplayBrightnessControllerTest.java index e6d3bbc53c83..c4f483810478 100644 --- a/services/tests/servicestests/src/com/android/server/display/brightness/DisplayBrightnessControllerTest.java +++ b/services/tests/servicestests/src/com/android/server/display/brightness/DisplayBrightnessControllerTest.java @@ -19,6 +19,7 @@ package com.android.server.display.brightness; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; +import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.Mockito.clearInvocations; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; @@ -247,6 +248,7 @@ public final class DisplayBrightnessControllerTest { 0.0f); verify(mBrightnessChangeExecutor).execute(mOnBrightnessChangeRunnable); verify(mBrightnessSetting).setBrightness(brightnessValue); + verify(mBrightnessSetting).setUserSerial(anyInt()); // Does nothing if the value is invalid mDisplayBrightnessController.updateScreenBrightnessSetting(Float.NaN); @@ -358,4 +360,28 @@ public final class DisplayBrightnessControllerTest { verify(mBrightnessSetting, never()).getBrightnessNitsForDefaultDisplay(); verify(mBrightnessSetting, never()).setBrightness(brightness); } + + @Test + public void testChangeBrightnessNitsWhenUserChanges() { + float brightnessValue1 = 0.3f; + float nits1 = 200f; + float brightnessValue2 = 0.5f; + float nits2 = 300f; + AutomaticBrightnessController automaticBrightnessController = + mock(AutomaticBrightnessController.class); + when(automaticBrightnessController.convertToNits(brightnessValue1)).thenReturn(nits1); + when(automaticBrightnessController.convertToNits(brightnessValue2)).thenReturn(nits2); + mDisplayBrightnessController.setAutomaticBrightnessController( + automaticBrightnessController); + + mDisplayBrightnessController.setBrightness(brightnessValue1, 1 /* user-serial */); + verify(mBrightnessSetting).setUserSerial(1); + verify(mBrightnessSetting).setBrightness(brightnessValue1); + verify(mBrightnessSetting).setBrightnessNitsForDefaultDisplay(nits1); + + mDisplayBrightnessController.setBrightness(brightnessValue2, 2 /* user-serial */); + verify(mBrightnessSetting).setUserSerial(2); + verify(mBrightnessSetting).setBrightness(brightnessValue2); + verify(mBrightnessSetting).setBrightnessNitsForDefaultDisplay(nits2); + } } diff --git a/services/tests/servicestests/src/com/android/server/power/PowerManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/power/PowerManagerServiceTest.java index 7aec04568e0f..933f00231313 100644 --- a/services/tests/servicestests/src/com/android/server/power/PowerManagerServiceTest.java +++ b/services/tests/servicestests/src/com/android/server/power/PowerManagerServiceTest.java @@ -26,9 +26,6 @@ import static android.os.PowerManagerInternal.WAKEFULNESS_AWAKE; import static android.os.PowerManagerInternal.WAKEFULNESS_DOZING; import static android.os.PowerManagerInternal.WAKEFULNESS_DREAMING; -import static com.android.server.policy.WindowManagerPolicy.WindowManagerFuncs.LID_ABSENT; -import static com.android.server.policy.WindowManagerPolicy.WindowManagerFuncs.LID_CLOSED; - import static com.google.common.truth.Truth.assertThat; import static org.junit.Assert.assertFalse; @@ -148,7 +145,6 @@ public class PowerManagerServiceTest { @Mock private ActivityManagerInternal mActivityManagerInternalMock; @Mock private AttentionManagerInternal mAttentionManagerInternalMock; @Mock private DreamManagerInternal mDreamManagerInternalMock; - @Mock private WindowManagerPolicy mPolicyMock; @Mock private PowerManagerService.NativeWrapper mNativeWrapperMock; @Mock private Notifier mNotifierMock; @Mock private WirelessChargerDetector mWirelessChargerDetectorMock; @@ -209,7 +205,6 @@ public class PowerManagerServiceTest { .thenReturn(true); when(mSystemPropertiesMock.get(eq(SYSTEM_PROPERTY_QUIESCENT), anyString())).thenReturn(""); when(mAmbientDisplayConfigurationMock.ambientDisplayAvailable()).thenReturn(true); - when(mPolicyMock.getLidState()).thenReturn(LID_ABSENT); addLocalServiceMock(LightsManager.class, mLightsManagerMock); addLocalServiceMock(DisplayManagerInternal.class, mDisplayManagerInternalMock); @@ -217,7 +212,6 @@ public class PowerManagerServiceTest { addLocalServiceMock(ActivityManagerInternal.class, mActivityManagerInternalMock); addLocalServiceMock(AttentionManagerInternal.class, mAttentionManagerInternalMock); addLocalServiceMock(DreamManagerInternal.class, mDreamManagerInternalMock); - addLocalServiceMock(WindowManagerPolicy.class, mPolicyMock); mContextSpy = spy(new ContextWrapper(ApplicationProvider.getApplicationContext())); mResourcesSpy = spy(mContextSpy.getResources()); @@ -684,20 +678,6 @@ public class PowerManagerServiceTest { assertThat(mService.getGlobalWakefulnessLocked()).isEqualTo(WAKEFULNESS_AWAKE); } - @Test - public void testWakefulnessAwake_ShouldNotWakeUpWhenLidClosed() { - when(mPolicyMock.getLidState()).thenReturn(LID_CLOSED); - createService(); - startSystem(); - forceSleep(); - - mService.getBinderServiceInstance().wakeUp(mClock.now(), - PowerManager.WAKE_REASON_POWER_BUTTON, - "testing IPowerManager.wakeUp()", "pkg.name"); - - assertThat(mService.getGlobalWakefulnessLocked()).isEqualTo(WAKEFULNESS_ASLEEP); - } - /** * Tests a series of variants that control whether a device wakes-up when it is plugged in * or docked. diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java index 9166b3d75f60..8f7f2f66308b 100755 --- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java +++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java @@ -81,6 +81,7 @@ import static com.android.internal.config.sysui.SystemUiSystemPropertiesFlags.No import static com.android.internal.config.sysui.SystemUiSystemPropertiesFlags.NotificationFlags.FSI_FORCE_DEMOTE; import static com.android.internal.config.sysui.SystemUiSystemPropertiesFlags.NotificationFlags.SHOW_STICKY_HUN_FOR_DENIED_FSI; import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_USER_LOCKDOWN; +import static com.android.server.notification.NotificationManagerService.DEFAULT_MAX_NOTIFICATION_ENQUEUE_RATE; import static com.android.server.notification.NotificationRecordLogger.NotificationReportedEvent.NOTIFICATION_ADJUSTED; import static com.android.server.notification.NotificationRecordLogger.NotificationReportedEvent.NOTIFICATION_POSTED; import static com.android.server.notification.NotificationRecordLogger.NotificationReportedEvent.NOTIFICATION_UPDATED; @@ -678,6 +679,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { @After public void assertAllTrackersFinishedOrCancelled() { + waitForIdle(); // Finish async work. // Verify that no trackers were left dangling. for (PostNotificationTracker tracker : mPostNotificationTrackerFactory.mCreatedTrackers) { assertThat(tracker.isOngoing()).isFalse(); @@ -11768,6 +11770,103 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { assertFalse(n.isUserInitiatedJob()); } + @Test + public void enqueue_updatesEnqueueRate() throws Exception { + Notification n = generateNotificationRecord(null).getNotification(); + + mBinderService.enqueueNotificationWithTag(PKG, PKG, "tag", 0, n, mUserId); + // Don't waitForIdle() here. We want to verify the "intermediate" state. + + verify(mUsageStats).registerEnqueuedByApp(eq(PKG)); + verify(mUsageStats).registerEnqueuedByAppAndAccepted(eq(PKG)); + verify(mUsageStats, never()).registerPostedByApp(any()); + + waitForIdle(); + } + + @Test + public void enqueue_withPost_updatesEnqueueRateAndPost() throws Exception { + Notification n = generateNotificationRecord(null).getNotification(); + + mBinderService.enqueueNotificationWithTag(PKG, PKG, "tag", 0, n, mUserId); + waitForIdle(); + + verify(mUsageStats).registerEnqueuedByApp(eq(PKG)); + verify(mUsageStats).registerEnqueuedByAppAndAccepted(eq(PKG)); + verify(mUsageStats).registerPostedByApp(any()); + } + + @Test + public void enqueueNew_whenOverEnqueueRate_accepts() throws Exception { + Notification n = generateNotificationRecord(null).getNotification(); + when(mUsageStats.getAppEnqueueRate(eq(PKG))) + .thenReturn(DEFAULT_MAX_NOTIFICATION_ENQUEUE_RATE + 1f); + + mBinderService.enqueueNotificationWithTag(PKG, PKG, "tag", 0, n, mUserId); + waitForIdle(); + + assertThat(mService.mNotificationsByKey).hasSize(1); + verify(mUsageStats).registerEnqueuedByApp(eq(PKG)); + verify(mUsageStats).registerEnqueuedByAppAndAccepted(eq(PKG)); + verify(mUsageStats).registerPostedByApp(any()); + } + + @Test + public void enqueueUpdate_whenBelowMaxEnqueueRate_accepts() throws Exception { + // Post the first version. + Notification original = generateNotificationRecord(null).getNotification(); + original.when = 111; + mBinderService.enqueueNotificationWithTag(PKG, PKG, "tag", 0, original, mUserId); + waitForIdle(); + assertThat(mService.mNotificationList).hasSize(1); + assertThat(mService.mNotificationList.get(0).getNotification().when).isEqualTo(111); + + reset(mUsageStats); + when(mUsageStats.getAppEnqueueRate(eq(PKG))) + .thenReturn(DEFAULT_MAX_NOTIFICATION_ENQUEUE_RATE - 1f); + + // Post the update. + Notification update = generateNotificationRecord(null).getNotification(); + update.when = 222; + mBinderService.enqueueNotificationWithTag(PKG, PKG, "tag", 0, update, mUserId); + waitForIdle(); + + verify(mUsageStats).registerEnqueuedByApp(eq(PKG)); + verify(mUsageStats).registerEnqueuedByAppAndAccepted(eq(PKG)); + verify(mUsageStats, never()).registerPostedByApp(any()); + verify(mUsageStats).registerUpdatedByApp(any(), any()); + assertThat(mService.mNotificationList).hasSize(1); + assertThat(mService.mNotificationList.get(0).getNotification().when).isEqualTo(222); + } + + @Test + public void enqueueUpdate_whenAboveMaxEnqueueRate_rejects() throws Exception { + // Post the first version. + Notification original = generateNotificationRecord(null).getNotification(); + original.when = 111; + mBinderService.enqueueNotificationWithTag(PKG, PKG, "tag", 0, original, mUserId); + waitForIdle(); + assertThat(mService.mNotificationList).hasSize(1); + assertThat(mService.mNotificationList.get(0).getNotification().when).isEqualTo(111); + + reset(mUsageStats); + when(mUsageStats.getAppEnqueueRate(eq(PKG))) + .thenReturn(DEFAULT_MAX_NOTIFICATION_ENQUEUE_RATE + 1f); + + // Post the update. + Notification update = generateNotificationRecord(null).getNotification(); + update.when = 222; + mBinderService.enqueueNotificationWithTag(PKG, PKG, "tag", 0, update, mUserId); + waitForIdle(); + + verify(mUsageStats).registerEnqueuedByApp(eq(PKG)); + verify(mUsageStats, never()).registerEnqueuedByAppAndAccepted(any()); + verify(mUsageStats, never()).registerPostedByApp(any()); + verify(mUsageStats, never()).registerUpdatedByApp(any(), any()); + assertThat(mService.mNotificationList).hasSize(1); + assertThat(mService.mNotificationList.get(0).getNotification().when).isEqualTo(111); // old + } + private void setDpmAppOppsExemptFromDismissal(boolean isOn) { DeviceConfig.setProperty( DeviceConfig.NAMESPACE_DEVICE_POLICY_MANAGER, diff --git a/services/tests/uiservicestests/src/com/android/server/notification/RateEstimatorTest.java b/services/tests/uiservicestests/src/com/android/server/notification/RateEstimatorTest.java index 68aa1dc1bf3b..4840f07285d6 100644 --- a/services/tests/uiservicestests/src/com/android/server/notification/RateEstimatorTest.java +++ b/services/tests/uiservicestests/src/com/android/server/notification/RateEstimatorTest.java @@ -15,8 +15,9 @@ */ package com.android.server.notification; -import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertTrue; +import static com.google.common.truth.Truth.assertThat; + +import static java.util.concurrent.TimeUnit.HOURS; import android.test.suitebuilder.annotation.SmallTest; @@ -42,110 +43,120 @@ public class RateEstimatorTest extends UiServiceTestCase { @Test public void testRunningTimeBackwardDoesntExplodeUpdate() throws Exception { - assertUpdateTime(mTestStartTime); - assertUpdateTime(mTestStartTime - 1000L); + updateAndVerifyRate(mTestStartTime); + updateAndVerifyRate(mTestStartTime - 1000L); } @Test public void testRunningTimeBackwardDoesntExplodeGet() throws Exception { - assertUpdateTime(mTestStartTime); + updateAndVerifyRate(mTestStartTime); final float rate = mEstimator.getRate(mTestStartTime - 1000L); - assertFalse(Float.isInfinite(rate)); - assertFalse(Float.isNaN(rate)); + assertThat(rate).isFinite(); } @Test public void testInstantaneousEventsDontExplodeUpdate() throws Exception { - assertUpdateTime(mTestStartTime); - assertUpdateTime(mTestStartTime); + updateAndVerifyRate(mTestStartTime); + updateAndVerifyRate(mTestStartTime); } @Test public void testInstantaneousEventsDontExplodeGet() throws Exception { - assertUpdateTime(mTestStartTime); - assertUpdateTime(mTestStartTime); + updateAndVerifyRate(mTestStartTime); + updateAndVerifyRate(mTestStartTime); final float rate = mEstimator.getRate(mTestStartTime); - assertFalse(Float.isInfinite(rate)); - assertFalse(Float.isNaN(rate)); + assertThat(rate).isFinite(); } @Test public void testInstantaneousBurstIsEstimatedUnderTwoPercent() throws Exception { - assertUpdateTime(mTestStartTime); + updateAndVerifyRate(mTestStartTime); long eventStart = mTestStartTime + 1000; // start event a long time after initialization long nextEventTime = postEvents(eventStart, 0, 5); // five events at \inf final float rate = mEstimator.getRate(nextEventTime); - assertLessThan("Rate", rate, 20f); + assertThat(rate).isLessThan(20f); } @Test public void testCompactBurstIsEstimatedUnderTwoPercent() throws Exception { - assertUpdateTime(mTestStartTime); + updateAndVerifyRate(mTestStartTime); long eventStart = mTestStartTime + 1000; // start event a long time after initialization long nextEventTime = postEvents(eventStart, 1, 5); // five events at 1000Hz final float rate = mEstimator.getRate(nextEventTime); - assertLessThan("Rate", rate, 20f); + assertThat(rate).isLessThan(20f); } @Test public void testSustained1000HzBurstIsEstimatedOverNinetyPercent() throws Exception { - assertUpdateTime(mTestStartTime); + updateAndVerifyRate(mTestStartTime); long eventStart = mTestStartTime + 1000; // start event a long time after initialization long nextEventTime = postEvents(eventStart, 1, 100); // one hundred events at 1000Hz final float rate = mEstimator.getRate(nextEventTime); - assertGreaterThan("Rate", rate, 900f); + assertThat(rate).isGreaterThan(900f); } @Test public void testSustained100HzBurstIsEstimatedOverNinetyPercent() throws Exception { - assertUpdateTime(mTestStartTime); + updateAndVerifyRate(mTestStartTime); long eventStart = mTestStartTime + 1000; // start event a long time after initialization long nextEventTime = postEvents(eventStart, 10, 100); // one hundred events at 100Hz final float rate = mEstimator.getRate(nextEventTime); - assertGreaterThan("Rate", rate, 90f); + assertThat(rate).isGreaterThan(90f); } @Test public void testRecoverQuicklyAfterSustainedBurst() throws Exception { - assertUpdateTime(mTestStartTime); + updateAndVerifyRate(mTestStartTime); long eventStart = mTestStartTime + 1000; // start event a long time after initialization - long nextEventTime = postEvents(eventStart, 10, 1000); // one hundred events at 100Hz - final float rate = mEstimator.getRate(nextEventTime + 5000L); // two seconds later - assertLessThan("Rate", rate, 2f); + long nextEventTime = postEvents(eventStart, 10, 1000); // one thousand events at 100Hz + final float rate = mEstimator.getRate(nextEventTime + 5000L); // five seconds later + assertThat(rate).isLessThan(2f); } @Test public void testEstimateShouldNotOvershoot() throws Exception { - assertUpdateTime(mTestStartTime); + updateAndVerifyRate(mTestStartTime); long eventStart = mTestStartTime + 1000; // start event a long time after initialization - long nextEventTime = postEvents(eventStart, 1, 1000); // one thousand events at 1000Hz + long nextEventTime = postEvents(eventStart, 1, 5000); // five thousand events at 1000Hz final float rate = mEstimator.getRate(nextEventTime); - assertLessThan("Rate", rate, 1000f); + assertThat(rate).isAtMost(1000f); } @Test public void testGetRateWithoutUpdate() throws Exception { final float rate = mEstimator.getRate(mTestStartTime); - assertLessThan("Rate", rate, 0.1f); + assertThat(rate).isLessThan(0.1f); } @Test public void testGetRateWithOneUpdate() throws Exception { - assertUpdateTime(mTestStartTime); + updateAndVerifyRate(mTestStartTime); final float rate = mEstimator.getRate(mTestStartTime+1); - assertLessThan("Rate", rate, 1f); + assertThat(rate).isLessThan(1f); } - private void assertLessThan(String label, float a, float b) { - assertTrue(String.format("%s was %f, but should be less than %f", label, a, b), a <= b); - } + @Test + public void testEstimateCatchesUpQuickly() { + long nextEventTime = postEvents(mTestStartTime, 10, 20); // 20 events at 100Hz + + final float firstBurstRate = mEstimator.getRate(nextEventTime); + assertThat(firstBurstRate).isWithin(10f).of(100); + + nextEventTime += HOURS.toMillis(3); // 3 hours later... + nextEventTime = postEvents(nextEventTime, 10, 20); // same burst of 20 events at 100Hz + + // Catching up. Rate is not yet 100, since we had a long period of inactivity... + float secondBurstRate = mEstimator.getRate(nextEventTime); + assertThat(secondBurstRate).isWithin(10f).of(60); - private void assertGreaterThan(String label, float a, float b) { - assertTrue(String.format("%s was %f, but should be more than %f", label, a, b), a >= b); + // ... but after a few more events, we are there. + nextEventTime = postEvents(nextEventTime, 10, 10); // 10 more events at 100Hz + secondBurstRate = mEstimator.getRate(nextEventTime); + assertThat(secondBurstRate).isWithin(10f).of(100); } - /** @returns the next event time. */ + /** @return the next event time. */ private long postEvents(long start, long dt, int num) { long time = start; for (int i = 0; i < num; i++) { @@ -155,9 +166,8 @@ public class RateEstimatorTest extends UiServiceTestCase { return time; } - private void assertUpdateTime(long time) { - final float rate = mEstimator.update(time); - assertFalse(Float.isInfinite(rate)); - assertFalse(Float.isNaN(rate)); + private void updateAndVerifyRate(long time) { + mEstimator.update(time); + assertThat(mEstimator.getRate(time)).isFinite(); } -} +}
\ No newline at end of file diff --git a/services/tests/wmtests/src/com/android/server/wm/TestWindowManagerPolicy.java b/services/tests/wmtests/src/com/android/server/wm/TestWindowManagerPolicy.java index 192632ce0277..adf3f3976f38 100644 --- a/services/tests/wmtests/src/com/android/server/wm/TestWindowManagerPolicy.java +++ b/services/tests/wmtests/src/com/android/server/wm/TestWindowManagerPolicy.java @@ -18,8 +18,6 @@ package com.android.server.wm; import static android.view.WindowManager.LayoutParams.TYPE_NOTIFICATION_SHADE; -import static com.android.server.policy.WindowManagerPolicy.WindowManagerFuncs.LID_ABSENT; - import android.annotation.Nullable; import android.content.Context; import android.content.res.Configuration; @@ -356,9 +354,4 @@ class TestWindowManagerPolicy implements WindowManagerPolicy { public boolean isGlobalKey(int keyCode) { return false; } - - @Override - public int getLidState() { - return LID_ABSENT; - } } diff --git a/tests/FlickerTests/Android.bp b/tests/FlickerTests/Android.bp index 4c55f7fa49e9..a996fa100da9 100644 --- a/tests/FlickerTests/Android.bp +++ b/tests/FlickerTests/Android.bp @@ -149,8 +149,8 @@ android_test { name: "FlickerTestsQuickswitch", defaults: ["FlickerTestsDefault"], additional_manifests: ["manifests/AndroidManifestQuickswitch.xml"], - package_name: "com.android.server.wm.flicker.rotation", - instrumentation_target_package: "com.android.server.wm.flicker.rotation", + package_name: "com.android.server.wm.flicker.quickswitch", + instrumentation_target_package: "com.android.server.wm.flicker.quickswitch", srcs: [ ":FlickerTestsBase-src", ":FlickerTestsQuickswitch-src", diff --git a/tests/FlickerTests/AndroidTestTemplate.xml b/tests/FlickerTests/AndroidTestTemplate.xml index ec01317af1c2..1ede943a9fa2 100644 --- a/tests/FlickerTests/AndroidTestTemplate.xml +++ b/tests/FlickerTests/AndroidTestTemplate.xml @@ -3,46 +3,47 @@ * Copyright 2018 Google Inc. All Rights Reserved. --> <configuration description="Runs WindowManager {MODULE}"> - <option name="test-tag" value="FlickerTests" /> + <option name="test-tag" value="FlickerTests"/> <!-- Needed for storing the perfetto trace files in the sdcard/test_results--> - <option name="isolated-storage" value="false" /> + <option name="isolated-storage" value="false"/> <target_preparer class="com.android.tradefed.targetprep.DeviceSetup"> <!-- keeps the screen on during tests --> - <option name="screen-always-on" value="on" /> + <option name="screen-always-on" value="on"/> <!-- prevents the phone from restarting --> - <option name="force-skip-system-props" value="true" /> + <option name="force-skip-system-props" value="true"/> <!-- set WM tracing verbose level to all --> - <option name="run-command" value="cmd window tracing level all" /> + <option name="run-command" value="cmd window tracing level all"/> <!-- set WM tracing to frame (avoid incomplete states) --> - <option name="run-command" value="cmd window tracing frame" /> + <option name="run-command" value="cmd window tracing frame"/> <!-- ensure lock screen mode is swipe --> - <option name="run-command" value="locksettings set-disabled false" /> + <option name="run-command" value="locksettings set-disabled false"/> <!-- disable betterbug as it's log collection dialogues cause flakes in e2e tests --> - <option name="run-command" value="pm disable com.google.android.internal.betterbug" /> + <option name="run-command" value="pm disable com.google.android.internal.betterbug"/> <!-- restart launcher to activate TAPL --> - <option name="run-command" value="setprop ro.test_harness 1 ; am force-stop com.google.android.apps.nexuslauncher" /> - <!-- Ensure output directory is empty at the start --> - <option name="run-command" value="rm -rf /sdcard/flicker" /> + <option name="run-command" + value="setprop ro.test_harness 1 ; am force-stop com.google.android.apps.nexuslauncher"/> <!-- Increase trace size: 20mb for WM and 80mb for SF --> - <option name="run-command" value="cmd window tracing size 20480" /> - <option name="run-command" value="su root service call SurfaceFlinger 1029 i32 81920" /> + <option name="run-command" value="cmd window tracing size 20480"/> + <option name="run-command" value="su root service call SurfaceFlinger 1029 i32 81920"/> </target_preparer> <target_preparer class="com.android.tradefed.targetprep.RunCommandTargetPreparer"> - <option name="run-command" value="settings put secure show_ime_with_hard_keyboard 1" /> - <option name="run-command" value="settings put system show_touches 1" /> - <option name="run-command" value="settings put system pointer_location 1" /> + <option name="test-user-token" value="%TEST_USER%"/> + <option name="run-command" value="rm -rf /data/user/%TEST_USER%/files/*"/> + <option name="run-command" value="settings put secure show_ime_with_hard_keyboard 1"/> + <option name="run-command" value="settings put system show_touches 1"/> + <option name="run-command" value="settings put system pointer_location 1"/> <option name="teardown-command" - value="settings delete secure show_ime_with_hard_keyboard" /> - <option name="teardown-command" value="settings delete system show_touches" /> - <option name="teardown-command" value="settings delete system pointer_location" /> + value="settings delete secure show_ime_with_hard_keyboard"/> + <option name="teardown-command" value="settings delete system show_touches"/> + <option name="teardown-command" value="settings delete system pointer_location"/> <option name="teardown-command" - value="cmd overlay enable com.android.internal.systemui.navbar.gestural" /> + value="cmd overlay enable com.android.internal.systemui.navbar.gestural"/> </target_preparer> <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller"> <option name="cleanup-apks" value="true"/> <option name="test-file-name" value="{MODULE}.apk"/> - <option name="test-file-name" value="FlickerTestApp.apk" /> + <option name="test-file-name" value="FlickerTestApp.apk"/> </target_preparer> <!-- Needed for pushing the trace config file --> <target_preparer class="com.android.tradefed.targetprep.RootTargetPreparer"/> @@ -53,30 +54,38 @@ /> <!--Install the content provider automatically when we push some file in sdcard folder.--> <!--Needed to avoid the installation during the test suite.--> - <option name="push-file" key="trace_config.textproto" value="/sdcard/sample.textproto" /> + <option name="push-file" key="trace_config.textproto" value="/sdcard/sample.textproto"/> </target_preparer> <test class="com.android.tradefed.testtype.AndroidJUnitTest"> <option name="package" value="{PACKAGE}"/> - <option name="shell-timeout" value="6600s" /> - <option name="test-timeout" value="6600s" /> - <option name="hidden-api-checks" value="false" /> - <option name="device-listeners" value="android.device.collectors.PerfettoListener" /> + <option name="shell-timeout" value="6600s"/> + <option name="test-timeout" value="6600s"/> + <option name="hidden-api-checks" value="false"/> + <option name="device-listeners" value="android.device.collectors.PerfettoListener"/> <!-- PerfettoListener related arguments --> - <option name="instrumentation-arg" key="perfetto_config_text_proto" value="true" /> + <option name="instrumentation-arg" key="perfetto_config_text_proto" value="true"/> <option name="instrumentation-arg" key="perfetto_config_file" value="trace_config.textproto" /> - <option name="instrumentation-arg" key="per_run" value="true" /> + <option name="instrumentation-arg" key="per_run" value="true"/> </test> <!-- Needed for pulling the collected trace config on to the host --> <metrics_collector class="com.android.tradefed.device.metric.FilePullerLogCollector"> - <option name="pull-pattern-keys" value="perfetto_file_path" /> - </metrics_collector> - <metrics_collector class="com.android.tradefed.device.metric.FilePullerLogCollector"> - <option name="pull-pattern-keys" value="(\w)+\.winscope" /> - <option name="pull-pattern-keys" value="(\w)+\.mp4" /> - <option name="collect-on-run-ended-only" value="false" /> - <option name="clean-up" value="true" /> + <option name="pull-pattern-keys" value="perfetto_file_path"/> + <option name="directory-keys" + value="/data/user/0/com.android.server.wm.flicker/files"/> + <option name="directory-keys" + value="/data/user/0/com.android.server.wm.flicker.close/files"/> + <option name="directory-keys" + value="/data/user/0/com.android.server.wm.flicker.ime/files"/> + <option name="directory-keys" + value="/data/user/0/com.android.server.wm.flicker.launch/files"/> + <option name="directory-keys" + value="/data/user/0/com.android.server.wm.flicker.quickswitch/files"/> + <option name="directory-keys" + value="/data/user/0/com.android.server.wm.flicker.rotation/files"/> + <option name="collect-on-run-ended-only" value="true"/> + <option name="clean-up" value="true"/> </metrics_collector> </configuration> diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/ActivitiesTransitionTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/ActivityTransitionTest.kt index e6594c969373..a87fae857509 100644 --- a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/ActivitiesTransitionTest.kt +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/ActivityTransitionTest.kt @@ -57,7 +57,7 @@ import org.junit.runners.Parameterized @RunWith(Parameterized::class) @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class) @FixMethodOrder(MethodSorters.NAME_ASCENDING) -open class ActivitiesTransitionTest(flicker: FlickerTest) : BaseTest(flicker) { +open class ActivityTransitionTest(flicker: FlickerTest) : BaseTest(flicker) { private val testApp: TwoActivitiesAppHelper = TwoActivitiesAppHelper(instrumentation) /** {@inheritDoc} */ diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppAfterCameraTestCfArm.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/ActivityTransitionTestCfArm.kt index ac05c7687311..85344a156d7a 100644 --- a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppAfterCameraTestCfArm.kt +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/ActivityTransitionTestCfArm.kt @@ -27,7 +27,7 @@ import org.junit.runners.Parameterized @RunWith(Parameterized::class) @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class) @FixMethodOrder(MethodSorters.NAME_ASCENDING) -class OpenAppAfterCameraTestCfArm(flicker: FlickerTest) : OpenAppAfterCameraTest(flicker) { +class ActivityTransitionTestCfArm(flicker: FlickerTest) : ActivityTransitionTest(flicker) { companion object { /** * Creates the test configurations. diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppColdFromIcon.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromIconColdTest.kt index 3a80c6649833..575206591e59 100644 --- a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppColdFromIcon.kt +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromIconColdTest.kt @@ -55,7 +55,7 @@ import org.junit.runners.Parameterized @RunWith(Parameterized::class) @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class) @FixMethodOrder(MethodSorters.NAME_ASCENDING) -open class OpenAppColdFromIcon(flicker: FlickerTest) : OpenAppFromLauncherTransition(flicker) { +open class OpenAppFromIconColdTest(flicker: FlickerTest) : OpenAppFromLauncherTransition(flicker) { /** {@inheritDoc} */ override val transition: FlickerBuilder.() -> Unit get() = { diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppColdFromIconCfArm.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromIconColdTestCfArm.kt index d33a2724ca44..d453c1ae908a 100644 --- a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppColdFromIconCfArm.kt +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromIconColdTestCfArm.kt @@ -32,7 +32,7 @@ import org.junit.runners.Parameterized @RunWith(Parameterized::class) @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class) @FixMethodOrder(MethodSorters.NAME_ASCENDING) -class OpenAppColdFromIconCfArm(flicker: FlickerTest) : OpenAppColdFromIcon(flicker) { +class OpenAppFromIconColdTestCfArm(flicker: FlickerTest) : OpenAppFromIconColdTest(flicker) { @Test @FlakyTest override fun visibleLayersShownMoreThanOneConsecutiveEntry() { diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppAfterCameraTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromIntentColdAfterCameraTest.kt index 549183f407e2..e74731555642 100644 --- a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppAfterCameraTest.kt +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromIntentColdAfterCameraTest.kt @@ -38,7 +38,8 @@ import org.junit.runners.Parameterized @RunWith(Parameterized::class) @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class) @FixMethodOrder(MethodSorters.NAME_ASCENDING) -open class OpenAppAfterCameraTest(flicker: FlickerTest) : OpenAppFromLauncherTransition(flicker) { +open class OpenAppFromIntentColdAfterCameraTest(flicker: FlickerTest) : + OpenAppFromLauncherTransition(flicker) { private val cameraApp = CameraAppHelper(instrumentation) /** {@inheritDoc} */ override val transition: FlickerBuilder.() -> Unit diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromNotificationWarmCfArm.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromIntentColdAfterCameraTestCfArm.kt index 43d28fa60e51..177ad7dc09d1 100644 --- a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromNotificationWarmCfArm.kt +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromIntentColdAfterCameraTestCfArm.kt @@ -27,8 +27,8 @@ import org.junit.runners.Parameterized @RunWith(Parameterized::class) @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class) @FixMethodOrder(MethodSorters.NAME_ASCENDING) -class OpenAppFromNotificationWarmCfArm(flicker: FlickerTest) : - OpenAppFromNotificationWarm(flicker) { +class OpenAppFromIntentColdAfterCameraTestCfArm(flicker: FlickerTest) : + OpenAppFromIntentColdAfterCameraTest(flicker) { companion object { /** * Creates the test configurations. diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppColdTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromIntentColdTest.kt index 26f88d23cda0..f45f728664cf 100644 --- a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppColdTest.kt +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromIntentColdTest.kt @@ -58,7 +58,8 @@ import org.junit.runners.Parameterized @RunWith(Parameterized::class) @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class) @FixMethodOrder(MethodSorters.NAME_ASCENDING) -open class OpenAppColdTest(flicker: FlickerTest) : OpenAppFromLauncherTransition(flicker) { +open class OpenAppFromIntentColdTest(flicker: FlickerTest) : + OpenAppFromLauncherTransition(flicker) { /** {@inheritDoc} */ override val transition: FlickerBuilder.() -> Unit get() = { diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppColdTestCfArm.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromIntentColdTestCfArm.kt index d9a99dadbd3d..0d695f306c00 100644 --- a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppColdTestCfArm.kt +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromIntentColdTestCfArm.kt @@ -31,7 +31,7 @@ import org.junit.runners.Parameterized @RunWith(Parameterized::class) @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class) @FixMethodOrder(MethodSorters.NAME_ASCENDING) -class OpenAppColdTestCfArm(flicker: FlickerTest) : OpenAppColdTest(flicker) { +class OpenAppFromIntentColdTestCfArm(flicker: FlickerTest) : OpenAppFromIntentColdTest(flicker) { @FlakyTest(bugId = 273696733) @Test override fun appLayerReplacesLauncher() = super.appLayerReplacesLauncher() diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppWarmTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromIntentWarmTest.kt index 3385830ee77f..a42bff5bf170 100644 --- a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppWarmTest.kt +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromIntentWarmTest.kt @@ -58,7 +58,8 @@ import org.junit.runners.Parameterized @RunWith(Parameterized::class) @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class) @FixMethodOrder(MethodSorters.NAME_ASCENDING) -open class OpenAppWarmTest(flicker: FlickerTest) : OpenAppFromLauncherTransition(flicker) { +open class OpenAppFromIntentWarmTest(flicker: FlickerTest) : + OpenAppFromLauncherTransition(flicker) { /** Defines the transition used to run the test */ override val transition: FlickerBuilder.() -> Unit get() = { diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppWarmTestCfArm.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromIntentWarmTestCfArm.kt index d8b38b30cf13..b6ffcb3df9f3 100644 --- a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppWarmTestCfArm.kt +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromIntentWarmTestCfArm.kt @@ -29,7 +29,7 @@ import org.junit.runners.Parameterized @RunWith(Parameterized::class) @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class) @FixMethodOrder(MethodSorters.NAME_ASCENDING) -class OpenAppWarmTestCfArm(flicker: FlickerTest) : OpenAppWarmTest(flicker) { +class OpenAppFromIntentWarmTestCfArm(flicker: FlickerTest) : OpenAppFromIntentWarmTest(flicker) { companion object { /** * Creates the test configurations. diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromLockNotificationCold.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromLockscreenNotificationColdTest.kt index b21777b30b21..fd4272600d55 100644 --- a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromLockNotificationCold.kt +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromLockscreenNotificationColdTest.kt @@ -44,8 +44,8 @@ import org.junit.runners.Parameterized @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class) @FixMethodOrder(MethodSorters.NAME_ASCENDING) @Postsubmit -open class OpenAppFromLockNotificationCold(flicker: FlickerTest) : - OpenAppFromNotificationCold(flicker) { +open class OpenAppFromLockscreenNotificationColdTest(flicker: FlickerTest) : + OpenAppFromNotificationColdTest(flicker) { override val openingNotificationsFromLockScreen = true diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromLockNotificationWarm.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromLockscreenNotificationWarmTest.kt index ec92ca65f80a..fd051d50d032 100644 --- a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromLockNotificationWarm.kt +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromLockscreenNotificationWarmTest.kt @@ -46,7 +46,8 @@ import org.junit.runners.Parameterized @RunWith(Parameterized::class) @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class) @FixMethodOrder(MethodSorters.NAME_ASCENDING) -class OpenAppFromLockNotificationWarm(flicker: FlickerTest) : OpenAppFromNotificationWarm(flicker) { +class OpenAppFromLockscreenNotificationWarmTest(flicker: FlickerTest) : + OpenAppFromNotificationWarmTest(flicker) { override val openingNotificationsFromLockScreen = true diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromLockNotificationWithLockOverlayApp.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromLockscreenNotificationWithOverlayAppTest.kt index 009d61797fe0..37afa8d0caba 100644 --- a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromLockNotificationWithLockOverlayApp.kt +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromLockscreenNotificationWithOverlayAppTest.kt @@ -37,6 +37,8 @@ import org.junit.runners.Parameterized * Test cold launching an app from a notification from the lock screen when there is an app overlaid * on the lock screen. * + * This test assumes the device doesn't have AOD enabled + * * To run this test: `atest FlickerTests:OpenAppFromLockNotificationWithLockOverlayApp` */ @RequiresDevice @@ -44,8 +46,8 @@ import org.junit.runners.Parameterized @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class) @FixMethodOrder(MethodSorters.NAME_ASCENDING) @Postsubmit -class OpenAppFromLockNotificationWithLockOverlayApp(flicker: FlickerTest) : - OpenAppFromLockNotificationCold(flicker) { +class OpenAppFromLockscreenNotificationWithOverlayAppTest(flicker: FlickerTest) : + OpenAppFromLockscreenNotificationColdTest(flicker) { private val showWhenLockedApp = ShowWhenLockedAppHelper(instrumentation) // Although we are technically still locked here, the overlay app means we should open the diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromLockTransition.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromLockscreenTransition.kt index eae9ca10c711..30c3ec205bb1 100644 --- a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromLockTransition.kt +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromLockscreenTransition.kt @@ -28,7 +28,7 @@ import org.junit.Ignore import org.junit.Test /** Base class for app launch tests from lock screen */ -abstract class OpenAppFromLockTransition(flicker: FlickerTest) : OpenAppTransition(flicker) { +abstract class OpenAppFromLockscreenTransition(flicker: FlickerTest) : OpenAppTransition(flicker) { /** Defines the transition used to run the test */ override val transition: FlickerBuilder.() -> Unit = { diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppNonResizeableTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromLockscreenViaIntentTest.kt index 1383ae39f760..924d03f6d302 100644 --- a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppNonResizeableTest.kt +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromLockscreenViaIntentTest.kt @@ -63,7 +63,8 @@ import org.junit.runners.Parameterized @RunWith(Parameterized::class) @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class) @FixMethodOrder(MethodSorters.NAME_ASCENDING) -open class OpenAppNonResizeableTest(flicker: FlickerTest) : OpenAppFromLockTransition(flicker) { +open class OpenAppFromLockscreenViaIntentTest(flicker: FlickerTest) : + OpenAppFromLockscreenTransition(flicker) { override val testApp = NonResizeableAppHelper(instrumentation) /** diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromNotificationCold.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromNotificationColdTest.kt index 7bcb91070ecf..d873ec5e83e2 100644 --- a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromNotificationCold.kt +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromNotificationColdTest.kt @@ -35,8 +35,6 @@ import org.junit.runners.Parameterized /** * Test cold launching an app from a notification. * - * This test assumes the device doesn't have AOD enabled - * * To run this test: `atest FlickerTests:OpenAppFromNotificationCold` */ @RequiresDevice @@ -44,8 +42,8 @@ import org.junit.runners.Parameterized @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class) @FixMethodOrder(MethodSorters.NAME_ASCENDING) @Postsubmit -open class OpenAppFromNotificationCold(flicker: FlickerTest) : - OpenAppFromNotificationWarm(flicker) { +open class OpenAppFromNotificationColdTest(flicker: FlickerTest) : + OpenAppFromNotificationWarmTest(flicker) { /** {@inheritDoc} */ override val transition: FlickerBuilder.() -> Unit get() = { diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromNotificationColdCfArm.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromNotificationColdTestCfArm.kt index 8b4a613305c0..fb2a48c2ad7f 100644 --- a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromNotificationColdCfArm.kt +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromNotificationColdTestCfArm.kt @@ -29,8 +29,8 @@ import org.junit.runners.Parameterized @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class) @FixMethodOrder(MethodSorters.NAME_ASCENDING) @Postsubmit -class OpenAppFromNotificationColdCfArm(flicker: FlickerTest) : - OpenAppFromNotificationCold(flicker) { +class OpenAppFromNotificationColdTestCfArm(flicker: FlickerTest) : + OpenAppFromNotificationColdTest(flicker) { companion object { /** * Creates the test configurations. diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromNotificationWarm.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromNotificationWarmTest.kt index 425e674dec3a..99668ecd0b68 100644 --- a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromNotificationWarm.kt +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromNotificationWarmTest.kt @@ -47,15 +47,13 @@ import org.junit.runners.Parameterized /** * Test cold launching an app from a notification. * - * This test assumes the device doesn't have AOD enabled - * * To run this test: `atest FlickerTests:OpenAppFromNotificationWarm` */ @RequiresDevice @RunWith(Parameterized::class) @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class) @FixMethodOrder(MethodSorters.NAME_ASCENDING) -open class OpenAppFromNotificationWarm(flicker: FlickerTest) : OpenAppTransition(flicker) { +open class OpenAppFromNotificationWarmTest(flicker: FlickerTest) : OpenAppTransition(flicker) { override val testApp: NotificationAppHelper = NotificationAppHelper(instrumentation) open val openingNotificationsFromLockScreen = false diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/ActivitiesTransitionTestCfArm.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromNotificationWarmTestCfArm.kt index 8b89a8b4c40d..2a2597e1ebe8 100644 --- a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/ActivitiesTransitionTestCfArm.kt +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromNotificationWarmTestCfArm.kt @@ -27,7 +27,8 @@ import org.junit.runners.Parameterized @RunWith(Parameterized::class) @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class) @FixMethodOrder(MethodSorters.NAME_ASCENDING) -class ActivitiesTransitionTestCfArm(flicker: FlickerTest) : ActivitiesTransitionTest(flicker) { +class OpenAppFromNotificationWarmTestCfArm(flicker: FlickerTest) : + OpenAppFromNotificationWarmTest(flicker) { companion object { /** * Creates the test configurations. diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenCameraOnDoubleClickPowerButton.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenCameraFromHomeOnDoubleClickPowerButtonTest.kt index ae9ca8007dc8..6ee8ae69924a 100644 --- a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenCameraOnDoubleClickPowerButton.kt +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenCameraFromHomeOnDoubleClickPowerButtonTest.kt @@ -60,7 +60,7 @@ import org.junit.runners.Parameterized @RunWith(Parameterized::class) @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class) @FixMethodOrder(MethodSorters.NAME_ASCENDING) -class OpenCameraOnDoubleClickPowerButton(flicker: FlickerTest) : +class OpenCameraFromHomeOnDoubleClickPowerButtonTest(flicker: FlickerTest) : OpenAppFromLauncherTransition(flicker) { private val cameraApp = CameraAppHelper(instrumentation) override val testApp: StandardAppHelper diff --git a/tests/InputMethodStressTest/AndroidManifest.xml b/tests/InputMethodStressTest/AndroidManifest.xml index 62eee0270cac..e890c9974882 100644 --- a/tests/InputMethodStressTest/AndroidManifest.xml +++ b/tests/InputMethodStressTest/AndroidManifest.xml @@ -19,8 +19,7 @@ package="com.android.inputmethod.stresstest"> <uses-permission android:name="android.permission.POST_NOTIFICATIONS"/> <application> - <activity android:name=".ImeStressTestUtil$TestActivity" - android:configChanges="orientation|screenSize"/> + <activity android:name=".ImeStressTestUtil$TestActivity"/> </application> <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner" diff --git a/tests/InputMethodStressTest/src/com/android/inputmethod/stresstest/ImeOpenCloseStressTest.java b/tests/InputMethodStressTest/src/com/android/inputmethod/stresstest/ImeOpenCloseStressTest.java index 7632ab08b655..b76a4eb8c0e6 100644 --- a/tests/InputMethodStressTest/src/com/android/inputmethod/stresstest/ImeOpenCloseStressTest.java +++ b/tests/InputMethodStressTest/src/com/android/inputmethod/stresstest/ImeOpenCloseStressTest.java @@ -493,6 +493,7 @@ public final class ImeOpenCloseStressTest { verifyShowBehavior(activity); } + // TODO: Add tests for activities that don't handle the rotation. @Test public void testRotateScreenWithKeyboardOn() throws Exception { Intent intent = @@ -514,14 +515,14 @@ public final class ImeOpenCloseStressTest { Thread.sleep(1000); Log.i(TAG, "Rotate screen right"); assertThat(uiDevice.isNaturalOrientation()).isFalse(); - verifyShowBehavior(activity); + verifyRotateBehavior(activity); uiDevice.setOrientationLeft(); uiDevice.waitForIdle(); Thread.sleep(1000); Log.i(TAG, "Rotate screen left"); assertThat(uiDevice.isNaturalOrientation()).isFalse(); - verifyShowBehavior(activity); + verifyRotateBehavior(activity); uiDevice.setOrientationNatural(); uiDevice.waitForIdle(); @@ -569,4 +570,36 @@ public final class ImeOpenCloseStressTest { waitOnMainUntilImeIsShown(editText); } } + + private static void verifyRotateBehavior(TestActivity activity) { + // Get the new TestActivity after recreation. + TestActivity newActivity = TestActivity.getLastCreatedInstance(); + assertThat(newActivity).isNotNull(); + assertThat(newActivity).isNotEqualTo(activity); + + EditText newEditText = newActivity.getEditText(); + int softInputMode = newActivity.getWindow().getAttributes().softInputMode; + int softInputVisibility = softInputMode & WindowManager.LayoutParams.SOFT_INPUT_MASK_STATE; + + if (hasUnfocusableWindowFlags(newActivity)) { + verifyImeAlwaysHiddenWithWindowFlagSet(newActivity); + return; + } + + if (softInputVisibility == WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_HIDDEN) { + // After rotation, the keyboard would be hidden only when the flag is + // SOFT_INPUT_STATE_ALWAYS_HIDDEN. However, SOFT_INPUT_STATE_HIDDEN is different because + // it requires appending SOFT_INPUT_IS_FORWARD_NAVIGATION flag, which won't be added + // when rotating the devices (rotating doesn't navigate forward to the next app window.) + verifyWindowAndViewFocus(newEditText, /*expectWindowFocus*/ true, /*expectViewFocus*/ + true); + waitOnMainUntilImeIsHidden(newEditText); + + } else { + // Other cases, keyboard would be shown. + verifyWindowAndViewFocus(newEditText, /*expectWindowFocus*/ true, /*expectViewFocus*/ + true); + waitOnMainUntilImeIsShown(newEditText); + } + } } diff --git a/tests/InputMethodStressTest/src/com/android/inputmethod/stresstest/ImeStressTestUtil.java b/tests/InputMethodStressTest/src/com/android/inputmethod/stresstest/ImeStressTestUtil.java index e16c9152df3e..f3c81942ae42 100644 --- a/tests/InputMethodStressTest/src/com/android/inputmethod/stresstest/ImeStressTestUtil.java +++ b/tests/InputMethodStressTest/src/com/android/inputmethod/stresstest/ImeStressTestUtil.java @@ -45,6 +45,7 @@ import androidx.test.platform.app.InstrumentationRegistry; import com.android.compatibility.common.util.ThrowingRunnable; +import java.lang.ref.WeakReference; import java.util.ArrayList; import java.util.List; import java.util.concurrent.Callable; @@ -296,6 +297,8 @@ public final class ImeStressTestUtil { private static final String TAG = "ImeStressTestUtil.TestActivity"; private EditText mEditText; private boolean mIsAnimating; + private static WeakReference<TestActivity> sLastCreatedInstance = + new WeakReference<>(null); private final WindowInsetsAnimation.Callback mWindowInsetsAnimationCallback = new WindowInsetsAnimation.Callback(DISPATCH_MODE_STOP) { @@ -336,6 +339,7 @@ public final class ImeStressTestUtil { protected void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); Log.i(TAG, "onCreate()"); + sLastCreatedInstance = new WeakReference<>(this); boolean isUnfocusableView = getIntent().getBooleanExtra(UNFOCUSABLE_VIEW, false); boolean requestFocus = getIntent().getBooleanExtra(REQUEST_FOCUS_ON_CREATE, false); int softInputFlags = getIntent().getIntExtra(SOFT_INPUT_FLAGS, 0); @@ -378,6 +382,12 @@ public final class ImeStressTestUtil { } } + /** Get the last created TestActivity instance. */ + @Nullable + public static TestActivity getLastCreatedInstance() { + return sLastCreatedInstance.get(); + } + /** Show IME with InputMethodManager. */ public boolean showImeWithInputMethodManager() { boolean showResult = diff --git a/tools/protologtool/Android.bp b/tools/protologtool/Android.bp index 039bb4e2788c..46745e995f64 100644 --- a/tools/protologtool/Android.bp +++ b/tools/protologtool/Android.bp @@ -11,7 +11,7 @@ java_library_host { name: "protologtool-lib", srcs: [ "src/com/android/protolog/tool/**/*.kt", - ":protolog-common-src", + ":protolog-common-no-android-src", ], static_libs: [ "javaparser", |