diff options
90 files changed, 1600 insertions, 541 deletions
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/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/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/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/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/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/libs/WindowManager/Shell/src/com/android/wm/shell/keyguard/KeyguardTransitionHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/keyguard/KeyguardTransitionHandler.java index 25e940129081..c25352b6e57d 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; } } 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 fc301b6993c0..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 @@ -103,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; 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 e6d4603db10d..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 @@ -570,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. 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/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/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 2bca7cf9762e..1ff2befd19ed 100644 --- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothBroadcastUtils.java +++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothBroadcastUtils.java @@ -43,5 +43,5 @@ public final class BluetoothBroadcastUtils { /** * Bluetooth scheme. */ - public static final String SCHEME_BT_BROADCAST_METADATA = "BT:BluetoothLeBroadcastMetadata:"; + public static final String SCHEME_BT_BROADCAST_METADATA = "BT:"; } diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothLeBroadcastMetadataExt.kt b/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothLeBroadcastMetadataExt.kt index b54b115213d5..9bb11f8da645 100644 --- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothLeBroadcastMetadataExt.kt +++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothLeBroadcastMetadataExt.kt @@ -15,23 +15,92 @@ */ package com.android.settingslib.bluetooth +import android.annotation.TargetApi +import android.bluetooth.BluetoothAdapter +import android.bluetooth.BluetoothDevice +import android.bluetooth.BluetoothLeAudioCodecConfigMetadata +import android.bluetooth.BluetoothLeAudioContentMetadata +import android.bluetooth.BluetoothLeBroadcastChannel import android.bluetooth.BluetoothLeBroadcastMetadata -import android.os.Parcel -import android.os.Parcelable +import android.bluetooth.BluetoothLeBroadcastSubgroup +import android.os.Build import android.util.Base64 import android.util.Log import com.android.settingslib.bluetooth.BluetoothBroadcastUtils.SCHEME_BT_BROADCAST_METADATA object BluetoothLeBroadcastMetadataExt { - private const val TAG = "BluetoothLeBroadcastMetadataExt" + private const val TAG = "BtLeBroadcastMetadataExt" + + // BluetoothLeBroadcastMetadata + private const val KEY_BT_QR_VER = "R" + private const val KEY_BT_ADDRESS_TYPE = "T" + private const val KEY_BT_DEVICE = "D" + private const val KEY_BT_ADVERTISING_SID = "AS" + private const val KEY_BT_BROADCAST_ID = "B" + private const val KEY_BT_BROADCAST_NAME = "BN" + private const val KEY_BT_PUBLIC_BROADCAST_DATA = "PM" + private const val KEY_BT_SYNC_INTERVAL = "SI" + private const val KEY_BT_BROADCAST_CODE = "C" + private const val KEY_BT_SUBGROUPS = "SG" + private const val KEY_BT_VENDOR_SPECIFIC = "V" + private const val KEY_BT_ANDROID_VERSION = "VN" + + // Subgroup data + private const val KEY_BTSG_BIS_SYNC = "BS" + private const val KEY_BTSG_BIS_MASK = "BM" + private const val KEY_BTSG_AUDIO_CONTENT = "AC" + + // Vendor specific data + private const val KEY_BTVSD_COMPANY_ID = "VI" + private const val KEY_BTVSD_VENDOR_DATA = "VD" + + private const val DELIMITER_KEY_VALUE = ":" + private const val DELIMITER_BT_LEVEL_1 = ";" + private const val DELIMITER_BT_LEVEL_2 = "," + + private const val SUFFIX_QR_CODE = ";;" + + private const val ANDROID_VER = "U" + private const val QR_CODE_VER = 0x010000 + + // BT constants + private const val BIS_SYNC_MAX_CHANNEL = 32 + private const val BIS_SYNC_NO_PREFERENCE = 0xFFFFFFFFu + private const val SUBGROUP_LC3_CODEC_ID = 0x6L /** * Converts [BluetoothLeBroadcastMetadata] to QR code string. * - * QR code string will prefix with "BT:BluetoothLeBroadcastMetadata:". + * QR code string will prefix with "BT:". */ - fun BluetoothLeBroadcastMetadata.toQrCodeString(): String = - SCHEME_BT_BROADCAST_METADATA + Base64.encodeToString(toBytes(this), Base64.NO_WRAP) + fun BluetoothLeBroadcastMetadata.toQrCodeString(): String { + val entries = mutableListOf<Pair<String, String>>() + entries.add(Pair(KEY_BT_QR_VER, QR_CODE_VER.toString())) + entries.add(Pair(KEY_BT_ADDRESS_TYPE, this.sourceAddressType.toString())) + entries.add(Pair(KEY_BT_DEVICE, this.sourceDevice.address.replace(":", "-"))) + entries.add(Pair(KEY_BT_ADVERTISING_SID, this.sourceAdvertisingSid.toString())) + entries.add(Pair(KEY_BT_BROADCAST_ID, this.broadcastId.toString())) + if (this.broadcastName != null) { + entries.add(Pair(KEY_BT_BROADCAST_NAME, Base64.encodeToString( + this.broadcastName?.toByteArray(Charsets.UTF_8), Base64.NO_WRAP))) + } + if (this.publicBroadcastMetadata != null) { + entries.add(Pair(KEY_BT_PUBLIC_BROADCAST_DATA, Base64.encodeToString( + this.publicBroadcastMetadata?.rawMetadata, Base64.NO_WRAP))) + } + entries.add(Pair(KEY_BT_SYNC_INTERVAL, this.paSyncInterval.toString())) + if (this.broadcastCode != null) { + entries.add(Pair(KEY_BT_BROADCAST_CODE, + Base64.encodeToString(this.broadcastCode, Base64.NO_WRAP))) + } + this.subgroups.forEach { + subgroup -> entries.add(Pair(KEY_BT_SUBGROUPS, subgroup.toQrCodeString())) } + entries.add(Pair(KEY_BT_ANDROID_VERSION, ANDROID_VER)) + val qrCodeString = SCHEME_BT_BROADCAST_METADATA + + entries.toQrCodeString(DELIMITER_BT_LEVEL_1) + SUFFIX_QR_CODE + Log.d(TAG, "Generated QR string : $qrCodeString") + return qrCodeString + } /** * Converts QR code string to [BluetoothLeBroadcastMetadata]. @@ -39,32 +108,255 @@ object BluetoothLeBroadcastMetadataExt { * QR code string should prefix with "BT:BluetoothLeBroadcastMetadata:". */ fun convertToBroadcastMetadata(qrCodeString: String): BluetoothLeBroadcastMetadata? { - if (!qrCodeString.startsWith(SCHEME_BT_BROADCAST_METADATA)) return null + if (!qrCodeString.startsWith(SCHEME_BT_BROADCAST_METADATA)) { + Log.e(TAG, "String \"$qrCodeString\" does not begin with " + + "\"$SCHEME_BT_BROADCAST_METADATA\"") + return null + } return try { - val encodedString = qrCodeString.removePrefix(SCHEME_BT_BROADCAST_METADATA) - val bytes = Base64.decode(encodedString, Base64.NO_WRAP) - createFromBytes(BluetoothLeBroadcastMetadata.CREATOR, bytes) + Log.d(TAG, "Parsing QR string: $qrCodeString") + val strippedString = + qrCodeString.removePrefix(SCHEME_BT_BROADCAST_METADATA) + .removeSuffix(SUFFIX_QR_CODE) + Log.d(TAG, "Stripped to: $strippedString") + parseQrCodeToMetadata(strippedString) } catch (e: Exception) { - Log.w(TAG, "Cannot convert QR code string to BluetoothLeBroadcastMetadata", e) + Log.w(TAG, "Cannot parse: $qrCodeString", e) null } } - private fun toBytes(parcelable: Parcelable): ByteArray = - Parcel.obtain().run { - parcelable.writeToParcel(this, 0) - setDataPosition(0) - val bytes = marshall() - recycle() - bytes + private fun BluetoothLeBroadcastSubgroup.toQrCodeString(): String { + val entries = mutableListOf<Pair<String, String>>() + entries.add(Pair(KEY_BTSG_BIS_SYNC, getBisSyncFromChannels(this.channels).toString())) + entries.add(Pair(KEY_BTSG_BIS_MASK, getBisMaskFromChannels(this.channels).toString())) + entries.add(Pair(KEY_BTSG_AUDIO_CONTENT, + Base64.encodeToString(this.contentMetadata.rawMetadata, Base64.NO_WRAP))) + return entries.toQrCodeString(DELIMITER_BT_LEVEL_2) + } + + private fun List<Pair<String, String>>.toQrCodeString(delimiter: String): String { + val entryStrings = this.map{ it.first + DELIMITER_KEY_VALUE + it.second } + return entryStrings.joinToString(separator = delimiter) + } + + @TargetApi(Build.VERSION_CODES.TIRAMISU) + private fun parseQrCodeToMetadata(input: String): BluetoothLeBroadcastMetadata { + // Split into a list of list + val level1Fields = input.split(DELIMITER_BT_LEVEL_1) + .map{it.split(DELIMITER_KEY_VALUE, limit = 2)} + var qrCodeVersion = -1 + var sourceAddrType = BluetoothDevice.ADDRESS_TYPE_UNKNOWN + var sourceAddrString: String? = null + var sourceAdvertiserSid = -1 + var broadcastId = -1 + var broadcastName: String? = null + var publicBroadcastMetadata: BluetoothLeAudioContentMetadata? = null + var paSyncInterval = -1 + var broadcastCode: ByteArray? = null + // List of VendorID -> Data Pairs + var vendorDataList = mutableListOf<Pair<Int, ByteArray?>>() + var androidVersion: String? = null + val builder = BluetoothLeBroadcastMetadata.Builder() + + for (field: List<String> in level1Fields) { + if (field.isEmpty()) { + continue + } + val key = field[0] + // Ignore 3rd value and after + val value = if (field.size > 1) field[1] else "" + when (key) { + KEY_BT_QR_VER -> { + require(qrCodeVersion == -1) { "Duplicate qrCodeVersion: $input" } + qrCodeVersion = value.toInt() + } + KEY_BT_ADDRESS_TYPE -> { + require(sourceAddrType == BluetoothDevice.ADDRESS_TYPE_UNKNOWN) { + "Duplicate sourceAddrType: $input" + } + sourceAddrType = value.toInt() + } + KEY_BT_DEVICE -> { + require(sourceAddrString == null) { "Duplicate sourceAddr: $input" } + sourceAddrString = value.replace("-", ":") + } + KEY_BT_ADVERTISING_SID -> { + require(sourceAdvertiserSid == -1) { "Duplicate sourceAdvertiserSid: $input" } + sourceAdvertiserSid = value.toInt() + } + KEY_BT_BROADCAST_ID -> { + require(broadcastId == -1) { "Duplicate broadcastId: $input" } + broadcastId = value.toInt() + } + KEY_BT_BROADCAST_NAME -> { + require(broadcastName == null) { "Duplicate broadcastName: $input" } + broadcastName = String(Base64.decode(value, Base64.NO_WRAP)) + } + KEY_BT_PUBLIC_BROADCAST_DATA -> { + require(publicBroadcastMetadata == null) { + "Duplicate publicBroadcastMetadata $input" + } + publicBroadcastMetadata = BluetoothLeAudioContentMetadata + .fromRawBytes(Base64.decode(value, Base64.NO_WRAP)) + } + KEY_BT_SYNC_INTERVAL -> { + require(paSyncInterval == -1) { "Duplicate paSyncInterval: $input" } + paSyncInterval = value.toInt() + } + KEY_BT_BROADCAST_CODE -> { + require(broadcastCode == null) { "Duplicate broadcastCode: $input" } + broadcastCode = Base64.decode(value, Base64.NO_WRAP) + } + KEY_BT_ANDROID_VERSION -> { + require(androidVersion == null) { "Duplicate androidVersion: $input" } + androidVersion = value + Log.i(TAG, "QR code Android version: $androidVersion") + } + // Repeatable + KEY_BT_SUBGROUPS -> { + builder.addSubgroup(parseSubgroupData(value)) + } + // Repeatable + KEY_BT_VENDOR_SPECIFIC -> { + vendorDataList.add(parseVendorData(value)) + } + } + } + Log.d(TAG, "parseQrCodeToMetadata: sourceAddrType=$sourceAddrType, " + + "sourceAddr=$sourceAddrString, sourceAdvertiserSid=$sourceAdvertiserSid, " + + "broadcastId=$broadcastId, broadcastName=$broadcastName, " + + "publicBroadcastMetadata=${publicBroadcastMetadata != null}, " + + "paSyncInterval=$paSyncInterval, " + + "broadcastCode=${broadcastCode?.toString(Charsets.UTF_8)}") + Log.d(TAG, "Not used in current code, but part of the specification: " + + "qrCodeVersion=$qrCodeVersion, androidVersion=$androidVersion, " + + "vendorDataListSize=${vendorDataList.size}") + val adapter = BluetoothAdapter.getDefaultAdapter() + // add source device and set broadcast code + val device = adapter.getRemoteLeDevice(requireNotNull(sourceAddrString), sourceAddrType) + builder.apply { + setSourceDevice(device, sourceAddrType) + setSourceAdvertisingSid(sourceAdvertiserSid) + setBroadcastId(broadcastId) + setBroadcastName(broadcastName) + setPublicBroadcast(publicBroadcastMetadata != null) + setPublicBroadcastMetadata(publicBroadcastMetadata) + setPaSyncInterval(paSyncInterval) + setEncrypted(broadcastCode != null) + setBroadcastCode(broadcastCode) + // Presentation delay is unknown and not useful when adding source + // Broadcast sink needs to sync to the Broadcast source to get presentation delay + setPresentationDelayMicros(0) + } + return builder.build() + } + + private fun parseSubgroupData(input: String): BluetoothLeBroadcastSubgroup { + Log.d(TAG, "parseSubgroupData: $input") + val fields = input.split(DELIMITER_BT_LEVEL_2) + var bisSync: UInt? = null + var bisMask: UInt? = null + var metadata: ByteArray? = null + + fields.forEach { field -> + val(key, value) = field.split(DELIMITER_KEY_VALUE) + when (key) { + KEY_BTSG_BIS_SYNC -> { + require(bisSync == null) { "Duplicate bisSync: $input" } + bisSync = value.toUInt() + } + KEY_BTSG_BIS_MASK -> { + require(bisMask == null) { "Duplicate bisMask: $input" } + bisMask = value.toUInt() + } + KEY_BTSG_AUDIO_CONTENT -> { + require(metadata == null) { "Duplicate metadata: $input" } + metadata = Base64.decode(value, Base64.NO_WRAP) + } + } } + val channels = convertToChannels(requireNotNull(bisSync), requireNotNull(bisMask)) + val audioCodecConfigMetadata = BluetoothLeAudioCodecConfigMetadata.Builder() + .setAudioLocation(0).build() + return BluetoothLeBroadcastSubgroup.Builder().apply { + setCodecId(SUBGROUP_LC3_CODEC_ID) + setCodecSpecificConfig(audioCodecConfigMetadata) + setContentMetadata( + BluetoothLeAudioContentMetadata.fromRawBytes(metadata ?: ByteArray(0))) + channels.forEach(::addChannel) + }.build() + } + + private fun parseVendorData(input: String): Pair<Int, ByteArray?> { + var companyId = -1 + var data: ByteArray? = null + val fields = input.split(DELIMITER_BT_LEVEL_2) + fields.forEach { field -> + val(key, value) = field.split(DELIMITER_KEY_VALUE) + when (key) { + KEY_BTVSD_COMPANY_ID -> { + require(companyId == -1) { "Duplicate companyId: $input" } + companyId = value.toInt() + } + KEY_BTVSD_VENDOR_DATA -> { + require(data == null) { "Duplicate data: $input" } + data = Base64.decode(value, Base64.NO_WRAP) + } + } + } + return Pair(companyId, data) + } - private fun <T> createFromBytes(creator: Parcelable.Creator<T>, bytes: ByteArray): T = - Parcel.obtain().run { - unmarshall(bytes, 0, bytes.size) - setDataPosition(0) - val created = creator.createFromParcel(this) - recycle() - created + private fun getBisSyncFromChannels(channels: List<BluetoothLeBroadcastChannel>): UInt { + var bisSync = 0u + // channel index starts from 1 + channels.forEach { channel -> + if (channel.isSelected && channel.channelIndex > 0) { + bisSync = bisSync or (1u shl (channel.channelIndex - 1)) + } } + // No channel is selected means no preference on Android platform + return if (bisSync == 0u) BIS_SYNC_NO_PREFERENCE else bisSync + } + + private fun getBisMaskFromChannels(channels: List<BluetoothLeBroadcastChannel>): UInt { + var bisMask = 0u + // channel index starts from 1 + channels.forEach { channel -> + if (channel.channelIndex > 0) { + bisMask = bisMask or (1u shl (channel.channelIndex - 1)) + } + } + return bisMask + } + + private fun convertToChannels(bisSync: UInt, bisMask: UInt): + List<BluetoothLeBroadcastChannel> { + Log.d(TAG, "convertToChannels: bisSync=$bisSync, bisMask=$bisMask") + var selectionMask = bisSync + if (bisSync != BIS_SYNC_NO_PREFERENCE) { + require(bisMask == (bisMask or bisSync)) { + "bisSync($bisSync) must select a subset of bisMask($bisMask) if it has preferences" + } + } else { + // No channel preference means no channel is selected + selectionMask = 0u + } + val channels = mutableListOf<BluetoothLeBroadcastChannel>() + val audioCodecConfigMetadata = BluetoothLeAudioCodecConfigMetadata.Builder() + .setAudioLocation(0).build() + for (i in 0 until BIS_SYNC_MAX_CHANNEL) { + val channelMask = 1u shl i + if ((bisMask and channelMask) != 0u) { + val channel = BluetoothLeBroadcastChannel.Builder().apply { + setSelected((selectionMask and channelMask) != 0u) + setChannelIndex(i + 1) + setCodecMetadata(audioCodecConfigMetadata) + } + channels.add(channel.build()) + } + } + return channels + } }
\ No newline at end of file diff --git a/packages/SettingsLib/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/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/src/com/android/settingslib/bluetooth/BluetoothLeBroadcastMetadataExtTest.kt b/packages/SettingsLib/tests/unit/src/com/android/settingslib/bluetooth/BluetoothLeBroadcastMetadataExtTest.kt index 0e3590d96a14..27d7078774d5 100644 --- a/packages/SettingsLib/tests/unit/src/com/android/settingslib/bluetooth/BluetoothLeBroadcastMetadataExtTest.kt +++ b/packages/SettingsLib/tests/unit/src/com/android/settingslib/bluetooth/BluetoothLeBroadcastMetadataExtTest.kt @@ -34,24 +34,33 @@ class BluetoothLeBroadcastMetadataExtTest { @Test fun toQrCodeString() { val subgroup = BluetoothLeBroadcastSubgroup.Builder().apply { - setCodecId(100) + setCodecId(0x6) val audioCodecConfigMetadata = BluetoothLeAudioCodecConfigMetadata.Builder().build() setCodecSpecificConfig(audioCodecConfigMetadata) - setContentMetadata(BluetoothLeAudioContentMetadata.Builder().build()) + setContentMetadata(BluetoothLeAudioContentMetadata.Builder() + .setProgramInfo("Test").setLanguage("eng").build()) addChannel(BluetoothLeBroadcastChannel.Builder().apply { - setChannelIndex(1000) + setSelected(true) + setChannelIndex(2) + setCodecMetadata(audioCodecConfigMetadata) + }.build()) + addChannel(BluetoothLeBroadcastChannel.Builder().apply { + setSelected(true) + setChannelIndex(1) setCodecMetadata(audioCodecConfigMetadata) }.build()) }.build() val metadata = BluetoothLeBroadcastMetadata.Builder().apply { - setSourceDevice(Device, 0) + setSourceDevice(Device, BluetoothDevice.ADDRESS_TYPE_RANDOM) setSourceAdvertisingSid(1) - setBroadcastId(2) - setPaSyncInterval(3) + setBroadcastId(123456) + setBroadcastName("Test") + setPublicBroadcastMetadata(BluetoothLeAudioContentMetadata.Builder() + .setProgramInfo("pTest").build()) + setPaSyncInterval(160) setEncrypted(true) - setBroadcastCode(byteArrayOf(10, 11, 12, 13)) - setPresentationDelayMicros(4) + setBroadcastCode("TestCode".toByteArray(Charsets.UTF_8)) addSubgroup(subgroup) }.build() @@ -61,6 +70,108 @@ class BluetoothLeBroadcastMetadataExtTest { } @Test + fun toQrCodeString_NoChannelSelected() { + val subgroup = BluetoothLeBroadcastSubgroup.Builder().apply { + setCodecId(0x6) + val audioCodecConfigMetadata = BluetoothLeAudioCodecConfigMetadata.Builder().build() + setCodecSpecificConfig(audioCodecConfigMetadata) + setContentMetadata(BluetoothLeAudioContentMetadata.Builder() + .setProgramInfo("Test").setLanguage("eng").build()) + addChannel(BluetoothLeBroadcastChannel.Builder().apply { + setSelected(false) + setChannelIndex(2) + setCodecMetadata(audioCodecConfigMetadata) + }.build()) + addChannel(BluetoothLeBroadcastChannel.Builder().apply { + setSelected(false) + setChannelIndex(1) + setCodecMetadata(audioCodecConfigMetadata) + }.build()) + }.build() + + val metadata = BluetoothLeBroadcastMetadata.Builder().apply { + setSourceDevice(Device, BluetoothDevice.ADDRESS_TYPE_RANDOM) + setSourceAdvertisingSid(1) + setBroadcastId(123456) + setBroadcastName("Test") + setPublicBroadcastMetadata(BluetoothLeAudioContentMetadata.Builder() + .setProgramInfo("pTest").build()) + setPaSyncInterval(160) + setEncrypted(true) + setBroadcastCode("TestCode".toByteArray(Charsets.UTF_8)) + addSubgroup(subgroup) + }.build() + + val qrCodeString = metadata.toQrCodeString() + + val parsedMetadata = + BluetoothLeBroadcastMetadataExt.convertToBroadcastMetadata(qrCodeString)!! + + assertThat(parsedMetadata).isNotNull() + assertThat(parsedMetadata.subgroups).isNotNull() + assertThat(parsedMetadata.subgroups.size).isEqualTo(1) + assertThat(parsedMetadata.subgroups[0].channels).isNotNull() + assertThat(parsedMetadata.subgroups[0].channels.size).isEqualTo(2) + assertThat(parsedMetadata.subgroups[0].hasChannelPreference()).isFalse() + // Input order does not matter due to parsing through bisMask + assertThat(parsedMetadata.subgroups[0].channels[0].channelIndex).isEqualTo(1) + assertThat(parsedMetadata.subgroups[0].channels[0].isSelected).isFalse() + assertThat(parsedMetadata.subgroups[0].channels[1].channelIndex).isEqualTo(2) + assertThat(parsedMetadata.subgroups[0].channels[1].isSelected).isFalse() + } + + @Test + fun toQrCodeString_OneChannelSelected() { + val subgroup = BluetoothLeBroadcastSubgroup.Builder().apply { + setCodecId(0x6) + val audioCodecConfigMetadata = BluetoothLeAudioCodecConfigMetadata.Builder().build() + setCodecSpecificConfig(audioCodecConfigMetadata) + setContentMetadata(BluetoothLeAudioContentMetadata.Builder() + .setProgramInfo("Test").setLanguage("eng").build()) + addChannel(BluetoothLeBroadcastChannel.Builder().apply { + setSelected(false) + setChannelIndex(1) + setCodecMetadata(audioCodecConfigMetadata) + }.build()) + addChannel(BluetoothLeBroadcastChannel.Builder().apply { + setSelected(true) + setChannelIndex(2) + setCodecMetadata(audioCodecConfigMetadata) + }.build()) + }.build() + + val metadata = BluetoothLeBroadcastMetadata.Builder().apply { + setSourceDevice(Device, BluetoothDevice.ADDRESS_TYPE_RANDOM) + setSourceAdvertisingSid(1) + setBroadcastId(123456) + setBroadcastName("Test") + setPublicBroadcastMetadata(BluetoothLeAudioContentMetadata.Builder() + .setProgramInfo("pTest").build()) + setPaSyncInterval(160) + setEncrypted(true) + setBroadcastCode("TestCode".toByteArray(Charsets.UTF_8)) + addSubgroup(subgroup) + }.build() + + val qrCodeString = metadata.toQrCodeString() + + val parsedMetadata = + BluetoothLeBroadcastMetadataExt.convertToBroadcastMetadata(qrCodeString)!! + + assertThat(parsedMetadata).isNotNull() + assertThat(parsedMetadata.subgroups).isNotNull() + assertThat(parsedMetadata.subgroups.size).isEqualTo(1) + assertThat(parsedMetadata.subgroups[0].channels).isNotNull() + // Only selected channel can be recovered + assertThat(parsedMetadata.subgroups[0].channels.size).isEqualTo(2) + assertThat(parsedMetadata.subgroups[0].hasChannelPreference()).isTrue() + assertThat(parsedMetadata.subgroups[0].channels[0].channelIndex).isEqualTo(1) + assertThat(parsedMetadata.subgroups[0].channels[0].isSelected).isFalse() + assertThat(parsedMetadata.subgroups[0].channels[1].channelIndex).isEqualTo(2) + assertThat(parsedMetadata.subgroups[0].channels[1].isSelected).isTrue() + } + + @Test fun decodeAndEncodeAgain_sameString() { val metadata = BluetoothLeBroadcastMetadataExt.convertToBroadcastMetadata(QR_CODE_STRING)!! @@ -73,13 +184,12 @@ class BluetoothLeBroadcastMetadataExtTest { const val TEST_DEVICE_ADDRESS = "00:A1:A1:A1:A1:A1" val Device: BluetoothDevice = - BluetoothAdapter.getDefaultAdapter().getRemoteDevice(TEST_DEVICE_ADDRESS) + BluetoothAdapter.getDefaultAdapter().getRemoteLeDevice(TEST_DEVICE_ADDRESS, + BluetoothDevice.ADDRESS_TYPE_RANDOM) const val QR_CODE_STRING = - "BT:BluetoothLeBroadcastMetadata:AAAAAAEAAAABAAAAEQAAADAAMAA6AEEAMQA6AEEAMQA6AEEAMQA6" + - "AEEAMQA6AEEAMQAAAAAAAAABAAAAAgAAAAMAAAABAAAABAAAAAQAAAAKCwwNBAAAAAEAAAABAAAAZAAA" + - "AAAAAAABAAAAAAAAAAAAAAAGAAAABgAAAAUDAAAAAAAAAAAAAAAAAAAAAAAAAQAAAP//////////AAAA" + - "AAAAAAABAAAAAQAAAAAAAADoAwAAAQAAAAAAAAAAAAAABgAAAAYAAAAFAwAAAAAAAAAAAAAAAAAAAAAA" + - "AAAAAAD/////AAAAAAAAAAA=" + "BT:R:65536;T:1;D:00-A1-A1-A1-A1-A1;AS:1;B:123456;BN:VGVzdA==;" + + "PM:BgNwVGVzdA==;SI:160;C:VGVzdENvZGU=;SG:BS:3,BM:3,AC:BQNUZXN0BARlbmc=;" + + "VN:U;;" } }
\ No newline at end of file diff --git a/packages/SystemUI/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/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/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/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/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/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/KeyguardPreviewSmartspaceViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardPreviewSmartspaceViewBinder.kt index 387e9a6ff74c..f5e4c6adcb91 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardPreviewSmartspaceViewBinder.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardPreviewSmartspaceViewBinder.kt @@ -18,10 +18,12 @@ 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 { @@ -31,10 +33,11 @@ object KeyguardPreviewSmartspaceViewBinder { smartspace: View, viewModel: KeyguardPreviewSmartspaceViewModel, ) { - smartspace.repeatWhenAttached { repeatOnLifecycle(Lifecycle.State.STARTED) { - viewModel.smartSpaceTopPadding.collect { smartspace.setTopPadding(it) } + launch { viewModel.smartspaceTopPadding.collect { smartspace.setTopPadding(it) } } + + launch { viewModel.shouldHideSmartspace.collect { smartspace.isInvisible = it } } } } } 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 f9a8b988a27f..fe62bf4388e9 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 @@ -240,7 +240,7 @@ constructor( smartSpaceView?.let { it.setPaddingRelative(startPadding, topPadding, endPadding, 0) it.isClickable = false - + it.isInvisible = true parentView.addView( it, FrameLayout.LayoutParams( @@ -399,9 +399,6 @@ constructor( 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) { diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardPreviewSmartspaceViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardPreviewSmartspaceViewModel.kt index e60bb3412c31..bf51976e27f0 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardPreviewSmartspaceViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardPreviewSmartspaceViewModel.kt @@ -24,6 +24,7 @@ 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 smartspace. */ @@ -34,7 +35,7 @@ constructor( interactor: KeyguardClockInteractor, ) { - val smartSpaceTopPadding: Flow<Int> = + val smartspaceTopPadding: Flow<Int> = interactor.selectedClockSize.map { when (it) { SettingsClockSize.DYNAMIC -> getLargeClockSmartspaceTopPadding(context.resources) @@ -42,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/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/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/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/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/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/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/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/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/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java index 460ce4484a4b..1bd0675e95b6 100644 --- a/services/core/java/com/android/server/am/ActivityManagerService.java +++ b/services/core/java/com/android/server/am/ActivityManagerService.java @@ -4854,7 +4854,7 @@ public class ActivityManagerService extends IActivityManager.Stub } checkTime(startTime, "finishAttachApplicationInner: " + "after dispatching broadcasts"); - } catch (Exception e) { + } catch (BroadcastDeliveryFailedException e) { // If the app died trying to launch the receiver we declare it 'bad' Slog.wtf(TAG, "Exception thrown dispatching broadcasts in " + app, e); badApp = true; diff --git a/services/core/java/com/android/server/am/BroadcastDeliveryFailedException.java b/services/core/java/com/android/server/am/BroadcastDeliveryFailedException.java new file mode 100644 index 000000000000..9c9281635cf7 --- /dev/null +++ b/services/core/java/com/android/server/am/BroadcastDeliveryFailedException.java @@ -0,0 +1,32 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.am; + +import android.util.AndroidException; + +/** + * Exception to represent that broadcast could not be delivered. + */ +public class BroadcastDeliveryFailedException extends AndroidException { + public BroadcastDeliveryFailedException(String name) { + super(name); + } + + public BroadcastDeliveryFailedException(Exception cause) { + super(cause); + } +} diff --git a/services/core/java/com/android/server/am/BroadcastProcessQueue.java b/services/core/java/com/android/server/am/BroadcastProcessQueue.java index 9b53af2e9b5c..0b5b1cb2902e 100644 --- a/services/core/java/com/android/server/am/BroadcastProcessQueue.java +++ b/services/core/java/com/android/server/am/BroadcastProcessQueue.java @@ -280,6 +280,25 @@ class BroadcastProcessQueue { } /** + * Re-enqueue the active broadcast so that it can be made active and delivered again. In order + * to keep its previous position same to avoid issues with reordering, insert it at the head + * of the queue. + * + * Callers are responsible for clearing the active broadcast by calling + * {@link #makeActiveIdle()} after re-enqueuing it. + */ + public void reEnqueueActiveBroadcast() { + final BroadcastRecord record = getActive(); + final int recordIndex = getActiveIndex(); + + final SomeArgs broadcastArgs = SomeArgs.obtain(); + broadcastArgs.arg1 = record; + broadcastArgs.argi1 = recordIndex; + getQueueForBroadcast(record).addFirst(broadcastArgs); + onBroadcastEnqueued(record, recordIndex); + } + + /** * Searches from newest to oldest in the pending broadcast queues, and at the first matching * pending broadcast it finds, replaces it in-place and returns -- does not attempt to handle * "duplicate" broadcasts in the queue. diff --git a/services/core/java/com/android/server/am/BroadcastQueue.java b/services/core/java/com/android/server/am/BroadcastQueue.java index 8e76e5b5cf48..e38a2eefcd92 100644 --- a/services/core/java/com/android/server/am/BroadcastQueue.java +++ b/services/core/java/com/android/server/am/BroadcastQueue.java @@ -148,7 +148,8 @@ public abstract class BroadcastQueue { * dispatching a pending broadcast */ @GuardedBy("mService") - public abstract boolean onApplicationAttachedLocked(@NonNull ProcessRecord app); + public abstract boolean onApplicationAttachedLocked(@NonNull ProcessRecord app) + throws BroadcastDeliveryFailedException; /** * Signal from OS internals that the given process has timed out during diff --git a/services/core/java/com/android/server/am/BroadcastQueueImpl.java b/services/core/java/com/android/server/am/BroadcastQueueImpl.java index e389821595a0..4eedfe259633 100644 --- a/services/core/java/com/android/server/am/BroadcastQueueImpl.java +++ b/services/core/java/com/android/server/am/BroadcastQueueImpl.java @@ -438,7 +438,8 @@ public class BroadcastQueueImpl extends BroadcastQueue { scheduleBroadcastsLocked(); } - public boolean onApplicationAttachedLocked(ProcessRecord app) { + public boolean onApplicationAttachedLocked(ProcessRecord app) + throws BroadcastDeliveryFailedException { updateUidReadyForBootCompletedBroadcastLocked(app.uid); if (mPendingBroadcast != null && mPendingBroadcast.curApp == app) { @@ -460,7 +461,8 @@ public class BroadcastQueueImpl extends BroadcastQueue { skipCurrentOrPendingReceiverLocked(app); } - public boolean sendPendingBroadcastsLocked(ProcessRecord app) { + public boolean sendPendingBroadcastsLocked(ProcessRecord app) + throws BroadcastDeliveryFailedException { boolean didSomething = false; final BroadcastRecord br = mPendingBroadcast; if (br != null && br.curApp.getPid() > 0 && br.curApp.getPid() == app.getPid()) { @@ -483,7 +485,7 @@ public class BroadcastQueueImpl extends BroadcastQueue { scheduleBroadcastsLocked(); // We need to reset the state if we failed to start the receiver. br.state = BroadcastRecord.IDLE; - throw new RuntimeException(e.getMessage()); + throw new BroadcastDeliveryFailedException(e); } } return didSomething; diff --git a/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java b/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java index 22c0855ec54f..bb5fcbec7010 100644 --- a/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java +++ b/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java @@ -469,10 +469,15 @@ class BroadcastQueueModernImpl extends BroadcastQueue { if (DEBUG_BROADCAST) logv("Promoting " + queue + " from runnable to running; process is " + queue.app); promoteToRunningLocked(queue); - final boolean completed; + boolean completed; if (processWarm) { updateOomAdj |= queue.runningOomAdjusted; - completed = scheduleReceiverWarmLocked(queue); + try { + completed = scheduleReceiverWarmLocked(queue); + } catch (BroadcastDeliveryFailedException e) { + reEnqueueActiveBroadcast(queue); + completed = true; + } } else { completed = scheduleReceiverColdLocked(queue); } @@ -513,7 +518,9 @@ class BroadcastQueueModernImpl extends BroadcastQueue { private void clearInvalidPendingColdStart() { logw("Clearing invalid pending cold start: " + mRunningColdStart); - onApplicationCleanupLocked(mRunningColdStart.app); + mRunningColdStart.reEnqueueActiveBroadcast(); + demoteFromRunningLocked(mRunningColdStart); + clearRunningColdStart(); } private void checkPendingColdStartValidity() { @@ -535,8 +542,19 @@ class BroadcastQueueModernImpl extends BroadcastQueue { } } + private void reEnqueueActiveBroadcast(@NonNull BroadcastProcessQueue queue) { + checkState(queue.isActive(), "isActive"); + + final BroadcastRecord record = queue.getActive(); + final int index = queue.getActiveIndex(); + setDeliveryState(queue, queue.app, record, index, record.receivers.get(index), + BroadcastRecord.DELIVERY_PENDING, "reEnqueueActiveBroadcast"); + queue.reEnqueueActiveBroadcast(); + } + @Override - public boolean onApplicationAttachedLocked(@NonNull ProcessRecord app) { + public boolean onApplicationAttachedLocked(@NonNull ProcessRecord app) + throws BroadcastDeliveryFailedException { // Process records can be recycled, so always start by looking up the // relevant per-process queue final BroadcastProcessQueue queue = getProcessQueue(app); @@ -557,8 +575,14 @@ class BroadcastQueueModernImpl extends BroadcastQueue { queue.traceProcessEnd(); queue.traceProcessRunningBegin(); - if (scheduleReceiverWarmLocked(queue)) { + try { + if (scheduleReceiverWarmLocked(queue)) { + demoteFromRunningLocked(queue); + } + } catch (BroadcastDeliveryFailedException e) { + reEnqueueActiveBroadcast(queue); demoteFromRunningLocked(queue); + throw e; } // We might be willing to kick off another cold start @@ -588,14 +612,7 @@ class BroadcastQueueModernImpl extends BroadcastQueue { } if ((mRunningColdStart != null) && (mRunningColdStart == queue)) { - // We've been waiting for this app to cold start, and it had - // trouble; clear the slot and fail delivery below - mRunningColdStart = null; - - queue.traceProcessEnd(); - - // We might be willing to kick off another cold start - enqueueUpdateRunningList(); + clearRunningColdStart(); } if (queue != null) { @@ -618,6 +635,17 @@ class BroadcastQueueModernImpl extends BroadcastQueue { } } + private void clearRunningColdStart() { + mRunningColdStart.traceProcessEnd(); + + // We've been waiting for this app to cold start, and it had + // trouble; clear the slot and fail delivery below + mRunningColdStart = null; + + // We might be willing to kick off another cold start + enqueueUpdateRunningList(); + } + @Override public int getPreferredSchedulingGroupLocked(@NonNull ProcessRecord app) { final BroadcastProcessQueue queue = getProcessQueue(app); @@ -836,7 +864,8 @@ class BroadcastQueueModernImpl extends BroadcastQueue { */ @CheckResult @GuardedBy("mService") - private boolean scheduleReceiverWarmLocked(@NonNull BroadcastProcessQueue queue) { + private boolean scheduleReceiverWarmLocked(@NonNull BroadcastProcessQueue queue) + throws BroadcastDeliveryFailedException { checkState(queue.isActive(), "isActive"); final int cookie = traceBegin("scheduleReceiverWarmLocked"); @@ -918,7 +947,7 @@ class BroadcastQueueModernImpl extends BroadcastQueue { */ @CheckResult private boolean dispatchReceivers(@NonNull BroadcastProcessQueue queue, - @NonNull BroadcastRecord r, int index) { + @NonNull BroadcastRecord r, int index) throws BroadcastDeliveryFailedException { final ProcessRecord app = queue.app; final Object receiver = r.receivers.get(index); @@ -1005,6 +1034,11 @@ class BroadcastQueueModernImpl extends BroadcastQueue { logw(msg); app.killLocked("Can't deliver broadcast", ApplicationExitInfo.REASON_OTHER, ApplicationExitInfo.SUBREASON_UNDELIVERED_BROADCAST, true); + // If we were trying to deliver a manifest broadcast, throw the error as we need + // to try redelivering the broadcast to this receiver. + if (receiver instanceof ResolveInfo) { + throw new BroadcastDeliveryFailedException(e); + } finishReceiverActiveLocked(queue, BroadcastRecord.DELIVERY_FAILURE, "remote app"); return false; @@ -1092,7 +1126,7 @@ class BroadcastQueueModernImpl extends BroadcastQueue { boolean waitForServices) { final BroadcastProcessQueue queue = getProcessQueue(app); if ((queue == null) || !queue.isActive()) { - logw("Ignoring finish; no active broadcast for " + queue); + logw("Ignoring finishReceiverLocked; no active broadcast for " + queue); return false; } @@ -1129,7 +1163,13 @@ class BroadcastQueueModernImpl extends BroadcastQueue { // We're on a roll; move onto the next broadcast for this process queue.makeActiveNextPending(); - if (scheduleReceiverWarmLocked(queue)) { + try { + if (scheduleReceiverWarmLocked(queue)) { + demoteFromRunningLocked(queue); + return true; + } + } catch (BroadcastDeliveryFailedException e) { + reEnqueueActiveBroadcast(queue); demoteFromRunningLocked(queue); return true; } @@ -1166,7 +1206,7 @@ class BroadcastQueueModernImpl extends BroadcastQueue { private void finishReceiverActiveLocked(@NonNull BroadcastProcessQueue queue, @DeliveryState int deliveryState, @NonNull String reason) { if (!queue.isActive()) { - logw("Ignoring finish; no active broadcast for " + queue); + logw("Ignoring finishReceiverActiveLocked; no active broadcast for " + queue); return; } diff --git a/services/core/java/com/android/server/am/BroadcastRecord.java b/services/core/java/com/android/server/am/BroadcastRecord.java index 564b1fe8de34..863dd63a2922 100644 --- a/services/core/java/com/android/server/am/BroadcastRecord.java +++ b/services/core/java/com/android/server/am/BroadcastRecord.java @@ -699,6 +699,9 @@ final class BroadcastRecord extends Binder { break; } switch (newDeliveryState) { + case DELIVERY_PENDING: + scheduledTime[index] = 0; + break; case DELIVERY_SCHEDULED: scheduledTime[index] = SystemClock.uptimeMillis(); break; diff --git a/services/core/java/com/android/server/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/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/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/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/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/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/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/wmtests/src/com/android/server/wm/DisplayPolicyTests.java b/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyTests.java index d0eb1b2ec220..b0609ddfddfd 100644 --- a/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyTests.java @@ -362,10 +362,12 @@ public class DisplayPolicyTests extends WindowTestsBase { 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()) 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) } } |