diff options
238 files changed, 4041 insertions, 1791 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/app/ServiceStartArgs.java b/core/java/android/app/ServiceStartArgs.java index 0b000af56400..9c52367392a5 100644 --- a/core/java/android/app/ServiceStartArgs.java +++ b/core/java/android/app/ServiceStartArgs.java @@ -49,7 +49,7 @@ public class ServiceStartArgs implements Parcelable { public void writeToParcel(Parcel out, int flags) { out.writeInt(taskRemoved ? 1 : 0); out.writeInt(startId); - out.writeInt(flags); + out.writeInt(this.flags); if (args != null) { out.writeInt(1); args.writeToParcel(out, 0); diff --git a/core/java/android/app/WallpaperInfo.java b/core/java/android/app/WallpaperInfo.java index 99d406446dae..b29e73a717f4 100644 --- a/core/java/android/app/WallpaperInfo.java +++ b/core/java/android/app/WallpaperInfo.java @@ -292,12 +292,12 @@ public final class WallpaperInfo implements Parcelable { packageName = mService.serviceInfo.packageName; applicationInfo = mService.serviceInfo.applicationInfo; } - String contextUriString = pm.getText( - packageName, mContextUriResource, applicationInfo).toString(); - if (contextUriString == null) { + CharSequence contextUriCharSequence = pm.getText( + packageName, mContextUriResource, applicationInfo); + if (contextUriCharSequence == null) { return null; } - return Uri.parse(contextUriString); + return Uri.parse(contextUriCharSequence.toString()); } /** diff --git a/core/java/android/content/pm/PackageInstaller.java b/core/java/android/content/pm/PackageInstaller.java index 96118f6c43ea..105b38a40825 100644 --- a/core/java/android/content/pm/PackageInstaller.java +++ b/core/java/android/content/pm/PackageInstaller.java @@ -444,6 +444,10 @@ public class PackageInstaller { * exist, it may be missing native code for the ABIs supported by the * device, or it requires a newer SDK version, etc. * + * Starting in {@link Build.VERSION_CODES#UPSIDE_DOWN_CAKE}, an app with only 32-bit native + * code can still be installed on a device that supports both 64-bit and 32-bit ABIs. + * However, a warning dialog will be displayed when the app is launched. + * * @see #EXTRA_STATUS_MESSAGE */ public static final int STATUS_FAILURE_INCOMPATIBLE = 7; diff --git a/core/java/android/service/wallpaper/WallpaperService.java b/core/java/android/service/wallpaper/WallpaperService.java index 7f0a666da91f..4e086c2ad134 100644 --- a/core/java/android/service/wallpaper/WallpaperService.java +++ b/core/java/android/service/wallpaper/WallpaperService.java @@ -1391,7 +1391,7 @@ public abstract class WallpaperService extends Service { Trace.endSection(); } - if (redrawNeeded) { + if (redrawNeeded || sizeChanged) { Trace.beginSection("WPMS.Engine.onSurfaceRedrawNeeded"); onSurfaceRedrawNeeded(mSurfaceHolder); Trace.endSection(); 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/SurfaceControl.java b/core/java/android/view/SurfaceControl.java index ddaa71c6b1b5..c11f4975149d 100644 --- a/core/java/android/view/SurfaceControl.java +++ b/core/java/android/view/SurfaceControl.java @@ -786,7 +786,11 @@ public final class SurfaceControl implements Parcelable { mReleaseStack = null; } setUnreleasedWarningCallSite(callsite); - addToRegistry(); + if (nativeObject != 0) { + // Only add valid surface controls to the registry. This is called at the end of this + // method since its information is dumped if the process threshold is reached. + addToRegistry(); + } } /** @@ -893,6 +897,10 @@ public final class SurfaceControl implements Parcelable { "Only buffer layers can set a valid buffer size."); } + if (mName == null) { + Log.w(TAG, "Missing name for SurfaceControl", new Throwable()); + } + if ((mFlags & FX_SURFACE_MASK) == FX_SURFACE_NORMAL) { setBLASTLayer(); } @@ -1254,6 +1262,9 @@ public final class SurfaceControl implements Parcelable { } /** + * Note: Most callers should use {@link SurfaceControl.Builder} or one of the other constructors + * to build an instance of a SurfaceControl. This constructor is mainly used for + * unparceling and passing into an AIDL call as an out parameter. * @hide */ public SurfaceControl() { @@ -2495,6 +2506,7 @@ public final class SurfaceControl implements Parcelable { public static SurfaceControl mirrorSurface(SurfaceControl mirrorOf) { long nativeObj = nativeMirrorSurface(mirrorOf.mNativeObject); SurfaceControl sc = new SurfaceControl(); + sc.mName = mirrorOf.mName + " (mirror)"; sc.assignNativeObject(nativeObj, "mirrorSurface"); return sc; } diff --git a/core/java/android/view/SurfaceControlViewHost.java b/core/java/android/view/SurfaceControlViewHost.java index d9872174f9be..c8cf7d9a5194 100644 --- a/core/java/android/view/SurfaceControlViewHost.java +++ b/core/java/android/view/SurfaceControlViewHost.java @@ -171,8 +171,7 @@ public class SurfaceControlViewHost { public SurfacePackage(@NonNull SurfacePackage other) { SurfaceControl otherSurfaceControl = other.mSurfaceControl; if (otherSurfaceControl != null && otherSurfaceControl.isValid()) { - mSurfaceControl = new SurfaceControl(); - mSurfaceControl.copyFrom(otherSurfaceControl, "SurfacePackage"); + mSurfaceControl = new SurfaceControl(otherSurfaceControl, "SurfacePackage"); } mAccessibilityEmbeddedConnection = other.mAccessibilityEmbeddedConnection; mInputToken = other.mInputToken; diff --git a/core/java/android/view/contentprotection/OWNERS b/core/java/android/view/contentprotection/OWNERS new file mode 100644 index 000000000000..b3583a7f6ab1 --- /dev/null +++ b/core/java/android/view/contentprotection/OWNERS @@ -0,0 +1,4 @@ +# Bug component: 544200 + +include /core/java/android/view/contentcapture/OWNERS + diff --git a/core/java/android/widget/RemoteViews.java b/core/java/android/widget/RemoteViews.java index dedbf50124f9..7f96266a1f69 100644 --- a/core/java/android/widget/RemoteViews.java +++ b/core/java/android/widget/RemoteViews.java @@ -1850,7 +1850,7 @@ public class RemoteViews implements Parcelable, Filter { } @Override - public final void visitUris(@NonNull Consumer<Uri> visitor) { + public void visitUris(@NonNull Consumer<Uri> visitor) { switch (this.type) { case URI: final Uri uri = (Uri) getParameterValue(null); @@ -2313,6 +2313,14 @@ public class RemoteViews implements Parcelable, Filter { public int getActionTag() { return NIGHT_MODE_REFLECTION_ACTION_TAG; } + + @Override + public void visitUris(@NonNull Consumer<Uri> visitor) { + if (this.type == ICON) { + visitIconUri((Icon) mDarkValue, visitor); + visitIconUri((Icon) mLightValue, visitor); + } + } } /** diff --git a/core/java/android/window/SnapshotDrawerUtils.java b/core/java/android/window/SnapshotDrawerUtils.java index 52e17ca4ab0d..f40874b77536 100644 --- a/core/java/android/window/SnapshotDrawerUtils.java +++ b/core/java/android/window/SnapshotDrawerUtils.java @@ -43,6 +43,7 @@ import static com.android.internal.policy.DecorView.NAVIGATION_BAR_COLOR_VIEW_AT import static com.android.internal.policy.DecorView.STATUS_BAR_COLOR_VIEW_ATTRIBUTES; import static com.android.internal.policy.DecorView.getNavigationBarRect; +import android.annotation.NonNull; import android.annotation.Nullable; import android.app.ActivityManager; import android.app.ActivityThread; @@ -183,7 +184,7 @@ public class SnapshotDrawerUtils { // We consider nearly matched dimensions as there can be rounding errors and the user // won't notice very minute differences from scaling one dimension more than the other - final boolean aspectRatioMismatch = !isAspectRatioMatch(mFrame, mSnapshot); + boolean aspectRatioMismatch = !isAspectRatioMatch(mFrame, mSnapshot); // Keep a reference to it such that it doesn't get destroyed when finalized. SurfaceControl childSurfaceControl = new SurfaceControl.Builder(session) @@ -199,8 +200,20 @@ public class SnapshotDrawerUtils { // still hidden. mTransaction.show(childSurfaceControl); if (aspectRatioMismatch) { - // Clip off ugly navigation bar. - final Rect crop = calculateSnapshotCrop(); + Rect crop = null; + final Rect letterboxInsets = mSnapshot.getLetterboxInsets(); + if (letterboxInsets.left != 0 || letterboxInsets.top != 0 + || letterboxInsets.right != 0 || letterboxInsets.bottom != 0) { + // Clip off letterbox. + crop = calculateSnapshotCrop(letterboxInsets); + // If the snapshot can cover the frame, then no need to draw background. + aspectRatioMismatch = !isAspectRatioMatch(mFrame, crop); + } + // if letterbox doesn't match window frame, try crop by content insets + if (aspectRatioMismatch) { + // Clip off ugly navigation bar. + crop = calculateSnapshotCrop(mSnapshot.getContentInsets()); + } frame = calculateSnapshotFrame(crop); mTransaction.setWindowCrop(childSurfaceControl, crop); mTransaction.setPosition(childSurfaceControl, frame.left, frame.top); @@ -242,14 +255,13 @@ public class SnapshotDrawerUtils { /** * Calculates the snapshot crop in snapshot coordinate space. - * + * @param insets Content insets or Letterbox insets * @return crop rect in snapshot coordinate space. */ - Rect calculateSnapshotCrop() { + Rect calculateSnapshotCrop(@NonNull Rect insets) { final Rect rect = new Rect(); final HardwareBuffer snapshot = mSnapshot.getHardwareBuffer(); rect.set(0, 0, snapshot.getWidth(), snapshot.getHeight()); - final Rect insets = mSnapshot.getContentInsets(); final float scaleX = (float) snapshot.getWidth() / mSnapshot.getTaskSize().x; final float scaleY = (float) snapshot.getHeight() / mSnapshot.getTaskSize().y; @@ -334,6 +346,15 @@ public class SnapshotDrawerUtils { - ((float) frame.width() / frame.height())) <= 0.01f; } + private static boolean isAspectRatioMatch(Rect frame1, Rect frame2) { + if (frame1.isEmpty() || frame2.isEmpty()) { + return false; + } + return Math.abs( + ((float) frame2.width() / frame2.height()) + - ((float) frame1.width() / frame1.height())) <= 0.01f; + } + /** * Get or create a TaskDescription from a RunningTaskInfo. */ diff --git a/core/java/android/window/SurfaceSyncGroup.java b/core/java/android/window/SurfaceSyncGroup.java index dfdff9ef97ce..5d14698c82b3 100644 --- a/core/java/android/window/SurfaceSyncGroup.java +++ b/core/java/android/window/SurfaceSyncGroup.java @@ -26,6 +26,7 @@ import android.os.Debug; import android.os.Handler; import android.os.HandlerThread; import android.os.IBinder; +import android.os.Looper; import android.os.RemoteException; import android.os.Trace; import android.util.ArraySet; @@ -800,22 +801,25 @@ public final class SurfaceSyncGroup { } private void addTimeout() { + Looper looper = null; synchronized (sHandlerThreadLock) { if (sHandlerThread == null) { sHandlerThread = new HandlerThread("SurfaceSyncGroupTimer"); sHandlerThread.start(); } + + looper = sHandlerThread.getLooper(); } synchronized (mLock) { - if (mTimeoutAdded || mTimeoutDisabled) { + if (mTimeoutAdded || mTimeoutDisabled || looper == null) { // We only need one timeout for the entire SurfaceSyncGroup since we just want to // ensure it doesn't stay stuck forever. return; } if (mHandler == null) { - mHandler = new Handler(sHandlerThread.getLooper()); + mHandler = new Handler(looper); } mTimeoutAdded = true; diff --git a/core/java/android/window/WindowOnBackInvokedDispatcher.java b/core/java/android/window/WindowOnBackInvokedDispatcher.java index 22b2ec09c034..421d9983d6b7 100644 --- a/core/java/android/window/WindowOnBackInvokedDispatcher.java +++ b/core/java/android/window/WindowOnBackInvokedDispatcher.java @@ -31,6 +31,8 @@ import android.util.Log; import android.view.IWindow; import android.view.IWindowSession; +import androidx.annotation.VisibleForTesting; + import java.io.PrintWriter; import java.lang.ref.WeakReference; import java.util.ArrayList; @@ -66,7 +68,9 @@ public class WindowOnBackInvokedDispatcher implements OnBackInvokedDispatcher { /** Convenience hashmap to quickly decide if a callback has been added. */ private final HashMap<OnBackInvokedCallback, Integer> mAllCallbacks = new HashMap<>(); /** Holds all callbacks by priorities. */ - private final TreeMap<Integer, ArrayList<OnBackInvokedCallback>> + + @VisibleForTesting + public final TreeMap<Integer, ArrayList<OnBackInvokedCallback>> mOnBackInvokedCallbacks = new TreeMap<>(); private Checker mChecker; diff --git a/core/java/com/android/internal/accessibility/util/AccessibilityStatsLogUtils.java b/core/java/com/android/internal/accessibility/util/AccessibilityStatsLogUtils.java index 1f7640d97b4d..7c4252e7aa5d 100644 --- a/core/java/com/android/internal/accessibility/util/AccessibilityStatsLogUtils.java +++ b/core/java/com/android/internal/accessibility/util/AccessibilityStatsLogUtils.java @@ -258,6 +258,10 @@ public final class AccessibilityStatsLogUtils { } private static int convertToLoggingMagnificationScale(float scale) { - return (int) (scale * 100); + // per b/269366674, we make every 10% a bucket for both privacy and readability concern. + // For example + // 1. both 2.30f(230%) and 2.36f(236%) would return 230 as bucket id. + // 2. bucket id 370 means scale range in [370%, 379%] + return ((int) (scale * 10)) * 10; } } diff --git a/core/java/com/android/internal/config/sysui/SystemUiSystemPropertiesFlags.java b/core/java/com/android/internal/config/sysui/SystemUiSystemPropertiesFlags.java index 86c2893c9fab..3d95dd341cc0 100644 --- a/core/java/com/android/internal/config/sysui/SystemUiSystemPropertiesFlags.java +++ b/core/java/com/android/internal/config/sysui/SystemUiSystemPropertiesFlags.java @@ -80,7 +80,7 @@ public class SystemUiSystemPropertiesFlags { /** Gating the logging of DND state change events. */ public static final Flag LOG_DND_STATE_EVENTS = - devFlag("persist.sysui.notification.log_dnd_state_events"); + releasedFlag("persist.sysui.notification.log_dnd_state_events"); } //// == End of flags. Everything below this line is the implementation. == //// diff --git a/core/res/res/layout/language_picker_section_header.xml b/core/res/res/layout/language_picker_section_header.xml index 58042f9a42f8..54ac6776dd04 100644 --- a/core/res/res/layout/language_picker_section_header.xml +++ b/core/res/res/layout/language_picker_section_header.xml @@ -25,4 +25,5 @@ android:textColor="?android:attr/colorAccent" android:textStyle="bold" android:id="@+id/language_picker_header" - tools:text="@string/language_picker_section_all"/> + tools:text="@string/language_picker_section_all" + android:scrollbars="none"/> diff --git a/core/tests/coretests/src/android/content/OWNERS b/core/tests/coretests/src/android/content/OWNERS index a69c6ffef8d8..6f38b848853b 100644 --- a/core/tests/coretests/src/android/content/OWNERS +++ b/core/tests/coretests/src/android/content/OWNERS @@ -5,3 +5,5 @@ per-file *Launcher* = file:/core/java/android/content/pm/LAUNCHER_OWNERS per-file *Shortcut* = file:/core/java/android/content/pm/SHORTCUT_OWNERS per-file *ComponentCallbacks* = file:/services/core/java/com/android/server/wm/OWNERS per-file *ComponentCallbacks* = charlesccchen@google.com +per-file ContentCaptureOptions* = file:/core/java/android/view/contentcapture/OWNERS + diff --git a/core/tests/coretests/src/android/content/res/FontScaleConverterActivityTest.java b/core/tests/coretests/src/android/content/res/FontScaleConverterActivityTest.java index 6e8e93a8c86b..b2e42ba90152 100644 --- a/core/tests/coretests/src/android/content/res/FontScaleConverterActivityTest.java +++ b/core/tests/coretests/src/android/content/res/FontScaleConverterActivityTest.java @@ -26,6 +26,7 @@ import android.app.Activity; import android.compat.testing.PlatformCompatChangeRule; import android.os.Bundle; import android.platform.test.annotations.IwTest; +import android.platform.test.annotations.PlatinumTest; import android.platform.test.annotations.Presubmit; import android.provider.Settings; import android.util.PollingCheck; @@ -72,6 +73,7 @@ public class FontScaleConverterActivityTest { restoreSystemFontScaleToDefault(); } + @PlatinumTest(focusArea = "accessibility") @IwTest(focusArea = "accessibility") @Test public void testFontsScaleNonLinearly() { @@ -103,6 +105,7 @@ public class FontScaleConverterActivityTest { ))); } + @PlatinumTest(focusArea = "accessibility") @IwTest(focusArea = "accessibility") @Test public void testOnConfigurationChanged_doesNotCrash() { @@ -117,6 +120,7 @@ public class FontScaleConverterActivityTest { }); } + @PlatinumTest(focusArea = "accessibility") @IwTest(focusArea = "accessibility") @Test public void testUpdateConfiguration_doesNotCrash() { diff --git a/core/tests/coretests/src/android/view/SurfaceControlRegistryTests.java b/core/tests/coretests/src/android/view/SurfaceControlRegistryTests.java index d10ba7ccbac4..e117051ba9de 100644 --- a/core/tests/coretests/src/android/view/SurfaceControlRegistryTests.java +++ b/core/tests/coretests/src/android/view/SurfaceControlRegistryTests.java @@ -104,6 +104,18 @@ public class SurfaceControlRegistryTests { } @Test + public void testInvalidSurfaceControlNotAddedToRegistry() { + int hash0 = SurfaceControlRegistry.getProcessInstance().hashCode(); + // Verify no changes to the registry when dealing with invalid surface controls + SurfaceControl sc0 = new SurfaceControl(); + SurfaceControl sc1 = new SurfaceControl(sc0, "test"); + assertEquals(hash0, SurfaceControlRegistry.getProcessInstance().hashCode()); + sc0.release(); + sc1.release(); + assertEquals(hash0, SurfaceControlRegistry.getProcessInstance().hashCode()); + } + + @Test public void testThresholds() { SurfaceControlRegistry registry = SurfaceControlRegistry.getProcessInstance(); TestReporter reporter = new TestReporter(); diff --git a/core/tests/coretests/src/android/view/contentprotection/OWNERS b/core/tests/coretests/src/android/view/contentprotection/OWNERS new file mode 100644 index 000000000000..b3583a7f6ab1 --- /dev/null +++ b/core/tests/coretests/src/android/view/contentprotection/OWNERS @@ -0,0 +1,4 @@ +# Bug component: 544200 + +include /core/java/android/view/contentcapture/OWNERS + diff --git a/core/tests/coretests/src/android/widget/RemoteViewsTest.java b/core/tests/coretests/src/android/widget/RemoteViewsTest.java index b42a4bdc352f..73aa93603e56 100644 --- a/core/tests/coretests/src/android/widget/RemoteViewsTest.java +++ b/core/tests/coretests/src/android/widget/RemoteViewsTest.java @@ -717,6 +717,19 @@ public class RemoteViewsTest { } @Test + public void visitUris_themedIcons() { + RemoteViews views = new RemoteViews(mPackage, R.layout.remote_views_test); + final Icon iconLight = Icon.createWithContentUri("content://light/icon"); + final Icon iconDark = Icon.createWithContentUri("content://dark/icon"); + views.setIcon(R.id.layout, "setLargeIcon", iconLight, iconDark); + + Consumer<Uri> visitor = (Consumer<Uri>) spy(Consumer.class); + views.visitUris(visitor); + verify(visitor, times(1)).accept(eq(iconLight.getUri())); + verify(visitor, times(1)).accept(eq(iconDark.getUri())); + } + + @Test public void visitUris_nestedViews() { final RemoteViews outer = new RemoteViews(mPackage, R.layout.remote_views_test); diff --git a/core/tests/coretests/src/android/window/SnapshotDrawerUtilsTest.java b/core/tests/coretests/src/android/window/SnapshotDrawerUtilsTest.java index 6764ac85dcef..036154634ae7 100644 --- a/core/tests/coretests/src/android/window/SnapshotDrawerUtilsTest.java +++ b/core/tests/coretests/src/android/window/SnapshotDrawerUtilsTest.java @@ -158,32 +158,42 @@ public class SnapshotDrawerUtilsTest { @Test public void testCalculateSnapshotCrop() { - setupSurface(100, 100, new Rect(0, 10, 0, 10), 0, new Rect(0, 0, 100, 100)); - assertEquals(new Rect(0, 0, 100, 90), mSnapshotSurface.calculateSnapshotCrop()); + final Rect contentInsets = new Rect(0, 10, 0, 10); + setupSurface(100, 100, contentInsets, 0, new Rect(0, 0, 100, 100)); + assertEquals(new Rect(0, 0, 100, 90), + mSnapshotSurface.calculateSnapshotCrop(contentInsets)); } @Test public void testCalculateSnapshotCrop_taskNotOnTop() { - setupSurface(100, 100, new Rect(0, 10, 0, 10), 0, new Rect(0, 50, 100, 150)); - assertEquals(new Rect(0, 10, 100, 90), mSnapshotSurface.calculateSnapshotCrop()); + final Rect contentInsets = new Rect(0, 10, 0, 10); + setupSurface(100, 100, contentInsets, 0, new Rect(0, 50, 100, 150)); + assertEquals(new Rect(0, 10, 100, 90), + mSnapshotSurface.calculateSnapshotCrop(contentInsets)); } @Test public void testCalculateSnapshotCrop_navBarLeft() { - setupSurface(100, 100, new Rect(10, 10, 0, 0), 0, new Rect(0, 0, 100, 100)); - assertEquals(new Rect(10, 0, 100, 100), mSnapshotSurface.calculateSnapshotCrop()); + final Rect contentInsets = new Rect(0, 10, 0, 0); + setupSurface(100, 100, contentInsets, 0, new Rect(0, 0, 100, 100)); + assertEquals(new Rect(10, 0, 100, 100), + mSnapshotSurface.calculateSnapshotCrop(contentInsets)); } @Test public void testCalculateSnapshotCrop_navBarRight() { - setupSurface(100, 100, new Rect(0, 10, 10, 0), 0, new Rect(0, 0, 100, 100)); - assertEquals(new Rect(0, 0, 90, 100), mSnapshotSurface.calculateSnapshotCrop()); + final Rect contentInsets = new Rect(0, 10, 10, 0); + setupSurface(100, 100, contentInsets, 0, new Rect(0, 0, 100, 100)); + assertEquals(new Rect(0, 0, 90, 100), + mSnapshotSurface.calculateSnapshotCrop(contentInsets)); } @Test public void testCalculateSnapshotCrop_waterfall() { - setupSurface(100, 100, new Rect(5, 10, 5, 10), 0, new Rect(0, 0, 100, 100)); - assertEquals(new Rect(5, 0, 95, 90), mSnapshotSurface.calculateSnapshotCrop()); + final Rect contentInsets = new Rect(5, 10, 5, 10); + setupSurface(100, 100, contentInsets, 0, new Rect(0, 0, 100, 100)); + assertEquals(new Rect(5, 0, 95, 90), + mSnapshotSurface.calculateSnapshotCrop(contentInsets)); } @Test diff --git a/core/tests/coretests/src/android/window/WindowOnBackInvokedDispatcherTest.java b/core/tests/coretests/src/android/window/WindowOnBackInvokedDispatcherTest.java index 8e772a2d1845..2ef2d3a968e0 100644 --- a/core/tests/coretests/src/android/window/WindowOnBackInvokedDispatcherTest.java +++ b/core/tests/coretests/src/android/window/WindowOnBackInvokedDispatcherTest.java @@ -16,12 +16,15 @@ package android.window; +import static android.window.OnBackInvokedDispatcher.PRIORITY_DEFAULT; +import static android.window.OnBackInvokedDispatcher.PRIORITY_OVERLAY; + import static org.junit.Assert.assertEquals; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.isNull; +import static org.mockito.Mockito.atLeast; +import static org.mockito.Mockito.atMost; import static org.mockito.Mockito.doReturn; -import static org.mockito.Mockito.reset; -import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verifyNoMoreInteractions; import static org.mockito.Mockito.verifyZeroInteractions; @@ -45,6 +48,9 @@ import org.mockito.Mock; import org.mockito.Mockito; import org.mockito.MockitoAnnotations; +import java.util.ArrayList; +import java.util.List; + /** * Tests for {@link WindowOnBackInvokedDispatcherTest} * @@ -69,6 +75,8 @@ public class WindowOnBackInvokedDispatcherTest { @Mock private ApplicationInfo mApplicationInfo; + private int mCallbackInfoCalls = 0; + private final BackMotionEvent mBackEvent = new BackMotionEvent( /* touchX = */ 0, /* touchY = */ 0, @@ -93,105 +101,243 @@ public class WindowOnBackInvokedDispatcherTest { InstrumentationRegistry.getInstrumentation().waitForIdleSync(); } + private List<OnBackInvokedCallbackInfo> captureCallbackInfo() throws RemoteException { + ArgumentCaptor<OnBackInvokedCallbackInfo> captor = ArgumentCaptor + .forClass(OnBackInvokedCallbackInfo.class); + // atLeast(0) -> get all setOnBackInvokedCallbackInfo() invocations + verify(mWindowSession, atLeast(0)) + .setOnBackInvokedCallbackInfo(Mockito.eq(mWindow), captor.capture()); + verifyNoMoreInteractions(mWindowSession); + return captor.getAllValues(); + } + + private OnBackInvokedCallbackInfo assertSetCallbackInfo() throws RemoteException { + List<OnBackInvokedCallbackInfo> callbackInfos = captureCallbackInfo(); + int actual = callbackInfos.size(); + assertEquals("setOnBackInvokedCallbackInfo", ++mCallbackInfoCalls, actual); + return callbackInfos.get(mCallbackInfoCalls - 1); + } + + private void assertNoSetCallbackInfo() throws RemoteException { + List<OnBackInvokedCallbackInfo> callbackInfos = captureCallbackInfo(); + int actual = callbackInfos.size(); + assertEquals("No setOnBackInvokedCallbackInfo", mCallbackInfoCalls, actual); + } + + private void assertCallbacksSize(int expectedDefault, int expectedOverlay) { + ArrayList<OnBackInvokedCallback> callbacksDefault = mDispatcher + .mOnBackInvokedCallbacks.get(PRIORITY_DEFAULT); + int actualSizeDefault = callbacksDefault != null ? callbacksDefault.size() : 0; + assertEquals("mOnBackInvokedCallbacks DEFAULT size", expectedDefault, actualSizeDefault); + + ArrayList<OnBackInvokedCallback> callbacksOverlay = mDispatcher + .mOnBackInvokedCallbacks.get(PRIORITY_OVERLAY); + int actualSizeOverlay = callbacksOverlay != null ? callbacksOverlay.size() : 0; + assertEquals("mOnBackInvokedCallbacks OVERLAY size", expectedOverlay, actualSizeOverlay); + } + + private void assertTopCallback(OnBackInvokedCallback expectedCallback) { + assertEquals("topCallback", expectedCallback, mDispatcher.getTopCallback()); + } + + @Test + public void registerCallback_samePriority_sameCallback() throws RemoteException { + mDispatcher.registerOnBackInvokedCallback(PRIORITY_DEFAULT, mCallback1); + assertCallbacksSize(/* default */ 1, /* overlay */ 0); + assertSetCallbackInfo(); + assertTopCallback(mCallback1); + + // The callback is removed and added again + mDispatcher.registerOnBackInvokedCallback(PRIORITY_DEFAULT, mCallback1); + assertCallbacksSize(/* default */ 1, /* overlay */ 0); + assertSetCallbackInfo(); + assertTopCallback(mCallback1); + + waitForIdle(); + verifyNoMoreInteractions(mWindowSession); + verifyNoMoreInteractions(mCallback1); + } + + @Test + public void registerCallback_samePriority_differentCallback() throws RemoteException { + mDispatcher.registerOnBackInvokedCallback(PRIORITY_DEFAULT, mCallback1); + assertCallbacksSize(/* default */ 1, /* overlay */ 0); + assertSetCallbackInfo(); + assertTopCallback(mCallback1); + + // The new callback becomes the TopCallback + mDispatcher.registerOnBackInvokedCallback(PRIORITY_DEFAULT, mCallback2); + assertCallbacksSize(/* default */ 2, /* overlay */ 0); + assertSetCallbackInfo(); + assertTopCallback(mCallback2); + + waitForIdle(); + verifyNoMoreInteractions(mWindowSession); + verifyNoMoreInteractions(mCallback1); + verifyNoMoreInteractions(mCallback2); + } + + @Test + public void registerCallback_differentPriority_sameCallback() throws RemoteException { + mDispatcher.registerOnBackInvokedCallback(PRIORITY_OVERLAY, mCallback1); + assertCallbacksSize(/* default */ 0, /* overlay */ 1); + assertSetCallbackInfo(); + assertTopCallback(mCallback1); + + // The callback is moved to the new priority list + mDispatcher.registerOnBackInvokedCallback(PRIORITY_DEFAULT, mCallback1); + assertCallbacksSize(/* default */ 1, /* overlay */ 0); + assertSetCallbackInfo(); + assertTopCallback(mCallback1); + + waitForIdle(); + verifyNoMoreInteractions(mWindowSession); + verifyNoMoreInteractions(mCallback1); + } + + @Test + public void registerCallback_differentPriority_differentCallback() throws RemoteException { + mDispatcher.registerOnBackInvokedCallback(PRIORITY_OVERLAY, mCallback1); + assertSetCallbackInfo(); + assertCallbacksSize(/* default */ 0, /* overlay */ 1); + assertTopCallback(mCallback1); + + // The callback with higher priority is still the TopCallback + mDispatcher.registerOnBackInvokedCallback(PRIORITY_DEFAULT, mCallback2); + assertNoSetCallbackInfo(); + assertCallbacksSize(/* default */ 1, /* overlay */ 1); + assertTopCallback(mCallback1); + + waitForIdle(); + verifyNoMoreInteractions(mWindowSession); + verifyNoMoreInteractions(mCallback1); + verifyNoMoreInteractions(mCallback2); + } + + @Test + public void registerCallback_sameInstanceAddedTwice() throws RemoteException { + mDispatcher.registerOnBackInvokedCallback(PRIORITY_OVERLAY, mCallback1); + assertCallbacksSize(/* default */ 0, /* overlay */ 1); + assertSetCallbackInfo(); + assertTopCallback(mCallback1); + + mDispatcher.registerOnBackInvokedCallback(PRIORITY_DEFAULT, mCallback2); + assertCallbacksSize(/* default */ 1, /* overlay */ 1); + assertNoSetCallbackInfo(); + assertTopCallback(mCallback1); + + mDispatcher.registerOnBackInvokedCallback(PRIORITY_DEFAULT, mCallback1); + assertCallbacksSize(/* default */ 2, /* overlay */ 0); + assertSetCallbackInfo(); + assertTopCallback(mCallback1); + + mDispatcher.registerOnBackInvokedCallback(PRIORITY_OVERLAY, mCallback2); + assertCallbacksSize(/* default */ 1, /* overlay */ 1); + assertSetCallbackInfo(); + assertTopCallback(mCallback2); + + waitForIdle(); + verifyNoMoreInteractions(mWindowSession); + verifyNoMoreInteractions(mCallback1); + verifyNoMoreInteractions(mCallback2); + } + @Test public void propagatesTopCallback_samePriority() throws RemoteException { - ArgumentCaptor<OnBackInvokedCallbackInfo> captor = - ArgumentCaptor.forClass(OnBackInvokedCallbackInfo.class); - - mDispatcher.registerOnBackInvokedCallback( - OnBackInvokedDispatcher.PRIORITY_DEFAULT, mCallback1); - mDispatcher.registerOnBackInvokedCallback( - OnBackInvokedDispatcher.PRIORITY_DEFAULT, mCallback2); - - verify(mWindowSession, times(2)).setOnBackInvokedCallbackInfo( - Mockito.eq(mWindow), - captor.capture()); - captor.getAllValues().get(0).getCallback().onBackStarted(mBackEvent); + mDispatcher.registerOnBackInvokedCallback(PRIORITY_DEFAULT, mCallback1); + OnBackInvokedCallbackInfo callbackInfo1 = assertSetCallbackInfo(); + + mDispatcher.registerOnBackInvokedCallback(PRIORITY_DEFAULT, mCallback2); + OnBackInvokedCallbackInfo callbackInfo2 = assertSetCallbackInfo(); + + callbackInfo1.getCallback().onBackStarted(mBackEvent); + waitForIdle(); verify(mCallback1).onBackStarted(any(BackEvent.class)); verifyZeroInteractions(mCallback2); - captor.getAllValues().get(1).getCallback().onBackStarted(mBackEvent); + callbackInfo2.getCallback().onBackStarted(mBackEvent); + waitForIdle(); verify(mCallback2).onBackStarted(any(BackEvent.class)); + + // Calls sequence: BackProgressAnimator.onBackStarted() -> BackProgressAnimator.reset() -> + // Spring.animateToFinalPosition(0). This causes a progress event to be fired. + verify(mCallback1, atMost(1)).onBackProgressed(any(BackEvent.class)); verifyNoMoreInteractions(mCallback1); } @Test public void propagatesTopCallback_differentPriority() throws RemoteException { - ArgumentCaptor<OnBackInvokedCallbackInfo> captor = - ArgumentCaptor.forClass(OnBackInvokedCallbackInfo.class); + mDispatcher.registerOnBackInvokedCallback(PRIORITY_OVERLAY, mCallback1); + mDispatcher.registerOnBackInvokedCallback(PRIORITY_DEFAULT, mCallback2); - mDispatcher.registerOnBackInvokedCallback( - OnBackInvokedDispatcher.PRIORITY_OVERLAY, mCallback1); - mDispatcher.registerOnBackInvokedCallback( - OnBackInvokedDispatcher.PRIORITY_DEFAULT, mCallback2); + OnBackInvokedCallbackInfo callbackInfo = assertSetCallbackInfo(); - verify(mWindowSession).setOnBackInvokedCallbackInfo( - Mockito.eq(mWindow), captor.capture()); verifyNoMoreInteractions(mWindowSession); - assertEquals(captor.getValue().getPriority(), OnBackInvokedDispatcher.PRIORITY_OVERLAY); - captor.getValue().getCallback().onBackStarted(mBackEvent); + assertEquals(callbackInfo.getPriority(), PRIORITY_OVERLAY); + + callbackInfo.getCallback().onBackStarted(mBackEvent); + waitForIdle(); verify(mCallback1).onBackStarted(any(BackEvent.class)); } @Test public void propagatesTopCallback_withRemoval() throws RemoteException { - mDispatcher.registerOnBackInvokedCallback( - OnBackInvokedDispatcher.PRIORITY_DEFAULT, mCallback1); - mDispatcher.registerOnBackInvokedCallback( - OnBackInvokedDispatcher.PRIORITY_DEFAULT, mCallback2); + mDispatcher.registerOnBackInvokedCallback(PRIORITY_DEFAULT, mCallback1); + assertSetCallbackInfo(); + + mDispatcher.registerOnBackInvokedCallback(PRIORITY_DEFAULT, mCallback2); + assertSetCallbackInfo(); - reset(mWindowSession); mDispatcher.unregisterOnBackInvokedCallback(mCallback1); - verifyZeroInteractions(mWindowSession); + + waitForIdle(); + verifyNoMoreInteractions(mWindowSession); + verifyNoMoreInteractions(mCallback1); mDispatcher.unregisterOnBackInvokedCallback(mCallback2); + + waitForIdle(); verify(mWindowSession).setOnBackInvokedCallbackInfo(Mockito.eq(mWindow), isNull()); } @Test public void propagatesTopCallback_sameInstanceAddedTwice() throws RemoteException { - ArgumentCaptor<OnBackInvokedCallbackInfo> captor = - ArgumentCaptor.forClass(OnBackInvokedCallbackInfo.class); - - mDispatcher.registerOnBackInvokedCallback(OnBackInvokedDispatcher.PRIORITY_OVERLAY, - mCallback1 - ); - mDispatcher.registerOnBackInvokedCallback( - OnBackInvokedDispatcher.PRIORITY_DEFAULT, mCallback2); - mDispatcher.registerOnBackInvokedCallback( - OnBackInvokedDispatcher.PRIORITY_DEFAULT, mCallback1); - - reset(mWindowSession); - mDispatcher.registerOnBackInvokedCallback( - OnBackInvokedDispatcher.PRIORITY_OVERLAY, mCallback2); - verify(mWindowSession).setOnBackInvokedCallbackInfo(Mockito.eq(mWindow), captor.capture()); - captor.getValue().getCallback().onBackStarted(mBackEvent); + mDispatcher.registerOnBackInvokedCallback(PRIORITY_OVERLAY, mCallback1); + assertSetCallbackInfo(); + mDispatcher.registerOnBackInvokedCallback(PRIORITY_DEFAULT, mCallback2); + assertNoSetCallbackInfo(); + mDispatcher.registerOnBackInvokedCallback(PRIORITY_DEFAULT, mCallback1); + assertSetCallbackInfo(); + + mDispatcher.registerOnBackInvokedCallback(PRIORITY_OVERLAY, mCallback2); + + OnBackInvokedCallbackInfo lastCallbackInfo = assertSetCallbackInfo(); + + lastCallbackInfo.getCallback().onBackStarted(mBackEvent); + waitForIdle(); verify(mCallback2).onBackStarted(any(BackEvent.class)); } @Test public void onUnregisterWhileBackInProgress_callOnBackCancelled() throws RemoteException { - ArgumentCaptor<OnBackInvokedCallbackInfo> captor = - ArgumentCaptor.forClass(OnBackInvokedCallbackInfo.class); + mDispatcher.registerOnBackInvokedCallback(PRIORITY_DEFAULT, mCallback1); - mDispatcher.registerOnBackInvokedCallback( - OnBackInvokedDispatcher.PRIORITY_DEFAULT, mCallback1); + OnBackInvokedCallbackInfo callbackInfo = assertSetCallbackInfo(); + + callbackInfo.getCallback().onBackStarted(mBackEvent); - verify(mWindowSession).setOnBackInvokedCallbackInfo( - Mockito.eq(mWindow), - captor.capture()); - IOnBackInvokedCallback iOnBackInvokedCallback = captor.getValue().getCallback(); - iOnBackInvokedCallback.onBackStarted(mBackEvent); waitForIdle(); verify(mCallback1).onBackStarted(any(BackEvent.class)); mDispatcher.unregisterOnBackInvokedCallback(mCallback1); + + waitForIdle(); verify(mCallback1).onBackCancelled(); - verifyNoMoreInteractions(mCallback1); + verify(mWindowSession).setOnBackInvokedCallbackInfo(Mockito.eq(mWindow), isNull()); } } diff --git a/graphics/java/android/graphics/GraphicBuffer.java b/graphics/java/android/graphics/GraphicBuffer.java index f9113a21405c..6705b25ab0ec 100644 --- a/graphics/java/android/graphics/GraphicBuffer.java +++ b/graphics/java/android/graphics/GraphicBuffer.java @@ -57,7 +57,7 @@ public class GraphicBuffer implements Parcelable { private final int mUsage; // Note: do not rename, this field is used by native code @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) - private final long mNativeObject; + private long mNativeObject; // These two fields are only used by lock/unlockCanvas() private Canvas mCanvas; @@ -219,6 +219,7 @@ public class GraphicBuffer implements Parcelable { if (!mDestroyed) { mDestroyed = true; nDestroyGraphicBuffer(mNativeObject); + mNativeObject = 0; } } @@ -239,7 +240,7 @@ public class GraphicBuffer implements Parcelable { @Override protected void finalize() throws Throwable { try { - if (!mDestroyed) nDestroyGraphicBuffer(mNativeObject); + destroy(); } finally { super.finalize(); } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/keyguard/KeyguardTransitionHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/keyguard/KeyguardTransitionHandler.java index 658359ed31d0..513638eeb960 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/keyguard/KeyguardTransitionHandler.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/keyguard/KeyguardTransitionHandler.java @@ -166,7 +166,7 @@ public class KeyguardTransitionHandler implements Transitions.TransitionHandler "unocclude", transition, info, startTransaction, finishTransaction, finishCallback); } else { - Log.wtf(TAG, "Failed to play: " + info); + Log.w(TAG, "Failed to play: " + info); return false; } } @@ -185,7 +185,11 @@ public class KeyguardTransitionHandler implements Transitions.TransitionHandler @Override public void onTransitionFinished( WindowContainerTransaction wct, SurfaceControl.Transaction sct) { + if (sct != null) { + finishTransaction.merge(sct); + } mMainExecutor.execute(() -> { + mStartedTransitions.remove(transition); finishCallback.onTransitionFinished(wct, null); }); } @@ -206,7 +210,7 @@ public class KeyguardTransitionHandler implements Transitions.TransitionHandler final IRemoteTransition playing = mStartedTransitions.get(currentTransition); if (playing == null) { - ProtoLog.e(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, + ProtoLog.e(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, "unknown keyguard transition %s", currentTransition); return; } @@ -217,14 +221,17 @@ public class KeyguardTransitionHandler implements Transitions.TransitionHandler // the device sleeping/waking, so it's best to ignore this and keep playing anyway. return; } else { - finishAnimationImmediately(currentTransition); + finishAnimationImmediately(currentTransition, playing); } } @Override public void onTransitionConsumed(IBinder transition, boolean aborted, SurfaceControl.Transaction finishTransaction) { - finishAnimationImmediately(transition); + final IRemoteTransition playing = mStartedTransitions.remove(transition); + if (playing != null) { + finishAnimationImmediately(transition, playing); + } } @Nullable @@ -234,21 +241,17 @@ public class KeyguardTransitionHandler implements Transitions.TransitionHandler return null; } - private void finishAnimationImmediately(IBinder transition) { - final IRemoteTransition playing = mStartedTransitions.get(transition); - - if (playing != null) { - final IBinder fakeTransition = new Binder(); - final TransitionInfo fakeInfo = new TransitionInfo(TRANSIT_SLEEP, 0x0); - final SurfaceControl.Transaction fakeT = new SurfaceControl.Transaction(); - final FakeFinishCallback fakeFinishCb = new FakeFinishCallback(); - try { - playing.mergeAnimation(fakeTransition, fakeInfo, fakeT, transition, fakeFinishCb); - } catch (RemoteException e) { - // There is no good reason for this to happen because the player is a local object - // implementing an AIDL interface. - Log.wtf(TAG, "RemoteException thrown from KeyguardService transition", e); - } + private void finishAnimationImmediately(IBinder transition, IRemoteTransition playing) { + final IBinder fakeTransition = new Binder(); + final TransitionInfo fakeInfo = new TransitionInfo(TRANSIT_SLEEP, 0x0); + final SurfaceControl.Transaction fakeT = new SurfaceControl.Transaction(); + final FakeFinishCallback fakeFinishCb = new FakeFinishCallback(); + try { + playing.mergeAnimation(fakeTransition, fakeInfo, fakeT, transition, fakeFinishCb); + } catch (RemoteException e) { + // There is no good reason for this to happen because the player is a local object + // implementing an AIDL interface. + Log.wtf(TAG, "RemoteException thrown from KeyguardService transition", e); } } 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/src/com/android/wm/shell/splitscreen/SplitScreenTransitions.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenTransitions.java index d2b0e2800dc9..986309948ada 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenTransitions.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenTransitions.java @@ -348,8 +348,6 @@ class SplitScreenTransitions { WindowContainerTransaction wct, @Nullable RemoteTransition remoteTransition, Transitions.TransitionHandler handler, - @Nullable TransitionConsumedCallback consumedCallback, - @Nullable TransitionFinishedCallback finishedCallback, int extraTransitType, boolean resizeAnim) { if (mPendingEnter != null) { ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, " splitTransition " @@ -357,20 +355,16 @@ class SplitScreenTransitions { return null; } final IBinder transition = mTransitions.startTransition(transitType, wct, handler); - setEnterTransition(transition, remoteTransition, consumedCallback, finishedCallback, - extraTransitType, resizeAnim); + setEnterTransition(transition, remoteTransition, extraTransitType, resizeAnim); return transition; } /** Sets a transition to enter split. */ void setEnterTransition(@NonNull IBinder transition, @Nullable RemoteTransition remoteTransition, - @Nullable TransitionConsumedCallback consumedCallback, - @Nullable TransitionFinishedCallback finishedCallback, int extraTransitType, boolean resizeAnim) { mPendingEnter = new EnterSession( - transition, consumedCallback, finishedCallback, remoteTransition, extraTransitType, - resizeAnim); + transition, remoteTransition, extraTransitType, resizeAnim); ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, " splitTransition " + " deduced Enter split screen"); @@ -608,12 +602,10 @@ class SplitScreenTransitions { final boolean mResizeAnim; EnterSession(IBinder transition, - @Nullable TransitionConsumedCallback consumedCallback, - @Nullable TransitionFinishedCallback finishedCallback, @Nullable RemoteTransition remoteTransition, int extraTransitType, boolean resizeAnim) { - super(transition, consumedCallback, finishedCallback, remoteTransition, - extraTransitType); + super(transition, null /* consumedCallback */, null /* finishedCallback */, + remoteTransition, extraTransitType); this.mResizeAnim = resizeAnim; } } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java index 256d48c5618c..b2526ee97a21 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java @@ -396,7 +396,7 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, prepareEnterSplitScreen(wct, task, stagePosition); if (ENABLE_SHELL_TRANSITIONS) { mSplitTransitions.startEnterTransition(TRANSIT_TO_FRONT, wct, - null, this, null /* consumedCallback */, null /* finishedCallback */, + null, this, isSplitScreenVisible() ? TRANSIT_SPLIT_SCREEN_OPEN_TO_SIDE : TRANSIT_SPLIT_SCREEN_PAIR_OPEN, !mIsDropEntering); @@ -507,8 +507,7 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, prepareEnterSplitScreen(wct, null /* taskInfo */, position); mSplitTransitions.startEnterTransition(TRANSIT_TO_FRONT, wct, null, this, - null /* consumedCallback */, null /* finishedCallback */, extraTransitType, - !mIsDropEntering); + extraTransitType, !mIsDropEntering); } /** Launches an activity into split by legacy transition. */ @@ -665,8 +664,7 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, if (mPausingTasks.contains(mainTaskId)) { mPausingTasks.clear(); } - mSplitTransitions.startEnterTransition( - TRANSIT_TO_FRONT, wct, remoteTransition, this, null, null, + mSplitTransitions.startEnterTransition(TRANSIT_TO_FRONT, wct, remoteTransition, this, TRANSIT_SPLIT_SCREEN_PAIR_OPEN, false); setEnterInstanceId(instanceId); } @@ -716,8 +714,7 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, wct.sendPendingIntent(pendingIntent2, fillInIntent2, options2); } - mSplitTransitions.startEnterTransition( - TRANSIT_TO_FRONT, wct, remoteTransition, this, null, null, + mSplitTransitions.startEnterTransition(TRANSIT_TO_FRONT, wct, remoteTransition, this, TRANSIT_SPLIT_SCREEN_PAIR_OPEN, false); setEnterInstanceId(instanceId); } @@ -1453,8 +1450,6 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, if (!mMainStage.isActive()) return; mSideStage.removeAllTasks(wct, stageToTop == STAGE_TYPE_SIDE); mMainStage.deactivate(wct, stageToTop == STAGE_TYPE_MAIN); - wct.setReparentLeafTaskIfRelaunch(mRootTaskInfo.token, - false /* reparentLeafTaskIfRelaunch */); } private void prepareEnterSplitScreen(WindowContainerTransaction wct) { @@ -2295,14 +2290,28 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, out = new WindowContainerTransaction(); final StageTaskListener stage = getStageOfTask(triggerTask); if (stage != null) { - // Dismiss split if the last task in one of the stages is going away if (isClosingType(type) && stage.getChildCount() == 1) { + // Dismiss split if the last task in one of the stages is going away // The top should be the opposite side that is closing: - int dismissTop = getStageType(stage) == STAGE_TYPE_MAIN ? STAGE_TYPE_SIDE - : STAGE_TYPE_MAIN; + int dismissTop = getStageType(stage) == STAGE_TYPE_MAIN + ? STAGE_TYPE_SIDE : STAGE_TYPE_MAIN; prepareExitSplitScreen(dismissTop, out); mSplitTransitions.setDismissTransition(transition, dismissTop, EXIT_REASON_APP_FINISHED); + } else if (isOpening && !mPausingTasks.isEmpty()) { + // One of the splitting task is opening while animating the split pair in + // recents, which means to dismiss the split pair to this task. + int dismissTop = getStageType(stage) == STAGE_TYPE_MAIN + ? STAGE_TYPE_MAIN : STAGE_TYPE_SIDE; + prepareExitSplitScreen(dismissTop, out); + mSplitTransitions.setDismissTransition(transition, dismissTop, + EXIT_REASON_APP_FINISHED); + } else if (!isSplitScreenVisible() && isOpening) { + // If split running backgroud and trigger task is appearing into split, + // prepare to enter split screen. + prepareEnterSplitScreen(out); + mSplitTransitions.setEnterTransition(transition, request.getRemoteTransition(), + TRANSIT_SPLIT_SCREEN_PAIR_OPEN, !mIsDropEntering); } } else if (isOpening && inFullscreen) { final int activityType = triggerTask.getActivityType(); @@ -2327,8 +2336,7 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, out = new WindowContainerTransaction(); prepareEnterSplitScreen(out); mSplitTransitions.setEnterTransition(transition, request.getRemoteTransition(), - null /* consumedCallback */, null /* finishedCallback */, - TRANSIT_SPLIT_SCREEN_OPEN_TO_SIDE, !mIsDropEntering); + TRANSIT_SPLIT_SCREEN_PAIR_OPEN, !mIsDropEntering); } } return out; @@ -2586,6 +2594,7 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, // First, verify that we actually have opened apps in both splits. TransitionInfo.Change mainChild = null; TransitionInfo.Change sideChild = null; + final WindowContainerTransaction evictWct = new WindowContainerTransaction(); for (int iC = 0; iC < info.getChanges().size(); ++iC) { final TransitionInfo.Change change = info.getChanges().get(iC); final ActivityManager.RunningTaskInfo taskInfo = change.getTaskInfo(); @@ -2594,12 +2603,16 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, continue; } final @StageType int stageType = getStageType(getStageOfTask(taskInfo)); - if (stageType == STAGE_TYPE_MAIN + if (mainChild == null && stageType == STAGE_TYPE_MAIN && (isOpeningType(change.getMode()) || change.getMode() == TRANSIT_CHANGE)) { // Includes TRANSIT_CHANGE to cover reparenting top-most task to split. mainChild = change; - } else if (stageType == STAGE_TYPE_SIDE && isOpeningType(change.getMode())) { + } else if (sideChild == null && stageType == STAGE_TYPE_SIDE + && isOpeningType(change.getMode())) { sideChild = change; + } else if (stageType != STAGE_TYPE_UNDEFINED && change.getMode() == TRANSIT_TO_BACK) { + // Collect all to back task's and evict them when transition finished. + evictWct.reparent(taskInfo.token, null /* parent */, false /* onTop */); } } @@ -2661,10 +2674,14 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, mSideStage.evictInvisibleChildren(callbackWct); } } + if (!evictWct.isEmpty()) { + callbackWct.merge(evictWct, true); + } if (enterTransition.mResizeAnim) { mShowDecorImmediately = true; mSplitLayout.flingDividerToCenter(); } + callbackWct.setReparentLeafTaskIfRelaunch(mRootTaskInfo.token, false); mPausingTasks.clear(); }); @@ -2815,6 +2832,7 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, dismissTransition.setFinishedCallback((callbackWct, callbackT) -> { mMainStage.getSplitDecorManager().release(callbackT); mSideStage.getSplitDecorManager().release(callbackT); + callbackWct.setReparentLeafTaskIfRelaunch(mRootTaskInfo.token, false); }); addDividerBarToTransition(info, false /* show */); @@ -2868,8 +2886,7 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, } setSplitsVisible(false); - finishWct.setReparentLeafTaskIfRelaunch(mRootTaskInfo.token, - true /* reparentLeafTaskIfRelaunch */); + prepareExitSplitScreen(STAGE_TYPE_UNDEFINED, finishWct); logExit(EXIT_REASON_UNKNOWN); } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java index dc8a25842969..4c678a2bf29b 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java @@ -327,6 +327,7 @@ public class DefaultTransitionHandler implements Transitions.TransitionHandler { final int wallpaperTransit = getWallpaperTransitType(info); boolean isDisplayRotationAnimationStarted = false; final boolean isDreamTransition = isDreamTransition(info); + final boolean isOnlyTranslucent = isOnlyTranslucent(info); for (int i = info.getChanges().size() - 1; i >= 0; --i) { final TransitionInfo.Change change = info.getChanges().get(i); @@ -452,6 +453,17 @@ public class DefaultTransitionHandler implements Transitions.TransitionHandler { final int layer = zSplitLine + numChanges - i; startTransaction.setLayer(change.getLeash(), layer); } + } else if (isOnlyTranslucent && TransitionUtil.isOpeningType(info.getType()) + && TransitionUtil.isClosingType(mode)) { + // If there is a closing translucent task in an OPENING transition, we will + // actually select a CLOSING animation, so move the closing task into + // the animating part of the z-order. + + // See Transitions#setupAnimHierarchy for details about these variables. + final int numChanges = info.getChanges().size(); + final int zSplitLine = numChanges + 1; + final int layer = zSplitLine + numChanges - i; + startTransaction.setLayer(change.getLeash(), layer); } } @@ -543,6 +555,29 @@ public class DefaultTransitionHandler implements Transitions.TransitionHandler { return false; } + /** + * Does `info` only contain translucent visibility changes (CHANGEs are ignored). We select + * different animations and z-orders for these + */ + private static boolean isOnlyTranslucent(@NonNull TransitionInfo info) { + int translucentOpen = 0; + int translucentClose = 0; + for (int i = info.getChanges().size() - 1; i >= 0; --i) { + final TransitionInfo.Change change = info.getChanges().get(i); + if (change.getMode() == TRANSIT_CHANGE) continue; + if (change.hasFlags(FLAG_TRANSLUCENT)) { + if (TransitionUtil.isOpeningType(change.getMode())) { + translucentOpen += 1; + } else { + translucentClose += 1; + } + } else { + return false; + } + } + return (translucentOpen + translucentClose) > 0; + } + @Override public void mergeAnimation(@NonNull IBinder transition, @NonNull TransitionInfo info, @NonNull SurfaceControl.Transaction t, @NonNull IBinder mergeTarget, diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/TransitionAnimationHelper.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/TransitionAnimationHelper.java index 19d8384ace41..d978eafa97f3 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/TransitionAnimationHelper.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/TransitionAnimationHelper.java @@ -23,6 +23,7 @@ import static android.view.WindowManager.TRANSIT_OPEN; import static android.view.WindowManager.TRANSIT_TO_BACK; import static android.view.WindowManager.TRANSIT_TO_FRONT; import static android.view.WindowManager.transitTypeToString; +import static android.window.TransitionInfo.FLAGS_IS_NON_APP_WINDOW; import static android.window.TransitionInfo.FLAG_STARTING_WINDOW_TRANSFER_RECIPIENT; import static android.window.TransitionInfo.FLAG_TRANSLUCENT; @@ -102,10 +103,11 @@ public class TransitionAnimationHelper { // We will translucent open animation for translucent activities and tasks. Choose // WindowAnimation_activityOpenEnterAnimation and set translucent here, then // TransitionAnimation loads appropriate animation later. - if ((changeFlags & FLAG_TRANSLUCENT) != 0 && enter) { - translucent = true; - } - if (isTask && !translucent) { + translucent = (changeFlags & FLAG_TRANSLUCENT) != 0; + if (isTask && translucent && !enter) { + // For closing translucent tasks, use the activity close animation + animAttr = R.styleable.WindowAnimation_activityCloseExitAnimation; + } else if (isTask && !translucent) { animAttr = enter ? R.styleable.WindowAnimation_taskOpenEnterAnimation : R.styleable.WindowAnimation_taskOpenExitAnimation; @@ -150,6 +152,10 @@ public class TransitionAnimationHelper { .loadAnimationAttr(options.getPackageName(), options.getAnimations(), animAttr, translucent); } + } else if (translucent && !isTask && ((changeFlags & FLAGS_IS_NON_APP_WINDOW) == 0)) { + // Un-styled translucent activities technically have undefined animations; however, + // as is always the case, some apps now rely on this being no-animation, so skip + // loading animations here. } else { a = transitionAnimation.loadDefaultAnimationAttr(animAttr, translucent); } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java index 3b306e793640..ce8d792ef302 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java @@ -16,9 +16,12 @@ package com.android.wm.shell.transition; +import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM; +import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN; import static android.view.WindowManager.TRANSIT_CHANGE; import static android.view.WindowManager.TRANSIT_CLOSE; import static android.view.WindowManager.TRANSIT_FIRST_CUSTOM; +import static android.view.WindowManager.TRANSIT_KEYGUARD_OCCLUDE; import static android.view.WindowManager.TRANSIT_OPEN; import static android.view.WindowManager.TRANSIT_SLEEP; import static android.view.WindowManager.TRANSIT_TO_BACK; @@ -567,8 +570,8 @@ public class Transitions implements RemoteCallable<Transitions>, layer = zSplitLine + numChanges - i; } } else { // CHANGE or other - if (isClosing) { - // Put below CLOSE mode. + if (isClosing || TransitionUtil.isOrderOnly(change)) { + // Put below CLOSE mode (in the "static" section). layer = zSplitLine - i; } else { // Put above CLOSE mode. @@ -1081,6 +1084,16 @@ public class Transitions implements RemoteCallable<Transitions>, } } } + if (request.getType() == TRANSIT_KEYGUARD_OCCLUDE && request.getTriggerTask() != null + && request.getTriggerTask().getWindowingMode() == WINDOWING_MODE_FREEFORM) { + // This freeform task is on top of keyguard, so its windowing mode should be changed to + // fullscreen. + if (wct == null) { + wct = new WindowContainerTransaction(); + } + wct.setWindowingMode(request.getTriggerTask().token, WINDOWING_MODE_FULLSCREEN); + wct.setBounds(request.getTriggerTask().token, null); + } mOrganizer.startTransition(transitionToken, wct != null && wct.isEmpty() ? null : wct); active.mToken = transitionToken; // Currently, WMCore only does one transition at a time. If it makes a requestStart, it diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/CopyContentInSplit.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/CopyContentInSplit.kt index b7e73ad11c9f..72f25f36c9d9 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/CopyContentInSplit.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/CopyContentInSplit.kt @@ -18,6 +18,7 @@ package com.android.wm.shell.flicker.splitscreen import android.platform.test.annotations.FlakyTest import android.platform.test.annotations.IwTest +import android.platform.test.annotations.PlatinumTest import android.platform.test.annotations.Presubmit import android.tools.common.traces.component.ComponentNameMatcher import android.tools.common.traces.component.EdgeExtensionComponentMatcher @@ -60,6 +61,7 @@ class CopyContentInSplit(override val flicker: FlickerTest) : thisTransition(this) } + @PlatinumTest(focusArea = "sysui") @IwTest(focusArea = "sysui") @Presubmit @Test diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/DragDividerToResize.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/DragDividerToResize.kt index 0b0a3dad320b..ed3df9ced3b0 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/DragDividerToResize.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/DragDividerToResize.kt @@ -18,6 +18,7 @@ package com.android.wm.shell.flicker.splitscreen import android.platform.test.annotations.FlakyTest import android.platform.test.annotations.IwTest +import android.platform.test.annotations.PlatinumTest import android.platform.test.annotations.Presubmit import android.tools.device.flicker.junit.FlickerParametersRunnerFactory import android.tools.device.flicker.legacy.FlickerBuilder @@ -58,6 +59,7 @@ class DragDividerToResize(override val flicker: FlickerTest) : thisTransition(this) } + @PlatinumTest(focusArea = "sysui") @IwTest(focusArea = "sysui") @Presubmit @Test diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchAppByDoubleTapDivider.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchAppByDoubleTapDivider.kt index 8cf871f88314..b4c6afd14a90 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchAppByDoubleTapDivider.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchAppByDoubleTapDivider.kt @@ -17,6 +17,7 @@ package com.android.wm.shell.flicker.splitscreen import android.platform.test.annotations.IwTest +import android.platform.test.annotations.PlatinumTest import android.platform.test.annotations.Postsubmit import android.platform.test.annotations.Presubmit import android.tools.common.NavBar @@ -59,6 +60,7 @@ class SwitchAppByDoubleTapDivider(override val flicker: FlickerTest) : thisTransition(this) } + @PlatinumTest(focusArea = "sysui") @IwTest(focusArea = "sysui") @Presubmit @Test diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchBetweenSplitPairs.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchBetweenSplitPairs.kt index 3b2da8dbcf9f..2cedc3570cfd 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchBetweenSplitPairs.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchBetweenSplitPairs.kt @@ -18,6 +18,7 @@ package com.android.wm.shell.flicker.splitscreen import android.platform.test.annotations.FlakyTest import android.platform.test.annotations.IwTest +import android.platform.test.annotations.PlatinumTest import android.platform.test.annotations.Presubmit import android.tools.device.flicker.junit.FlickerParametersRunnerFactory import android.tools.device.flicker.legacy.FlickerBuilder @@ -62,6 +63,7 @@ class SwitchBetweenSplitPairs(override val flicker: FlickerTest) : thisTransition(this) } + @PlatinumTest(focusArea = "sysui") @IwTest(focusArea = "sysui") @Presubmit @Test diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/UnlockKeyguardToSplitScreen.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/UnlockKeyguardToSplitScreen.kt index e0dbfa3853cc..676c150815ad 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/UnlockKeyguardToSplitScreen.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/UnlockKeyguardToSplitScreen.kt @@ -18,7 +18,6 @@ package com.android.wm.shell.flicker.splitscreen import android.platform.test.annotations.Postsubmit import android.tools.common.NavBar -import android.tools.common.Rotation import android.tools.common.flicker.subject.region.RegionSubject import android.tools.device.flicker.junit.FlickerParametersRunnerFactory import android.tools.device.flicker.legacy.FlickerBuilder @@ -105,8 +104,6 @@ class UnlockKeyguardToSplitScreen(override val flicker: FlickerTest) : @JvmStatic fun getParams(): List<FlickerTest> { return FlickerTestFactory.nonRotationTests( - // TODO(b/283963801) address entireScreenCovered test faliure in landscape. - supportedRotations = listOf(Rotation.ROTATION_0), supportedNavigationModes = listOf(NavBar.MODE_GESTURAL) ) } diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/CopyContentInSplitBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/CopyContentInSplitBenchmark.kt index a189a3f67eca..a5ad97d8ad65 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/CopyContentInSplitBenchmark.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/CopyContentInSplitBenchmark.kt @@ -17,6 +17,7 @@ package com.android.wm.shell.flicker.splitscreen.benchmark import android.platform.test.annotations.IwTest +import android.platform.test.annotations.PlatinumTest import android.platform.test.annotations.Presubmit import android.tools.common.traces.component.ComponentNameMatcher import android.tools.device.flicker.junit.FlickerParametersRunnerFactory @@ -62,6 +63,7 @@ open class CopyContentInSplitBenchmark(override val flicker: FlickerTest) : thisTransition(this) } + @PlatinumTest(focusArea = "sysui") @IwTest(focusArea = "sysui") @Presubmit @Test diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/DismissSplitScreenByDividerBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/DismissSplitScreenByDividerBenchmark.kt index 55ab7b3a30e4..fa6a4bfbcda6 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/DismissSplitScreenByDividerBenchmark.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/DismissSplitScreenByDividerBenchmark.kt @@ -17,6 +17,7 @@ package com.android.wm.shell.flicker.splitscreen.benchmark import android.platform.test.annotations.IwTest +import android.platform.test.annotations.PlatinumTest import android.platform.test.annotations.Presubmit import android.tools.device.flicker.junit.FlickerParametersRunnerFactory import android.tools.device.flicker.legacy.FlickerBuilder @@ -68,6 +69,7 @@ open class DismissSplitScreenByDividerBenchmark(flicker: FlickerTest) : SplitScr thisTransition(this) } + @PlatinumTest(focusArea = "sysui") @IwTest(focusArea = "sysui") @Presubmit @Test diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/DismissSplitScreenByGoHomeBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/DismissSplitScreenByGoHomeBenchmark.kt index c4cfd1add25c..d2beb678a4d5 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/DismissSplitScreenByGoHomeBenchmark.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/DismissSplitScreenByGoHomeBenchmark.kt @@ -17,6 +17,7 @@ package com.android.wm.shell.flicker.splitscreen.benchmark import android.platform.test.annotations.IwTest +import android.platform.test.annotations.PlatinumTest import android.platform.test.annotations.Presubmit import android.tools.device.flicker.junit.FlickerParametersRunnerFactory import android.tools.device.flicker.legacy.FlickerBuilder @@ -55,6 +56,7 @@ open class DismissSplitScreenByGoHomeBenchmark(override val flicker: FlickerTest thisTransition(this) } + @PlatinumTest(focusArea = "sysui") @IwTest(focusArea = "sysui") @Presubmit @Test diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/DragDividerToResizeBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/DragDividerToResizeBenchmark.kt index 146287c21c75..e95fd947ebde 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/DragDividerToResizeBenchmark.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/DragDividerToResizeBenchmark.kt @@ -17,6 +17,7 @@ package com.android.wm.shell.flicker.splitscreen.benchmark import android.platform.test.annotations.IwTest +import android.platform.test.annotations.PlatinumTest import android.platform.test.annotations.Presubmit import android.tools.device.flicker.junit.FlickerParametersRunnerFactory import android.tools.device.flicker.legacy.FlickerBuilder @@ -58,6 +59,7 @@ open class DragDividerToResizeBenchmark(override val flicker: FlickerTest) : Assume.assumeTrue(tapl.isTablet || !flicker.scenario.isLandscapeOrSeascapeAtStart) } + @PlatinumTest(focusArea = "sysui") @IwTest(focusArea = "sysui") @Presubmit @Test diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/EnterSplitScreenByDragFromAllAppsBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/EnterSplitScreenByDragFromAllAppsBenchmark.kt index cc715021adf4..63b74e29c39c 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/EnterSplitScreenByDragFromAllAppsBenchmark.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/EnterSplitScreenByDragFromAllAppsBenchmark.kt @@ -17,6 +17,7 @@ package com.android.wm.shell.flicker.splitscreen.benchmark import android.platform.test.annotations.IwTest +import android.platform.test.annotations.PlatinumTest import android.platform.test.annotations.Presubmit import android.tools.common.NavBar import android.tools.device.flicker.junit.FlickerParametersRunnerFactory @@ -70,6 +71,7 @@ open class EnterSplitScreenByDragFromAllAppsBenchmark(override val flicker: Flic Assume.assumeTrue(tapl.isTablet) } + @PlatinumTest(focusArea = "sysui") @IwTest(focusArea = "sysui") @Presubmit @Test diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/EnterSplitScreenByDragFromNotificationBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/EnterSplitScreenByDragFromNotificationBenchmark.kt index de78f09d3ef4..e94da8713b43 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/EnterSplitScreenByDragFromNotificationBenchmark.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/EnterSplitScreenByDragFromNotificationBenchmark.kt @@ -17,6 +17,7 @@ package com.android.wm.shell.flicker.splitscreen.benchmark import android.platform.test.annotations.IwTest +import android.platform.test.annotations.PlatinumTest import android.platform.test.annotations.Presubmit import android.tools.common.NavBar import android.tools.device.flicker.junit.FlickerParametersRunnerFactory @@ -67,6 +68,7 @@ open class EnterSplitScreenByDragFromNotificationBenchmark(override val flicker: thisTransition(this) } + @PlatinumTest(focusArea = "sysui") @IwTest(focusArea = "sysui") @Presubmit @Test diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/EnterSplitScreenByDragFromShortcutBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/EnterSplitScreenByDragFromShortcutBenchmark.kt index a29eb4085e54..f41117f0d54e 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/EnterSplitScreenByDragFromShortcutBenchmark.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/EnterSplitScreenByDragFromShortcutBenchmark.kt @@ -17,6 +17,7 @@ package com.android.wm.shell.flicker.splitscreen.benchmark import android.platform.test.annotations.IwTest +import android.platform.test.annotations.PlatinumTest import android.platform.test.annotations.Presubmit import android.tools.common.NavBar import android.tools.device.flicker.junit.FlickerParametersRunnerFactory @@ -70,6 +71,7 @@ open class EnterSplitScreenByDragFromShortcutBenchmark(flicker: FlickerTest) : thisTransition(this) } + @PlatinumTest(focusArea = "sysui") @IwTest(focusArea = "sysui") @Presubmit @Test diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/EnterSplitScreenByDragFromTaskbarBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/EnterSplitScreenByDragFromTaskbarBenchmark.kt index b2395cafafc1..12f610b73e13 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/EnterSplitScreenByDragFromTaskbarBenchmark.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/EnterSplitScreenByDragFromTaskbarBenchmark.kt @@ -17,6 +17,7 @@ package com.android.wm.shell.flicker.splitscreen.benchmark import android.platform.test.annotations.IwTest +import android.platform.test.annotations.PlatinumTest import android.platform.test.annotations.Presubmit import android.tools.common.NavBar import android.tools.device.flicker.junit.FlickerParametersRunnerFactory @@ -65,6 +66,7 @@ open class EnterSplitScreenByDragFromTaskbarBenchmark(override val flicker: Flic thisTransition(this) } + @PlatinumTest(focusArea = "sysui") @IwTest(focusArea = "sysui") @Presubmit @Test diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/EnterSplitScreenFromOverviewBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/EnterSplitScreenFromOverviewBenchmark.kt index e1d85d0a4371..77818d380053 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/EnterSplitScreenFromOverviewBenchmark.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/EnterSplitScreenFromOverviewBenchmark.kt @@ -17,6 +17,7 @@ package com.android.wm.shell.flicker.splitscreen.benchmark import android.platform.test.annotations.IwTest +import android.platform.test.annotations.PlatinumTest import android.platform.test.annotations.Presubmit import android.tools.device.flicker.junit.FlickerParametersRunnerFactory import android.tools.device.flicker.legacy.FlickerBuilder @@ -64,6 +65,7 @@ open class EnterSplitScreenFromOverviewBenchmark(override val flicker: FlickerTe thisTransition(this) } + @PlatinumTest(focusArea = "sysui") @IwTest(focusArea = "sysui") @Presubmit @Test diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/SwitchAppByDoubleTapDividerBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/SwitchAppByDoubleTapDividerBenchmark.kt index ba8c46091808..6ff22902667c 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/SwitchAppByDoubleTapDividerBenchmark.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/SwitchAppByDoubleTapDividerBenchmark.kt @@ -17,6 +17,7 @@ package com.android.wm.shell.flicker.splitscreen.benchmark import android.platform.test.annotations.IwTest +import android.platform.test.annotations.PlatinumTest import android.platform.test.annotations.Presubmit import android.tools.common.NavBar import android.tools.common.Rotation @@ -134,6 +135,7 @@ open class SwitchAppByDoubleTapDividerBenchmark(override val flicker: FlickerTes return displayBounds.width > displayBounds.height } + @PlatinumTest(focusArea = "sysui") @IwTest(focusArea = "sysui") @Presubmit @Test diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/SwitchBackToSplitFromAnotherAppBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/SwitchBackToSplitFromAnotherAppBenchmark.kt index bbb2edc621e2..400adea8880d 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/SwitchBackToSplitFromAnotherAppBenchmark.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/SwitchBackToSplitFromAnotherAppBenchmark.kt @@ -17,6 +17,7 @@ package com.android.wm.shell.flicker.splitscreen.benchmark import android.platform.test.annotations.IwTest +import android.platform.test.annotations.PlatinumTest import android.platform.test.annotations.Presubmit import android.tools.common.NavBar import android.tools.device.flicker.junit.FlickerParametersRunnerFactory @@ -63,6 +64,7 @@ open class SwitchBackToSplitFromAnotherAppBenchmark(override val flicker: Flicke thisTransition(this) } + @PlatinumTest(focusArea = "sysui") @IwTest(focusArea = "sysui") @Presubmit @Test diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/SwitchBackToSplitFromHomeBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/SwitchBackToSplitFromHomeBenchmark.kt index fa382932eb88..1ec43405ee44 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/SwitchBackToSplitFromHomeBenchmark.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/SwitchBackToSplitFromHomeBenchmark.kt @@ -17,6 +17,7 @@ package com.android.wm.shell.flicker.splitscreen.benchmark import android.platform.test.annotations.IwTest +import android.platform.test.annotations.PlatinumTest import android.platform.test.annotations.Presubmit import android.tools.common.NavBar import android.tools.device.flicker.junit.FlickerParametersRunnerFactory @@ -61,6 +62,7 @@ open class SwitchBackToSplitFromHomeBenchmark(override val flicker: FlickerTest) thisTransition(this) } + @PlatinumTest(focusArea = "sysui") @IwTest(focusArea = "sysui") @Presubmit @Test diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/SwitchBackToSplitFromRecentBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/SwitchBackToSplitFromRecentBenchmark.kt index 1064bd93e3c1..9757153929ca 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/SwitchBackToSplitFromRecentBenchmark.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/SwitchBackToSplitFromRecentBenchmark.kt @@ -17,6 +17,7 @@ package com.android.wm.shell.flicker.splitscreen.benchmark import android.platform.test.annotations.IwTest +import android.platform.test.annotations.PlatinumTest import android.platform.test.annotations.Presubmit import android.tools.common.NavBar import android.tools.device.flicker.junit.FlickerParametersRunnerFactory @@ -61,6 +62,7 @@ open class SwitchBackToSplitFromRecentBenchmark(override val flicker: FlickerTes thisTransition(this) } + @PlatinumTest(focusArea = "sysui") @IwTest(focusArea = "sysui") @Presubmit @Test diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/SwitchBetweenSplitPairsBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/SwitchBetweenSplitPairsBenchmark.kt index 8f4393f8d20d..c19a38dc9daf 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/SwitchBetweenSplitPairsBenchmark.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/SwitchBetweenSplitPairsBenchmark.kt @@ -17,6 +17,7 @@ package com.android.wm.shell.flicker.splitscreen.benchmark import android.platform.test.annotations.IwTest +import android.platform.test.annotations.PlatinumTest import android.platform.test.annotations.Presubmit import android.tools.device.flicker.junit.FlickerParametersRunnerFactory import android.tools.device.flicker.legacy.FlickerBuilder @@ -64,6 +65,7 @@ open class SwitchBetweenSplitPairsBenchmark(override val flicker: FlickerTest) : thisTransition(this) } + @PlatinumTest(focusArea = "sysui") @IwTest(focusArea = "sysui") @Presubmit @Test open fun cujCompleted() {} companion object { diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitTransitionTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitTransitionTests.java index 5f705d743601..3b05651f884b 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitTransitionTests.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitTransitionTests.java @@ -185,7 +185,7 @@ public class SplitTransitionTests extends ShellTestCase { IBinder transition = mSplitScreenTransitions.startEnterTransition( TRANSIT_OPEN, new WindowContainerTransaction(), - new RemoteTransition(testRemote, "Test"), mStageCoordinator, null, null, + new RemoteTransition(testRemote, "Test"), mStageCoordinator, TRANSIT_SPLIT_SCREEN_PAIR_OPEN, false); mMainStage.onTaskAppeared(mMainChild, createMockSurface()); mSideStage.onTaskAppeared(mSideChild, createMockSurface()); @@ -412,7 +412,7 @@ public class SplitTransitionTests extends ShellTestCase { IBinder enterTransit = mSplitScreenTransitions.startEnterTransition( TRANSIT_OPEN, new WindowContainerTransaction(), new RemoteTransition(new TestRemoteTransition(), "Test"), - mStageCoordinator, null, null, TRANSIT_SPLIT_SCREEN_PAIR_OPEN, false); + mStageCoordinator, TRANSIT_SPLIT_SCREEN_PAIR_OPEN, false); mMainStage.onTaskAppeared(mMainChild, createMockSurface()); mSideStage.onTaskAppeared(mSideChild, createMockSurface()); mStageCoordinator.startAnimation(enterTransit, enterInfo, diff --git a/libs/hwui/Properties.cpp b/libs/hwui/Properties.cpp index 7af6efb7da41..06aed63d8def 100644 --- a/libs/hwui/Properties.cpp +++ b/libs/hwui/Properties.cpp @@ -17,7 +17,6 @@ #include "Properties.h" #include "Debug.h" -#include "log/log_main.h" #ifdef __ANDROID__ #include "HWUIProperties.sysprop.h" #endif @@ -220,12 +219,15 @@ RenderPipelineType Properties::getRenderPipelineType() { return sRenderPipelineType; } -void Properties::overrideRenderPipelineType(RenderPipelineType type, bool inUnitTest) { +void Properties::overrideRenderPipelineType(RenderPipelineType type) { // If we're doing actual rendering then we can't change the renderer after it's been set. - // Unit tests can freely change this as often as it wants. - LOG_ALWAYS_FATAL_IF(sRenderPipelineType != RenderPipelineType::NotInitialized && - sRenderPipelineType != type && !inUnitTest, - "Trying to change pipeline but it's already set."); + // Unit tests can freely change this as often as it wants, though, as there's no actual + // GL rendering happening + if (sRenderPipelineType != RenderPipelineType::NotInitialized) { + LOG_ALWAYS_FATAL_IF(sRenderPipelineType != type, + "Trying to change pipeline but it's already set"); + return; + } sRenderPipelineType = type; } diff --git a/libs/hwui/Properties.h b/libs/hwui/Properties.h index 24e206bbc3b1..bb477449fff0 100644 --- a/libs/hwui/Properties.h +++ b/libs/hwui/Properties.h @@ -303,7 +303,7 @@ public: static bool enableRTAnimations; // Used for testing only to change the render pipeline. - static void overrideRenderPipelineType(RenderPipelineType, bool inUnitTest = false); + static void overrideRenderPipelineType(RenderPipelineType); static bool runningInEmulator; diff --git a/libs/hwui/renderthread/VulkanManager.cpp b/libs/hwui/renderthread/VulkanManager.cpp index 4cffc6c2efe3..d4e919fefbbd 100644 --- a/libs/hwui/renderthread/VulkanManager.cpp +++ b/libs/hwui/renderthread/VulkanManager.cpp @@ -23,13 +23,11 @@ #include <GrDirectContext.h> #include <GrTypes.h> #include <android/sync.h> +#include <gui/TraceUtils.h> #include <ui/FatVector.h> #include <vk/GrVkExtensions.h> #include <vk/GrVkTypes.h> -#include <cstring> - -#include <gui/TraceUtils.h> #include "Properties.h" #include "RenderThread.h" #include "pipeline/skia/ShaderCache.h" @@ -90,19 +88,6 @@ static void free_features_extensions_structs(const VkPhysicalDeviceFeatures2& fe } } -GrVkGetProc VulkanManager::sSkiaGetProp = [](const char* proc_name, VkInstance instance, - VkDevice device) { - if (device != VK_NULL_HANDLE) { - if (strcmp("vkQueueSubmit", proc_name) == 0) { - return (PFN_vkVoidFunction)VulkanManager::interceptedVkQueueSubmit; - } else if (strcmp("vkQueueWaitIdle", proc_name) == 0) { - return (PFN_vkVoidFunction)VulkanManager::interceptedVkQueueWaitIdle; - } - return vkGetDeviceProcAddr(device, proc_name); - } - return vkGetInstanceProcAddr(instance, proc_name); -}; - #define GET_PROC(F) m##F = (PFN_vk##F)vkGetInstanceProcAddr(VK_NULL_HANDLE, "vk" #F) #define GET_INST_PROC(F) m##F = (PFN_vk##F)vkGetInstanceProcAddr(mInstance, "vk" #F) #define GET_DEV_PROC(F) m##F = (PFN_vk##F)vkGetDeviceProcAddr(mDevice, "vk" #F) @@ -138,6 +123,7 @@ VulkanManager::~VulkanManager() { } mGraphicsQueue = VK_NULL_HANDLE; + mAHBUploadQueue = VK_NULL_HANDLE; mDevice = VK_NULL_HANDLE; mPhysicalDevice = VK_NULL_HANDLE; mInstance = VK_NULL_HANDLE; @@ -231,7 +217,7 @@ void VulkanManager::setupDevice(GrVkExtensions& grExtensions, VkPhysicalDeviceFe mDriverVersion = physDeviceProperties.driverVersion; // query to get the initial queue props size - uint32_t queueCount; + uint32_t queueCount = 0; mGetPhysicalDeviceQueueFamilyProperties(mPhysicalDevice, &queueCount, nullptr); LOG_ALWAYS_FATAL_IF(!queueCount); @@ -239,11 +225,14 @@ void VulkanManager::setupDevice(GrVkExtensions& grExtensions, VkPhysicalDeviceFe std::unique_ptr<VkQueueFamilyProperties[]> queueProps(new VkQueueFamilyProperties[queueCount]); mGetPhysicalDeviceQueueFamilyProperties(mPhysicalDevice, &queueCount, queueProps.get()); + constexpr auto kRequestedQueueCount = 2; + // iterate to find the graphics queue mGraphicsQueueIndex = queueCount; for (uint32_t i = 0; i < queueCount; i++) { if (queueProps[i].queueFlags & VK_QUEUE_GRAPHICS_BIT) { mGraphicsQueueIndex = i; + LOG_ALWAYS_FATAL_IF(queueProps[i].queueCount < kRequestedQueueCount); break; } } @@ -273,7 +262,14 @@ void VulkanManager::setupDevice(GrVkExtensions& grExtensions, VkPhysicalDeviceFe LOG_ALWAYS_FATAL_IF(!hasKHRSwapchainExtension); } - grExtensions.init(sSkiaGetProp, mInstance, mPhysicalDevice, mInstanceExtensions.size(), + auto getProc = [](const char* proc_name, VkInstance instance, VkDevice device) { + if (device != VK_NULL_HANDLE) { + return vkGetDeviceProcAddr(device, proc_name); + } + return vkGetInstanceProcAddr(instance, proc_name); + }; + + grExtensions.init(getProc, mInstance, mPhysicalDevice, mInstanceExtensions.size(), mInstanceExtensions.data(), mDeviceExtensions.size(), mDeviceExtensions.data()); @@ -312,7 +308,7 @@ void VulkanManager::setupDevice(GrVkExtensions& grExtensions, VkPhysicalDeviceFe // and we can't depend on it on all platforms features.features.robustBufferAccess = VK_FALSE; - float queuePriorities[1] = {0.0}; + float queuePriorities[kRequestedQueueCount] = {0.0}; void* queueNextPtr = nullptr; @@ -345,7 +341,7 @@ void VulkanManager::setupDevice(GrVkExtensions& grExtensions, VkPhysicalDeviceFe queueNextPtr, // pNext 0, // VkDeviceQueueCreateFlags mGraphicsQueueIndex, // queueFamilyIndex - 1, // queueCount + kRequestedQueueCount, // queueCount queuePriorities, // pQueuePriorities }; @@ -403,6 +399,7 @@ void VulkanManager::initialize() { this->setupDevice(mExtensions, mPhysicalDeviceFeatures2); mGetDeviceQueue(mDevice, mGraphicsQueueIndex, 0, &mGraphicsQueue); + mGetDeviceQueue(mDevice, mGraphicsQueueIndex, 1, &mAHBUploadQueue); if (Properties::enablePartialUpdates && Properties::useBufferAge) { mSwapBehavior = SwapBehavior::BufferAge; @@ -416,16 +413,24 @@ static void onGrContextReleased(void* context) { sk_sp<GrDirectContext> VulkanManager::createContext(GrContextOptions& options, ContextType contextType) { + auto getProc = [](const char* proc_name, VkInstance instance, VkDevice device) { + if (device != VK_NULL_HANDLE) { + return vkGetDeviceProcAddr(device, proc_name); + } + return vkGetInstanceProcAddr(instance, proc_name); + }; + GrVkBackendContext backendContext; backendContext.fInstance = mInstance; backendContext.fPhysicalDevice = mPhysicalDevice; backendContext.fDevice = mDevice; - backendContext.fQueue = mGraphicsQueue; + backendContext.fQueue = + (contextType == ContextType::kRenderThread) ? mGraphicsQueue : mAHBUploadQueue; backendContext.fGraphicsQueueIndex = mGraphicsQueueIndex; backendContext.fMaxAPIVersion = mAPIVersion; backendContext.fVkExtensions = &mExtensions; backendContext.fDeviceFeatures2 = &mPhysicalDeviceFeatures2; - backendContext.fGetProc = sSkiaGetProp; + backendContext.fGetProc = std::move(getProc); LOG_ALWAYS_FATAL_IF(options.fContextDeleteProc != nullptr, "Conflicting fContextDeleteProcs!"); this->incStrong((void*)onGrContextReleased); @@ -636,8 +641,6 @@ void VulkanManager::swapBuffers(VulkanSurface* surface, const SkRect& dirtyRect) ALOGE_IF(VK_SUCCESS != err, "VulkanManager::swapBuffers(): Failed to get semaphore Fd"); } else { ALOGE("VulkanManager::swapBuffers(): Semaphore submission failed"); - - std::lock_guard<std::mutex> lock(mGraphicsQueueMutex); mQueueWaitIdle(mGraphicsQueue); } if (mDestroySemaphoreContext) { @@ -652,7 +655,6 @@ void VulkanManager::swapBuffers(VulkanSurface* surface, const SkRect& dirtyRect) void VulkanManager::destroySurface(VulkanSurface* surface) { // Make sure all submit commands have finished before starting to destroy objects. if (VK_NULL_HANDLE != mGraphicsQueue) { - std::lock_guard<std::mutex> lock(mGraphicsQueueMutex); mQueueWaitIdle(mGraphicsQueue); } mDeviceWaitIdle(mDevice); diff --git a/libs/hwui/renderthread/VulkanManager.h b/libs/hwui/renderthread/VulkanManager.h index 00a40c0c85c3..2be1ffdbc423 100644 --- a/libs/hwui/renderthread/VulkanManager.h +++ b/libs/hwui/renderthread/VulkanManager.h @@ -17,10 +17,6 @@ #ifndef VULKANMANAGER_H #define VULKANMANAGER_H -#include <functional> -#include <mutex> - -#include "vulkan/vulkan_core.h" #if !defined(VK_USE_PLATFORM_ANDROID_KHR) #define VK_USE_PLATFORM_ANDROID_KHR #endif @@ -186,25 +182,8 @@ private: VkDevice mDevice = VK_NULL_HANDLE; uint32_t mGraphicsQueueIndex; - - std::mutex mGraphicsQueueMutex; VkQueue mGraphicsQueue = VK_NULL_HANDLE; - - static VKAPI_ATTR VkResult interceptedVkQueueSubmit(VkQueue queue, uint32_t submitCount, - const VkSubmitInfo* pSubmits, - VkFence fence) { - sp<VulkanManager> manager = VulkanManager::getInstance(); - std::lock_guard<std::mutex> lock(manager->mGraphicsQueueMutex); - return manager->mQueueSubmit(queue, submitCount, pSubmits, fence); - } - - static VKAPI_ATTR VkResult interceptedVkQueueWaitIdle(VkQueue queue) { - sp<VulkanManager> manager = VulkanManager::getInstance(); - std::lock_guard<std::mutex> lock(manager->mGraphicsQueueMutex); - return manager->mQueueWaitIdle(queue); - } - - static GrVkGetProc sSkiaGetProp; + VkQueue mAHBUploadQueue = VK_NULL_HANDLE; // Variables saved to populate VkFunctorInitParams. static const uint32_t mAPIVersion = VK_MAKE_VERSION(1, 1, 0); diff --git a/libs/hwui/tests/common/TestUtils.h b/libs/hwui/tests/common/TestUtils.h index 9d5c13e5cd75..81ecfe59d3bc 100644 --- a/libs/hwui/tests/common/TestUtils.h +++ b/libs/hwui/tests/common/TestUtils.h @@ -61,12 +61,12 @@ namespace uirenderer { ADD_FAILURE() << "ClipState not a rect"; \ } -#define INNER_PIPELINE_TEST(test_case_name, test_name, pipeline, functionCall) \ - TEST(test_case_name, test_name##_##pipeline) { \ - RenderPipelineType oldType = Properties::getRenderPipelineType(); \ - Properties::overrideRenderPipelineType(RenderPipelineType::pipeline, true); \ - functionCall; \ - Properties::overrideRenderPipelineType(oldType, true); \ +#define INNER_PIPELINE_TEST(test_case_name, test_name, pipeline, functionCall) \ + TEST(test_case_name, test_name##_##pipeline) { \ + RenderPipelineType oldType = Properties::getRenderPipelineType(); \ + Properties::overrideRenderPipelineType(RenderPipelineType::pipeline); \ + functionCall; \ + Properties::overrideRenderPipelineType(oldType); \ }; #define INNER_PIPELINE_RENDERTHREAD_TEST(test_case_name, test_name, pipeline) \ @@ -78,27 +78,29 @@ namespace uirenderer { * Like gtest's TEST, but runs on the RenderThread, and 'renderThread' is passed, in top level scope * (for e.g. accessing its RenderState) */ -#define RENDERTHREAD_TEST(test_case_name, test_name) \ - class test_case_name##_##test_name##_RenderThreadTest { \ - public: \ - static void doTheThing(renderthread::RenderThread& renderThread); \ - }; \ - INNER_PIPELINE_RENDERTHREAD_TEST(test_case_name, test_name, SkiaGL); \ - INNER_PIPELINE_RENDERTHREAD_TEST(test_case_name, test_name, SkiaVulkan); \ - void test_case_name##_##test_name##_RenderThreadTest::doTheThing( \ +#define RENDERTHREAD_TEST(test_case_name, test_name) \ + class test_case_name##_##test_name##_RenderThreadTest { \ + public: \ + static void doTheThing(renderthread::RenderThread& renderThread); \ + }; \ + INNER_PIPELINE_RENDERTHREAD_TEST(test_case_name, test_name, SkiaGL); \ + /* Temporarily disabling Vulkan until we can figure out a way to stub out the driver */ \ + /* INNER_PIPELINE_RENDERTHREAD_TEST(test_case_name, test_name, SkiaVulkan); */ \ + void test_case_name##_##test_name##_RenderThreadTest::doTheThing( \ renderthread::RenderThread& renderThread) /** * Like RENDERTHREAD_TEST, but only runs with the Skia RenderPipelineTypes */ -#define RENDERTHREAD_SKIA_PIPELINE_TEST(test_case_name, test_name) \ - class test_case_name##_##test_name##_RenderThreadTest { \ - public: \ - static void doTheThing(renderthread::RenderThread& renderThread); \ - }; \ - INNER_PIPELINE_RENDERTHREAD_TEST(test_case_name, test_name, SkiaGL); \ - INNER_PIPELINE_RENDERTHREAD_TEST(test_case_name, test_name, SkiaVulkan); \ - void test_case_name##_##test_name##_RenderThreadTest::doTheThing( \ +#define RENDERTHREAD_SKIA_PIPELINE_TEST(test_case_name, test_name) \ + class test_case_name##_##test_name##_RenderThreadTest { \ + public: \ + static void doTheThing(renderthread::RenderThread& renderThread); \ + }; \ + INNER_PIPELINE_RENDERTHREAD_TEST(test_case_name, test_name, SkiaGL); \ + /* Temporarily disabling Vulkan until we can figure out a way to stub out the driver */ \ + /* INNER_PIPELINE_RENDERTHREAD_TEST(test_case_name, test_name, SkiaVulkan); */ \ + void test_case_name##_##test_name##_RenderThreadTest::doTheThing( \ renderthread::RenderThread& renderThread) /** diff --git a/media/java/android/media/MediaRoute2ProviderService.java b/media/java/android/media/MediaRoute2ProviderService.java index 11cb2be6660b..1e3c15419cff 100644 --- a/media/java/android/media/MediaRoute2ProviderService.java +++ b/media/java/android/media/MediaRoute2ProviderService.java @@ -81,6 +81,18 @@ public abstract class MediaRoute2ProviderService extends Service { public static final String SERVICE_INTERFACE = "android.media.MediaRoute2ProviderService"; /** + * A category indicating that the associated provider is only intended for use within the app + * that hosts the provider. + * + * <p>Declaring this category helps the system save resources by avoiding the launch of services + * whose routes are known to be private to the app that provides them. + * + * @hide + */ + public static final String CATEGORY_SELF_SCAN_ONLY = + "android.media.MediaRoute2ProviderService.SELF_SCAN_ONLY"; + + /** * The request ID to pass {@link #notifySessionCreated(long, RoutingSessionInfo)} * when {@link MediaRoute2ProviderService} created a session although there was no creation * request. diff --git a/media/java/android/media/RouteListingPreference.java b/media/java/android/media/RouteListingPreference.java index ee1f20308922..3935de837f51 100644 --- a/media/java/android/media/RouteListingPreference.java +++ b/media/java/android/media/RouteListingPreference.java @@ -440,14 +440,14 @@ public final class RouteListingPreference implements Parcelable { * <p>If this method returns {@link #SUBTEXT_CUSTOM}, then the subtext is obtained form * {@link #getCustomSubtextMessage()}. * - * @see #SUBTEXT_NONE, - * @see #SUBTEXT_ERROR_UNKNOWN, - * @see #SUBTEXT_SUBSCRIPTION_REQUIRED, - * @see #SUBTEXT_DOWNLOADED_CONTENT_ROUTING_DISALLOWED, - * @see #SUBTEXT_AD_ROUTING_DISALLOWED, - * @see #SUBTEXT_DEVICE_LOW_POWER, - * @see #SUBTEXT_UNAUTHORIZED , - * @see #SUBTEXT_TRACK_UNSUPPORTED, + * @see #SUBTEXT_NONE + * @see #SUBTEXT_ERROR_UNKNOWN + * @see #SUBTEXT_SUBSCRIPTION_REQUIRED + * @see #SUBTEXT_DOWNLOADED_CONTENT_ROUTING_DISALLOWED + * @see #SUBTEXT_AD_ROUTING_DISALLOWED + * @see #SUBTEXT_DEVICE_LOW_POWER + * @see #SUBTEXT_UNAUTHORIZED + * @see #SUBTEXT_TRACK_UNSUPPORTED * @see #SUBTEXT_CUSTOM */ @SubText diff --git a/media/java/android/media/tv/TvInputService.java b/media/java/android/media/tv/TvInputService.java index 85b02ad54718..720d9a6291de 100644 --- a/media/java/android/media/tv/TvInputService.java +++ b/media/java/android/media/tv/TvInputService.java @@ -63,6 +63,7 @@ import android.widget.FrameLayout; import com.android.internal.os.SomeArgs; import com.android.internal.util.Preconditions; +import java.io.IOException; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.util.ArrayList; @@ -1009,6 +1010,13 @@ public abstract class TvInputService extends Service { * @param buffer the {@link AdBuffer} that was consumed. */ public void notifyAdBufferConsumed(@NonNull AdBuffer buffer) { + AdBuffer dupBuffer; + try { + dupBuffer = AdBuffer.dupAdBuffer(buffer); + } catch (IOException e) { + Log.w(TAG, "dup AdBuffer error in notifyAdBufferConsumed:", e); + return; + } executeOrPostRunnableOnMainThread(new Runnable() { @MainThread @Override @@ -1016,10 +1024,14 @@ public abstract class TvInputService extends Service { try { if (DEBUG) Log.d(TAG, "notifyAdBufferConsumed"); if (mSessionCallback != null) { - mSessionCallback.onAdBufferConsumed(buffer); + mSessionCallback.onAdBufferConsumed(dupBuffer); } } catch (RemoteException e) { Log.w(TAG, "error in notifyAdBufferConsumed", e); + } finally { + if (dupBuffer != null) { + dupBuffer.getSharedMemory().close(); + } } } }); diff --git a/media/java/android/media/tv/interactive/TvInteractiveAppService.java b/media/java/android/media/tv/interactive/TvInteractiveAppService.java index ec85cc7af499..241940486a14 100755 --- a/media/java/android/media/tv/interactive/TvInteractiveAppService.java +++ b/media/java/android/media/tv/interactive/TvInteractiveAppService.java @@ -1964,6 +1964,13 @@ public abstract class TvInteractiveAppService extends Service { */ @CallSuper public void notifyAdBufferReady(@NonNull AdBuffer buffer) { + AdBuffer dupBuffer; + try { + dupBuffer = AdBuffer.dupAdBuffer(buffer); + } catch (IOException e) { + Log.w(TAG, "dup AdBuffer error in notifyAdBufferReady:", e); + return; + } executeOrPostRunnableOnMainThread(new Runnable() { @MainThread @Override @@ -1974,10 +1981,14 @@ public abstract class TvInteractiveAppService extends Service { "notifyAdBufferReady(buffer=" + buffer + ")"); } if (mSessionCallback != null) { - mSessionCallback.onAdBufferReady(AdBuffer.dupAdBuffer(buffer)); + mSessionCallback.onAdBufferReady(dupBuffer); } - } catch (RemoteException | IOException e) { + } catch (RemoteException e) { Log.w(TAG, "error in notifyAdBuffer", e); + } finally { + if (dupBuffer != null) { + dupBuffer.getSharedMemory().close(); + } } } }); diff --git a/media/jni/android_media_ImageReader.cpp b/media/jni/android_media_ImageReader.cpp index ca1bb3e97815..da2e56f5b6fa 100644 --- a/media/jni/android_media_ImageReader.cpp +++ b/media/jni/android_media_ImageReader.cpp @@ -768,6 +768,7 @@ static void ImageReader_unlockGraphicBuffer(JNIEnv* env, jobject /*thiz*/, android_graphics_GraphicBuffer_getNativeGraphicsBuffer(env, buffer); if (graphicBuffer.get() == NULL) { jniThrowRuntimeException(env, "Invalid graphic buffer!"); + return; } status_t res = graphicBuffer->unlock(); diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/theme/SettingsTypography.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/theme/SettingsTypography.kt index 5eaa4956b2e8..460bf9993b41 100644 --- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/theme/SettingsTypography.kt +++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/theme/SettingsTypography.kt @@ -21,6 +21,7 @@ import androidx.compose.runtime.Composable import androidx.compose.runtime.remember import androidx.compose.ui.text.TextStyle import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.text.style.Hyphens import androidx.compose.ui.unit.em import androidx.compose.ui.unit.sp @@ -34,42 +35,48 @@ private class SettingsTypography(settingsFontFamily: SettingsFontFamily) { fontWeight = FontWeight.Normal, fontSize = 57.sp, lineHeight = 64.sp, - letterSpacing = (-0.2).sp + letterSpacing = (-0.2).sp, + hyphens = Hyphens.Auto, ), displayMedium = TextStyle( fontFamily = brand, fontWeight = FontWeight.Normal, fontSize = 45.sp, lineHeight = 52.sp, - letterSpacing = 0.0.sp + letterSpacing = 0.0.sp, + hyphens = Hyphens.Auto, ), displaySmall = TextStyle( fontFamily = brand, fontWeight = FontWeight.Normal, fontSize = 36.sp, lineHeight = 44.sp, - letterSpacing = 0.0.sp + letterSpacing = 0.0.sp, + hyphens = Hyphens.Auto, ), headlineLarge = TextStyle( fontFamily = brand, fontWeight = FontWeight.Normal, fontSize = 32.sp, lineHeight = 40.sp, - letterSpacing = 0.0.sp + letterSpacing = 0.0.sp, + hyphens = Hyphens.Auto, ), headlineMedium = TextStyle( fontFamily = brand, fontWeight = FontWeight.Normal, fontSize = 28.sp, lineHeight = 36.sp, - letterSpacing = 0.0.sp + letterSpacing = 0.0.sp, + hyphens = Hyphens.Auto, ), headlineSmall = TextStyle( fontFamily = brand, fontWeight = FontWeight.Normal, fontSize = 24.sp, lineHeight = 32.sp, - letterSpacing = 0.0.sp + letterSpacing = 0.0.sp, + hyphens = Hyphens.Auto, ), titleLarge = TextStyle( fontFamily = brand, @@ -77,6 +84,7 @@ private class SettingsTypography(settingsFontFamily: SettingsFontFamily) { fontSize = 22.sp, lineHeight = 28.sp, letterSpacing = 0.02.em, + hyphens = Hyphens.Auto, ), titleMedium = TextStyle( fontFamily = brand, @@ -84,6 +92,7 @@ private class SettingsTypography(settingsFontFamily: SettingsFontFamily) { fontSize = 20.sp, lineHeight = 24.sp, letterSpacing = 0.02.em, + hyphens = Hyphens.Auto, ), titleSmall = TextStyle( fontFamily = brand, @@ -91,6 +100,7 @@ private class SettingsTypography(settingsFontFamily: SettingsFontFamily) { fontSize = 18.sp, lineHeight = 20.sp, letterSpacing = 0.02.em, + hyphens = Hyphens.Auto, ), bodyLarge = TextStyle( fontFamily = plain, @@ -98,6 +108,7 @@ private class SettingsTypography(settingsFontFamily: SettingsFontFamily) { fontSize = 16.sp, lineHeight = 24.sp, letterSpacing = 0.01.em, + hyphens = Hyphens.Auto, ), bodyMedium = TextStyle( fontFamily = plain, @@ -105,6 +116,7 @@ private class SettingsTypography(settingsFontFamily: SettingsFontFamily) { fontSize = 14.sp, lineHeight = 20.sp, letterSpacing = 0.01.em, + hyphens = Hyphens.Auto, ), bodySmall = TextStyle( fontFamily = plain, @@ -112,6 +124,7 @@ private class SettingsTypography(settingsFontFamily: SettingsFontFamily) { fontSize = 12.sp, lineHeight = 16.sp, letterSpacing = 0.01.em, + hyphens = Hyphens.Auto, ), labelLarge = TextStyle( fontFamily = plain, @@ -119,6 +132,7 @@ private class SettingsTypography(settingsFontFamily: SettingsFontFamily) { fontSize = 16.sp, lineHeight = 24.sp, letterSpacing = 0.01.em, + hyphens = Hyphens.Auto, ), labelMedium = TextStyle( fontFamily = plain, @@ -126,6 +140,7 @@ private class SettingsTypography(settingsFontFamily: SettingsFontFamily) { fontSize = 14.sp, lineHeight = 20.sp, letterSpacing = 0.01.em, + hyphens = Hyphens.Auto, ), labelSmall = TextStyle( fontFamily = plain, @@ -133,6 +148,7 @@ private class SettingsTypography(settingsFontFamily: SettingsFontFamily) { fontSize = 12.sp, lineHeight = 16.sp, letterSpacing = 0.01.em, + hyphens = Hyphens.Auto, ), ) } diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/ui/Text.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/ui/Text.kt index 5f2344ed97c0..01ba8f82850d 100644 --- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/ui/Text.kt +++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/ui/Text.kt @@ -19,6 +19,7 @@ package com.android.settingslib.spa.widget.ui import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.width import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Text @@ -29,6 +30,7 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp +import com.android.settingslib.spa.framework.theme.SettingsDimension import com.android.settingslib.spa.framework.theme.SettingsTheme import com.android.settingslib.spa.framework.theme.toMediumWeight @@ -78,7 +80,7 @@ fun SettingsBody( @Composable fun PlaceholderTitle(title: String) { Box( - modifier = Modifier.fillMaxSize(), + modifier = Modifier.fillMaxSize().padding(SettingsDimension.itemPadding), contentAlignment = Alignment.Center, ) { Text( diff --git a/packages/SettingsLib/TEST_MAPPING b/packages/SettingsLib/TEST_MAPPING new file mode 100644 index 000000000000..f6ada4c1ab7f --- /dev/null +++ b/packages/SettingsLib/TEST_MAPPING @@ -0,0 +1,13 @@ +{ + "presubmit": [ + { + "name": "SettingsLibUnitTests" + }, + { + "name": "SpaPrivilegedLibTests" + }, + { + "name": "SettingsSpaUnitTests" + } + ] +} diff --git a/packages/SettingsLib/res/drawable/ic_docked_tablet.xml b/packages/SettingsLib/res/drawable/ic_docked_tablet.xml new file mode 100644 index 000000000000..96a4900f361f --- /dev/null +++ b/packages/SettingsLib/res/drawable/ic_docked_tablet.xml @@ -0,0 +1,26 @@ +<!-- + ~ 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. + --> + +<vector xmlns:android="http://schemas.android.com/apk/res/android" + android:width="24dp" + android:height="24dp" + android:viewportWidth="960" + android:viewportHeight="960" + android:tint="?attr/colorControlNormal"> + <path + android:fillColor="#000000" + android:pathData="M480,280Q497,280 508.5,268.5Q520,257 520,240Q520,223 508.5,211.5Q497,200 480,200Q463,200 451.5,211.5Q440,223 440,240Q440,257 451.5,268.5Q463,280 480,280ZM120,720Q87,720 63.5,696.5Q40,673 40,640L60,160Q60,127 83.5,103.5Q107,80 140,80L820,80Q853,80 876.5,103.5Q900,127 900,160L920,640Q920,673 896.5,696.5Q873,720 840,720L120,720ZM120,640L840,640Q840,640 840,640Q840,640 840,640L820,160Q820,160 820,160Q820,160 820,160L140,160Q140,160 140,160Q140,160 140,160L120,640Q120,640 120,640Q120,640 120,640ZM320,880Q259,880 209.5,850Q160,820 160,765L160,720L240,720L240,760Q253,780 274.5,790Q296,800 320,800L640,800Q664,800 685.5,790.5Q707,781 720,761L720,720L800,720L800,765Q800,820 750.5,850Q701,880 640,880L320,880ZM480,400Q480,400 480,400Q480,400 480,400L480,400Q480,400 480,400Q480,400 480,400L480,400Q480,400 480,400Q480,400 480,400L480,400Q480,400 480,400Q480,400 480,400L480,400Z"/> +</vector>
\ No newline at end of file diff --git a/packages/SettingsLib/res/values/strings.xml b/packages/SettingsLib/res/values/strings.xml index f581091f0f3e..dac7f8d15388 100644 --- a/packages/SettingsLib/res/values/strings.xml +++ b/packages/SettingsLib/res/values/strings.xml @@ -1327,6 +1327,12 @@ <string name="media_transfer_this_device_name" product="default">This phone</string> <!-- Name of the tablet device. [CHAR LIMIT=30] --> <string name="media_transfer_this_device_name" product="tablet">This tablet</string> + <!-- Name of the dock device. [CHAR LIMIT=30] --> + <string name="media_transfer_dock_speaker_device_name">Dock speaker</string> + <!-- Default name of the external device. [CHAR LIMIT=30] --> + <string name="media_transfer_external_device_name">External Device</string> + <!-- Default name of the connected device. [CHAR LIMIT=30] --> + <string name="media_transfer_default_device_name">Connected device</string> <!-- Name of the phone device with an active remote session. [CHAR LIMIT=30] --> <string name="media_transfer_this_phone">This phone</string> <!-- Sub status indicates device is not available due to an unknown error. [CHAR LIMIT=NONE] --> diff --git a/packages/SettingsLib/res/values/styles.xml b/packages/SettingsLib/res/values/styles.xml index 2584be725747..4ac4aae8f2ab 100644 --- a/packages/SettingsLib/res/values/styles.xml +++ b/packages/SettingsLib/res/values/styles.xml @@ -17,6 +17,7 @@ <resources xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"> <style name="TextAppearanceSmall"> <item name="android:textAppearance">?android:attr/textAppearanceSmall</item> + <item name="android:textColor">?android:attr/textColorSecondary</item> </style> <style name="TextAppearanceMedium"> <item name="android:textAppearance">?android:attr/textAppearanceMedium</item> @@ -91,7 +92,7 @@ <item name="android:ellipsize">end</item> </style> - <style name="DialogButtonPositive" xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"> + <style name="DialogButtonPositive"> <item name="android:buttonCornerRadius">0dp</item> <item name="android:background">@drawable/dialog_btn_filled</item> <item name="android:textColor">?androidprv:attr/textColorOnAccent</item> @@ -105,7 +106,6 @@ <style name="DialogButtonNegative"> <item name="android:buttonCornerRadius">28dp</item> <item name="android:background">@drawable/dialog_btn_outline</item> - <item name="android:buttonCornerRadius">28dp</item> <item name="android:textColor">?android:attr/textColorPrimary</item> <item name="android:textSize">14sp</item> <item name="android:lineHeight">20sp</item> diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothBroadcastUtils.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothBroadcastUtils.java index a80061efb19a..1ff2befd19ed 100644 --- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothBroadcastUtils.java +++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothBroadcastUtils.java @@ -44,38 +44,4 @@ public final class BluetoothBroadcastUtils { * Bluetooth scheme. */ public static final String SCHEME_BT_BROADCAST_METADATA = "BT:"; - - // BluetoothLeBroadcastMetadata - static final String PREFIX_BT_ADDRESS_TYPE = "T:"; - static final String PREFIX_BT_DEVICE = "D:"; - static final String PREFIX_BT_ADVERTISING_SID = "AS:"; - static final String PREFIX_BT_BROADCAST_ID = "B:"; - static final String PREFIX_BT_SYNC_INTERVAL = "SI:"; - static final String PREFIX_BT_IS_ENCRYPTED = "E:"; - static final String PREFIX_BT_BROADCAST_CODE = "C:"; - static final String PREFIX_BT_PRESENTATION_DELAY = "PD:"; - static final String PREFIX_BT_SUBGROUPS = "SG:"; - static final String PREFIX_BT_ANDROID_VERSION = "V:"; - - // BluetoothLeBroadcastSubgroup - static final String PREFIX_BTSG_CODEC_ID = "CID:"; - static final String PREFIX_BTSG_CODEC_CONFIG = "CC:"; - static final String PREFIX_BTSG_AUDIO_CONTENT = "AC:"; - static final String PREFIX_BTSG_CHANNEL_PREF = "CP:"; - static final String PREFIX_BTSG_BROADCAST_CHANNEL = "BC:"; - - // BluetoothLeAudioCodecConfigMetadata - static final String PREFIX_BTCC_AUDIO_LOCATION = "AL:"; - static final String PREFIX_BTCC_RAW_METADATA = "CCRM:"; - - // BluetoothLeAudioContentMetadata - static final String PREFIX_BTAC_PROGRAM_INFO = "PI:"; - static final String PREFIX_BTAC_LANGUAGE = "L:"; - static final String PREFIX_BTAC_RAW_METADATA = "ACRM:"; - - // BluetoothLeBroadcastChannel - static final String PREFIX_BTBC_CHANNEL_INDEX = "CI:"; - static final String PREFIX_BTBC_CODEC_CONFIG = "BCCM:"; - - static final String DELIMITER_QR_CODE = ";"; } diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothLeBroadcastMetadataExt.kt b/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothLeBroadcastMetadataExt.kt new file mode 100644 index 000000000000..9bb11f8da645 --- /dev/null +++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothLeBroadcastMetadataExt.kt @@ -0,0 +1,362 @@ +/* + * 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.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.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 = "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:". + */ + 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]. + * + * QR code string should prefix with "BT:BluetoothLeBroadcastMetadata:". + */ + fun convertToBroadcastMetadata(qrCodeString: String): BluetoothLeBroadcastMetadata? { + if (!qrCodeString.startsWith(SCHEME_BT_BROADCAST_METADATA)) { + Log.e(TAG, "String \"$qrCodeString\" does not begin with " + + "\"$SCHEME_BT_BROADCAST_METADATA\"") + return null + } + return try { + 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 parse: $qrCodeString", e) + null + } + } + + 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 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/src/com/android/settingslib/bluetooth/LocalBluetoothLeBroadcastMetadata.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothLeBroadcastMetadata.java deleted file mode 100644 index 0630a2e515e0..000000000000 --- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothLeBroadcastMetadata.java +++ /dev/null @@ -1,454 +0,0 @@ -/* - * Copyright (C) 2022 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.settingslib.bluetooth; - -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.bluetooth.BluetoothLeBroadcastSubgroup; -import android.util.Log; - -import java.nio.charset.StandardCharsets; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.regex.Matcher; -import java.util.regex.Pattern; - -public class LocalBluetoothLeBroadcastMetadata { - private static final boolean DEBUG = BluetoothUtils.D; - private static final String TAG = "LocalBluetoothLeBroadcastMetadata"; - private static final String METADATA_START = "<"; - private static final String METADATA_END = ">"; - private static final String PATTERN_REGEX = "<(.*?)>"; - private static final String PATTERN_BT_BROADCAST_METADATA = - "T:<(.*?)>;+D:<(.*?)>;+AS:<(.*?)>;+B:<(.*?)>;+SI:<(.*?)>;+E:<(.*?)>;+C:<(.*?)>;" - + "+PD:<(.*?)>;+SG:(.*)"; - private static final String PATTERN_BT_SUBGROUP = - "CID:<(.*?)>;+CC:<(.*?);>;+AC:<(.*?);>;+CP:<(.*?)>;+BC:<(.*)>;>;"; - private static final String PATTERN_BT_CHANNEL = "CI:<(.*?)>;+BCCM:<(.*?);>;"; - - /* Index for BluetoothLeBroadcastMetadata */ - private static int MATCH_INDEX_ADDRESS_TYPE = 1; - private static int MATCH_INDEX_DEVICE = 2; - private static int MATCH_INDEX_ADVERTISING_SID = 3; - private static int MATCH_INDEX_BROADCAST_ID = 4; - private static int MATCH_INDEX_SYNC_INTERVAL = 5; - private static int MATCH_INDEX_IS_ENCRYPTED = 6; - private static int MATCH_INDEX_BROADCAST_CODE = 7; - private static int MATCH_INDEX_PRESENTATION_DELAY = 8; - private static int MATCH_INDEX_SUBGROUPS = 9; - - /* Index for BluetoothLeBroadcastSubgroup */ - private static int MATCH_INDEX_CODEC_ID = 1; - private static int MATCH_INDEX_CODEC_CONFIG = 2; - private static int MATCH_INDEX_AUDIO_CONTENT = 3; - private static int MATCH_INDEX_CHANNEL_PREF = 4; - private static int MATCH_INDEX_BROADCAST_CHANNEL = 5; - - /* Index for BluetoothLeAudioCodecConfigMetadata */ - private static int LIST_INDEX_AUDIO_LOCATION = 0; - private static int LIST_INDEX_CODEC_CONFIG_RAW_METADATA = 1; - - /* Index for BluetoothLeAudioContentMetadata */ - private static int LIST_INDEX_PROGRAM_INFO = 0; - private static int LIST_INDEX_LANGUAGE = 1; - private static int LIST_INDEX_AUDIO_CONTENT_RAW_METADATA = 2; - - /* Index for BluetoothLeBroadcastChannel */ - private static int MATCH_INDEX_CHANNEL_INDEX = 1; - private static int MATCH_INDEX_CHANNEL_CODEC_CONFIG = 2; - - private BluetoothLeBroadcastSubgroup mSubgroup; - private List<BluetoothLeBroadcastSubgroup> mSubgroupList; - - // BluetoothLeBroadcastMetadata - // Optional: Identity address type - private int mSourceAddressType; - // Optional: Must use identity address - private BluetoothDevice mSourceDevice; - private int mSourceAdvertisingSid; - private int mBroadcastId; - private int mPaSyncInterval; - private int mPresentationDelayMicros; - private boolean mIsEncrypted; - private byte[] mBroadcastCode; - - // BluetoothLeBroadcastSubgroup - private int mCodecId; - private BluetoothLeAudioContentMetadata mContentMetadata; - private BluetoothLeAudioCodecConfigMetadata mConfigMetadata; - private Boolean mNoChannelPreference; - private List<BluetoothLeBroadcastChannel> mChannel; - - // BluetoothLeAudioCodecConfigMetadata - private long mAudioLocation; - private byte[] mCodecConfigMetadata; - - // BluetoothLeAudioContentMetadata - private String mLanguage; - private String mProgramInfo; - private byte[] mAudioContentMetadata; - - // BluetoothLeBroadcastChannel - private boolean mIsSelected; - private int mChannelIndex; - - - LocalBluetoothLeBroadcastMetadata(BluetoothLeBroadcastMetadata metadata) { - mSourceAddressType = metadata.getSourceAddressType(); - mSourceDevice = metadata.getSourceDevice(); - mSourceAdvertisingSid = metadata.getSourceAdvertisingSid(); - mBroadcastId = metadata.getBroadcastId(); - mPaSyncInterval = metadata.getPaSyncInterval(); - mIsEncrypted = metadata.isEncrypted(); - mBroadcastCode = metadata.getBroadcastCode(); - mPresentationDelayMicros = metadata.getPresentationDelayMicros(); - mSubgroupList = metadata.getSubgroups(); - } - - public LocalBluetoothLeBroadcastMetadata() { - } - - public void setBroadcastCode(byte[] code) { - mBroadcastCode = code; - } - - public int getBroadcastId() { - return mBroadcastId; - } - - public String convertToQrCodeString() { - String subgroupString = convertSubgroupToString(mSubgroupList); - return new StringBuilder() - .append(BluetoothBroadcastUtils.SCHEME_BT_BROADCAST_METADATA) - .append(BluetoothBroadcastUtils.PREFIX_BT_ADDRESS_TYPE) - .append(METADATA_START).append(mSourceAddressType).append(METADATA_END) - .append(BluetoothBroadcastUtils.DELIMITER_QR_CODE) - .append(BluetoothBroadcastUtils.PREFIX_BT_DEVICE) - .append(METADATA_START).append(mSourceDevice).append(METADATA_END) - .append(BluetoothBroadcastUtils.DELIMITER_QR_CODE) - .append(BluetoothBroadcastUtils.PREFIX_BT_ADVERTISING_SID) - .append(METADATA_START).append(mSourceAdvertisingSid).append(METADATA_END) - .append(BluetoothBroadcastUtils.DELIMITER_QR_CODE) - .append(BluetoothBroadcastUtils.PREFIX_BT_BROADCAST_ID) - .append(METADATA_START).append(mBroadcastId).append(METADATA_END) - .append(BluetoothBroadcastUtils.DELIMITER_QR_CODE) - .append(BluetoothBroadcastUtils.PREFIX_BT_SYNC_INTERVAL) - .append(METADATA_START).append(mPaSyncInterval).append(METADATA_END) - .append(BluetoothBroadcastUtils.DELIMITER_QR_CODE) - .append(BluetoothBroadcastUtils.PREFIX_BT_IS_ENCRYPTED) - .append(METADATA_START).append(mIsEncrypted).append(METADATA_END) - .append(BluetoothBroadcastUtils.DELIMITER_QR_CODE) - .append(BluetoothBroadcastUtils.PREFIX_BT_BROADCAST_CODE) - .append(METADATA_START).append(Arrays.toString(mBroadcastCode)).append(METADATA_END) - .append(BluetoothBroadcastUtils.DELIMITER_QR_CODE) - .append(BluetoothBroadcastUtils.PREFIX_BT_PRESENTATION_DELAY) - .append(METADATA_START).append(mPresentationDelayMicros).append(METADATA_END) - .append(BluetoothBroadcastUtils.DELIMITER_QR_CODE) - .append(BluetoothBroadcastUtils.PREFIX_BT_SUBGROUPS) - .append(METADATA_START).append(subgroupString).append(METADATA_END) - .append(BluetoothBroadcastUtils.DELIMITER_QR_CODE) - .toString(); - } - - private String convertSubgroupToString(List<BluetoothLeBroadcastSubgroup> subgroupList) { - StringBuilder subgroupListBuilder = new StringBuilder(); - String subgroupString = ""; - for (BluetoothLeBroadcastSubgroup subgroup: subgroupList) { - String audioCodec = convertAudioCodecConfigToString(subgroup.getCodecSpecificConfig()); - String audioContent = convertAudioContentToString(subgroup.getContentMetadata()); - boolean hasChannelPreference = subgroup.hasChannelPreference(); - String channels = convertChannelToString(subgroup.getChannels()); - subgroupString = new StringBuilder() - .append(BluetoothBroadcastUtils.PREFIX_BTSG_CODEC_ID) - .append(METADATA_START).append(subgroup.getCodecId()).append(METADATA_END) - .append(BluetoothBroadcastUtils.DELIMITER_QR_CODE) - .append(BluetoothBroadcastUtils.PREFIX_BTSG_CODEC_CONFIG) - .append(METADATA_START).append(audioCodec).append(METADATA_END) - .append(BluetoothBroadcastUtils.DELIMITER_QR_CODE) - .append(BluetoothBroadcastUtils.PREFIX_BTSG_AUDIO_CONTENT) - .append(METADATA_START).append(audioContent).append(METADATA_END) - .append(BluetoothBroadcastUtils.DELIMITER_QR_CODE) - .append(BluetoothBroadcastUtils.PREFIX_BTSG_CHANNEL_PREF) - .append(METADATA_START).append(hasChannelPreference).append(METADATA_END) - .append(BluetoothBroadcastUtils.DELIMITER_QR_CODE) - .append(BluetoothBroadcastUtils.PREFIX_BTSG_BROADCAST_CHANNEL) - .append(METADATA_START).append(channels).append(METADATA_END) - .append(BluetoothBroadcastUtils.DELIMITER_QR_CODE) - .toString(); - subgroupListBuilder.append(subgroupString); - } - return subgroupListBuilder.toString(); - } - - private String convertAudioCodecConfigToString(BluetoothLeAudioCodecConfigMetadata config) { - String audioLocation = String.valueOf(config.getAudioLocation()); - String rawMetadata = new String(config.getRawMetadata(), StandardCharsets.UTF_8); - return new StringBuilder() - .append(BluetoothBroadcastUtils.PREFIX_BTCC_AUDIO_LOCATION) - .append(METADATA_START).append(audioLocation).append(METADATA_END) - .append(BluetoothBroadcastUtils.DELIMITER_QR_CODE) - .append(BluetoothBroadcastUtils.PREFIX_BTCC_RAW_METADATA) - .append(METADATA_START).append(rawMetadata).append(METADATA_END) - .append(BluetoothBroadcastUtils.DELIMITER_QR_CODE) - .toString(); - } - - private String convertAudioContentToString(BluetoothLeAudioContentMetadata audioContent) { - String rawMetadata = new String(audioContent.getRawMetadata(), StandardCharsets.UTF_8); - return new StringBuilder() - .append(BluetoothBroadcastUtils.PREFIX_BTAC_PROGRAM_INFO) - .append(METADATA_START).append(audioContent.getProgramInfo()).append(METADATA_END) - .append(BluetoothBroadcastUtils.DELIMITER_QR_CODE) - .append(BluetoothBroadcastUtils.PREFIX_BTAC_LANGUAGE) - .append(METADATA_START).append(audioContent.getLanguage()).append(METADATA_END) - .append(BluetoothBroadcastUtils.DELIMITER_QR_CODE) - .append(BluetoothBroadcastUtils.PREFIX_BTAC_RAW_METADATA) - .append(METADATA_START).append(rawMetadata).append(METADATA_END) - .append(BluetoothBroadcastUtils.DELIMITER_QR_CODE) - .toString(); - } - - private String convertChannelToString(List<BluetoothLeBroadcastChannel> channelList) { - StringBuilder channelListBuilder = new StringBuilder(); - String channelString = ""; - for (BluetoothLeBroadcastChannel channel: channelList) { - String channelAudioCodec = convertAudioCodecConfigToString(channel.getCodecMetadata()); - channelString = new StringBuilder() - .append(BluetoothBroadcastUtils.PREFIX_BTBC_CHANNEL_INDEX) - .append(METADATA_START).append(channel.getChannelIndex()).append(METADATA_END) - .append(BluetoothBroadcastUtils.DELIMITER_QR_CODE) - .append(BluetoothBroadcastUtils.PREFIX_BTBC_CODEC_CONFIG) - .append(METADATA_START).append(channelAudioCodec).append(METADATA_END) - .append(BluetoothBroadcastUtils.DELIMITER_QR_CODE) - .toString(); - channelListBuilder.append(channelString); - } - return channelListBuilder.toString(); - } - - /** - * Example : prefix is with the “BT:”, and end by the Android Version. - * BT:T:<1>;D:<00:11:22:AA:BB:CC>;AS:<1>;B:…;V:T;; - * - * @return BluetoothLeBroadcastMetadata - */ - public BluetoothLeBroadcastMetadata convertToBroadcastMetadata(String qrCodeString) { - if (DEBUG) { - Log.d(TAG, "Convert " + qrCodeString + "to BluetoothLeBroadcastMetadata"); - } - - Pattern pattern = Pattern.compile(PATTERN_BT_BROADCAST_METADATA); - Matcher match = pattern.matcher(qrCodeString); - if (match.find()) { - try { - mSourceAddressType = Integer.parseInt(match.group(MATCH_INDEX_ADDRESS_TYPE)); - mSourceDevice = BluetoothAdapter.getDefaultAdapter().getRemoteDevice( - match.group(MATCH_INDEX_DEVICE)); - mSourceAdvertisingSid = Integer.parseInt( - match.group(MATCH_INDEX_ADVERTISING_SID)); - mBroadcastId = Integer.parseInt(match.group(MATCH_INDEX_BROADCAST_ID)); - mPaSyncInterval = Integer.parseInt(match.group(MATCH_INDEX_SYNC_INTERVAL)); - mIsEncrypted = Boolean.valueOf(match.group(MATCH_INDEX_IS_ENCRYPTED)); - mBroadcastCode = match.group(MATCH_INDEX_BROADCAST_CODE).getBytes(); - mPresentationDelayMicros = - Integer.parseInt(match.group(MATCH_INDEX_PRESENTATION_DELAY)); - - if (DEBUG) { - Log.d(TAG, "Converted qrCodeString result: " - + " ,Type = " + mSourceAddressType - + " ,Device = " + mSourceDevice - + " ,AdSid = " + mSourceAdvertisingSid - + " ,BroadcastId = " + mBroadcastId - + " ,paSync = " + mPaSyncInterval - + " ,encrypted = " + mIsEncrypted - + " ,BroadcastCode = " + Arrays.toString(mBroadcastCode) - + " ,delay = " + mPresentationDelayMicros); - } - - mSubgroup = convertToSubgroup(match.group(MATCH_INDEX_SUBGROUPS)); - - return new BluetoothLeBroadcastMetadata.Builder() - .setSourceDevice(mSourceDevice, mSourceAddressType) - .setSourceAdvertisingSid(mSourceAdvertisingSid) - .setBroadcastId(mBroadcastId) - .setPaSyncInterval(mPaSyncInterval) - .setEncrypted(mIsEncrypted) - .setBroadcastCode(mBroadcastCode) - .setPresentationDelayMicros(mPresentationDelayMicros) - .addSubgroup(mSubgroup) - .build(); - } catch (IllegalArgumentException e) { - Log.d(TAG, "IllegalArgumentException when convert : " + e); - return null; - } - } else { - if (DEBUG) { - Log.d(TAG, "The match fail, can not convert it to BluetoothLeBroadcastMetadata."); - } - return null; - } - } - - private BluetoothLeBroadcastSubgroup convertToSubgroup(String subgroupString) { - if (DEBUG) { - Log.d(TAG, "Convert " + subgroupString + "to BluetoothLeBroadcastSubgroup"); - } - Pattern pattern = Pattern.compile(PATTERN_BT_SUBGROUP); - Matcher match = pattern.matcher(subgroupString); - if (match.find()) { - mCodecId = Integer.parseInt(match.group(MATCH_INDEX_CODEC_ID)); - mConfigMetadata = convertToConfigMetadata(match.group(MATCH_INDEX_CODEC_CONFIG)); - mContentMetadata = convertToContentMetadata(match.group(MATCH_INDEX_AUDIO_CONTENT)); - mNoChannelPreference = Boolean.valueOf(match.group(MATCH_INDEX_CHANNEL_PREF)); - mChannel = - convertToChannel(match.group(MATCH_INDEX_BROADCAST_CHANNEL), mConfigMetadata); - - BluetoothLeBroadcastSubgroup.Builder subgroupBuilder = - new BluetoothLeBroadcastSubgroup.Builder(); - subgroupBuilder.setCodecId(mCodecId); - subgroupBuilder.setCodecSpecificConfig(mConfigMetadata); - subgroupBuilder.setContentMetadata(mContentMetadata); - - for (BluetoothLeBroadcastChannel channel : mChannel) { - subgroupBuilder.addChannel(channel); - } - return subgroupBuilder.build(); - } else { - if (DEBUG) { - Log.d(TAG, - "The match fail, can not convert it to BluetoothLeBroadcastSubgroup."); - } - return null; - } - } - - private BluetoothLeAudioCodecConfigMetadata convertToConfigMetadata( - String configMetadataString) { - if (DEBUG) { - Log.d(TAG, - "Convert " + configMetadataString + "to BluetoothLeAudioCodecConfigMetadata"); - } - Pattern pattern = Pattern.compile(PATTERN_REGEX); - Matcher match = pattern.matcher(configMetadataString); - ArrayList<String> resultList = new ArrayList<>(); - while (match.find()) { - resultList.add(match.group(1)); - Log.d(TAG, "Codec Config match : " + match.group(1)); - } - if (DEBUG) { - Log.d(TAG, "Converted configMetadataString result: " + resultList.size()); - } - if (resultList.size() > 0) { - mAudioLocation = Long.parseLong(resultList.get(LIST_INDEX_AUDIO_LOCATION)); - mCodecConfigMetadata = resultList.get(LIST_INDEX_CODEC_CONFIG_RAW_METADATA).getBytes(); - return new BluetoothLeAudioCodecConfigMetadata.Builder() - .setAudioLocation(mAudioLocation) - .build(); - } else { - if (DEBUG) { - Log.d(TAG, - "The match fail, can not convert it to " - + "BluetoothLeAudioCodecConfigMetadata."); - } - return null; - } - } - - private BluetoothLeAudioContentMetadata convertToContentMetadata(String contentMetadataString) { - if (DEBUG) { - Log.d(TAG, "Convert " + contentMetadataString + "to BluetoothLeAudioContentMetadata"); - } - Pattern pattern = Pattern.compile(PATTERN_REGEX); - Matcher match = pattern.matcher(contentMetadataString); - ArrayList<String> resultList = new ArrayList<>(); - while (match.find()) { - Log.d(TAG, "Audio Content match : " + match.group(1)); - resultList.add(match.group(1)); - } - if (DEBUG) { - Log.d(TAG, "Converted contentMetadataString result: " + resultList.size()); - } - if (resultList.size() > 0) { - mProgramInfo = resultList.get(LIST_INDEX_PROGRAM_INFO); - mLanguage = resultList.get(LIST_INDEX_LANGUAGE); - mAudioContentMetadata = - resultList.get(LIST_INDEX_AUDIO_CONTENT_RAW_METADATA).getBytes(); - - /* TODO(b/265253566) : Need to set the default value for language when the user starts - * the broadcast. - */ - if (mLanguage.equals("null")) { - mLanguage = "eng"; - } - - return new BluetoothLeAudioContentMetadata.Builder() - .setProgramInfo(mProgramInfo) - .setLanguage(mLanguage) - .build(); - } else { - if (DEBUG) { - Log.d(TAG, - "The match fail, can not convert it to BluetoothLeAudioContentMetadata."); - } - return null; - } - } - - private List<BluetoothLeBroadcastChannel> convertToChannel(String channelString, - BluetoothLeAudioCodecConfigMetadata configMetadata) { - if (DEBUG) { - Log.d(TAG, "Convert " + channelString + "to BluetoothLeBroadcastChannel"); - } - Pattern pattern = Pattern.compile(PATTERN_BT_CHANNEL); - Matcher match = pattern.matcher(channelString); - Map<Integer, BluetoothLeAudioCodecConfigMetadata> channel = - new HashMap<Integer, BluetoothLeAudioCodecConfigMetadata>(); - while (match.find()) { - channel.put(Integer.parseInt(match.group(MATCH_INDEX_CHANNEL_INDEX)), - convertToConfigMetadata(match.group(MATCH_INDEX_CHANNEL_CODEC_CONFIG))); - } - - if (channel.size() > 0) { - mIsSelected = false; - ArrayList<BluetoothLeBroadcastChannel> broadcastChannelList = new ArrayList<>(); - for (Map.Entry<Integer, BluetoothLeAudioCodecConfigMetadata> entry : - channel.entrySet()) { - - broadcastChannelList.add( - new BluetoothLeBroadcastChannel.Builder() - .setSelected(mIsSelected) - .setChannelIndex(entry.getKey()) - .setCodecMetadata(entry.getValue()) - .build()); - } - return broadcastChannelList; - } else { - if (DEBUG) { - Log.d(TAG, - "The match fail, can not convert it to BluetoothLeBroadcastChannel."); - } - return null; - } - } -} diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothLeBroadcastMetadata.kt b/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothLeBroadcastMetadata.kt new file mode 100644 index 000000000000..870ea8d8b59c --- /dev/null +++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothLeBroadcastMetadata.kt @@ -0,0 +1,30 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.settingslib.bluetooth + +import android.bluetooth.BluetoothLeBroadcastMetadata +import com.android.settingslib.bluetooth.BluetoothLeBroadcastMetadataExt.toQrCodeString + +@Deprecated("Replace with BluetoothLeBroadcastMetadataExt") +class LocalBluetoothLeBroadcastMetadata(private val metadata: BluetoothLeBroadcastMetadata?) { + + constructor() : this(null) + + fun convertToQrCodeString(): String = metadata?.toQrCodeString() ?: "" + + fun convertToBroadcastMetadata(qrCodeString: String) = + BluetoothLeBroadcastMetadataExt.convertToBroadcastMetadata(qrCodeString) +}
\ No newline at end of file diff --git a/packages/SettingsLib/src/com/android/settingslib/fuelgauge/BatteryStatus.java b/packages/SettingsLib/src/com/android/settingslib/fuelgauge/BatteryStatus.java index 765566391ef2..1251b0dc306a 100644 --- a/packages/SettingsLib/src/com/android/settingslib/fuelgauge/BatteryStatus.java +++ b/packages/SettingsLib/src/com/android/settingslib/fuelgauge/BatteryStatus.java @@ -196,6 +196,9 @@ public class BatteryStatus { /** Gets the battery level from the intent. */ public static int getBatteryLevel(Intent batteryChangedIntent) { + if (batteryChangedIntent == null) { + return -1; /*invalid battery level*/ + } final int level = batteryChangedIntent.getIntExtra(BatteryManager.EXTRA_LEVEL, -1); final int scale = batteryChangedIntent.getIntExtra(BatteryManager.EXTRA_SCALE, 0); return scale == 0 diff --git a/packages/SettingsLib/src/com/android/settingslib/media/PhoneMediaDevice.java b/packages/SettingsLib/src/com/android/settingslib/media/PhoneMediaDevice.java index 1c82be9c801f..34519c993d27 100644 --- a/packages/SettingsLib/src/com/android/settingslib/media/PhoneMediaDevice.java +++ b/packages/SettingsLib/src/com/android/settingslib/media/PhoneMediaDevice.java @@ -70,13 +70,17 @@ public class PhoneMediaDevice extends MediaDevice { name = mContext.getString(R.string.media_transfer_wired_usb_device_name); break; case TYPE_DOCK: - case TYPE_HDMI: - name = mRouteInfo.getName(); + name = mContext.getString(R.string.media_transfer_dock_speaker_device_name); break; case TYPE_BUILTIN_SPEAKER: - default: name = mContext.getString(R.string.media_transfer_this_device_name); break; + case TYPE_HDMI: + name = mContext.getString(R.string.media_transfer_external_device_name); + break; + default: + name = mContext.getString(R.string.media_transfer_default_device_name); + break; } return name.toString(); } diff --git a/packages/SettingsLib/src/com/android/settingslib/mobile/dataservice/SubscriptionInfoDao.java b/packages/SettingsLib/src/com/android/settingslib/mobile/dataservice/SubscriptionInfoDao.java index e835125b9b81..e6b1cfb440fa 100644 --- a/packages/SettingsLib/src/com/android/settingslib/mobile/dataservice/SubscriptionInfoDao.java +++ b/packages/SettingsLib/src/com/android/settingslib/mobile/dataservice/SubscriptionInfoDao.java @@ -16,14 +16,13 @@ package com.android.settingslib.mobile.dataservice; -import java.util.List; - import androidx.lifecycle.LiveData; import androidx.room.Dao; import androidx.room.Insert; import androidx.room.OnConflictStrategy; import androidx.room.Query; -import androidx.room.Update; + +import java.util.List; @Dao public interface SubscriptionInfoDao { @@ -32,7 +31,9 @@ public interface SubscriptionInfoDao { void insertSubsInfo(SubscriptionInfoEntity... subscriptionInfo); @Query("SELECT * FROM " + DataServiceUtils.SubscriptionInfoData.TABLE_NAME + " ORDER BY " - + DataServiceUtils.SubscriptionInfoData.COLUMN_ID) + + " CASE WHEN " + DataServiceUtils.SubscriptionInfoData.COLUMN_SIM_SLOT_INDEX + + " >= 0 THEN 1 ELSE 2 END , " + + DataServiceUtils.SubscriptionInfoData.COLUMN_SIM_SLOT_INDEX) LiveData<List<SubscriptionInfoEntity>> queryAvailableSubInfos(); @Query("SELECT * FROM " + DataServiceUtils.SubscriptionInfoData.TABLE_NAME + " WHERE " @@ -43,7 +44,8 @@ public interface SubscriptionInfoDao { + DataServiceUtils.SubscriptionInfoData.COLUMN_IS_ACTIVE_SUBSCRIPTION_ID + " = :isActiveSubscription" + " AND " + DataServiceUtils.SubscriptionInfoData.COLUMN_IS_SUBSCRIPTION_VISIBLE - + " = :isSubscriptionVisible") + + " = :isSubscriptionVisible" + " ORDER BY " + + DataServiceUtils.SubscriptionInfoData.COLUMN_SIM_SLOT_INDEX) LiveData<List<SubscriptionInfoEntity>> queryActiveSubInfos( boolean isActiveSubscription, boolean isSubscriptionVisible); diff --git a/packages/SettingsLib/tests/unit/Android.bp b/packages/SettingsLib/tests/unit/Android.bp new file mode 100644 index 000000000000..a4558f1bd40b --- /dev/null +++ b/packages/SettingsLib/tests/unit/Android.bp @@ -0,0 +1,35 @@ +// +// Copyright (C) 2023 The Android Open Source Project +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +package { + default_applicable_licenses: ["frameworks_base_license"], +} + +android_test { + name: "SettingsLibUnitTests", + test_suites: ["device-tests"], + + srcs: [ + "src/**/*.kt", + ], + + static_libs: [ + "SettingsLib", + "androidx.test.ext.junit", + "androidx.test.runner", + "truth-prebuilt", + ], +} diff --git a/packages/SettingsLib/tests/unit/AndroidManifest.xml b/packages/SettingsLib/tests/unit/AndroidManifest.xml new file mode 100644 index 000000000000..568f9cb2b79e --- /dev/null +++ b/packages/SettingsLib/tests/unit/AndroidManifest.xml @@ -0,0 +1,29 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + 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. +--> + +<manifest xmlns:android="http://schemas.android.com/apk/res/android" + package="com.android.settingslib.test"> + <application> + <uses-library android:name="android.test.runner" /> + </application> + + <instrumentation + android:name="androidx.test.runner.AndroidJUnitRunner" + android:label="Tests for SettingsLib" + android:targetPackage="com.android.settingslib.test"> + </instrumentation> +</manifest> diff --git a/packages/SettingsLib/tests/unit/OWNERS b/packages/SettingsLib/tests/unit/OWNERS new file mode 100644 index 000000000000..66559252048b --- /dev/null +++ b/packages/SettingsLib/tests/unit/OWNERS @@ -0,0 +1,2 @@ +# We do not guard tests - everyone is welcomed to contribute to tests. +per-file *.kt=* 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 new file mode 100644 index 000000000000..27d7078774d5 --- /dev/null +++ b/packages/SettingsLib/tests/unit/src/com/android/settingslib/bluetooth/BluetoothLeBroadcastMetadataExtTest.kt @@ -0,0 +1,195 @@ +/* + * 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.settingslib.bluetooth + +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.bluetooth.BluetoothLeBroadcastSubgroup +import androidx.test.ext.junit.runners.AndroidJUnit4 +import com.android.settingslib.bluetooth.BluetoothLeBroadcastMetadataExt.toQrCodeString +import com.google.common.truth.Truth.assertThat +import org.junit.Test +import org.junit.runner.RunWith + +@RunWith(AndroidJUnit4::class) +class BluetoothLeBroadcastMetadataExtTest { + + @Test + fun toQrCodeString() { + 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(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, 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() + + assertThat(qrCodeString).isEqualTo(QR_CODE_STRING) + } + + @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)!! + + val qrCodeString = metadata.toQrCodeString() + + assertThat(qrCodeString).isEqualTo(QR_CODE_STRING) + } + + private companion object { + const val TEST_DEVICE_ADDRESS = "00:A1:A1:A1:A1:A1" + + val Device: BluetoothDevice = + BluetoothAdapter.getDefaultAdapter().getRemoteLeDevice(TEST_DEVICE_ADDRESS, + BluetoothDevice.ADDRESS_TYPE_RANDOM) + + const val QR_CODE_STRING = + "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 new file mode 100644 index 000000000000..8fe1f48dcaee --- /dev/null +++ b/packages/SystemUI/compose/core/src/com/android/compose/animation/Easings.kt @@ -0,0 +1,63 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.compose.animation + +import androidx.compose.animation.core.Easing +import androidx.core.animation.Interpolator +import com.android.app.animation.InterpolatorsAndroidX + +/** + * Compose-compatible definition of Android motion eases, see + * https://carbon.googleplex.com/android-motion/pages/easing + */ +object Easings { + + /** The standard interpolator that should be used on every normal animation */ + val StandardEasing = fromInterpolator(InterpolatorsAndroidX.STANDARD) + + /** + * The standard accelerating interpolator that should be used on every regular movement of + * content that is disappearing e.g. when moving off screen. + */ + val StandardAccelerateEasing = fromInterpolator(InterpolatorsAndroidX.STANDARD_ACCELERATE) + + /** + * The standard decelerating interpolator that should be used on every regular movement of + * content that is appearing e.g. when coming from off screen. + */ + val StandardDecelerateEasing = fromInterpolator(InterpolatorsAndroidX.STANDARD_DECELERATE) + + /** The default emphasized interpolator. Used for hero / emphasized movement of content. */ + val EmphasizedEasing = fromInterpolator(InterpolatorsAndroidX.EMPHASIZED) + + /** + * The accelerated emphasized interpolator. Used for hero / emphasized movement of content that + * is disappearing e.g. when moving off screen. + */ + val EmphasizedAccelerateEasing = fromInterpolator(InterpolatorsAndroidX.EMPHASIZED_ACCELERATE) + + /** + * The decelerating emphasized interpolator. Used for hero / emphasized movement of content that + * is appearing e.g. when coming from off screen + */ + val EmphasizedDecelerateEasing = fromInterpolator(InterpolatorsAndroidX.EMPHASIZED_DECELERATE) + + /** The linear interpolator. */ + val LinearEasing = fromInterpolator(InterpolatorsAndroidX.LINEAR) + + private fun fromInterpolator(source: Interpolator) = Easing { x -> source.getInterpolation(x) } +} diff --git a/packages/SystemUI/compose/facade/enabled/src/com/android/systemui/scene/ui/composable/SceneModule.kt b/packages/SystemUI/compose/facade/enabled/src/com/android/systemui/scene/ui/composable/SceneModule.kt index 954bad56bcc2..d3643747ad91 100644 --- a/packages/SystemUI/compose/facade/enabled/src/com/android/systemui/scene/ui/composable/SceneModule.kt +++ b/packages/SystemUI/compose/facade/enabled/src/com/android/systemui/scene/ui/composable/SceneModule.kt @@ -16,6 +16,7 @@ package com.android.systemui.scene.ui.composable +import android.content.Context import com.android.systemui.bouncer.ui.composable.BouncerScene import com.android.systemui.bouncer.ui.viewmodel.BouncerViewModel import com.android.systemui.dagger.SysUISingleton @@ -28,6 +29,7 @@ import com.android.systemui.scene.shared.model.Scene import com.android.systemui.scene.shared.model.SceneContainerNames import com.android.systemui.shade.ui.composable.ShadeScene import com.android.systemui.shade.ui.viewmodel.ShadeSceneViewModel +import com.android.systemui.statusbar.phone.SystemUIDialog import dagger.Module import dagger.Provides import javax.inject.Named @@ -57,6 +59,7 @@ object SceneModule { @SysUISingleton @Named(SceneContainerNames.SYSTEM_UI_DEFAULT) fun bouncerScene( + @Application context: Context, viewModelFactory: BouncerViewModel.Factory, ): BouncerScene { return BouncerScene( @@ -64,6 +67,7 @@ object SceneModule { viewModelFactory.create( containerName = SceneContainerNames.SYSTEM_UI_DEFAULT, ), + dialogFactory = { SystemUIDialog(context) }, ) } diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/BouncerScene.kt b/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/BouncerScene.kt index 3c74ef5adfeb..240bace21a1c 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/BouncerScene.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/BouncerScene.kt @@ -14,9 +14,16 @@ * limitations under the License. */ +@file:OptIn(ExperimentalMaterial3Api::class) + package com.android.systemui.bouncer.ui.composable +import android.app.AlertDialog +import android.app.Dialog +import android.content.DialogInterface import androidx.compose.animation.Crossfade +import androidx.compose.animation.core.snap +import androidx.compose.animation.core.tween import androidx.compose.foundation.background import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box @@ -26,15 +33,20 @@ import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.padding import androidx.compose.material3.Button import androidx.compose.material3.ButtonDefaults +import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.collectAsState import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp +import com.android.systemui.R import com.android.systemui.bouncer.ui.viewmodel.AuthMethodBouncerViewModel import com.android.systemui.bouncer.ui.viewmodel.BouncerViewModel import com.android.systemui.bouncer.ui.viewmodel.PasswordBouncerViewModel @@ -51,6 +63,7 @@ import kotlinx.coroutines.flow.asStateFlow /** The bouncer scene displays authentication challenges like PIN, password, or pattern. */ class BouncerScene( private val viewModel: BouncerViewModel, + private val dialogFactory: () -> AlertDialog, ) : ComposableScene { override val key = SceneKey.Bouncer @@ -68,16 +81,19 @@ class BouncerScene( override fun Content( containerName: String, modifier: Modifier, - ) = BouncerScene(viewModel, modifier) + ) = BouncerScene(viewModel, dialogFactory, modifier) } @Composable private fun BouncerScene( viewModel: BouncerViewModel, + dialogFactory: () -> AlertDialog, modifier: Modifier = Modifier, ) { - val message: String by viewModel.message.collectAsState() + val message: BouncerViewModel.MessageViewModel by viewModel.message.collectAsState() val authMethodViewModel: AuthMethodBouncerViewModel? by viewModel.authMethod.collectAsState() + val dialogMessage: String? by viewModel.throttlingDialogMessage.collectAsState() + var dialog: Dialog? by remember { mutableStateOf(null) } Column( horizontalAlignment = Alignment.CenterHorizontally, @@ -88,9 +104,10 @@ private fun BouncerScene( Crossfade( targetState = message, label = "Bouncer message", - ) { + animationSpec = if (message.isUpdateAnimated) tween() else snap(), + ) { message -> Text( - text = it, + text = message.text, color = MaterialTheme.colorScheme.onSurface, style = MaterialTheme.typography.bodyLarge, ) @@ -132,5 +149,26 @@ private fun BouncerScene( style = MaterialTheme.typography.bodyMedium, ) } + + if (dialogMessage != null) { + if (dialog == null) { + dialog = + dialogFactory().apply { + setMessage(dialogMessage) + setButton( + DialogInterface.BUTTON_NEUTRAL, + context.getString(R.string.ok), + ) { _, _ -> + viewModel.onThrottlingDialogDismissed() + } + setCancelable(false) + setCanceledOnTouchOutside(false) + show() + } + } + } else { + dialog?.dismiss() + dialog = null + } } } diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/PasswordBouncer.kt b/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/PasswordBouncer.kt index 4e85621e9e23..7545ff464bab 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/PasswordBouncer.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/PasswordBouncer.kt @@ -53,6 +53,8 @@ internal fun PasswordBouncer( ) { val focusRequester = remember { FocusRequester() } val password: String by viewModel.password.collectAsState() + val isInputEnabled: Boolean by viewModel.isInputEnabled.collectAsState() + val animateFailure: Boolean by viewModel.animateFailure.collectAsState() LaunchedEffect(Unit) { // When the UI comes up, request focus on the TextField to bring up the software keyboard. @@ -61,6 +63,13 @@ internal fun PasswordBouncer( viewModel.onShown() } + LaunchedEffect(animateFailure) { + if (animateFailure) { + // We don't currently have a failure animation for password, just consume it: + viewModel.onFailureAnimationShown() + } + } + Column( horizontalAlignment = Alignment.CenterHorizontally, modifier = modifier, @@ -71,6 +80,7 @@ internal fun PasswordBouncer( TextField( value = password, onValueChange = viewModel::onPasswordInputChanged, + enabled = isInputEnabled, visualTransformation = PasswordVisualTransformation(), singleLine = true, textStyle = LocalTextStyle.current.copy(textAlign = TextAlign.Center), diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/PatternBouncer.kt b/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/PatternBouncer.kt index 3afd33f4c90c..88441146ad23 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/PatternBouncer.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/PatternBouncer.kt @@ -18,6 +18,7 @@ package com.android.systemui.bouncer.ui.composable import android.view.HapticFeedbackConstants import androidx.compose.animation.core.Animatable +import androidx.compose.animation.core.AnimationVector1D import androidx.compose.animation.core.tween import androidx.compose.foundation.Canvas import androidx.compose.foundation.gestures.detectDragGestures @@ -41,12 +42,15 @@ import androidx.compose.ui.platform.LocalView import androidx.compose.ui.res.integerResource import androidx.compose.ui.unit.IntSize import androidx.compose.ui.unit.dp +import com.android.compose.animation.Easings import com.android.internal.R import com.android.systemui.bouncer.ui.viewmodel.PatternBouncerViewModel import com.android.systemui.bouncer.ui.viewmodel.PatternDotViewModel +import com.android.systemui.compose.modifiers.thenIf import kotlin.math.min import kotlin.math.pow import kotlin.math.sqrt +import kotlinx.coroutines.coroutineScope import kotlinx.coroutines.launch /** @@ -66,9 +70,9 @@ internal fun PatternBouncer( val rowCount = viewModel.rowCount val dotColor = MaterialTheme.colorScheme.secondary - val dotRadius = with(LocalDensity.current) { 8.dp.toPx() } + val dotRadius = with(LocalDensity.current) { (DOT_DIAMETER_DP / 2).dp.toPx() } val lineColor = MaterialTheme.colorScheme.primary - val lineStrokeWidth = dotRadius * 2 + with(LocalDensity.current) { 4.dp.toPx() } + val lineStrokeWidth = with(LocalDensity.current) { LINE_STROKE_WIDTH_DP.dp.toPx() } var containerSize: IntSize by remember { mutableStateOf(IntSize(0, 0)) } val horizontalSpacing = containerSize.width / colCount @@ -82,6 +86,9 @@ internal fun PatternBouncer( val currentDot: PatternDotViewModel? by viewModel.currentDot.collectAsState() // The dots selected so far, if the user is currently dragging. val selectedDots: List<PatternDotViewModel> by viewModel.selectedDots.collectAsState() + val isInputEnabled: Boolean by viewModel.isInputEnabled.collectAsState() + val isAnimationEnabled: Boolean by viewModel.isPatternVisible.collectAsState() + val animateFailure: Boolean by viewModel.animateFailure.collectAsState() // Map of animatables for the scale of each dot, keyed by dot. val dotScalingAnimatables = remember(dots) { dots.associateWith { Animatable(1f) } } @@ -96,19 +103,46 @@ internal fun PatternBouncer( val view = LocalView.current // When the current dot is changed, we need to update our animations. - LaunchedEffect(currentDot) { - view.performHapticFeedback( - HapticFeedbackConstants.VIRTUAL_KEY, - HapticFeedbackConstants.FLAG_IGNORE_VIEW_SETTING, - ) + LaunchedEffect(currentDot, isAnimationEnabled) { + // Perform haptic feedback, but only if the current dot is not null, so we don't perform it + // when the UI first shows up or when the user lifts their pointer/finger. + if (currentDot != null) { + view.performHapticFeedback( + HapticFeedbackConstants.VIRTUAL_KEY, + HapticFeedbackConstants.FLAG_IGNORE_VIEW_SETTING, + ) + } + + if (!isAnimationEnabled) { + return@LaunchedEffect + } - // Make sure that the current dot is scaled up while the other dots are scaled back down. + // Make sure that the current dot is scaled up while the other dots are scaled back + // down. dotScalingAnimatables.entries.forEach { (dot, animatable) -> val isSelected = dot == currentDot - launch { - animatable.animateTo(if (isSelected) 2f else 1f) + // Launch using the longer-lived scope because we want these animations to proceed to + // completion even if the LaunchedEffect is canceled because its key objects have + // changed. + scope.launch { if (isSelected) { - animatable.animateTo(1f) + animatable.animateTo( + targetValue = (SELECTED_DOT_DIAMETER_DP / DOT_DIAMETER_DP.toFloat()), + animationSpec = + tween( + durationMillis = SELECTED_DOT_REACTION_ANIMATION_DURATION_MS, + easing = Easings.StandardAccelerateEasing, + ), + ) + } else { + animatable.animateTo( + targetValue = 1f, + animationSpec = + tween( + durationMillis = SELECTED_DOT_RETRACT_ANIMATION_DURATION_MS, + easing = Easings.StandardDecelerateEasing, + ), + ) } } } @@ -116,14 +150,18 @@ internal fun PatternBouncer( selectedDots.forEach { dot -> lineFadeOutAnimatables[dot]?.let { line -> if (!line.isRunning) { + // Launch using the longer-lived scope because we want these animations to + // proceed to completion even if the LaunchedEffect is canceled because its key + // objects have changed. scope.launch { if (dot == currentDot) { - // Reset the fade-out animation for the current dot. When the current - // dot is switched, this entire code block runs again for the newly - // selected dot. + // Reset the fade-out animation for the current dot. When the + // current dot is switched, this entire code block runs again for + // the newly selected dot. line.snapTo(1f) } else { - // For all non-current dots, make sure that the lines are fading out. + // For all non-current dots, make sure that the lines are fading + // out. line.animateTo( targetValue = 0f, animationSpec = @@ -139,6 +177,17 @@ internal fun PatternBouncer( } } + // Show the failure animation if the user entered the wrong input. + LaunchedEffect(animateFailure) { + if (animateFailure) { + showFailureAnimation( + dots = dots, + scalingAnimatables = dotScalingAnimatables, + ) + viewModel.onFailureAnimationShown() + } + } + // This is the position of the input pointer. var inputPosition: Offset? by remember { mutableStateOf(null) } @@ -148,27 +197,34 @@ internal fun PatternBouncer( // when it leaves the bounds of the dot grid. .clipToBounds() .onSizeChanged { containerSize = it } - .pointerInput(Unit) { - detectDragGestures( - onDragStart = { start -> - inputPosition = start - viewModel.onDragStart() - }, - onDragEnd = { - inputPosition = null - lineFadeOutAnimatables.values.forEach { animatable -> - scope.launch { animatable.animateTo(1f) } - } - viewModel.onDragEnd() - }, - ) { change, _ -> - inputPosition = change.position - viewModel.onDrag( - xPx = change.position.x, - yPx = change.position.y, - containerSizePx = containerSize.width, - verticalOffsetPx = verticalOffset, - ) + .thenIf(isInputEnabled) { + Modifier.pointerInput(Unit) { + detectDragGestures( + onDragStart = { start -> + inputPosition = start + viewModel.onDragStart() + }, + onDragEnd = { + inputPosition = null + if (isAnimationEnabled) { + lineFadeOutAnimatables.values.forEach { animatable -> + // Launch using the longer-lived scope because we want these + // animations to proceed to completion even if the surrounding + // scope is canceled. + scope.launch { animatable.animateTo(1f) } + } + } + viewModel.onDragEnd() + }, + ) { change, _ -> + inputPosition = change.position + viewModel.onDrag( + xPx = change.position.x, + yPx = change.position.y, + containerSizePx = containerSize.width, + verticalOffsetPx = verticalOffset, + ) + } } } ) { @@ -247,3 +303,62 @@ private fun lineAlpha(gridSpacing: Float, lineLength: Float = gridSpacing): Floa // farther the user input pointer goes from the line, the more opaque the line gets. return ((lineLength / gridSpacing - 0.3f) * 4f).coerceIn(0f, 1f) } + +private suspend fun showFailureAnimation( + dots: List<PatternDotViewModel>, + scalingAnimatables: Map<PatternDotViewModel, Animatable<Float, AnimationVector1D>>, +) { + val dotsByRow = + buildList<MutableList<PatternDotViewModel>> { + dots.forEach { dot -> + val rowIndex = dot.y + while (size <= rowIndex) { + add(mutableListOf()) + } + get(rowIndex).add(dot) + } + } + + coroutineScope { + dotsByRow.forEachIndexed { rowIndex, rowDots -> + rowDots.forEach { dot -> + scalingAnimatables[dot]?.let { dotScaleAnimatable -> + launch { + dotScaleAnimatable.animateTo( + targetValue = + FAILURE_ANIMATION_DOT_DIAMETER_DP / DOT_DIAMETER_DP.toFloat(), + animationSpec = + tween( + durationMillis = + FAILURE_ANIMATION_DOT_SHRINK_ANIMATION_DURATION_MS, + delayMillis = + rowIndex * FAILURE_ANIMATION_DOT_SHRINK_STAGGER_DELAY_MS, + easing = Easings.LinearEasing, + ), + ) + + dotScaleAnimatable.animateTo( + targetValue = 1f, + animationSpec = + tween( + durationMillis = + FAILURE_ANIMATION_DOT_REVERT_ANIMATION_DURATION, + easing = Easings.StandardEasing, + ), + ) + } + } + } + } + } +} + +private const val DOT_DIAMETER_DP = 16 +private const val SELECTED_DOT_DIAMETER_DP = 24 +private const val SELECTED_DOT_REACTION_ANIMATION_DURATION_MS = 83 +private const val SELECTED_DOT_RETRACT_ANIMATION_DURATION_MS = 750 +private const val LINE_STROKE_WIDTH_DP = 16 +private const val FAILURE_ANIMATION_DOT_DIAMETER_DP = 13 +private const val FAILURE_ANIMATION_DOT_SHRINK_ANIMATION_DURATION_MS = 50 +private const val FAILURE_ANIMATION_DOT_SHRINK_STAGGER_DELAY_MS = 33 +private const val FAILURE_ANIMATION_DOT_REVERT_ANIMATION_DURATION = 617 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 9c210c225ab3..968e5ab8ad8c 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 @@ -18,11 +18,15 @@ 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.animateDpAsState +import androidx.compose.animation.core.tween import androidx.compose.animation.fadeIn import androidx.compose.animation.fadeOut import androidx.compose.animation.scaleIn @@ -48,6 +52,7 @@ import androidx.compose.runtime.collectAsState import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember +import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier @@ -55,15 +60,23 @@ 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.platform.LocalView 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.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 +import kotlinx.coroutines.delay +import kotlinx.coroutines.launch @Composable internal fun PinBouncer( @@ -75,6 +88,16 @@ internal fun PinBouncer( // 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() + + // Show the failure animation if the user entered the wrong input. + LaunchedEffect(animateFailure) { + if (animateFailure) { + showFailureAnimation() + viewModel.onFailureAnimationShown() + } + } Column( horizontalAlignment = Alignment.CenterHorizontally, @@ -116,6 +139,7 @@ internal fun PinBouncer( val digit = index + 1 PinButton( onClicked = { viewModel.onPinButtonClicked(digit) }, + isEnabled = isInputEnabled, ) { contentColor -> PinDigit(digit, contentColor) } @@ -124,7 +148,8 @@ internal fun PinBouncer( PinButton( onClicked = { viewModel.onBackspaceButtonClicked() }, onLongPressed = { viewModel.onBackspaceButtonLongPressed() }, - isHighlighted = true, + isEnabled = isInputEnabled, + isIconButton = true, ) { contentColor -> PinIcon( Icon.Resource( @@ -138,13 +163,15 @@ internal fun PinBouncer( PinButton( onClicked = { viewModel.onPinButtonClicked(0) }, + isEnabled = isInputEnabled, ) { contentColor -> PinDigit(0, contentColor) } PinButton( onClicked = { viewModel.onAuthenticateButtonClicked() }, - isHighlighted = true, + isEnabled = isInputEnabled, + isIconButton = true, ) { contentColor -> PinIcon( Icon.Resource( @@ -187,61 +214,107 @@ private fun PinIcon( @Composable private fun PinButton( onClicked: () -> Unit, + isEnabled: Boolean, modifier: Modifier = Modifier, onLongPressed: (() -> Unit)? = null, - isHighlighted: Boolean = false, + isIconButton: Boolean = false, content: @Composable (contentColor: Color) -> Unit, ) { var isPressed: Boolean by remember { mutableStateOf(false) } + + val view = LocalView.current + LaunchedEffect(isPressed) { + if (isPressed) { + view.performHapticFeedback( + HapticFeedbackConstants.VIRTUAL_KEY, + HapticFeedbackConstants.FLAG_IGNORE_VIEW_SETTING, + ) + } + } + + // Pin button animation specification is asymmetric: fast animation to the pressed state, and a + // slow animation upon release. Note that isPressed is guaranteed to be true for at least the + // press animation duration (see below in detectTapGestures). + val animEasing = if (isPressed) pinButtonPressedEasing else pinButtonReleasedEasing + val animDurationMillis = + (if (isPressed) pinButtonPressedDuration else pinButtonReleasedDuration).toInt( + DurationUnit.MILLISECONDS + ) + val cornerRadius: Dp by animateDpAsState( - if (isPressed) 24.dp else PinButtonSize / 2, + if (isPressed) 24.dp else pinButtonSize / 2, label = "PinButton round corners", + animationSpec = tween(animDurationMillis, easing = animEasing) ) + val colorAnimationSpec: AnimationSpec<Color> = tween(animDurationMillis, easing = animEasing) val containerColor: Color by animateColorAsState( when { - isPressed -> MaterialTheme.colorScheme.primaryContainer - isHighlighted -> MaterialTheme.colorScheme.secondaryContainer - else -> MaterialTheme.colorScheme.surface + isPressed -> MaterialTheme.colorScheme.primary + isIconButton -> MaterialTheme.colorScheme.secondaryContainer + else -> MaterialTheme.colorScheme.surfaceVariant }, label = "Pin button container color", + animationSpec = colorAnimationSpec ) val contentColor: Color by animateColorAsState( when { - isPressed -> MaterialTheme.colorScheme.onPrimaryContainer - isHighlighted -> MaterialTheme.colorScheme.onSecondaryContainer - else -> MaterialTheme.colorScheme.onSurface + isPressed -> MaterialTheme.colorScheme.onPrimary + isIconButton -> MaterialTheme.colorScheme.onSecondaryContainer + else -> MaterialTheme.colorScheme.onSurfaceVariant }, label = "Pin button container color", + animationSpec = colorAnimationSpec ) + val scope = rememberCoroutineScope() + Box( contentAlignment = Alignment.Center, modifier = modifier - .size(PinButtonSize) + .size(pinButtonSize) .drawBehind { drawRoundRect( color = containerColor, cornerRadius = CornerRadius(cornerRadius.toPx()), ) } - .pointerInput(Unit) { - detectTapGestures( - onPress = { - isPressed = true - tryAwaitRelease() - isPressed = false - }, - onTap = { onClicked() }, - onLongPress = onLongPressed?.let { { onLongPressed() } }, - ) + .thenIf(isEnabled) { + Modifier.pointerInput(Unit) { + detectTapGestures( + onPress = { + scope.launch { + isPressed = true + val minDuration = async { + delay(pinButtonPressedDuration + pinButtonHoldTime) + } + tryAwaitRelease() + minDuration.await() + isPressed = false + } + }, + onTap = { onClicked() }, + onLongPress = onLongPressed?.let { { onLongPressed() } }, + ) + } }, ) { content(contentColor) } } -private val PinButtonSize = 84.dp +private fun showFailureAnimation() { + // TODO(b/282730134): implement. +} + +private val pinButtonSize = 84.dp + +// Pin button motion spec: http://shortn/_9TTIG6SoEa +private val pinButtonPressedDuration = 100.milliseconds +private val pinButtonPressedEasing = LinearEasing +private val pinButtonHoldTime = 33.milliseconds +private val pinButtonReleasedDuration = 420.milliseconds +private val pinButtonReleasedEasing = Easings.StandardEasing diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/compose/modifiers/ConditionalModifiers.kt b/packages/SystemUI/compose/features/src/com/android/systemui/compose/modifiers/ConditionalModifiers.kt new file mode 100644 index 000000000000..83071d78c64d --- /dev/null +++ b/packages/SystemUI/compose/features/src/com/android/systemui/compose/modifiers/ConditionalModifiers.kt @@ -0,0 +1,52 @@ +/* + * 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.compose.modifiers + +import androidx.compose.ui.Modifier + +/** + * Concatenates this modifier with another if `condition` is true. + * + * @param condition Whether or not to apply the modifiers. + * @param factory Creates the modifier to concatenate with the current one. + * @return a Modifier representing this modifier followed by other in sequence. + * @see Modifier.then + * + * This method allows inline conditional addition of modifiers to a modifier chain. Instead of + * writing + * + * ``` + * val aModifier = Modifier.a() + * val bModifier = if(condition) aModifier.b() else aModifier + * Composable(modifier = bModifier) + * ``` + * + * You can instead write + * + * ``` + * Composable(modifier = Modifier.a().thenIf(condition){ + * Modifier.b() + * } + * ``` + * + * This makes the modifier chain easier to read. + * + * Note that unlike the non-factory version, the conditional modifier is recreated each time, and + * may never be created at all. + */ +inline fun Modifier.thenIf(condition: Boolean, crossinline factory: () -> Modifier): Modifier = + if (condition) this.then(factory()) else this 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/clocks/DefaultClockController.kt b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/DefaultClockController.kt index 252c8e343e0e..e557c8e5902a 100644 --- a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/DefaultClockController.kt +++ b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/DefaultClockController.kt @@ -33,6 +33,7 @@ import com.android.systemui.plugins.ClockFaceConfig import com.android.systemui.plugins.ClockFaceController import com.android.systemui.plugins.ClockFaceEvents import com.android.systemui.plugins.ClockSettings +import com.android.systemui.plugins.WeatherData import java.io.PrintWriter import java.util.Locale import java.util.TimeZone @@ -50,6 +51,7 @@ class DefaultClockController( private val layoutInflater: LayoutInflater, private val resources: Resources, private val settings: ClockSettings?, + private val hasStepClockAnimation: Boolean = false, ) : ClockController { override val smallClock: DefaultClockFaceController override val largeClock: LargeClockFaceController @@ -170,7 +172,8 @@ class DefaultClockController( view: AnimatableClockView, seedColor: Int?, ) : DefaultClockFaceController(view, seedColor) { - override val config = ClockFaceConfig(hasCustomPositionUpdatedAnimation = true) + override val config = + ClockFaceConfig(hasCustomPositionUpdatedAnimation = hasStepClockAnimation) init { animations = LargeClockAnimations(view, 0f, 0f) @@ -225,6 +228,8 @@ class DefaultClockController( clocks.forEach { it.refreshFormat() } } + + override fun onWeatherDataChanged(data: WeatherData) {} } open inner class DefaultClockAnimations( @@ -271,6 +276,8 @@ class DefaultClockController( // the top margin change in recomputePadding to make clock be centered view.translationY = 0.5f * view.bottom * (1 - swipingFraction) } + + override fun onPositionUpdated(fromLeft: Int, direction: Int, fraction: Float) {} } inner class LargeClockAnimations( diff --git a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/DefaultClockProvider.kt b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/DefaultClockProvider.kt index 0fd1b492c146..949641a7f75e 100644 --- a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/DefaultClockProvider.kt +++ b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/DefaultClockProvider.kt @@ -29,10 +29,11 @@ const val DEFAULT_CLOCK_NAME = "Default Clock" const val DEFAULT_CLOCK_ID = "DEFAULT" /** Provides the default system clock */ -class DefaultClockProvider constructor( +class DefaultClockProvider( val ctx: Context, val layoutInflater: LayoutInflater, - val resources: Resources + val resources: Resources, + val hasStepClockAnimation: Boolean = false ) : ClockProvider { override fun getClocks(): List<ClockMetadata> = listOf(ClockMetadata(DEFAULT_CLOCK_ID, DEFAULT_CLOCK_NAME)) @@ -42,7 +43,13 @@ class DefaultClockProvider constructor( throw IllegalArgumentException("${settings.clockId} is unsupported by $TAG") } - return DefaultClockController(ctx, layoutInflater, resources, settings) + return DefaultClockController( + ctx, + layoutInflater, + resources, + settings, + hasStepClockAnimation, + ) } override fun getClockThumbnail(id: ClockId): Drawable? { 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/plugin/src/com/android/systemui/plugins/ClockProviderPlugin.kt b/packages/SystemUI/plugin/src/com/android/systemui/plugins/ClockProviderPlugin.kt index 5d0a3af74e5d..3ae328e67e3d 100644 --- a/packages/SystemUI/plugin/src/com/android/systemui/plugins/ClockProviderPlugin.kt +++ b/packages/SystemUI/plugin/src/com/android/systemui/plugins/ClockProviderPlugin.kt @@ -74,18 +74,10 @@ interface ClockController { resources: Resources, dozeFraction: Float, foldFraction: Float, - ) { - events.onColorPaletteChanged(resources) - smallClock.animations.doze(dozeFraction) - largeClock.animations.doze(dozeFraction) - smallClock.animations.fold(foldFraction) - largeClock.animations.fold(foldFraction) - smallClock.events.onTimeTick() - largeClock.events.onTimeTick() - } + ) /** Optional method for dumping debug information */ - fun dump(pw: PrintWriter) {} + fun dump(pw: PrintWriter) } /** Interface for a specific clock face version rendered by the clock */ @@ -109,37 +101,37 @@ interface ClockFaceController { /** Events that should call when various rendering parameters change */ interface ClockEvents { /** Call whenever timezone changes */ - fun onTimeZoneChanged(timeZone: TimeZone) {} + fun onTimeZoneChanged(timeZone: TimeZone) /** Call whenever the text time format changes (12hr vs 24hr) */ - fun onTimeFormatChanged(is24Hr: Boolean) {} + fun onTimeFormatChanged(is24Hr: Boolean) /** Call whenever the locale changes */ - fun onLocaleChanged(locale: Locale) {} + fun onLocaleChanged(locale: Locale) /** Call whenever the color palette should update */ - fun onColorPaletteChanged(resources: Resources) {} + fun onColorPaletteChanged(resources: Resources) /** Call if the seed color has changed and should be updated */ - fun onSeedColorChanged(seedColor: Int?) {} + fun onSeedColorChanged(seedColor: Int?) /** Call whenever the weather data should update */ - fun onWeatherDataChanged(data: WeatherData) {} + fun onWeatherDataChanged(data: WeatherData) } /** Methods which trigger various clock animations */ interface ClockAnimations { /** Runs an enter animation (if any) */ - fun enter() {} + fun enter() /** Sets how far into AOD the device currently is. */ - fun doze(fraction: Float) {} + fun doze(fraction: Float) /** Sets how far into the folding animation the device is. */ - fun fold(fraction: Float) {} + fun fold(fraction: Float) /** Runs the battery animation (if any). */ - fun charge() {} + fun charge() /** * Runs when the clock's position changed during the move animation. @@ -150,32 +142,32 @@ interface ClockAnimations { * @param fraction fraction of the clock movement. 0 means it is at the beginning, and 1 means * it finished moving. */ - fun onPositionUpdated(fromLeft: Int, direction: Int, fraction: Float) {} + fun onPositionUpdated(fromLeft: Int, direction: Int, fraction: Float) /** * Runs when swiping clock picker, swipingFraction: 1.0 -> clock is scaled up in the preview, * 0.0 -> clock is scaled down in the shade; previewRatio is previewSize / screenSize */ - fun onPickerCarouselSwiping(swipingFraction: Float) {} + fun onPickerCarouselSwiping(swipingFraction: Float) } /** Events that have specific data about the related face */ interface ClockFaceEvents { /** Call every time tick */ - fun onTimeTick() {} + fun onTimeTick() /** * Region Darkness specific to the clock face. * - isRegionDark = dark theme -> clock should be light * - !isRegionDark = light theme -> clock should be dark */ - fun onRegionDarknessChanged(isRegionDark: Boolean) {} + fun onRegionDarknessChanged(isRegionDark: Boolean) /** * Call whenever font settings change. Pass in a target font size in pixels. The specific clock * design is allowed to ignore this target size on a case-by-case basis. */ - fun onFontSettingChanged(fontSizePx: Float) {} + fun onFontSettingChanged(fontSizePx: Float) /** * Target region information for the clock face. For small clock, this will match the bounds of @@ -184,7 +176,7 @@ interface ClockFaceEvents { * render within the centered targetRect to avoid obstructing other elements. The specified * targetRegion is relative to the parent view. */ - fun onTargetRegionChanged(targetRegion: Rect?) {} + fun onTargetRegionChanged(targetRegion: Rect?) } /** Tick rates for clocks */ 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/media_output_list_item_advanced.xml b/packages/SystemUI/res/layout/media_output_list_item_advanced.xml index 054193a5b323..a595566ef817 100644 --- a/packages/SystemUI/res/layout/media_output_list_item_advanced.xml +++ b/packages/SystemUI/res/layout/media_output_list_item_advanced.xml @@ -65,6 +65,8 @@ <TextView android:id="@+id/volume_value" android:animateLayoutChanges="true" + android:focusable="false" + android:importantForAccessibility="no" android:layout_gravity="center" android:layout_width="wrap_content" android:layout_height="wrap_content" @@ -75,6 +77,8 @@ <TextView android:id="@+id/title" + android:focusable="false" + android:importantForAccessibility="no" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_gravity="center_vertical|start" diff --git a/packages/SystemUI/shared/src/com/android/systemui/unfold/util/UnfoldOnlyProgressProvider.kt b/packages/SystemUI/shared/src/com/android/systemui/unfold/util/UnfoldOnlyProgressProvider.kt new file mode 100644 index 000000000000..495d3a13d961 --- /dev/null +++ b/packages/SystemUI/shared/src/com/android/systemui/unfold/util/UnfoldOnlyProgressProvider.kt @@ -0,0 +1,63 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the + * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ +package com.android.systemui.unfold.util + +import com.android.systemui.dagger.qualifiers.Main +import com.android.systemui.unfold.UnfoldTransitionProgressProvider +import com.android.systemui.unfold.UnfoldTransitionProgressProvider.TransitionProgressListener +import com.android.systemui.unfold.updates.FoldProvider +import com.android.systemui.unfold.updates.FoldProvider.FoldCallback +import java.util.concurrent.Executor + +/** + * [UnfoldTransitionProgressProvider] that emits transition progress only when unfolding but not + * when folding, so we can play the animation only one way but not the other way. + */ +class UnfoldOnlyProgressProvider( + foldProvider: FoldProvider, + @Main private val executor: Executor, + private val sourceProvider: UnfoldTransitionProgressProvider, + private val scopedProvider: ScopedUnfoldTransitionProgressProvider = + ScopedUnfoldTransitionProgressProvider(sourceProvider) +) : UnfoldTransitionProgressProvider by scopedProvider { + + private var isFolded = false + + init { + foldProvider.registerCallback(FoldListener(), executor) + sourceProvider.addCallback(SourceTransitionListener()) + } + + private inner class SourceTransitionListener : TransitionProgressListener { + override fun onTransitionFinished() { + // Disable scoped progress provider after the first unfold animation, so fold animation + // will not be propagated. It will be re-enabled after folding so we can play + // the unfold animation again. + if (!isFolded) { + scopedProvider.setReadyToHandleTransition(false) + } + } + } + + private inner class FoldListener : FoldCallback { + override fun onFoldUpdated(isFolded: Boolean) { + if (isFolded) { + scopedProvider.setReadyToHandleTransition(true) + } + + this@UnfoldOnlyProgressProvider.isFolded = isFolded + } + } +} diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardPatternView.java b/packages/SystemUI/src/com/android/keyguard/KeyguardPatternView.java index afc25909ca91..99b5d52f8322 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardPatternView.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardPatternView.java @@ -158,15 +158,18 @@ public class KeyguardPatternView extends KeyguardInputView public void startAppearAnimation() { enableClipping(false); - setAlpha(1f); + setAlpha(0f); setTranslationY(mAppearAnimationUtils.getStartTranslation()); AppearAnimationUtils.startTranslationYAnimation(this, 0 /* delay */, 500 /* duration */, 0, mAppearAnimationUtils.getInterpolator(), getAnimationListener(InteractionJankMonitor.CUJ_LOCKSCREEN_PATTERN_APPEAR)); - mAppearAnimationUtils.startAnimation2d( - mLockPatternView.getCellStates(), - () -> enableClipping(true), - this); + mLockPatternView.post(() -> { + setAlpha(1f); + mAppearAnimationUtils.startAnimation2d( + mLockPatternView.getCellStates(), + () -> enableClipping(true), + KeyguardPatternView.this); + }); if (!TextUtils.isEmpty(mSecurityMessageDisplay.getText())) { mAppearAnimationUtils.createAnimation(mSecurityMessageDisplay, 0, AppearAnimationUtils.DEFAULT_APPEAR_DURATION, diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardStatusViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardStatusViewController.java index 1db0ab65e44f..835cc13bebb1 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardStatusViewController.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardStatusViewController.java @@ -46,7 +46,6 @@ import com.android.keyguard.KeyguardClockSwitch.ClockSize; import com.android.keyguard.logging.KeyguardLogger; import com.android.systemui.R; import com.android.systemui.flags.FeatureFlags; -import com.android.systemui.flags.Flags; import com.android.systemui.plugins.ClockController; import com.android.systemui.statusbar.notification.AnimatableProperty; import com.android.systemui.statusbar.notification.PropertyAnimator; @@ -363,7 +362,7 @@ public class KeyguardStatusViewController extends ViewController<KeyguardStatusV boolean customClockAnimation = clock != null && clock.getLargeClock().getConfig().getHasCustomPositionUpdatedAnimation(); - if (mFeatureFlags.isEnabled(Flags.STEP_CLOCK_ANIMATION) && customClockAnimation) { + if (customClockAnimation) { // Find the clock, so we can exclude it from this transition. FrameLayout clockContainerView = mView.findViewById(R.id.lockscreen_clock_view_large); @@ -400,8 +399,10 @@ public class KeyguardStatusViewController extends ViewController<KeyguardStatusV @VisibleForTesting static class SplitShadeTransitionAdapter extends Transition { private static final String PROP_BOUNDS_LEFT = "splitShadeTransitionAdapter:boundsLeft"; + private static final String PROP_BOUNDS_RIGHT = "splitShadeTransitionAdapter:boundsRight"; private static final String PROP_X_IN_WINDOW = "splitShadeTransitionAdapter:xInWindow"; - private static final String[] TRANSITION_PROPERTIES = { PROP_BOUNDS_LEFT, PROP_X_IN_WINDOW}; + private static final String[] TRANSITION_PROPERTIES = { + PROP_BOUNDS_LEFT, PROP_BOUNDS_RIGHT, PROP_X_IN_WINDOW}; private final KeyguardClockSwitchController mController; @@ -412,6 +413,7 @@ public class KeyguardStatusViewController extends ViewController<KeyguardStatusV private void captureValues(TransitionValues transitionValues) { transitionValues.values.put(PROP_BOUNDS_LEFT, transitionValues.view.getLeft()); + transitionValues.values.put(PROP_BOUNDS_RIGHT, transitionValues.view.getRight()); int[] locationInWindowTmp = new int[2]; transitionValues.view.getLocationInWindow(locationInWindowTmp); transitionValues.values.put(PROP_X_IN_WINDOW, locationInWindowTmp[0]); diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java index e8046dc07c02..1721891550a1 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java @@ -246,6 +246,7 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab private static final int MSG_TIME_FORMAT_UPDATE = 344; private static final int MSG_REQUIRE_NFC_UNLOCK = 345; private static final int MSG_KEYGUARD_DISMISS_ANIMATION_FINISHED = 346; + private static final int MSG_SERVICE_PROVIDERS_UPDATED = 347; /** Biometric authentication state: Not listening. */ private static final int BIOMETRIC_STATE_STOPPED = 0; @@ -1826,6 +1827,8 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab } else if (TelephonyManager.ACTION_PHONE_STATE_CHANGED.equals(action)) { String state = intent.getStringExtra(TelephonyManager.EXTRA_STATE); mHandler.sendMessage(mHandler.obtainMessage(MSG_PHONE_STATE_CHANGED, state)); + } else if (TelephonyManager.ACTION_SERVICE_PROVIDERS_UPDATED.equals(action)) { + mHandler.obtainMessage(MSG_SERVICE_PROVIDERS_UPDATED, intent).sendToTarget(); } else if (Intent.ACTION_AIRPLANE_MODE_CHANGED.equals(action)) { mHandler.sendEmptyMessage(MSG_AIRPLANE_MODE_CHANGED); } else if (Intent.ACTION_SERVICE_STATE.equals(action)) { @@ -2402,6 +2405,9 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab case MSG_SERVICE_STATE_CHANGE: handleServiceStateChange(msg.arg1, (ServiceState) msg.obj); break; + case MSG_SERVICE_PROVIDERS_UPDATED: + handleServiceProvidersUpdated((Intent) msg.obj); + break; case MSG_SCREEN_TURNED_OFF: Trace.beginSection("KeyguardUpdateMonitor#handler MSG_SCREEN_TURNED_OFF"); handleScreenTurnedOff(); @@ -2472,6 +2478,7 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab filter.addAction(Intent.ACTION_SERVICE_STATE); filter.addAction(TelephonyManager.ACTION_DEFAULT_DATA_SUBSCRIPTION_CHANGED); filter.addAction(TelephonyManager.ACTION_PHONE_STATE_CHANGED); + filter.addAction(TelephonyManager.ACTION_SERVICE_PROVIDERS_UPDATED); filter.addAction(DevicePolicyManager.ACTION_DEVICE_POLICY_MANAGER_STATE_CHANGED); filter.addAction(UsbManager.ACTION_USB_PORT_COMPLIANCE_CHANGED); mBroadcastDispatcher.registerReceiverWithHandler(mBroadcastReceiver, filter, mHandler); @@ -3727,6 +3734,14 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab } /** + * Handle {@link #MSG_SERVICE_PROVIDERS_UPDATED} + */ + private void handleServiceProvidersUpdated(Intent intent) { + mLogger.logServiceProvidersUpdated(intent); + callbacksRefreshCarrierInfo(); + } + + /** * Whether the keyguard is showing and not occluded. */ public boolean isKeyguardVisible() { diff --git a/packages/SystemUI/src/com/android/keyguard/dagger/ClockRegistryModule.java b/packages/SystemUI/src/com/android/keyguard/dagger/ClockRegistryModule.java index 2d0bf9cc2aba..d2b10d687f04 100644 --- a/packages/SystemUI/src/com/android/keyguard/dagger/ClockRegistryModule.java +++ b/packages/SystemUI/src/com/android/keyguard/dagger/ClockRegistryModule.java @@ -63,7 +63,11 @@ public abstract class ClockRegistryModule { bgDispatcher, featureFlags.isEnabled(Flags.LOCKSCREEN_CUSTOM_CLOCKS), /* handleAllUsers= */ true, - new DefaultClockProvider(context, layoutInflater, resources), + new DefaultClockProvider( + context, + layoutInflater, + resources, + featureFlags.isEnabled(Flags.STEP_CLOCK_ANIMATION)), context.getString(R.string.lockscreen_clock_id_fallback), logBuffer, /* keepAllLoaded = */ false, diff --git a/packages/SystemUI/src/com/android/keyguard/logging/KeyguardUpdateMonitorLogger.kt b/packages/SystemUI/src/com/android/keyguard/logging/KeyguardUpdateMonitorLogger.kt index 17cc23632d94..4923ab0fab18 100644 --- a/packages/SystemUI/src/com/android/keyguard/logging/KeyguardUpdateMonitorLogger.kt +++ b/packages/SystemUI/src/com/android/keyguard/logging/KeyguardUpdateMonitorLogger.kt @@ -16,11 +16,15 @@ package com.android.keyguard.logging +import android.content.Intent import android.hardware.biometrics.BiometricConstants.LockoutMode import android.os.PowerManager import android.os.PowerManager.WakeReason import android.telephony.ServiceState import android.telephony.SubscriptionInfo +import android.telephony.SubscriptionManager.EXTRA_SUBSCRIPTION_INDEX +import android.telephony.SubscriptionManager.INVALID_SUBSCRIPTION_ID +import android.telephony.TelephonyManager import com.android.keyguard.ActiveUnlockConfig import com.android.keyguard.FaceAuthUiEvent import com.android.keyguard.KeyguardListenModel @@ -363,6 +367,21 @@ constructor(@KeyguardUpdateMonitorLog private val logBuffer: LogBuffer) { ) } + fun logServiceProvidersUpdated(intent: Intent) { + logBuffer.log( + TAG, + VERBOSE, + { + int1 = intent.getIntExtra(EXTRA_SUBSCRIPTION_INDEX, INVALID_SUBSCRIPTION_ID) + str1 = intent.getStringExtra(TelephonyManager.EXTRA_SPN) + str2 = intent.getStringExtra(TelephonyManager.EXTRA_PLMN) + }, + { + "action SERVICE_PROVIDERS_UPDATED subId=$int1 spn=$str1 plmn=$str2" + } + ) + } + fun logSimState(subId: Int, slotId: Int, state: Int) { logBuffer.log( TAG, diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/SideFpsController.kt b/packages/SystemUI/src/com/android/systemui/biometrics/SideFpsController.kt index 07825377440b..630cfffe5969 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/SideFpsController.kt +++ b/packages/SystemUI/src/com/android/systemui/biometrics/SideFpsController.kt @@ -152,7 +152,7 @@ constructor( WindowManager.LayoutParams( WindowManager.LayoutParams.WRAP_CONTENT, WindowManager.LayoutParams.WRAP_CONTENT, - WindowManager.LayoutParams.TYPE_KEYGUARD_DIALOG, + WindowManager.LayoutParams.TYPE_NAVIGATION_BAR_PANEL, Utils.FINGERPRINT_OVERLAY_LAYOUT_PARAM_FLAGS, PixelFormat.TRANSLUCENT ) 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 e462e2f5b7d8..1d2fce7d8b05 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 @@ -148,12 +148,17 @@ constructor( * * If the input is correct, the device will be unlocked and the lock screen and bouncer will be * dismissed and hidden. + * + * @param input The input from the user to try to authenticate with. This can be a list of + * different things, based on the current authentication method. + * @return `true` if the authentication succeeded and the device is now unlocked; `false` + * otherwise. */ fun authenticate( input: List<Any>, - ) { + ): Boolean { if (repository.throttling.value != null) { - return + return false } val isAuthenticated = authenticationInteractor.authenticate(input) @@ -186,6 +191,8 @@ constructor( } else -> repository.setMessage(errorMessage(authenticationMethod.value)) } + + return isAuthenticated } private fun promptMessage(authMethod: AuthenticationMethodModel): String { diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/AuthMethodBouncerViewModel.kt b/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/AuthMethodBouncerViewModel.kt index 774a5593530c..d95b70c85fe0 100644 --- a/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/AuthMethodBouncerViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/AuthMethodBouncerViewModel.kt @@ -16,14 +16,37 @@ package com.android.systemui.bouncer.ui.viewmodel +import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.flow.asStateFlow -sealed interface AuthMethodBouncerViewModel { +sealed class AuthMethodBouncerViewModel( /** * Whether user input is enabled. * * If `false`, user input should be completely ignored in the UI as the user is "locked out" of * being able to attempt to unlock the device. */ - val isInputEnabled: StateFlow<Boolean> + val isInputEnabled: StateFlow<Boolean>, +) { + + private val _animateFailure = MutableStateFlow(false) + /** + * Whether a failure animation should be shown. Once consumed, the UI must call + * [onFailureAnimationShown] to consume this state. + */ + val animateFailure: StateFlow<Boolean> = _animateFailure.asStateFlow() + + /** + * Notifies that the failure animation has been shown. This should be called to consume a `true` + * value in [animateFailure]. + */ + fun onFailureAnimationShown() { + _animateFailure.value = false + } + + /** Ask the UI to show the failure animation. */ + protected fun showFailureAnimation() { + _animateFailure.value = true + } } 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 02991bd47c6e..984d9ab1c1be 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 @@ -20,6 +20,7 @@ import android.content.Context import com.android.systemui.R import com.android.systemui.authentication.shared.model.AuthenticationMethodModel import com.android.systemui.bouncer.domain.interactor.BouncerInteractor +import com.android.systemui.bouncer.shared.model.AuthenticationThrottledModel import com.android.systemui.dagger.qualifiers.Application import dagger.assisted.Assisted import dagger.assisted.AssistedFactory @@ -29,6 +30,7 @@ import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.asStateFlow +import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.distinctUntilChanged import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.stateIn @@ -45,21 +47,6 @@ constructor( ) { private val interactor: BouncerInteractor = interactorFactory.create(containerName) - /** - * Whether updates to the message should be cross-animated from one message to another. - * - * If `false`, no animation should be applied, the message text should just be replaced - * instantly. - */ - val isMessageUpdateAnimationsEnabled: StateFlow<Boolean> = - interactor.throttling - .map { it == null } - .stateIn( - scope = applicationScope, - started = SharingStarted.WhileSubscribed(), - initialValue = interactor.throttling.value == null, - ) - private val isInputEnabled: StateFlow<Boolean> = interactor.throttling .map { it == null } @@ -104,13 +91,21 @@ constructor( ) /** The user-facing message to show in the bouncer. */ - val message: StateFlow<String> = - interactor.message - .map { it ?: "" } + val message: StateFlow<MessageViewModel> = + combine( + interactor.message, + interactor.throttling, + ) { message, throttling -> + toMessageViewModel(message, throttling) + } .stateIn( scope = applicationScope, started = SharingStarted.WhileSubscribed(), - initialValue = interactor.message.value ?: "", + initialValue = + toMessageViewModel( + message = interactor.message.value, + throttling = interactor.throttling.value, + ), ) private val _throttlingDialogMessage = MutableStateFlow<String?>(null) @@ -177,6 +172,28 @@ constructor( } } + private fun toMessageViewModel( + message: String?, + throttling: AuthenticationThrottledModel?, + ): MessageViewModel { + return MessageViewModel( + text = message ?: "", + isUpdateAnimated = throttling == null, + ) + } + + data class MessageViewModel( + val text: String, + + /** + * Whether updates to the message should be cross-animated from one message to another. + * + * If `false`, no animation should be applied, the message text should just be replaced + * instantly. + */ + val isUpdateAnimated: Boolean, + ) + @AssistedFactory interface Factory { fun create( diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/PasswordBouncerViewModel.kt b/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/PasswordBouncerViewModel.kt index c38fcaa3b657..55929b566cf1 100644 --- a/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/PasswordBouncerViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/PasswordBouncerViewModel.kt @@ -24,8 +24,11 @@ import kotlinx.coroutines.flow.asStateFlow /** Holds UI state and handles user input for the password bouncer UI. */ class PasswordBouncerViewModel( private val interactor: BouncerInteractor, - override val isInputEnabled: StateFlow<Boolean>, -) : AuthMethodBouncerViewModel { + isInputEnabled: StateFlow<Boolean>, +) : + AuthMethodBouncerViewModel( + isInputEnabled = isInputEnabled, + ) { private val _password = MutableStateFlow("") /** The password entered so far. */ @@ -47,7 +50,10 @@ class PasswordBouncerViewModel( /** Notifies that the user has pressed the key for attempting to authenticate the password. */ fun onAuthenticateKeyPressed() { - interactor.authenticate(password.value.toCharArray().toList()) + if (!interactor.authenticate(password.value.toCharArray().toList())) { + showFailureAnimation() + } + _password.value = "" } } diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/PatternBouncerViewModel.kt b/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/PatternBouncerViewModel.kt index 1b0b38ea6e9c..d9ef75db6103 100644 --- a/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/PatternBouncerViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/PatternBouncerViewModel.kt @@ -37,8 +37,11 @@ class PatternBouncerViewModel( private val applicationContext: Context, applicationScope: CoroutineScope, private val interactor: BouncerInteractor, - override val isInputEnabled: StateFlow<Boolean>, -) : AuthMethodBouncerViewModel { + isInputEnabled: StateFlow<Boolean>, +) : + AuthMethodBouncerViewModel( + isInputEnabled = isInputEnabled, + ) { /** The number of columns in the dot grid. */ val columnCount = 3 @@ -150,7 +153,11 @@ class PatternBouncerViewModel( /** Notifies that the user has ended the drag gesture across the dot grid. */ fun onDragEnd() { - interactor.authenticate(_selectedDots.value.map { it.toCoordinate() }) + val isSuccessfullyAuthenticated = + interactor.authenticate(_selectedDots.value.map { it.toCoordinate() }) + if (!isSuccessfullyAuthenticated) { + showFailureAnimation() + } _dots.value = defaultDots() _currentDot.value = 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 2a733d93b857..5c0fd92e7299 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 @@ -33,8 +33,11 @@ import kotlinx.coroutines.launch class PinBouncerViewModel( private val applicationScope: CoroutineScope, private val interactor: BouncerInteractor, - override val isInputEnabled: StateFlow<Boolean>, -) : AuthMethodBouncerViewModel { + isInputEnabled: StateFlow<Boolean>, +) : + AuthMethodBouncerViewModel( + isInputEnabled = isInputEnabled, + ) { private val entered = MutableStateFlow<List<Int>>(emptyList()) /** @@ -92,7 +95,10 @@ class PinBouncerViewModel( /** Notifies that the user clicked the "enter" button. */ fun onAuthenticateButtonClicked() { - interactor.authenticate(entered.value) + if (!interactor.authenticate(entered.value)) { + showFailureAnimation() + } + entered.value = emptyList() } diff --git a/packages/SystemUI/src/com/android/systemui/controls/management/ControlsListingControllerImpl.kt b/packages/SystemUI/src/com/android/systemui/controls/management/ControlsListingControllerImpl.kt index 8ba060e02c3d..1eba66765ce3 100644 --- a/packages/SystemUI/src/com/android/systemui/controls/management/ControlsListingControllerImpl.kt +++ b/packages/SystemUI/src/com/android/systemui/controls/management/ControlsListingControllerImpl.kt @@ -35,6 +35,7 @@ import com.android.systemui.dump.DumpManager import com.android.systemui.flags.FeatureFlags import com.android.systemui.flags.Flags import com.android.systemui.settings.UserTracker +import com.android.systemui.util.ActivityTaskManagerProxy import com.android.systemui.util.asIndenting import com.android.systemui.util.indentIfPossible import java.io.PrintWriter @@ -67,6 +68,7 @@ class ControlsListingControllerImpl @VisibleForTesting constructor( @Background private val backgroundExecutor: Executor, private val serviceListingBuilder: (Context) -> ServiceListing, private val userTracker: UserTracker, + private val activityTaskManagerProxy: ActivityTaskManagerProxy, dumpManager: DumpManager, private val featureFlags: FeatureFlags ) : ControlsListingController, Dumpable { @@ -76,9 +78,18 @@ class ControlsListingControllerImpl @VisibleForTesting constructor( context: Context, @Background executor: Executor, userTracker: UserTracker, + activityTaskManagerProxy: ActivityTaskManagerProxy, dumpManager: DumpManager, featureFlags: FeatureFlags - ) : this(context, executor, ::createServiceListing, userTracker, dumpManager, featureFlags) + ) : this( + context, + executor, + ::createServiceListing, + userTracker, + activityTaskManagerProxy, + dumpManager, + featureFlags + ) private var serviceListing = serviceListingBuilder(context) // All operations in background thread @@ -113,7 +124,8 @@ class ControlsListingControllerImpl @VisibleForTesting constructor( } private fun updateServices(newServices: List<ControlsServiceInfo>) { - if (featureFlags.isEnabled(Flags.USE_APP_PANELS)) { + if (featureFlags.isEnabled(Flags.USE_APP_PANELS) && + activityTaskManagerProxy.supportsMultiWindow(context)) { val allowAllApps = featureFlags.isEnabled(Flags.APP_PANELS_ALL_APPS_ALLOWED) newServices.forEach { it.resolvePanelActivity(allowAllApps) } 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/flags/Flags.kt b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt index d6c082928168..765299d7401f 100644 --- a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt +++ b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt @@ -138,7 +138,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/KeyguardService.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java index a8d22c48e709..5a8c2253b185 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java +++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java @@ -29,6 +29,7 @@ import static android.view.WindowManager.TRANSIT_OLD_KEYGUARD_OCCLUDE; import static android.view.WindowManager.TRANSIT_OLD_KEYGUARD_OCCLUDE_BY_DREAM; import static android.view.WindowManager.TRANSIT_OLD_KEYGUARD_UNOCCLUDE; import static android.view.WindowManager.TRANSIT_OLD_NONE; +import static android.view.WindowManager.TRANSIT_TO_BACK; import static android.view.WindowManager.TransitionFlags; import static android.view.WindowManager.TransitionOldType; import static android.view.WindowManager.TransitionType; @@ -49,6 +50,7 @@ import android.os.RemoteException; import android.os.Trace; import android.util.ArrayMap; import android.util.Log; +import android.util.RotationUtils; import android.util.Slog; import android.view.IRemoteAnimationFinishedCallback; import android.view.IRemoteAnimationRunner; @@ -73,6 +75,7 @@ import com.android.systemui.SystemUIApplication; import com.android.systemui.settings.DisplayTracker; import com.android.wm.shell.transition.ShellTransitions; import com.android.wm.shell.transition.Transitions; +import com.android.wm.shell.util.CounterRotator; import com.android.wm.shell.util.TransitionUtil; import java.util.ArrayList; @@ -103,7 +106,8 @@ public class KeyguardService extends Service { } private static RemoteAnimationTarget[] wrap(TransitionInfo info, boolean wallpapers, - SurfaceControl.Transaction t, ArrayMap<SurfaceControl, SurfaceControl> leashMap) { + SurfaceControl.Transaction t, ArrayMap<SurfaceControl, SurfaceControl> leashMap, + CounterRotator counterWallpaper) { final ArrayList<RemoteAnimationTarget> out = new ArrayList<>(); for (int i = 0; i < info.getChanges().size(); i++) { boolean changeIsWallpaper = @@ -133,6 +137,25 @@ public class KeyguardService extends Service { (change.getFlags() & TransitionInfo.FLAG_SHOW_WALLPAPER) != 0, info, t, leashMap); + if (changeIsWallpaper) { + int rotateDelta = RotationUtils.deltaRotation(change.getStartRotation(), + change.getEndRotation()); + if (rotateDelta != 0 && change.getParent() != null + && change.getMode() == TRANSIT_TO_BACK) { + final TransitionInfo.Change parent = info.getChange(change.getParent()); + if (parent != null) { + float displayW = parent.getEndAbsBounds().width(); + float displayH = parent.getEndAbsBounds().height(); + counterWallpaper.setup(t, parent.getLeash(), rotateDelta, displayW, + displayH); + } + if (counterWallpaper.getSurface() != null) { + t.setLayer(counterWallpaper.getSurface(), -1); + counterWallpaper.addChild(t, leashMap.get(change.getLeash())); + } + } + } + out.add(target); } return out.toArray(new RemoteAnimationTarget[out.size()]); @@ -163,6 +186,8 @@ public class KeyguardService extends Service { return new IRemoteTransition.Stub() { private final ArrayMap<SurfaceControl, SurfaceControl> mLeashMap = new ArrayMap<>(); + private final CounterRotator mCounterRotator = new CounterRotator(); + @GuardedBy("mLeashMap") private IRemoteTransitionFinishedCallback mFinishCallback = null; @@ -175,9 +200,9 @@ public class KeyguardService extends Service { synchronized (mLeashMap) { final RemoteAnimationTarget[] apps = - wrap(info, false /* wallpapers */, t, mLeashMap); + wrap(info, false /* wallpapers */, t, mLeashMap, mCounterRotator); final RemoteAnimationTarget[] wallpapers = - wrap(info, true /* wallpapers */, t, mLeashMap); + wrap(info, true /* wallpapers */, t, mLeashMap, mCounterRotator); final RemoteAnimationTarget[] nonApps = new RemoteAnimationTarget[0]; // Set alpha back to 1 for the independent changes because we will be animating @@ -231,11 +256,19 @@ public class KeyguardService extends Service { @GuardedBy("mLeashMap") private void finish() throws RemoteException { + SurfaceControl.Transaction finishTransaction = null; + if (mCounterRotator.getSurface() != null + && mCounterRotator.getSurface().isValid()) { + finishTransaction = new SurfaceControl.Transaction(); + mCounterRotator.cleanUp(finishTransaction); + } mLeashMap.clear(); final IRemoteTransitionFinishedCallback finishCallback = mFinishCallback; if (finishCallback != null) { mFinishCallback = null; - finishCallback.onTransitionFinished(null /* wct */, null /* t */); + finishCallback.onTransitionFinished(null /* wct */, finishTransaction); + } else if (finishTransaction != null) { + finishTransaction.apply(); } } }; diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java index 45e4623c7e27..3b40d8602e6f 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java +++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java @@ -1602,9 +1602,9 @@ public class KeyguardViewMediator implements CoreStartable, Dumpable, final ContentResolver cr = mContext.getContentResolver(); // From SecuritySettings - final long lockAfterTimeout = Settings.Secure.getInt(cr, + final long lockAfterTimeout = Settings.Secure.getIntForUser(cr, Settings.Secure.LOCK_SCREEN_LOCK_AFTER_TIMEOUT, - KEYGUARD_LOCK_AFTER_DELAY_DEFAULT); + KEYGUARD_LOCK_AFTER_DELAY_DEFAULT, userId); // From DevicePolicyAdmin final long policyTimeout = mLockPatternUtils.getDevicePolicyManager() @@ -1616,8 +1616,8 @@ public class KeyguardViewMediator implements CoreStartable, Dumpable, timeout = lockAfterTimeout; } else { // From DisplaySettings - long displayTimeout = Settings.System.getInt(cr, SCREEN_OFF_TIMEOUT, - KEYGUARD_DISPLAY_TIMEOUT_DELAY_DEFAULT); + long displayTimeout = Settings.System.getIntForUser(cr, SCREEN_OFF_TIMEOUT, + KEYGUARD_DISPLAY_TIMEOUT_DELAY_DEFAULT, userId); // policy in effect. Make sure we don't go beyond policy limit. displayTimeout = Math.max(displayTimeout, 0); // ignore negative values @@ -2469,7 +2469,10 @@ public class KeyguardViewMediator implements CoreStartable, Dumpable, private void playSound(int soundId) { if (soundId == 0) return; final ContentResolver cr = mContext.getContentResolver(); - if (Settings.System.getInt(cr, Settings.System.LOCKSCREEN_SOUNDS_ENABLED, 1) == 1) { + int lockscreenSoundsEnabled = Settings.System.getIntForUser(cr, + Settings.System.LOCKSCREEN_SOUNDS_ENABLED, 1, + KeyguardUpdateMonitor.getCurrentUser()); + if (lockscreenSoundsEnabled == 1) { mLockSounds.stop(mLockSoundStreamId); // Init mAudioManager diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardQuickAffordanceLocalUserSelectionManager.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardQuickAffordanceLocalUserSelectionManager.kt index 356a8fb65883..4dad17907aca 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardQuickAffordanceLocalUserSelectionManager.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardQuickAffordanceLocalUserSelectionManager.kt @@ -47,7 +47,7 @@ import kotlinx.coroutines.flow.onStart class KeyguardQuickAffordanceLocalUserSelectionManager @Inject constructor( - @Application context: Context, + @Application private val context: Context, private val userFileManager: UserFileManager, private val userTracker: UserTracker, broadcastDispatcher: BroadcastDispatcher, @@ -126,6 +126,11 @@ constructor( } override fun getSelections(): Map<String, List<String>> { + // If the custom shortcuts feature is not enabled, ignore prior selections and use defaults + if (!context.resources.getBoolean(R.bool.custom_lockscreen_shortcuts_enabled)) { + return defaults + } + val slotKeys = sharedPrefs.all.keys.filter { it.startsWith(KEY_PREFIX_SLOT) } val result = slotKeys diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/QuickAccessWalletKeyguardQuickAffordanceConfig.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/QuickAccessWalletKeyguardQuickAffordanceConfig.kt index 4ba2eb913742..12270784adca 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/QuickAccessWalletKeyguardQuickAffordanceConfig.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/QuickAccessWalletKeyguardQuickAffordanceConfig.kt @@ -18,7 +18,6 @@ package com.android.systemui.keyguard.data.quickaffordance import android.content.Context -import android.content.Intent import android.graphics.drawable.Drawable import android.service.quickaccesswallet.GetWalletCardsError import android.service.quickaccesswallet.GetWalletCardsResponse @@ -33,7 +32,6 @@ import com.android.systemui.common.shared.model.ContentDescription import com.android.systemui.common.shared.model.Icon import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Application -import com.android.systemui.keyguard.data.quickaffordance.KeyguardQuickAffordanceConfig.Companion.componentName import com.android.systemui.plugins.ActivityStarter import com.android.systemui.wallet.controller.QuickAccessWalletController import javax.inject.Inject @@ -103,17 +101,6 @@ constructor( !walletController.walletClient.isWalletServiceAvailable -> KeyguardQuickAffordanceConfig.PickerScreenState.UnavailableOnDevice !walletController.isWalletEnabled || queryCards().isEmpty() -> { - val componentName = - walletController.walletClient.createWalletSettingsIntent().toComponentName() - val actionText = - if (componentName != null) { - context.getString( - R.string.keyguard_affordance_enablement_dialog_action_template, - pickerName, - ) - } else { - null - } KeyguardQuickAffordanceConfig.PickerScreenState.Disabled( instructions = listOf( @@ -124,8 +111,6 @@ constructor( R.string.keyguard_affordance_enablement_dialog_wallet_instruction_2 ), ), - actionText = actionText, - actionComponentName = componentName, ) } else -> KeyguardQuickAffordanceConfig.PickerScreenState.Default() @@ -182,14 +167,6 @@ constructor( } } - private fun Intent?.toComponentName(): String? { - if (this == null) { - return null - } - - return componentName(packageName = `package`, action = action) - } - companion object { private const val TAG = "QuickAccessWalletKeyguardQuickAffordanceConfig" } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardClockRepository.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardClockRepository.kt index 641e20b4a3fc..16ad29aeb734 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardClockRepository.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardClockRepository.kt @@ -21,12 +21,17 @@ import android.provider.Settings import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Background import com.android.systemui.keyguard.shared.model.SettingsClockSize +import com.android.systemui.plugins.ClockId +import com.android.systemui.shared.clocks.ClockRegistry import com.android.systemui.util.settings.SecureSettings import com.android.systemui.util.settings.SettingsProxyExt.observerFlow import javax.inject.Inject import kotlinx.coroutines.CoroutineDispatcher +import kotlinx.coroutines.channels.awaitClose import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.callbackFlow import kotlinx.coroutines.flow.map +import kotlinx.coroutines.flow.mapNotNull import kotlinx.coroutines.flow.onStart import kotlinx.coroutines.withContext @@ -35,6 +40,7 @@ class KeyguardClockRepository @Inject constructor( private val secureSettings: SecureSettings, + private val clockRegistry: ClockRegistry, @Background private val backgroundDispatcher: CoroutineDispatcher, ) { @@ -47,6 +53,24 @@ constructor( .onStart { emit(Unit) } // Forces an initial update. .map { getClockSize() } + val currentClockId: Flow<ClockId> = + callbackFlow { + fun send() { + trySend(clockRegistry.currentClockId) + } + + val listener = + object : ClockRegistry.ClockChangeListener { + override fun onCurrentClockChanged() { + send() + } + } + clockRegistry.registerClockChangeListener(listener) + send() + awaitClose { clockRegistry.unregisterClockChangeListener(listener) } + } + .mapNotNull { it } + private suspend fun getClockSize(): SettingsClockSize { return withContext(backgroundDispatcher) { if ( diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardClockInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardClockInteractor.kt index 98f445c4419a..dad5831d6d4f 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardClockInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardClockInteractor.kt @@ -20,6 +20,7 @@ package com.android.systemui.keyguard.domain.interactor import com.android.systemui.dagger.SysUISingleton import com.android.systemui.keyguard.data.repository.KeyguardClockRepository import com.android.systemui.keyguard.shared.model.SettingsClockSize +import com.android.systemui.plugins.ClockId import javax.inject.Inject import kotlinx.coroutines.flow.Flow @@ -31,4 +32,6 @@ constructor( repository: KeyguardClockRepository, ) { val selectedClockSize: Flow<SettingsClockSize> = repository.selectedClockSize + + val currentClockId: Flow<ClockId> = repository.currentClockId } 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 c8d37a165a0e..a8d662c96284 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 @@ -406,19 +406,21 @@ object KeyguardBottomAreaViewBinder { view.isClickable = viewModel.isClickable if (viewModel.isClickable) { if (viewModel.useLongPress) { - view.setOnTouchListener( - KeyguardQuickAffordanceOnTouchListener( - view, - viewModel, - messageDisplayer, - vibratorHelper, - falsingManager, - ) + val onTouchListener = KeyguardQuickAffordanceOnTouchListener( + view, + viewModel, + messageDisplayer, + vibratorHelper, + falsingManager, ) + view.setOnTouchListener(onTouchListener) + view.onLongClickListener = + OnLongClickListener(falsingManager, viewModel, vibratorHelper, onTouchListener) } else { view.setOnClickListener(OnClickListener(viewModel, checkNotNull(falsingManager))) } } else { + view.onLongClickListener = null view.setOnClickListener(null) view.setOnTouchListener(null) } @@ -454,6 +456,42 @@ object KeyguardBottomAreaViewBinder { .start() } + private class OnLongClickListener( + private val falsingManager: FalsingManager?, + private val viewModel: KeyguardQuickAffordanceViewModel, + private val vibratorHelper: VibratorHelper?, + private val onTouchListener: KeyguardQuickAffordanceOnTouchListener + ) : View.OnLongClickListener { + override fun onLongClick(view: View): Boolean { + if (falsingManager?.isFalseLongTap(FalsingManager.MODERATE_PENALTY) == true) { + return true + } + + if (viewModel.configKey != null) { + viewModel.onClicked( + KeyguardQuickAffordanceViewModel.OnClickedParameters( + configKey = viewModel.configKey, + expandable = Expandable.fromView(view), + slotId = viewModel.slotId, + ) + ) + vibratorHelper?.vibrate( + if (viewModel.isActivated) { + KeyguardBottomAreaVibrations.Activated + } else { + KeyguardBottomAreaVibrations.Deactivated + } + ) + } + + onTouchListener.cancel() + return true + } + + override fun onLongClickUseDefaultHapticFeedback(view: View?) = false + + } + private class OnClickListener( private val viewModel: KeyguardQuickAffordanceViewModel, private val falsingManager: FalsingManager, diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardPreviewClockSmartspaceViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardPreviewClockViewBinder.kt index 57c32b3a56d9..1b5b329f3f7e 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardPreviewClockSmartspaceViewBinder.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardPreviewClockViewBinder.kt @@ -21,19 +21,17 @@ import android.view.View import androidx.core.view.isVisible import androidx.lifecycle.Lifecycle import androidx.lifecycle.repeatOnLifecycle -import com.android.systemui.keyguard.ui.viewmodel.KeyguardPreviewClockSmartspaceViewModel +import com.android.systemui.keyguard.ui.viewmodel.KeyguardPreviewClockViewModel import com.android.systemui.lifecycle.repeatWhenAttached -import kotlinx.coroutines.flow.collect -/** Binder for the small clock view, large clock view and smartspace. */ -object KeyguardPreviewClockSmartspaceViewBinder { +/** Binder for the small clock view, large clock view. */ +object KeyguardPreviewClockViewBinder { @JvmStatic fun bind( largeClockHostView: View, smallClockHostView: View, - smartspace: View?, - viewModel: KeyguardPreviewClockSmartspaceViewModel, + viewModel: KeyguardPreviewClockViewModel, ) { largeClockHostView.repeatWhenAttached { repeatOnLifecycle(Lifecycle.State.STARTED) { @@ -46,15 +44,5 @@ object KeyguardPreviewClockSmartspaceViewBinder { viewModel.isSmallClockVisible.collect { smallClockHostView.isVisible = it } } } - - smartspace?.repeatWhenAttached { - repeatOnLifecycle(Lifecycle.State.STARTED) { - viewModel.smartSpaceTopPadding.collect { smartspace.setTopPadding(it) } - } - } - } - - private fun View.setTopPadding(padding: Int) { - setPaddingRelative(paddingStart, padding, paddingEnd, paddingBottom) } } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardPreviewSmartspaceViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardPreviewSmartspaceViewBinder.kt new file mode 100644 index 000000000000..f5e4c6adcb91 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardPreviewSmartspaceViewBinder.kt @@ -0,0 +1,48 @@ +/* + * 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.view.View +import androidx.core.view.isInvisible +import androidx.lifecycle.Lifecycle +import androidx.lifecycle.repeatOnLifecycle +import com.android.systemui.keyguard.ui.viewmodel.KeyguardPreviewSmartspaceViewModel +import com.android.systemui.lifecycle.repeatWhenAttached +import kotlinx.coroutines.launch + +/** Binder for the small clock view, large clock view and smartspace. */ +object KeyguardPreviewSmartspaceViewBinder { + + @JvmStatic + fun bind( + smartspace: View, + viewModel: KeyguardPreviewSmartspaceViewModel, + ) { + smartspace.repeatWhenAttached { + repeatOnLifecycle(Lifecycle.State.STARTED) { + launch { viewModel.smartspaceTopPadding.collect { smartspace.setTopPadding(it) } } + + launch { viewModel.shouldHideSmartspace.collect { smartspace.isInvisible = it } } + } + } + } + + private fun View.setTopPadding(padding: Int) { + setPaddingRelative(paddingStart, padding, paddingEnd, paddingBottom) + } +} diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardQuickAffordanceOnTouchListener.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardQuickAffordanceOnTouchListener.kt index 5745d6ae077e..7685345805f4 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardQuickAffordanceOnTouchListener.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardQuickAffordanceOnTouchListener.kt @@ -46,7 +46,7 @@ class KeyguardQuickAffordanceOnTouchListener( @SuppressLint("ClickableViewAccessibility") override fun onTouch(v: View, event: MotionEvent): Boolean { return when (event.actionMasked) { - MotionEvent.ACTION_DOWN -> + MotionEvent.ACTION_DOWN -> { if (viewModel.configKey != null) { downDisplayCoords.set(event.rawX, event.rawY) if (isUsingAccurateTool(event)) { @@ -62,21 +62,10 @@ class KeyguardQuickAffordanceOnTouchListener( .scaleX(PRESSED_SCALE) .scaleY(PRESSED_SCALE) .setDuration(longPressDurationMs) - .withEndAction { - if ( - falsingManager?.isFalseLongTap( - FalsingManager.MODERATE_PENALTY - ) == false - ) { - dispatchClick(viewModel.configKey) - } - cancel() - } } - true - } else { - false } + false + } MotionEvent.ACTION_MOVE -> { if (!isUsingAccurateTool(event)) { // Moving too far while performing a long-press gesture cancels that @@ -91,7 +80,7 @@ class KeyguardQuickAffordanceOnTouchListener( cancel() } } - true + false } MotionEvent.ACTION_UP -> { if (isUsingAccurateTool(event)) { @@ -146,7 +135,7 @@ class KeyguardQuickAffordanceOnTouchListener( } ) } - true + false } MotionEvent.ACTION_CANCEL -> { cancel() @@ -179,7 +168,7 @@ class KeyguardQuickAffordanceOnTouchListener( view.setOnClickListener(null) } - private fun cancel(onAnimationEnd: Runnable? = null) { + fun cancel(onAnimationEnd: Runnable? = null) { longPressAnimator?.cancel() longPressAnimator = null view.animate().scaleX(1f).scaleY(1f).withEndAction(onAnimationEnd) 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 4308d843c27a..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 @@ -42,9 +42,12 @@ import com.android.systemui.biometrics.domain.interactor.UdfpsOverlayInteractor import com.android.systemui.broadcast.BroadcastDispatcher import com.android.systemui.dagger.qualifiers.Application import com.android.systemui.dagger.qualifiers.Main -import com.android.systemui.keyguard.ui.binder.KeyguardPreviewClockSmartspaceViewBinder +import com.android.systemui.keyguard.ui.binder.KeyguardPreviewClockViewBinder +import com.android.systemui.keyguard.ui.binder.KeyguardPreviewSmartspaceViewBinder import com.android.systemui.keyguard.ui.viewmodel.KeyguardBottomAreaViewModel -import com.android.systemui.keyguard.ui.viewmodel.KeyguardPreviewClockSmartspaceViewModel +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 @@ -65,7 +68,8 @@ constructor( @Application private val context: Context, @Main private val mainDispatcher: CoroutineDispatcher, @Main private val mainHandler: Handler, - private val clockSmartspaceViewModel: KeyguardPreviewClockSmartspaceViewModel, + private val clockViewModel: KeyguardPreviewClockViewModel, + private val smartspaceViewModel: KeyguardPreviewSmartspaceViewModel, private val bottomAreaViewModel: KeyguardBottomAreaViewModel, displayManager: DisplayManager, private val windowManager: WindowManager, @@ -88,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 @@ -97,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 @@ -129,16 +133,18 @@ constructor( setUpBottomArea(rootView) setUpSmartspace(rootView) + smartSpaceView?.let { + KeyguardPreviewSmartspaceViewBinder.bind(it, smartspaceViewModel) + } setUpUdfps(rootView) if (!shouldHideClock) { setUpClock(rootView) - KeyguardPreviewClockSmartspaceViewBinder.bind( + KeyguardPreviewClockViewBinder.bind( largeClockHostView, smallClockHostView, - smartSpaceView, - clockSmartspaceViewModel, + clockViewModel, ) } @@ -167,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) } } @@ -190,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 * @@ -219,8 +221,8 @@ constructor( smartSpaceView = lockscreenSmartspaceController.buildAndConnectDateView(parentView) val topPadding: Int = - KeyguardPreviewClockSmartspaceViewModel.getLargeClockSmartspaceTopPadding( - context.resources + KeyguardPreviewSmartspaceViewModel.getLargeClockSmartspaceTopPadding( + context.resources, ) val startPadding: Int = @@ -235,7 +237,7 @@ constructor( smartSpaceView?.let { it.setPaddingRelative(startPadding, topPadding, endPadding, 0) it.isClickable = false - + it.isInvisible = true parentView.addView( it, FrameLayout.LayoutParams( @@ -372,7 +374,7 @@ constructor( resources.getDimensionPixelSize(R.dimen.small_clock_height) ) layoutParams.topMargin = - KeyguardPreviewClockSmartspaceViewModel.getStatusBarHeight(resources) + + KeyguardPreviewSmartspaceViewModel.getStatusBarHeight(resources) + resources.getDimensionPixelSize(R.dimen.small_clock_padding_top) hostView.layoutParams = layoutParams @@ -390,13 +392,27 @@ 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) - - // Hide smart space if the clock has weather display; otherwise show it - hideSmartspace(clock.largeClock.config.hasCustomWeatherDataDisplay) } private fun updateLargeClock(clock: ClockController) { @@ -426,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/viewmodel/KeyguardPreviewClockViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardPreviewClockViewModel.kt new file mode 100644 index 000000000000..53013026670c --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardPreviewClockViewModel.kt @@ -0,0 +1,40 @@ +/* + * 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 android.content.Context +import com.android.systemui.dagger.qualifiers.Application +import com.android.systemui.keyguard.domain.interactor.KeyguardClockInteractor +import com.android.systemui.keyguard.shared.model.SettingsClockSize +import javax.inject.Inject +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.map + +/** View model for the small clock view, large clock view. */ +class KeyguardPreviewClockViewModel +@Inject +constructor( + @Application private val context: Context, + interactor: KeyguardClockInteractor, +) { + + val isLargeClockVisible: Flow<Boolean> = + interactor.selectedClockSize.map { it == SettingsClockSize.DYNAMIC } + + val isSmallClockVisible: Flow<Boolean> = + interactor.selectedClockSize.map { it == SettingsClockSize.SMALL } +} diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardPreviewClockSmartspaceViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardPreviewSmartspaceViewModel.kt index 00c603b04ccf..bf51976e27f0 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardPreviewClockSmartspaceViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardPreviewSmartspaceViewModel.kt @@ -24,23 +24,18 @@ import com.android.systemui.keyguard.domain.interactor.KeyguardClockInteractor import com.android.systemui.keyguard.shared.model.SettingsClockSize import javax.inject.Inject import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.map -/** View model for the small clock view, large clock view and smartspace. */ -class KeyguardPreviewClockSmartspaceViewModel +/** View model for the smartspace. */ +class KeyguardPreviewSmartspaceViewModel @Inject constructor( @Application private val context: Context, interactor: KeyguardClockInteractor, ) { - val isLargeClockVisible: Flow<Boolean> = - interactor.selectedClockSize.map { it == SettingsClockSize.DYNAMIC } - - val isSmallClockVisible: Flow<Boolean> = - interactor.selectedClockSize.map { it == SettingsClockSize.SMALL } - - val smartSpaceTopPadding: Flow<Int> = + val smartspaceTopPadding: Flow<Int> = interactor.selectedClockSize.map { when (it) { SettingsClockSize.DYNAMIC -> getLargeClockSmartspaceTopPadding(context.resources) @@ -48,6 +43,22 @@ constructor( } } + val shouldHideSmartspace: Flow<Boolean> = + combine( + interactor.selectedClockSize, + interactor.currentClockId, + ::Pair, + ) + .map { (size, currentClockId) -> + when (size) { + // TODO (b/284122375) This is temporary. We should use clockController + // .largeClock.config.hasCustomWeatherDataDisplay instead, but + // ClockRegistry.createCurrentClock is not reliable. + SettingsClockSize.DYNAMIC -> currentClockId == "DIGITAL_CLOCK_WEATHER" + SettingsClockSize.SMALL -> false + } + } + companion object { fun getLargeClockSmartspaceTopPadding(resources: Resources): Int { return with(resources) { diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaProjectionPermissionActivity.java b/packages/SystemUI/src/com/android/systemui/media/MediaProjectionPermissionActivity.java index c9c2ea27e5f4..f6a2f3704283 100644 --- a/packages/SystemUI/src/com/android/systemui/media/MediaProjectionPermissionActivity.java +++ b/packages/SystemUI/src/com/android/systemui/media/MediaProjectionPermissionActivity.java @@ -29,6 +29,7 @@ import android.annotation.Nullable; import android.app.Activity; import android.app.ActivityManager; import android.app.AlertDialog; +import android.content.Context; import android.content.DialogInterface; import android.content.Intent; import android.content.pm.ApplicationInfo; @@ -203,14 +204,17 @@ public class MediaProjectionPermissionActivity extends Activity dialogTitle = getString(R.string.media_projection_dialog_title, appName); } + // Using application context for the dialog, instead of the activity context, so we get + // the correct screen width when in split screen. + Context dialogContext = getApplicationContext(); if (isPartialScreenSharingEnabled()) { - mDialog = new MediaProjectionPermissionDialog(this, () -> { + mDialog = new MediaProjectionPermissionDialog(dialogContext, () -> { ScreenShareOption selectedOption = ((MediaProjectionPermissionDialog) mDialog).getSelectedScreenShareOption(); grantMediaProjectionPermission(selectedOption.getMode()); }, () -> finish(RECORD_CANCEL, /* projection= */ null), appName); } else { - AlertDialog.Builder dialogBuilder = new AlertDialog.Builder(this, + AlertDialog.Builder dialogBuilder = new AlertDialog.Builder(dialogContext, R.style.Theme_SystemUI_Dialog) .setTitle(dialogTitle) .setIcon(R.drawable.ic_media_projection_permission) @@ -263,7 +267,10 @@ public class MediaProjectionPermissionActivity extends Activity final UserHandle hostUserHandle = getHostUserHandle(); if (mScreenCaptureDevicePolicyResolver.get() .isScreenCaptureCompletelyDisabled(hostUserHandle)) { - AlertDialog dialog = new ScreenCaptureDisabledDialog(this); + // Using application context for the dialog, instead of the activity context, so we get + // the correct screen width when in split screen. + Context dialogContext = getApplicationContext(); + AlertDialog dialog = new ScreenCaptureDisabledDialog(dialogContext); setUpDialog(dialog); dialog.show(); return true; diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/pipeline/MediaDataManager.kt b/packages/SystemUI/src/com/android/systemui/media/controls/pipeline/MediaDataManager.kt index bce334610f28..6b993ce9e7bf 100644 --- a/packages/SystemUI/src/com/android/systemui/media/controls/pipeline/MediaDataManager.kt +++ b/packages/SystemUI/src/com/android/systemui/media/controls/pipeline/MediaDataManager.kt @@ -53,6 +53,7 @@ import android.text.TextUtils import android.util.Log import android.util.Pair as APair import androidx.media.utils.MediaConstants +import com.android.internal.annotations.Keep import com.android.internal.logging.InstanceId import com.android.keyguard.KeyguardUpdateMonitor import com.android.systemui.Dumpable @@ -219,7 +220,7 @@ class MediaDataManager( private val mediaEntries: LinkedHashMap<String, MediaData> = LinkedHashMap() // There should ONLY be at most one Smartspace media recommendation. var smartspaceMediaData: SmartspaceMediaData = EMPTY_SMARTSPACE_MEDIA_DATA - private var smartspaceSession: SmartspaceSession? = null + @Keep private var smartspaceSession: SmartspaceSession? = null private var allowMediaRecommendations = allowMediaRecommendations(context) private val artworkWidth = @@ -381,6 +382,8 @@ class MediaDataManager( fun destroy() { smartspaceMediaDataProvider.unregisterListener(this) + smartspaceSession?.close() + smartspaceSession = null context.unregisterReceiver(appChangeReceiver) } diff --git a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputAdapter.java b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputAdapter.java index 316c903eed5b..88ffa8da666b 100644 --- a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputAdapter.java +++ b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputAdapter.java @@ -28,6 +28,7 @@ import android.os.Build; import android.util.Log; import android.view.View; import android.view.ViewGroup; +import android.view.accessibility.AccessibilityNodeInfo; import android.widget.CheckBox; import android.widget.TextView; @@ -151,6 +152,7 @@ public class MediaOutputAdapter extends MediaOutputBaseAdapter { mCurrentActivePosition = -1; } mStatusIcon.setVisibility(View.GONE); + enableFocusPropertyForView(mContainerLayout); if (mController.isAnyDeviceTransferring()) { if (device.getState() == MediaDeviceState.STATE_CONNECTING @@ -250,7 +252,8 @@ public class MediaOutputAdapter extends MediaOutputBaseAdapter { mController.getColorItemContent()); updateGroupableCheckBox(true, isDeviceDeselectable, device); updateEndClickArea(device, isDeviceDeselectable); - setUpContentDescriptionForView(mContainerLayout, false, device); + disableFocusPropertyForView(mContainerLayout); + setUpContentDescriptionForView(mSeekBar, device); setSingleLineLayout(getItemTitle(device), true /* showSeekBar */, false /* showProgressBar */, true /* showCheckBox */, true /* showEndTouchArea */); @@ -274,7 +277,8 @@ public class MediaOutputAdapter extends MediaOutputBaseAdapter { mController.getDeselectableMediaDevice(), device); updateGroupableCheckBox(true, isDeviceDeselectable, device); updateEndClickArea(device, isDeviceDeselectable); - setUpContentDescriptionForView(mContainerLayout, false, device); + disableFocusPropertyForView(mContainerLayout); + setUpContentDescriptionForView(mSeekBar, device); setSingleLineLayout(getItemTitle(device), true /* showSeekBar */, false /* showProgressBar */, true /* showCheckBox */, true /* showEndTouchArea */); @@ -282,7 +286,8 @@ public class MediaOutputAdapter extends MediaOutputBaseAdapter { } else { updateTitleIcon(R.drawable.media_output_icon_volume, mController.getColorItemContent()); - setUpContentDescriptionForView(mContainerLayout, false, device); + disableFocusPropertyForView(mContainerLayout); + setUpContentDescriptionForView(mSeekBar, device); mCurrentActivePosition = position; setSingleLineLayout(getItemTitle(device), true /* showSeekBar */, false /* showProgressBar */, false /* showCheckBox */, @@ -390,7 +395,7 @@ public class MediaOutputAdapter extends MediaOutputBaseAdapter { View.IMPORTANT_FOR_ACCESSIBILITY_YES); mEndTouchArea.setBackgroundTintList( ColorStateList.valueOf(mController.getColorItemBackground())); - setUpContentDescriptionForView(mEndTouchArea, true, device); + setUpContentDescriptionForView(mEndTouchArea, device); } private void updateGroupableCheckBox(boolean isSelected, boolean isGroupable, @@ -471,14 +476,29 @@ public class MediaOutputAdapter extends MediaOutputBaseAdapter { notifyDataSetChanged(); } - private void setUpContentDescriptionForView(View view, boolean clickable, - MediaDevice device) { - view.setClickable(clickable); + private void disableFocusPropertyForView(View view) { + view.setFocusable(false); + view.setImportantForAccessibility(View.IMPORTANT_FOR_ACCESSIBILITY_NO); + } + + private void enableFocusPropertyForView(View view) { + view.setFocusable(true); + view.setImportantForAccessibility(View.IMPORTANT_FOR_ACCESSIBILITY_AUTO); + } + + private void setUpContentDescriptionForView(View view, MediaDevice device) { view.setContentDescription( mContext.getString(device.getDeviceType() == MediaDevice.MediaDeviceType.TYPE_BLUETOOTH_DEVICE ? R.string.accessibility_bluetooth_name : R.string.accessibility_cast_name, device.getName())); + view.setAccessibilityDelegate(new View.AccessibilityDelegate() { + public void onInitializeAccessibilityNodeInfo(View host, + AccessibilityNodeInfo info) { + super.onInitializeAccessibilityNodeInfo(host, info); + host.setOnClickListener(null); + } + }); } } diff --git a/packages/SystemUI/src/com/android/systemui/screenrecord/ScreenRecordPermissionDialog.kt b/packages/SystemUI/src/com/android/systemui/screenrecord/ScreenRecordPermissionDialog.kt index bfaf3d0d2ec5..604d44967e68 100644 --- a/packages/SystemUI/src/com/android/systemui/screenrecord/ScreenRecordPermissionDialog.kt +++ b/packages/SystemUI/src/com/android/systemui/screenrecord/ScreenRecordPermissionDialog.kt @@ -93,6 +93,7 @@ class ScreenRecordPermissionDialog( } dismiss() } + setCancelButtonOnClickListener { dismiss() } initRecordOptionsView() } diff --git a/packages/SystemUI/src/com/android/systemui/scrim/ScrimDrawable.java b/packages/SystemUI/src/com/android/systemui/scrim/ScrimDrawable.java index 10e2afe0baa9..9a1ffcbab8d1 100644 --- a/packages/SystemUI/src/com/android/systemui/scrim/ScrimDrawable.java +++ b/packages/SystemUI/src/com/android/systemui/scrim/ScrimDrawable.java @@ -203,7 +203,10 @@ public class ScrimDrawable extends Drawable { } public void setBottomEdgeRadius(float radius) { - mBottomEdgeRadius = radius; + if (mBottomEdgeRadius != radius) { + mBottomEdgeRadius = radius; + invalidateSelf(); + } } @Override diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java index 784a36092922..047fea123dc3 100644 --- a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java +++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java @@ -2206,6 +2206,7 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump // TODO: non-linearly transform progress fraction into squish amount (ease-in, linear out) mCurrentBackProgress = progressFraction; applyBackScaling(progressFraction); + mQsController.setClippingBounds(); } /** Resets back progress. */ diff --git a/packages/SystemUI/src/com/android/systemui/shade/QuickSettingsController.java b/packages/SystemUI/src/com/android/systemui/shade/QuickSettingsController.java index 8672260790c3..c42c2f4fa15a 100644 --- a/packages/SystemUI/src/com/android/systemui/shade/QuickSettingsController.java +++ b/packages/SystemUI/src/com/android/systemui/shade/QuickSettingsController.java @@ -114,6 +114,8 @@ import javax.inject.Inject; public class QuickSettingsController implements Dumpable { public static final String TAG = "QuickSettingsController"; + public static final int SHADE_BACK_ANIM_SCALE_MULTIPLIER = 100; + private QS mQs; private final Lazy<NotificationPanelViewController> mPanelViewControllerLazy; @@ -1128,7 +1130,7 @@ public class QuickSettingsController implements Dumpable { * Updates scrim bounds, QS clipping, notifications clipping and keyguard status view clipping * as well based on the bounds of the shade and QS state. */ - private void setClippingBounds() { + void setClippingBounds() { float qsExpansionFraction = computeExpansionFraction(); final int qsPanelBottomY = calculateBottomPosition(qsExpansionFraction); // Split shade has no QQS @@ -1216,7 +1218,10 @@ public class QuickSettingsController implements Dumpable { ? 0 : mScreenCornerRadius; radius = (int) MathUtils.lerp(screenCornerRadius, mScrimCornerRadius, Math.min(top / (float) mScrimCornerRadius, 1f)); - mScrimController.setNotificationBottomRadius(radius); + + float bottomRadius = mExpanded ? screenCornerRadius : + calculateBottomCornerRadius(screenCornerRadius); + mScrimController.setNotificationBottomRadius(bottomRadius); } if (isQsFragmentCreated()) { float qsTranslation = 0; @@ -1279,6 +1284,28 @@ public class QuickSettingsController implements Dumpable { nsslLeft, nsslTop, nsslRight, nsslBottom, topRadius, bottomRadius); } + /** + * Bottom corner radius should follow screen corner radius unless + * predictive back is running. We want a smooth transition from screen + * corner radius to scrim corner radius as the notification scrim is scaled down, + * but the transition should be brief enough to accommodate very short back gestures. + */ + @VisibleForTesting + int calculateBottomCornerRadius(float screenCornerRadius) { + return (int) MathUtils.lerp(screenCornerRadius, mScrimCornerRadius, + Math.min(calculateBottomRadiusProgress(), 1f)); + } + + @VisibleForTesting + float calculateBottomRadiusProgress() { + return (1 - mScrimController.getBackScaling()) * SHADE_BACK_ANIM_SCALE_MULTIPLIER; + } + + @VisibleForTesting + int getScrimCornerRadius() { + return mScrimCornerRadius; + } + void setDisplayInsets(int leftInset, int rightInset) { mDisplayLeftInset = leftInset; mDisplayRightInset = rightInset; 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 df1a47abea98..1827a46958f8 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ActivityStarterImpl.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ActivityStarterImpl.kt @@ -572,6 +572,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/ScrimController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java index fdb772bc6c13..25ecf1a424e0 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java @@ -602,6 +602,10 @@ public class ScrimController implements ViewTreeObserver.OnPreDrawListener, Dump mNotificationsScrim.setScaleY(scale); } + public float getBackScaling() { + return mNotificationsScrim.getScaleY(); + } + public void onTrackingStarted() { mDarkenWhileDragging = !mKeyguardStateController.canDismissLockScreen(); if (!mKeyguardUnlockAnimationController.isPlayingCannedUnlockAnimation()) { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java index 0414a14205d7..1bf63be3c8b0 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java @@ -495,6 +495,7 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb return mKeyguardStateController.isShowing() && !primaryBouncerIsOrWillBeShowing() + && !mKeyguardStateController.isKeyguardGoingAway() && isUserTrackingStarted && !hideBouncerOverDream && !mKeyguardStateController.isOccluded() 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/unfold/UnfoldTransitionModule.kt b/packages/SystemUI/src/com/android/systemui/unfold/UnfoldTransitionModule.kt index 2709da38a7d8..992b0221068c 100644 --- a/packages/SystemUI/src/com/android/systemui/unfold/UnfoldTransitionModule.kt +++ b/packages/SystemUI/src/com/android/systemui/unfold/UnfoldTransitionModule.kt @@ -18,16 +18,19 @@ package com.android.systemui.unfold import android.content.Context import android.hardware.devicestate.DeviceStateManager +import android.os.SystemProperties import com.android.systemui.dagger.qualifiers.Application import com.android.systemui.dagger.qualifiers.Main import com.android.systemui.keyguard.LifecycleScreenStatusProvider import com.android.systemui.unfold.config.UnfoldTransitionConfig import com.android.systemui.unfold.system.SystemUnfoldSharedModule +import com.android.systemui.unfold.updates.FoldProvider import com.android.systemui.unfold.updates.FoldStateProvider import com.android.systemui.unfold.updates.RotationChangeProvider import com.android.systemui.unfold.updates.screen.ScreenStatusProvider import com.android.systemui.unfold.util.NaturalRotationUnfoldProgressProvider import com.android.systemui.unfold.util.ScopedUnfoldTransitionProgressProvider +import com.android.systemui.unfold.util.UnfoldOnlyProgressProvider import com.android.systemui.unfold.util.UnfoldTransitionATracePrefix import com.android.systemui.util.time.SystemClockImpl import com.android.wm.shell.unfold.ShellUnfoldProgressProvider @@ -37,6 +40,7 @@ import dagger.Provides import java.util.Optional import java.util.concurrent.Executor import javax.inject.Named +import javax.inject.Provider import javax.inject.Singleton @Module(includes = [UnfoldSharedModule::class, SystemUnfoldSharedModule::class]) @@ -91,6 +95,18 @@ class UnfoldTransitionModule { } @Provides + @Singleton + @Named(UNFOLD_ONLY_PROVIDER) + fun provideUnfoldOnlyProvider( + foldProvider: FoldProvider, + @Main executor: Executor, + sourceProvider: Optional<UnfoldTransitionProgressProvider> + ): Optional<UnfoldTransitionProgressProvider> = + sourceProvider.map { provider -> + UnfoldOnlyProgressProvider(foldProvider, executor, provider) + } + + @Provides @Named(UNFOLD_STATUS_BAR) @Singleton fun provideStatusBarScopedTransitionProvider( @@ -102,16 +118,35 @@ class UnfoldTransitionModule { @Singleton fun provideShellProgressProvider( config: UnfoldTransitionConfig, - provider: Optional<UnfoldTransitionProgressProvider> - ): ShellUnfoldProgressProvider = - if (config.isEnabled && provider.isPresent) { - UnfoldProgressProvider(provider.get()) - } else { - ShellUnfoldProgressProvider.NO_PROVIDER - } + provider: Provider<Optional<UnfoldTransitionProgressProvider>>, + @Named(UNFOLD_ONLY_PROVIDER) + unfoldOnlyProvider: Provider<Optional<UnfoldTransitionProgressProvider>> + ): ShellUnfoldProgressProvider { + val resultingProvider = + if (config.isEnabled) { + // Return unfold only provider to the shell if we don't want to animate tasks during + // folding. Shell provider listeners are responsible for animating task bounds. + if (ENABLE_FOLD_TASK_ANIMATIONS) { + provider + } else { + unfoldOnlyProvider + } + } else { + null + } + + return resultingProvider?.get()?.orElse(null)?.let(::UnfoldProgressProvider) + ?: ShellUnfoldProgressProvider.NO_PROVIDER + } @Provides fun screenStatusProvider(impl: LifecycleScreenStatusProvider): ScreenStatusProvider = impl } const val UNFOLD_STATUS_BAR = "unfold_status_bar" +const val UNFOLD_ONLY_PROVIDER = "unfold_only_provider" + +// TODO: b/265764985 - tracking bug to clean-up the flag +// FeatureFlags are not accessible here because it's a global submodule (see GlobalModule.java) +private val ENABLE_FOLD_TASK_ANIMATIONS = + SystemProperties.getBoolean("persist.unfold.enable_fold_tasks_animation", false) diff --git a/packages/SystemUI/src/com/android/systemui/util/ActivityTaskManagerProxy.kt b/packages/SystemUI/src/com/android/systemui/util/ActivityTaskManagerProxy.kt new file mode 100644 index 000000000000..6e82cf673307 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/util/ActivityTaskManagerProxy.kt @@ -0,0 +1,30 @@ +/* + * 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.util + +import android.app.ActivityTaskManager +import android.content.Context +import com.android.systemui.dagger.SysUISingleton +import javax.inject.Inject + +/** Proxy for static calls to [ActivityTaskManager]. */ +@SysUISingleton +class ActivityTaskManagerProxy @Inject constructor() { + + /** Calls [ActivityTaskManager.supportsMultiWindow] */ + fun supportsMultiWindow(context: Context) = ActivityTaskManager.supportsMultiWindow(context) +} 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/KeyguardUpdateMonitorTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java index 2f72cb95db98..419d045885a8 100644 --- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java +++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java @@ -654,6 +654,24 @@ public class KeyguardUpdateMonitorTest extends SysuiTestCase { } @Test + public void serviceProvidersUpdated_broadcastTriggersInfoRefresh() { + // The callback is invoked once on init + verify(mTestCallback, times(1)).onRefreshCarrierInfo(); + + // WHEN the SERVICE_PROVIDERS_UPDATED broadcast is sent + Intent intent = new Intent(TelephonyManager.ACTION_SERVICE_PROVIDERS_UPDATED); + intent.putExtra(TelephonyManager.EXTRA_SPN, "spn"); + intent.putExtra(TelephonyManager.EXTRA_PLMN, "plmn"); + mKeyguardUpdateMonitor.mBroadcastReceiver.onReceive(getContext(), + putPhoneInfo(intent, null, true)); + mTestableLooper.processAllMessages(); + + // THEN verify keyguardUpdateMonitorCallback receives a refresh callback + // Note that we have times(2) here because it's been called once already + verify(mTestCallback, times(2)).onRefreshCarrierInfo(); + } + + @Test public void testTriesToAuthenticateFingerprint_whenKeyguard() { mKeyguardUpdateMonitor.dispatchStartedGoingToSleep(0 /* why */); mTestableLooper.processAllMessages(); 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 9f5c181c3129..374c28d6dce8 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 @@ -82,7 +82,7 @@ class BouncerInteractorTest : SysuiTestCase() { assertThat(message).isEqualTo(MESSAGE_ENTER_YOUR_PIN) // Wrong input. - underTest.authenticate(listOf(9, 8, 7)) + assertThat(underTest.authenticate(listOf(9, 8, 7))).isFalse() assertThat(message).isEqualTo(MESSAGE_WRONG_PIN) assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer)) @@ -90,7 +90,7 @@ class BouncerInteractorTest : SysuiTestCase() { assertThat(message).isEqualTo(MESSAGE_ENTER_YOUR_PIN) // Correct input. - underTest.authenticate(listOf(1, 2, 3, 4)) + assertThat(underTest.authenticate(listOf(1, 2, 3, 4))).isTrue() assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Gone)) } @@ -114,7 +114,7 @@ class BouncerInteractorTest : SysuiTestCase() { assertThat(message).isEqualTo(MESSAGE_ENTER_YOUR_PASSWORD) // Wrong input. - underTest.authenticate("alohamora".toList()) + assertThat(underTest.authenticate("alohamora".toList())).isFalse() assertThat(message).isEqualTo(MESSAGE_WRONG_PASSWORD) assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer)) @@ -122,7 +122,7 @@ class BouncerInteractorTest : SysuiTestCase() { assertThat(message).isEqualTo(MESSAGE_ENTER_YOUR_PASSWORD) // Correct input. - underTest.authenticate("password".toList()) + assertThat(underTest.authenticate("password".toList())).isTrue() assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Gone)) } @@ -146,9 +146,12 @@ class BouncerInteractorTest : SysuiTestCase() { assertThat(message).isEqualTo(MESSAGE_ENTER_YOUR_PATTERN) // Wrong input. - underTest.authenticate( - listOf(AuthenticationMethodModel.Pattern.PatternCoordinate(3, 4)) - ) + assertThat( + underTest.authenticate( + listOf(AuthenticationMethodModel.Pattern.PatternCoordinate(3, 4)) + ) + ) + .isFalse() assertThat(message).isEqualTo(MESSAGE_WRONG_PATTERN) assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer)) @@ -156,7 +159,7 @@ class BouncerInteractorTest : SysuiTestCase() { assertThat(message).isEqualTo(MESSAGE_ENTER_YOUR_PATTERN) // Correct input. - underTest.authenticate(emptyList()) + assertThat(underTest.authenticate(emptyList())).isTrue() assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Gone)) } @@ -214,7 +217,7 @@ class BouncerInteractorTest : SysuiTestCase() { assertThat(isUnlocked).isFalse() repeat(BouncerInteractor.THROTTLE_EVERY) { times -> // Wrong PIN. - underTest.authenticate(listOf(6, 7, 8, 9)) + assertThat(underTest.authenticate(listOf(6, 7, 8, 9))).isFalse() if (times < BouncerInteractor.THROTTLE_EVERY - 1) { assertThat(message).isEqualTo(MESSAGE_WRONG_PIN) } @@ -223,7 +226,7 @@ class BouncerInteractorTest : SysuiTestCase() { assertTryAgainMessage(message, BouncerInteractor.THROTTLE_DURATION_SEC) // Correct PIN, but throttled, so doesn't unlock: - underTest.authenticate(listOf(1, 2, 3, 4)) + assertThat(underTest.authenticate(listOf(1, 2, 3, 4))).isFalse() assertThat(isUnlocked).isFalse() assertTryAgainMessage(message, BouncerInteractor.THROTTLE_DURATION_SEC) @@ -241,7 +244,7 @@ class BouncerInteractorTest : SysuiTestCase() { assertThat(isUnlocked).isFalse() // Correct PIN and no longer throttled so unlocks: - underTest.authenticate(listOf(1, 2, 3, 4)) + assertThat(underTest.authenticate(listOf(1, 2, 3, 4))).isTrue() assertThat(isUnlocked).isTrue() } 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 new file mode 100644 index 000000000000..1642410e5a3f --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/AuthMethodBouncerViewModelTest.kt @@ -0,0 +1,81 @@ +/* + * 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.bouncer.ui.viewmodel + +import androidx.test.filters.SmallTest +import com.android.systemui.SysuiTestCase +import com.android.systemui.authentication.shared.model.AuthenticationMethodModel +import com.android.systemui.coroutines.collectLastValue +import com.android.systemui.scene.SceneTestUtils +import com.google.common.truth.Truth.assertThat +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.test.TestScope +import kotlinx.coroutines.test.runTest +import org.junit.Test +import org.junit.runner.RunWith +import org.junit.runners.JUnit4 + +@OptIn(ExperimentalCoroutinesApi::class) +@SmallTest +@RunWith(JUnit4::class) +class AuthMethodBouncerViewModelTest : SysuiTestCase() { + + private val testScope = TestScope() + private val utils = SceneTestUtils(this, testScope) + private val authenticationInteractor = + utils.authenticationInteractor( + utils.authenticationRepository(), + ) + private val underTest = + PinBouncerViewModel( + applicationScope = testScope.backgroundScope, + interactor = + utils.bouncerInteractor( + authenticationInteractor = authenticationInteractor, + sceneInteractor = utils.sceneInteractor(), + ), + isInputEnabled = MutableStateFlow(true), + ) + + @Test + fun animateFailure() = + testScope.runTest { + authenticationInteractor.setAuthenticationMethod(AuthenticationMethodModel.PIN(1234)) + val animateFailure by collectLastValue(underTest.animateFailure) + assertThat(animateFailure).isFalse() + + // Wrong PIN: + underTest.onPinButtonClicked(3) + underTest.onPinButtonClicked(4) + underTest.onPinButtonClicked(5) + underTest.onPinButtonClicked(6) + underTest.onAuthenticateButtonClicked() + assertThat(animateFailure).isTrue() + + underTest.onFailureAnimationShown() + assertThat(animateFailure).isFalse() + + // Correct PIN: + underTest.onPinButtonClicked(1) + underTest.onPinButtonClicked(2) + underTest.onPinButtonClicked(3) + underTest.onPinButtonClicked(4) + underTest.onAuthenticateButtonClicked() + 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 b942ccbb51f3..e8c946cdd59d 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 @@ -93,22 +93,21 @@ class BouncerViewModelTest : SysuiTestCase() { } @Test - fun isMessageUpdateAnimationsEnabled() = + fun message() = testScope.runTest { - val isMessageUpdateAnimationsEnabled by - collectLastValue(underTest.isMessageUpdateAnimationsEnabled) + val message by collectLastValue(underTest.message) val throttling by collectLastValue(bouncerInteractor.throttling) authenticationInteractor.setAuthenticationMethod(AuthenticationMethodModel.PIN(1234)) - assertThat(isMessageUpdateAnimationsEnabled).isTrue() + assertThat(message?.isUpdateAnimated).isTrue() repeat(BouncerInteractor.THROTTLE_EVERY) { // Wrong PIN. bouncerInteractor.authenticate(listOf(3, 4, 5, 6)) } - assertThat(isMessageUpdateAnimationsEnabled).isFalse() + assertThat(message?.isUpdateAnimated).isFalse() throttling?.totalDurationSec?.let { seconds -> advanceTimeBy(seconds * 1000L) } - assertThat(isMessageUpdateAnimationsEnabled).isTrue() + assertThat(message?.isUpdateAnimated).isTrue() } @Test diff --git a/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/PasswordBouncerViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/PasswordBouncerViewModelTest.kt index b7b90de3b54a..f436aa309ac5 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/PasswordBouncerViewModelTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/PasswordBouncerViewModelTest.kt @@ -85,7 +85,7 @@ class PasswordBouncerViewModelTest : SysuiTestCase() { underTest.onShown() - assertThat(message).isEqualTo(ENTER_YOUR_PASSWORD) + assertThat(message?.text).isEqualTo(ENTER_YOUR_PASSWORD) assertThat(password).isEqualTo("") assertThat(isUnlocked).isFalse() assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer)) @@ -109,7 +109,7 @@ class PasswordBouncerViewModelTest : SysuiTestCase() { underTest.onPasswordInputChanged("password") - assertThat(message).isEmpty() + assertThat(message?.text).isEmpty() assertThat(password).isEqualTo("password") assertThat(isUnlocked).isFalse() assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer)) @@ -156,7 +156,7 @@ class PasswordBouncerViewModelTest : SysuiTestCase() { underTest.onAuthenticateKeyPressed() assertThat(password).isEqualTo("") - assertThat(message).isEqualTo(WRONG_PASSWORD) + assertThat(message?.text).isEqualTo(WRONG_PASSWORD) assertThat(isUnlocked).isFalse() assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer)) } @@ -179,13 +179,13 @@ class PasswordBouncerViewModelTest : SysuiTestCase() { underTest.onPasswordInputChanged("wrong") underTest.onAuthenticateKeyPressed() assertThat(password).isEqualTo("") - assertThat(message).isEqualTo(WRONG_PASSWORD) + assertThat(message?.text).isEqualTo(WRONG_PASSWORD) assertThat(isUnlocked).isFalse() assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer)) // Enter the correct password: underTest.onPasswordInputChanged("password") - assertThat(message).isEmpty() + assertThat(message?.text).isEmpty() underTest.onAuthenticateKeyPressed() diff --git a/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/PatternBouncerViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/PatternBouncerViewModelTest.kt index b588ba2b2574..d7d7154705ed 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/PatternBouncerViewModelTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/PatternBouncerViewModelTest.kt @@ -89,7 +89,7 @@ class PatternBouncerViewModelTest : SysuiTestCase() { underTest.onShown() - assertThat(message).isEqualTo(ENTER_YOUR_PATTERN) + assertThat(message?.text).isEqualTo(ENTER_YOUR_PATTERN) assertThat(selectedDots).isEmpty() assertThat(currentDot).isNull() assertThat(isUnlocked).isFalse() @@ -115,7 +115,7 @@ class PatternBouncerViewModelTest : SysuiTestCase() { underTest.onDragStart() - assertThat(message).isEmpty() + assertThat(message?.text).isEmpty() assertThat(selectedDots).isEmpty() assertThat(currentDot).isNull() assertThat(isUnlocked).isFalse() @@ -202,7 +202,7 @@ class PatternBouncerViewModelTest : SysuiTestCase() { assertThat(selectedDots).isEmpty() assertThat(currentDot).isNull() - assertThat(message).isEqualTo(WRONG_PATTERN) + assertThat(message?.text).isEqualTo(WRONG_PATTERN) assertThat(isUnlocked).isFalse() assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer)) } @@ -235,7 +235,7 @@ class PatternBouncerViewModelTest : SysuiTestCase() { underTest.onDragEnd() assertThat(selectedDots).isEmpty() assertThat(currentDot).isNull() - assertThat(message).isEqualTo(WRONG_PATTERN) + assertThat(message?.text).isEqualTo(WRONG_PATTERN) assertThat(isUnlocked).isFalse() assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer)) 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 83f9687d7ac5..3bdaf0590888 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 @@ -94,7 +94,7 @@ class PinBouncerViewModelTest : SysuiTestCase() { underTest.onShown() - assertThat(message).isEqualTo(ENTER_YOUR_PIN) + assertThat(message?.text).isEqualTo(ENTER_YOUR_PIN) assertThat(pinLengths).isEqualTo(0 to 0) assertThat(isUnlocked).isFalse() assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer)) @@ -116,7 +116,7 @@ class PinBouncerViewModelTest : SysuiTestCase() { underTest.onPinButtonClicked(1) - assertThat(message).isEmpty() + assertThat(message?.text).isEmpty() assertThat(pinLengths).isEqualTo(0 to 1) assertThat(isUnlocked).isFalse() assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer)) @@ -140,7 +140,7 @@ class PinBouncerViewModelTest : SysuiTestCase() { underTest.onBackspaceButtonClicked() - assertThat(message).isEmpty() + assertThat(message?.text).isEmpty() assertThat(pinLengths).isEqualTo(1 to 0) assertThat(isUnlocked).isFalse() assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer)) @@ -170,7 +170,7 @@ class PinBouncerViewModelTest : SysuiTestCase() { advanceTimeBy(PinBouncerViewModel.BACKSPACE_LONG_PRESS_DELAY_MS) } - assertThat(message).isEmpty() + assertThat(message?.text).isEmpty() assertThat(pinLengths).isEqualTo(1 to 0) assertThat(isUnlocked).isFalse() assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer)) @@ -220,7 +220,7 @@ class PinBouncerViewModelTest : SysuiTestCase() { underTest.onAuthenticateButtonClicked() assertThat(pinLengths).isEqualTo(0 to 0) - assertThat(message).isEqualTo(WRONG_PIN) + assertThat(message?.text).isEqualTo(WRONG_PIN) assertThat(isUnlocked).isFalse() assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer)) } @@ -244,7 +244,7 @@ class PinBouncerViewModelTest : SysuiTestCase() { underTest.onPinButtonClicked(4) underTest.onPinButtonClicked(5) // PIN is now wrong! underTest.onAuthenticateButtonClicked() - assertThat(message).isEqualTo(WRONG_PIN) + assertThat(message?.text).isEqualTo(WRONG_PIN) assertThat(pinLengths).isEqualTo(0 to 0) assertThat(isUnlocked).isFalse() assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer)) @@ -254,7 +254,7 @@ class PinBouncerViewModelTest : SysuiTestCase() { underTest.onPinButtonClicked(2) underTest.onPinButtonClicked(3) underTest.onPinButtonClicked(4) - assertThat(message).isEmpty() + assertThat(message?.text).isEmpty() underTest.onAuthenticateButtonClicked() diff --git a/packages/SystemUI/tests/src/com/android/systemui/controls/management/ControlsListingControllerImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/controls/management/ControlsListingControllerImplTest.kt index 10bfc1b292d3..ee213f7c4257 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/controls/management/ControlsListingControllerImplTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/controls/management/ControlsListingControllerImplTest.kt @@ -39,6 +39,7 @@ import com.android.systemui.flags.FeatureFlags import com.android.systemui.flags.Flags.APP_PANELS_ALL_APPS_ALLOWED import com.android.systemui.flags.Flags.USE_APP_PANELS import com.android.systemui.settings.UserTracker +import com.android.systemui.util.ActivityTaskManagerProxy import com.android.systemui.util.concurrency.FakeExecutor import com.android.systemui.util.mockito.any import com.android.systemui.util.mockito.argThat @@ -88,6 +89,8 @@ class ControlsListingControllerImplTest : SysuiTestCase() { private lateinit var packageManager: PackageManager @Mock private lateinit var featureFlags: FeatureFlags + @Mock + private lateinit var activityTaskManagerProxy: ActivityTaskManagerProxy private var componentName = ComponentName("pkg", "class1") private var activityName = ComponentName("pkg", "activity") @@ -112,6 +115,7 @@ class ControlsListingControllerImplTest : SysuiTestCase() { // Return disabled by default `when`(packageManager.getComponentEnabledSetting(any())) .thenReturn(PackageManager.COMPONENT_ENABLED_STATE_DISABLED) + `when`(activityTaskManagerProxy.supportsMultiWindow(any())).thenReturn(true) mContext.setMockPackageManager(packageManager) mContext.orCreateTestableResources @@ -136,6 +140,7 @@ class ControlsListingControllerImplTest : SysuiTestCase() { executor, { mockSL }, userTracker, + activityTaskManagerProxy, dumpManager, featureFlags ) @@ -171,6 +176,7 @@ class ControlsListingControllerImplTest : SysuiTestCase() { exec, { mockServiceListing }, userTracker, + activityTaskManagerProxy, dumpManager, featureFlags ) @@ -637,7 +643,34 @@ class ControlsListingControllerImplTest : SysuiTestCase() { assertThat(services[0].serviceInfo.componentName).isEqualTo(componentName) } + @Test + fun testNoPanelIfMultiWindowNotSupported() { + `when`(activityTaskManagerProxy.supportsMultiWindow(any())).thenReturn(false) + + val serviceInfo = ServiceInfo( + componentName, + activityName + ) + + `when`(packageManager.getComponentEnabledSetting(eq(activityName))) + .thenReturn(PackageManager.COMPONENT_ENABLED_STATE_DEFAULT) + + setUpQueryResult(listOf( + ActivityInfo( + activityName, + enabled = true, + exported = true, + permission = Manifest.permission.BIND_CONTROLS + ) + )) + + val list = listOf(serviceInfo) + serviceListingCallbackCaptor.value.onServicesReloaded(list) + executor.runAllReady() + + assertNull(controller.getCurrentServices()[0].panelActivity) + } private fun ServiceInfo( componentName: ComponentName, 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/data/quickaffordance/KeyguardQuickAffordanceLegacySettingSyncerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardQuickAffordanceLegacySettingSyncerTest.kt index 9200d7219948..de3bb6f3d031 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardQuickAffordanceLegacySettingSyncerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardQuickAffordanceLegacySettingSyncerTest.kt @@ -72,6 +72,8 @@ class KeyguardQuickAffordanceLegacySettingSyncerTest : SysuiTestCase() { val resources: Resources = mock() whenever(resources.getStringArray(R.array.config_keyguardQuickAffordanceDefaults)) .thenReturn(emptyArray()) + whenever(resources.getBoolean(R.bool.custom_lockscreen_shortcuts_enabled)) + .thenReturn(true) whenever(context.resources).thenReturn(resources) testDispatcher = UnconfinedTestDispatcher() diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardQuickAffordanceLocalUserSelectionManagerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardQuickAffordanceLocalUserSelectionManagerTest.kt index bad4b360ce07..b2528c5b9832 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardQuickAffordanceLocalUserSelectionManagerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardQuickAffordanceLocalUserSelectionManagerTest.kt @@ -66,6 +66,7 @@ class KeyguardQuickAffordanceLocalUserSelectionManagerTest : SysuiTestCase() { @Before fun setUp() { MockitoAnnotations.initMocks(this) + overrideResource(R.bool.custom_lockscreen_shortcuts_enabled, true) sharedPrefs = mutableMapOf() whenever(userFileManager.getSharedPreferences(anyString(), anyInt(), anyInt())).thenAnswer { val userId = it.arguments[2] as Int @@ -86,6 +87,13 @@ class KeyguardQuickAffordanceLocalUserSelectionManagerTest : SysuiTestCase() { @After fun tearDown() { + mContext + .getOrCreateTestableResources() + .removeOverride(R.bool.custom_lockscreen_shortcuts_enabled) + mContext + .getOrCreateTestableResources() + .removeOverride(R.array.config_keyguardQuickAffordanceDefaults) + Dispatchers.resetMain() } @@ -358,6 +366,22 @@ class KeyguardQuickAffordanceLocalUserSelectionManagerTest : SysuiTestCase() { job.cancel() } + @Test + fun getSelections_alwaysReturnsDefaultsIfCustomShortcutsFeatureDisabled() { + overrideResource(R.bool.custom_lockscreen_shortcuts_enabled, false) + overrideResource( + R.array.config_keyguardQuickAffordanceDefaults, + arrayOf("leftTest:testShortcut1", "rightTest:testShortcut2") + ) + + assertThat(underTest.getSelections()).isEqualTo( + mapOf( + "leftTest" to listOf("testShortcut1"), + "rightTest" to listOf("testShortcut2"), + ) + ) + } + private fun assertSelections( observed: Map<String, List<String>>?, expected: Map<String, List<String>>, diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardQuickAffordanceRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardQuickAffordanceRepositoryTest.kt index 8dc04bd73b8e..ca7c5db478a1 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardQuickAffordanceRepositoryTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardQuickAffordanceRepositoryTest.kt @@ -47,6 +47,7 @@ import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.test.StandardTestDispatcher import kotlinx.coroutines.test.TestScope import kotlinx.coroutines.test.runTest +import org.junit.After import org.junit.Before import org.junit.Test import org.junit.runner.RunWith @@ -70,6 +71,7 @@ class KeyguardQuickAffordanceRepositoryTest : SysuiTestCase() { @Before fun setUp() { + overrideResource(R.bool.custom_lockscreen_shortcuts_enabled, true) context.resources.configuration.setLayoutDirection(Locale.US) config1 = FakeKeyguardQuickAffordanceConfig(FakeCustomizationProviderClient.AFFORDANCE_1) config2 = FakeKeyguardQuickAffordanceConfig(FakeCustomizationProviderClient.AFFORDANCE_2) @@ -137,6 +139,13 @@ class KeyguardQuickAffordanceRepositoryTest : SysuiTestCase() { ) } + @After + fun tearDown() { + mContext + .getOrCreateTestableResources() + .removeOverride(R.bool.custom_lockscreen_shortcuts_enabled) + } + @Test fun setSelections() = testScope.runTest { 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 5d2c3edd40af..895c1cd5a1a1 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 @@ -102,6 +102,8 @@ class KeyguardQuickAffordanceInteractorTest : SysuiTestCase() { fun setUp() { MockitoAnnotations.initMocks(this) + overrideResource(R.bool.custom_lockscreen_shortcuts_enabled, true) + repository = FakeKeyguardRepository() repository.setKeyguardShowing(true) @@ -200,7 +202,7 @@ class KeyguardQuickAffordanceInteractorTest : SysuiTestCase() { devicePolicyManager = devicePolicyManager, dockManager = dockManager, backgroundDispatcher = testDispatcher, - appContext = mContext, + appContext = context, ) } diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputAdapterTest.java b/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputAdapterTest.java index c89897c0233c..6d8c9b106881 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputAdapterTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputAdapterTest.java @@ -247,6 +247,20 @@ public class MediaOutputAdapterTest extends SysuiTestCase { } @Test + public void onBindViewHolder_bindConnectedRemoteDevice_verifyContentDescriptionNotNull() { + when(mMediaOutputController.getSelectableMediaDevice()).thenReturn( + ImmutableList.of(mMediaDevice2)); + when(mMediaOutputController.isCurrentConnectedDeviceRemote()).thenReturn(true); + mViewHolder = (MediaOutputAdapter.MediaDeviceViewHolder) mMediaOutputAdapter + .onCreateViewHolder(new LinearLayout(mContext), 0); + mMediaOutputAdapter.onBindViewHolder(mViewHolder, 0); + + assertThat(mViewHolder.mSeekBar.getContentDescription()).isNotNull(); + assertThat(mViewHolder.mSeekBar.getAccessibilityDelegate()).isNotNull(); + assertThat(mViewHolder.mContainerLayout.isFocusable()).isFalse(); + } + + @Test public void onBindViewHolder_bindSingleConnectedRemoteDevice_verifyView() { when(mMediaOutputController.getSelectableMediaDevice()).thenReturn( ImmutableList.of()); @@ -334,6 +348,7 @@ public class MediaOutputAdapterTest extends SysuiTestCase { assertThat(mViewHolder.mCheckBox.getVisibility()).isEqualTo(View.VISIBLE); assertThat(mViewHolder.mTitleText.getVisibility()).isEqualTo(View.VISIBLE); assertThat(mViewHolder.mTitleText.getText().toString()).isEqualTo(TEST_DEVICE_NAME_2); + assertThat(mViewHolder.mContainerLayout.isFocusable()).isTrue(); } @Test diff --git a/packages/SystemUI/tests/src/com/android/systemui/screenrecord/ScreenRecordPermissionDialogTest.kt b/packages/SystemUI/tests/src/com/android/systemui/screenrecord/ScreenRecordPermissionDialogTest.kt index 5b094c93ea9d..07feedf1d654 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/screenrecord/ScreenRecordPermissionDialogTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/screenrecord/ScreenRecordPermissionDialogTest.kt @@ -104,6 +104,26 @@ class ScreenRecordPermissionDialogTest : SysuiTestCase() { assertThat(visibility).isEqualTo(View.VISIBLE) } + @Test + fun showDialog_dialogIsShowing() { + dialog.show() + + assertThat(dialog.isShowing).isTrue() + } + + @Test + fun showDialog_cancelClicked_dialogIsDismissed() { + dialog.show() + + clickOnCancel() + + assertThat(dialog.isShowing).isFalse() + } + + private fun clickOnCancel() { + dialog.requireViewById<View>(android.R.id.button2).performClick() + } + private fun onSpinnerItemSelected(position: Int) { val spinner = dialog.requireViewById<Spinner>(R.id.screen_share_mode_spinner) spinner.onItemSelectedListener.onItemSelected(spinner, mock(), position, /* id= */ 0) diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/QuickSettingsControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/shade/QuickSettingsControllerTest.java index 34d09a912c87..ff047aa2faf2 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/shade/QuickSettingsControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/shade/QuickSettingsControllerTest.java @@ -580,6 +580,30 @@ public class QuickSettingsControllerTest extends SysuiTestCase { verify(mQs).setQsVisible(true); } + @Test + public void calculateBottomCornerRadius_scrimScaleMax() { + when(mScrimController.getBackScaling()).thenReturn(1.0f); + assertThat(mQsController.calculateBottomCornerRadius(0.0f)).isEqualTo(0); + } + + @Test + public void calculateBottomCornerRadius_scrimScaleMin() { + when(mScrimController.getBackScaling()) + .thenReturn(mNotificationPanelViewController.SHADE_BACK_ANIM_MIN_SCALE); + assertThat(mQsController.calculateBottomCornerRadius(0.0f)) + .isEqualTo(mQsController.getScrimCornerRadius()); + } + + @Test + public void calculateBottomCornerRadius_scrimScaleCutoff() { + float ratio = 1 / mQsController.calculateBottomRadiusProgress(); + float cutoffScale = 1 - mNotificationPanelViewController.SHADE_BACK_ANIM_MIN_SCALE / ratio; + when(mScrimController.getBackScaling()) + .thenReturn(cutoffScale); + assertThat(mQsController.calculateBottomCornerRadius(0.0f)) + .isEqualTo(mQsController.getScrimCornerRadius()); + } + private void lockScreen() { mQsController.setBarState(KEYGUARD); } diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java index e56f0d6b3b0f..3eea93c6ec2c 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java @@ -352,6 +352,17 @@ public class StatusBarKeyguardViewManagerTest extends SysuiTestCase { } @Test + public void onPanelExpansionChanged_neverTranslatesBouncerWhenGoingAway() { + when(mKeyguardStateController.isKeyguardGoingAway()).thenReturn(true); + mStatusBarKeyguardViewManager.onPanelExpansionChanged( + expansionEvent( + /* fraction= */ EXPANSION_VISIBLE, + /* expanded= */ true, + /* tracking= */ false)); + verify(mPrimaryBouncerInteractor, never()).setPanelExpansion(anyFloat()); + } + + @Test public void onPanelExpansionChanged_neverTranslatesBouncerWhenShowBouncer() { // Since KeyguardBouncer.EXPANSION_VISIBLE = 0 panel expansion, if the unlock is dismissing // the bouncer, there may be an onPanelExpansionChanged(0) call to collapse the panel diff --git a/packages/SystemUI/tests/src/com/android/systemui/unfold/TestUnfoldTransitionProvider.kt b/packages/SystemUI/tests/src/com/android/systemui/unfold/TestUnfoldTransitionProvider.kt index 4a28cd1de255..56c624565971 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/unfold/TestUnfoldTransitionProvider.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/unfold/TestUnfoldTransitionProvider.kt @@ -4,7 +4,7 @@ import com.android.systemui.unfold.UnfoldTransitionProgressProvider.TransitionPr class TestUnfoldTransitionProvider : UnfoldTransitionProgressProvider, TransitionProgressListener { - private val listeners = arrayListOf<TransitionProgressListener>() + private val listeners = mutableListOf<TransitionProgressListener>() override fun destroy() { listeners.clear() diff --git a/packages/SystemUI/tests/src/com/android/systemui/unfold/UnfoldHapticsPlayerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/unfold/UnfoldHapticsPlayerTest.kt index d3fdbd94a5ac..3dec45b4ff9f 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/unfold/UnfoldHapticsPlayerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/unfold/UnfoldHapticsPlayerTest.kt @@ -20,10 +20,9 @@ import android.os.Vibrator import android.testing.AndroidTestingRunner import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase -import com.android.systemui.unfold.updates.FoldProvider +import com.android.systemui.unfold.util.TestFoldProvider import com.android.systemui.util.mockito.any import com.android.systemui.util.mockito.mock -import java.util.concurrent.Executor import org.junit.Before import org.junit.Test import org.junit.runner.RunWith @@ -115,20 +114,4 @@ class UnfoldHapticsPlayerTest : SysuiTestCase() { verify(vibrator).vibrate(any<VibrationEffect>()) } - - private class TestFoldProvider : FoldProvider { - private val listeners = arrayListOf<FoldProvider.FoldCallback>() - - override fun registerCallback(callback: FoldProvider.FoldCallback, executor: Executor) { - listeners += callback - } - - override fun unregisterCallback(callback: FoldProvider.FoldCallback) { - listeners -= callback - } - - fun onFoldUpdate(isFolded: Boolean) { - listeners.forEach { it.onFoldUpdated(isFolded) } - } - } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/unfold/progress/TestUnfoldProgressListener.kt b/packages/SystemUI/tests/src/com/android/systemui/unfold/progress/TestUnfoldProgressListener.kt index e2aef31b4f10..e461e3f7fb1a 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/unfold/progress/TestUnfoldProgressListener.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/unfold/progress/TestUnfoldProgressListener.kt @@ -74,6 +74,11 @@ class TestUnfoldProgressListener : UnfoldTransitionProgressProvider.TransitionPr currentRecording?.assertLastProgress(progress) ?: error("unfold not in progress.") } + fun clear() { + currentRecording = null + recordings.clear() + } + class UnfoldTransitionRecording { private val progressHistory: MutableList<Float> = arrayListOf() private var finishingInvocations: Int = 0 diff --git a/packages/SystemUI/tests/src/com/android/systemui/unfold/util/TestFoldProvider.kt b/packages/SystemUI/tests/src/com/android/systemui/unfold/util/TestFoldProvider.kt new file mode 100644 index 000000000000..35df35ccfe9c --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/unfold/util/TestFoldProvider.kt @@ -0,0 +1,34 @@ +/* + * 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.unfold.util + +import com.android.systemui.unfold.updates.FoldProvider +import java.util.concurrent.Executor + +class TestFoldProvider : FoldProvider { + private val listeners = arrayListOf<FoldProvider.FoldCallback>() + + override fun registerCallback(callback: FoldProvider.FoldCallback, executor: Executor) { + listeners += callback + } + + override fun unregisterCallback(callback: FoldProvider.FoldCallback) { + listeners -= callback + } + + fun onFoldUpdate(isFolded: Boolean) { + listeners.forEach { it.onFoldUpdated(isFolded) } + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/unfold/util/UnfoldOnlyProgressProviderTest.kt b/packages/SystemUI/tests/src/com/android/systemui/unfold/util/UnfoldOnlyProgressProviderTest.kt new file mode 100644 index 000000000000..4a38fc069d9f --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/unfold/util/UnfoldOnlyProgressProviderTest.kt @@ -0,0 +1,128 @@ +/* + * 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.unfold.util + +import android.testing.AndroidTestingRunner +import androidx.test.filters.SmallTest +import com.android.systemui.SysuiTestCase +import com.android.systemui.unfold.TestUnfoldTransitionProvider +import com.android.systemui.unfold.progress.TestUnfoldProgressListener +import com.google.common.util.concurrent.MoreExecutors +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith + +@RunWith(AndroidTestingRunner::class) +@SmallTest +class UnfoldOnlyProgressProviderTest : SysuiTestCase() { + + private val listener = TestUnfoldProgressListener() + private val sourceProvider = TestUnfoldTransitionProvider() + + private val foldProvider = TestFoldProvider() + + private lateinit var progressProvider: UnfoldOnlyProgressProvider + + @Before + fun setUp() { + progressProvider = + UnfoldOnlyProgressProvider(foldProvider, MoreExecutors.directExecutor(), sourceProvider) + + progressProvider.addCallback(listener) + } + + @Test + fun unfolded_unfoldAnimationFinished_propagatesEvents() { + foldProvider.onFoldUpdate(isFolded = true) + foldProvider.onFoldUpdate(isFolded = false) + + // Unfold animation + sourceProvider.onTransitionStarted() + sourceProvider.onTransitionProgress(0.5f) + sourceProvider.onTransitionFinished() + + with(listener.ensureTransitionFinished()) { + assertLastProgress(0.5f) + } + } + + @Test + fun unfoldedWithAnimation_foldAnimation_doesNotPropagateEvents() { + foldProvider.onFoldUpdate(isFolded = true) + foldProvider.onFoldUpdate(isFolded = false) + // Unfold animation + sourceProvider.onTransitionStarted() + sourceProvider.onTransitionProgress(0.5f) + sourceProvider.onTransitionFinished() + listener.clear() + + // Fold animation + sourceProvider.onTransitionStarted() + sourceProvider.onTransitionProgress(0.2f) + sourceProvider.onTransitionFinished() + + listener.assertNotStarted() + } + + @Test + fun unfoldedWithAnimation_foldAnimationSeveralTimes_doesNotPropagateEvents() { + foldProvider.onFoldUpdate(isFolded = true) + foldProvider.onFoldUpdate(isFolded = false) + // Unfold animation + sourceProvider.onTransitionStarted() + sourceProvider.onTransitionProgress(0.5f) + sourceProvider.onTransitionFinished() + listener.clear() + + // Start and stop fold animation several times + repeat(3) { + sourceProvider.onTransitionStarted() + sourceProvider.onTransitionProgress(0.2f) + sourceProvider.onTransitionFinished() + } + + listener.assertNotStarted() + } + + @Test + fun unfoldedAgainAfterFolding_propagatesEvents() { + foldProvider.onFoldUpdate(isFolded = true) + foldProvider.onFoldUpdate(isFolded = false) + + // Unfold animation + sourceProvider.onTransitionStarted() + sourceProvider.onTransitionProgress(0.5f) + sourceProvider.onTransitionFinished() + + // Fold animation + sourceProvider.onTransitionStarted() + sourceProvider.onTransitionProgress(0.2f) + sourceProvider.onTransitionFinished() + foldProvider.onFoldUpdate(isFolded = true) + + listener.clear() + + // Second unfold animation after folding + foldProvider.onFoldUpdate(isFolded = false) + sourceProvider.onTransitionStarted() + sourceProvider.onTransitionProgress(0.1f) + sourceProvider.onTransitionFinished() + + with(listener.ensureTransitionFinished()) { + assertLastProgress(0.1f) + } + } +} diff --git a/packages/services/CameraExtensionsProxy/src/com/android/cameraextensions/CameraExtensionsProxyService.java b/packages/services/CameraExtensionsProxy/src/com/android/cameraextensions/CameraExtensionsProxyService.java index f31ca8178eb6..c2ebddf00fb4 100644 --- a/packages/services/CameraExtensionsProxy/src/com/android/cameraextensions/CameraExtensionsProxyService.java +++ b/packages/services/CameraExtensionsProxy/src/com/android/cameraextensions/CameraExtensionsProxyService.java @@ -2057,7 +2057,11 @@ public class CameraExtensionsProxyService extends Service { mIsImageValid = false; if (mGraphicBuffer != null) { - ImageReader.unlockGraphicBuffer(mGraphicBuffer); + try { + ImageReader.unlockGraphicBuffer(mGraphicBuffer); + } catch (RuntimeException e) { + e.printStackTrace(); + } mGraphicBuffer.destroy(); mGraphicBuffer = null; } diff --git a/services/autofill/java/com/android/server/autofill/AutofillManagerService.java b/services/autofill/java/com/android/server/autofill/AutofillManagerService.java index fc758cba617f..1a57bc1b7a0d 100644 --- a/services/autofill/java/com/android/server/autofill/AutofillManagerService.java +++ b/services/autofill/java/com/android/server/autofill/AutofillManagerService.java @@ -761,6 +761,18 @@ public final class AutofillManagerService } // Called by Shell command + boolean isFieldDetectionServiceEnabledForUser(@UserIdInt int userId) { + enforceCallingPermissionForManagement(); + synchronized (mLock) { + final AutofillManagerServiceImpl service = getServiceForUserLocked(userId); + if (service != null) { + return service.isPccClassificationEnabled(); + } + } + return false; + } + + // Called by Shell command String getFieldDetectionServiceName(@UserIdInt int userId) { enforceCallingPermissionForManagement(); return mFieldClassificationResolver.readServiceName(userId); diff --git a/services/autofill/java/com/android/server/autofill/AutofillManagerServiceShellCommand.java b/services/autofill/java/com/android/server/autofill/AutofillManagerServiceShellCommand.java index cd6de8749f47..c66fb81f51c6 100644 --- a/services/autofill/java/com/android/server/autofill/AutofillManagerServiceShellCommand.java +++ b/services/autofill/java/com/android/server/autofill/AutofillManagerServiceShellCommand.java @@ -26,7 +26,6 @@ import android.os.RemoteCallback; import android.os.ShellCommand; import android.os.UserHandle; import android.service.autofill.AutofillFieldClassificationService.Scores; -import android.text.TextUtils; import android.view.autofill.AutofillManager; import com.android.internal.os.IResultReceiver; @@ -348,9 +347,7 @@ public final class AutofillManagerServiceShellCommand extends ShellCommand { private int isFieldDetectionServiceEnabled(PrintWriter pw) { final int userId = getNextIntArgRequired(); - String name = mService.getFieldDetectionServiceName(userId); - boolean pccFlagEnabled = mService.isPccClassificationFlagEnabled(); - boolean enabled = (!TextUtils.isEmpty(name)) && pccFlagEnabled; + boolean enabled = mService.isFieldDetectionServiceEnabledForUser(userId); pw.println(enabled); return 0; } diff --git a/services/autofill/java/com/android/server/autofill/InlineSuggestionRequestConsumer.java b/services/autofill/java/com/android/server/autofill/InlineSuggestionRequestConsumer.java new file mode 100644 index 000000000000..a3efb25a5735 --- /dev/null +++ b/services/autofill/java/com/android/server/autofill/InlineSuggestionRequestConsumer.java @@ -0,0 +1,55 @@ +/* + * 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.autofill; + +import android.util.Slog; +import android.view.inputmethod.InlineSuggestionsRequest; + +import java.lang.ref.WeakReference; +import java.util.function.Consumer; + +class InlineSuggestionRequestConsumer implements Consumer<InlineSuggestionsRequest> { + + static final String TAG = "InlineSuggestionRequestConsumer"; + + private final WeakReference<Session.AssistDataReceiverImpl> mAssistDataReceiverWeakReference; + private final WeakReference<ViewState> mViewStateWeakReference; + + InlineSuggestionRequestConsumer(WeakReference<Session.AssistDataReceiverImpl> + assistDataReceiverWeakReference, + WeakReference<ViewState> viewStateWeakReference) { + mAssistDataReceiverWeakReference = assistDataReceiverWeakReference; + mViewStateWeakReference = viewStateWeakReference; + } + + @Override + public void accept(InlineSuggestionsRequest inlineSuggestionsRequest) { + Session.AssistDataReceiverImpl assistDataReceiver = mAssistDataReceiverWeakReference.get(); + ViewState viewState = mViewStateWeakReference.get(); + if (assistDataReceiver == null) { + Slog.wtf(TAG, "assistDataReceiver is null when accepting new inline suggestion" + + "requests"); + return; + } + + if (viewState == null) { + Slog.wtf(TAG, "view state is null when accepting new inline suggestion requests"); + return; + } + assistDataReceiver.handleInlineSuggestionRequest(inlineSuggestionsRequest, viewState); + } +} diff --git a/services/autofill/java/com/android/server/autofill/Session.java b/services/autofill/java/com/android/server/autofill/Session.java index 44c50334649f..4576abb4a730 100644 --- a/services/autofill/java/com/android/server/autofill/Session.java +++ b/services/autofill/java/com/android/server/autofill/Session.java @@ -323,7 +323,7 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState * Id of the View currently being displayed. */ @GuardedBy("mLock") - @Nullable AutofillId mCurrentViewId; + private @Nullable AutofillId mCurrentViewId; @GuardedBy("mLock") private IAutoFillManagerClient mClient; @@ -614,7 +614,7 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState * TODO(b/151867668): improve how asynchronous data dependencies are handled, without using * CountDownLatch. */ - private final class AssistDataReceiverImpl extends IAssistDataReceiver.Stub { + final class AssistDataReceiverImpl extends IAssistDataReceiver.Stub { @GuardedBy("mLock") private boolean mWaitForInlineRequest; @GuardedBy("mLock") @@ -629,17 +629,28 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState mPendingFillRequest = null; mWaitForInlineRequest = isInlineRequest; mPendingInlineSuggestionsRequest = null; - return isInlineRequest ? (inlineSuggestionsRequest) -> { - synchronized (mLock) { - if (!mWaitForInlineRequest || mPendingInlineSuggestionsRequest != null) { - return; - } - mWaitForInlineRequest = inlineSuggestionsRequest != null; - mPendingInlineSuggestionsRequest = inlineSuggestionsRequest; - maybeRequestFillLocked(); - viewState.resetState(ViewState.STATE_PENDING_CREATE_INLINE_REQUEST); + if (isInlineRequest) { + WeakReference<AssistDataReceiverImpl> assistDataReceiverWeakReference = + new WeakReference<AssistDataReceiverImpl>(this); + WeakReference<ViewState> viewStateWeakReference = + new WeakReference<ViewState>(viewState); + return new InlineSuggestionRequestConsumer(assistDataReceiverWeakReference, + viewStateWeakReference); + } + return null; + } + + void handleInlineSuggestionRequest(InlineSuggestionsRequest inlineSuggestionsRequest, + ViewState viewState) { + synchronized (mLock) { + if (!mWaitForInlineRequest || mPendingInlineSuggestionsRequest != null) { + return; } - } : null; + mWaitForInlineRequest = inlineSuggestionsRequest != null; + mPendingInlineSuggestionsRequest = inlineSuggestionsRequest; + maybeRequestFillLocked(); + viewState.resetState(ViewState.STATE_PENDING_CREATE_INLINE_REQUEST); + } } @GuardedBy("mLock") diff --git a/services/core/java/com/android/server/am/ActiveServices.java b/services/core/java/com/android/server/am/ActiveServices.java index 31d60f25ef24..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. @@ -3797,7 +3678,9 @@ public final class ActiveServices { } clientPsr.addConnection(c); c.startAssociationIfNeeded(); - if (c.hasFlag(Context.BIND_ABOVE_CLIENT)) { + // Don't set hasAboveClient if binding to self to prevent modifyRawOomAdj() from + // dropping the process' adjustment level. + if (b.client != s.app && c.hasFlag(Context.BIND_ABOVE_CLIENT)) { clientPsr.setHasAboveClient(true); } if (c.hasFlag(BIND_ALLOW_WHITELIST_MANAGEMENT)) { @@ -3837,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; @@ -7552,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; @@ -7578,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); } @@ -7666,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, @@ -7731,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 3d02c96ae386..1bd0675e95b6 100644 --- a/services/core/java/com/android/server/am/ActivityManagerService.java +++ b/services/core/java/com/android/server/am/ActivityManagerService.java @@ -3393,7 +3393,7 @@ public class ActivityManagerService extends IActivityManager.Stub mProcessList.noteAppKill(app, ApplicationExitInfo.REASON_OTHER, ApplicationExitInfo.SUBREASON_UNKNOWN, reason); } - ProcessList.killProcessGroup(app.uid, pid); + app.killProcessGroupIfNecessaryLocked(true); synchronized (mProcLock) { app.setKilled(true); } @@ -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/OomAdjuster.java b/services/core/java/com/android/server/am/OomAdjuster.java index 85a01851187e..6c3f01e6cd3a 100644 --- a/services/core/java/com/android/server/am/OomAdjuster.java +++ b/services/core/java/com/android/server/am/OomAdjuster.java @@ -3253,7 +3253,11 @@ public class OomAdjuster { // {@link SCHED_GROUP_TOP_APP}. We don't check render thread because it // is not ready when attaching. app.getWindowProcessController().onTopProcChanged(); - setThreadPriority(app.getPid(), THREAD_PRIORITY_TOP_APP_BOOST); + if (mService.mUseFifoUiScheduling) { + mService.scheduleAsFifoPriority(app.getPid(), true); + } else { + setThreadPriority(app.getPid(), THREAD_PRIORITY_TOP_APP_BOOST); + } initialSchedGroup = SCHED_GROUP_TOP_APP; } catch (Exception e) { Slog.w(TAG, "Failed to pre-set top priority to " + app + " " + e); diff --git a/services/core/java/com/android/server/am/ProcessRecord.java b/services/core/java/com/android/server/am/ProcessRecord.java index 335d6768c37b..d6495c78574c 100644 --- a/services/core/java/com/android/server/am/ProcessRecord.java +++ b/services/core/java/com/android/server/am/ProcessRecord.java @@ -1197,26 +1197,7 @@ class ProcessRecord implements WindowProcessListener { EventLog.writeEvent(EventLogTags.AM_KILL, userId, mPid, processName, mState.getSetAdj(), reason); Process.killProcessQuiet(mPid); - final boolean killProcessGroup; - if (mHostingRecord != null - && (mHostingRecord.usesWebviewZygote() || mHostingRecord.usesAppZygote())) { - synchronized (ProcessRecord.this) { - killProcessGroup = mProcessGroupCreated; - if (!killProcessGroup) { - // The process group hasn't been created, request to skip it. - mSkipProcessGroupCreation = true; - } - } - } else { - killProcessGroup = true; - } - if (killProcessGroup) { - if (asyncKPG) { - ProcessList.killProcessGroup(uid, mPid); - } else { - Process.sendSignalToProcessGroup(uid, mPid, OsConstants.SIGKILL); - } - } + killProcessGroupIfNecessaryLocked(asyncKPG); } else { mPendingStart = false; } @@ -1231,6 +1212,30 @@ class ProcessRecord implements WindowProcessListener { } } + @GuardedBy("mService") + void killProcessGroupIfNecessaryLocked(boolean async) { + final boolean killProcessGroup; + if (mHostingRecord != null + && (mHostingRecord.usesWebviewZygote() || mHostingRecord.usesAppZygote())) { + synchronized (ProcessRecord.this) { + killProcessGroup = mProcessGroupCreated; + if (!killProcessGroup) { + // The process group hasn't been created, request to skip it. + mSkipProcessGroupCreation = true; + } + } + } else { + killProcessGroup = true; + } + if (killProcessGroup) { + if (async) { + ProcessList.killProcessGroup(uid, mPid); + } else { + Process.sendSignalToProcessGroup(uid, mPid, OsConstants.SIGKILL); + } + } + } + @Override public void dumpDebug(ProtoOutputStream proto, long fieldId) { dumpDebug(proto, fieldId, -1); diff --git a/services/core/java/com/android/server/am/ProcessServiceRecord.java b/services/core/java/com/android/server/am/ProcessServiceRecord.java index 81d0b6ac700b..7ff6d116baaf 100644 --- a/services/core/java/com/android/server/am/ProcessServiceRecord.java +++ b/services/core/java/com/android/server/am/ProcessServiceRecord.java @@ -341,7 +341,8 @@ final class ProcessServiceRecord { mHasAboveClient = false; for (int i = mConnections.size() - 1; i >= 0; i--) { ConnectionRecord cr = mConnections.valueAt(i); - if (cr.hasFlag(Context.BIND_ABOVE_CLIENT)) { + if (cr.binding.service.app.mServices != this + && cr.hasFlag(Context.BIND_ABOVE_CLIENT)) { mHasAboveClient = true; 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/am/UserController.java b/services/core/java/com/android/server/am/UserController.java index 12bb5d23e4cf..76a994ec63e9 100644 --- a/services/core/java/com/android/server/am/UserController.java +++ b/services/core/java/com/android/server/am/UserController.java @@ -151,6 +151,8 @@ import java.util.Arrays; import java.util.Iterator; import java.util.List; import java.util.Objects; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicInteger; import java.util.function.Consumer; @@ -1707,7 +1709,8 @@ class UserController implements Handler.Callback { mInjector.getWindowManager().setSwitchingUser(true); // Only lock if the user has a secure keyguard PIN/Pattern/Pwd if (mInjector.getKeyguardManager().isDeviceSecure(userId)) { - mInjector.getWindowManager().lockNow(null); + // Make sure the device is locked before moving on with the user switch + mInjector.lockDeviceNowAndWaitForKeyguardShown(); } } @@ -3444,6 +3447,11 @@ class UserController implements Handler.Callback { WindowManagerService getWindowManager() { return mService.mWindowManager; } + + ActivityTaskManagerInternal getActivityTaskManagerInternal() { + return mService.mAtmInternal; + } + void activityManagerOnUserStopped(@UserIdInt int userId) { LocalServices.getService(ActivityTaskManagerInternal.class).onUserStopped(userId); } @@ -3667,5 +3675,43 @@ class UserController implements Handler.Callback { void onSystemUserVisibilityChanged(boolean visible) { getUserManagerInternal().onSystemUserVisibilityChanged(visible); } + + void lockDeviceNowAndWaitForKeyguardShown() { + if (getWindowManager().isKeyguardLocked()) { + return; + } + + final TimingsTraceAndSlog t = new TimingsTraceAndSlog(); + t.traceBegin("lockDeviceNowAndWaitForKeyguardShown"); + + final CountDownLatch latch = new CountDownLatch(1); + ActivityTaskManagerInternal.ScreenObserver screenObserver = + new ActivityTaskManagerInternal.ScreenObserver() { + @Override + public void onAwakeStateChanged(boolean isAwake) { + + } + + @Override + public void onKeyguardStateChanged(boolean isShowing) { + if (isShowing) { + latch.countDown(); + } + } + }; + + getActivityTaskManagerInternal().registerScreenObserver(screenObserver); + getWindowManager().lockDeviceNow(); + try { + if (!latch.await(20, TimeUnit.SECONDS)) { + throw new RuntimeException("Keyguard is not shown in 20 seconds"); + } + } catch (InterruptedException e) { + throw new RuntimeException(e); + } finally { + getActivityTaskManagerInternal().unregisterScreenObserver(screenObserver); + t.traceEnd(); + } + } } } diff --git a/services/core/java/com/android/server/biometrics/log/BiometricContextProvider.java b/services/core/java/com/android/server/biometrics/log/BiometricContextProvider.java index 21ade1bbbc4a..fc3d7c8114b0 100644 --- a/services/core/java/com/android/server/biometrics/log/BiometricContextProvider.java +++ b/services/core/java/com/android/server/biometrics/log/BiometricContextProvider.java @@ -156,7 +156,7 @@ public final class BiometricContextProvider implements BiometricContext { @Override public OperationContextExt updateContext(@NonNull OperationContextExt operationContext, boolean isCryptoOperation) { - return operationContext.update(this); + return operationContext.update(this, isCryptoOperation); } @Nullable @@ -238,7 +238,7 @@ public final class BiometricContextProvider implements BiometricContext { private void notifySubscribers() { mSubscribers.forEach((context, consumer) -> { - consumer.accept(context.update(this).toAidlContext()); + consumer.accept(context.update(this, context.isCrypto()).toAidlContext()); }); } diff --git a/services/core/java/com/android/server/biometrics/log/OperationContextExt.java b/services/core/java/com/android/server/biometrics/log/OperationContextExt.java index 4d821e9819b4..4a10e8e428a3 100644 --- a/services/core/java/com/android/server/biometrics/log/OperationContextExt.java +++ b/services/core/java/com/android/server/biometrics/log/OperationContextExt.java @@ -241,9 +241,10 @@ public class OperationContextExt { } /** Update this object with the latest values from the given context. */ - OperationContextExt update(@NonNull BiometricContext biometricContext) { + OperationContextExt update(@NonNull BiometricContext biometricContext, boolean isCrypto) { mAidlContext.isAod = biometricContext.isAod(); mAidlContext.displayState = toAidlDisplayState(biometricContext.getDisplayState()); + mAidlContext.isCrypto = isCrypto; setFirstSessionId(biometricContext); mIsDisplayOn = biometricContext.isDisplayOn(); diff --git a/services/core/java/com/android/server/biometrics/sensors/BiometricScheduler.java b/services/core/java/com/android/server/biometrics/sensors/BiometricScheduler.java index b474cad962c3..aa5f9fa96875 100644 --- a/services/core/java/com/android/server/biometrics/sensors/BiometricScheduler.java +++ b/services/core/java/com/android/server/biometrics/sensors/BiometricScheduler.java @@ -32,6 +32,7 @@ import android.util.Slog; import android.util.proto.ProtoOutputStream; import com.android.internal.annotations.VisibleForTesting; +import com.android.modules.expresslog.Counter; import com.android.server.biometrics.BiometricSchedulerProto; import com.android.server.biometrics.BiometricsProto; import com.android.server.biometrics.sensors.fingerprint.GestureAvailabilityDispatcher; @@ -569,9 +570,10 @@ public class BiometricScheduler { if (mCurrentOperation == null) { return; } - final BiometricSchedulerOperation mOperation = mCurrentOperation; + final BiometricSchedulerOperation operation = mCurrentOperation; mHandler.postDelayed(() -> { - if (mOperation == mCurrentOperation) { + if (operation == mCurrentOperation) { + Counter.logIncrement("biometric.scheduler_watchdog_triggered_count"); clearScheduler(); } }, 10000); diff --git a/services/core/java/com/android/server/biometrics/sensors/face/FaceService.java b/services/core/java/com/android/server/biometrics/sensors/face/FaceService.java index c2994a953f31..6f26e7b3e4b4 100644 --- a/services/core/java/com/android/server/biometrics/sensors/face/FaceService.java +++ b/services/core/java/com/android/server/biometrics/sensors/face/FaceService.java @@ -415,6 +415,11 @@ public class FaceService extends SystemService { } } } + + @Override + public void onError(int error, int vendorCode) throws RemoteException { + receiver.onError(error, vendorCode); + } }; // This effectively iterates through all sensors, but has to do so by finding all diff --git a/services/core/java/com/android/server/display/AutomaticBrightnessController.java b/services/core/java/com/android/server/display/AutomaticBrightnessController.java index 774087c63f0f..75709fbb365a 100644 --- a/services/core/java/com/android/server/display/AutomaticBrightnessController.java +++ b/services/core/java/com/android/server/display/AutomaticBrightnessController.java @@ -333,11 +333,8 @@ public class AutomaticBrightnessController { // Initialize to active (normal) screen brightness mode switchToInteractiveScreenBrightnessMode(); - if (userLux != BrightnessMappingStrategy.NO_USER_LUX - && userBrightness != BrightnessMappingStrategy.NO_USER_BRIGHTNESS) { - // Use the given short-term model - setScreenBrightnessByUser(userLux, userBrightness); - } + // Use the given short-term model + setScreenBrightnessByUser(userLux, userBrightness); } /** @@ -520,6 +517,10 @@ public class AutomaticBrightnessController { } private boolean setScreenBrightnessByUser(float lux, float brightness) { + if (lux == BrightnessMappingStrategy.NO_USER_LUX + || brightness == BrightnessMappingStrategy.NO_USER_BRIGHTNESS) { + return false; + } mCurrentBrightnessMapper.addUserDataPoint(lux, brightness); mShortTermModel.setUserBrightness(lux, brightness); return true; @@ -1234,14 +1235,14 @@ public class AutomaticBrightnessController { // light. // The anchor determines what were the light levels when the user has set their preference, // and we use a relative threshold to determine when to revert to the OEM curve. - private float mAnchor = -1f; - private float mBrightness; - private boolean mIsValid = true; + private float mAnchor = BrightnessMappingStrategy.NO_USER_LUX; + private float mBrightness = BrightnessMappingStrategy.NO_USER_BRIGHTNESS; + private boolean mIsValid = false; private void reset() { - mAnchor = -1f; - mBrightness = -1f; - mIsValid = true; + mAnchor = BrightnessMappingStrategy.NO_USER_LUX; + mBrightness = BrightnessMappingStrategy.NO_USER_BRIGHTNESS; + mIsValid = false; } private void invalidate() { 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/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/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/input/InputManagerService.java b/services/core/java/com/android/server/input/InputManagerService.java index 662591e3d264..4cb22dbcf308 100644 --- a/services/core/java/com/android/server/input/InputManagerService.java +++ b/services/core/java/com/android/server/input/InputManagerService.java @@ -500,8 +500,6 @@ public class InputManagerService extends IInputManager.Stub // Add ourselves to the Watchdog monitors. Watchdog.getInstance().addMonitor(this); - - mSettingsObserver.registerAndUpdate(); } // TODO(BT) Pass in parameter for bluetooth system @@ -512,6 +510,8 @@ public class InputManagerService extends IInputManager.Stub mDisplayManagerInternal = LocalServices.getService(DisplayManagerInternal.class); + mSettingsObserver.registerAndUpdate(); + synchronized (mLidSwitchLock) { mSystemReady = true; diff --git a/services/core/java/com/android/server/media/MediaRoute2Provider.java b/services/core/java/com/android/server/media/MediaRoute2Provider.java index c076c0574afe..c59b73350865 100644 --- a/services/core/java/com/android/server/media/MediaRoute2Provider.java +++ b/services/core/java/com/android/server/media/MediaRoute2Provider.java @@ -31,6 +31,7 @@ import java.io.PrintWriter; import java.util.ArrayList; import java.util.List; import java.util.Objects; +import java.util.Set; abstract class MediaRoute2Provider { final ComponentName mComponentName; @@ -56,7 +57,9 @@ abstract class MediaRoute2Provider { public abstract void requestCreateSession(long requestId, String packageName, String routeId, @Nullable Bundle sessionHints); public abstract void releaseSession(long requestId, String sessionId); - public abstract void updateDiscoveryPreference(RouteDiscoveryPreference discoveryPreference); + + public abstract void updateDiscoveryPreference( + Set<String> activelyScanningPackages, RouteDiscoveryPreference discoveryPreference); public abstract void selectRoute(long requestId, String sessionId, String routeId); public abstract void deselectRoute(long requestId, String sessionId, String routeId); diff --git a/services/core/java/com/android/server/media/MediaRoute2ProviderServiceProxy.java b/services/core/java/com/android/server/media/MediaRoute2ProviderServiceProxy.java index 72b843672ae8..3cf0786b7e28 100644 --- a/services/core/java/com/android/server/media/MediaRoute2ProviderServiceProxy.java +++ b/services/core/java/com/android/server/media/MediaRoute2ProviderServiceProxy.java @@ -49,6 +49,7 @@ import java.util.ArrayList; import java.util.Collections; import java.util.List; import java.util.Objects; +import java.util.Set; /** * Maintains a connection to a particular {@link MediaRoute2ProviderService}. @@ -61,6 +62,7 @@ final class MediaRoute2ProviderServiceProxy extends MediaRoute2Provider private final Context mContext; private final int mUserId; private final Handler mHandler; + private final boolean mIsSelfScanOnlyProvider; // Connection state private boolean mRunning; @@ -70,14 +72,19 @@ final class MediaRoute2ProviderServiceProxy extends MediaRoute2Provider private boolean mIsManagerScanning; private RouteDiscoveryPreference mLastDiscoveryPreference = null; + private boolean mLastDiscoveryPreferenceIncludesThisPackage = false; @GuardedBy("mLock") final List<RoutingSessionInfo> mReleasingSessions = new ArrayList<>(); - MediaRoute2ProviderServiceProxy(@NonNull Context context, @NonNull ComponentName componentName, + MediaRoute2ProviderServiceProxy( + @NonNull Context context, + @NonNull ComponentName componentName, + boolean isSelfScanOnlyProvider, int userId) { super(componentName); mContext = Objects.requireNonNull(context, "Context must not be null."); + mIsSelfScanOnlyProvider = isSelfScanOnlyProvider; mUserId = userId; mHandler = new Handler(Looper.myLooper()); } @@ -107,8 +114,11 @@ final class MediaRoute2ProviderServiceProxy extends MediaRoute2Provider } @Override - public void updateDiscoveryPreference(RouteDiscoveryPreference discoveryPreference) { + public void updateDiscoveryPreference( + Set<String> activelyScanningPackages, RouteDiscoveryPreference discoveryPreference) { mLastDiscoveryPreference = discoveryPreference; + mLastDiscoveryPreferenceIncludesThisPackage = + activelyScanningPackages.contains(mComponentName.getPackageName()); if (mConnectionReady) { mActiveConnection.updateDiscoveryPreference(discoveryPreference); } @@ -209,11 +219,15 @@ final class MediaRoute2ProviderServiceProxy extends MediaRoute2Provider private boolean shouldBind() { if (mRunning) { - // Bind when there is a discovery preference or an active route session. - return (mLastDiscoveryPreference != null - && !mLastDiscoveryPreference.getPreferredFeatures().isEmpty()) - || !getSessionInfos().isEmpty() - || mIsManagerScanning; + boolean shouldBind = + mLastDiscoveryPreference != null + && !mLastDiscoveryPreference.getPreferredFeatures().isEmpty(); + if (mIsSelfScanOnlyProvider) { + shouldBind &= mLastDiscoveryPreferenceIncludesThisPackage; + } + shouldBind |= mIsManagerScanning; + shouldBind |= !getSessionInfos().isEmpty(); + return shouldBind; } return false; } @@ -301,7 +315,11 @@ final class MediaRoute2ProviderServiceProxy extends MediaRoute2Provider if (mActiveConnection == connection) { mConnectionReady = true; if (mLastDiscoveryPreference != null) { - updateDiscoveryPreference(mLastDiscoveryPreference); + updateDiscoveryPreference( + mLastDiscoveryPreferenceIncludesThisPackage + ? Set.of(mComponentName.getPackageName()) + : Set.of(), + mLastDiscoveryPreference); } } } diff --git a/services/core/java/com/android/server/media/MediaRoute2ProviderWatcher.java b/services/core/java/com/android/server/media/MediaRoute2ProviderWatcher.java index 46bccaf314d9..bd252e7fdfd2 100644 --- a/services/core/java/com/android/server/media/MediaRoute2ProviderWatcher.java +++ b/services/core/java/com/android/server/media/MediaRoute2ProviderWatcher.java @@ -16,6 +16,8 @@ package com.android.server.media; +import static android.content.pm.PackageManager.GET_RESOLVED_FILTER; + import android.annotation.NonNull; import android.content.BroadcastReceiver; import android.content.ComponentName; @@ -34,6 +36,7 @@ import android.util.Slog; import java.io.PrintWriter; import java.util.ArrayList; import java.util.Collections; +import java.util.Iterator; /** * Watches changes of packages, or scan them for finding media route providers. @@ -41,8 +44,8 @@ import java.util.Collections; final class MediaRoute2ProviderWatcher { private static final String TAG = "MR2ProviderWatcher"; private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG); - private static final PackageManager.ResolveInfoFlags RESOLVE_INFO_FLAGS_NONE = - PackageManager.ResolveInfoFlags.of(0); + private static final PackageManager.ResolveInfoFlags RESOLVE_INFO_FLAGS = + PackageManager.ResolveInfoFlags.of(GET_RESOLVED_FILTER); private final Context mContext; private final Callback mCallback; @@ -118,16 +121,26 @@ final class MediaRoute2ProviderWatcher { int targetIndex = 0; Intent intent = new Intent(MediaRoute2ProviderService.SERVICE_INTERFACE); for (ResolveInfo resolveInfo : - mPackageManager.queryIntentServicesAsUser( - intent, RESOLVE_INFO_FLAGS_NONE, mUserId)) { + mPackageManager.queryIntentServicesAsUser(intent, RESOLVE_INFO_FLAGS, mUserId)) { ServiceInfo serviceInfo = resolveInfo.serviceInfo; if (serviceInfo != null) { + boolean isSelfScanOnlyProvider = false; + Iterator<String> categoriesIterator = resolveInfo.filter.categoriesIterator(); + if (categoriesIterator != null) { + while (categoriesIterator.hasNext()) { + isSelfScanOnlyProvider |= + MediaRoute2ProviderService.CATEGORY_SELF_SCAN_ONLY.equals( + categoriesIterator.next()); + } + } int sourceIndex = findProvider(serviceInfo.packageName, serviceInfo.name); if (sourceIndex < 0) { MediaRoute2ProviderServiceProxy proxy = - new MediaRoute2ProviderServiceProxy(mContext, - new ComponentName(serviceInfo.packageName, serviceInfo.name), - mUserId); + new MediaRoute2ProviderServiceProxy( + mContext, + new ComponentName(serviceInfo.packageName, serviceInfo.name), + isSelfScanOnlyProvider, + mUserId); proxy.start(); mProxies.add(targetIndex++, proxy); mCallback.onAddProviderService(proxy); diff --git a/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java b/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java index b79991e5d013..1059c1981ce5 100644 --- a/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java +++ b/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java @@ -1478,6 +1478,7 @@ class MediaRouter2ServiceImpl { final ArrayList<RouterRecord> mRouterRecords = new ArrayList<>(); final ArrayList<ManagerRecord> mManagerRecords = new ArrayList<>(); RouteDiscoveryPreference mCompositeDiscoveryPreference = RouteDiscoveryPreference.EMPTY; + Set<String> mActivelyScanningPackages = Set.of(); final UserHandler mHandler; UserRecord(int userId) { @@ -1525,7 +1526,12 @@ class MediaRouter2ServiceImpl { pw.println(indent + "<no manager records>"); } - mCompositeDiscoveryPreference.dump(pw, indent); + pw.println(indent + "Composite discovery preference:"); + mCompositeDiscoveryPreference.dump(pw, indent + " "); + pw.println( + indent + + "Packages actively scanning: " + + String.join(", ", mActivelyScanningPackages)); if (!mHandler.runWithScissors(() -> mHandler.dump(pw, indent), 1000)) { pw.println(indent + "<could not dump handler state>"); @@ -1834,7 +1840,9 @@ class MediaRouter2ServiceImpl { public void onAddProviderService(@NonNull MediaRoute2ProviderServiceProxy proxy) { proxy.setCallback(this); mRouteProviders.add(proxy); - proxy.updateDiscoveryPreference(mUserRecord.mCompositeDiscoveryPreference); + proxy.updateDiscoveryPreference( + mUserRecord.mActivelyScanningPackages, + mUserRecord.mCompositeDiscoveryPreference); } @Override @@ -2341,8 +2349,8 @@ class MediaRouter2ServiceImpl { return; } notifySessionInfoChangedToRouters(getRouterRecords(true), sessionInfo); - notifySessionInfoChangedToRouters(getRouterRecords(false), - mSystemProvider.getDefaultSessionInfo()); + notifySessionInfoChangedToRouters( + getRouterRecords(false), mSystemProvider.getDefaultSessionInfo()); return; } @@ -2711,8 +2719,8 @@ class MediaRouter2ServiceImpl { if (service == null) { return; } - List<RouteDiscoveryPreference> discoveryPreferences = Collections.emptyList(); - List<RouterRecord> routerRecords = getRouterRecords(); + List<RouterRecord> activeRouterRecords = Collections.emptyList(); + List<RouterRecord> allRouterRecords = getRouterRecords(); List<ManagerRecord> managerRecords = getManagerRecords(); boolean isManagerScanning = false; @@ -2723,15 +2731,16 @@ class MediaRouter2ServiceImpl { <= sPackageImportanceForScanning); if (isManagerScanning) { - discoveryPreferences = routerRecords.stream() - .map(record -> record.mDiscoveryPreference) - .collect(Collectors.toList()); + activeRouterRecords = allRouterRecords; } else { - discoveryPreferences = routerRecords.stream().filter(record -> - service.mActivityManager.getPackageImportance(record.mPackageName) - <= sPackageImportanceForScanning) - .map(record -> record.mDiscoveryPreference) - .collect(Collectors.toList()); + activeRouterRecords = + allRouterRecords.stream() + .filter( + record -> + service.mActivityManager.getPackageImportance( + record.mPackageName) + <= sPackageImportanceForScanning) + .collect(Collectors.toList()); } } @@ -2748,22 +2757,30 @@ class MediaRouter2ServiceImpl { // to query route providers once to obtain all of the routes of interest, which // can be subsequently filtered for the individual discovery preferences. Set<String> preferredFeatures = new HashSet<>(); + Set<String> activelyScanningPackages = new HashSet<>(); boolean activeScan = false; - for (RouteDiscoveryPreference preference : discoveryPreferences) { + for (RouterRecord activeRouterRecord : activeRouterRecords) { + RouteDiscoveryPreference preference = activeRouterRecord.mDiscoveryPreference; preferredFeatures.addAll(preference.getPreferredFeatures()); - activeScan |= preference.shouldPerformActiveScan(); + if (preference.shouldPerformActiveScan()) { + activeScan = true; + activelyScanningPackages.add(activeRouterRecord.mPackageName); + } } RouteDiscoveryPreference newPreference = new RouteDiscoveryPreference.Builder( List.copyOf(preferredFeatures), activeScan || isManagerScanning).build(); synchronized (service.mLock) { - if (newPreference.equals(mUserRecord.mCompositeDiscoveryPreference)) { + if (newPreference.equals(mUserRecord.mCompositeDiscoveryPreference) + && activelyScanningPackages.equals(mUserRecord.mActivelyScanningPackages)) { return; } mUserRecord.mCompositeDiscoveryPreference = newPreference; + mUserRecord.mActivelyScanningPackages = activelyScanningPackages; } for (MediaRoute2Provider provider : mRouteProviders) { - provider.updateDiscoveryPreference(mUserRecord.mCompositeDiscoveryPreference); + provider.updateDiscoveryPreference( + activelyScanningPackages, mUserRecord.mCompositeDiscoveryPreference); } } diff --git a/services/core/java/com/android/server/media/SystemMediaRoute2Provider.java b/services/core/java/com/android/server/media/SystemMediaRoute2Provider.java index 426bc5eed051..f4e6abd47b44 100644 --- a/services/core/java/com/android/server/media/SystemMediaRoute2Provider.java +++ b/services/core/java/com/android/server/media/SystemMediaRoute2Provider.java @@ -42,6 +42,7 @@ import com.android.internal.annotations.GuardedBy; import java.util.List; import java.util.Objects; +import java.util.Set; /** * Provides routes for local playbacks such as phone speaker, wired headset, or Bluetooth speakers. @@ -196,7 +197,8 @@ class SystemMediaRoute2Provider extends MediaRoute2Provider { } @Override - public void updateDiscoveryPreference(RouteDiscoveryPreference discoveryPreference) { + public void updateDiscoveryPreference( + Set<String> activelyScanningPackages, RouteDiscoveryPreference discoveryPreference) { // Do nothing } diff --git a/services/core/java/com/android/server/policy/PhoneWindowManager.java b/services/core/java/com/android/server/policy/PhoneWindowManager.java index a148de6848d0..3e81f465a6c2 100644 --- a/services/core/java/com/android/server/policy/PhoneWindowManager.java +++ b/services/core/java/com/android/server/policy/PhoneWindowManager.java @@ -1086,6 +1086,8 @@ public class PhoneWindowManager implements WindowManagerPolicy { final DreamManagerInternal dreamManagerInternal = getDreamManagerInternal(); if (dreamManagerInternal == null || !dreamManagerInternal.canStartDreaming(isScreenOn)) { + Slog.d(TAG, "Can't start dreaming when attempting to dream from short power" + + " press (isScreenOn=" + isScreenOn + ")"); noDreamAction.run(); return; } @@ -6010,12 +6012,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/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java index 3db031510317..2b2100e56f44 100644 --- a/services/core/java/com/android/server/wm/ActivityRecord.java +++ b/services/core/java/com/android/server/wm/ActivityRecord.java @@ -5630,11 +5630,18 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A setClientVisible(visible); } + final DisplayContent displayContent = getDisplayContent(); if (!visible) { mImeInsetsFrozenUntilStartInput = true; + if (usingShellTransitions) { + final WindowState wallpaperTarget = + displayContent.mWallpaperController.getWallpaperTarget(); + if (wallpaperTarget != null && wallpaperTarget.mActivityRecord == this) { + displayContent.mWallpaperController.hideWallpapers(wallpaperTarget); + } + } } - final DisplayContent displayContent = getDisplayContent(); if (!displayContent.mClosingApps.contains(this) && !displayContent.mOpeningApps.contains(this) && !fromTransition) { diff --git a/services/core/java/com/android/server/wm/ActivityStarter.java b/services/core/java/com/android/server/wm/ActivityStarter.java index 3e3eb570d0e3..0ebec11ea36f 100644 --- a/services/core/java/com/android/server/wm/ActivityStarter.java +++ b/services/core/java/com/android/server/wm/ActivityStarter.java @@ -1821,7 +1821,8 @@ class ActivityStarter { // If Activity's launching into PiP, move the mStartActivity immediately to pinned mode. // Note that mStartActivity and source should be in the same Task at this point. if (mOptions != null && mOptions.isLaunchIntoPip() - && sourceRecord != null && sourceRecord.getTask() == mStartActivity.getTask()) { + && sourceRecord != null && sourceRecord.getTask() == mStartActivity.getTask() + && balCode != BAL_BLOCK) { mRootWindowContainer.moveActivityToPinnedRootTask(mStartActivity, sourceRecord, "launch-into-pip"); } diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerInternal.java b/services/core/java/com/android/server/wm/ActivityTaskManagerInternal.java index e07c65426278..32f1f42aacb9 100644 --- a/services/core/java/com/android/server/wm/ActivityTaskManagerInternal.java +++ b/services/core/java/com/android/server/wm/ActivityTaskManagerInternal.java @@ -262,9 +262,17 @@ public abstract class ActivityTaskManagerInternal { */ public abstract void setVr2dDisplayId(int vr2dDisplayId); + /** + * Registers a {@link ScreenObserver}. + */ public abstract void registerScreenObserver(ScreenObserver observer); /** + * Unregisters the given {@link ScreenObserver}. + */ + public abstract void unregisterScreenObserver(ScreenObserver observer); + + /** * Returns is the caller has the same uid as the Recents component */ public abstract boolean isCallerRecents(int callingUid); diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java index 89862918744c..a650d7709b67 100644 --- a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java +++ b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java @@ -296,6 +296,7 @@ import java.text.DateFormat; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; +import java.util.Collections; import java.util.Date; import java.util.HashSet; import java.util.List; @@ -652,7 +653,8 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub { */ float mMinPercentageMultiWindowSupportWidth; - final List<ActivityTaskManagerInternal.ScreenObserver> mScreenObservers = new ArrayList<>(); + final List<ActivityTaskManagerInternal.ScreenObserver> mScreenObservers = + Collections.synchronizedList(new ArrayList<>()); // VR Vr2d Display Id. int mVr2dDisplayId = INVALID_DISPLAY; @@ -3558,10 +3560,10 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub { mRootWindowContainer.forAllDisplays(displayContent -> { mKeyguardController.keyguardGoingAway(displayContent.getDisplayId(), flags); }); - WallpaperManagerInternal wallpaperManagerInternal = getWallpaperManagerInternal(); - if (wallpaperManagerInternal != null) { - wallpaperManagerInternal.onKeyguardGoingAway(); - } + } + WallpaperManagerInternal wallpaperManagerInternal = getWallpaperManagerInternal(); + if (wallpaperManagerInternal != null) { + wallpaperManagerInternal.onKeyguardGoingAway(); } } finally { Binder.restoreCallingIdentity(token); @@ -5869,6 +5871,11 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub { } @Override + public void unregisterScreenObserver(ScreenObserver observer) { + mScreenObservers.remove(observer); + } + + @Override public boolean isCallerRecents(int callingUid) { return ActivityTaskManagerService.this.isCallerRecents(callingUid); } diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java index e44d27911262..6aec96988a77 100644 --- a/services/core/java/com/android/server/wm/DisplayContent.java +++ b/services/core/java/com/android/server/wm/DisplayContent.java @@ -6511,6 +6511,22 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp .getKeyguardController().isDisplayOccluded(mDisplayId); } + /** + * @return the task that is occluding the keyguard + */ + @Nullable + Task getTaskOccludingKeyguard() { + final KeyguardController keyguardController = mRootWindowContainer.mTaskSupervisor + .getKeyguardController(); + if (keyguardController.getTopOccludingActivity(mDisplayId) != null) { + return keyguardController.getTopOccludingActivity(mDisplayId).getRootTask(); + } + if (keyguardController.getDismissKeyguardActivity(mDisplayId) != null) { + return keyguardController.getDismissKeyguardActivity(mDisplayId).getRootTask(); + } + return null; + } + @VisibleForTesting void removeAllTasks() { forAllTasks((t) -> { t.getRootTask().removeChild(t, "removeAllTasks"); }); diff --git a/services/core/java/com/android/server/wm/DisplayPolicy.java b/services/core/java/com/android/server/wm/DisplayPolicy.java index 46141f8e1b79..65771d1ce373 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; } @@ -1856,6 +1854,9 @@ public class DisplayPolicy { */ final Rect mConfigFrame = new Rect(); + /** The count of insets sources when calculating this info. */ + int mLastInsetsSourceCount; + private boolean mNeedUpdate = true; void update(DisplayContent dc, int rotation, int w, int h) { @@ -1877,6 +1878,7 @@ public class DisplayPolicy { mNonDecorFrame.inset(mNonDecorInsets); mConfigFrame.set(displayFrame); mConfigFrame.inset(mConfigInsets); + mLastInsetsSourceCount = dc.getDisplayPolicy().mInsetsSourceWindowsExceptIme.size(); mNeedUpdate = false; } @@ -1885,6 +1887,7 @@ public class DisplayPolicy { mConfigInsets.set(other.mConfigInsets); mNonDecorFrame.set(other.mNonDecorFrame); mConfigFrame.set(other.mConfigFrame); + mLastInsetsSourceCount = other.mLastInsetsSourceCount; mNeedUpdate = false; } @@ -1983,6 +1986,19 @@ public class DisplayPolicy { newInfo.update(mDisplayContent, rotation, dw, dh); final DecorInsets.Info currentInfo = getDecorInsetsInfo(rotation, dw, dh); if (newInfo.mConfigFrame.equals(currentInfo.mConfigFrame)) { + // Even if the config frame is not changed in current rotation, it may change the + // insets in other rotations if the source count is changed. + if (newInfo.mLastInsetsSourceCount != currentInfo.mLastInsetsSourceCount) { + for (int i = mDecorInsets.mInfoForRotation.length - 1; i >= 0; i--) { + if (i != rotation) { + final boolean flipSize = (i + rotation) % 2 == 1; + final int w = flipSize ? dh : dw; + final int h = flipSize ? dw : dh; + mDecorInsets.mInfoForRotation[i].update(mDisplayContent, i, w, h); + } + } + mDecorInsets.mInfoForRotation[rotation].set(newInfo); + } return false; } if (mCachedDecorInsets != null && !mCachedDecorInsets.canPreserve() diff --git a/services/core/java/com/android/server/wm/KeyguardController.java b/services/core/java/com/android/server/wm/KeyguardController.java index 5f6d66011768..99878a3d0ffe 100644 --- a/services/core/java/com/android/server/wm/KeyguardController.java +++ b/services/core/java/com/android/server/wm/KeyguardController.java @@ -17,7 +17,6 @@ package com.android.server.wm; import static android.app.WindowConfiguration.ACTIVITY_TYPE_DREAM; -import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN; import static android.os.Trace.TRACE_TAG_WINDOW_MANAGER; import static android.view.Display.DEFAULT_DISPLAY; import static android.view.WindowManager.TRANSIT_FLAG_KEYGUARD_GOING_AWAY; @@ -411,19 +410,20 @@ class KeyguardController { if (waitAppTransition) { mService.deferWindowLayout(); try { - mRootWindowContainer.getDefaultDisplay() - .requestTransitionAndLegacyPrepare( - isDisplayOccluded(DEFAULT_DISPLAY) - ? TRANSIT_KEYGUARD_OCCLUDE - : TRANSIT_KEYGUARD_UNOCCLUDE, 0 /* flags */); + if (isDisplayOccluded(DEFAULT_DISPLAY)) { + mRootWindowContainer.getDefaultDisplay().requestTransitionAndLegacyPrepare( + TRANSIT_KEYGUARD_OCCLUDE, + topActivity != null ? topActivity.getRootTask() : null); + } else { + mRootWindowContainer.getDefaultDisplay().requestTransitionAndLegacyPrepare( + TRANSIT_KEYGUARD_UNOCCLUDE, 0 /* flags */); + } updateKeyguardSleepToken(DEFAULT_DISPLAY); mWindowManager.executeAppTransition(); } finally { mService.continueWindowLayout(); } } - dismissMultiWindowModeForTaskIfNeeded(displayId, topActivity != null - ? topActivity.getRootTask() : null); } /** @@ -473,6 +473,14 @@ class KeyguardController { return getDisplayState(displayId).mOccluded; } + ActivityRecord getTopOccludingActivity(int displayId) { + return getDisplayState(displayId).mTopOccludesActivity; + } + + ActivityRecord getDismissKeyguardActivity(int displayId) { + return getDisplayState(displayId).mDismissingKeyguardActivity; + } + /** * @return true if Keyguard can be currently dismissed without entering credentials. */ @@ -488,22 +496,6 @@ class KeyguardController { return getDisplayState(DEFAULT_DISPLAY).mShowingDream; } - private void dismissMultiWindowModeForTaskIfNeeded(int displayId, - @Nullable Task currentTaskControllingOcclusion) { - // TODO(b/113840485): Handle docked stack for individual display. - if (!getDisplayState(displayId).mKeyguardShowing || !isDisplayOccluded(DEFAULT_DISPLAY)) { - return; - } - - // Dismiss freeform windowing mode - if (currentTaskControllingOcclusion == null) { - return; - } - if (currentTaskControllingOcclusion.inFreeformWindowingMode()) { - currentTaskControllingOcclusion.setWindowingMode(WINDOWING_MODE_FULLSCREEN); - } - } - private void updateKeyguardSleepToken() { for (int displayNdx = mRootWindowContainer.getChildCount() - 1; displayNdx >= 0; displayNdx--) { diff --git a/services/core/java/com/android/server/wm/RootWindowContainer.java b/services/core/java/com/android/server/wm/RootWindowContainer.java index 9c35858ef7ab..4995236da1be 100644 --- a/services/core/java/com/android/server/wm/RootWindowContainer.java +++ b/services/core/java/com/android/server/wm/RootWindowContainer.java @@ -2384,6 +2384,7 @@ class RootWindowContainer extends WindowContainer<DisplayContent> if (!displayShouldSleep && display.mTransitionController.isShellTransitionsEnabled() && !display.mTransitionController.isCollecting()) { int transit = TRANSIT_NONE; + Task startTask = null; if (!display.getDisplayPolicy().isAwake()) { // Note that currently this only happens on default display because non-default // display is always awake. @@ -2391,12 +2392,12 @@ class RootWindowContainer extends WindowContainer<DisplayContent> } else if (display.isKeyguardOccluded()) { // The display was awake so this is resuming activity for occluding keyguard. transit = WindowManager.TRANSIT_KEYGUARD_OCCLUDE; + startTask = display.getTaskOccludingKeyguard(); } if (transit != TRANSIT_NONE) { display.mTransitionController.requestStartTransition( display.mTransitionController.createTransition(transit), - null /* startTask */, null /* remoteTransition */, - null /* displayChange */); + startTask, null /* remoteTransition */, null /* displayChange */); } } // Set the sleeping state of the root tasks on the display. diff --git a/services/core/java/com/android/server/wm/Transition.java b/services/core/java/com/android/server/wm/Transition.java index d5a7ff582a58..b938b9e4ec51 100644 --- a/services/core/java/com/android/server/wm/Transition.java +++ b/services/core/java/com/android/server/wm/Transition.java @@ -2249,8 +2249,10 @@ class Transition implements BLASTSyncEngine.TransactionReadyListener { while (leashReference.getParent() != ancestor) { leashReference = leashReference.getParent(); } + final SurfaceControl rootLeash = leashReference.makeAnimationLeash().setName( "Transition Root: " + leashReference.getName()).build(); + rootLeash.setUnreleasedWarningCallSite("Transition.calculateTransitionRoots"); startT.setLayer(rootLeash, leashReference.getLastLayer()); outInfo.addRootLeash(endDisplayId, rootLeash, ancestor.getBounds().left, ancestor.getBounds().top); diff --git a/services/core/java/com/android/server/wm/WallpaperController.java b/services/core/java/com/android/server/wm/WallpaperController.java index edafe0606b13..20ce98ca2fae 100644 --- a/services/core/java/com/android/server/wm/WallpaperController.java +++ b/services/core/java/com/android/server/wm/WallpaperController.java @@ -146,11 +146,10 @@ class WallpaperController { } } else { final ActivityRecord ar = w.mActivityRecord; - final TransitionController tc = w.mTransitionController; // The animating window can still be visible on screen if it is in transition, so we // should check whether this window can be wallpaper target even when visibleRequested // is false. - if (ar != null && !ar.isVisibleRequested() && !tc.inTransition(ar)) { + if (ar != null && !ar.isVisibleRequested() && !ar.isVisible()) { // An activity that is not going to remain visible shouldn't be the target. return false; } 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 60207111ed76..edb8e0c5aa64 100644 --- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java +++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java @@ -11113,7 +11113,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { || hasCallingOrSelfPermission(permission.INTERACT_ACROSS_USERS); } - private boolean canUserUseLockTaskLocked(int userId) { + private boolean canDPCManagedUserUseLockTaskLocked(int userId) { if (isUserAffiliatedWithDeviceLocked(userId)) { return true; } @@ -11122,19 +11122,16 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { if (mOwners.hasDeviceOwner()) { return false; } - - if (!isPermissionCheckFlagEnabled() && !isPolicyEngineForFinanceFlagEnabled()) { - final ComponentName profileOwner = getProfileOwnerAsUser(userId); - if (profileOwner == null) { - return false; - } + + final ComponentName profileOwner = getProfileOwnerAsUser(userId); + if (profileOwner == null) { + return false; } - // Managed profiles are not allowed to use lock task if (isManagedProfile(userId)) { return false; } - + return true; } private void enforceCanQueryLockTaskLocked(ComponentName who, String callerPackageName) { @@ -11142,7 +11139,8 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { final int userId = caller.getUserId(); enforceCanQuery(MANAGE_DEVICE_POLICY_LOCK_TASK, caller.getPackageName(), userId); - if (!canUserUseLockTaskLocked(userId)) { + if ((isDeviceOwner(caller) || isProfileOwner(caller)) + && !canDPCManagedUserUseLockTaskLocked(userId)) { throw new SecurityException("User " + userId + " is not allowed to use lock task"); } } @@ -11158,7 +11156,8 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { caller.getPackageName(), userId ); - if (!canUserUseLockTaskLocked(userId)) { + if ((isDeviceOwner(caller) || isProfileOwner(caller)) + && !canDPCManagedUserUseLockTaskLocked(userId)) { throw new SecurityException("User " + userId + " is not allowed to use lock task"); } return enforcingAdmin; @@ -11169,7 +11168,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { || isDefaultDeviceOwner(caller) || isFinancedDeviceOwner(caller)); final int userId = caller.getUserId(); - if (!canUserUseLockTaskLocked(userId)) { + if (!canDPCManagedUserUseLockTaskLocked(userId)) { throw new SecurityException("User " + userId + " is not allowed to use lock task"); } } @@ -14907,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, @@ -15101,7 +15099,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { final List<UserInfo> userInfos = mUserManager.getAliveUsers(); for (int i = userInfos.size() - 1; i >= 0; i--) { int userId = userInfos.get(i).id; - if (canUserUseLockTaskLocked(userId)) { + if (canDPCManagedUserUseLockTaskLocked(userId)) { continue; } @@ -20690,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, @@ -20706,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); @@ -20718,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/am/MockingOomAdjusterTests.java b/services/tests/mockingservicestests/src/com/android/server/am/MockingOomAdjusterTests.java index cda5456723fb..770f04a2db5e 100644 --- a/services/tests/mockingservicestests/src/com/android/server/am/MockingOomAdjusterTests.java +++ b/services/tests/mockingservicestests/src/com/android/server/am/MockingOomAdjusterTests.java @@ -68,6 +68,7 @@ import static com.android.server.am.ProcessList.UNKNOWN_ADJ; import static com.android.server.am.ProcessList.VISIBLE_APP_ADJ; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotEquals; import static org.junit.Assert.assertTrue; import static org.mockito.AdditionalAnswers.answer; @@ -2489,6 +2490,28 @@ public class MockingOomAdjusterTests { assertProcStates(app2, false, PROCESS_STATE_SERVICE, SERVICE_ADJ, "started-services"); } + @SuppressWarnings("GuardedBy") + @Test + public void testUpdateOomAdj_DoOne_AboveClient_SameProcess() { + ProcessRecord app = spy(makeDefaultProcessRecord(MOCKAPP_PID, MOCKAPP_UID, + MOCKAPP_PROCESSNAME, MOCKAPP_PACKAGENAME, true)); + doReturn(PROCESS_STATE_TOP).when(sService.mAtmInternal).getTopProcessState(); + doReturn(app).when(sService).getTopApp(); + sService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE); + sService.mOomAdjuster.updateOomAdjLocked(app, OOM_ADJ_REASON_NONE); + + assertEquals(FOREGROUND_APP_ADJ, app.mState.getSetAdj()); + + // Simulate binding to a service in the same process using BIND_ABOVE_CLIENT and + // verify that its OOM adjustment level is unaffected. + bindService(app, app, null, Context.BIND_ABOVE_CLIENT, mock(IBinder.class)); + app.mServices.updateHasAboveClientLocked(); + assertFalse(app.mServices.hasAboveClient()); + + sService.mOomAdjuster.updateOomAdjLocked(app, OOM_ADJ_REASON_NONE); + assertEquals(FOREGROUND_APP_ADJ, app.mState.getSetAdj()); + } + private ProcessRecord makeDefaultProcessRecord(int pid, int uid, String processName, String packageName, boolean hasShownUi) { long now = SystemClock.uptimeMillis(); 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/am/UserControllerTest.java b/services/tests/servicestests/src/com/android/server/am/UserControllerTest.java index 317fd58e0c76..dccacb4d301a 100644 --- a/services/tests/servicestests/src/com/android/server/am/UserControllerTest.java +++ b/services/tests/servicestests/src/com/android/server/am/UserControllerTest.java @@ -58,6 +58,7 @@ import static org.mockito.Matchers.anyBoolean; import static org.mockito.Matchers.anyInt; import static org.mockito.Matchers.eq; import static org.mockito.Mockito.doAnswer; +import static org.mockito.Mockito.doCallRealMethod; import static org.mockito.Mockito.doNothing; import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.mock; @@ -103,11 +104,13 @@ import com.android.server.am.UserState.KeyEvictedCallback; import com.android.server.pm.UserJourneyLogger; import com.android.server.pm.UserManagerInternal; import com.android.server.pm.UserManagerService; +import com.android.server.wm.ActivityTaskManagerInternal; import com.android.server.wm.WindowManagerService; import org.junit.After; import org.junit.Before; import org.junit.Test; +import org.mockito.ArgumentCaptor; import java.util.ArrayList; import java.util.Arrays; @@ -187,6 +190,7 @@ public class UserControllerTest { doNothing().when(mInjector).activityManagerOnUserStopped(anyInt()); doNothing().when(mInjector).clearBroadcastQueueForUser(anyInt()); doNothing().when(mInjector).taskSupervisorRemoveUser(anyInt()); + doNothing().when(mInjector).lockDeviceNowAndWaitForKeyguardShown(); mockIsUsersOnSecondaryDisplaysEnabled(false); // All UserController params are set to default. @@ -951,6 +955,45 @@ public class UserControllerTest { .systemServiceManagerOnUserCompletedEvent(eq(user2), eq(event2a)); } + @Test + public void testStallUserSwitchUntilTheKeyguardIsShown() throws Exception { + // enable user switch ui, because keyguard is only shown then + mUserController.setInitialConfig(/* userSwitchUiEnabled= */ true, + /* maxRunningUsers= */ 3, /* delayUserDataLocking= */ false); + + // mock the device to be secure in order to expect the keyguard to be shown + when(mInjector.mKeyguardManagerMock.isDeviceSecure(anyInt())).thenReturn(true); + + // call real lockDeviceNowAndWaitForKeyguardShown method for this test + doCallRealMethod().when(mInjector).lockDeviceNowAndWaitForKeyguardShown(); + + // call startUser on a thread because we're expecting it to be blocked + Thread threadStartUser = new Thread(()-> { + mUserController.startUser(TEST_USER_ID, USER_START_MODE_FOREGROUND); + }); + threadStartUser.start(); + + // make sure the switch is stalled... + Thread.sleep(2000); + // by checking REPORT_USER_SWITCH_MSG is not sent yet + assertNull(mInjector.mHandler.getMessageForCode(REPORT_USER_SWITCH_MSG)); + // and the thread is still alive + assertTrue(threadStartUser.isAlive()); + + // mock send the keyguard shown event + ArgumentCaptor<ActivityTaskManagerInternal.ScreenObserver> captor = ArgumentCaptor.forClass( + ActivityTaskManagerInternal.ScreenObserver.class); + verify(mInjector.mActivityTaskManagerInternal).registerScreenObserver(captor.capture()); + captor.getValue().onKeyguardStateChanged(true); + + // verify the switch now moves on... + Thread.sleep(1000); + // by checking REPORT_USER_SWITCH_MSG is sent + assertNotNull(mInjector.mHandler.getMessageForCode(REPORT_USER_SWITCH_MSG)); + // and the thread is finished + assertFalse(threadStartUser.isAlive()); + } + private void setUpAndStartUserInBackground(int userId) throws Exception { setUpUser(userId, 0); mUserController.startUser(userId, USER_START_MODE_BACKGROUND); @@ -1092,6 +1135,7 @@ public class UserControllerTest { private final IStorageManager mStorageManagerMock; private final UserManagerInternal mUserManagerInternalMock; private final WindowManagerService mWindowManagerMock; + private final ActivityTaskManagerInternal mActivityTaskManagerInternal; private final KeyguardManager mKeyguardManagerMock; private final LockPatternUtils mLockPatternUtilsMock; @@ -1111,6 +1155,7 @@ public class UserControllerTest { mUserManagerMock = mock(UserManagerService.class); mUserManagerInternalMock = mock(UserManagerInternal.class); mWindowManagerMock = mock(WindowManagerService.class); + mActivityTaskManagerInternal = mock(ActivityTaskManagerInternal.class); mStorageManagerMock = mock(IStorageManager.class); mKeyguardManagerMock = mock(KeyguardManager.class); when(mKeyguardManagerMock.isDeviceSecure(anyInt())).thenReturn(true); @@ -1172,6 +1217,11 @@ public class UserControllerTest { } @Override + ActivityTaskManagerInternal getActivityTaskManagerInternal() { + return mActivityTaskManagerInternal; + } + + @Override KeyguardManager getKeyguardManager() { return mKeyguardManagerMock; } diff --git a/services/tests/servicestests/src/com/android/server/biometrics/log/BiometricContextProviderTest.java b/services/tests/servicestests/src/com/android/server/biometrics/log/BiometricContextProviderTest.java index fb3a5f64d808..a4423038a072 100644 --- a/services/tests/servicestests/src/com/android/server/biometrics/log/BiometricContextProviderTest.java +++ b/services/tests/servicestests/src/com/android/server/biometrics/log/BiometricContextProviderTest.java @@ -316,13 +316,13 @@ public class BiometricContextProviderTest { assertThat(aidlContext.isAod).isEqualTo(false); assertThat(aidlContext.isCrypto).isEqualTo(false); - context = mProvider.updateContext(mOpContext, false /* crypto */); + context = mProvider.updateContext(mOpContext, true /* crypto */); aidlContext = context.toAidlContext(); assertThat(context).isSameInstanceAs(mOpContext); assertThat(aidlContext.id).isEqualTo(0); assertThat(aidlContext.reason).isEqualTo(OperationReason.UNKNOWN); assertThat(aidlContext.isAod).isEqualTo(false); - assertThat(aidlContext.isCrypto).isEqualTo(false); + assertThat(aidlContext.isCrypto).isEqualTo(true); } @Test diff --git a/services/tests/servicestests/src/com/android/server/biometrics/log/OperationContextExtTest.java b/services/tests/servicestests/src/com/android/server/biometrics/log/OperationContextExtTest.java index 5cf59602a43b..32284fd7541a 100644 --- a/services/tests/servicestests/src/com/android/server/biometrics/log/OperationContextExtTest.java +++ b/services/tests/servicestests/src/com/android/server/biometrics/log/OperationContextExtTest.java @@ -98,7 +98,7 @@ public class OperationContextExtTest { for (Map.Entry<Integer, Integer> entry : map.entrySet()) { final OperationContextExt context = new OperationContextExt(newAidlContext(), true); when(mBiometricContext.getDisplayState()).thenReturn(entry.getKey()); - assertThat(context.update(mBiometricContext).getDisplayState()) + assertThat(context.update(mBiometricContext, context.isCrypto()).getDisplayState()) .isEqualTo(entry.getValue()); } } @@ -139,7 +139,7 @@ public class OperationContextExtTest { final OperationContextExt context = new OperationContextExt(newAidlContext(), sessionType == OperationReason.BIOMETRIC_PROMPT); - assertThat(context.update(mBiometricContext)).isSameInstanceAs(context); + assertThat(context.update(mBiometricContext, context.isCrypto())).isSameInstanceAs(context); if (sessionInfo != null) { assertThat(context.getId()).isEqualTo(sessionInfo.getId()); diff --git a/services/tests/servicestests/src/com/android/server/contentcapture/OWNERS b/services/tests/servicestests/src/com/android/server/contentcapture/OWNERS new file mode 100644 index 000000000000..b3583a7f6ab1 --- /dev/null +++ b/services/tests/servicestests/src/com/android/server/contentcapture/OWNERS @@ -0,0 +1,4 @@ +# Bug component: 544200 + +include /core/java/android/view/contentcapture/OWNERS + diff --git a/services/tests/servicestests/src/com/android/server/display/AutomaticBrightnessControllerTest.java b/services/tests/servicestests/src/com/android/server/display/AutomaticBrightnessControllerTest.java index 3bef413a3e72..962e86776ea2 100644 --- a/services/tests/servicestests/src/com/android/server/display/AutomaticBrightnessControllerTest.java +++ b/services/tests/servicestests/src/com/android/server/display/AutomaticBrightnessControllerTest.java @@ -473,6 +473,42 @@ public class AutomaticBrightnessControllerTest { } @Test + public void testSwitchBetweenModesNoUserInteractions() throws Exception { + ArgumentCaptor<SensorEventListener> listenerCaptor = + ArgumentCaptor.forClass(SensorEventListener.class); + verify(mSensorManager).registerListener(listenerCaptor.capture(), eq(mLightSensor), + eq(INITIAL_LIGHT_SENSOR_RATE * 1000), any(Handler.class)); + SensorEventListener listener = listenerCaptor.getValue(); + + // Sensor reads 123 lux, + listener.onSensorChanged(TestUtils.createSensorEvent(mLightSensor, 123)); + when(mBrightnessMappingStrategy.getShortTermModelTimeout()).thenReturn(2000L); + when(mBrightnessMappingStrategy.getUserBrightness()).thenReturn(-1.0f); + when(mBrightnessMappingStrategy.getUserLux()).thenReturn(-1.0f); + + // No user brightness interaction. + + mController.switchToIdleMode(); + when(mIdleBrightnessMappingStrategy.isForIdleMode()).thenReturn(true); + when(mIdleBrightnessMappingStrategy.getUserBrightness()).thenReturn(-1.0f); + when(mIdleBrightnessMappingStrategy.getUserLux()).thenReturn(-1.0f); + + // Sensor reads 1000 lux, + listener.onSensorChanged(TestUtils.createSensorEvent(mLightSensor, 1000)); + // Do not fast-forward time. + mTestLooper.dispatchAll(); + + mController.switchToInteractiveScreenBrightnessMode(); + // Do not fast-forward time + mTestLooper.dispatchAll(); + + // Ensure that there are no data points added, since the user has never adjusted the + // brightness + verify(mBrightnessMappingStrategy, times(0)) + .addUserDataPoint(anyFloat(), anyFloat()); + } + + @Test public void testSwitchToIdleMappingStrategy() throws Exception { ArgumentCaptor<SensorEventListener> listenerCaptor = ArgumentCaptor.forClass(SensorEventListener.class); 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/NotificationVisitUrisTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationVisitUrisTest.java index 4c10ddc76894..6668f8520820 100644 --- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationVisitUrisTest.java +++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationVisitUrisTest.java @@ -87,7 +87,6 @@ public class NotificationVisitUrisTest extends UiServiceTestCase { // This list should be emptied! Items can be removed as bugs are fixed. private static final Multimap<Class<?>, String> KNOWN_BAD = ImmutableMultimap.<Class<?>, String>builder() - .put(RemoteViews.class, "setIcon") // b/281018094 .put(Notification.WearableExtender.class, "addAction") // TODO: b/281044385 .put(Person.Builder.class, "setUri") // TODO: b/281044385 .put(RemoteViews.class, "setRemoteAdapter") // TODO: b/281044385 diff --git a/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyTests.java b/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyTests.java index 5ec36048234b..b0609ddfddfd 100644 --- a/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyTests.java @@ -51,14 +51,18 @@ import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.spy; import static org.mockito.Mockito.when; +import android.graphics.Insets; import android.graphics.PixelFormat; import android.graphics.Rect; import android.platform.test.annotations.Presubmit; import android.view.DisplayInfo; import android.view.DisplayShape; +import android.view.InsetsFrameProvider; import android.view.InsetsSource; import android.view.InsetsState; import android.view.PrivacyIndicatorBounds; +import android.view.Surface; +import android.view.WindowInsets; import android.view.WindowInsets.Side; import android.view.WindowManager; @@ -350,6 +354,36 @@ public class DisplayPolicyTests extends WindowTestsBase { && displayPolicy.updateDecorInsetsInfo()); assertEquals(STATUS_BAR_HEIGHT, displayPolicy.getDecorInsetsInfo(di.rotation, di.logicalWidth, di.logicalHeight).mConfigInsets.top); + + // Add a window that provides the same insets in current rotation. But it specifies + // different insets in other rotations. + final WindowState bar2 = createWindow(null, statusBar.mAttrs.type, "bar2"); + bar2.mAttrs.providedInsets = new InsetsFrameProvider[] { + new InsetsFrameProvider(bar2, 0, WindowInsets.Type.statusBars()) + .setInsetsSize(Insets.of(0, STATUS_BAR_HEIGHT, 0, 0)) + }; + bar2.mAttrs.setFitInsetsTypes(0); + bar2.mAttrs.paramsForRotation = new WindowManager.LayoutParams[4]; + final int doubleHeightFor90 = STATUS_BAR_HEIGHT * 2; + for (int i = ROTATION_0; i <= Surface.ROTATION_270; i++) { + final WindowManager.LayoutParams params = new WindowManager.LayoutParams(); + params.setFitInsetsTypes(0); + if (i == Surface.ROTATION_90) { + params.providedInsets = new InsetsFrameProvider[] { + new InsetsFrameProvider(bar2, 0, WindowInsets.Type.statusBars()) + .setInsetsSize(Insets.of(0, doubleHeightFor90, 0, 0)) + }; + } else { + params.providedInsets = bar2.mAttrs.providedInsets; + } + bar2.mAttrs.paramsForRotation[i] = params; + } + displayPolicy.addWindowLw(bar2, bar2.mAttrs); + // Current rotation is 0 and the top insets is still STATUS_BAR_HEIGHT, so no change. + assertFalse(displayPolicy.updateDecorInsetsInfo()); + // The insets in other rotations should be still updated. + assertEquals(doubleHeightFor90, displayPolicy.getDecorInsetsInfo(Surface.ROTATION_90, + di.logicalHeight, di.logicalWidth).mConfigInsets.top); } @SetupWindows(addWindows = { W_NAVIGATION_BAR, W_INPUT_METHOD }) 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/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java b/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java index 863523f826b2..ddc729f773b2 100644 --- a/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java +++ b/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java @@ -485,6 +485,7 @@ class WindowTestsBase extends SystemServiceTestsBase { new InsetsFrameProvider(owner, 0, WindowInsets.Type.statusBars()) .setInsetsSize(Insets.of(0, STATUS_BAR_HEIGHT, 0, 0)) }; + statusBar.mAttrs.setFitInsetsTypes(0); dc.getDisplayPolicy().addWindowLw(statusBar, statusBar.mAttrs); return statusBar; } diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/CommonAssertions.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/CommonAssertions.kt index 1f120d47a1ac..ed9e14fd86da 100644 --- a/tests/FlickerTests/src/com/android/server/wm/flicker/CommonAssertions.kt +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/CommonAssertions.kt @@ -97,19 +97,19 @@ fun FlickerTest.entireScreenCovered(allStates: Boolean = true) { if (allStates) { assertLayers { this.invoke("entireScreenCovered") { entry -> - entry.entry.displays.forEach { display -> + entry.entry.displays.filter { it.isOn }.forEach { display -> entry.visibleRegion().coversAtLeast(display.layerStackSpace) } } } } else { assertLayersStart { - this.entry.displays.forEach { display -> + this.entry.displays.filter { it.isOn }.forEach { display -> this.visibleRegion().coversAtLeast(display.layerStackSpace) } } assertLayersEnd { - this.entry.displays.forEach { display -> + this.entry.displays.filter { it.isOn }.forEach { display -> this.visibleRegion().coversAtLeast(display.layerStackSpace) } } diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeOnGoHomeTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeOnGoHomeTest.kt index 7f496d828996..122a6cbbb98f 100644 --- a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeOnGoHomeTest.kt +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeOnGoHomeTest.kt @@ -17,6 +17,7 @@ package com.android.server.wm.flicker.ime import android.platform.test.annotations.IwTest +import android.platform.test.annotations.PlatinumTest import android.platform.test.annotations.Presubmit import android.tools.common.Rotation import android.tools.common.traces.component.ComponentNameMatcher @@ -102,6 +103,7 @@ open class CloseImeOnGoHomeTest(flicker: FlickerTest) : BaseTest(flicker) { @Presubmit @Test + @PlatinumTest(focusArea = "ime") @IwTest(focusArea = "ime") override fun cujCompleted() { super.cujCompleted() diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeToAppOnPressBackTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeToAppOnPressBackTest.kt index c693ca759bdd..5aa43820e5cb 100644 --- a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeToAppOnPressBackTest.kt +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeToAppOnPressBackTest.kt @@ -17,6 +17,7 @@ package com.android.server.wm.flicker.ime import android.platform.test.annotations.IwTest +import android.platform.test.annotations.PlatinumTest import android.platform.test.annotations.Presubmit import android.tools.common.traces.component.ComponentNameMatcher import android.tools.device.flicker.junit.FlickerParametersRunnerFactory @@ -103,6 +104,7 @@ open class CloseImeToAppOnPressBackTest(flicker: FlickerTest) : BaseTest(flicker @Presubmit @Test + @PlatinumTest(focusArea = "ime") @IwTest(focusArea = "ime") override fun cujCompleted() { super.cujCompleted() diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeToHomeOnFinishActivityTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeToHomeOnFinishActivityTest.kt index d5208e060883..6731089f8903 100644 --- a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeToHomeOnFinishActivityTest.kt +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeToHomeOnFinishActivityTest.kt @@ -18,6 +18,7 @@ package com.android.server.wm.flicker.ime import android.platform.test.annotations.FlakyTest import android.platform.test.annotations.IwTest +import android.platform.test.annotations.PlatinumTest import android.platform.test.annotations.Presubmit import android.tools.common.Rotation import android.tools.device.flicker.junit.FlickerParametersRunnerFactory @@ -71,6 +72,7 @@ open class CloseImeToHomeOnFinishActivityTest(flicker: FlickerTest) : BaseTest(f @Presubmit @Test + @PlatinumTest(focusArea = "ime") @IwTest(focusArea = "ime") override fun cujCompleted() { runAndIgnoreAssumptionViolation { entireScreenCovered() } diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/ShowImeWhenFocusingOnInputFieldTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/ShowImeWhenFocusingOnInputFieldTest.kt index d1335294f629..c70e3cb773d4 100644 --- a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/ShowImeWhenFocusingOnInputFieldTest.kt +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/ShowImeWhenFocusingOnInputFieldTest.kt @@ -17,6 +17,7 @@ package com.android.server.wm.flicker.ime import android.platform.test.annotations.IwTest +import android.platform.test.annotations.PlatinumTest import android.platform.test.annotations.Presubmit import android.tools.common.Rotation import android.tools.device.flicker.junit.FlickerParametersRunnerFactory @@ -52,6 +53,7 @@ open class ShowImeWhenFocusingOnInputFieldTest(flicker: FlickerTest) : BaseTest( @Presubmit @Test + @PlatinumTest(focusArea = "ime") @IwTest(focusArea = "ime") override fun cujCompleted() { super.cujCompleted() diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/rotation/ChangeAppRotationTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/rotation/ChangeAppRotationTest.kt index 855ea3e38b3e..7fbcfec40121 100644 --- a/tests/FlickerTests/src/com/android/server/wm/flicker/rotation/ChangeAppRotationTest.kt +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/rotation/ChangeAppRotationTest.kt @@ -17,6 +17,7 @@ package com.android.server.wm.flicker.rotation import android.platform.test.annotations.IwTest +import android.platform.test.annotations.PlatinumTest import android.platform.test.annotations.Presubmit import android.tools.common.traces.component.ComponentNameMatcher import android.tools.device.flicker.junit.FlickerParametersRunnerFactory @@ -129,6 +130,7 @@ open class ChangeAppRotationTest(flicker: FlickerTest) : RotationTransition(flic } @Test + @PlatinumTest(focusArea = "framework") @IwTest(focusArea = "framework") override fun cujCompleted() { super.cujCompleted() diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/rotation/SeamlessAppRotationTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/rotation/SeamlessAppRotationTest.kt index 0cbbb83b48ee..44ae14a8e4bb 100644 --- a/tests/FlickerTests/src/com/android/server/wm/flicker/rotation/SeamlessAppRotationTest.kt +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/rotation/SeamlessAppRotationTest.kt @@ -17,6 +17,7 @@ package com.android.server.wm.flicker.rotation import android.platform.test.annotations.IwTest +import android.platform.test.annotations.PlatinumTest import android.platform.test.annotations.Presubmit import android.tools.common.ScenarioBuilder import android.tools.common.traces.component.ComponentNameMatcher @@ -213,6 +214,7 @@ open class SeamlessAppRotationTest(flicker: FlickerTest) : RotationTransition(fl } @Test + @PlatinumTest(focusArea = "framework") @IwTest(focusArea = "framework") override fun cujCompleted() { appWindowFullScreen() 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 = |