diff options
213 files changed, 3259 insertions, 1306 deletions
diff --git a/core/java/android/app/ServiceStartArgs.java b/core/java/android/app/ServiceStartArgs.java index 0b000af56400..9c52367392a5 100644 --- a/core/java/android/app/ServiceStartArgs.java +++ b/core/java/android/app/ServiceStartArgs.java @@ -49,7 +49,7 @@ public class ServiceStartArgs implements Parcelable { public void writeToParcel(Parcel out, int flags) { out.writeInt(taskRemoved ? 1 : 0); out.writeInt(startId); - out.writeInt(flags); + out.writeInt(this.flags); if (args != null) { out.writeInt(1); args.writeToParcel(out, 0); diff --git a/core/java/android/app/WallpaperInfo.java b/core/java/android/app/WallpaperInfo.java index 99d406446dae..b29e73a717f4 100644 --- a/core/java/android/app/WallpaperInfo.java +++ b/core/java/android/app/WallpaperInfo.java @@ -292,12 +292,12 @@ public final class WallpaperInfo implements Parcelable { packageName = mService.serviceInfo.packageName; applicationInfo = mService.serviceInfo.applicationInfo; } - String contextUriString = pm.getText( - packageName, mContextUriResource, applicationInfo).toString(); - if (contextUriString == null) { + CharSequence contextUriCharSequence = pm.getText( + packageName, mContextUriResource, applicationInfo); + if (contextUriCharSequence == null) { return null; } - return Uri.parse(contextUriString); + return Uri.parse(contextUriCharSequence.toString()); } /** diff --git a/core/java/android/content/pm/PackageInstaller.java b/core/java/android/content/pm/PackageInstaller.java index 96118f6c43ea..105b38a40825 100644 --- a/core/java/android/content/pm/PackageInstaller.java +++ b/core/java/android/content/pm/PackageInstaller.java @@ -444,6 +444,10 @@ public class PackageInstaller { * exist, it may be missing native code for the ABIs supported by the * device, or it requires a newer SDK version, etc. * + * Starting in {@link Build.VERSION_CODES#UPSIDE_DOWN_CAKE}, an app with only 32-bit native + * code can still be installed on a device that supports both 64-bit and 32-bit ABIs. + * However, a warning dialog will be displayed when the app is launched. + * * @see #EXTRA_STATUS_MESSAGE */ public static final int STATUS_FAILURE_INCOMPATIBLE = 7; diff --git a/core/java/android/service/wallpaper/WallpaperService.java b/core/java/android/service/wallpaper/WallpaperService.java index 7f0a666da91f..4e086c2ad134 100644 --- a/core/java/android/service/wallpaper/WallpaperService.java +++ b/core/java/android/service/wallpaper/WallpaperService.java @@ -1391,7 +1391,7 @@ public abstract class WallpaperService extends Service { Trace.endSection(); } - if (redrawNeeded) { + if (redrawNeeded || sizeChanged) { Trace.beginSection("WPMS.Engine.onSurfaceRedrawNeeded"); onSurfaceRedrawNeeded(mSurfaceHolder); Trace.endSection(); diff --git a/core/java/android/speech/SpeechRecognizer.java b/core/java/android/speech/SpeechRecognizer.java index dacb25ca1628..bb5dd7f0cdfe 100644 --- a/core/java/android/speech/SpeechRecognizer.java +++ b/core/java/android/speech/SpeechRecognizer.java @@ -812,7 +812,7 @@ public class SpeechRecognizer { Intent recognizerIntent, Executor callbackExecutor, RecognitionSupportCallback recognitionSupportCallback) { - if (!maybeInitializeManagerService()) { + if (!maybeInitializeManagerService() || !checkOpenConnection()) { return; } try { @@ -831,7 +831,7 @@ public class SpeechRecognizer { Intent recognizerIntent, @Nullable Executor callbackExecutor, @Nullable ModelDownloadListener modelDownloadListener) { - if (!maybeInitializeManagerService()) { + if (!maybeInitializeManagerService() || !checkOpenConnection()) { return; } diff --git a/core/java/android/view/InsetsController.java b/core/java/android/view/InsetsController.java index d8bff1c4cb10..f570c6d15672 100644 --- a/core/java/android/view/InsetsController.java +++ b/core/java/android/view/InsetsController.java @@ -658,6 +658,9 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation /** Set of inset types which cannot be controlled by the user animation */ private @InsetsType int mDisabledUserAnimationInsetsTypes; + /** Set of inset types which are existing */ + private @InsetsType int mExistingTypes = 0; + /** Set of inset types which are visible */ private @InsetsType int mVisibleTypes = WindowInsets.Type.defaultVisible(); @@ -906,6 +909,12 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation } mVisibleTypes = visibleTypes; } + if (mExistingTypes != existingTypes) { + if (WindowInsets.Type.hasCompatSystemBars(mExistingTypes ^ existingTypes)) { + mCompatSysUiVisibilityStaled = true; + } + mExistingTypes = existingTypes; + } InsetsState.traverse(mState, newState, mRemoveGoneSources); updateDisabledUserAnimationTypes(disabledUserAnimationTypes); @@ -1662,7 +1671,8 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation if (mCompatSysUiVisibilityStaled) { mCompatSysUiVisibilityStaled = false; mHost.updateCompatSysUiVisibility( - mVisibleTypes, mRequestedVisibleTypes, mControllableTypes); + // Treat non-existing types as controllable types for compatibility. + mVisibleTypes, mRequestedVisibleTypes, mControllableTypes | ~mExistingTypes); } } diff --git a/core/java/android/view/SurfaceControl.java b/core/java/android/view/SurfaceControl.java index ddaa71c6b1b5..c11f4975149d 100644 --- a/core/java/android/view/SurfaceControl.java +++ b/core/java/android/view/SurfaceControl.java @@ -786,7 +786,11 @@ public final class SurfaceControl implements Parcelable { mReleaseStack = null; } setUnreleasedWarningCallSite(callsite); - addToRegistry(); + if (nativeObject != 0) { + // Only add valid surface controls to the registry. This is called at the end of this + // method since its information is dumped if the process threshold is reached. + addToRegistry(); + } } /** @@ -893,6 +897,10 @@ public final class SurfaceControl implements Parcelable { "Only buffer layers can set a valid buffer size."); } + if (mName == null) { + Log.w(TAG, "Missing name for SurfaceControl", new Throwable()); + } + if ((mFlags & FX_SURFACE_MASK) == FX_SURFACE_NORMAL) { setBLASTLayer(); } @@ -1254,6 +1262,9 @@ public final class SurfaceControl implements Parcelable { } /** + * Note: Most callers should use {@link SurfaceControl.Builder} or one of the other constructors + * to build an instance of a SurfaceControl. This constructor is mainly used for + * unparceling and passing into an AIDL call as an out parameter. * @hide */ public SurfaceControl() { @@ -2495,6 +2506,7 @@ public final class SurfaceControl implements Parcelable { public static SurfaceControl mirrorSurface(SurfaceControl mirrorOf) { long nativeObj = nativeMirrorSurface(mirrorOf.mNativeObject); SurfaceControl sc = new SurfaceControl(); + sc.mName = mirrorOf.mName + " (mirror)"; sc.assignNativeObject(nativeObj, "mirrorSurface"); return sc; } diff --git a/core/java/android/view/SurfaceControlViewHost.java b/core/java/android/view/SurfaceControlViewHost.java index d9872174f9be..c8cf7d9a5194 100644 --- a/core/java/android/view/SurfaceControlViewHost.java +++ b/core/java/android/view/SurfaceControlViewHost.java @@ -171,8 +171,7 @@ public class SurfaceControlViewHost { public SurfacePackage(@NonNull SurfacePackage other) { SurfaceControl otherSurfaceControl = other.mSurfaceControl; if (otherSurfaceControl != null && otherSurfaceControl.isValid()) { - mSurfaceControl = new SurfaceControl(); - mSurfaceControl.copyFrom(otherSurfaceControl, "SurfacePackage"); + mSurfaceControl = new SurfaceControl(otherSurfaceControl, "SurfacePackage"); } mAccessibilityEmbeddedConnection = other.mAccessibilityEmbeddedConnection; mInputToken = other.mInputToken; diff --git a/core/java/android/view/WindowManager.java b/core/java/android/view/WindowManager.java index e95ba797a985..e40f8e78dafc 100644 --- a/core/java/android/view/WindowManager.java +++ b/core/java/android/view/WindowManager.java @@ -555,6 +555,13 @@ public interface WindowManager extends ViewManager { int TRANSIT_FLAG_KEYGUARD_GOING_AWAY_TO_LAUNCHER_CLEAR_SNAPSHOT = (1 << 9); // 0x200 /** + * Transition flag: The transition is prepared when nothing is visible on screen, e.g. screen + * is off. The animation handlers can decide whether to skip animations. + * @hide + */ + int TRANSIT_FLAG_INVISIBLE = (1 << 10); // 0x400 + + /** * @hide */ @IntDef(flag = true, prefix = { "TRANSIT_FLAG_" }, value = { @@ -567,7 +574,8 @@ public interface WindowManager extends ViewManager { TRANSIT_FLAG_KEYGUARD_LOCKED, TRANSIT_FLAG_IS_RECENTS, TRANSIT_FLAG_KEYGUARD_GOING_AWAY, - TRANSIT_FLAG_KEYGUARD_GOING_AWAY_TO_LAUNCHER_CLEAR_SNAPSHOT + TRANSIT_FLAG_KEYGUARD_GOING_AWAY_TO_LAUNCHER_CLEAR_SNAPSHOT, + TRANSIT_FLAG_INVISIBLE, }) @Retention(RetentionPolicy.SOURCE) @interface TransitionFlags {} diff --git a/core/java/android/view/contentprotection/OWNERS b/core/java/android/view/contentprotection/OWNERS new file mode 100644 index 000000000000..b3583a7f6ab1 --- /dev/null +++ b/core/java/android/view/contentprotection/OWNERS @@ -0,0 +1,4 @@ +# Bug component: 544200 + +include /core/java/android/view/contentcapture/OWNERS + diff --git a/core/java/android/widget/RemoteViews.java b/core/java/android/widget/RemoteViews.java index 5525336735b7..7f96266a1f69 100644 --- a/core/java/android/widget/RemoteViews.java +++ b/core/java/android/widget/RemoteViews.java @@ -726,6 +726,11 @@ public class RemoteViews implements Parcelable, Filter { mActions.get(i).visitUris(visitor); } } + if (mSizedRemoteViews != null) { + for (int i = 0; i < mSizedRemoteViews.size(); i++) { + mSizedRemoteViews.get(i).visitUris(visitor); + } + } if (mLandscape != null) { mLandscape.visitUris(visitor); } @@ -1845,7 +1850,7 @@ public class RemoteViews implements Parcelable, Filter { } @Override - public final void visitUris(@NonNull Consumer<Uri> visitor) { + public void visitUris(@NonNull Consumer<Uri> visitor) { switch (this.type) { case URI: final Uri uri = (Uri) getParameterValue(null); @@ -2308,6 +2313,14 @@ public class RemoteViews implements Parcelable, Filter { public int getActionTag() { return NIGHT_MODE_REFLECTION_ACTION_TAG; } + + @Override + public void visitUris(@NonNull Consumer<Uri> visitor) { + if (this.type == ICON) { + visitIconUri((Icon) mDarkValue, visitor); + visitIconUri((Icon) mLightValue, visitor); + } + } } /** diff --git a/core/java/android/window/SnapshotDrawerUtils.java b/core/java/android/window/SnapshotDrawerUtils.java index 52e17ca4ab0d..f40874b77536 100644 --- a/core/java/android/window/SnapshotDrawerUtils.java +++ b/core/java/android/window/SnapshotDrawerUtils.java @@ -43,6 +43,7 @@ import static com.android.internal.policy.DecorView.NAVIGATION_BAR_COLOR_VIEW_AT import static com.android.internal.policy.DecorView.STATUS_BAR_COLOR_VIEW_ATTRIBUTES; import static com.android.internal.policy.DecorView.getNavigationBarRect; +import android.annotation.NonNull; import android.annotation.Nullable; import android.app.ActivityManager; import android.app.ActivityThread; @@ -183,7 +184,7 @@ public class SnapshotDrawerUtils { // We consider nearly matched dimensions as there can be rounding errors and the user // won't notice very minute differences from scaling one dimension more than the other - final boolean aspectRatioMismatch = !isAspectRatioMatch(mFrame, mSnapshot); + boolean aspectRatioMismatch = !isAspectRatioMatch(mFrame, mSnapshot); // Keep a reference to it such that it doesn't get destroyed when finalized. SurfaceControl childSurfaceControl = new SurfaceControl.Builder(session) @@ -199,8 +200,20 @@ public class SnapshotDrawerUtils { // still hidden. mTransaction.show(childSurfaceControl); if (aspectRatioMismatch) { - // Clip off ugly navigation bar. - final Rect crop = calculateSnapshotCrop(); + Rect crop = null; + final Rect letterboxInsets = mSnapshot.getLetterboxInsets(); + if (letterboxInsets.left != 0 || letterboxInsets.top != 0 + || letterboxInsets.right != 0 || letterboxInsets.bottom != 0) { + // Clip off letterbox. + crop = calculateSnapshotCrop(letterboxInsets); + // If the snapshot can cover the frame, then no need to draw background. + aspectRatioMismatch = !isAspectRatioMatch(mFrame, crop); + } + // if letterbox doesn't match window frame, try crop by content insets + if (aspectRatioMismatch) { + // Clip off ugly navigation bar. + crop = calculateSnapshotCrop(mSnapshot.getContentInsets()); + } frame = calculateSnapshotFrame(crop); mTransaction.setWindowCrop(childSurfaceControl, crop); mTransaction.setPosition(childSurfaceControl, frame.left, frame.top); @@ -242,14 +255,13 @@ public class SnapshotDrawerUtils { /** * Calculates the snapshot crop in snapshot coordinate space. - * + * @param insets Content insets or Letterbox insets * @return crop rect in snapshot coordinate space. */ - Rect calculateSnapshotCrop() { + Rect calculateSnapshotCrop(@NonNull Rect insets) { final Rect rect = new Rect(); final HardwareBuffer snapshot = mSnapshot.getHardwareBuffer(); rect.set(0, 0, snapshot.getWidth(), snapshot.getHeight()); - final Rect insets = mSnapshot.getContentInsets(); final float scaleX = (float) snapshot.getWidth() / mSnapshot.getTaskSize().x; final float scaleY = (float) snapshot.getHeight() / mSnapshot.getTaskSize().y; @@ -334,6 +346,15 @@ public class SnapshotDrawerUtils { - ((float) frame.width() / frame.height())) <= 0.01f; } + private static boolean isAspectRatioMatch(Rect frame1, Rect frame2) { + if (frame1.isEmpty() || frame2.isEmpty()) { + return false; + } + return Math.abs( + ((float) frame2.width() / frame2.height()) + - ((float) frame1.width() / frame1.height())) <= 0.01f; + } + /** * Get or create a TaskDescription from a RunningTaskInfo. */ diff --git a/core/java/android/window/SurfaceSyncGroup.java b/core/java/android/window/SurfaceSyncGroup.java index dfdff9ef97ce..5d14698c82b3 100644 --- a/core/java/android/window/SurfaceSyncGroup.java +++ b/core/java/android/window/SurfaceSyncGroup.java @@ -26,6 +26,7 @@ import android.os.Debug; import android.os.Handler; import android.os.HandlerThread; import android.os.IBinder; +import android.os.Looper; import android.os.RemoteException; import android.os.Trace; import android.util.ArraySet; @@ -800,22 +801,25 @@ public final class SurfaceSyncGroup { } private void addTimeout() { + Looper looper = null; synchronized (sHandlerThreadLock) { if (sHandlerThread == null) { sHandlerThread = new HandlerThread("SurfaceSyncGroupTimer"); sHandlerThread.start(); } + + looper = sHandlerThread.getLooper(); } synchronized (mLock) { - if (mTimeoutAdded || mTimeoutDisabled) { + if (mTimeoutAdded || mTimeoutDisabled || looper == null) { // We only need one timeout for the entire SurfaceSyncGroup since we just want to // ensure it doesn't stay stuck forever. return; } if (mHandler == null) { - mHandler = new Handler(sHandlerThread.getLooper()); + mHandler = new Handler(looper); } mTimeoutAdded = true; diff --git a/core/java/android/window/WindowOnBackInvokedDispatcher.java b/core/java/android/window/WindowOnBackInvokedDispatcher.java index 22b2ec09c034..421d9983d6b7 100644 --- a/core/java/android/window/WindowOnBackInvokedDispatcher.java +++ b/core/java/android/window/WindowOnBackInvokedDispatcher.java @@ -31,6 +31,8 @@ import android.util.Log; import android.view.IWindow; import android.view.IWindowSession; +import androidx.annotation.VisibleForTesting; + import java.io.PrintWriter; import java.lang.ref.WeakReference; import java.util.ArrayList; @@ -66,7 +68,9 @@ public class WindowOnBackInvokedDispatcher implements OnBackInvokedDispatcher { /** Convenience hashmap to quickly decide if a callback has been added. */ private final HashMap<OnBackInvokedCallback, Integer> mAllCallbacks = new HashMap<>(); /** Holds all callbacks by priorities. */ - private final TreeMap<Integer, ArrayList<OnBackInvokedCallback>> + + @VisibleForTesting + public final TreeMap<Integer, ArrayList<OnBackInvokedCallback>> mOnBackInvokedCallbacks = new TreeMap<>(); private Checker mChecker; diff --git a/core/java/com/android/internal/accessibility/util/AccessibilityStatsLogUtils.java b/core/java/com/android/internal/accessibility/util/AccessibilityStatsLogUtils.java index 1f7640d97b4d..7c4252e7aa5d 100644 --- a/core/java/com/android/internal/accessibility/util/AccessibilityStatsLogUtils.java +++ b/core/java/com/android/internal/accessibility/util/AccessibilityStatsLogUtils.java @@ -258,6 +258,10 @@ public final class AccessibilityStatsLogUtils { } private static int convertToLoggingMagnificationScale(float scale) { - return (int) (scale * 100); + // per b/269366674, we make every 10% a bucket for both privacy and readability concern. + // For example + // 1. both 2.30f(230%) and 2.36f(236%) would return 230 as bucket id. + // 2. bucket id 370 means scale range in [370%, 379%] + return ((int) (scale * 10)) * 10; } } diff --git a/core/java/com/android/internal/config/sysui/SystemUiSystemPropertiesFlags.java b/core/java/com/android/internal/config/sysui/SystemUiSystemPropertiesFlags.java index 86c2893c9fab..3d95dd341cc0 100644 --- a/core/java/com/android/internal/config/sysui/SystemUiSystemPropertiesFlags.java +++ b/core/java/com/android/internal/config/sysui/SystemUiSystemPropertiesFlags.java @@ -80,7 +80,7 @@ public class SystemUiSystemPropertiesFlags { /** Gating the logging of DND state change events. */ public static final Flag LOG_DND_STATE_EVENTS = - devFlag("persist.sysui.notification.log_dnd_state_events"); + releasedFlag("persist.sysui.notification.log_dnd_state_events"); } //// == End of flags. Everything below this line is the implementation. == //// diff --git a/core/java/com/android/internal/widget/ImageFloatingTextView.java b/core/java/com/android/internal/widget/ImageFloatingTextView.java index 2695b9c7651f..1ac5e1f12bfa 100644 --- a/core/java/com/android/internal/widget/ImageFloatingTextView.java +++ b/core/java/com/android/internal/widget/ImageFloatingTextView.java @@ -82,7 +82,7 @@ public class ImageFloatingTextView extends TextView { .setIncludePad(getIncludeFontPadding()) .setUseLineSpacingFromFallbacks(true) .setBreakStrategy(Layout.BREAK_STRATEGY_HIGH_QUALITY) - .setHyphenationFrequency(Layout.HYPHENATION_FREQUENCY_FULL); + .setHyphenationFrequency(Layout.HYPHENATION_FREQUENCY_FULL_FAST); int maxLines; if (mMaxLinesForHeight > 0) { maxLines = mMaxLinesForHeight; diff --git a/core/res/res/layout/language_picker_section_header.xml b/core/res/res/layout/language_picker_section_header.xml index 58042f9a42f8..54ac6776dd04 100644 --- a/core/res/res/layout/language_picker_section_header.xml +++ b/core/res/res/layout/language_picker_section_header.xml @@ -25,4 +25,5 @@ android:textColor="?android:attr/colorAccent" android:textStyle="bold" android:id="@+id/language_picker_header" - tools:text="@string/language_picker_section_all"/> + tools:text="@string/language_picker_section_all" + android:scrollbars="none"/> diff --git a/core/tests/coretests/src/android/content/OWNERS b/core/tests/coretests/src/android/content/OWNERS index a69c6ffef8d8..6f38b848853b 100644 --- a/core/tests/coretests/src/android/content/OWNERS +++ b/core/tests/coretests/src/android/content/OWNERS @@ -5,3 +5,5 @@ per-file *Launcher* = file:/core/java/android/content/pm/LAUNCHER_OWNERS per-file *Shortcut* = file:/core/java/android/content/pm/SHORTCUT_OWNERS per-file *ComponentCallbacks* = file:/services/core/java/com/android/server/wm/OWNERS per-file *ComponentCallbacks* = charlesccchen@google.com +per-file ContentCaptureOptions* = file:/core/java/android/view/contentcapture/OWNERS + diff --git a/core/tests/coretests/src/android/content/res/FontScaleConverterActivityTest.java b/core/tests/coretests/src/android/content/res/FontScaleConverterActivityTest.java index 6e8e93a8c86b..b2e42ba90152 100644 --- a/core/tests/coretests/src/android/content/res/FontScaleConverterActivityTest.java +++ b/core/tests/coretests/src/android/content/res/FontScaleConverterActivityTest.java @@ -26,6 +26,7 @@ import android.app.Activity; import android.compat.testing.PlatformCompatChangeRule; import android.os.Bundle; import android.platform.test.annotations.IwTest; +import android.platform.test.annotations.PlatinumTest; import android.platform.test.annotations.Presubmit; import android.provider.Settings; import android.util.PollingCheck; @@ -72,6 +73,7 @@ public class FontScaleConverterActivityTest { restoreSystemFontScaleToDefault(); } + @PlatinumTest(focusArea = "accessibility") @IwTest(focusArea = "accessibility") @Test public void testFontsScaleNonLinearly() { @@ -103,6 +105,7 @@ public class FontScaleConverterActivityTest { ))); } + @PlatinumTest(focusArea = "accessibility") @IwTest(focusArea = "accessibility") @Test public void testOnConfigurationChanged_doesNotCrash() { @@ -117,6 +120,7 @@ public class FontScaleConverterActivityTest { }); } + @PlatinumTest(focusArea = "accessibility") @IwTest(focusArea = "accessibility") @Test public void testUpdateConfiguration_doesNotCrash() { diff --git a/core/tests/coretests/src/android/view/SurfaceControlRegistryTests.java b/core/tests/coretests/src/android/view/SurfaceControlRegistryTests.java index d10ba7ccbac4..e117051ba9de 100644 --- a/core/tests/coretests/src/android/view/SurfaceControlRegistryTests.java +++ b/core/tests/coretests/src/android/view/SurfaceControlRegistryTests.java @@ -104,6 +104,18 @@ public class SurfaceControlRegistryTests { } @Test + public void testInvalidSurfaceControlNotAddedToRegistry() { + int hash0 = SurfaceControlRegistry.getProcessInstance().hashCode(); + // Verify no changes to the registry when dealing with invalid surface controls + SurfaceControl sc0 = new SurfaceControl(); + SurfaceControl sc1 = new SurfaceControl(sc0, "test"); + assertEquals(hash0, SurfaceControlRegistry.getProcessInstance().hashCode()); + sc0.release(); + sc1.release(); + assertEquals(hash0, SurfaceControlRegistry.getProcessInstance().hashCode()); + } + + @Test public void testThresholds() { SurfaceControlRegistry registry = SurfaceControlRegistry.getProcessInstance(); TestReporter reporter = new TestReporter(); diff --git a/core/tests/coretests/src/android/view/contentprotection/OWNERS b/core/tests/coretests/src/android/view/contentprotection/OWNERS new file mode 100644 index 000000000000..b3583a7f6ab1 --- /dev/null +++ b/core/tests/coretests/src/android/view/contentprotection/OWNERS @@ -0,0 +1,4 @@ +# Bug component: 544200 + +include /core/java/android/view/contentcapture/OWNERS + diff --git a/core/tests/coretests/src/android/widget/RemoteViewsTest.java b/core/tests/coretests/src/android/widget/RemoteViewsTest.java index 78798015729f..73aa93603e56 100644 --- a/core/tests/coretests/src/android/widget/RemoteViewsTest.java +++ b/core/tests/coretests/src/android/widget/RemoteViewsTest.java @@ -58,6 +58,7 @@ import org.junit.runner.RunWith; import java.util.ArrayList; import java.util.Arrays; +import java.util.HashMap; import java.util.Map; import java.util.concurrent.CountDownLatch; import java.util.function.Consumer; @@ -716,6 +717,19 @@ public class RemoteViewsTest { } @Test + public void visitUris_themedIcons() { + RemoteViews views = new RemoteViews(mPackage, R.layout.remote_views_test); + final Icon iconLight = Icon.createWithContentUri("content://light/icon"); + final Icon iconDark = Icon.createWithContentUri("content://dark/icon"); + views.setIcon(R.id.layout, "setLargeIcon", iconLight, iconDark); + + Consumer<Uri> visitor = (Consumer<Uri>) spy(Consumer.class); + views.visitUris(visitor); + verify(visitor, times(1)).accept(eq(iconLight.getUri())); + verify(visitor, times(1)).accept(eq(iconDark.getUri())); + } + + @Test public void visitUris_nestedViews() { final RemoteViews outer = new RemoteViews(mPackage, R.layout.remote_views_test); @@ -774,4 +788,43 @@ public class RemoteViewsTest { verify(visitor, times(1)).accept(eq(icon3P.getUri())); verify(visitor, times(1)).accept(eq(icon4P.getUri())); } + + @Test + public void visitUris_sizedViews() { + final RemoteViews large = new RemoteViews(mPackage, R.layout.remote_views_test); + final Uri imageUriL = Uri.parse("content://large/image"); + final Icon icon1L = Icon.createWithContentUri("content://large/icon1"); + final Icon icon2L = Icon.createWithContentUri("content://large/icon2"); + final Icon icon3L = Icon.createWithContentUri("content://large/icon3"); + final Icon icon4L = Icon.createWithContentUri("content://large/icon4"); + large.setImageViewUri(R.id.image, imageUriL); + large.setTextViewCompoundDrawables(R.id.text, icon1L, icon2L, icon3L, icon4L); + + final RemoteViews small = new RemoteViews(mPackage, 33); + final Uri imageUriS = Uri.parse("content://small/image"); + final Icon icon1S = Icon.createWithContentUri("content://small/icon1"); + final Icon icon2S = Icon.createWithContentUri("content://small/icon2"); + final Icon icon3S = Icon.createWithContentUri("content://small/icon3"); + final Icon icon4S = Icon.createWithContentUri("content://small/icon4"); + small.setImageViewUri(R.id.image, imageUriS); + small.setTextViewCompoundDrawables(R.id.text, icon1S, icon2S, icon3S, icon4S); + + HashMap<SizeF, RemoteViews> sizedViews = new HashMap<>(); + sizedViews.put(new SizeF(300, 300), large); + sizedViews.put(new SizeF(100, 100), small); + RemoteViews views = new RemoteViews(sizedViews); + + Consumer<Uri> visitor = (Consumer<Uri>) spy(Consumer.class); + views.visitUris(visitor); + verify(visitor, times(1)).accept(eq(imageUriL)); + verify(visitor, times(1)).accept(eq(icon1L.getUri())); + verify(visitor, times(1)).accept(eq(icon2L.getUri())); + verify(visitor, times(1)).accept(eq(icon3L.getUri())); + verify(visitor, times(1)).accept(eq(icon4L.getUri())); + verify(visitor, times(1)).accept(eq(imageUriS)); + verify(visitor, times(1)).accept(eq(icon1S.getUri())); + verify(visitor, times(1)).accept(eq(icon2S.getUri())); + verify(visitor, times(1)).accept(eq(icon3S.getUri())); + verify(visitor, times(1)).accept(eq(icon4S.getUri())); + } } diff --git a/core/tests/coretests/src/android/window/SnapshotDrawerUtilsTest.java b/core/tests/coretests/src/android/window/SnapshotDrawerUtilsTest.java index 6764ac85dcef..036154634ae7 100644 --- a/core/tests/coretests/src/android/window/SnapshotDrawerUtilsTest.java +++ b/core/tests/coretests/src/android/window/SnapshotDrawerUtilsTest.java @@ -158,32 +158,42 @@ public class SnapshotDrawerUtilsTest { @Test public void testCalculateSnapshotCrop() { - setupSurface(100, 100, new Rect(0, 10, 0, 10), 0, new Rect(0, 0, 100, 100)); - assertEquals(new Rect(0, 0, 100, 90), mSnapshotSurface.calculateSnapshotCrop()); + final Rect contentInsets = new Rect(0, 10, 0, 10); + setupSurface(100, 100, contentInsets, 0, new Rect(0, 0, 100, 100)); + assertEquals(new Rect(0, 0, 100, 90), + mSnapshotSurface.calculateSnapshotCrop(contentInsets)); } @Test public void testCalculateSnapshotCrop_taskNotOnTop() { - setupSurface(100, 100, new Rect(0, 10, 0, 10), 0, new Rect(0, 50, 100, 150)); - assertEquals(new Rect(0, 10, 100, 90), mSnapshotSurface.calculateSnapshotCrop()); + final Rect contentInsets = new Rect(0, 10, 0, 10); + setupSurface(100, 100, contentInsets, 0, new Rect(0, 50, 100, 150)); + assertEquals(new Rect(0, 10, 100, 90), + mSnapshotSurface.calculateSnapshotCrop(contentInsets)); } @Test public void testCalculateSnapshotCrop_navBarLeft() { - setupSurface(100, 100, new Rect(10, 10, 0, 0), 0, new Rect(0, 0, 100, 100)); - assertEquals(new Rect(10, 0, 100, 100), mSnapshotSurface.calculateSnapshotCrop()); + final Rect contentInsets = new Rect(0, 10, 0, 0); + setupSurface(100, 100, contentInsets, 0, new Rect(0, 0, 100, 100)); + assertEquals(new Rect(10, 0, 100, 100), + mSnapshotSurface.calculateSnapshotCrop(contentInsets)); } @Test public void testCalculateSnapshotCrop_navBarRight() { - setupSurface(100, 100, new Rect(0, 10, 10, 0), 0, new Rect(0, 0, 100, 100)); - assertEquals(new Rect(0, 0, 90, 100), mSnapshotSurface.calculateSnapshotCrop()); + final Rect contentInsets = new Rect(0, 10, 10, 0); + setupSurface(100, 100, contentInsets, 0, new Rect(0, 0, 100, 100)); + assertEquals(new Rect(0, 0, 90, 100), + mSnapshotSurface.calculateSnapshotCrop(contentInsets)); } @Test public void testCalculateSnapshotCrop_waterfall() { - setupSurface(100, 100, new Rect(5, 10, 5, 10), 0, new Rect(0, 0, 100, 100)); - assertEquals(new Rect(5, 0, 95, 90), mSnapshotSurface.calculateSnapshotCrop()); + final Rect contentInsets = new Rect(5, 10, 5, 10); + setupSurface(100, 100, contentInsets, 0, new Rect(0, 0, 100, 100)); + assertEquals(new Rect(5, 0, 95, 90), + mSnapshotSurface.calculateSnapshotCrop(contentInsets)); } @Test diff --git a/core/tests/coretests/src/android/window/WindowOnBackInvokedDispatcherTest.java b/core/tests/coretests/src/android/window/WindowOnBackInvokedDispatcherTest.java index 8e772a2d1845..2ef2d3a968e0 100644 --- a/core/tests/coretests/src/android/window/WindowOnBackInvokedDispatcherTest.java +++ b/core/tests/coretests/src/android/window/WindowOnBackInvokedDispatcherTest.java @@ -16,12 +16,15 @@ package android.window; +import static android.window.OnBackInvokedDispatcher.PRIORITY_DEFAULT; +import static android.window.OnBackInvokedDispatcher.PRIORITY_OVERLAY; + import static org.junit.Assert.assertEquals; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.isNull; +import static org.mockito.Mockito.atLeast; +import static org.mockito.Mockito.atMost; import static org.mockito.Mockito.doReturn; -import static org.mockito.Mockito.reset; -import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verifyNoMoreInteractions; import static org.mockito.Mockito.verifyZeroInteractions; @@ -45,6 +48,9 @@ import org.mockito.Mock; import org.mockito.Mockito; import org.mockito.MockitoAnnotations; +import java.util.ArrayList; +import java.util.List; + /** * Tests for {@link WindowOnBackInvokedDispatcherTest} * @@ -69,6 +75,8 @@ public class WindowOnBackInvokedDispatcherTest { @Mock private ApplicationInfo mApplicationInfo; + private int mCallbackInfoCalls = 0; + private final BackMotionEvent mBackEvent = new BackMotionEvent( /* touchX = */ 0, /* touchY = */ 0, @@ -93,105 +101,243 @@ public class WindowOnBackInvokedDispatcherTest { InstrumentationRegistry.getInstrumentation().waitForIdleSync(); } + private List<OnBackInvokedCallbackInfo> captureCallbackInfo() throws RemoteException { + ArgumentCaptor<OnBackInvokedCallbackInfo> captor = ArgumentCaptor + .forClass(OnBackInvokedCallbackInfo.class); + // atLeast(0) -> get all setOnBackInvokedCallbackInfo() invocations + verify(mWindowSession, atLeast(0)) + .setOnBackInvokedCallbackInfo(Mockito.eq(mWindow), captor.capture()); + verifyNoMoreInteractions(mWindowSession); + return captor.getAllValues(); + } + + private OnBackInvokedCallbackInfo assertSetCallbackInfo() throws RemoteException { + List<OnBackInvokedCallbackInfo> callbackInfos = captureCallbackInfo(); + int actual = callbackInfos.size(); + assertEquals("setOnBackInvokedCallbackInfo", ++mCallbackInfoCalls, actual); + return callbackInfos.get(mCallbackInfoCalls - 1); + } + + private void assertNoSetCallbackInfo() throws RemoteException { + List<OnBackInvokedCallbackInfo> callbackInfos = captureCallbackInfo(); + int actual = callbackInfos.size(); + assertEquals("No setOnBackInvokedCallbackInfo", mCallbackInfoCalls, actual); + } + + private void assertCallbacksSize(int expectedDefault, int expectedOverlay) { + ArrayList<OnBackInvokedCallback> callbacksDefault = mDispatcher + .mOnBackInvokedCallbacks.get(PRIORITY_DEFAULT); + int actualSizeDefault = callbacksDefault != null ? callbacksDefault.size() : 0; + assertEquals("mOnBackInvokedCallbacks DEFAULT size", expectedDefault, actualSizeDefault); + + ArrayList<OnBackInvokedCallback> callbacksOverlay = mDispatcher + .mOnBackInvokedCallbacks.get(PRIORITY_OVERLAY); + int actualSizeOverlay = callbacksOverlay != null ? callbacksOverlay.size() : 0; + assertEquals("mOnBackInvokedCallbacks OVERLAY size", expectedOverlay, actualSizeOverlay); + } + + private void assertTopCallback(OnBackInvokedCallback expectedCallback) { + assertEquals("topCallback", expectedCallback, mDispatcher.getTopCallback()); + } + + @Test + public void registerCallback_samePriority_sameCallback() throws RemoteException { + mDispatcher.registerOnBackInvokedCallback(PRIORITY_DEFAULT, mCallback1); + assertCallbacksSize(/* default */ 1, /* overlay */ 0); + assertSetCallbackInfo(); + assertTopCallback(mCallback1); + + // The callback is removed and added again + mDispatcher.registerOnBackInvokedCallback(PRIORITY_DEFAULT, mCallback1); + assertCallbacksSize(/* default */ 1, /* overlay */ 0); + assertSetCallbackInfo(); + assertTopCallback(mCallback1); + + waitForIdle(); + verifyNoMoreInteractions(mWindowSession); + verifyNoMoreInteractions(mCallback1); + } + + @Test + public void registerCallback_samePriority_differentCallback() throws RemoteException { + mDispatcher.registerOnBackInvokedCallback(PRIORITY_DEFAULT, mCallback1); + assertCallbacksSize(/* default */ 1, /* overlay */ 0); + assertSetCallbackInfo(); + assertTopCallback(mCallback1); + + // The new callback becomes the TopCallback + mDispatcher.registerOnBackInvokedCallback(PRIORITY_DEFAULT, mCallback2); + assertCallbacksSize(/* default */ 2, /* overlay */ 0); + assertSetCallbackInfo(); + assertTopCallback(mCallback2); + + waitForIdle(); + verifyNoMoreInteractions(mWindowSession); + verifyNoMoreInteractions(mCallback1); + verifyNoMoreInteractions(mCallback2); + } + + @Test + public void registerCallback_differentPriority_sameCallback() throws RemoteException { + mDispatcher.registerOnBackInvokedCallback(PRIORITY_OVERLAY, mCallback1); + assertCallbacksSize(/* default */ 0, /* overlay */ 1); + assertSetCallbackInfo(); + assertTopCallback(mCallback1); + + // The callback is moved to the new priority list + mDispatcher.registerOnBackInvokedCallback(PRIORITY_DEFAULT, mCallback1); + assertCallbacksSize(/* default */ 1, /* overlay */ 0); + assertSetCallbackInfo(); + assertTopCallback(mCallback1); + + waitForIdle(); + verifyNoMoreInteractions(mWindowSession); + verifyNoMoreInteractions(mCallback1); + } + + @Test + public void registerCallback_differentPriority_differentCallback() throws RemoteException { + mDispatcher.registerOnBackInvokedCallback(PRIORITY_OVERLAY, mCallback1); + assertSetCallbackInfo(); + assertCallbacksSize(/* default */ 0, /* overlay */ 1); + assertTopCallback(mCallback1); + + // The callback with higher priority is still the TopCallback + mDispatcher.registerOnBackInvokedCallback(PRIORITY_DEFAULT, mCallback2); + assertNoSetCallbackInfo(); + assertCallbacksSize(/* default */ 1, /* overlay */ 1); + assertTopCallback(mCallback1); + + waitForIdle(); + verifyNoMoreInteractions(mWindowSession); + verifyNoMoreInteractions(mCallback1); + verifyNoMoreInteractions(mCallback2); + } + + @Test + public void registerCallback_sameInstanceAddedTwice() throws RemoteException { + mDispatcher.registerOnBackInvokedCallback(PRIORITY_OVERLAY, mCallback1); + assertCallbacksSize(/* default */ 0, /* overlay */ 1); + assertSetCallbackInfo(); + assertTopCallback(mCallback1); + + mDispatcher.registerOnBackInvokedCallback(PRIORITY_DEFAULT, mCallback2); + assertCallbacksSize(/* default */ 1, /* overlay */ 1); + assertNoSetCallbackInfo(); + assertTopCallback(mCallback1); + + mDispatcher.registerOnBackInvokedCallback(PRIORITY_DEFAULT, mCallback1); + assertCallbacksSize(/* default */ 2, /* overlay */ 0); + assertSetCallbackInfo(); + assertTopCallback(mCallback1); + + mDispatcher.registerOnBackInvokedCallback(PRIORITY_OVERLAY, mCallback2); + assertCallbacksSize(/* default */ 1, /* overlay */ 1); + assertSetCallbackInfo(); + assertTopCallback(mCallback2); + + waitForIdle(); + verifyNoMoreInteractions(mWindowSession); + verifyNoMoreInteractions(mCallback1); + verifyNoMoreInteractions(mCallback2); + } + @Test public void propagatesTopCallback_samePriority() throws RemoteException { - ArgumentCaptor<OnBackInvokedCallbackInfo> captor = - ArgumentCaptor.forClass(OnBackInvokedCallbackInfo.class); - - mDispatcher.registerOnBackInvokedCallback( - OnBackInvokedDispatcher.PRIORITY_DEFAULT, mCallback1); - mDispatcher.registerOnBackInvokedCallback( - OnBackInvokedDispatcher.PRIORITY_DEFAULT, mCallback2); - - verify(mWindowSession, times(2)).setOnBackInvokedCallbackInfo( - Mockito.eq(mWindow), - captor.capture()); - captor.getAllValues().get(0).getCallback().onBackStarted(mBackEvent); + mDispatcher.registerOnBackInvokedCallback(PRIORITY_DEFAULT, mCallback1); + OnBackInvokedCallbackInfo callbackInfo1 = assertSetCallbackInfo(); + + mDispatcher.registerOnBackInvokedCallback(PRIORITY_DEFAULT, mCallback2); + OnBackInvokedCallbackInfo callbackInfo2 = assertSetCallbackInfo(); + + callbackInfo1.getCallback().onBackStarted(mBackEvent); + waitForIdle(); verify(mCallback1).onBackStarted(any(BackEvent.class)); verifyZeroInteractions(mCallback2); - captor.getAllValues().get(1).getCallback().onBackStarted(mBackEvent); + callbackInfo2.getCallback().onBackStarted(mBackEvent); + waitForIdle(); verify(mCallback2).onBackStarted(any(BackEvent.class)); + + // Calls sequence: BackProgressAnimator.onBackStarted() -> BackProgressAnimator.reset() -> + // Spring.animateToFinalPosition(0). This causes a progress event to be fired. + verify(mCallback1, atMost(1)).onBackProgressed(any(BackEvent.class)); verifyNoMoreInteractions(mCallback1); } @Test public void propagatesTopCallback_differentPriority() throws RemoteException { - ArgumentCaptor<OnBackInvokedCallbackInfo> captor = - ArgumentCaptor.forClass(OnBackInvokedCallbackInfo.class); + mDispatcher.registerOnBackInvokedCallback(PRIORITY_OVERLAY, mCallback1); + mDispatcher.registerOnBackInvokedCallback(PRIORITY_DEFAULT, mCallback2); - mDispatcher.registerOnBackInvokedCallback( - OnBackInvokedDispatcher.PRIORITY_OVERLAY, mCallback1); - mDispatcher.registerOnBackInvokedCallback( - OnBackInvokedDispatcher.PRIORITY_DEFAULT, mCallback2); + OnBackInvokedCallbackInfo callbackInfo = assertSetCallbackInfo(); - verify(mWindowSession).setOnBackInvokedCallbackInfo( - Mockito.eq(mWindow), captor.capture()); verifyNoMoreInteractions(mWindowSession); - assertEquals(captor.getValue().getPriority(), OnBackInvokedDispatcher.PRIORITY_OVERLAY); - captor.getValue().getCallback().onBackStarted(mBackEvent); + assertEquals(callbackInfo.getPriority(), PRIORITY_OVERLAY); + + callbackInfo.getCallback().onBackStarted(mBackEvent); + waitForIdle(); verify(mCallback1).onBackStarted(any(BackEvent.class)); } @Test public void propagatesTopCallback_withRemoval() throws RemoteException { - mDispatcher.registerOnBackInvokedCallback( - OnBackInvokedDispatcher.PRIORITY_DEFAULT, mCallback1); - mDispatcher.registerOnBackInvokedCallback( - OnBackInvokedDispatcher.PRIORITY_DEFAULT, mCallback2); + mDispatcher.registerOnBackInvokedCallback(PRIORITY_DEFAULT, mCallback1); + assertSetCallbackInfo(); + + mDispatcher.registerOnBackInvokedCallback(PRIORITY_DEFAULT, mCallback2); + assertSetCallbackInfo(); - reset(mWindowSession); mDispatcher.unregisterOnBackInvokedCallback(mCallback1); - verifyZeroInteractions(mWindowSession); + + waitForIdle(); + verifyNoMoreInteractions(mWindowSession); + verifyNoMoreInteractions(mCallback1); mDispatcher.unregisterOnBackInvokedCallback(mCallback2); + + waitForIdle(); verify(mWindowSession).setOnBackInvokedCallbackInfo(Mockito.eq(mWindow), isNull()); } @Test public void propagatesTopCallback_sameInstanceAddedTwice() throws RemoteException { - ArgumentCaptor<OnBackInvokedCallbackInfo> captor = - ArgumentCaptor.forClass(OnBackInvokedCallbackInfo.class); - - mDispatcher.registerOnBackInvokedCallback(OnBackInvokedDispatcher.PRIORITY_OVERLAY, - mCallback1 - ); - mDispatcher.registerOnBackInvokedCallback( - OnBackInvokedDispatcher.PRIORITY_DEFAULT, mCallback2); - mDispatcher.registerOnBackInvokedCallback( - OnBackInvokedDispatcher.PRIORITY_DEFAULT, mCallback1); - - reset(mWindowSession); - mDispatcher.registerOnBackInvokedCallback( - OnBackInvokedDispatcher.PRIORITY_OVERLAY, mCallback2); - verify(mWindowSession).setOnBackInvokedCallbackInfo(Mockito.eq(mWindow), captor.capture()); - captor.getValue().getCallback().onBackStarted(mBackEvent); + mDispatcher.registerOnBackInvokedCallback(PRIORITY_OVERLAY, mCallback1); + assertSetCallbackInfo(); + mDispatcher.registerOnBackInvokedCallback(PRIORITY_DEFAULT, mCallback2); + assertNoSetCallbackInfo(); + mDispatcher.registerOnBackInvokedCallback(PRIORITY_DEFAULT, mCallback1); + assertSetCallbackInfo(); + + mDispatcher.registerOnBackInvokedCallback(PRIORITY_OVERLAY, mCallback2); + + OnBackInvokedCallbackInfo lastCallbackInfo = assertSetCallbackInfo(); + + lastCallbackInfo.getCallback().onBackStarted(mBackEvent); + waitForIdle(); verify(mCallback2).onBackStarted(any(BackEvent.class)); } @Test public void onUnregisterWhileBackInProgress_callOnBackCancelled() throws RemoteException { - ArgumentCaptor<OnBackInvokedCallbackInfo> captor = - ArgumentCaptor.forClass(OnBackInvokedCallbackInfo.class); + mDispatcher.registerOnBackInvokedCallback(PRIORITY_DEFAULT, mCallback1); - mDispatcher.registerOnBackInvokedCallback( - OnBackInvokedDispatcher.PRIORITY_DEFAULT, mCallback1); + OnBackInvokedCallbackInfo callbackInfo = assertSetCallbackInfo(); + + callbackInfo.getCallback().onBackStarted(mBackEvent); - verify(mWindowSession).setOnBackInvokedCallbackInfo( - Mockito.eq(mWindow), - captor.capture()); - IOnBackInvokedCallback iOnBackInvokedCallback = captor.getValue().getCallback(); - iOnBackInvokedCallback.onBackStarted(mBackEvent); waitForIdle(); verify(mCallback1).onBackStarted(any(BackEvent.class)); mDispatcher.unregisterOnBackInvokedCallback(mCallback1); + + waitForIdle(); verify(mCallback1).onBackCancelled(); - verifyNoMoreInteractions(mCallback1); + verify(mWindowSession).setOnBackInvokedCallbackInfo(Mockito.eq(mWindow), isNull()); } } diff --git a/graphics/java/android/graphics/GraphicBuffer.java b/graphics/java/android/graphics/GraphicBuffer.java index f9113a21405c..6705b25ab0ec 100644 --- a/graphics/java/android/graphics/GraphicBuffer.java +++ b/graphics/java/android/graphics/GraphicBuffer.java @@ -57,7 +57,7 @@ public class GraphicBuffer implements Parcelable { private final int mUsage; // Note: do not rename, this field is used by native code @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) - private final long mNativeObject; + private long mNativeObject; // These two fields are only used by lock/unlockCanvas() private Canvas mCanvas; @@ -219,6 +219,7 @@ public class GraphicBuffer implements Parcelable { if (!mDestroyed) { mDestroyed = true; nDestroyGraphicBuffer(mNativeObject); + mNativeObject = 0; } } @@ -239,7 +240,7 @@ public class GraphicBuffer implements Parcelable { @Override protected void finalize() throws Throwable { try { - if (!mDestroyed) nDestroyGraphicBuffer(mNativeObject); + destroy(); } finally { super.finalize(); } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/keyguard/KeyguardTransitionHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/keyguard/KeyguardTransitionHandler.java index 4d8075a9b56c..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 @@ -121,21 +121,21 @@ public class KeyguardTransitionHandler implements Transitions.TransitionHandler } boolean hasOpeningOcclude = false; + boolean hasClosingOcclude = false; boolean hasOpeningDream = false; boolean hasClosingApp = false; // Check for occluding/dream/closing apps for (int i = info.getChanges().size() - 1; i >= 0; i--) { final TransitionInfo.Change change = info.getChanges().get(i); - if (isOpeningType(change.getMode())) { - if (change.hasFlags(FLAG_OCCLUDES_KEYGUARD)) { - hasOpeningOcclude = true; - } - if (change.getTaskInfo() != null - && change.getTaskInfo().getActivityType() == ACTIVITY_TYPE_DREAM) { - hasOpeningDream = true; - } + if ((change.getFlags() & TransitionInfo.FLAG_IS_WALLPAPER) != 0) { + continue; + } else if (isOpeningType(change.getMode())) { + hasOpeningOcclude |= change.hasFlags(FLAG_OCCLUDES_KEYGUARD); + hasOpeningDream |= (change.getTaskInfo() != null + && change.getTaskInfo().getActivityType() == ACTIVITY_TYPE_DREAM); } else if (isClosingType(change.getMode())) { + hasClosingOcclude |= change.hasFlags(FLAG_OCCLUDES_KEYGUARD); hasClosingApp = true; } } @@ -147,6 +147,11 @@ public class KeyguardTransitionHandler implements Transitions.TransitionHandler transition, info, startTransaction, finishTransaction, finishCallback); } if (hasOpeningOcclude || info.getType() == TRANSIT_KEYGUARD_OCCLUDE) { + if (hasClosingOcclude) { + // Transitions between apps on top of the keyguard can use the default handler. + // WM sends a final occlude status update after the transition is finished. + return false; + } if (hasOpeningDream) { return startAnimation(mOccludeByDreamTransition, "occlude-by-dream", @@ -161,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; } } @@ -181,6 +186,7 @@ public class KeyguardTransitionHandler implements Transitions.TransitionHandler public void onTransitionFinished( WindowContainerTransaction wct, SurfaceControl.Transaction sct) { mMainExecutor.execute(() -> { + mStartedTransitions.remove(transition); finishCallback.onTransitionFinished(wct, null); }); } @@ -201,7 +207,7 @@ public class KeyguardTransitionHandler implements Transitions.TransitionHandler final IRemoteTransition playing = mStartedTransitions.get(currentTransition); if (playing == null) { - ProtoLog.e(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, + ProtoLog.e(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, "unknown keyguard transition %s", currentTransition); return; } @@ -212,14 +218,17 @@ public class KeyguardTransitionHandler implements Transitions.TransitionHandler // the device sleeping/waking, so it's best to ignore this and keep playing anyway. return; } else { - finishAnimationImmediately(currentTransition); + finishAnimationImmediately(currentTransition, playing); } } @Override public void onTransitionConsumed(IBinder transition, boolean aborted, SurfaceControl.Transaction finishTransaction) { - finishAnimationImmediately(transition); + final IRemoteTransition playing = mStartedTransitions.remove(transition); + if (playing != null) { + finishAnimationImmediately(transition, playing); + } } @Nullable @@ -229,21 +238,17 @@ public class KeyguardTransitionHandler implements Transitions.TransitionHandler return null; } - private void finishAnimationImmediately(IBinder transition) { - final IRemoteTransition playing = mStartedTransitions.get(transition); - - if (playing != null) { - final IBinder fakeTransition = new Binder(); - final TransitionInfo fakeInfo = new TransitionInfo(TRANSIT_SLEEP, 0x0); - final SurfaceControl.Transaction fakeT = new SurfaceControl.Transaction(); - final FakeFinishCallback fakeFinishCb = new FakeFinishCallback(); - try { - playing.mergeAnimation(fakeTransition, fakeInfo, fakeT, transition, fakeFinishCb); - } catch (RemoteException e) { - // There is no good reason for this to happen because the player is a local object - // implementing an AIDL interface. - Log.wtf(TAG, "RemoteException thrown from KeyguardService transition", e); - } + private void finishAnimationImmediately(IBinder transition, IRemoteTransition playing) { + final IBinder fakeTransition = new Binder(); + final TransitionInfo fakeInfo = new TransitionInfo(TRANSIT_SLEEP, 0x0); + final SurfaceControl.Transaction fakeT = new SurfaceControl.Transaction(); + final FakeFinishCallback fakeFinishCb = new FakeFinishCallback(); + try { + playing.mergeAnimation(fakeTransition, fakeInfo, fakeT, transition, fakeFinishCb); + } catch (RemoteException e) { + // There is no good reason for this to happen because the player is a local object + // implementing an AIDL interface. + Log.wtf(TAG, "RemoteException thrown from KeyguardService transition", e); } } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenTransitions.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenTransitions.java index d2b0e2800dc9..986309948ada 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenTransitions.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenTransitions.java @@ -348,8 +348,6 @@ class SplitScreenTransitions { WindowContainerTransaction wct, @Nullable RemoteTransition remoteTransition, Transitions.TransitionHandler handler, - @Nullable TransitionConsumedCallback consumedCallback, - @Nullable TransitionFinishedCallback finishedCallback, int extraTransitType, boolean resizeAnim) { if (mPendingEnter != null) { ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, " splitTransition " @@ -357,20 +355,16 @@ class SplitScreenTransitions { return null; } final IBinder transition = mTransitions.startTransition(transitType, wct, handler); - setEnterTransition(transition, remoteTransition, consumedCallback, finishedCallback, - extraTransitType, resizeAnim); + setEnterTransition(transition, remoteTransition, extraTransitType, resizeAnim); return transition; } /** Sets a transition to enter split. */ void setEnterTransition(@NonNull IBinder transition, @Nullable RemoteTransition remoteTransition, - @Nullable TransitionConsumedCallback consumedCallback, - @Nullable TransitionFinishedCallback finishedCallback, int extraTransitType, boolean resizeAnim) { mPendingEnter = new EnterSession( - transition, consumedCallback, finishedCallback, remoteTransition, extraTransitType, - resizeAnim); + transition, remoteTransition, extraTransitType, resizeAnim); ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, " splitTransition " + " deduced Enter split screen"); @@ -608,12 +602,10 @@ class SplitScreenTransitions { final boolean mResizeAnim; EnterSession(IBinder transition, - @Nullable TransitionConsumedCallback consumedCallback, - @Nullable TransitionFinishedCallback finishedCallback, @Nullable RemoteTransition remoteTransition, int extraTransitType, boolean resizeAnim) { - super(transition, consumedCallback, finishedCallback, remoteTransition, - extraTransitType); + super(transition, null /* consumedCallback */, null /* finishedCallback */, + remoteTransition, extraTransitType); this.mResizeAnim = resizeAnim; } } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java index e0ffffffa727..b2526ee97a21 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java @@ -192,6 +192,9 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, private final SplitScreenTransitions mSplitTransitions; private final SplitscreenEventLogger mLogger; private final ShellExecutor mMainExecutor; + // Cache live tile tasks while entering recents, evict them from stages in finish transaction + // if user is opening another task(s). + private final ArrayList<Integer> mPausingTasks = new ArrayList<>(); private final Optional<RecentTasksController> mRecentTasks; private final Rect mTempRect1 = new Rect(); @@ -393,7 +396,7 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, prepareEnterSplitScreen(wct, task, stagePosition); if (ENABLE_SHELL_TRANSITIONS) { mSplitTransitions.startEnterTransition(TRANSIT_TO_FRONT, wct, - null, this, null /* consumedCallback */, null /* finishedCallback */, + null, this, isSplitScreenVisible() ? TRANSIT_SPLIT_SCREEN_OPEN_TO_SIDE : TRANSIT_SPLIT_SCREEN_PAIR_OPEN, !mIsDropEntering); @@ -504,8 +507,7 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, prepareEnterSplitScreen(wct, null /* taskInfo */, position); mSplitTransitions.startEnterTransition(TRANSIT_TO_FRONT, wct, null, this, - null /* consumedCallback */, null /* finishedCallback */, extraTransitType, - !mIsDropEntering); + extraTransitType, !mIsDropEntering); } /** Launches an activity into split by legacy transition. */ @@ -658,8 +660,11 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, // Add task launch requests wct.startTask(mainTaskId, mainOptions); - mSplitTransitions.startEnterTransition( - TRANSIT_TO_FRONT, wct, remoteTransition, this, null, null, + // leave recents animation by re-start pausing tasks + if (mPausingTasks.contains(mainTaskId)) { + mPausingTasks.clear(); + } + mSplitTransitions.startEnterTransition(TRANSIT_TO_FRONT, wct, remoteTransition, this, TRANSIT_SPLIT_SCREEN_PAIR_OPEN, false); setEnterInstanceId(instanceId); } @@ -709,8 +714,7 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, wct.sendPendingIntent(pendingIntent2, fillInIntent2, options2); } - mSplitTransitions.startEnterTransition( - TRANSIT_TO_FRONT, wct, remoteTransition, this, null, null, + mSplitTransitions.startEnterTransition(TRANSIT_TO_FRONT, wct, remoteTransition, this, TRANSIT_SPLIT_SCREEN_PAIR_OPEN, false); setEnterInstanceId(instanceId); } @@ -1446,8 +1450,6 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, if (!mMainStage.isActive()) return; mSideStage.removeAllTasks(wct, stageToTop == STAGE_TYPE_SIDE); mMainStage.deactivate(wct, stageToTop == STAGE_TYPE_MAIN); - wct.setReparentLeafTaskIfRelaunch(mRootTaskInfo.token, - false /* reparentLeafTaskIfRelaunch */); } private void prepareEnterSplitScreen(WindowContainerTransaction wct) { @@ -1630,7 +1632,8 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, } private void updateRecentTasksSplitPair() { - if (!mShouldUpdateRecents) { + // Preventing from single task update while processing recents. + if (!mShouldUpdateRecents || !mPausingTasks.isEmpty()) { return; } mRecentTasks.ifPresent(recentTasks -> { @@ -2287,14 +2290,28 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, out = new WindowContainerTransaction(); final StageTaskListener stage = getStageOfTask(triggerTask); if (stage != null) { - // Dismiss split if the last task in one of the stages is going away if (isClosingType(type) && stage.getChildCount() == 1) { + // Dismiss split if the last task in one of the stages is going away // The top should be the opposite side that is closing: - int dismissTop = getStageType(stage) == STAGE_TYPE_MAIN ? STAGE_TYPE_SIDE - : STAGE_TYPE_MAIN; + int dismissTop = getStageType(stage) == STAGE_TYPE_MAIN + ? STAGE_TYPE_SIDE : STAGE_TYPE_MAIN; + prepareExitSplitScreen(dismissTop, out); + mSplitTransitions.setDismissTransition(transition, dismissTop, + EXIT_REASON_APP_FINISHED); + } else if (isOpening && !mPausingTasks.isEmpty()) { + // One of the splitting task is opening while animating the split pair in + // recents, which means to dismiss the split pair to this task. + int dismissTop = getStageType(stage) == STAGE_TYPE_MAIN + ? STAGE_TYPE_MAIN : STAGE_TYPE_SIDE; prepareExitSplitScreen(dismissTop, out); mSplitTransitions.setDismissTransition(transition, dismissTop, EXIT_REASON_APP_FINISHED); + } else if (!isSplitScreenVisible() && isOpening) { + // If split running backgroud and trigger task is appearing into split, + // prepare to enter split screen. + prepareEnterSplitScreen(out); + mSplitTransitions.setEnterTransition(transition, request.getRemoteTransition(), + TRANSIT_SPLIT_SCREEN_PAIR_OPEN, !mIsDropEntering); } } else if (isOpening && inFullscreen) { final int activityType = triggerTask.getActivityType(); @@ -2319,8 +2336,7 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, out = new WindowContainerTransaction(); prepareEnterSplitScreen(out); mSplitTransitions.setEnterTransition(transition, request.getRemoteTransition(), - null /* consumedCallback */, null /* finishedCallback */, - TRANSIT_SPLIT_SCREEN_OPEN_TO_SIDE, !mIsDropEntering); + TRANSIT_SPLIT_SCREEN_PAIR_OPEN, !mIsDropEntering); } } return out; @@ -2578,17 +2594,25 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, // First, verify that we actually have opened apps in both splits. TransitionInfo.Change mainChild = null; TransitionInfo.Change sideChild = null; + final WindowContainerTransaction evictWct = new WindowContainerTransaction(); for (int iC = 0; iC < info.getChanges().size(); ++iC) { final TransitionInfo.Change change = info.getChanges().get(iC); final ActivityManager.RunningTaskInfo taskInfo = change.getTaskInfo(); if (taskInfo == null || !taskInfo.hasParentTask()) continue; + if (mPausingTasks.contains(taskInfo.taskId)) { + continue; + } final @StageType int stageType = getStageType(getStageOfTask(taskInfo)); - if (stageType == STAGE_TYPE_MAIN + if (mainChild == null && stageType == STAGE_TYPE_MAIN && (isOpeningType(change.getMode()) || change.getMode() == TRANSIT_CHANGE)) { // Includes TRANSIT_CHANGE to cover reparenting top-most task to split. mainChild = change; - } else if (stageType == STAGE_TYPE_SIDE && isOpeningType(change.getMode())) { + } else if (sideChild == null && stageType == STAGE_TYPE_SIDE + && isOpeningType(change.getMode())) { sideChild = change; + } else if (stageType != STAGE_TYPE_UNDEFINED && change.getMode() == TRANSIT_TO_BACK) { + // Collect all to back task's and evict them when transition finished. + evictWct.reparent(taskInfo.token, null /* parent */, false /* onTop */); } } @@ -2650,10 +2674,15 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, mSideStage.evictInvisibleChildren(callbackWct); } } + if (!evictWct.isEmpty()) { + callbackWct.merge(evictWct, true); + } if (enterTransition.mResizeAnim) { mShowDecorImmediately = true; mSplitLayout.flingDividerToCenter(); } + callbackWct.setReparentLeafTaskIfRelaunch(mRootTaskInfo.token, false); + mPausingTasks.clear(); }); finishEnterSplitScreen(finishT); @@ -2803,6 +2832,7 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, dismissTransition.setFinishedCallback((callbackWct, callbackT) -> { mMainStage.getSplitDecorManager().release(callbackT); mSideStage.getSplitDecorManager().release(callbackT); + callbackWct.setReparentLeafTaskIfRelaunch(mRootTaskInfo.token, false); }); addDividerBarToTransition(info, false /* show */); @@ -2811,12 +2841,33 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, /** Call this when starting the open-recents animation while split-screen is active. */ public void onRecentsInSplitAnimationStart(TransitionInfo info) { + if (isSplitScreenVisible()) { + // Cache tasks on live tile. + for (int i = 0; i < info.getChanges().size(); ++i) { + final TransitionInfo.Change change = info.getChanges().get(i); + if (TransitionUtil.isClosingType(change.getMode()) + && change.getTaskInfo() != null) { + final int taskId = change.getTaskInfo().taskId; + if (mMainStage.getTopVisibleChildTaskId() == taskId + || mSideStage.getTopVisibleChildTaskId() == taskId) { + mPausingTasks.add(taskId); + } + } + } + } + addDividerBarToTransition(info, false /* show */); } + /** Call this when the recents animation canceled during split-screen. */ + public void onRecentsInSplitAnimationCanceled() { + mPausingTasks.clear(); + } + /** Call this when the recents animation during split-screen finishes. */ public void onRecentsInSplitAnimationFinish(WindowContainerTransaction finishWct, - SurfaceControl.Transaction finishT, TransitionInfo info) { + SurfaceControl.Transaction finishT) { + mPausingTasks.clear(); // Check if the recent transition is finished by returning to the current // split, so we can restore the divider bar. for (int i = 0; i < finishWct.getHierarchyOps().size(); ++i) { @@ -2835,11 +2886,31 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, } setSplitsVisible(false); - finishWct.setReparentLeafTaskIfRelaunch(mRootTaskInfo.token, - true /* reparentLeafTaskIfRelaunch */); + prepareExitSplitScreen(STAGE_TYPE_UNDEFINED, finishWct); logExit(EXIT_REASON_UNKNOWN); } + /** Call this when the recents animation finishes by doing pair-to-pair switch. */ + public void onRecentsPairToPairAnimationFinish(WindowContainerTransaction finishWct) { + // Pair-to-pair switch happened so here should evict the live tile from its stage. + // Otherwise, the task will remain in stage, and occluding the new task when next time + // user entering recents. + for (int i = mPausingTasks.size() - 1; i >= 0; --i) { + final int taskId = mPausingTasks.get(i); + if (mMainStage.containsTask(taskId)) { + mMainStage.evictChildren(finishWct, taskId); + } else if (mSideStage.containsTask(taskId)) { + mSideStage.evictChildren(finishWct, taskId); + } + } + // If pending enter hasn't consumed, the mix handler will invoke start pending + // animation within following transition. + if (mSplitTransitions.mPendingEnter == null) { + mPausingTasks.clear(); + updateRecentTasksSplitPair(); + } + } + private void addDividerBarToTransition(@NonNull TransitionInfo info, boolean show) { final SurfaceControl leash = mSplitLayout.getDividerLeash(); if (leash == null || !leash.isValid()) { @@ -2892,6 +2963,9 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, pw.println(innerPrefix + "SplitLayout"); mSplitLayout.dump(pw, childPrefix); } + if (!mPausingTasks.isEmpty()) { + pw.println(childPrefix + "mPausingTasks=" + mPausingTasks); + } } /** diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageTaskListener.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageTaskListener.java index da7d18641a97..92ff5fed4584 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageTaskListener.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageTaskListener.java @@ -377,6 +377,13 @@ class StageTaskListener implements ShellTaskOrganizer.TaskListener { } } + void evictChildren(WindowContainerTransaction wct, int taskId) { + final ActivityManager.RunningTaskInfo taskInfo = mChildrenTaskInfo.get(taskId); + if (taskInfo != null) { + wct.reparent(taskInfo.token, null /* parent */, false /* onTop */); + } + } + void reparentTopTask(WindowContainerTransaction wct) { wct.reparentTasks(null /* currentParent */, mRootTaskInfo.token, CONTROLLED_WINDOWING_MODES, CONTROLLED_ACTIVITY_TYPES, diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultMixedHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultMixedHandler.java index 863b5ab73a7d..64571e067a83 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultMixedHandler.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultMixedHandler.java @@ -540,9 +540,12 @@ public class DefaultMixedHandler implements Transitions.TransitionHandler, mixed.mInFlightSubAnimations = 0; mActiveTransitions.remove(mixed); // If pair-to-pair switching, the post-recents clean-up isn't needed. + wct = wct != null ? wct : new WindowContainerTransaction(); if (mixed.mAnimType != MixedTransition.ANIM_TYPE_PAIR_TO_PAIR) { - wct = wct != null ? wct : new WindowContainerTransaction(); - mSplitHandler.onRecentsInSplitAnimationFinish(wct, finishTransaction, info); + mSplitHandler.onRecentsInSplitAnimationFinish(wct, finishTransaction); + } else { + // notify pair-to-pair recents animation finish + mSplitHandler.onRecentsPairToPairAnimationFinish(wct); } mSplitHandler.onTransitionAnimationComplete(); finishCallback.onTransitionFinished(wct, wctCB); @@ -552,6 +555,7 @@ public class DefaultMixedHandler implements Transitions.TransitionHandler, final boolean handled = mixed.mLeftoversHandler.startAnimation(mixed.mTransition, info, startTransaction, finishTransaction, finishCB); if (!handled) { + mSplitHandler.onRecentsInSplitAnimationCanceled(); mActiveTransitions.remove(mixed); } return handled; 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 6a2468a7eaa2..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 @@ -299,7 +299,8 @@ public class DefaultTransitionHandler implements Transitions.TransitionHandler { } // Early check if the transition doesn't warrant an animation. - if (Transitions.isAllNoAnimation(info) || Transitions.isAllOrderOnly(info)) { + if (Transitions.isAllNoAnimation(info) || Transitions.isAllOrderOnly(info) + || (info.getFlags() & WindowManager.TRANSIT_FLAG_INVISIBLE) != 0) { startTransaction.apply(); finishTransaction.apply(); finishCallback.onTransitionFinished(null /* wct */, null /* wctCB */); @@ -326,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); @@ -451,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); } } @@ -542,6 +555,29 @@ public class DefaultTransitionHandler implements Transitions.TransitionHandler { return false; } + /** + * Does `info` only contain translucent visibility changes (CHANGEs are ignored). We select + * different animations and z-orders for these + */ + private static boolean isOnlyTranslucent(@NonNull TransitionInfo info) { + int translucentOpen = 0; + int translucentClose = 0; + for (int i = info.getChanges().size() - 1; i >= 0; --i) { + final TransitionInfo.Change change = info.getChanges().get(i); + if (change.getMode() == TRANSIT_CHANGE) continue; + if (change.hasFlags(FLAG_TRANSLUCENT)) { + if (TransitionUtil.isOpeningType(change.getMode())) { + translucentOpen += 1; + } else { + translucentClose += 1; + } + } else { + return false; + } + } + return (translucentOpen + translucentClose) > 0; + } + @Override public void mergeAnimation(@NonNull IBinder transition, @NonNull TransitionInfo info, @NonNull SurfaceControl.Transaction t, @NonNull IBinder mergeTarget, diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/TransitionAnimationHelper.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/TransitionAnimationHelper.java index 19d8384ace41..d978eafa97f3 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/TransitionAnimationHelper.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/TransitionAnimationHelper.java @@ -23,6 +23,7 @@ import static android.view.WindowManager.TRANSIT_OPEN; import static android.view.WindowManager.TRANSIT_TO_BACK; import static android.view.WindowManager.TRANSIT_TO_FRONT; import static android.view.WindowManager.transitTypeToString; +import static android.window.TransitionInfo.FLAGS_IS_NON_APP_WINDOW; import static android.window.TransitionInfo.FLAG_STARTING_WINDOW_TRANSFER_RECIPIENT; import static android.window.TransitionInfo.FLAG_TRANSLUCENT; @@ -102,10 +103,11 @@ public class TransitionAnimationHelper { // We will translucent open animation for translucent activities and tasks. Choose // WindowAnimation_activityOpenEnterAnimation and set translucent here, then // TransitionAnimation loads appropriate animation later. - if ((changeFlags & FLAG_TRANSLUCENT) != 0 && enter) { - translucent = true; - } - if (isTask && !translucent) { + translucent = (changeFlags & FLAG_TRANSLUCENT) != 0; + if (isTask && translucent && !enter) { + // For closing translucent tasks, use the activity close animation + animAttr = R.styleable.WindowAnimation_activityCloseExitAnimation; + } else if (isTask && !translucent) { animAttr = enter ? R.styleable.WindowAnimation_taskOpenEnterAnimation : R.styleable.WindowAnimation_taskOpenExitAnimation; @@ -150,6 +152,10 @@ public class TransitionAnimationHelper { .loadAnimationAttr(options.getPackageName(), options.getAnimations(), animAttr, translucent); } + } else if (translucent && !isTask && ((changeFlags & FLAGS_IS_NON_APP_WINDOW) == 0)) { + // Un-styled translucent activities technically have undefined animations; however, + // as is always the case, some apps now rely on this being no-animation, so skip + // loading animations here. } else { a = transitionAnimation.loadDefaultAnimationAttr(animAttr, translucent); } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java index 3b306e793640..ce8d792ef302 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java @@ -16,9 +16,12 @@ package com.android.wm.shell.transition; +import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM; +import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN; import static android.view.WindowManager.TRANSIT_CHANGE; import static android.view.WindowManager.TRANSIT_CLOSE; import static android.view.WindowManager.TRANSIT_FIRST_CUSTOM; +import static android.view.WindowManager.TRANSIT_KEYGUARD_OCCLUDE; import static android.view.WindowManager.TRANSIT_OPEN; import static android.view.WindowManager.TRANSIT_SLEEP; import static android.view.WindowManager.TRANSIT_TO_BACK; @@ -567,8 +570,8 @@ public class Transitions implements RemoteCallable<Transitions>, layer = zSplitLine + numChanges - i; } } else { // CHANGE or other - if (isClosing) { - // Put below CLOSE mode. + if (isClosing || TransitionUtil.isOrderOnly(change)) { + // Put below CLOSE mode (in the "static" section). layer = zSplitLine - i; } else { // Put above CLOSE mode. @@ -1081,6 +1084,16 @@ public class Transitions implements RemoteCallable<Transitions>, } } } + if (request.getType() == TRANSIT_KEYGUARD_OCCLUDE && request.getTriggerTask() != null + && request.getTriggerTask().getWindowingMode() == WINDOWING_MODE_FREEFORM) { + // This freeform task is on top of keyguard, so its windowing mode should be changed to + // fullscreen. + if (wct == null) { + wct = new WindowContainerTransaction(); + } + wct.setWindowingMode(request.getTriggerTask().token, WINDOWING_MODE_FULLSCREEN); + wct.setBounds(request.getTriggerTask().token, null); + } mOrganizer.startTransition(transitionToken, wct != null && wct.isEmpty() ? null : wct); active.mToken = transitionToken; // Currently, WMCore only does one transition at a time. If it makes a requestStart, it diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/CopyContentInSplit.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/CopyContentInSplit.kt index b7e73ad11c9f..72f25f36c9d9 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/CopyContentInSplit.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/CopyContentInSplit.kt @@ -18,6 +18,7 @@ package com.android.wm.shell.flicker.splitscreen import android.platform.test.annotations.FlakyTest import android.platform.test.annotations.IwTest +import android.platform.test.annotations.PlatinumTest import android.platform.test.annotations.Presubmit import android.tools.common.traces.component.ComponentNameMatcher import android.tools.common.traces.component.EdgeExtensionComponentMatcher @@ -60,6 +61,7 @@ class CopyContentInSplit(override val flicker: FlickerTest) : thisTransition(this) } + @PlatinumTest(focusArea = "sysui") @IwTest(focusArea = "sysui") @Presubmit @Test diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/DragDividerToResize.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/DragDividerToResize.kt index 0b0a3dad320b..ed3df9ced3b0 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/DragDividerToResize.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/DragDividerToResize.kt @@ -18,6 +18,7 @@ package com.android.wm.shell.flicker.splitscreen import android.platform.test.annotations.FlakyTest import android.platform.test.annotations.IwTest +import android.platform.test.annotations.PlatinumTest import android.platform.test.annotations.Presubmit import android.tools.device.flicker.junit.FlickerParametersRunnerFactory import android.tools.device.flicker.legacy.FlickerBuilder @@ -58,6 +59,7 @@ class DragDividerToResize(override val flicker: FlickerTest) : thisTransition(this) } + @PlatinumTest(focusArea = "sysui") @IwTest(focusArea = "sysui") @Presubmit @Test diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchAppByDoubleTapDivider.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchAppByDoubleTapDivider.kt index 8cf871f88314..b4c6afd14a90 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchAppByDoubleTapDivider.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchAppByDoubleTapDivider.kt @@ -17,6 +17,7 @@ package com.android.wm.shell.flicker.splitscreen import android.platform.test.annotations.IwTest +import android.platform.test.annotations.PlatinumTest import android.platform.test.annotations.Postsubmit import android.platform.test.annotations.Presubmit import android.tools.common.NavBar @@ -59,6 +60,7 @@ class SwitchAppByDoubleTapDivider(override val flicker: FlickerTest) : thisTransition(this) } + @PlatinumTest(focusArea = "sysui") @IwTest(focusArea = "sysui") @Presubmit @Test diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchBetweenSplitPairs.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchBetweenSplitPairs.kt index 3b2da8dbcf9f..2cedc3570cfd 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchBetweenSplitPairs.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchBetweenSplitPairs.kt @@ -18,6 +18,7 @@ package com.android.wm.shell.flicker.splitscreen import android.platform.test.annotations.FlakyTest import android.platform.test.annotations.IwTest +import android.platform.test.annotations.PlatinumTest import android.platform.test.annotations.Presubmit import android.tools.device.flicker.junit.FlickerParametersRunnerFactory import android.tools.device.flicker.legacy.FlickerBuilder @@ -62,6 +63,7 @@ class SwitchBetweenSplitPairs(override val flicker: FlickerTest) : thisTransition(this) } + @PlatinumTest(focusArea = "sysui") @IwTest(focusArea = "sysui") @Presubmit @Test diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/CopyContentInSplitBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/CopyContentInSplitBenchmark.kt index a189a3f67eca..a5ad97d8ad65 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/CopyContentInSplitBenchmark.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/CopyContentInSplitBenchmark.kt @@ -17,6 +17,7 @@ package com.android.wm.shell.flicker.splitscreen.benchmark import android.platform.test.annotations.IwTest +import android.platform.test.annotations.PlatinumTest import android.platform.test.annotations.Presubmit import android.tools.common.traces.component.ComponentNameMatcher import android.tools.device.flicker.junit.FlickerParametersRunnerFactory @@ -62,6 +63,7 @@ open class CopyContentInSplitBenchmark(override val flicker: FlickerTest) : thisTransition(this) } + @PlatinumTest(focusArea = "sysui") @IwTest(focusArea = "sysui") @Presubmit @Test diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/DismissSplitScreenByDividerBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/DismissSplitScreenByDividerBenchmark.kt index 55ab7b3a30e4..fa6a4bfbcda6 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/DismissSplitScreenByDividerBenchmark.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/DismissSplitScreenByDividerBenchmark.kt @@ -17,6 +17,7 @@ package com.android.wm.shell.flicker.splitscreen.benchmark import android.platform.test.annotations.IwTest +import android.platform.test.annotations.PlatinumTest import android.platform.test.annotations.Presubmit import android.tools.device.flicker.junit.FlickerParametersRunnerFactory import android.tools.device.flicker.legacy.FlickerBuilder @@ -68,6 +69,7 @@ open class DismissSplitScreenByDividerBenchmark(flicker: FlickerTest) : SplitScr thisTransition(this) } + @PlatinumTest(focusArea = "sysui") @IwTest(focusArea = "sysui") @Presubmit @Test diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/DismissSplitScreenByGoHomeBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/DismissSplitScreenByGoHomeBenchmark.kt index c4cfd1add25c..d2beb678a4d5 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/DismissSplitScreenByGoHomeBenchmark.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/DismissSplitScreenByGoHomeBenchmark.kt @@ -17,6 +17,7 @@ package com.android.wm.shell.flicker.splitscreen.benchmark import android.platform.test.annotations.IwTest +import android.platform.test.annotations.PlatinumTest import android.platform.test.annotations.Presubmit import android.tools.device.flicker.junit.FlickerParametersRunnerFactory import android.tools.device.flicker.legacy.FlickerBuilder @@ -55,6 +56,7 @@ open class DismissSplitScreenByGoHomeBenchmark(override val flicker: FlickerTest thisTransition(this) } + @PlatinumTest(focusArea = "sysui") @IwTest(focusArea = "sysui") @Presubmit @Test diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/DragDividerToResizeBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/DragDividerToResizeBenchmark.kt index 146287c21c75..e95fd947ebde 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/DragDividerToResizeBenchmark.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/DragDividerToResizeBenchmark.kt @@ -17,6 +17,7 @@ package com.android.wm.shell.flicker.splitscreen.benchmark import android.platform.test.annotations.IwTest +import android.platform.test.annotations.PlatinumTest import android.platform.test.annotations.Presubmit import android.tools.device.flicker.junit.FlickerParametersRunnerFactory import android.tools.device.flicker.legacy.FlickerBuilder @@ -58,6 +59,7 @@ open class DragDividerToResizeBenchmark(override val flicker: FlickerTest) : Assume.assumeTrue(tapl.isTablet || !flicker.scenario.isLandscapeOrSeascapeAtStart) } + @PlatinumTest(focusArea = "sysui") @IwTest(focusArea = "sysui") @Presubmit @Test diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/EnterSplitScreenByDragFromAllAppsBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/EnterSplitScreenByDragFromAllAppsBenchmark.kt index cc715021adf4..63b74e29c39c 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/EnterSplitScreenByDragFromAllAppsBenchmark.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/EnterSplitScreenByDragFromAllAppsBenchmark.kt @@ -17,6 +17,7 @@ package com.android.wm.shell.flicker.splitscreen.benchmark import android.platform.test.annotations.IwTest +import android.platform.test.annotations.PlatinumTest import android.platform.test.annotations.Presubmit import android.tools.common.NavBar import android.tools.device.flicker.junit.FlickerParametersRunnerFactory @@ -70,6 +71,7 @@ open class EnterSplitScreenByDragFromAllAppsBenchmark(override val flicker: Flic Assume.assumeTrue(tapl.isTablet) } + @PlatinumTest(focusArea = "sysui") @IwTest(focusArea = "sysui") @Presubmit @Test diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/EnterSplitScreenByDragFromNotificationBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/EnterSplitScreenByDragFromNotificationBenchmark.kt index de78f09d3ef4..e94da8713b43 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/EnterSplitScreenByDragFromNotificationBenchmark.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/EnterSplitScreenByDragFromNotificationBenchmark.kt @@ -17,6 +17,7 @@ package com.android.wm.shell.flicker.splitscreen.benchmark import android.platform.test.annotations.IwTest +import android.platform.test.annotations.PlatinumTest import android.platform.test.annotations.Presubmit import android.tools.common.NavBar import android.tools.device.flicker.junit.FlickerParametersRunnerFactory @@ -67,6 +68,7 @@ open class EnterSplitScreenByDragFromNotificationBenchmark(override val flicker: thisTransition(this) } + @PlatinumTest(focusArea = "sysui") @IwTest(focusArea = "sysui") @Presubmit @Test diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/EnterSplitScreenByDragFromShortcutBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/EnterSplitScreenByDragFromShortcutBenchmark.kt index a29eb4085e54..f41117f0d54e 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/EnterSplitScreenByDragFromShortcutBenchmark.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/EnterSplitScreenByDragFromShortcutBenchmark.kt @@ -17,6 +17,7 @@ package com.android.wm.shell.flicker.splitscreen.benchmark import android.platform.test.annotations.IwTest +import android.platform.test.annotations.PlatinumTest import android.platform.test.annotations.Presubmit import android.tools.common.NavBar import android.tools.device.flicker.junit.FlickerParametersRunnerFactory @@ -70,6 +71,7 @@ open class EnterSplitScreenByDragFromShortcutBenchmark(flicker: FlickerTest) : thisTransition(this) } + @PlatinumTest(focusArea = "sysui") @IwTest(focusArea = "sysui") @Presubmit @Test diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/EnterSplitScreenByDragFromTaskbarBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/EnterSplitScreenByDragFromTaskbarBenchmark.kt index b2395cafafc1..12f610b73e13 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/EnterSplitScreenByDragFromTaskbarBenchmark.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/EnterSplitScreenByDragFromTaskbarBenchmark.kt @@ -17,6 +17,7 @@ package com.android.wm.shell.flicker.splitscreen.benchmark import android.platform.test.annotations.IwTest +import android.platform.test.annotations.PlatinumTest import android.platform.test.annotations.Presubmit import android.tools.common.NavBar import android.tools.device.flicker.junit.FlickerParametersRunnerFactory @@ -65,6 +66,7 @@ open class EnterSplitScreenByDragFromTaskbarBenchmark(override val flicker: Flic thisTransition(this) } + @PlatinumTest(focusArea = "sysui") @IwTest(focusArea = "sysui") @Presubmit @Test diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/EnterSplitScreenFromOverviewBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/EnterSplitScreenFromOverviewBenchmark.kt index e1d85d0a4371..77818d380053 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/EnterSplitScreenFromOverviewBenchmark.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/EnterSplitScreenFromOverviewBenchmark.kt @@ -17,6 +17,7 @@ package com.android.wm.shell.flicker.splitscreen.benchmark import android.platform.test.annotations.IwTest +import android.platform.test.annotations.PlatinumTest import android.platform.test.annotations.Presubmit import android.tools.device.flicker.junit.FlickerParametersRunnerFactory import android.tools.device.flicker.legacy.FlickerBuilder @@ -64,6 +65,7 @@ open class EnterSplitScreenFromOverviewBenchmark(override val flicker: FlickerTe thisTransition(this) } + @PlatinumTest(focusArea = "sysui") @IwTest(focusArea = "sysui") @Presubmit @Test diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/SwitchAppByDoubleTapDividerBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/SwitchAppByDoubleTapDividerBenchmark.kt index ba8c46091808..6ff22902667c 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/SwitchAppByDoubleTapDividerBenchmark.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/SwitchAppByDoubleTapDividerBenchmark.kt @@ -17,6 +17,7 @@ package com.android.wm.shell.flicker.splitscreen.benchmark import android.platform.test.annotations.IwTest +import android.platform.test.annotations.PlatinumTest import android.platform.test.annotations.Presubmit import android.tools.common.NavBar import android.tools.common.Rotation @@ -134,6 +135,7 @@ open class SwitchAppByDoubleTapDividerBenchmark(override val flicker: FlickerTes return displayBounds.width > displayBounds.height } + @PlatinumTest(focusArea = "sysui") @IwTest(focusArea = "sysui") @Presubmit @Test diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/SwitchBackToSplitFromAnotherAppBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/SwitchBackToSplitFromAnotherAppBenchmark.kt index bbb2edc621e2..400adea8880d 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/SwitchBackToSplitFromAnotherAppBenchmark.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/SwitchBackToSplitFromAnotherAppBenchmark.kt @@ -17,6 +17,7 @@ package com.android.wm.shell.flicker.splitscreen.benchmark import android.platform.test.annotations.IwTest +import android.platform.test.annotations.PlatinumTest import android.platform.test.annotations.Presubmit import android.tools.common.NavBar import android.tools.device.flicker.junit.FlickerParametersRunnerFactory @@ -63,6 +64,7 @@ open class SwitchBackToSplitFromAnotherAppBenchmark(override val flicker: Flicke thisTransition(this) } + @PlatinumTest(focusArea = "sysui") @IwTest(focusArea = "sysui") @Presubmit @Test diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/SwitchBackToSplitFromHomeBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/SwitchBackToSplitFromHomeBenchmark.kt index fa382932eb88..1ec43405ee44 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/SwitchBackToSplitFromHomeBenchmark.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/SwitchBackToSplitFromHomeBenchmark.kt @@ -17,6 +17,7 @@ package com.android.wm.shell.flicker.splitscreen.benchmark import android.platform.test.annotations.IwTest +import android.platform.test.annotations.PlatinumTest import android.platform.test.annotations.Presubmit import android.tools.common.NavBar import android.tools.device.flicker.junit.FlickerParametersRunnerFactory @@ -61,6 +62,7 @@ open class SwitchBackToSplitFromHomeBenchmark(override val flicker: FlickerTest) thisTransition(this) } + @PlatinumTest(focusArea = "sysui") @IwTest(focusArea = "sysui") @Presubmit @Test diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/SwitchBackToSplitFromRecentBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/SwitchBackToSplitFromRecentBenchmark.kt index 1064bd93e3c1..9757153929ca 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/SwitchBackToSplitFromRecentBenchmark.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/SwitchBackToSplitFromRecentBenchmark.kt @@ -17,6 +17,7 @@ package com.android.wm.shell.flicker.splitscreen.benchmark import android.platform.test.annotations.IwTest +import android.platform.test.annotations.PlatinumTest import android.platform.test.annotations.Presubmit import android.tools.common.NavBar import android.tools.device.flicker.junit.FlickerParametersRunnerFactory @@ -61,6 +62,7 @@ open class SwitchBackToSplitFromRecentBenchmark(override val flicker: FlickerTes thisTransition(this) } + @PlatinumTest(focusArea = "sysui") @IwTest(focusArea = "sysui") @Presubmit @Test diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/SwitchBetweenSplitPairsBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/SwitchBetweenSplitPairsBenchmark.kt index 8f4393f8d20d..c19a38dc9daf 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/SwitchBetweenSplitPairsBenchmark.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/SwitchBetweenSplitPairsBenchmark.kt @@ -17,6 +17,7 @@ package com.android.wm.shell.flicker.splitscreen.benchmark import android.platform.test.annotations.IwTest +import android.platform.test.annotations.PlatinumTest import android.platform.test.annotations.Presubmit import android.tools.device.flicker.junit.FlickerParametersRunnerFactory import android.tools.device.flicker.legacy.FlickerBuilder @@ -64,6 +65,7 @@ open class SwitchBetweenSplitPairsBenchmark(override val flicker: FlickerTest) : thisTransition(this) } + @PlatinumTest(focusArea = "sysui") @IwTest(focusArea = "sysui") @Presubmit @Test open fun cujCompleted() {} companion object { diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitTransitionTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitTransitionTests.java index 60c0e5535568..3b05651f884b 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitTransitionTests.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitTransitionTests.java @@ -185,7 +185,7 @@ public class SplitTransitionTests extends ShellTestCase { IBinder transition = mSplitScreenTransitions.startEnterTransition( TRANSIT_OPEN, new WindowContainerTransaction(), - new RemoteTransition(testRemote, "Test"), mStageCoordinator, null, null, + new RemoteTransition(testRemote, "Test"), mStageCoordinator, TRANSIT_SPLIT_SCREEN_PAIR_OPEN, false); mMainStage.onTaskAppeared(mMainChild, createMockSurface()); mSideStage.onTaskAppeared(mSideChild, createMockSurface()); @@ -283,7 +283,7 @@ public class SplitTransitionTests extends ShellTestCase { // Make sure it cleans-up if recents doesn't restore WindowContainerTransaction commitWCT = new WindowContainerTransaction(); mStageCoordinator.onRecentsInSplitAnimationFinish(commitWCT, - mock(SurfaceControl.Transaction.class), mock(TransitionInfo.class)); + mock(SurfaceControl.Transaction.class)); assertFalse(mStageCoordinator.isSplitScreenVisible()); } @@ -322,7 +322,7 @@ public class SplitTransitionTests extends ShellTestCase { mMainStage.onTaskAppeared(mMainChild, mock(SurfaceControl.class)); mSideStage.onTaskAppeared(mSideChild, mock(SurfaceControl.class)); mStageCoordinator.onRecentsInSplitAnimationFinish(restoreWCT, - mock(SurfaceControl.Transaction.class), mock(TransitionInfo.class)); + mock(SurfaceControl.Transaction.class)); assertTrue(mStageCoordinator.isSplitScreenVisible()); } @@ -412,7 +412,7 @@ public class SplitTransitionTests extends ShellTestCase { IBinder enterTransit = mSplitScreenTransitions.startEnterTransition( TRANSIT_OPEN, new WindowContainerTransaction(), new RemoteTransition(new TestRemoteTransition(), "Test"), - mStageCoordinator, null, null, TRANSIT_SPLIT_SCREEN_PAIR_OPEN, false); + mStageCoordinator, TRANSIT_SPLIT_SCREEN_PAIR_OPEN, false); mMainStage.onTaskAppeared(mMainChild, createMockSurface()); mSideStage.onTaskAppeared(mSideChild, createMockSurface()); mStageCoordinator.startAnimation(enterTransit, enterInfo, diff --git a/libs/hwui/Properties.cpp b/libs/hwui/Properties.cpp index 7af6efb7da41..06aed63d8def 100644 --- a/libs/hwui/Properties.cpp +++ b/libs/hwui/Properties.cpp @@ -17,7 +17,6 @@ #include "Properties.h" #include "Debug.h" -#include "log/log_main.h" #ifdef __ANDROID__ #include "HWUIProperties.sysprop.h" #endif @@ -220,12 +219,15 @@ RenderPipelineType Properties::getRenderPipelineType() { return sRenderPipelineType; } -void Properties::overrideRenderPipelineType(RenderPipelineType type, bool inUnitTest) { +void Properties::overrideRenderPipelineType(RenderPipelineType type) { // If we're doing actual rendering then we can't change the renderer after it's been set. - // Unit tests can freely change this as often as it wants. - LOG_ALWAYS_FATAL_IF(sRenderPipelineType != RenderPipelineType::NotInitialized && - sRenderPipelineType != type && !inUnitTest, - "Trying to change pipeline but it's already set."); + // Unit tests can freely change this as often as it wants, though, as there's no actual + // GL rendering happening + if (sRenderPipelineType != RenderPipelineType::NotInitialized) { + LOG_ALWAYS_FATAL_IF(sRenderPipelineType != type, + "Trying to change pipeline but it's already set"); + return; + } sRenderPipelineType = type; } diff --git a/libs/hwui/Properties.h b/libs/hwui/Properties.h index 24e206bbc3b1..bb477449fff0 100644 --- a/libs/hwui/Properties.h +++ b/libs/hwui/Properties.h @@ -303,7 +303,7 @@ public: static bool enableRTAnimations; // Used for testing only to change the render pipeline. - static void overrideRenderPipelineType(RenderPipelineType, bool inUnitTest = false); + static void overrideRenderPipelineType(RenderPipelineType); static bool runningInEmulator; diff --git a/libs/hwui/renderthread/VulkanManager.cpp b/libs/hwui/renderthread/VulkanManager.cpp index 4cffc6c2efe3..d4e919fefbbd 100644 --- a/libs/hwui/renderthread/VulkanManager.cpp +++ b/libs/hwui/renderthread/VulkanManager.cpp @@ -23,13 +23,11 @@ #include <GrDirectContext.h> #include <GrTypes.h> #include <android/sync.h> +#include <gui/TraceUtils.h> #include <ui/FatVector.h> #include <vk/GrVkExtensions.h> #include <vk/GrVkTypes.h> -#include <cstring> - -#include <gui/TraceUtils.h> #include "Properties.h" #include "RenderThread.h" #include "pipeline/skia/ShaderCache.h" @@ -90,19 +88,6 @@ static void free_features_extensions_structs(const VkPhysicalDeviceFeatures2& fe } } -GrVkGetProc VulkanManager::sSkiaGetProp = [](const char* proc_name, VkInstance instance, - VkDevice device) { - if (device != VK_NULL_HANDLE) { - if (strcmp("vkQueueSubmit", proc_name) == 0) { - return (PFN_vkVoidFunction)VulkanManager::interceptedVkQueueSubmit; - } else if (strcmp("vkQueueWaitIdle", proc_name) == 0) { - return (PFN_vkVoidFunction)VulkanManager::interceptedVkQueueWaitIdle; - } - return vkGetDeviceProcAddr(device, proc_name); - } - return vkGetInstanceProcAddr(instance, proc_name); -}; - #define GET_PROC(F) m##F = (PFN_vk##F)vkGetInstanceProcAddr(VK_NULL_HANDLE, "vk" #F) #define GET_INST_PROC(F) m##F = (PFN_vk##F)vkGetInstanceProcAddr(mInstance, "vk" #F) #define GET_DEV_PROC(F) m##F = (PFN_vk##F)vkGetDeviceProcAddr(mDevice, "vk" #F) @@ -138,6 +123,7 @@ VulkanManager::~VulkanManager() { } mGraphicsQueue = VK_NULL_HANDLE; + mAHBUploadQueue = VK_NULL_HANDLE; mDevice = VK_NULL_HANDLE; mPhysicalDevice = VK_NULL_HANDLE; mInstance = VK_NULL_HANDLE; @@ -231,7 +217,7 @@ void VulkanManager::setupDevice(GrVkExtensions& grExtensions, VkPhysicalDeviceFe mDriverVersion = physDeviceProperties.driverVersion; // query to get the initial queue props size - uint32_t queueCount; + uint32_t queueCount = 0; mGetPhysicalDeviceQueueFamilyProperties(mPhysicalDevice, &queueCount, nullptr); LOG_ALWAYS_FATAL_IF(!queueCount); @@ -239,11 +225,14 @@ void VulkanManager::setupDevice(GrVkExtensions& grExtensions, VkPhysicalDeviceFe std::unique_ptr<VkQueueFamilyProperties[]> queueProps(new VkQueueFamilyProperties[queueCount]); mGetPhysicalDeviceQueueFamilyProperties(mPhysicalDevice, &queueCount, queueProps.get()); + constexpr auto kRequestedQueueCount = 2; + // iterate to find the graphics queue mGraphicsQueueIndex = queueCount; for (uint32_t i = 0; i < queueCount; i++) { if (queueProps[i].queueFlags & VK_QUEUE_GRAPHICS_BIT) { mGraphicsQueueIndex = i; + LOG_ALWAYS_FATAL_IF(queueProps[i].queueCount < kRequestedQueueCount); break; } } @@ -273,7 +262,14 @@ void VulkanManager::setupDevice(GrVkExtensions& grExtensions, VkPhysicalDeviceFe LOG_ALWAYS_FATAL_IF(!hasKHRSwapchainExtension); } - grExtensions.init(sSkiaGetProp, mInstance, mPhysicalDevice, mInstanceExtensions.size(), + auto getProc = [](const char* proc_name, VkInstance instance, VkDevice device) { + if (device != VK_NULL_HANDLE) { + return vkGetDeviceProcAddr(device, proc_name); + } + return vkGetInstanceProcAddr(instance, proc_name); + }; + + grExtensions.init(getProc, mInstance, mPhysicalDevice, mInstanceExtensions.size(), mInstanceExtensions.data(), mDeviceExtensions.size(), mDeviceExtensions.data()); @@ -312,7 +308,7 @@ void VulkanManager::setupDevice(GrVkExtensions& grExtensions, VkPhysicalDeviceFe // and we can't depend on it on all platforms features.features.robustBufferAccess = VK_FALSE; - float queuePriorities[1] = {0.0}; + float queuePriorities[kRequestedQueueCount] = {0.0}; void* queueNextPtr = nullptr; @@ -345,7 +341,7 @@ void VulkanManager::setupDevice(GrVkExtensions& grExtensions, VkPhysicalDeviceFe queueNextPtr, // pNext 0, // VkDeviceQueueCreateFlags mGraphicsQueueIndex, // queueFamilyIndex - 1, // queueCount + kRequestedQueueCount, // queueCount queuePriorities, // pQueuePriorities }; @@ -403,6 +399,7 @@ void VulkanManager::initialize() { this->setupDevice(mExtensions, mPhysicalDeviceFeatures2); mGetDeviceQueue(mDevice, mGraphicsQueueIndex, 0, &mGraphicsQueue); + mGetDeviceQueue(mDevice, mGraphicsQueueIndex, 1, &mAHBUploadQueue); if (Properties::enablePartialUpdates && Properties::useBufferAge) { mSwapBehavior = SwapBehavior::BufferAge; @@ -416,16 +413,24 @@ static void onGrContextReleased(void* context) { sk_sp<GrDirectContext> VulkanManager::createContext(GrContextOptions& options, ContextType contextType) { + auto getProc = [](const char* proc_name, VkInstance instance, VkDevice device) { + if (device != VK_NULL_HANDLE) { + return vkGetDeviceProcAddr(device, proc_name); + } + return vkGetInstanceProcAddr(instance, proc_name); + }; + GrVkBackendContext backendContext; backendContext.fInstance = mInstance; backendContext.fPhysicalDevice = mPhysicalDevice; backendContext.fDevice = mDevice; - backendContext.fQueue = mGraphicsQueue; + backendContext.fQueue = + (contextType == ContextType::kRenderThread) ? mGraphicsQueue : mAHBUploadQueue; backendContext.fGraphicsQueueIndex = mGraphicsQueueIndex; backendContext.fMaxAPIVersion = mAPIVersion; backendContext.fVkExtensions = &mExtensions; backendContext.fDeviceFeatures2 = &mPhysicalDeviceFeatures2; - backendContext.fGetProc = sSkiaGetProp; + backendContext.fGetProc = std::move(getProc); LOG_ALWAYS_FATAL_IF(options.fContextDeleteProc != nullptr, "Conflicting fContextDeleteProcs!"); this->incStrong((void*)onGrContextReleased); @@ -636,8 +641,6 @@ void VulkanManager::swapBuffers(VulkanSurface* surface, const SkRect& dirtyRect) ALOGE_IF(VK_SUCCESS != err, "VulkanManager::swapBuffers(): Failed to get semaphore Fd"); } else { ALOGE("VulkanManager::swapBuffers(): Semaphore submission failed"); - - std::lock_guard<std::mutex> lock(mGraphicsQueueMutex); mQueueWaitIdle(mGraphicsQueue); } if (mDestroySemaphoreContext) { @@ -652,7 +655,6 @@ void VulkanManager::swapBuffers(VulkanSurface* surface, const SkRect& dirtyRect) void VulkanManager::destroySurface(VulkanSurface* surface) { // Make sure all submit commands have finished before starting to destroy objects. if (VK_NULL_HANDLE != mGraphicsQueue) { - std::lock_guard<std::mutex> lock(mGraphicsQueueMutex); mQueueWaitIdle(mGraphicsQueue); } mDeviceWaitIdle(mDevice); diff --git a/libs/hwui/renderthread/VulkanManager.h b/libs/hwui/renderthread/VulkanManager.h index 00a40c0c85c3..2be1ffdbc423 100644 --- a/libs/hwui/renderthread/VulkanManager.h +++ b/libs/hwui/renderthread/VulkanManager.h @@ -17,10 +17,6 @@ #ifndef VULKANMANAGER_H #define VULKANMANAGER_H -#include <functional> -#include <mutex> - -#include "vulkan/vulkan_core.h" #if !defined(VK_USE_PLATFORM_ANDROID_KHR) #define VK_USE_PLATFORM_ANDROID_KHR #endif @@ -186,25 +182,8 @@ private: VkDevice mDevice = VK_NULL_HANDLE; uint32_t mGraphicsQueueIndex; - - std::mutex mGraphicsQueueMutex; VkQueue mGraphicsQueue = VK_NULL_HANDLE; - - static VKAPI_ATTR VkResult interceptedVkQueueSubmit(VkQueue queue, uint32_t submitCount, - const VkSubmitInfo* pSubmits, - VkFence fence) { - sp<VulkanManager> manager = VulkanManager::getInstance(); - std::lock_guard<std::mutex> lock(manager->mGraphicsQueueMutex); - return manager->mQueueSubmit(queue, submitCount, pSubmits, fence); - } - - static VKAPI_ATTR VkResult interceptedVkQueueWaitIdle(VkQueue queue) { - sp<VulkanManager> manager = VulkanManager::getInstance(); - std::lock_guard<std::mutex> lock(manager->mGraphicsQueueMutex); - return manager->mQueueWaitIdle(queue); - } - - static GrVkGetProc sSkiaGetProp; + VkQueue mAHBUploadQueue = VK_NULL_HANDLE; // Variables saved to populate VkFunctorInitParams. static const uint32_t mAPIVersion = VK_MAKE_VERSION(1, 1, 0); diff --git a/libs/hwui/tests/common/TestUtils.h b/libs/hwui/tests/common/TestUtils.h index 9d5c13e5cd75..81ecfe59d3bc 100644 --- a/libs/hwui/tests/common/TestUtils.h +++ b/libs/hwui/tests/common/TestUtils.h @@ -61,12 +61,12 @@ namespace uirenderer { ADD_FAILURE() << "ClipState not a rect"; \ } -#define INNER_PIPELINE_TEST(test_case_name, test_name, pipeline, functionCall) \ - TEST(test_case_name, test_name##_##pipeline) { \ - RenderPipelineType oldType = Properties::getRenderPipelineType(); \ - Properties::overrideRenderPipelineType(RenderPipelineType::pipeline, true); \ - functionCall; \ - Properties::overrideRenderPipelineType(oldType, true); \ +#define INNER_PIPELINE_TEST(test_case_name, test_name, pipeline, functionCall) \ + TEST(test_case_name, test_name##_##pipeline) { \ + RenderPipelineType oldType = Properties::getRenderPipelineType(); \ + Properties::overrideRenderPipelineType(RenderPipelineType::pipeline); \ + functionCall; \ + Properties::overrideRenderPipelineType(oldType); \ }; #define INNER_PIPELINE_RENDERTHREAD_TEST(test_case_name, test_name, pipeline) \ @@ -78,27 +78,29 @@ namespace uirenderer { * Like gtest's TEST, but runs on the RenderThread, and 'renderThread' is passed, in top level scope * (for e.g. accessing its RenderState) */ -#define RENDERTHREAD_TEST(test_case_name, test_name) \ - class test_case_name##_##test_name##_RenderThreadTest { \ - public: \ - static void doTheThing(renderthread::RenderThread& renderThread); \ - }; \ - INNER_PIPELINE_RENDERTHREAD_TEST(test_case_name, test_name, SkiaGL); \ - INNER_PIPELINE_RENDERTHREAD_TEST(test_case_name, test_name, SkiaVulkan); \ - void test_case_name##_##test_name##_RenderThreadTest::doTheThing( \ +#define RENDERTHREAD_TEST(test_case_name, test_name) \ + class test_case_name##_##test_name##_RenderThreadTest { \ + public: \ + static void doTheThing(renderthread::RenderThread& renderThread); \ + }; \ + INNER_PIPELINE_RENDERTHREAD_TEST(test_case_name, test_name, SkiaGL); \ + /* Temporarily disabling Vulkan until we can figure out a way to stub out the driver */ \ + /* INNER_PIPELINE_RENDERTHREAD_TEST(test_case_name, test_name, SkiaVulkan); */ \ + void test_case_name##_##test_name##_RenderThreadTest::doTheThing( \ renderthread::RenderThread& renderThread) /** * Like RENDERTHREAD_TEST, but only runs with the Skia RenderPipelineTypes */ -#define RENDERTHREAD_SKIA_PIPELINE_TEST(test_case_name, test_name) \ - class test_case_name##_##test_name##_RenderThreadTest { \ - public: \ - static void doTheThing(renderthread::RenderThread& renderThread); \ - }; \ - INNER_PIPELINE_RENDERTHREAD_TEST(test_case_name, test_name, SkiaGL); \ - INNER_PIPELINE_RENDERTHREAD_TEST(test_case_name, test_name, SkiaVulkan); \ - void test_case_name##_##test_name##_RenderThreadTest::doTheThing( \ +#define RENDERTHREAD_SKIA_PIPELINE_TEST(test_case_name, test_name) \ + class test_case_name##_##test_name##_RenderThreadTest { \ + public: \ + static void doTheThing(renderthread::RenderThread& renderThread); \ + }; \ + INNER_PIPELINE_RENDERTHREAD_TEST(test_case_name, test_name, SkiaGL); \ + /* Temporarily disabling Vulkan until we can figure out a way to stub out the driver */ \ + /* INNER_PIPELINE_RENDERTHREAD_TEST(test_case_name, test_name, SkiaVulkan); */ \ + void test_case_name##_##test_name##_RenderThreadTest::doTheThing( \ renderthread::RenderThread& renderThread) /** diff --git a/media/java/android/media/MediaRoute2ProviderService.java b/media/java/android/media/MediaRoute2ProviderService.java index 11cb2be6660b..1e3c15419cff 100644 --- a/media/java/android/media/MediaRoute2ProviderService.java +++ b/media/java/android/media/MediaRoute2ProviderService.java @@ -81,6 +81,18 @@ public abstract class MediaRoute2ProviderService extends Service { public static final String SERVICE_INTERFACE = "android.media.MediaRoute2ProviderService"; /** + * A category indicating that the associated provider is only intended for use within the app + * that hosts the provider. + * + * <p>Declaring this category helps the system save resources by avoiding the launch of services + * whose routes are known to be private to the app that provides them. + * + * @hide + */ + public static final String CATEGORY_SELF_SCAN_ONLY = + "android.media.MediaRoute2ProviderService.SELF_SCAN_ONLY"; + + /** * The request ID to pass {@link #notifySessionCreated(long, RoutingSessionInfo)} * when {@link MediaRoute2ProviderService} created a session although there was no creation * request. diff --git a/media/java/android/media/RouteListingPreference.java b/media/java/android/media/RouteListingPreference.java index ee1f20308922..3935de837f51 100644 --- a/media/java/android/media/RouteListingPreference.java +++ b/media/java/android/media/RouteListingPreference.java @@ -440,14 +440,14 @@ public final class RouteListingPreference implements Parcelable { * <p>If this method returns {@link #SUBTEXT_CUSTOM}, then the subtext is obtained form * {@link #getCustomSubtextMessage()}. * - * @see #SUBTEXT_NONE, - * @see #SUBTEXT_ERROR_UNKNOWN, - * @see #SUBTEXT_SUBSCRIPTION_REQUIRED, - * @see #SUBTEXT_DOWNLOADED_CONTENT_ROUTING_DISALLOWED, - * @see #SUBTEXT_AD_ROUTING_DISALLOWED, - * @see #SUBTEXT_DEVICE_LOW_POWER, - * @see #SUBTEXT_UNAUTHORIZED , - * @see #SUBTEXT_TRACK_UNSUPPORTED, + * @see #SUBTEXT_NONE + * @see #SUBTEXT_ERROR_UNKNOWN + * @see #SUBTEXT_SUBSCRIPTION_REQUIRED + * @see #SUBTEXT_DOWNLOADED_CONTENT_ROUTING_DISALLOWED + * @see #SUBTEXT_AD_ROUTING_DISALLOWED + * @see #SUBTEXT_DEVICE_LOW_POWER + * @see #SUBTEXT_UNAUTHORIZED + * @see #SUBTEXT_TRACK_UNSUPPORTED * @see #SUBTEXT_CUSTOM */ @SubText diff --git a/media/java/android/media/tv/TvInputService.java b/media/java/android/media/tv/TvInputService.java index 85b02ad54718..720d9a6291de 100644 --- a/media/java/android/media/tv/TvInputService.java +++ b/media/java/android/media/tv/TvInputService.java @@ -63,6 +63,7 @@ import android.widget.FrameLayout; import com.android.internal.os.SomeArgs; import com.android.internal.util.Preconditions; +import java.io.IOException; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.util.ArrayList; @@ -1009,6 +1010,13 @@ public abstract class TvInputService extends Service { * @param buffer the {@link AdBuffer} that was consumed. */ public void notifyAdBufferConsumed(@NonNull AdBuffer buffer) { + AdBuffer dupBuffer; + try { + dupBuffer = AdBuffer.dupAdBuffer(buffer); + } catch (IOException e) { + Log.w(TAG, "dup AdBuffer error in notifyAdBufferConsumed:", e); + return; + } executeOrPostRunnableOnMainThread(new Runnable() { @MainThread @Override @@ -1016,10 +1024,14 @@ public abstract class TvInputService extends Service { try { if (DEBUG) Log.d(TAG, "notifyAdBufferConsumed"); if (mSessionCallback != null) { - mSessionCallback.onAdBufferConsumed(buffer); + mSessionCallback.onAdBufferConsumed(dupBuffer); } } catch (RemoteException e) { Log.w(TAG, "error in notifyAdBufferConsumed", e); + } finally { + if (dupBuffer != null) { + dupBuffer.getSharedMemory().close(); + } } } }); diff --git a/media/java/android/media/tv/interactive/TvInteractiveAppService.java b/media/java/android/media/tv/interactive/TvInteractiveAppService.java index ec85cc7af499..241940486a14 100755 --- a/media/java/android/media/tv/interactive/TvInteractiveAppService.java +++ b/media/java/android/media/tv/interactive/TvInteractiveAppService.java @@ -1964,6 +1964,13 @@ public abstract class TvInteractiveAppService extends Service { */ @CallSuper public void notifyAdBufferReady(@NonNull AdBuffer buffer) { + AdBuffer dupBuffer; + try { + dupBuffer = AdBuffer.dupAdBuffer(buffer); + } catch (IOException e) { + Log.w(TAG, "dup AdBuffer error in notifyAdBufferReady:", e); + return; + } executeOrPostRunnableOnMainThread(new Runnable() { @MainThread @Override @@ -1974,10 +1981,14 @@ public abstract class TvInteractiveAppService extends Service { "notifyAdBufferReady(buffer=" + buffer + ")"); } if (mSessionCallback != null) { - mSessionCallback.onAdBufferReady(AdBuffer.dupAdBuffer(buffer)); + mSessionCallback.onAdBufferReady(dupBuffer); } - } catch (RemoteException | IOException e) { + } catch (RemoteException e) { Log.w(TAG, "error in notifyAdBuffer", e); + } finally { + if (dupBuffer != null) { + dupBuffer.getSharedMemory().close(); + } } } }); diff --git a/media/jni/android_media_ImageReader.cpp b/media/jni/android_media_ImageReader.cpp index ca1bb3e97815..da2e56f5b6fa 100644 --- a/media/jni/android_media_ImageReader.cpp +++ b/media/jni/android_media_ImageReader.cpp @@ -768,6 +768,7 @@ static void ImageReader_unlockGraphicBuffer(JNIEnv* env, jobject /*thiz*/, android_graphics_GraphicBuffer_getNativeGraphicsBuffer(env, buffer); if (graphicBuffer.get() == NULL) { jniThrowRuntimeException(env, "Invalid graphic buffer!"); + return; } status_t res = graphicBuffer->unlock(); diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/theme/SettingsTypography.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/theme/SettingsTypography.kt index 5eaa4956b2e8..460bf9993b41 100644 --- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/theme/SettingsTypography.kt +++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/theme/SettingsTypography.kt @@ -21,6 +21,7 @@ import androidx.compose.runtime.Composable import androidx.compose.runtime.remember import androidx.compose.ui.text.TextStyle import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.text.style.Hyphens import androidx.compose.ui.unit.em import androidx.compose.ui.unit.sp @@ -34,42 +35,48 @@ private class SettingsTypography(settingsFontFamily: SettingsFontFamily) { fontWeight = FontWeight.Normal, fontSize = 57.sp, lineHeight = 64.sp, - letterSpacing = (-0.2).sp + letterSpacing = (-0.2).sp, + hyphens = Hyphens.Auto, ), displayMedium = TextStyle( fontFamily = brand, fontWeight = FontWeight.Normal, fontSize = 45.sp, lineHeight = 52.sp, - letterSpacing = 0.0.sp + letterSpacing = 0.0.sp, + hyphens = Hyphens.Auto, ), displaySmall = TextStyle( fontFamily = brand, fontWeight = FontWeight.Normal, fontSize = 36.sp, lineHeight = 44.sp, - letterSpacing = 0.0.sp + letterSpacing = 0.0.sp, + hyphens = Hyphens.Auto, ), headlineLarge = TextStyle( fontFamily = brand, fontWeight = FontWeight.Normal, fontSize = 32.sp, lineHeight = 40.sp, - letterSpacing = 0.0.sp + letterSpacing = 0.0.sp, + hyphens = Hyphens.Auto, ), headlineMedium = TextStyle( fontFamily = brand, fontWeight = FontWeight.Normal, fontSize = 28.sp, lineHeight = 36.sp, - letterSpacing = 0.0.sp + letterSpacing = 0.0.sp, + hyphens = Hyphens.Auto, ), headlineSmall = TextStyle( fontFamily = brand, fontWeight = FontWeight.Normal, fontSize = 24.sp, lineHeight = 32.sp, - letterSpacing = 0.0.sp + letterSpacing = 0.0.sp, + hyphens = Hyphens.Auto, ), titleLarge = TextStyle( fontFamily = brand, @@ -77,6 +84,7 @@ private class SettingsTypography(settingsFontFamily: SettingsFontFamily) { fontSize = 22.sp, lineHeight = 28.sp, letterSpacing = 0.02.em, + hyphens = Hyphens.Auto, ), titleMedium = TextStyle( fontFamily = brand, @@ -84,6 +92,7 @@ private class SettingsTypography(settingsFontFamily: SettingsFontFamily) { fontSize = 20.sp, lineHeight = 24.sp, letterSpacing = 0.02.em, + hyphens = Hyphens.Auto, ), titleSmall = TextStyle( fontFamily = brand, @@ -91,6 +100,7 @@ private class SettingsTypography(settingsFontFamily: SettingsFontFamily) { fontSize = 18.sp, lineHeight = 20.sp, letterSpacing = 0.02.em, + hyphens = Hyphens.Auto, ), bodyLarge = TextStyle( fontFamily = plain, @@ -98,6 +108,7 @@ private class SettingsTypography(settingsFontFamily: SettingsFontFamily) { fontSize = 16.sp, lineHeight = 24.sp, letterSpacing = 0.01.em, + hyphens = Hyphens.Auto, ), bodyMedium = TextStyle( fontFamily = plain, @@ -105,6 +116,7 @@ private class SettingsTypography(settingsFontFamily: SettingsFontFamily) { fontSize = 14.sp, lineHeight = 20.sp, letterSpacing = 0.01.em, + hyphens = Hyphens.Auto, ), bodySmall = TextStyle( fontFamily = plain, @@ -112,6 +124,7 @@ private class SettingsTypography(settingsFontFamily: SettingsFontFamily) { fontSize = 12.sp, lineHeight = 16.sp, letterSpacing = 0.01.em, + hyphens = Hyphens.Auto, ), labelLarge = TextStyle( fontFamily = plain, @@ -119,6 +132,7 @@ private class SettingsTypography(settingsFontFamily: SettingsFontFamily) { fontSize = 16.sp, lineHeight = 24.sp, letterSpacing = 0.01.em, + hyphens = Hyphens.Auto, ), labelMedium = TextStyle( fontFamily = plain, @@ -126,6 +140,7 @@ private class SettingsTypography(settingsFontFamily: SettingsFontFamily) { fontSize = 14.sp, lineHeight = 20.sp, letterSpacing = 0.01.em, + hyphens = Hyphens.Auto, ), labelSmall = TextStyle( fontFamily = plain, @@ -133,6 +148,7 @@ private class SettingsTypography(settingsFontFamily: SettingsFontFamily) { fontSize = 12.sp, lineHeight = 16.sp, letterSpacing = 0.01.em, + hyphens = Hyphens.Auto, ), ) } diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/ui/Text.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/ui/Text.kt index 5f2344ed97c0..01ba8f82850d 100644 --- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/ui/Text.kt +++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/ui/Text.kt @@ -19,6 +19,7 @@ package com.android.settingslib.spa.widget.ui import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.width import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Text @@ -29,6 +30,7 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp +import com.android.settingslib.spa.framework.theme.SettingsDimension import com.android.settingslib.spa.framework.theme.SettingsTheme import com.android.settingslib.spa.framework.theme.toMediumWeight @@ -78,7 +80,7 @@ fun SettingsBody( @Composable fun PlaceholderTitle(title: String) { Box( - modifier = Modifier.fillMaxSize(), + modifier = Modifier.fillMaxSize().padding(SettingsDimension.itemPadding), contentAlignment = Alignment.Center, ) { Text( diff --git a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppInfo.kt b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppInfo.kt index e3ea2e78756f..a0ff216875a2 100644 --- a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppInfo.kt +++ b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppInfo.kt @@ -76,7 +76,7 @@ class AppInfoProvider(private val packageInfo: PackageInfo) { private fun AppVersion() { if (packageInfo.versionName == null) return Spacer(modifier = Modifier.height(4.dp)) - SettingsBody(packageInfo.versionName) + SettingsBody(packageInfo.versionNameBidiWrapped) } @Composable @@ -84,10 +84,15 @@ class AppInfoProvider(private val packageInfo: PackageInfo) { if (packageInfo.versionName == null) return Divider() Box(modifier = Modifier.padding(SettingsDimension.itemPadding)) { - val versionName = BidiFormatter.getInstance().unicodeWrap(packageInfo.versionName) - SettingsBody(stringResource(R.string.version_text, versionName)) + SettingsBody(stringResource(R.string.version_text, packageInfo.versionNameBidiWrapped)) } } + + private companion object { + /** Wrapped the version name, so its directionality still keep same when RTL. */ + val PackageInfo.versionNameBidiWrapped: String + get() = BidiFormatter.getInstance().unicodeWrap(versionName) + } } @Composable diff --git a/packages/SettingsLib/TEST_MAPPING b/packages/SettingsLib/TEST_MAPPING new file mode 100644 index 000000000000..f6ada4c1ab7f --- /dev/null +++ b/packages/SettingsLib/TEST_MAPPING @@ -0,0 +1,13 @@ +{ + "presubmit": [ + { + "name": "SettingsLibUnitTests" + }, + { + "name": "SpaPrivilegedLibTests" + }, + { + "name": "SettingsSpaUnitTests" + } + ] +} diff --git a/packages/SettingsLib/res/values/strings.xml b/packages/SettingsLib/res/values/strings.xml index f581091f0f3e..dac7f8d15388 100644 --- a/packages/SettingsLib/res/values/strings.xml +++ b/packages/SettingsLib/res/values/strings.xml @@ -1327,6 +1327,12 @@ <string name="media_transfer_this_device_name" product="default">This phone</string> <!-- Name of the tablet device. [CHAR LIMIT=30] --> <string name="media_transfer_this_device_name" product="tablet">This tablet</string> + <!-- Name of the dock device. [CHAR LIMIT=30] --> + <string name="media_transfer_dock_speaker_device_name">Dock speaker</string> + <!-- Default name of the external device. [CHAR LIMIT=30] --> + <string name="media_transfer_external_device_name">External Device</string> + <!-- Default name of the connected device. [CHAR LIMIT=30] --> + <string name="media_transfer_default_device_name">Connected device</string> <!-- Name of the phone device with an active remote session. [CHAR LIMIT=30] --> <string name="media_transfer_this_phone">This phone</string> <!-- Sub status indicates device is not available due to an unknown error. [CHAR LIMIT=NONE] --> diff --git a/packages/SettingsLib/res/values/styles.xml b/packages/SettingsLib/res/values/styles.xml index 2584be725747..4ac4aae8f2ab 100644 --- a/packages/SettingsLib/res/values/styles.xml +++ b/packages/SettingsLib/res/values/styles.xml @@ -17,6 +17,7 @@ <resources xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"> <style name="TextAppearanceSmall"> <item name="android:textAppearance">?android:attr/textAppearanceSmall</item> + <item name="android:textColor">?android:attr/textColorSecondary</item> </style> <style name="TextAppearanceMedium"> <item name="android:textAppearance">?android:attr/textAppearanceMedium</item> @@ -91,7 +92,7 @@ <item name="android:ellipsize">end</item> </style> - <style name="DialogButtonPositive" xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"> + <style name="DialogButtonPositive"> <item name="android:buttonCornerRadius">0dp</item> <item name="android:background">@drawable/dialog_btn_filled</item> <item name="android:textColor">?androidprv:attr/textColorOnAccent</item> @@ -105,7 +106,6 @@ <style name="DialogButtonNegative"> <item name="android:buttonCornerRadius">28dp</item> <item name="android:background">@drawable/dialog_btn_outline</item> - <item name="android:buttonCornerRadius">28dp</item> <item name="android:textColor">?android:attr/textColorPrimary</item> <item name="android:textSize">14sp</item> <item name="android:lineHeight">20sp</item> diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothBroadcastUtils.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothBroadcastUtils.java index a80061efb19a..2bca7cf9762e 100644 --- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothBroadcastUtils.java +++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothBroadcastUtils.java @@ -43,39 +43,5 @@ public final class BluetoothBroadcastUtils { /** * Bluetooth scheme. */ - public static final String SCHEME_BT_BROADCAST_METADATA = "BT:"; - - // BluetoothLeBroadcastMetadata - static final String PREFIX_BT_ADDRESS_TYPE = "T:"; - static final String PREFIX_BT_DEVICE = "D:"; - static final String PREFIX_BT_ADVERTISING_SID = "AS:"; - static final String PREFIX_BT_BROADCAST_ID = "B:"; - static final String PREFIX_BT_SYNC_INTERVAL = "SI:"; - static final String PREFIX_BT_IS_ENCRYPTED = "E:"; - static final String PREFIX_BT_BROADCAST_CODE = "C:"; - static final String PREFIX_BT_PRESENTATION_DELAY = "PD:"; - static final String PREFIX_BT_SUBGROUPS = "SG:"; - static final String PREFIX_BT_ANDROID_VERSION = "V:"; - - // BluetoothLeBroadcastSubgroup - static final String PREFIX_BTSG_CODEC_ID = "CID:"; - static final String PREFIX_BTSG_CODEC_CONFIG = "CC:"; - static final String PREFIX_BTSG_AUDIO_CONTENT = "AC:"; - static final String PREFIX_BTSG_CHANNEL_PREF = "CP:"; - static final String PREFIX_BTSG_BROADCAST_CHANNEL = "BC:"; - - // BluetoothLeAudioCodecConfigMetadata - static final String PREFIX_BTCC_AUDIO_LOCATION = "AL:"; - static final String PREFIX_BTCC_RAW_METADATA = "CCRM:"; - - // BluetoothLeAudioContentMetadata - static final String PREFIX_BTAC_PROGRAM_INFO = "PI:"; - static final String PREFIX_BTAC_LANGUAGE = "L:"; - static final String PREFIX_BTAC_RAW_METADATA = "ACRM:"; - - // BluetoothLeBroadcastChannel - static final String PREFIX_BTBC_CHANNEL_INDEX = "CI:"; - static final String PREFIX_BTBC_CODEC_CONFIG = "BCCM:"; - - static final String DELIMITER_QR_CODE = ";"; + public static final String SCHEME_BT_BROADCAST_METADATA = "BT:BluetoothLeBroadcastMetadata:"; } diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothLeBroadcastMetadataExt.kt b/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothLeBroadcastMetadataExt.kt new file mode 100644 index 000000000000..b54b115213d5 --- /dev/null +++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothLeBroadcastMetadataExt.kt @@ -0,0 +1,70 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.settingslib.bluetooth + +import android.bluetooth.BluetoothLeBroadcastMetadata +import android.os.Parcel +import android.os.Parcelable +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" + + /** + * Converts [BluetoothLeBroadcastMetadata] to QR code string. + * + * QR code string will prefix with "BT:BluetoothLeBroadcastMetadata:". + */ + fun BluetoothLeBroadcastMetadata.toQrCodeString(): String = + SCHEME_BT_BROADCAST_METADATA + Base64.encodeToString(toBytes(this), Base64.NO_WRAP) + + /** + * Converts QR code string to [BluetoothLeBroadcastMetadata]. + * + * QR code string should prefix with "BT:BluetoothLeBroadcastMetadata:". + */ + fun convertToBroadcastMetadata(qrCodeString: String): BluetoothLeBroadcastMetadata? { + if (!qrCodeString.startsWith(SCHEME_BT_BROADCAST_METADATA)) return null + return try { + val encodedString = qrCodeString.removePrefix(SCHEME_BT_BROADCAST_METADATA) + val bytes = Base64.decode(encodedString, Base64.NO_WRAP) + createFromBytes(BluetoothLeBroadcastMetadata.CREATOR, bytes) + } catch (e: Exception) { + Log.w(TAG, "Cannot convert QR code string to BluetoothLeBroadcastMetadata", e) + null + } + } + + private fun toBytes(parcelable: Parcelable): ByteArray = + Parcel.obtain().run { + parcelable.writeToParcel(this, 0) + setDataPosition(0) + val bytes = marshall() + recycle() + bytes + } + + 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 + } +}
\ No newline at end of file diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothLeBroadcastMetadata.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothLeBroadcastMetadata.java deleted file mode 100644 index 0630a2e515e0..000000000000 --- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothLeBroadcastMetadata.java +++ /dev/null @@ -1,454 +0,0 @@ -/* - * Copyright (C) 2022 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.settingslib.bluetooth; - -import android.bluetooth.BluetoothAdapter; -import android.bluetooth.BluetoothDevice; -import android.bluetooth.BluetoothLeAudioCodecConfigMetadata; -import android.bluetooth.BluetoothLeAudioContentMetadata; -import android.bluetooth.BluetoothLeBroadcastChannel; -import android.bluetooth.BluetoothLeBroadcastMetadata; -import android.bluetooth.BluetoothLeBroadcastSubgroup; -import android.util.Log; - -import java.nio.charset.StandardCharsets; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.regex.Matcher; -import java.util.regex.Pattern; - -public class LocalBluetoothLeBroadcastMetadata { - private static final boolean DEBUG = BluetoothUtils.D; - private static final String TAG = "LocalBluetoothLeBroadcastMetadata"; - private static final String METADATA_START = "<"; - private static final String METADATA_END = ">"; - private static final String PATTERN_REGEX = "<(.*?)>"; - private static final String PATTERN_BT_BROADCAST_METADATA = - "T:<(.*?)>;+D:<(.*?)>;+AS:<(.*?)>;+B:<(.*?)>;+SI:<(.*?)>;+E:<(.*?)>;+C:<(.*?)>;" - + "+PD:<(.*?)>;+SG:(.*)"; - private static final String PATTERN_BT_SUBGROUP = - "CID:<(.*?)>;+CC:<(.*?);>;+AC:<(.*?);>;+CP:<(.*?)>;+BC:<(.*)>;>;"; - private static final String PATTERN_BT_CHANNEL = "CI:<(.*?)>;+BCCM:<(.*?);>;"; - - /* Index for BluetoothLeBroadcastMetadata */ - private static int MATCH_INDEX_ADDRESS_TYPE = 1; - private static int MATCH_INDEX_DEVICE = 2; - private static int MATCH_INDEX_ADVERTISING_SID = 3; - private static int MATCH_INDEX_BROADCAST_ID = 4; - private static int MATCH_INDEX_SYNC_INTERVAL = 5; - private static int MATCH_INDEX_IS_ENCRYPTED = 6; - private static int MATCH_INDEX_BROADCAST_CODE = 7; - private static int MATCH_INDEX_PRESENTATION_DELAY = 8; - private static int MATCH_INDEX_SUBGROUPS = 9; - - /* Index for BluetoothLeBroadcastSubgroup */ - private static int MATCH_INDEX_CODEC_ID = 1; - private static int MATCH_INDEX_CODEC_CONFIG = 2; - private static int MATCH_INDEX_AUDIO_CONTENT = 3; - private static int MATCH_INDEX_CHANNEL_PREF = 4; - private static int MATCH_INDEX_BROADCAST_CHANNEL = 5; - - /* Index for BluetoothLeAudioCodecConfigMetadata */ - private static int LIST_INDEX_AUDIO_LOCATION = 0; - private static int LIST_INDEX_CODEC_CONFIG_RAW_METADATA = 1; - - /* Index for BluetoothLeAudioContentMetadata */ - private static int LIST_INDEX_PROGRAM_INFO = 0; - private static int LIST_INDEX_LANGUAGE = 1; - private static int LIST_INDEX_AUDIO_CONTENT_RAW_METADATA = 2; - - /* Index for BluetoothLeBroadcastChannel */ - private static int MATCH_INDEX_CHANNEL_INDEX = 1; - private static int MATCH_INDEX_CHANNEL_CODEC_CONFIG = 2; - - private BluetoothLeBroadcastSubgroup mSubgroup; - private List<BluetoothLeBroadcastSubgroup> mSubgroupList; - - // BluetoothLeBroadcastMetadata - // Optional: Identity address type - private int mSourceAddressType; - // Optional: Must use identity address - private BluetoothDevice mSourceDevice; - private int mSourceAdvertisingSid; - private int mBroadcastId; - private int mPaSyncInterval; - private int mPresentationDelayMicros; - private boolean mIsEncrypted; - private byte[] mBroadcastCode; - - // BluetoothLeBroadcastSubgroup - private int mCodecId; - private BluetoothLeAudioContentMetadata mContentMetadata; - private BluetoothLeAudioCodecConfigMetadata mConfigMetadata; - private Boolean mNoChannelPreference; - private List<BluetoothLeBroadcastChannel> mChannel; - - // BluetoothLeAudioCodecConfigMetadata - private long mAudioLocation; - private byte[] mCodecConfigMetadata; - - // BluetoothLeAudioContentMetadata - private String mLanguage; - private String mProgramInfo; - private byte[] mAudioContentMetadata; - - // BluetoothLeBroadcastChannel - private boolean mIsSelected; - private int mChannelIndex; - - - LocalBluetoothLeBroadcastMetadata(BluetoothLeBroadcastMetadata metadata) { - mSourceAddressType = metadata.getSourceAddressType(); - mSourceDevice = metadata.getSourceDevice(); - mSourceAdvertisingSid = metadata.getSourceAdvertisingSid(); - mBroadcastId = metadata.getBroadcastId(); - mPaSyncInterval = metadata.getPaSyncInterval(); - mIsEncrypted = metadata.isEncrypted(); - mBroadcastCode = metadata.getBroadcastCode(); - mPresentationDelayMicros = metadata.getPresentationDelayMicros(); - mSubgroupList = metadata.getSubgroups(); - } - - public LocalBluetoothLeBroadcastMetadata() { - } - - public void setBroadcastCode(byte[] code) { - mBroadcastCode = code; - } - - public int getBroadcastId() { - return mBroadcastId; - } - - public String convertToQrCodeString() { - String subgroupString = convertSubgroupToString(mSubgroupList); - return new StringBuilder() - .append(BluetoothBroadcastUtils.SCHEME_BT_BROADCAST_METADATA) - .append(BluetoothBroadcastUtils.PREFIX_BT_ADDRESS_TYPE) - .append(METADATA_START).append(mSourceAddressType).append(METADATA_END) - .append(BluetoothBroadcastUtils.DELIMITER_QR_CODE) - .append(BluetoothBroadcastUtils.PREFIX_BT_DEVICE) - .append(METADATA_START).append(mSourceDevice).append(METADATA_END) - .append(BluetoothBroadcastUtils.DELIMITER_QR_CODE) - .append(BluetoothBroadcastUtils.PREFIX_BT_ADVERTISING_SID) - .append(METADATA_START).append(mSourceAdvertisingSid).append(METADATA_END) - .append(BluetoothBroadcastUtils.DELIMITER_QR_CODE) - .append(BluetoothBroadcastUtils.PREFIX_BT_BROADCAST_ID) - .append(METADATA_START).append(mBroadcastId).append(METADATA_END) - .append(BluetoothBroadcastUtils.DELIMITER_QR_CODE) - .append(BluetoothBroadcastUtils.PREFIX_BT_SYNC_INTERVAL) - .append(METADATA_START).append(mPaSyncInterval).append(METADATA_END) - .append(BluetoothBroadcastUtils.DELIMITER_QR_CODE) - .append(BluetoothBroadcastUtils.PREFIX_BT_IS_ENCRYPTED) - .append(METADATA_START).append(mIsEncrypted).append(METADATA_END) - .append(BluetoothBroadcastUtils.DELIMITER_QR_CODE) - .append(BluetoothBroadcastUtils.PREFIX_BT_BROADCAST_CODE) - .append(METADATA_START).append(Arrays.toString(mBroadcastCode)).append(METADATA_END) - .append(BluetoothBroadcastUtils.DELIMITER_QR_CODE) - .append(BluetoothBroadcastUtils.PREFIX_BT_PRESENTATION_DELAY) - .append(METADATA_START).append(mPresentationDelayMicros).append(METADATA_END) - .append(BluetoothBroadcastUtils.DELIMITER_QR_CODE) - .append(BluetoothBroadcastUtils.PREFIX_BT_SUBGROUPS) - .append(METADATA_START).append(subgroupString).append(METADATA_END) - .append(BluetoothBroadcastUtils.DELIMITER_QR_CODE) - .toString(); - } - - private String convertSubgroupToString(List<BluetoothLeBroadcastSubgroup> subgroupList) { - StringBuilder subgroupListBuilder = new StringBuilder(); - String subgroupString = ""; - for (BluetoothLeBroadcastSubgroup subgroup: subgroupList) { - String audioCodec = convertAudioCodecConfigToString(subgroup.getCodecSpecificConfig()); - String audioContent = convertAudioContentToString(subgroup.getContentMetadata()); - boolean hasChannelPreference = subgroup.hasChannelPreference(); - String channels = convertChannelToString(subgroup.getChannels()); - subgroupString = new StringBuilder() - .append(BluetoothBroadcastUtils.PREFIX_BTSG_CODEC_ID) - .append(METADATA_START).append(subgroup.getCodecId()).append(METADATA_END) - .append(BluetoothBroadcastUtils.DELIMITER_QR_CODE) - .append(BluetoothBroadcastUtils.PREFIX_BTSG_CODEC_CONFIG) - .append(METADATA_START).append(audioCodec).append(METADATA_END) - .append(BluetoothBroadcastUtils.DELIMITER_QR_CODE) - .append(BluetoothBroadcastUtils.PREFIX_BTSG_AUDIO_CONTENT) - .append(METADATA_START).append(audioContent).append(METADATA_END) - .append(BluetoothBroadcastUtils.DELIMITER_QR_CODE) - .append(BluetoothBroadcastUtils.PREFIX_BTSG_CHANNEL_PREF) - .append(METADATA_START).append(hasChannelPreference).append(METADATA_END) - .append(BluetoothBroadcastUtils.DELIMITER_QR_CODE) - .append(BluetoothBroadcastUtils.PREFIX_BTSG_BROADCAST_CHANNEL) - .append(METADATA_START).append(channels).append(METADATA_END) - .append(BluetoothBroadcastUtils.DELIMITER_QR_CODE) - .toString(); - subgroupListBuilder.append(subgroupString); - } - return subgroupListBuilder.toString(); - } - - private String convertAudioCodecConfigToString(BluetoothLeAudioCodecConfigMetadata config) { - String audioLocation = String.valueOf(config.getAudioLocation()); - String rawMetadata = new String(config.getRawMetadata(), StandardCharsets.UTF_8); - return new StringBuilder() - .append(BluetoothBroadcastUtils.PREFIX_BTCC_AUDIO_LOCATION) - .append(METADATA_START).append(audioLocation).append(METADATA_END) - .append(BluetoothBroadcastUtils.DELIMITER_QR_CODE) - .append(BluetoothBroadcastUtils.PREFIX_BTCC_RAW_METADATA) - .append(METADATA_START).append(rawMetadata).append(METADATA_END) - .append(BluetoothBroadcastUtils.DELIMITER_QR_CODE) - .toString(); - } - - private String convertAudioContentToString(BluetoothLeAudioContentMetadata audioContent) { - String rawMetadata = new String(audioContent.getRawMetadata(), StandardCharsets.UTF_8); - return new StringBuilder() - .append(BluetoothBroadcastUtils.PREFIX_BTAC_PROGRAM_INFO) - .append(METADATA_START).append(audioContent.getProgramInfo()).append(METADATA_END) - .append(BluetoothBroadcastUtils.DELIMITER_QR_CODE) - .append(BluetoothBroadcastUtils.PREFIX_BTAC_LANGUAGE) - .append(METADATA_START).append(audioContent.getLanguage()).append(METADATA_END) - .append(BluetoothBroadcastUtils.DELIMITER_QR_CODE) - .append(BluetoothBroadcastUtils.PREFIX_BTAC_RAW_METADATA) - .append(METADATA_START).append(rawMetadata).append(METADATA_END) - .append(BluetoothBroadcastUtils.DELIMITER_QR_CODE) - .toString(); - } - - private String convertChannelToString(List<BluetoothLeBroadcastChannel> channelList) { - StringBuilder channelListBuilder = new StringBuilder(); - String channelString = ""; - for (BluetoothLeBroadcastChannel channel: channelList) { - String channelAudioCodec = convertAudioCodecConfigToString(channel.getCodecMetadata()); - channelString = new StringBuilder() - .append(BluetoothBroadcastUtils.PREFIX_BTBC_CHANNEL_INDEX) - .append(METADATA_START).append(channel.getChannelIndex()).append(METADATA_END) - .append(BluetoothBroadcastUtils.DELIMITER_QR_CODE) - .append(BluetoothBroadcastUtils.PREFIX_BTBC_CODEC_CONFIG) - .append(METADATA_START).append(channelAudioCodec).append(METADATA_END) - .append(BluetoothBroadcastUtils.DELIMITER_QR_CODE) - .toString(); - channelListBuilder.append(channelString); - } - return channelListBuilder.toString(); - } - - /** - * Example : prefix is with the “BT:”, and end by the Android Version. - * BT:T:<1>;D:<00:11:22:AA:BB:CC>;AS:<1>;B:…;V:T;; - * - * @return BluetoothLeBroadcastMetadata - */ - public BluetoothLeBroadcastMetadata convertToBroadcastMetadata(String qrCodeString) { - if (DEBUG) { - Log.d(TAG, "Convert " + qrCodeString + "to BluetoothLeBroadcastMetadata"); - } - - Pattern pattern = Pattern.compile(PATTERN_BT_BROADCAST_METADATA); - Matcher match = pattern.matcher(qrCodeString); - if (match.find()) { - try { - mSourceAddressType = Integer.parseInt(match.group(MATCH_INDEX_ADDRESS_TYPE)); - mSourceDevice = BluetoothAdapter.getDefaultAdapter().getRemoteDevice( - match.group(MATCH_INDEX_DEVICE)); - mSourceAdvertisingSid = Integer.parseInt( - match.group(MATCH_INDEX_ADVERTISING_SID)); - mBroadcastId = Integer.parseInt(match.group(MATCH_INDEX_BROADCAST_ID)); - mPaSyncInterval = Integer.parseInt(match.group(MATCH_INDEX_SYNC_INTERVAL)); - mIsEncrypted = Boolean.valueOf(match.group(MATCH_INDEX_IS_ENCRYPTED)); - mBroadcastCode = match.group(MATCH_INDEX_BROADCAST_CODE).getBytes(); - mPresentationDelayMicros = - Integer.parseInt(match.group(MATCH_INDEX_PRESENTATION_DELAY)); - - if (DEBUG) { - Log.d(TAG, "Converted qrCodeString result: " - + " ,Type = " + mSourceAddressType - + " ,Device = " + mSourceDevice - + " ,AdSid = " + mSourceAdvertisingSid - + " ,BroadcastId = " + mBroadcastId - + " ,paSync = " + mPaSyncInterval - + " ,encrypted = " + mIsEncrypted - + " ,BroadcastCode = " + Arrays.toString(mBroadcastCode) - + " ,delay = " + mPresentationDelayMicros); - } - - mSubgroup = convertToSubgroup(match.group(MATCH_INDEX_SUBGROUPS)); - - return new BluetoothLeBroadcastMetadata.Builder() - .setSourceDevice(mSourceDevice, mSourceAddressType) - .setSourceAdvertisingSid(mSourceAdvertisingSid) - .setBroadcastId(mBroadcastId) - .setPaSyncInterval(mPaSyncInterval) - .setEncrypted(mIsEncrypted) - .setBroadcastCode(mBroadcastCode) - .setPresentationDelayMicros(mPresentationDelayMicros) - .addSubgroup(mSubgroup) - .build(); - } catch (IllegalArgumentException e) { - Log.d(TAG, "IllegalArgumentException when convert : " + e); - return null; - } - } else { - if (DEBUG) { - Log.d(TAG, "The match fail, can not convert it to BluetoothLeBroadcastMetadata."); - } - return null; - } - } - - private BluetoothLeBroadcastSubgroup convertToSubgroup(String subgroupString) { - if (DEBUG) { - Log.d(TAG, "Convert " + subgroupString + "to BluetoothLeBroadcastSubgroup"); - } - Pattern pattern = Pattern.compile(PATTERN_BT_SUBGROUP); - Matcher match = pattern.matcher(subgroupString); - if (match.find()) { - mCodecId = Integer.parseInt(match.group(MATCH_INDEX_CODEC_ID)); - mConfigMetadata = convertToConfigMetadata(match.group(MATCH_INDEX_CODEC_CONFIG)); - mContentMetadata = convertToContentMetadata(match.group(MATCH_INDEX_AUDIO_CONTENT)); - mNoChannelPreference = Boolean.valueOf(match.group(MATCH_INDEX_CHANNEL_PREF)); - mChannel = - convertToChannel(match.group(MATCH_INDEX_BROADCAST_CHANNEL), mConfigMetadata); - - BluetoothLeBroadcastSubgroup.Builder subgroupBuilder = - new BluetoothLeBroadcastSubgroup.Builder(); - subgroupBuilder.setCodecId(mCodecId); - subgroupBuilder.setCodecSpecificConfig(mConfigMetadata); - subgroupBuilder.setContentMetadata(mContentMetadata); - - for (BluetoothLeBroadcastChannel channel : mChannel) { - subgroupBuilder.addChannel(channel); - } - return subgroupBuilder.build(); - } else { - if (DEBUG) { - Log.d(TAG, - "The match fail, can not convert it to BluetoothLeBroadcastSubgroup."); - } - return null; - } - } - - private BluetoothLeAudioCodecConfigMetadata convertToConfigMetadata( - String configMetadataString) { - if (DEBUG) { - Log.d(TAG, - "Convert " + configMetadataString + "to BluetoothLeAudioCodecConfigMetadata"); - } - Pattern pattern = Pattern.compile(PATTERN_REGEX); - Matcher match = pattern.matcher(configMetadataString); - ArrayList<String> resultList = new ArrayList<>(); - while (match.find()) { - resultList.add(match.group(1)); - Log.d(TAG, "Codec Config match : " + match.group(1)); - } - if (DEBUG) { - Log.d(TAG, "Converted configMetadataString result: " + resultList.size()); - } - if (resultList.size() > 0) { - mAudioLocation = Long.parseLong(resultList.get(LIST_INDEX_AUDIO_LOCATION)); - mCodecConfigMetadata = resultList.get(LIST_INDEX_CODEC_CONFIG_RAW_METADATA).getBytes(); - return new BluetoothLeAudioCodecConfigMetadata.Builder() - .setAudioLocation(mAudioLocation) - .build(); - } else { - if (DEBUG) { - Log.d(TAG, - "The match fail, can not convert it to " - + "BluetoothLeAudioCodecConfigMetadata."); - } - return null; - } - } - - private BluetoothLeAudioContentMetadata convertToContentMetadata(String contentMetadataString) { - if (DEBUG) { - Log.d(TAG, "Convert " + contentMetadataString + "to BluetoothLeAudioContentMetadata"); - } - Pattern pattern = Pattern.compile(PATTERN_REGEX); - Matcher match = pattern.matcher(contentMetadataString); - ArrayList<String> resultList = new ArrayList<>(); - while (match.find()) { - Log.d(TAG, "Audio Content match : " + match.group(1)); - resultList.add(match.group(1)); - } - if (DEBUG) { - Log.d(TAG, "Converted contentMetadataString result: " + resultList.size()); - } - if (resultList.size() > 0) { - mProgramInfo = resultList.get(LIST_INDEX_PROGRAM_INFO); - mLanguage = resultList.get(LIST_INDEX_LANGUAGE); - mAudioContentMetadata = - resultList.get(LIST_INDEX_AUDIO_CONTENT_RAW_METADATA).getBytes(); - - /* TODO(b/265253566) : Need to set the default value for language when the user starts - * the broadcast. - */ - if (mLanguage.equals("null")) { - mLanguage = "eng"; - } - - return new BluetoothLeAudioContentMetadata.Builder() - .setProgramInfo(mProgramInfo) - .setLanguage(mLanguage) - .build(); - } else { - if (DEBUG) { - Log.d(TAG, - "The match fail, can not convert it to BluetoothLeAudioContentMetadata."); - } - return null; - } - } - - private List<BluetoothLeBroadcastChannel> convertToChannel(String channelString, - BluetoothLeAudioCodecConfigMetadata configMetadata) { - if (DEBUG) { - Log.d(TAG, "Convert " + channelString + "to BluetoothLeBroadcastChannel"); - } - Pattern pattern = Pattern.compile(PATTERN_BT_CHANNEL); - Matcher match = pattern.matcher(channelString); - Map<Integer, BluetoothLeAudioCodecConfigMetadata> channel = - new HashMap<Integer, BluetoothLeAudioCodecConfigMetadata>(); - while (match.find()) { - channel.put(Integer.parseInt(match.group(MATCH_INDEX_CHANNEL_INDEX)), - convertToConfigMetadata(match.group(MATCH_INDEX_CHANNEL_CODEC_CONFIG))); - } - - if (channel.size() > 0) { - mIsSelected = false; - ArrayList<BluetoothLeBroadcastChannel> broadcastChannelList = new ArrayList<>(); - for (Map.Entry<Integer, BluetoothLeAudioCodecConfigMetadata> entry : - channel.entrySet()) { - - broadcastChannelList.add( - new BluetoothLeBroadcastChannel.Builder() - .setSelected(mIsSelected) - .setChannelIndex(entry.getKey()) - .setCodecMetadata(entry.getValue()) - .build()); - } - return broadcastChannelList; - } else { - if (DEBUG) { - Log.d(TAG, - "The match fail, can not convert it to BluetoothLeBroadcastChannel."); - } - return null; - } - } -} diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothLeBroadcastMetadata.kt b/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothLeBroadcastMetadata.kt new file mode 100644 index 000000000000..870ea8d8b59c --- /dev/null +++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothLeBroadcastMetadata.kt @@ -0,0 +1,30 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.settingslib.bluetooth + +import android.bluetooth.BluetoothLeBroadcastMetadata +import com.android.settingslib.bluetooth.BluetoothLeBroadcastMetadataExt.toQrCodeString + +@Deprecated("Replace with BluetoothLeBroadcastMetadataExt") +class LocalBluetoothLeBroadcastMetadata(private val metadata: BluetoothLeBroadcastMetadata?) { + + constructor() : this(null) + + fun convertToQrCodeString(): String = metadata?.toQrCodeString() ?: "" + + fun convertToBroadcastMetadata(qrCodeString: String) = + BluetoothLeBroadcastMetadataExt.convertToBroadcastMetadata(qrCodeString) +}
\ No newline at end of file diff --git a/packages/SettingsLib/src/com/android/settingslib/fuelgauge/BatteryStatus.java b/packages/SettingsLib/src/com/android/settingslib/fuelgauge/BatteryStatus.java index 765566391ef2..1251b0dc306a 100644 --- a/packages/SettingsLib/src/com/android/settingslib/fuelgauge/BatteryStatus.java +++ b/packages/SettingsLib/src/com/android/settingslib/fuelgauge/BatteryStatus.java @@ -196,6 +196,9 @@ public class BatteryStatus { /** Gets the battery level from the intent. */ public static int getBatteryLevel(Intent batteryChangedIntent) { + if (batteryChangedIntent == null) { + return -1; /*invalid battery level*/ + } final int level = batteryChangedIntent.getIntExtra(BatteryManager.EXTRA_LEVEL, -1); final int scale = batteryChangedIntent.getIntExtra(BatteryManager.EXTRA_SCALE, 0); return scale == 0 diff --git a/packages/SettingsLib/src/com/android/settingslib/media/PhoneMediaDevice.java b/packages/SettingsLib/src/com/android/settingslib/media/PhoneMediaDevice.java index 1c82be9c801f..34519c993d27 100644 --- a/packages/SettingsLib/src/com/android/settingslib/media/PhoneMediaDevice.java +++ b/packages/SettingsLib/src/com/android/settingslib/media/PhoneMediaDevice.java @@ -70,13 +70,17 @@ public class PhoneMediaDevice extends MediaDevice { name = mContext.getString(R.string.media_transfer_wired_usb_device_name); break; case TYPE_DOCK: - case TYPE_HDMI: - name = mRouteInfo.getName(); + name = mContext.getString(R.string.media_transfer_dock_speaker_device_name); break; case TYPE_BUILTIN_SPEAKER: - default: name = mContext.getString(R.string.media_transfer_this_device_name); break; + case TYPE_HDMI: + name = mContext.getString(R.string.media_transfer_external_device_name); + break; + default: + name = mContext.getString(R.string.media_transfer_default_device_name); + break; } return name.toString(); } diff --git a/packages/SettingsLib/src/com/android/settingslib/mobile/dataservice/SubscriptionInfoDao.java b/packages/SettingsLib/src/com/android/settingslib/mobile/dataservice/SubscriptionInfoDao.java index e835125b9b81..e6b1cfb440fa 100644 --- a/packages/SettingsLib/src/com/android/settingslib/mobile/dataservice/SubscriptionInfoDao.java +++ b/packages/SettingsLib/src/com/android/settingslib/mobile/dataservice/SubscriptionInfoDao.java @@ -16,14 +16,13 @@ package com.android.settingslib.mobile.dataservice; -import java.util.List; - import androidx.lifecycle.LiveData; import androidx.room.Dao; import androidx.room.Insert; import androidx.room.OnConflictStrategy; import androidx.room.Query; -import androidx.room.Update; + +import java.util.List; @Dao public interface SubscriptionInfoDao { @@ -32,7 +31,9 @@ public interface SubscriptionInfoDao { void insertSubsInfo(SubscriptionInfoEntity... subscriptionInfo); @Query("SELECT * FROM " + DataServiceUtils.SubscriptionInfoData.TABLE_NAME + " ORDER BY " - + DataServiceUtils.SubscriptionInfoData.COLUMN_ID) + + " CASE WHEN " + DataServiceUtils.SubscriptionInfoData.COLUMN_SIM_SLOT_INDEX + + " >= 0 THEN 1 ELSE 2 END , " + + DataServiceUtils.SubscriptionInfoData.COLUMN_SIM_SLOT_INDEX) LiveData<List<SubscriptionInfoEntity>> queryAvailableSubInfos(); @Query("SELECT * FROM " + DataServiceUtils.SubscriptionInfoData.TABLE_NAME + " WHERE " @@ -43,7 +44,8 @@ public interface SubscriptionInfoDao { + DataServiceUtils.SubscriptionInfoData.COLUMN_IS_ACTIVE_SUBSCRIPTION_ID + " = :isActiveSubscription" + " AND " + DataServiceUtils.SubscriptionInfoData.COLUMN_IS_SUBSCRIPTION_VISIBLE - + " = :isSubscriptionVisible") + + " = :isSubscriptionVisible" + " ORDER BY " + + DataServiceUtils.SubscriptionInfoData.COLUMN_SIM_SLOT_INDEX) LiveData<List<SubscriptionInfoEntity>> queryActiveSubInfos( boolean isActiveSubscription, boolean isSubscriptionVisible); diff --git a/packages/SettingsLib/tests/unit/Android.bp b/packages/SettingsLib/tests/unit/Android.bp new file mode 100644 index 000000000000..a4558f1bd40b --- /dev/null +++ b/packages/SettingsLib/tests/unit/Android.bp @@ -0,0 +1,35 @@ +// +// Copyright (C) 2023 The Android Open Source Project +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +package { + default_applicable_licenses: ["frameworks_base_license"], +} + +android_test { + name: "SettingsLibUnitTests", + test_suites: ["device-tests"], + + srcs: [ + "src/**/*.kt", + ], + + static_libs: [ + "SettingsLib", + "androidx.test.ext.junit", + "androidx.test.runner", + "truth-prebuilt", + ], +} diff --git a/packages/SettingsLib/tests/unit/AndroidManifest.xml b/packages/SettingsLib/tests/unit/AndroidManifest.xml new file mode 100644 index 000000000000..568f9cb2b79e --- /dev/null +++ b/packages/SettingsLib/tests/unit/AndroidManifest.xml @@ -0,0 +1,29 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + Copyright (C) 2023 The Android Open Source Project + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +--> + +<manifest xmlns:android="http://schemas.android.com/apk/res/android" + package="com.android.settingslib.test"> + <application> + <uses-library android:name="android.test.runner" /> + </application> + + <instrumentation + android:name="androidx.test.runner.AndroidJUnitRunner" + android:label="Tests for SettingsLib" + android:targetPackage="com.android.settingslib.test"> + </instrumentation> +</manifest> diff --git a/packages/SettingsLib/tests/unit/OWNERS b/packages/SettingsLib/tests/unit/OWNERS new file mode 100644 index 000000000000..66559252048b --- /dev/null +++ b/packages/SettingsLib/tests/unit/OWNERS @@ -0,0 +1,2 @@ +# We do not guard tests - everyone is welcomed to contribute to tests. +per-file *.kt=* diff --git a/packages/SettingsLib/tests/unit/src/com/android/settingslib/bluetooth/BluetoothLeBroadcastMetadataExtTest.kt b/packages/SettingsLib/tests/unit/src/com/android/settingslib/bluetooth/BluetoothLeBroadcastMetadataExtTest.kt new file mode 100644 index 000000000000..0e3590d96a14 --- /dev/null +++ b/packages/SettingsLib/tests/unit/src/com/android/settingslib/bluetooth/BluetoothLeBroadcastMetadataExtTest.kt @@ -0,0 +1,85 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.settingslib.bluetooth + +import android.bluetooth.BluetoothAdapter +import android.bluetooth.BluetoothDevice +import android.bluetooth.BluetoothLeAudioCodecConfigMetadata +import android.bluetooth.BluetoothLeAudioContentMetadata +import android.bluetooth.BluetoothLeBroadcastChannel +import android.bluetooth.BluetoothLeBroadcastMetadata +import android.bluetooth.BluetoothLeBroadcastSubgroup +import androidx.test.ext.junit.runners.AndroidJUnit4 +import com.android.settingslib.bluetooth.BluetoothLeBroadcastMetadataExt.toQrCodeString +import com.google.common.truth.Truth.assertThat +import org.junit.Test +import org.junit.runner.RunWith + +@RunWith(AndroidJUnit4::class) +class BluetoothLeBroadcastMetadataExtTest { + + @Test + fun toQrCodeString() { + val subgroup = BluetoothLeBroadcastSubgroup.Builder().apply { + setCodecId(100) + val audioCodecConfigMetadata = BluetoothLeAudioCodecConfigMetadata.Builder().build() + setCodecSpecificConfig(audioCodecConfigMetadata) + setContentMetadata(BluetoothLeAudioContentMetadata.Builder().build()) + addChannel(BluetoothLeBroadcastChannel.Builder().apply { + setChannelIndex(1000) + setCodecMetadata(audioCodecConfigMetadata) + }.build()) + }.build() + + val metadata = BluetoothLeBroadcastMetadata.Builder().apply { + setSourceDevice(Device, 0) + setSourceAdvertisingSid(1) + setBroadcastId(2) + setPaSyncInterval(3) + setEncrypted(true) + setBroadcastCode(byteArrayOf(10, 11, 12, 13)) + setPresentationDelayMicros(4) + addSubgroup(subgroup) + }.build() + + val qrCodeString = metadata.toQrCodeString() + + assertThat(qrCodeString).isEqualTo(QR_CODE_STRING) + } + + @Test + fun decodeAndEncodeAgain_sameString() { + val metadata = BluetoothLeBroadcastMetadataExt.convertToBroadcastMetadata(QR_CODE_STRING)!! + + val qrCodeString = metadata.toQrCodeString() + + assertThat(qrCodeString).isEqualTo(QR_CODE_STRING) + } + + private companion object { + const val TEST_DEVICE_ADDRESS = "00:A1:A1:A1:A1:A1" + + val Device: BluetoothDevice = + BluetoothAdapter.getDefaultAdapter().getRemoteDevice(TEST_DEVICE_ADDRESS) + + const val QR_CODE_STRING = + "BT:BluetoothLeBroadcastMetadata:AAAAAAEAAAABAAAAEQAAADAAMAA6AEEAMQA6AEEAMQA6AEEAMQA6" + + "AEEAMQA6AEEAMQAAAAAAAAABAAAAAgAAAAMAAAABAAAABAAAAAQAAAAKCwwNBAAAAAEAAAABAAAAZAAA" + + "AAAAAAABAAAAAAAAAAAAAAAGAAAABgAAAAUDAAAAAAAAAAAAAAAAAAAAAAAAAQAAAP//////////AAAA" + + "AAAAAAABAAAAAQAAAAAAAADoAwAAAQAAAAAAAAAAAAAABgAAAAYAAAAFAwAAAAAAAAAAAAAAAAAAAAAA" + + "AAAAAAD/////AAAAAAAAAAA=" + } +}
\ No newline at end of file diff --git a/packages/SystemUI/compose/core/src/com/android/compose/animation/Easings.kt b/packages/SystemUI/compose/core/src/com/android/compose/animation/Easings.kt new file mode 100644 index 000000000000..8fe1f48dcaee --- /dev/null +++ b/packages/SystemUI/compose/core/src/com/android/compose/animation/Easings.kt @@ -0,0 +1,63 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.compose.animation + +import androidx.compose.animation.core.Easing +import androidx.core.animation.Interpolator +import com.android.app.animation.InterpolatorsAndroidX + +/** + * Compose-compatible definition of Android motion eases, see + * https://carbon.googleplex.com/android-motion/pages/easing + */ +object Easings { + + /** The standard interpolator that should be used on every normal animation */ + val StandardEasing = fromInterpolator(InterpolatorsAndroidX.STANDARD) + + /** + * The standard accelerating interpolator that should be used on every regular movement of + * content that is disappearing e.g. when moving off screen. + */ + val StandardAccelerateEasing = fromInterpolator(InterpolatorsAndroidX.STANDARD_ACCELERATE) + + /** + * The standard decelerating interpolator that should be used on every regular movement of + * content that is appearing e.g. when coming from off screen. + */ + val StandardDecelerateEasing = fromInterpolator(InterpolatorsAndroidX.STANDARD_DECELERATE) + + /** The default emphasized interpolator. Used for hero / emphasized movement of content. */ + val EmphasizedEasing = fromInterpolator(InterpolatorsAndroidX.EMPHASIZED) + + /** + * The accelerated emphasized interpolator. Used for hero / emphasized movement of content that + * is disappearing e.g. when moving off screen. + */ + val EmphasizedAccelerateEasing = fromInterpolator(InterpolatorsAndroidX.EMPHASIZED_ACCELERATE) + + /** + * The decelerating emphasized interpolator. Used for hero / emphasized movement of content that + * is appearing e.g. when coming from off screen + */ + val EmphasizedDecelerateEasing = fromInterpolator(InterpolatorsAndroidX.EMPHASIZED_DECELERATE) + + /** The linear interpolator. */ + val LinearEasing = fromInterpolator(InterpolatorsAndroidX.LINEAR) + + private fun fromInterpolator(source: Interpolator) = Easing { x -> source.getInterpolation(x) } +} diff --git a/packages/SystemUI/compose/facade/enabled/src/com/android/systemui/scene/ui/composable/SceneModule.kt b/packages/SystemUI/compose/facade/enabled/src/com/android/systemui/scene/ui/composable/SceneModule.kt index 954bad56bcc2..d3643747ad91 100644 --- a/packages/SystemUI/compose/facade/enabled/src/com/android/systemui/scene/ui/composable/SceneModule.kt +++ b/packages/SystemUI/compose/facade/enabled/src/com/android/systemui/scene/ui/composable/SceneModule.kt @@ -16,6 +16,7 @@ package com.android.systemui.scene.ui.composable +import android.content.Context import com.android.systemui.bouncer.ui.composable.BouncerScene import com.android.systemui.bouncer.ui.viewmodel.BouncerViewModel import com.android.systemui.dagger.SysUISingleton @@ -28,6 +29,7 @@ import com.android.systemui.scene.shared.model.Scene import com.android.systemui.scene.shared.model.SceneContainerNames import com.android.systemui.shade.ui.composable.ShadeScene import com.android.systemui.shade.ui.viewmodel.ShadeSceneViewModel +import com.android.systemui.statusbar.phone.SystemUIDialog import dagger.Module import dagger.Provides import javax.inject.Named @@ -57,6 +59,7 @@ object SceneModule { @SysUISingleton @Named(SceneContainerNames.SYSTEM_UI_DEFAULT) fun bouncerScene( + @Application context: Context, viewModelFactory: BouncerViewModel.Factory, ): BouncerScene { return BouncerScene( @@ -64,6 +67,7 @@ object SceneModule { viewModelFactory.create( containerName = SceneContainerNames.SYSTEM_UI_DEFAULT, ), + dialogFactory = { SystemUIDialog(context) }, ) } diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/BouncerScene.kt b/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/BouncerScene.kt index 3c74ef5adfeb..240bace21a1c 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/BouncerScene.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/BouncerScene.kt @@ -14,9 +14,16 @@ * limitations under the License. */ +@file:OptIn(ExperimentalMaterial3Api::class) + package com.android.systemui.bouncer.ui.composable +import android.app.AlertDialog +import android.app.Dialog +import android.content.DialogInterface import androidx.compose.animation.Crossfade +import androidx.compose.animation.core.snap +import androidx.compose.animation.core.tween import androidx.compose.foundation.background import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box @@ -26,15 +33,20 @@ import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.padding import androidx.compose.material3.Button import androidx.compose.material3.ButtonDefaults +import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.collectAsState import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp +import com.android.systemui.R import com.android.systemui.bouncer.ui.viewmodel.AuthMethodBouncerViewModel import com.android.systemui.bouncer.ui.viewmodel.BouncerViewModel import com.android.systemui.bouncer.ui.viewmodel.PasswordBouncerViewModel @@ -51,6 +63,7 @@ import kotlinx.coroutines.flow.asStateFlow /** The bouncer scene displays authentication challenges like PIN, password, or pattern. */ class BouncerScene( private val viewModel: BouncerViewModel, + private val dialogFactory: () -> AlertDialog, ) : ComposableScene { override val key = SceneKey.Bouncer @@ -68,16 +81,19 @@ class BouncerScene( override fun Content( containerName: String, modifier: Modifier, - ) = BouncerScene(viewModel, modifier) + ) = BouncerScene(viewModel, dialogFactory, modifier) } @Composable private fun BouncerScene( viewModel: BouncerViewModel, + dialogFactory: () -> AlertDialog, modifier: Modifier = Modifier, ) { - val message: String by viewModel.message.collectAsState() + val message: BouncerViewModel.MessageViewModel by viewModel.message.collectAsState() val authMethodViewModel: AuthMethodBouncerViewModel? by viewModel.authMethod.collectAsState() + val dialogMessage: String? by viewModel.throttlingDialogMessage.collectAsState() + var dialog: Dialog? by remember { mutableStateOf(null) } Column( horizontalAlignment = Alignment.CenterHorizontally, @@ -88,9 +104,10 @@ private fun BouncerScene( Crossfade( targetState = message, label = "Bouncer message", - ) { + animationSpec = if (message.isUpdateAnimated) tween() else snap(), + ) { message -> Text( - text = it, + text = message.text, color = MaterialTheme.colorScheme.onSurface, style = MaterialTheme.typography.bodyLarge, ) @@ -132,5 +149,26 @@ private fun BouncerScene( style = MaterialTheme.typography.bodyMedium, ) } + + if (dialogMessage != null) { + if (dialog == null) { + dialog = + dialogFactory().apply { + setMessage(dialogMessage) + setButton( + DialogInterface.BUTTON_NEUTRAL, + context.getString(R.string.ok), + ) { _, _ -> + viewModel.onThrottlingDialogDismissed() + } + setCancelable(false) + setCanceledOnTouchOutside(false) + show() + } + } + } else { + dialog?.dismiss() + dialog = null + } } } diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/PasswordBouncer.kt b/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/PasswordBouncer.kt index 4e85621e9e23..7545ff464bab 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/PasswordBouncer.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/PasswordBouncer.kt @@ -53,6 +53,8 @@ internal fun PasswordBouncer( ) { val focusRequester = remember { FocusRequester() } val password: String by viewModel.password.collectAsState() + val isInputEnabled: Boolean by viewModel.isInputEnabled.collectAsState() + val animateFailure: Boolean by viewModel.animateFailure.collectAsState() LaunchedEffect(Unit) { // When the UI comes up, request focus on the TextField to bring up the software keyboard. @@ -61,6 +63,13 @@ internal fun PasswordBouncer( viewModel.onShown() } + LaunchedEffect(animateFailure) { + if (animateFailure) { + // We don't currently have a failure animation for password, just consume it: + viewModel.onFailureAnimationShown() + } + } + Column( horizontalAlignment = Alignment.CenterHorizontally, modifier = modifier, @@ -71,6 +80,7 @@ internal fun PasswordBouncer( TextField( value = password, onValueChange = viewModel::onPasswordInputChanged, + enabled = isInputEnabled, visualTransformation = PasswordVisualTransformation(), singleLine = true, textStyle = LocalTextStyle.current.copy(textAlign = TextAlign.Center), diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/PatternBouncer.kt b/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/PatternBouncer.kt index 3afd33f4c90c..88441146ad23 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/PatternBouncer.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/PatternBouncer.kt @@ -18,6 +18,7 @@ package com.android.systemui.bouncer.ui.composable import android.view.HapticFeedbackConstants import androidx.compose.animation.core.Animatable +import androidx.compose.animation.core.AnimationVector1D import androidx.compose.animation.core.tween import androidx.compose.foundation.Canvas import androidx.compose.foundation.gestures.detectDragGestures @@ -41,12 +42,15 @@ import androidx.compose.ui.platform.LocalView import androidx.compose.ui.res.integerResource import androidx.compose.ui.unit.IntSize import androidx.compose.ui.unit.dp +import com.android.compose.animation.Easings import com.android.internal.R import com.android.systemui.bouncer.ui.viewmodel.PatternBouncerViewModel import com.android.systemui.bouncer.ui.viewmodel.PatternDotViewModel +import com.android.systemui.compose.modifiers.thenIf import kotlin.math.min import kotlin.math.pow import kotlin.math.sqrt +import kotlinx.coroutines.coroutineScope import kotlinx.coroutines.launch /** @@ -66,9 +70,9 @@ internal fun PatternBouncer( val rowCount = viewModel.rowCount val dotColor = MaterialTheme.colorScheme.secondary - val dotRadius = with(LocalDensity.current) { 8.dp.toPx() } + val dotRadius = with(LocalDensity.current) { (DOT_DIAMETER_DP / 2).dp.toPx() } val lineColor = MaterialTheme.colorScheme.primary - val lineStrokeWidth = dotRadius * 2 + with(LocalDensity.current) { 4.dp.toPx() } + val lineStrokeWidth = with(LocalDensity.current) { LINE_STROKE_WIDTH_DP.dp.toPx() } var containerSize: IntSize by remember { mutableStateOf(IntSize(0, 0)) } val horizontalSpacing = containerSize.width / colCount @@ -82,6 +86,9 @@ internal fun PatternBouncer( val currentDot: PatternDotViewModel? by viewModel.currentDot.collectAsState() // The dots selected so far, if the user is currently dragging. val selectedDots: List<PatternDotViewModel> by viewModel.selectedDots.collectAsState() + val isInputEnabled: Boolean by viewModel.isInputEnabled.collectAsState() + val isAnimationEnabled: Boolean by viewModel.isPatternVisible.collectAsState() + val animateFailure: Boolean by viewModel.animateFailure.collectAsState() // Map of animatables for the scale of each dot, keyed by dot. val dotScalingAnimatables = remember(dots) { dots.associateWith { Animatable(1f) } } @@ -96,19 +103,46 @@ internal fun PatternBouncer( val view = LocalView.current // When the current dot is changed, we need to update our animations. - LaunchedEffect(currentDot) { - view.performHapticFeedback( - HapticFeedbackConstants.VIRTUAL_KEY, - HapticFeedbackConstants.FLAG_IGNORE_VIEW_SETTING, - ) + LaunchedEffect(currentDot, isAnimationEnabled) { + // Perform haptic feedback, but only if the current dot is not null, so we don't perform it + // when the UI first shows up or when the user lifts their pointer/finger. + if (currentDot != null) { + view.performHapticFeedback( + HapticFeedbackConstants.VIRTUAL_KEY, + HapticFeedbackConstants.FLAG_IGNORE_VIEW_SETTING, + ) + } + + if (!isAnimationEnabled) { + return@LaunchedEffect + } - // Make sure that the current dot is scaled up while the other dots are scaled back down. + // Make sure that the current dot is scaled up while the other dots are scaled back + // down. dotScalingAnimatables.entries.forEach { (dot, animatable) -> val isSelected = dot == currentDot - launch { - animatable.animateTo(if (isSelected) 2f else 1f) + // Launch using the longer-lived scope because we want these animations to proceed to + // completion even if the LaunchedEffect is canceled because its key objects have + // changed. + scope.launch { if (isSelected) { - animatable.animateTo(1f) + animatable.animateTo( + targetValue = (SELECTED_DOT_DIAMETER_DP / DOT_DIAMETER_DP.toFloat()), + animationSpec = + tween( + durationMillis = SELECTED_DOT_REACTION_ANIMATION_DURATION_MS, + easing = Easings.StandardAccelerateEasing, + ), + ) + } else { + animatable.animateTo( + targetValue = 1f, + animationSpec = + tween( + durationMillis = SELECTED_DOT_RETRACT_ANIMATION_DURATION_MS, + easing = Easings.StandardDecelerateEasing, + ), + ) } } } @@ -116,14 +150,18 @@ internal fun PatternBouncer( selectedDots.forEach { dot -> lineFadeOutAnimatables[dot]?.let { line -> if (!line.isRunning) { + // Launch using the longer-lived scope because we want these animations to + // proceed to completion even if the LaunchedEffect is canceled because its key + // objects have changed. scope.launch { if (dot == currentDot) { - // Reset the fade-out animation for the current dot. When the current - // dot is switched, this entire code block runs again for the newly - // selected dot. + // Reset the fade-out animation for the current dot. When the + // current dot is switched, this entire code block runs again for + // the newly selected dot. line.snapTo(1f) } else { - // For all non-current dots, make sure that the lines are fading out. + // For all non-current dots, make sure that the lines are fading + // out. line.animateTo( targetValue = 0f, animationSpec = @@ -139,6 +177,17 @@ internal fun PatternBouncer( } } + // Show the failure animation if the user entered the wrong input. + LaunchedEffect(animateFailure) { + if (animateFailure) { + showFailureAnimation( + dots = dots, + scalingAnimatables = dotScalingAnimatables, + ) + viewModel.onFailureAnimationShown() + } + } + // This is the position of the input pointer. var inputPosition: Offset? by remember { mutableStateOf(null) } @@ -148,27 +197,34 @@ internal fun PatternBouncer( // when it leaves the bounds of the dot grid. .clipToBounds() .onSizeChanged { containerSize = it } - .pointerInput(Unit) { - detectDragGestures( - onDragStart = { start -> - inputPosition = start - viewModel.onDragStart() - }, - onDragEnd = { - inputPosition = null - lineFadeOutAnimatables.values.forEach { animatable -> - scope.launch { animatable.animateTo(1f) } - } - viewModel.onDragEnd() - }, - ) { change, _ -> - inputPosition = change.position - viewModel.onDrag( - xPx = change.position.x, - yPx = change.position.y, - containerSizePx = containerSize.width, - verticalOffsetPx = verticalOffset, - ) + .thenIf(isInputEnabled) { + Modifier.pointerInput(Unit) { + detectDragGestures( + onDragStart = { start -> + inputPosition = start + viewModel.onDragStart() + }, + onDragEnd = { + inputPosition = null + if (isAnimationEnabled) { + lineFadeOutAnimatables.values.forEach { animatable -> + // Launch using the longer-lived scope because we want these + // animations to proceed to completion even if the surrounding + // scope is canceled. + scope.launch { animatable.animateTo(1f) } + } + } + viewModel.onDragEnd() + }, + ) { change, _ -> + inputPosition = change.position + viewModel.onDrag( + xPx = change.position.x, + yPx = change.position.y, + containerSizePx = containerSize.width, + verticalOffsetPx = verticalOffset, + ) + } } } ) { @@ -247,3 +303,62 @@ private fun lineAlpha(gridSpacing: Float, lineLength: Float = gridSpacing): Floa // farther the user input pointer goes from the line, the more opaque the line gets. return ((lineLength / gridSpacing - 0.3f) * 4f).coerceIn(0f, 1f) } + +private suspend fun showFailureAnimation( + dots: List<PatternDotViewModel>, + scalingAnimatables: Map<PatternDotViewModel, Animatable<Float, AnimationVector1D>>, +) { + val dotsByRow = + buildList<MutableList<PatternDotViewModel>> { + dots.forEach { dot -> + val rowIndex = dot.y + while (size <= rowIndex) { + add(mutableListOf()) + } + get(rowIndex).add(dot) + } + } + + coroutineScope { + dotsByRow.forEachIndexed { rowIndex, rowDots -> + rowDots.forEach { dot -> + scalingAnimatables[dot]?.let { dotScaleAnimatable -> + launch { + dotScaleAnimatable.animateTo( + targetValue = + FAILURE_ANIMATION_DOT_DIAMETER_DP / DOT_DIAMETER_DP.toFloat(), + animationSpec = + tween( + durationMillis = + FAILURE_ANIMATION_DOT_SHRINK_ANIMATION_DURATION_MS, + delayMillis = + rowIndex * FAILURE_ANIMATION_DOT_SHRINK_STAGGER_DELAY_MS, + easing = Easings.LinearEasing, + ), + ) + + dotScaleAnimatable.animateTo( + targetValue = 1f, + animationSpec = + tween( + durationMillis = + FAILURE_ANIMATION_DOT_REVERT_ANIMATION_DURATION, + easing = Easings.StandardEasing, + ), + ) + } + } + } + } + } +} + +private const val DOT_DIAMETER_DP = 16 +private const val SELECTED_DOT_DIAMETER_DP = 24 +private const val SELECTED_DOT_REACTION_ANIMATION_DURATION_MS = 83 +private const val SELECTED_DOT_RETRACT_ANIMATION_DURATION_MS = 750 +private const val LINE_STROKE_WIDTH_DP = 16 +private const val FAILURE_ANIMATION_DOT_DIAMETER_DP = 13 +private const val FAILURE_ANIMATION_DOT_SHRINK_ANIMATION_DURATION_MS = 50 +private const val FAILURE_ANIMATION_DOT_SHRINK_STAGGER_DELAY_MS = 33 +private const val FAILURE_ANIMATION_DOT_REVERT_ANIMATION_DURATION = 617 diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/PinBouncer.kt b/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/PinBouncer.kt index 9c210c225ab3..968e5ab8ad8c 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/PinBouncer.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/PinBouncer.kt @@ -18,11 +18,15 @@ package com.android.systemui.bouncer.ui.composable +import android.view.HapticFeedbackConstants import androidx.compose.animation.AnimatedVisibility import androidx.compose.animation.ExperimentalAnimationApi import androidx.compose.animation.animateColorAsState import androidx.compose.animation.animateContentSize +import androidx.compose.animation.core.AnimationSpec +import androidx.compose.animation.core.LinearEasing import androidx.compose.animation.core.animateDpAsState +import androidx.compose.animation.core.tween import androidx.compose.animation.fadeIn import androidx.compose.animation.fadeOut import androidx.compose.animation.scaleIn @@ -48,6 +52,7 @@ import androidx.compose.runtime.collectAsState import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember +import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier @@ -55,15 +60,23 @@ import androidx.compose.ui.draw.drawBehind import androidx.compose.ui.geometry.CornerRadius import androidx.compose.ui.graphics.Color import androidx.compose.ui.input.pointer.pointerInput +import androidx.compose.ui.platform.LocalView import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.dp +import com.android.compose.animation.Easings import com.android.compose.grid.VerticalGrid import com.android.systemui.R import com.android.systemui.bouncer.ui.viewmodel.PinBouncerViewModel import com.android.systemui.common.shared.model.ContentDescription import com.android.systemui.common.shared.model.Icon import com.android.systemui.common.ui.compose.Icon +import com.android.systemui.compose.modifiers.thenIf import kotlin.math.max +import kotlin.time.Duration.Companion.milliseconds +import kotlin.time.DurationUnit +import kotlinx.coroutines.async +import kotlinx.coroutines.delay +import kotlinx.coroutines.launch @Composable internal fun PinBouncer( @@ -75,6 +88,16 @@ internal fun PinBouncer( // The length of the PIN input received so far, so we know how many dots to render. val pinLength: Pair<Int, Int> by viewModel.pinLengths.collectAsState() + val isInputEnabled: Boolean by viewModel.isInputEnabled.collectAsState() + val animateFailure: Boolean by viewModel.animateFailure.collectAsState() + + // Show the failure animation if the user entered the wrong input. + LaunchedEffect(animateFailure) { + if (animateFailure) { + showFailureAnimation() + viewModel.onFailureAnimationShown() + } + } Column( horizontalAlignment = Alignment.CenterHorizontally, @@ -116,6 +139,7 @@ internal fun PinBouncer( val digit = index + 1 PinButton( onClicked = { viewModel.onPinButtonClicked(digit) }, + isEnabled = isInputEnabled, ) { contentColor -> PinDigit(digit, contentColor) } @@ -124,7 +148,8 @@ internal fun PinBouncer( PinButton( onClicked = { viewModel.onBackspaceButtonClicked() }, onLongPressed = { viewModel.onBackspaceButtonLongPressed() }, - isHighlighted = true, + isEnabled = isInputEnabled, + isIconButton = true, ) { contentColor -> PinIcon( Icon.Resource( @@ -138,13 +163,15 @@ internal fun PinBouncer( PinButton( onClicked = { viewModel.onPinButtonClicked(0) }, + isEnabled = isInputEnabled, ) { contentColor -> PinDigit(0, contentColor) } PinButton( onClicked = { viewModel.onAuthenticateButtonClicked() }, - isHighlighted = true, + isEnabled = isInputEnabled, + isIconButton = true, ) { contentColor -> PinIcon( Icon.Resource( @@ -187,61 +214,107 @@ private fun PinIcon( @Composable private fun PinButton( onClicked: () -> Unit, + isEnabled: Boolean, modifier: Modifier = Modifier, onLongPressed: (() -> Unit)? = null, - isHighlighted: Boolean = false, + isIconButton: Boolean = false, content: @Composable (contentColor: Color) -> Unit, ) { var isPressed: Boolean by remember { mutableStateOf(false) } + + val view = LocalView.current + LaunchedEffect(isPressed) { + if (isPressed) { + view.performHapticFeedback( + HapticFeedbackConstants.VIRTUAL_KEY, + HapticFeedbackConstants.FLAG_IGNORE_VIEW_SETTING, + ) + } + } + + // Pin button animation specification is asymmetric: fast animation to the pressed state, and a + // slow animation upon release. Note that isPressed is guaranteed to be true for at least the + // press animation duration (see below in detectTapGestures). + val animEasing = if (isPressed) pinButtonPressedEasing else pinButtonReleasedEasing + val animDurationMillis = + (if (isPressed) pinButtonPressedDuration else pinButtonReleasedDuration).toInt( + DurationUnit.MILLISECONDS + ) + val cornerRadius: Dp by animateDpAsState( - if (isPressed) 24.dp else PinButtonSize / 2, + if (isPressed) 24.dp else pinButtonSize / 2, label = "PinButton round corners", + animationSpec = tween(animDurationMillis, easing = animEasing) ) + val colorAnimationSpec: AnimationSpec<Color> = tween(animDurationMillis, easing = animEasing) val containerColor: Color by animateColorAsState( when { - isPressed -> MaterialTheme.colorScheme.primaryContainer - isHighlighted -> MaterialTheme.colorScheme.secondaryContainer - else -> MaterialTheme.colorScheme.surface + isPressed -> MaterialTheme.colorScheme.primary + isIconButton -> MaterialTheme.colorScheme.secondaryContainer + else -> MaterialTheme.colorScheme.surfaceVariant }, label = "Pin button container color", + animationSpec = colorAnimationSpec ) val contentColor: Color by animateColorAsState( when { - isPressed -> MaterialTheme.colorScheme.onPrimaryContainer - isHighlighted -> MaterialTheme.colorScheme.onSecondaryContainer - else -> MaterialTheme.colorScheme.onSurface + isPressed -> MaterialTheme.colorScheme.onPrimary + isIconButton -> MaterialTheme.colorScheme.onSecondaryContainer + else -> MaterialTheme.colorScheme.onSurfaceVariant }, label = "Pin button container color", + animationSpec = colorAnimationSpec ) + val scope = rememberCoroutineScope() + Box( contentAlignment = Alignment.Center, modifier = modifier - .size(PinButtonSize) + .size(pinButtonSize) .drawBehind { drawRoundRect( color = containerColor, cornerRadius = CornerRadius(cornerRadius.toPx()), ) } - .pointerInput(Unit) { - detectTapGestures( - onPress = { - isPressed = true - tryAwaitRelease() - isPressed = false - }, - onTap = { onClicked() }, - onLongPress = onLongPressed?.let { { onLongPressed() } }, - ) + .thenIf(isEnabled) { + Modifier.pointerInput(Unit) { + detectTapGestures( + onPress = { + scope.launch { + isPressed = true + val minDuration = async { + delay(pinButtonPressedDuration + pinButtonHoldTime) + } + tryAwaitRelease() + minDuration.await() + isPressed = false + } + }, + onTap = { onClicked() }, + onLongPress = onLongPressed?.let { { onLongPressed() } }, + ) + } }, ) { content(contentColor) } } -private val PinButtonSize = 84.dp +private fun showFailureAnimation() { + // TODO(b/282730134): implement. +} + +private val pinButtonSize = 84.dp + +// Pin button motion spec: http://shortn/_9TTIG6SoEa +private val pinButtonPressedDuration = 100.milliseconds +private val pinButtonPressedEasing = LinearEasing +private val pinButtonHoldTime = 33.milliseconds +private val pinButtonReleasedDuration = 420.milliseconds +private val pinButtonReleasedEasing = Easings.StandardEasing diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/compose/modifiers/ConditionalModifiers.kt b/packages/SystemUI/compose/features/src/com/android/systemui/compose/modifiers/ConditionalModifiers.kt new file mode 100644 index 000000000000..83071d78c64d --- /dev/null +++ b/packages/SystemUI/compose/features/src/com/android/systemui/compose/modifiers/ConditionalModifiers.kt @@ -0,0 +1,52 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.compose.modifiers + +import androidx.compose.ui.Modifier + +/** + * Concatenates this modifier with another if `condition` is true. + * + * @param condition Whether or not to apply the modifiers. + * @param factory Creates the modifier to concatenate with the current one. + * @return a Modifier representing this modifier followed by other in sequence. + * @see Modifier.then + * + * This method allows inline conditional addition of modifiers to a modifier chain. Instead of + * writing + * + * ``` + * val aModifier = Modifier.a() + * val bModifier = if(condition) aModifier.b() else aModifier + * Composable(modifier = bModifier) + * ``` + * + * You can instead write + * + * ``` + * Composable(modifier = Modifier.a().thenIf(condition){ + * Modifier.b() + * } + * ``` + * + * This makes the modifier chain easier to read. + * + * Note that unlike the non-factory version, the conditional modifier is recreated each time, and + * may never be created at all. + */ +inline fun Modifier.thenIf(condition: Boolean, crossinline factory: () -> Modifier): Modifier = + if (condition) this.then(factory()) else this diff --git a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/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/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/res-keyguard/values-sw600dp-land/integers.xml b/packages/SystemUI/res-keyguard/values-sw600dp-land/integers.xml new file mode 100644 index 000000000000..2a8092010a37 --- /dev/null +++ b/packages/SystemUI/res-keyguard/values-sw600dp-land/integers.xml @@ -0,0 +1,20 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + ~ Copyright (C) 2023 The Android Open Source Project + ~ + ~ Licensed under the Apache License, Version 2.0 (the "License"); + ~ you may not use this file except in compliance with the License. + ~ You may obtain a copy of the License at + ~ + ~ http://www.apache.org/licenses/LICENSE-2.0 + ~ + ~ Unless required by applicable law or agreed to in writing, software + ~ distributed under the License is distributed on an "AS IS" BASIS, + ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + ~ See the License for the specific language governing permissions and + ~ limitations under the License. + --> +<resources> + <!-- Invisibility to use for the date & weather view when it is disabled by a clock --> + <integer name="keyguard_date_weather_view_invisibility">8</integer> +</resources> diff --git a/packages/SystemUI/res-keyguard/values/integers.xml b/packages/SystemUI/res-keyguard/values/integers.xml index c6e90c0fcdec..b08fde339d65 100644 --- a/packages/SystemUI/res-keyguard/values/integers.xml +++ b/packages/SystemUI/res-keyguard/values/integers.xml @@ -27,4 +27,7 @@ 0x50 = bottom, 0x01 = center_horizontal --> <integer name="keyguard_host_view_one_handed_gravity">0x51</integer> + + <!-- Invisibility to use for the date & weather view when it is disabled by a clock --> + <integer name="keyguard_date_weather_view_invisibility">4</integer> </resources> diff --git a/packages/SystemUI/res/layout/screen_share_dialog.xml b/packages/SystemUI/res/layout/screen_share_dialog.xml index 0d86e0a40b6d..ab522a388735 100644 --- a/packages/SystemUI/res/layout/screen_share_dialog.xml +++ b/packages/SystemUI/res/layout/screen_share_dialog.xml @@ -36,7 +36,7 @@ android:layout_width="@dimen/screenrecord_logo_size" android:layout_height="@dimen/screenrecord_logo_size" android:src="@drawable/ic_media_projection_permission" - android:tint="?androidprv:attr/colorAccentPrimary" + android:tint="?androidprv:attr/colorAccentPrimaryVariant" android:importantForAccessibility="no"/> <TextView android:id="@+id/screen_share_dialog_title" diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java index d8bf570954df..99e25745dda7 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java @@ -86,6 +86,7 @@ public class KeyguardClockSwitchController extends ViewController<KeyguardClockS private int mKeyguardSmallClockTopMargin = 0; private int mKeyguardLargeClockTopMargin = 0; + private int mKeyguardDateWeatherViewInvisibility = View.INVISIBLE; private final ClockRegistry.ClockChangeListener mClockChangedListener; private ViewGroup mStatusArea; @@ -201,6 +202,8 @@ public class KeyguardClockSwitchController extends ViewController<KeyguardClockS mView.getResources().getDimensionPixelSize(R.dimen.keyguard_clock_top_margin); mKeyguardLargeClockTopMargin = mView.getResources().getDimensionPixelSize(R.dimen.keyguard_large_clock_top_margin); + mKeyguardDateWeatherViewInvisibility = + mView.getResources().getInteger(R.integer.keyguard_date_weather_view_invisibility); if (mOnlyClock) { View ksv = mView.findViewById(R.id.keyguard_slice_view); @@ -335,7 +338,10 @@ public class KeyguardClockSwitchController extends ViewController<KeyguardClockS mView.getResources().getDimensionPixelSize(R.dimen.keyguard_clock_top_margin); mKeyguardLargeClockTopMargin = mView.getResources().getDimensionPixelSize(R.dimen.keyguard_large_clock_top_margin); + mKeyguardDateWeatherViewInvisibility = + mView.getResources().getInteger(R.integer.keyguard_date_weather_view_invisibility); mView.updateClockTargetRegions(); + setDateWeatherVisibility(); } @@ -497,8 +503,9 @@ public class KeyguardClockSwitchController extends ViewController<KeyguardClockS private void setDateWeatherVisibility() { if (mDateWeatherView != null) { mUiExecutor.execute(() -> { - mDateWeatherView.setVisibility( - clockHasCustomWeatherDataDisplay() ? View.INVISIBLE : View.VISIBLE); + mDateWeatherView.setVisibility(clockHasCustomWeatherDataDisplay() + ? mKeyguardDateWeatherViewInvisibility + : View.VISIBLE); }); } } diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardPatternView.java b/packages/SystemUI/src/com/android/keyguard/KeyguardPatternView.java index afc25909ca91..99b5d52f8322 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardPatternView.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardPatternView.java @@ -158,15 +158,18 @@ public class KeyguardPatternView extends KeyguardInputView public void startAppearAnimation() { enableClipping(false); - setAlpha(1f); + setAlpha(0f); setTranslationY(mAppearAnimationUtils.getStartTranslation()); AppearAnimationUtils.startTranslationYAnimation(this, 0 /* delay */, 500 /* duration */, 0, mAppearAnimationUtils.getInterpolator(), getAnimationListener(InteractionJankMonitor.CUJ_LOCKSCREEN_PATTERN_APPEAR)); - mAppearAnimationUtils.startAnimation2d( - mLockPatternView.getCellStates(), - () -> enableClipping(true), - this); + mLockPatternView.post(() -> { + setAlpha(1f); + mAppearAnimationUtils.startAnimation2d( + mLockPatternView.getCellStates(), + () -> enableClipping(true), + KeyguardPatternView.this); + }); if (!TextUtils.isEmpty(mSecurityMessageDisplay.getText())) { mAppearAnimationUtils.createAnimation(mSecurityMessageDisplay, 0, AppearAnimationUtils.DEFAULT_APPEAR_DURATION, diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardStatusViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardStatusViewController.java index 1db0ab65e44f..835cc13bebb1 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardStatusViewController.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardStatusViewController.java @@ -46,7 +46,6 @@ import com.android.keyguard.KeyguardClockSwitch.ClockSize; import com.android.keyguard.logging.KeyguardLogger; import com.android.systemui.R; import com.android.systemui.flags.FeatureFlags; -import com.android.systemui.flags.Flags; import com.android.systemui.plugins.ClockController; import com.android.systemui.statusbar.notification.AnimatableProperty; import com.android.systemui.statusbar.notification.PropertyAnimator; @@ -363,7 +362,7 @@ public class KeyguardStatusViewController extends ViewController<KeyguardStatusV boolean customClockAnimation = clock != null && clock.getLargeClock().getConfig().getHasCustomPositionUpdatedAnimation(); - if (mFeatureFlags.isEnabled(Flags.STEP_CLOCK_ANIMATION) && customClockAnimation) { + if (customClockAnimation) { // Find the clock, so we can exclude it from this transition. FrameLayout clockContainerView = mView.findViewById(R.id.lockscreen_clock_view_large); @@ -400,8 +399,10 @@ public class KeyguardStatusViewController extends ViewController<KeyguardStatusV @VisibleForTesting static class SplitShadeTransitionAdapter extends Transition { private static final String PROP_BOUNDS_LEFT = "splitShadeTransitionAdapter:boundsLeft"; + private static final String PROP_BOUNDS_RIGHT = "splitShadeTransitionAdapter:boundsRight"; private static final String PROP_X_IN_WINDOW = "splitShadeTransitionAdapter:xInWindow"; - private static final String[] TRANSITION_PROPERTIES = { PROP_BOUNDS_LEFT, PROP_X_IN_WINDOW}; + private static final String[] TRANSITION_PROPERTIES = { + PROP_BOUNDS_LEFT, PROP_BOUNDS_RIGHT, PROP_X_IN_WINDOW}; private final KeyguardClockSwitchController mController; @@ -412,6 +413,7 @@ public class KeyguardStatusViewController extends ViewController<KeyguardStatusV private void captureValues(TransitionValues transitionValues) { transitionValues.values.put(PROP_BOUNDS_LEFT, transitionValues.view.getLeft()); + transitionValues.values.put(PROP_BOUNDS_RIGHT, transitionValues.view.getRight()); int[] locationInWindowTmp = new int[2]; transitionValues.view.getLocationInWindow(locationInWindowTmp); transitionValues.values.put(PROP_X_IN_WINDOW, locationInWindowTmp[0]); diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java index e8046dc07c02..1721891550a1 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java @@ -246,6 +246,7 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab private static final int MSG_TIME_FORMAT_UPDATE = 344; private static final int MSG_REQUIRE_NFC_UNLOCK = 345; private static final int MSG_KEYGUARD_DISMISS_ANIMATION_FINISHED = 346; + private static final int MSG_SERVICE_PROVIDERS_UPDATED = 347; /** Biometric authentication state: Not listening. */ private static final int BIOMETRIC_STATE_STOPPED = 0; @@ -1826,6 +1827,8 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab } else if (TelephonyManager.ACTION_PHONE_STATE_CHANGED.equals(action)) { String state = intent.getStringExtra(TelephonyManager.EXTRA_STATE); mHandler.sendMessage(mHandler.obtainMessage(MSG_PHONE_STATE_CHANGED, state)); + } else if (TelephonyManager.ACTION_SERVICE_PROVIDERS_UPDATED.equals(action)) { + mHandler.obtainMessage(MSG_SERVICE_PROVIDERS_UPDATED, intent).sendToTarget(); } else if (Intent.ACTION_AIRPLANE_MODE_CHANGED.equals(action)) { mHandler.sendEmptyMessage(MSG_AIRPLANE_MODE_CHANGED); } else if (Intent.ACTION_SERVICE_STATE.equals(action)) { @@ -2402,6 +2405,9 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab case MSG_SERVICE_STATE_CHANGE: handleServiceStateChange(msg.arg1, (ServiceState) msg.obj); break; + case MSG_SERVICE_PROVIDERS_UPDATED: + handleServiceProvidersUpdated((Intent) msg.obj); + break; case MSG_SCREEN_TURNED_OFF: Trace.beginSection("KeyguardUpdateMonitor#handler MSG_SCREEN_TURNED_OFF"); handleScreenTurnedOff(); @@ -2472,6 +2478,7 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab filter.addAction(Intent.ACTION_SERVICE_STATE); filter.addAction(TelephonyManager.ACTION_DEFAULT_DATA_SUBSCRIPTION_CHANGED); filter.addAction(TelephonyManager.ACTION_PHONE_STATE_CHANGED); + filter.addAction(TelephonyManager.ACTION_SERVICE_PROVIDERS_UPDATED); filter.addAction(DevicePolicyManager.ACTION_DEVICE_POLICY_MANAGER_STATE_CHANGED); filter.addAction(UsbManager.ACTION_USB_PORT_COMPLIANCE_CHANGED); mBroadcastDispatcher.registerReceiverWithHandler(mBroadcastReceiver, filter, mHandler); @@ -3727,6 +3734,14 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab } /** + * Handle {@link #MSG_SERVICE_PROVIDERS_UPDATED} + */ + private void handleServiceProvidersUpdated(Intent intent) { + mLogger.logServiceProvidersUpdated(intent); + callbacksRefreshCarrierInfo(); + } + + /** * Whether the keyguard is showing and not occluded. */ public boolean isKeyguardVisible() { diff --git a/packages/SystemUI/src/com/android/keyguard/dagger/ClockRegistryModule.java b/packages/SystemUI/src/com/android/keyguard/dagger/ClockRegistryModule.java index 2d0bf9cc2aba..d2b10d687f04 100644 --- a/packages/SystemUI/src/com/android/keyguard/dagger/ClockRegistryModule.java +++ b/packages/SystemUI/src/com/android/keyguard/dagger/ClockRegistryModule.java @@ -63,7 +63,11 @@ public abstract class ClockRegistryModule { bgDispatcher, featureFlags.isEnabled(Flags.LOCKSCREEN_CUSTOM_CLOCKS), /* handleAllUsers= */ true, - new DefaultClockProvider(context, layoutInflater, resources), + new DefaultClockProvider( + context, + layoutInflater, + resources, + featureFlags.isEnabled(Flags.STEP_CLOCK_ANIMATION)), context.getString(R.string.lockscreen_clock_id_fallback), logBuffer, /* keepAllLoaded = */ false, diff --git a/packages/SystemUI/src/com/android/keyguard/logging/KeyguardUpdateMonitorLogger.kt b/packages/SystemUI/src/com/android/keyguard/logging/KeyguardUpdateMonitorLogger.kt index 17cc23632d94..4923ab0fab18 100644 --- a/packages/SystemUI/src/com/android/keyguard/logging/KeyguardUpdateMonitorLogger.kt +++ b/packages/SystemUI/src/com/android/keyguard/logging/KeyguardUpdateMonitorLogger.kt @@ -16,11 +16,15 @@ package com.android.keyguard.logging +import android.content.Intent import android.hardware.biometrics.BiometricConstants.LockoutMode import android.os.PowerManager import android.os.PowerManager.WakeReason import android.telephony.ServiceState import android.telephony.SubscriptionInfo +import android.telephony.SubscriptionManager.EXTRA_SUBSCRIPTION_INDEX +import android.telephony.SubscriptionManager.INVALID_SUBSCRIPTION_ID +import android.telephony.TelephonyManager import com.android.keyguard.ActiveUnlockConfig import com.android.keyguard.FaceAuthUiEvent import com.android.keyguard.KeyguardListenModel @@ -363,6 +367,21 @@ constructor(@KeyguardUpdateMonitorLog private val logBuffer: LogBuffer) { ) } + fun logServiceProvidersUpdated(intent: Intent) { + logBuffer.log( + TAG, + VERBOSE, + { + int1 = intent.getIntExtra(EXTRA_SUBSCRIPTION_INDEX, INVALID_SUBSCRIPTION_ID) + str1 = intent.getStringExtra(TelephonyManager.EXTRA_SPN) + str2 = intent.getStringExtra(TelephonyManager.EXTRA_PLMN) + }, + { + "action SERVICE_PROVIDERS_UPDATED subId=$int1 spn=$str1 plmn=$str2" + } + ) + } + fun logSimState(subId: Int, slotId: Int, state: Int) { logBuffer.log( TAG, diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/SideFpsController.kt b/packages/SystemUI/src/com/android/systemui/biometrics/SideFpsController.kt index 07825377440b..630cfffe5969 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/SideFpsController.kt +++ b/packages/SystemUI/src/com/android/systemui/biometrics/SideFpsController.kt @@ -152,7 +152,7 @@ constructor( WindowManager.LayoutParams( WindowManager.LayoutParams.WRAP_CONTENT, WindowManager.LayoutParams.WRAP_CONTENT, - WindowManager.LayoutParams.TYPE_KEYGUARD_DIALOG, + WindowManager.LayoutParams.TYPE_NAVIGATION_BAR_PANEL, Utils.FINGERPRINT_OVERLAY_LAYOUT_PARAM_FLAGS, PixelFormat.TRANSLUCENT ) diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/BouncerInteractor.kt b/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/BouncerInteractor.kt index e462e2f5b7d8..1d2fce7d8b05 100644 --- a/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/BouncerInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/BouncerInteractor.kt @@ -148,12 +148,17 @@ constructor( * * If the input is correct, the device will be unlocked and the lock screen and bouncer will be * dismissed and hidden. + * + * @param input The input from the user to try to authenticate with. This can be a list of + * different things, based on the current authentication method. + * @return `true` if the authentication succeeded and the device is now unlocked; `false` + * otherwise. */ fun authenticate( input: List<Any>, - ) { + ): Boolean { if (repository.throttling.value != null) { - return + return false } val isAuthenticated = authenticationInteractor.authenticate(input) @@ -186,6 +191,8 @@ constructor( } else -> repository.setMessage(errorMessage(authenticationMethod.value)) } + + return isAuthenticated } private fun promptMessage(authMethod: AuthenticationMethodModel): String { diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/AuthMethodBouncerViewModel.kt b/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/AuthMethodBouncerViewModel.kt index 774a5593530c..d95b70c85fe0 100644 --- a/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/AuthMethodBouncerViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/AuthMethodBouncerViewModel.kt @@ -16,14 +16,37 @@ package com.android.systemui.bouncer.ui.viewmodel +import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.flow.asStateFlow -sealed interface AuthMethodBouncerViewModel { +sealed class AuthMethodBouncerViewModel( /** * Whether user input is enabled. * * If `false`, user input should be completely ignored in the UI as the user is "locked out" of * being able to attempt to unlock the device. */ - val isInputEnabled: StateFlow<Boolean> + val isInputEnabled: StateFlow<Boolean>, +) { + + private val _animateFailure = MutableStateFlow(false) + /** + * Whether a failure animation should be shown. Once consumed, the UI must call + * [onFailureAnimationShown] to consume this state. + */ + val animateFailure: StateFlow<Boolean> = _animateFailure.asStateFlow() + + /** + * Notifies that the failure animation has been shown. This should be called to consume a `true` + * value in [animateFailure]. + */ + fun onFailureAnimationShown() { + _animateFailure.value = false + } + + /** Ask the UI to show the failure animation. */ + protected fun showFailureAnimation() { + _animateFailure.value = true + } } diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/BouncerViewModel.kt b/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/BouncerViewModel.kt index 02991bd47c6e..984d9ab1c1be 100644 --- a/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/BouncerViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/BouncerViewModel.kt @@ -20,6 +20,7 @@ import android.content.Context import com.android.systemui.R import com.android.systemui.authentication.shared.model.AuthenticationMethodModel import com.android.systemui.bouncer.domain.interactor.BouncerInteractor +import com.android.systemui.bouncer.shared.model.AuthenticationThrottledModel import com.android.systemui.dagger.qualifiers.Application import dagger.assisted.Assisted import dagger.assisted.AssistedFactory @@ -29,6 +30,7 @@ import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.asStateFlow +import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.distinctUntilChanged import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.stateIn @@ -45,21 +47,6 @@ constructor( ) { private val interactor: BouncerInteractor = interactorFactory.create(containerName) - /** - * Whether updates to the message should be cross-animated from one message to another. - * - * If `false`, no animation should be applied, the message text should just be replaced - * instantly. - */ - val isMessageUpdateAnimationsEnabled: StateFlow<Boolean> = - interactor.throttling - .map { it == null } - .stateIn( - scope = applicationScope, - started = SharingStarted.WhileSubscribed(), - initialValue = interactor.throttling.value == null, - ) - private val isInputEnabled: StateFlow<Boolean> = interactor.throttling .map { it == null } @@ -104,13 +91,21 @@ constructor( ) /** The user-facing message to show in the bouncer. */ - val message: StateFlow<String> = - interactor.message - .map { it ?: "" } + val message: StateFlow<MessageViewModel> = + combine( + interactor.message, + interactor.throttling, + ) { message, throttling -> + toMessageViewModel(message, throttling) + } .stateIn( scope = applicationScope, started = SharingStarted.WhileSubscribed(), - initialValue = interactor.message.value ?: "", + initialValue = + toMessageViewModel( + message = interactor.message.value, + throttling = interactor.throttling.value, + ), ) private val _throttlingDialogMessage = MutableStateFlow<String?>(null) @@ -177,6 +172,28 @@ constructor( } } + private fun toMessageViewModel( + message: String?, + throttling: AuthenticationThrottledModel?, + ): MessageViewModel { + return MessageViewModel( + text = message ?: "", + isUpdateAnimated = throttling == null, + ) + } + + data class MessageViewModel( + val text: String, + + /** + * Whether updates to the message should be cross-animated from one message to another. + * + * If `false`, no animation should be applied, the message text should just be replaced + * instantly. + */ + val isUpdateAnimated: Boolean, + ) + @AssistedFactory interface Factory { fun create( diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/PasswordBouncerViewModel.kt b/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/PasswordBouncerViewModel.kt index c38fcaa3b657..55929b566cf1 100644 --- a/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/PasswordBouncerViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/PasswordBouncerViewModel.kt @@ -24,8 +24,11 @@ import kotlinx.coroutines.flow.asStateFlow /** Holds UI state and handles user input for the password bouncer UI. */ class PasswordBouncerViewModel( private val interactor: BouncerInteractor, - override val isInputEnabled: StateFlow<Boolean>, -) : AuthMethodBouncerViewModel { + isInputEnabled: StateFlow<Boolean>, +) : + AuthMethodBouncerViewModel( + isInputEnabled = isInputEnabled, + ) { private val _password = MutableStateFlow("") /** The password entered so far. */ @@ -47,7 +50,10 @@ class PasswordBouncerViewModel( /** Notifies that the user has pressed the key for attempting to authenticate the password. */ fun onAuthenticateKeyPressed() { - interactor.authenticate(password.value.toCharArray().toList()) + if (!interactor.authenticate(password.value.toCharArray().toList())) { + showFailureAnimation() + } + _password.value = "" } } diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/PatternBouncerViewModel.kt b/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/PatternBouncerViewModel.kt index 1b0b38ea6e9c..d9ef75db6103 100644 --- a/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/PatternBouncerViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/PatternBouncerViewModel.kt @@ -37,8 +37,11 @@ class PatternBouncerViewModel( private val applicationContext: Context, applicationScope: CoroutineScope, private val interactor: BouncerInteractor, - override val isInputEnabled: StateFlow<Boolean>, -) : AuthMethodBouncerViewModel { + isInputEnabled: StateFlow<Boolean>, +) : + AuthMethodBouncerViewModel( + isInputEnabled = isInputEnabled, + ) { /** The number of columns in the dot grid. */ val columnCount = 3 @@ -150,7 +153,11 @@ class PatternBouncerViewModel( /** Notifies that the user has ended the drag gesture across the dot grid. */ fun onDragEnd() { - interactor.authenticate(_selectedDots.value.map { it.toCoordinate() }) + val isSuccessfullyAuthenticated = + interactor.authenticate(_selectedDots.value.map { it.toCoordinate() }) + if (!isSuccessfullyAuthenticated) { + showFailureAnimation() + } _dots.value = defaultDots() _currentDot.value = null diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/PinBouncerViewModel.kt b/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/PinBouncerViewModel.kt index 2a733d93b857..5c0fd92e7299 100644 --- a/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/PinBouncerViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/PinBouncerViewModel.kt @@ -33,8 +33,11 @@ import kotlinx.coroutines.launch class PinBouncerViewModel( private val applicationScope: CoroutineScope, private val interactor: BouncerInteractor, - override val isInputEnabled: StateFlow<Boolean>, -) : AuthMethodBouncerViewModel { + isInputEnabled: StateFlow<Boolean>, +) : + AuthMethodBouncerViewModel( + isInputEnabled = isInputEnabled, + ) { private val entered = MutableStateFlow<List<Int>>(emptyList()) /** @@ -92,7 +95,10 @@ class PinBouncerViewModel( /** Notifies that the user clicked the "enter" button. */ fun onAuthenticateButtonClicked() { - interactor.authenticate(entered.value) + if (!interactor.authenticate(entered.value)) { + showFailureAnimation() + } + entered.value = emptyList() } diff --git a/packages/SystemUI/src/com/android/systemui/controls/management/ControlsListingControllerImpl.kt b/packages/SystemUI/src/com/android/systemui/controls/management/ControlsListingControllerImpl.kt index 8ba060e02c3d..1eba66765ce3 100644 --- a/packages/SystemUI/src/com/android/systemui/controls/management/ControlsListingControllerImpl.kt +++ b/packages/SystemUI/src/com/android/systemui/controls/management/ControlsListingControllerImpl.kt @@ -35,6 +35,7 @@ import com.android.systemui.dump.DumpManager import com.android.systemui.flags.FeatureFlags import com.android.systemui.flags.Flags import com.android.systemui.settings.UserTracker +import com.android.systemui.util.ActivityTaskManagerProxy import com.android.systemui.util.asIndenting import com.android.systemui.util.indentIfPossible import java.io.PrintWriter @@ -67,6 +68,7 @@ class ControlsListingControllerImpl @VisibleForTesting constructor( @Background private val backgroundExecutor: Executor, private val serviceListingBuilder: (Context) -> ServiceListing, private val userTracker: UserTracker, + private val activityTaskManagerProxy: ActivityTaskManagerProxy, dumpManager: DumpManager, private val featureFlags: FeatureFlags ) : ControlsListingController, Dumpable { @@ -76,9 +78,18 @@ class ControlsListingControllerImpl @VisibleForTesting constructor( context: Context, @Background executor: Executor, userTracker: UserTracker, + activityTaskManagerProxy: ActivityTaskManagerProxy, dumpManager: DumpManager, featureFlags: FeatureFlags - ) : this(context, executor, ::createServiceListing, userTracker, dumpManager, featureFlags) + ) : this( + context, + executor, + ::createServiceListing, + userTracker, + activityTaskManagerProxy, + dumpManager, + featureFlags + ) private var serviceListing = serviceListingBuilder(context) // All operations in background thread @@ -113,7 +124,8 @@ class ControlsListingControllerImpl @VisibleForTesting constructor( } private fun updateServices(newServices: List<ControlsServiceInfo>) { - if (featureFlags.isEnabled(Flags.USE_APP_PANELS)) { + if (featureFlags.isEnabled(Flags.USE_APP_PANELS) && + activityTaskManagerProxy.supportsMultiWindow(context)) { val allowAllApps = featureFlags.isEnabled(Flags.APP_PANELS_ALL_APPS_ALLOWED) newServices.forEach { it.resolvePanelActivity(allowAllApps) } diff --git a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt index a2b159cda284..d6c082928168 100644 --- a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt +++ b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt @@ -731,4 +731,9 @@ object Flags { // TODO(b/283084712): Tracking Bug @JvmField val IMPROVED_HUN_ANIMATIONS = unreleasedFlag(283084712, "improved_hun_animations") + + // TODO(b/283447257): Tracking bug + @JvmField + val BIGPICTURE_NOTIFICATION_LAZY_LOADING = + unreleasedFlag(283447257, "bigpicture_notification_lazy_loading") } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java index b8d3121518e9..a8d22c48e709 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java +++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java @@ -122,6 +122,10 @@ public class KeyguardService extends Service { } } + // Avoid wrapping non-task and non-wallpaper changes as they don't need to animate + // for keyguard unlock animation. + if (taskId < 0 && !wallpapers) continue; + final RemoteAnimationTarget target = TransitionUtil.newTarget(change, // wallpapers go into the "below" layer space info.getChanges().size() - i, @@ -141,8 +145,8 @@ public class KeyguardService extends Service { return apps.length == 0 ? TRANSIT_OLD_KEYGUARD_GOING_AWAY_ON_WALLPAPER : TRANSIT_OLD_KEYGUARD_GOING_AWAY; } else if (type == TRANSIT_KEYGUARD_OCCLUDE) { - boolean isOccludeByDream = apps.length > 0 && apps[0].taskInfo.topActivityType - == WindowConfiguration.ACTIVITY_TYPE_DREAM; + boolean isOccludeByDream = apps.length > 0 && apps[0].taskInfo != null + && apps[0].taskInfo.topActivityType == WindowConfiguration.ACTIVITY_TYPE_DREAM; if (isOccludeByDream) return TRANSIT_OLD_KEYGUARD_OCCLUDE_BY_DREAM; return TRANSIT_OLD_KEYGUARD_OCCLUDE; } else if (type == TRANSIT_KEYGUARD_UNOCCLUDE) { @@ -303,46 +307,6 @@ public class KeyguardService extends Service { } } - final IRemoteTransition mOccludeAnimation = new IRemoteTransition.Stub() { - @Override - public void startAnimation(IBinder transition, TransitionInfo info, - SurfaceControl.Transaction t, IRemoteTransitionFinishedCallback finishCallback) - throws RemoteException { - t.apply(); - mBinder.setOccluded(true /* isOccluded */, true /* animate */); - finishCallback.onTransitionFinished(null /* wct */, null /* wctCB */); - info.releaseAllSurfaces(); - } - - @Override - public void mergeAnimation(IBinder transition, TransitionInfo info, - SurfaceControl.Transaction t, IBinder mergeTarget, - IRemoteTransitionFinishedCallback finishCallback) { - t.close(); - info.releaseAllSurfaces(); - } - }; - - final IRemoteTransition mUnoccludeAnimation = new IRemoteTransition.Stub() { - @Override - public void startAnimation(IBinder transition, TransitionInfo info, - SurfaceControl.Transaction t, IRemoteTransitionFinishedCallback finishCallback) - throws RemoteException { - t.apply(); - mBinder.setOccluded(false /* isOccluded */, true /* animate */); - finishCallback.onTransitionFinished(null /* wct */, null /* wctCB */); - info.releaseAllSurfaces(); - } - - @Override - public void mergeAnimation(IBinder transition, TransitionInfo info, - SurfaceControl.Transaction t, IBinder mergeTarget, - IRemoteTransitionFinishedCallback finishCallback) { - t.close(); - info.releaseAllSurfaces(); - } - }; - private final IKeyguardService.Stub mBinder = new IKeyguardService.Stub() { private static final String TRACK_NAME = "IKeyguardService"; diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardUnlockAnimationController.kt b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardUnlockAnimationController.kt index 122e25975837..29a7fe7d061a 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardUnlockAnimationController.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardUnlockAnimationController.kt @@ -637,7 +637,9 @@ class KeyguardUnlockAnimationController @Inject constructor( * Unlock to the launcher, using in-window animations, and the smartspace shared element * transition if possible. */ - private fun unlockToLauncherWithInWindowAnimations() { + + @VisibleForTesting + fun unlockToLauncherWithInWindowAnimations() { setSurfaceBehindAppearAmount(1f, wallpapers = false) try { @@ -662,10 +664,28 @@ class KeyguardUnlockAnimationController @Inject constructor( // Now that the Launcher surface (with its smartspace positioned identically to ours) is // visible, hide our smartspace. - lockscreenSmartspace?.visibility = View.INVISIBLE + if (lockscreenSmartspace?.visibility == View.VISIBLE) { + lockscreenSmartspace?.visibility = View.INVISIBLE + } + + // As soon as the shade has animated out of the way, start the canned unlock animation, + // which will finish keyguard exit when it completes. The in-window animations in the + // Launcher window will end on their own. + handler.postDelayed({ + if (keyguardViewMediator.get().isShowingAndNotOccluded && + !keyguardStateController.isKeyguardGoingAway) { + Log.e(TAG, "Finish keyguard exit animation delayed Runnable ran, but we are " + + "showing and not going away.") + return@postDelayed + } - // Start an animation for the wallpaper, which will finish keyguard exit when it completes. - fadeInWallpaper() + if (wallpaperTargets != null) { + fadeInWallpaper() + } else { + keyguardViewMediator.get().exitKeyguardAndFinishSurfaceBehindRemoteAnimation( + false /* cancelled */) + } + }, CANNED_UNLOCK_START_DELAY) } /** @@ -914,7 +934,9 @@ class KeyguardUnlockAnimationController @Inject constructor( willUnlockWithSmartspaceTransition = false // The lockscreen surface is gone, so it is now safe to re-show the smartspace. - lockscreenSmartspace?.visibility = View.VISIBLE + if (lockscreenSmartspace?.visibility == View.INVISIBLE) { + lockscreenSmartspace?.visibility = View.VISIBLE + } listeners.forEach { it.onUnlockAnimationFinished() } } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardQuickAffordanceLocalUserSelectionManager.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardQuickAffordanceLocalUserSelectionManager.kt index 356a8fb65883..4dad17907aca 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardQuickAffordanceLocalUserSelectionManager.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardQuickAffordanceLocalUserSelectionManager.kt @@ -47,7 +47,7 @@ import kotlinx.coroutines.flow.onStart class KeyguardQuickAffordanceLocalUserSelectionManager @Inject constructor( - @Application context: Context, + @Application private val context: Context, private val userFileManager: UserFileManager, private val userTracker: UserTracker, broadcastDispatcher: BroadcastDispatcher, @@ -126,6 +126,11 @@ constructor( } override fun getSelections(): Map<String, List<String>> { + // If the custom shortcuts feature is not enabled, ignore prior selections and use defaults + if (!context.resources.getBoolean(R.bool.custom_lockscreen_shortcuts_enabled)) { + return defaults + } + val slotKeys = sharedPrefs.all.keys.filter { it.startsWith(KEY_PREFIX_SLOT) } val result = slotKeys diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/QuickAccessWalletKeyguardQuickAffordanceConfig.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/QuickAccessWalletKeyguardQuickAffordanceConfig.kt index 4ba2eb913742..12270784adca 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/QuickAccessWalletKeyguardQuickAffordanceConfig.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/QuickAccessWalletKeyguardQuickAffordanceConfig.kt @@ -18,7 +18,6 @@ package com.android.systemui.keyguard.data.quickaffordance import android.content.Context -import android.content.Intent import android.graphics.drawable.Drawable import android.service.quickaccesswallet.GetWalletCardsError import android.service.quickaccesswallet.GetWalletCardsResponse @@ -33,7 +32,6 @@ import com.android.systemui.common.shared.model.ContentDescription import com.android.systemui.common.shared.model.Icon import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Application -import com.android.systemui.keyguard.data.quickaffordance.KeyguardQuickAffordanceConfig.Companion.componentName import com.android.systemui.plugins.ActivityStarter import com.android.systemui.wallet.controller.QuickAccessWalletController import javax.inject.Inject @@ -103,17 +101,6 @@ constructor( !walletController.walletClient.isWalletServiceAvailable -> KeyguardQuickAffordanceConfig.PickerScreenState.UnavailableOnDevice !walletController.isWalletEnabled || queryCards().isEmpty() -> { - val componentName = - walletController.walletClient.createWalletSettingsIntent().toComponentName() - val actionText = - if (componentName != null) { - context.getString( - R.string.keyguard_affordance_enablement_dialog_action_template, - pickerName, - ) - } else { - null - } KeyguardQuickAffordanceConfig.PickerScreenState.Disabled( instructions = listOf( @@ -124,8 +111,6 @@ constructor( R.string.keyguard_affordance_enablement_dialog_wallet_instruction_2 ), ), - actionText = actionText, - actionComponentName = componentName, ) } else -> KeyguardQuickAffordanceConfig.PickerScreenState.Default() @@ -182,14 +167,6 @@ constructor( } } - private fun Intent?.toComponentName(): String? { - if (this == null) { - return null - } - - return componentName(packageName = `package`, action = action) - } - companion object { private const val TAG = "QuickAccessWalletKeyguardQuickAffordanceConfig" } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardClockRepository.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardClockRepository.kt index 641e20b4a3fc..16ad29aeb734 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardClockRepository.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardClockRepository.kt @@ -21,12 +21,17 @@ import android.provider.Settings import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Background import com.android.systemui.keyguard.shared.model.SettingsClockSize +import com.android.systemui.plugins.ClockId +import com.android.systemui.shared.clocks.ClockRegistry import com.android.systemui.util.settings.SecureSettings import com.android.systemui.util.settings.SettingsProxyExt.observerFlow import javax.inject.Inject import kotlinx.coroutines.CoroutineDispatcher +import kotlinx.coroutines.channels.awaitClose import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.callbackFlow import kotlinx.coroutines.flow.map +import kotlinx.coroutines.flow.mapNotNull import kotlinx.coroutines.flow.onStart import kotlinx.coroutines.withContext @@ -35,6 +40,7 @@ class KeyguardClockRepository @Inject constructor( private val secureSettings: SecureSettings, + private val clockRegistry: ClockRegistry, @Background private val backgroundDispatcher: CoroutineDispatcher, ) { @@ -47,6 +53,24 @@ constructor( .onStart { emit(Unit) } // Forces an initial update. .map { getClockSize() } + val currentClockId: Flow<ClockId> = + callbackFlow { + fun send() { + trySend(clockRegistry.currentClockId) + } + + val listener = + object : ClockRegistry.ClockChangeListener { + override fun onCurrentClockChanged() { + send() + } + } + clockRegistry.registerClockChangeListener(listener) + send() + awaitClose { clockRegistry.unregisterClockChangeListener(listener) } + } + .mapNotNull { it } + private suspend fun getClockSize(): SettingsClockSize { return withContext(backgroundDispatcher) { if ( diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardClockInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardClockInteractor.kt index 98f445c4419a..dad5831d6d4f 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardClockInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardClockInteractor.kt @@ -20,6 +20,7 @@ package com.android.systemui.keyguard.domain.interactor import com.android.systemui.dagger.SysUISingleton import com.android.systemui.keyguard.data.repository.KeyguardClockRepository import com.android.systemui.keyguard.shared.model.SettingsClockSize +import com.android.systemui.plugins.ClockId import javax.inject.Inject import kotlinx.coroutines.flow.Flow @@ -31,4 +32,6 @@ constructor( repository: KeyguardClockRepository, ) { val selectedClockSize: Flow<SettingsClockSize> = repository.selectedClockSize + + val currentClockId: Flow<ClockId> = repository.currentClockId } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBottomAreaViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBottomAreaViewBinder.kt index c8d37a165a0e..a8d662c96284 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBottomAreaViewBinder.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBottomAreaViewBinder.kt @@ -406,19 +406,21 @@ object KeyguardBottomAreaViewBinder { view.isClickable = viewModel.isClickable if (viewModel.isClickable) { if (viewModel.useLongPress) { - view.setOnTouchListener( - KeyguardQuickAffordanceOnTouchListener( - view, - viewModel, - messageDisplayer, - vibratorHelper, - falsingManager, - ) + val onTouchListener = KeyguardQuickAffordanceOnTouchListener( + view, + viewModel, + messageDisplayer, + vibratorHelper, + falsingManager, ) + view.setOnTouchListener(onTouchListener) + view.onLongClickListener = + OnLongClickListener(falsingManager, viewModel, vibratorHelper, onTouchListener) } else { view.setOnClickListener(OnClickListener(viewModel, checkNotNull(falsingManager))) } } else { + view.onLongClickListener = null view.setOnClickListener(null) view.setOnTouchListener(null) } @@ -454,6 +456,42 @@ object KeyguardBottomAreaViewBinder { .start() } + private class OnLongClickListener( + private val falsingManager: FalsingManager?, + private val viewModel: KeyguardQuickAffordanceViewModel, + private val vibratorHelper: VibratorHelper?, + private val onTouchListener: KeyguardQuickAffordanceOnTouchListener + ) : View.OnLongClickListener { + override fun onLongClick(view: View): Boolean { + if (falsingManager?.isFalseLongTap(FalsingManager.MODERATE_PENALTY) == true) { + return true + } + + if (viewModel.configKey != null) { + viewModel.onClicked( + KeyguardQuickAffordanceViewModel.OnClickedParameters( + configKey = viewModel.configKey, + expandable = Expandable.fromView(view), + slotId = viewModel.slotId, + ) + ) + vibratorHelper?.vibrate( + if (viewModel.isActivated) { + KeyguardBottomAreaVibrations.Activated + } else { + KeyguardBottomAreaVibrations.Deactivated + } + ) + } + + onTouchListener.cancel() + return true + } + + override fun onLongClickUseDefaultHapticFeedback(view: View?) = false + + } + private class OnClickListener( private val viewModel: KeyguardQuickAffordanceViewModel, private val falsingManager: FalsingManager, diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardPreviewClockSmartspaceViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardPreviewClockViewBinder.kt index 57c32b3a56d9..1b5b329f3f7e 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardPreviewClockSmartspaceViewBinder.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardPreviewClockViewBinder.kt @@ -21,19 +21,17 @@ import android.view.View import androidx.core.view.isVisible import androidx.lifecycle.Lifecycle import androidx.lifecycle.repeatOnLifecycle -import com.android.systemui.keyguard.ui.viewmodel.KeyguardPreviewClockSmartspaceViewModel +import com.android.systemui.keyguard.ui.viewmodel.KeyguardPreviewClockViewModel import com.android.systemui.lifecycle.repeatWhenAttached -import kotlinx.coroutines.flow.collect -/** Binder for the small clock view, large clock view and smartspace. */ -object KeyguardPreviewClockSmartspaceViewBinder { +/** Binder for the small clock view, large clock view. */ +object KeyguardPreviewClockViewBinder { @JvmStatic fun bind( largeClockHostView: View, smallClockHostView: View, - smartspace: View?, - viewModel: KeyguardPreviewClockSmartspaceViewModel, + viewModel: KeyguardPreviewClockViewModel, ) { largeClockHostView.repeatWhenAttached { repeatOnLifecycle(Lifecycle.State.STARTED) { @@ -46,15 +44,5 @@ object KeyguardPreviewClockSmartspaceViewBinder { viewModel.isSmallClockVisible.collect { smallClockHostView.isVisible = it } } } - - smartspace?.repeatWhenAttached { - repeatOnLifecycle(Lifecycle.State.STARTED) { - viewModel.smartSpaceTopPadding.collect { smartspace.setTopPadding(it) } - } - } - } - - private fun View.setTopPadding(padding: Int) { - setPaddingRelative(paddingStart, padding, paddingEnd, paddingBottom) } } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardPreviewSmartspaceViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardPreviewSmartspaceViewBinder.kt new file mode 100644 index 000000000000..f5e4c6adcb91 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardPreviewSmartspaceViewBinder.kt @@ -0,0 +1,48 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package com.android.systemui.keyguard.ui.binder + +import android.view.View +import androidx.core.view.isInvisible +import androidx.lifecycle.Lifecycle +import androidx.lifecycle.repeatOnLifecycle +import com.android.systemui.keyguard.ui.viewmodel.KeyguardPreviewSmartspaceViewModel +import com.android.systemui.lifecycle.repeatWhenAttached +import kotlinx.coroutines.launch + +/** Binder for the small clock view, large clock view and smartspace. */ +object KeyguardPreviewSmartspaceViewBinder { + + @JvmStatic + fun bind( + smartspace: View, + viewModel: KeyguardPreviewSmartspaceViewModel, + ) { + smartspace.repeatWhenAttached { + repeatOnLifecycle(Lifecycle.State.STARTED) { + launch { viewModel.smartspaceTopPadding.collect { smartspace.setTopPadding(it) } } + + launch { viewModel.shouldHideSmartspace.collect { smartspace.isInvisible = it } } + } + } + } + + private fun View.setTopPadding(padding: Int) { + setPaddingRelative(paddingStart, padding, paddingEnd, paddingBottom) + } +} diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardQuickAffordanceOnTouchListener.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardQuickAffordanceOnTouchListener.kt index 5745d6ae077e..7685345805f4 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardQuickAffordanceOnTouchListener.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardQuickAffordanceOnTouchListener.kt @@ -46,7 +46,7 @@ class KeyguardQuickAffordanceOnTouchListener( @SuppressLint("ClickableViewAccessibility") override fun onTouch(v: View, event: MotionEvent): Boolean { return when (event.actionMasked) { - MotionEvent.ACTION_DOWN -> + MotionEvent.ACTION_DOWN -> { if (viewModel.configKey != null) { downDisplayCoords.set(event.rawX, event.rawY) if (isUsingAccurateTool(event)) { @@ -62,21 +62,10 @@ class KeyguardQuickAffordanceOnTouchListener( .scaleX(PRESSED_SCALE) .scaleY(PRESSED_SCALE) .setDuration(longPressDurationMs) - .withEndAction { - if ( - falsingManager?.isFalseLongTap( - FalsingManager.MODERATE_PENALTY - ) == false - ) { - dispatchClick(viewModel.configKey) - } - cancel() - } } - true - } else { - false } + false + } MotionEvent.ACTION_MOVE -> { if (!isUsingAccurateTool(event)) { // Moving too far while performing a long-press gesture cancels that @@ -91,7 +80,7 @@ class KeyguardQuickAffordanceOnTouchListener( cancel() } } - true + false } MotionEvent.ACTION_UP -> { if (isUsingAccurateTool(event)) { @@ -146,7 +135,7 @@ class KeyguardQuickAffordanceOnTouchListener( } ) } - true + false } MotionEvent.ACTION_CANCEL -> { cancel() @@ -179,7 +168,7 @@ class KeyguardQuickAffordanceOnTouchListener( view.setOnClickListener(null) } - private fun cancel(onAnimationEnd: Runnable? = null) { + fun cancel(onAnimationEnd: Runnable? = null) { longPressAnimator?.cancel() longPressAnimator = null view.animate().scaleX(1f).scaleY(1f).withEndAction(onAnimationEnd) diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/preview/KeyguardPreviewRenderer.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/preview/KeyguardPreviewRenderer.kt index 4308d843c27a..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 @@ -42,9 +42,11 @@ import com.android.systemui.biometrics.domain.interactor.UdfpsOverlayInteractor import com.android.systemui.broadcast.BroadcastDispatcher import com.android.systemui.dagger.qualifiers.Application import com.android.systemui.dagger.qualifiers.Main -import com.android.systemui.keyguard.ui.binder.KeyguardPreviewClockSmartspaceViewBinder +import com.android.systemui.keyguard.ui.binder.KeyguardPreviewClockViewBinder +import com.android.systemui.keyguard.ui.binder.KeyguardPreviewSmartspaceViewBinder import com.android.systemui.keyguard.ui.viewmodel.KeyguardBottomAreaViewModel -import com.android.systemui.keyguard.ui.viewmodel.KeyguardPreviewClockSmartspaceViewModel +import com.android.systemui.keyguard.ui.viewmodel.KeyguardPreviewClockViewModel +import com.android.systemui.keyguard.ui.viewmodel.KeyguardPreviewSmartspaceViewModel import com.android.systemui.plugins.ClockController import com.android.systemui.shared.clocks.ClockRegistry import com.android.systemui.shared.clocks.DefaultClockController @@ -65,7 +67,8 @@ constructor( @Application private val context: Context, @Main private val mainDispatcher: CoroutineDispatcher, @Main private val mainHandler: Handler, - private val clockSmartspaceViewModel: KeyguardPreviewClockSmartspaceViewModel, + private val clockViewModel: KeyguardPreviewClockViewModel, + private val smartspaceViewModel: KeyguardPreviewSmartspaceViewModel, private val bottomAreaViewModel: KeyguardBottomAreaViewModel, displayManager: DisplayManager, private val windowManager: WindowManager, @@ -129,16 +132,18 @@ constructor( setUpBottomArea(rootView) setUpSmartspace(rootView) + smartSpaceView?.let { + KeyguardPreviewSmartspaceViewBinder.bind(it, smartspaceViewModel) + } setUpUdfps(rootView) if (!shouldHideClock) { setUpClock(rootView) - KeyguardPreviewClockSmartspaceViewBinder.bind( + KeyguardPreviewClockViewBinder.bind( largeClockHostView, smallClockHostView, - smartSpaceView, - clockSmartspaceViewModel, + clockViewModel, ) } @@ -219,8 +224,8 @@ constructor( smartSpaceView = lockscreenSmartspaceController.buildAndConnectDateView(parentView) val topPadding: Int = - KeyguardPreviewClockSmartspaceViewModel.getLargeClockSmartspaceTopPadding( - context.resources + KeyguardPreviewSmartspaceViewModel.getLargeClockSmartspaceTopPadding( + context.resources, ) val startPadding: Int = @@ -235,7 +240,7 @@ constructor( smartSpaceView?.let { it.setPaddingRelative(startPadding, topPadding, endPadding, 0) it.isClickable = false - + it.isInvisible = true parentView.addView( it, FrameLayout.LayoutParams( @@ -372,7 +377,7 @@ constructor( resources.getDimensionPixelSize(R.dimen.small_clock_height) ) layoutParams.topMargin = - KeyguardPreviewClockSmartspaceViewModel.getStatusBarHeight(resources) + + KeyguardPreviewSmartspaceViewModel.getStatusBarHeight(resources) + resources.getDimensionPixelSize(R.dimen.small_clock_padding_top) hostView.layoutParams = layoutParams @@ -394,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/KeyguardPreviewClockViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardPreviewClockViewModel.kt new file mode 100644 index 000000000000..53013026670c --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardPreviewClockViewModel.kt @@ -0,0 +1,40 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License + */ + +package com.android.systemui.keyguard.ui.viewmodel + +import android.content.Context +import com.android.systemui.dagger.qualifiers.Application +import com.android.systemui.keyguard.domain.interactor.KeyguardClockInteractor +import com.android.systemui.keyguard.shared.model.SettingsClockSize +import javax.inject.Inject +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.map + +/** View model for the small clock view, large clock view. */ +class KeyguardPreviewClockViewModel +@Inject +constructor( + @Application private val context: Context, + interactor: KeyguardClockInteractor, +) { + + val isLargeClockVisible: Flow<Boolean> = + interactor.selectedClockSize.map { it == SettingsClockSize.DYNAMIC } + + val isSmallClockVisible: Flow<Boolean> = + interactor.selectedClockSize.map { it == SettingsClockSize.SMALL } +} diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardPreviewClockSmartspaceViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardPreviewSmartspaceViewModel.kt index 00c603b04ccf..bf51976e27f0 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardPreviewClockSmartspaceViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardPreviewSmartspaceViewModel.kt @@ -24,23 +24,18 @@ import com.android.systemui.keyguard.domain.interactor.KeyguardClockInteractor import com.android.systemui.keyguard.shared.model.SettingsClockSize import javax.inject.Inject import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.map -/** View model for the small clock view, large clock view and smartspace. */ -class KeyguardPreviewClockSmartspaceViewModel +/** View model for the smartspace. */ +class KeyguardPreviewSmartspaceViewModel @Inject constructor( @Application private val context: Context, interactor: KeyguardClockInteractor, ) { - val isLargeClockVisible: Flow<Boolean> = - interactor.selectedClockSize.map { it == SettingsClockSize.DYNAMIC } - - val isSmallClockVisible: Flow<Boolean> = - interactor.selectedClockSize.map { it == SettingsClockSize.SMALL } - - val smartSpaceTopPadding: Flow<Int> = + val smartspaceTopPadding: Flow<Int> = interactor.selectedClockSize.map { when (it) { SettingsClockSize.DYNAMIC -> getLargeClockSmartspaceTopPadding(context.resources) @@ -48,6 +43,22 @@ constructor( } } + val shouldHideSmartspace: Flow<Boolean> = + combine( + interactor.selectedClockSize, + interactor.currentClockId, + ::Pair, + ) + .map { (size, currentClockId) -> + when (size) { + // TODO (b/284122375) This is temporary. We should use clockController + // .largeClock.config.hasCustomWeatherDataDisplay instead, but + // ClockRegistry.createCurrentClock is not reliable. + SettingsClockSize.DYNAMIC -> currentClockId == "DIGITAL_CLOCK_WEATHER" + SettingsClockSize.SMALL -> false + } + } + companion object { fun getLargeClockSmartspaceTopPadding(resources: Resources): Int { return with(resources) { diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaProjectionPermissionActivity.java b/packages/SystemUI/src/com/android/systemui/media/MediaProjectionPermissionActivity.java index c9c2ea27e5f4..f6a2f3704283 100644 --- a/packages/SystemUI/src/com/android/systemui/media/MediaProjectionPermissionActivity.java +++ b/packages/SystemUI/src/com/android/systemui/media/MediaProjectionPermissionActivity.java @@ -29,6 +29,7 @@ import android.annotation.Nullable; import android.app.Activity; import android.app.ActivityManager; import android.app.AlertDialog; +import android.content.Context; import android.content.DialogInterface; import android.content.Intent; import android.content.pm.ApplicationInfo; @@ -203,14 +204,17 @@ public class MediaProjectionPermissionActivity extends Activity dialogTitle = getString(R.string.media_projection_dialog_title, appName); } + // Using application context for the dialog, instead of the activity context, so we get + // the correct screen width when in split screen. + Context dialogContext = getApplicationContext(); if (isPartialScreenSharingEnabled()) { - mDialog = new MediaProjectionPermissionDialog(this, () -> { + mDialog = new MediaProjectionPermissionDialog(dialogContext, () -> { ScreenShareOption selectedOption = ((MediaProjectionPermissionDialog) mDialog).getSelectedScreenShareOption(); grantMediaProjectionPermission(selectedOption.getMode()); }, () -> finish(RECORD_CANCEL, /* projection= */ null), appName); } else { - AlertDialog.Builder dialogBuilder = new AlertDialog.Builder(this, + AlertDialog.Builder dialogBuilder = new AlertDialog.Builder(dialogContext, R.style.Theme_SystemUI_Dialog) .setTitle(dialogTitle) .setIcon(R.drawable.ic_media_projection_permission) @@ -263,7 +267,10 @@ public class MediaProjectionPermissionActivity extends Activity final UserHandle hostUserHandle = getHostUserHandle(); if (mScreenCaptureDevicePolicyResolver.get() .isScreenCaptureCompletelyDisabled(hostUserHandle)) { - AlertDialog dialog = new ScreenCaptureDisabledDialog(this); + // Using application context for the dialog, instead of the activity context, so we get + // the correct screen width when in split screen. + Context dialogContext = getApplicationContext(); + AlertDialog dialog = new ScreenCaptureDisabledDialog(dialogContext); setUpDialog(dialog); dialog.show(); return true; diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/pipeline/MediaDataManager.kt b/packages/SystemUI/src/com/android/systemui/media/controls/pipeline/MediaDataManager.kt index bce334610f28..6b993ce9e7bf 100644 --- a/packages/SystemUI/src/com/android/systemui/media/controls/pipeline/MediaDataManager.kt +++ b/packages/SystemUI/src/com/android/systemui/media/controls/pipeline/MediaDataManager.kt @@ -53,6 +53,7 @@ import android.text.TextUtils import android.util.Log import android.util.Pair as APair import androidx.media.utils.MediaConstants +import com.android.internal.annotations.Keep import com.android.internal.logging.InstanceId import com.android.keyguard.KeyguardUpdateMonitor import com.android.systemui.Dumpable @@ -219,7 +220,7 @@ class MediaDataManager( private val mediaEntries: LinkedHashMap<String, MediaData> = LinkedHashMap() // There should ONLY be at most one Smartspace media recommendation. var smartspaceMediaData: SmartspaceMediaData = EMPTY_SMARTSPACE_MEDIA_DATA - private var smartspaceSession: SmartspaceSession? = null + @Keep private var smartspaceSession: SmartspaceSession? = null private var allowMediaRecommendations = allowMediaRecommendations(context) private val artworkWidth = @@ -381,6 +382,8 @@ class MediaDataManager( fun destroy() { smartspaceMediaDataProvider.unregisterListener(this) + smartspaceSession?.close() + smartspaceSession = null context.unregisterReceiver(appChangeReceiver) } diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/BackPanelController.kt b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/BackPanelController.kt index 1fd11bd61700..77e2847cbe76 100644 --- a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/BackPanelController.kt +++ b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/BackPanelController.kt @@ -607,7 +607,7 @@ class BackPanelController internal constructor( ) } - private var previousPreThresholdWidthInterpolator = params.entryWidthTowardsEdgeInterpolator + private var previousPreThresholdWidthInterpolator = params.entryWidthInterpolator private fun preThresholdWidthStretchAmount(progress: Float): Float { val interpolator = run { val isPastSlop = totalTouchDeltaInactive > viewConfiguration.scaledTouchSlop diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgePanelParams.kt b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgePanelParams.kt index 6d881d527ce4..9ddb78ae19ee 100644 --- a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgePanelParams.kt +++ b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgePanelParams.kt @@ -147,8 +147,21 @@ data class EdgePanelParams(private var resources: Resources) { val flungCommittedWidthSpring = createSpring(10000f, 1f) val flungCommittedHeightSpring = createSpring(10000f, 1f) - val entryIndicatorAlphaThreshold = .23f - val entryIndicatorAlphaFactor = 1.05f + val commonArrowDimensAlphaThreshold = .165f + val commonArrowDimensAlphaFactor = 1.05f + val commonArrowDimensAlphaSpring = Step( + threshold = commonArrowDimensAlphaThreshold, + factor = commonArrowDimensAlphaFactor, + postThreshold = createSpring(180f, 0.9f), + preThreshold = createSpring(2000f, 0.6f) + ) + val commonArrowDimensAlphaSpringInterpolator = Step( + threshold = commonArrowDimensAlphaThreshold, + factor = commonArrowDimensAlphaFactor, + postThreshold = 1f, + preThreshold = 0f + ) + entryIndicator = BackIndicatorDimens( horizontalTranslation = getDimen(R.dimen.navigation_edge_entry_margin), scale = getDimenFloat(R.dimen.navigation_edge_entry_scale), @@ -162,18 +175,8 @@ data class EdgePanelParams(private var resources: Resources) { alpha = 0f, lengthSpring = createSpring(600f, 0.4f), heightSpring = createSpring(600f, 0.4f), - alphaSpring = Step( - threshold = entryIndicatorAlphaThreshold, - factor = entryIndicatorAlphaFactor, - postThreshold = createSpring(200f, 1f), - preThreshold = createSpring(2000f, 0.6f) - ), - alphaInterpolator = Step( - threshold = entryIndicatorAlphaThreshold, - factor = entryIndicatorAlphaFactor, - postThreshold = 1f, - preThreshold = 0f - ) + alphaSpring = commonArrowDimensAlphaSpring, + alphaInterpolator = commonArrowDimensAlphaSpringInterpolator ), backgroundDimens = BackgroundDimens( alpha = 1f, @@ -188,20 +191,6 @@ data class EdgePanelParams(private var resources: Resources) { ) ) - val preThresholdAndActiveIndicatorAlphaThreshold = .355f - val preThresholdAndActiveIndicatorAlphaFactor = 1.05f - val preThresholdAndActiveAlphaSpring = Step( - threshold = preThresholdAndActiveIndicatorAlphaThreshold, - factor = preThresholdAndActiveIndicatorAlphaFactor, - postThreshold = createSpring(180f, 0.9f), - preThreshold = createSpring(2000f, 0.6f) - ) - val preThresholdAndActiveAlphaSpringInterpolator = Step( - threshold = preThresholdAndActiveIndicatorAlphaThreshold, - factor = preThresholdAndActiveIndicatorAlphaFactor, - postThreshold = 1f, - preThreshold = 0f - ) activeIndicator = BackIndicatorDimens( horizontalTranslation = getDimen(R.dimen.navigation_edge_active_margin), scale = getDimenFloat(R.dimen.navigation_edge_active_scale), @@ -214,8 +203,8 @@ data class EdgePanelParams(private var resources: Resources) { alpha = 1f, lengthSpring = activeCommittedArrowLengthSpring, heightSpring = activeCommittedArrowHeightSpring, - alphaSpring = preThresholdAndActiveAlphaSpring, - alphaInterpolator = preThresholdAndActiveAlphaSpringInterpolator + alphaSpring = commonArrowDimensAlphaSpring, + alphaInterpolator = commonArrowDimensAlphaSpringInterpolator ), backgroundDimens = BackgroundDimens( alpha = 1f, @@ -242,8 +231,8 @@ data class EdgePanelParams(private var resources: Resources) { alpha = 1f, lengthSpring = createSpring(100f, 0.6f), heightSpring = createSpring(100f, 0.6f), - alphaSpring = preThresholdAndActiveAlphaSpring, - alphaInterpolator = preThresholdAndActiveAlphaSpringInterpolator + alphaSpring = commonArrowDimensAlphaSpring, + alphaInterpolator = commonArrowDimensAlphaSpringInterpolator ), backgroundDimens = BackgroundDimens( alpha = 1f, diff --git a/packages/SystemUI/src/com/android/systemui/screenrecord/ScreenRecordPermissionDialog.kt b/packages/SystemUI/src/com/android/systemui/screenrecord/ScreenRecordPermissionDialog.kt index bfaf3d0d2ec5..604d44967e68 100644 --- a/packages/SystemUI/src/com/android/systemui/screenrecord/ScreenRecordPermissionDialog.kt +++ b/packages/SystemUI/src/com/android/systemui/screenrecord/ScreenRecordPermissionDialog.kt @@ -93,6 +93,7 @@ class ScreenRecordPermissionDialog( } dismiss() } + setCancelButtonOnClickListener { dismiss() } initRecordOptionsView() } diff --git a/packages/SystemUI/src/com/android/systemui/scrim/ScrimDrawable.java b/packages/SystemUI/src/com/android/systemui/scrim/ScrimDrawable.java index 10e2afe0baa9..9a1ffcbab8d1 100644 --- a/packages/SystemUI/src/com/android/systemui/scrim/ScrimDrawable.java +++ b/packages/SystemUI/src/com/android/systemui/scrim/ScrimDrawable.java @@ -203,7 +203,10 @@ public class ScrimDrawable extends Drawable { } public void setBottomEdgeRadius(float radius) { - mBottomEdgeRadius = radius; + if (mBottomEdgeRadius != radius) { + mBottomEdgeRadius = radius; + invalidateSelf(); + } } @Override diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java index 784a36092922..047fea123dc3 100644 --- a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java +++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java @@ -2206,6 +2206,7 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump // TODO: non-linearly transform progress fraction into squish amount (ease-in, linear out) mCurrentBackProgress = progressFraction; applyBackScaling(progressFraction); + mQsController.setClippingBounds(); } /** Resets back progress. */ diff --git a/packages/SystemUI/src/com/android/systemui/shade/QuickSettingsController.java b/packages/SystemUI/src/com/android/systemui/shade/QuickSettingsController.java index 8672260790c3..c42c2f4fa15a 100644 --- a/packages/SystemUI/src/com/android/systemui/shade/QuickSettingsController.java +++ b/packages/SystemUI/src/com/android/systemui/shade/QuickSettingsController.java @@ -114,6 +114,8 @@ import javax.inject.Inject; public class QuickSettingsController implements Dumpable { public static final String TAG = "QuickSettingsController"; + public static final int SHADE_BACK_ANIM_SCALE_MULTIPLIER = 100; + private QS mQs; private final Lazy<NotificationPanelViewController> mPanelViewControllerLazy; @@ -1128,7 +1130,7 @@ public class QuickSettingsController implements Dumpable { * Updates scrim bounds, QS clipping, notifications clipping and keyguard status view clipping * as well based on the bounds of the shade and QS state. */ - private void setClippingBounds() { + void setClippingBounds() { float qsExpansionFraction = computeExpansionFraction(); final int qsPanelBottomY = calculateBottomPosition(qsExpansionFraction); // Split shade has no QQS @@ -1216,7 +1218,10 @@ public class QuickSettingsController implements Dumpable { ? 0 : mScreenCornerRadius; radius = (int) MathUtils.lerp(screenCornerRadius, mScrimCornerRadius, Math.min(top / (float) mScrimCornerRadius, 1f)); - mScrimController.setNotificationBottomRadius(radius); + + float bottomRadius = mExpanded ? screenCornerRadius : + calculateBottomCornerRadius(screenCornerRadius); + mScrimController.setNotificationBottomRadius(bottomRadius); } if (isQsFragmentCreated()) { float qsTranslation = 0; @@ -1279,6 +1284,28 @@ public class QuickSettingsController implements Dumpable { nsslLeft, nsslTop, nsslRight, nsslBottom, topRadius, bottomRadius); } + /** + * Bottom corner radius should follow screen corner radius unless + * predictive back is running. We want a smooth transition from screen + * corner radius to scrim corner radius as the notification scrim is scaled down, + * but the transition should be brief enough to accommodate very short back gestures. + */ + @VisibleForTesting + int calculateBottomCornerRadius(float screenCornerRadius) { + return (int) MathUtils.lerp(screenCornerRadius, mScrimCornerRadius, + Math.min(calculateBottomRadiusProgress(), 1f)); + } + + @VisibleForTesting + float calculateBottomRadiusProgress() { + return (1 - mScrimController.getBackScaling()) * SHADE_BACK_ANIM_SCALE_MULTIPLIER; + } + + @VisibleForTesting + int getScrimCornerRadius() { + return mScrimCornerRadius; + } + void setDisplayInsets(int leftInset, int rightInset) { mDisplayLeftInset = leftInset; mDisplayRightInset = rightInset; diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java index fdb772bc6c13..25ecf1a424e0 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java @@ -602,6 +602,10 @@ public class ScrimController implements ViewTreeObserver.OnPreDrawListener, Dump mNotificationsScrim.setScaleY(scale); } + public float getBackScaling() { + return mNotificationsScrim.getScaleY(); + } + public void onTrackingStarted() { mDarkenWhileDragging = !mKeyguardStateController.canDismissLockScreen(); if (!mKeyguardUnlockAnimationController.isPlayingCannedUnlockAnimation()) { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java index 0414a14205d7..1bf63be3c8b0 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java @@ -495,6 +495,7 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb return mKeyguardStateController.isShowing() && !primaryBouncerIsOrWillBeShowing() + && !mKeyguardStateController.isKeyguardGoingAway() && isUserTrackingStarted && !hideBouncerOverDream && !mKeyguardStateController.isOccluded() diff --git a/packages/SystemUI/src/com/android/systemui/util/ActivityTaskManagerProxy.kt b/packages/SystemUI/src/com/android/systemui/util/ActivityTaskManagerProxy.kt new file mode 100644 index 000000000000..6e82cf673307 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/util/ActivityTaskManagerProxy.kt @@ -0,0 +1,30 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.util + +import android.app.ActivityTaskManager +import android.content.Context +import com.android.systemui.dagger.SysUISingleton +import javax.inject.Inject + +/** Proxy for static calls to [ActivityTaskManager]. */ +@SysUISingleton +class ActivityTaskManagerProxy @Inject constructor() { + + /** Calls [ActivityTaskManager.supportsMultiWindow] */ + fun supportsMultiWindow(context: Context) = ActivityTaskManager.supportsMultiWindow(context) +} diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchControllerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchControllerTest.java index fb738454fc71..b21cc6dde815 100644 --- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchControllerTest.java @@ -150,6 +150,8 @@ public class KeyguardClockSwitchControllerTest extends SysuiTestCase { .thenReturn(100); when(mResources.getDimensionPixelSize(R.dimen.keyguard_large_clock_top_margin)) .thenReturn(-200); + when(mResources.getInteger(R.integer.keyguard_date_weather_view_invisibility)) + .thenReturn(View.INVISIBLE); when(mView.findViewById(R.id.lockscreen_clock_view_large)).thenReturn(mLargeClockFrame); when(mView.findViewById(R.id.lockscreen_clock_view)).thenReturn(mSmallClockFrame); diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSliceViewControllerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSliceViewControllerTest.java index 68dc6c04bc79..4d3243a2ccf6 100644 --- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSliceViewControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSliceViewControllerTest.java @@ -15,11 +15,13 @@ */ package com.android.keyguard; +import static org.junit.Assume.assumeFalse; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; +import android.content.pm.PackageManager; import android.test.suitebuilder.annotation.SmallTest; import android.testing.AndroidTestingRunner; import android.testing.TestableLooper.RunWithLooper; @@ -76,12 +78,20 @@ public class KeyguardSliceViewControllerTest extends SysuiTestCase { @Test public void refresh_replacesSliceContentAndNotifiesListener() { + // Skips the test if running on a watch because watches don't have a SliceManager system + // service. + assumeFalse(isWatch()); + mController.refresh(); verify(mView).hideSlice(); } @Test public void onAttachedToWindow_registersListeners() { + // Skips the test if running on a watch because watches don't have a SliceManager system + // service. + assumeFalse(isWatch()); + mController.init(); verify(mTunerService).addTunable(any(TunerService.Tunable.class), anyString()); verify(mConfigurationController).addCallback( @@ -90,6 +100,10 @@ public class KeyguardSliceViewControllerTest extends SysuiTestCase { @Test public void onDetachedFromWindow_unregistersListeners() { + // Skips the test if running on a watch because watches don't have a SliceManager system + // service. + assumeFalse(isWatch()); + ArgumentCaptor<View.OnAttachStateChangeListener> attachListenerArgumentCaptor = ArgumentCaptor.forClass(View.OnAttachStateChangeListener.class); @@ -102,4 +116,9 @@ public class KeyguardSliceViewControllerTest extends SysuiTestCase { verify(mConfigurationController).removeCallback( any(ConfigurationController.ConfigurationListener.class)); } + + private boolean isWatch() { + final PackageManager pm = mContext.getPackageManager(); + return pm.hasSystemFeature(PackageManager.FEATURE_WATCH); + } } diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java index 2f72cb95db98..419d045885a8 100644 --- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java +++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java @@ -654,6 +654,24 @@ public class KeyguardUpdateMonitorTest extends SysuiTestCase { } @Test + public void serviceProvidersUpdated_broadcastTriggersInfoRefresh() { + // The callback is invoked once on init + verify(mTestCallback, times(1)).onRefreshCarrierInfo(); + + // WHEN the SERVICE_PROVIDERS_UPDATED broadcast is sent + Intent intent = new Intent(TelephonyManager.ACTION_SERVICE_PROVIDERS_UPDATED); + intent.putExtra(TelephonyManager.EXTRA_SPN, "spn"); + intent.putExtra(TelephonyManager.EXTRA_PLMN, "plmn"); + mKeyguardUpdateMonitor.mBroadcastReceiver.onReceive(getContext(), + putPhoneInfo(intent, null, true)); + mTestableLooper.processAllMessages(); + + // THEN verify keyguardUpdateMonitorCallback receives a refresh callback + // Note that we have times(2) here because it's been called once already + verify(mTestCallback, times(2)).onRefreshCarrierInfo(); + } + + @Test public void testTriesToAuthenticateFingerprint_whenKeyguard() { mKeyguardUpdateMonitor.dispatchStartedGoingToSleep(0 /* why */); mTestableLooper.processAllMessages(); diff --git a/packages/SystemUI/tests/src/com/android/systemui/bouncer/domain/interactor/BouncerInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/bouncer/domain/interactor/BouncerInteractorTest.kt index 9f5c181c3129..374c28d6dce8 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/bouncer/domain/interactor/BouncerInteractorTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/bouncer/domain/interactor/BouncerInteractorTest.kt @@ -82,7 +82,7 @@ class BouncerInteractorTest : SysuiTestCase() { assertThat(message).isEqualTo(MESSAGE_ENTER_YOUR_PIN) // Wrong input. - underTest.authenticate(listOf(9, 8, 7)) + assertThat(underTest.authenticate(listOf(9, 8, 7))).isFalse() assertThat(message).isEqualTo(MESSAGE_WRONG_PIN) assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer)) @@ -90,7 +90,7 @@ class BouncerInteractorTest : SysuiTestCase() { assertThat(message).isEqualTo(MESSAGE_ENTER_YOUR_PIN) // Correct input. - underTest.authenticate(listOf(1, 2, 3, 4)) + assertThat(underTest.authenticate(listOf(1, 2, 3, 4))).isTrue() assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Gone)) } @@ -114,7 +114,7 @@ class BouncerInteractorTest : SysuiTestCase() { assertThat(message).isEqualTo(MESSAGE_ENTER_YOUR_PASSWORD) // Wrong input. - underTest.authenticate("alohamora".toList()) + assertThat(underTest.authenticate("alohamora".toList())).isFalse() assertThat(message).isEqualTo(MESSAGE_WRONG_PASSWORD) assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer)) @@ -122,7 +122,7 @@ class BouncerInteractorTest : SysuiTestCase() { assertThat(message).isEqualTo(MESSAGE_ENTER_YOUR_PASSWORD) // Correct input. - underTest.authenticate("password".toList()) + assertThat(underTest.authenticate("password".toList())).isTrue() assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Gone)) } @@ -146,9 +146,12 @@ class BouncerInteractorTest : SysuiTestCase() { assertThat(message).isEqualTo(MESSAGE_ENTER_YOUR_PATTERN) // Wrong input. - underTest.authenticate( - listOf(AuthenticationMethodModel.Pattern.PatternCoordinate(3, 4)) - ) + assertThat( + underTest.authenticate( + listOf(AuthenticationMethodModel.Pattern.PatternCoordinate(3, 4)) + ) + ) + .isFalse() assertThat(message).isEqualTo(MESSAGE_WRONG_PATTERN) assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer)) @@ -156,7 +159,7 @@ class BouncerInteractorTest : SysuiTestCase() { assertThat(message).isEqualTo(MESSAGE_ENTER_YOUR_PATTERN) // Correct input. - underTest.authenticate(emptyList()) + assertThat(underTest.authenticate(emptyList())).isTrue() assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Gone)) } @@ -214,7 +217,7 @@ class BouncerInteractorTest : SysuiTestCase() { assertThat(isUnlocked).isFalse() repeat(BouncerInteractor.THROTTLE_EVERY) { times -> // Wrong PIN. - underTest.authenticate(listOf(6, 7, 8, 9)) + assertThat(underTest.authenticate(listOf(6, 7, 8, 9))).isFalse() if (times < BouncerInteractor.THROTTLE_EVERY - 1) { assertThat(message).isEqualTo(MESSAGE_WRONG_PIN) } @@ -223,7 +226,7 @@ class BouncerInteractorTest : SysuiTestCase() { assertTryAgainMessage(message, BouncerInteractor.THROTTLE_DURATION_SEC) // Correct PIN, but throttled, so doesn't unlock: - underTest.authenticate(listOf(1, 2, 3, 4)) + assertThat(underTest.authenticate(listOf(1, 2, 3, 4))).isFalse() assertThat(isUnlocked).isFalse() assertTryAgainMessage(message, BouncerInteractor.THROTTLE_DURATION_SEC) @@ -241,7 +244,7 @@ class BouncerInteractorTest : SysuiTestCase() { assertThat(isUnlocked).isFalse() // Correct PIN and no longer throttled so unlocks: - underTest.authenticate(listOf(1, 2, 3, 4)) + assertThat(underTest.authenticate(listOf(1, 2, 3, 4))).isTrue() assertThat(isUnlocked).isTrue() } diff --git a/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/AuthMethodBouncerViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/AuthMethodBouncerViewModelTest.kt new file mode 100644 index 000000000000..1642410e5a3f --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/AuthMethodBouncerViewModelTest.kt @@ -0,0 +1,81 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.bouncer.ui.viewmodel + +import androidx.test.filters.SmallTest +import com.android.systemui.SysuiTestCase +import com.android.systemui.authentication.shared.model.AuthenticationMethodModel +import com.android.systemui.coroutines.collectLastValue +import com.android.systemui.scene.SceneTestUtils +import com.google.common.truth.Truth.assertThat +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.test.TestScope +import kotlinx.coroutines.test.runTest +import org.junit.Test +import org.junit.runner.RunWith +import org.junit.runners.JUnit4 + +@OptIn(ExperimentalCoroutinesApi::class) +@SmallTest +@RunWith(JUnit4::class) +class AuthMethodBouncerViewModelTest : SysuiTestCase() { + + private val testScope = TestScope() + private val utils = SceneTestUtils(this, testScope) + private val authenticationInteractor = + utils.authenticationInteractor( + utils.authenticationRepository(), + ) + private val underTest = + PinBouncerViewModel( + applicationScope = testScope.backgroundScope, + interactor = + utils.bouncerInteractor( + authenticationInteractor = authenticationInteractor, + sceneInteractor = utils.sceneInteractor(), + ), + isInputEnabled = MutableStateFlow(true), + ) + + @Test + fun animateFailure() = + testScope.runTest { + authenticationInteractor.setAuthenticationMethod(AuthenticationMethodModel.PIN(1234)) + val animateFailure by collectLastValue(underTest.animateFailure) + assertThat(animateFailure).isFalse() + + // Wrong PIN: + underTest.onPinButtonClicked(3) + underTest.onPinButtonClicked(4) + underTest.onPinButtonClicked(5) + underTest.onPinButtonClicked(6) + underTest.onAuthenticateButtonClicked() + assertThat(animateFailure).isTrue() + + underTest.onFailureAnimationShown() + assertThat(animateFailure).isFalse() + + // Correct PIN: + underTest.onPinButtonClicked(1) + underTest.onPinButtonClicked(2) + underTest.onPinButtonClicked(3) + underTest.onPinButtonClicked(4) + underTest.onAuthenticateButtonClicked() + assertThat(animateFailure).isFalse() + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/BouncerViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/BouncerViewModelTest.kt index b942ccbb51f3..e8c946cdd59d 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/BouncerViewModelTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/BouncerViewModelTest.kt @@ -93,22 +93,21 @@ class BouncerViewModelTest : SysuiTestCase() { } @Test - fun isMessageUpdateAnimationsEnabled() = + fun message() = testScope.runTest { - val isMessageUpdateAnimationsEnabled by - collectLastValue(underTest.isMessageUpdateAnimationsEnabled) + val message by collectLastValue(underTest.message) val throttling by collectLastValue(bouncerInteractor.throttling) authenticationInteractor.setAuthenticationMethod(AuthenticationMethodModel.PIN(1234)) - assertThat(isMessageUpdateAnimationsEnabled).isTrue() + assertThat(message?.isUpdateAnimated).isTrue() repeat(BouncerInteractor.THROTTLE_EVERY) { // Wrong PIN. bouncerInteractor.authenticate(listOf(3, 4, 5, 6)) } - assertThat(isMessageUpdateAnimationsEnabled).isFalse() + assertThat(message?.isUpdateAnimated).isFalse() throttling?.totalDurationSec?.let { seconds -> advanceTimeBy(seconds * 1000L) } - assertThat(isMessageUpdateAnimationsEnabled).isTrue() + assertThat(message?.isUpdateAnimated).isTrue() } @Test diff --git a/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/PasswordBouncerViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/PasswordBouncerViewModelTest.kt index b7b90de3b54a..f436aa309ac5 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/PasswordBouncerViewModelTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/PasswordBouncerViewModelTest.kt @@ -85,7 +85,7 @@ class PasswordBouncerViewModelTest : SysuiTestCase() { underTest.onShown() - assertThat(message).isEqualTo(ENTER_YOUR_PASSWORD) + assertThat(message?.text).isEqualTo(ENTER_YOUR_PASSWORD) assertThat(password).isEqualTo("") assertThat(isUnlocked).isFalse() assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer)) @@ -109,7 +109,7 @@ class PasswordBouncerViewModelTest : SysuiTestCase() { underTest.onPasswordInputChanged("password") - assertThat(message).isEmpty() + assertThat(message?.text).isEmpty() assertThat(password).isEqualTo("password") assertThat(isUnlocked).isFalse() assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer)) @@ -156,7 +156,7 @@ class PasswordBouncerViewModelTest : SysuiTestCase() { underTest.onAuthenticateKeyPressed() assertThat(password).isEqualTo("") - assertThat(message).isEqualTo(WRONG_PASSWORD) + assertThat(message?.text).isEqualTo(WRONG_PASSWORD) assertThat(isUnlocked).isFalse() assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer)) } @@ -179,13 +179,13 @@ class PasswordBouncerViewModelTest : SysuiTestCase() { underTest.onPasswordInputChanged("wrong") underTest.onAuthenticateKeyPressed() assertThat(password).isEqualTo("") - assertThat(message).isEqualTo(WRONG_PASSWORD) + assertThat(message?.text).isEqualTo(WRONG_PASSWORD) assertThat(isUnlocked).isFalse() assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer)) // Enter the correct password: underTest.onPasswordInputChanged("password") - assertThat(message).isEmpty() + assertThat(message?.text).isEmpty() underTest.onAuthenticateKeyPressed() diff --git a/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/PatternBouncerViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/PatternBouncerViewModelTest.kt index b588ba2b2574..d7d7154705ed 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/PatternBouncerViewModelTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/PatternBouncerViewModelTest.kt @@ -89,7 +89,7 @@ class PatternBouncerViewModelTest : SysuiTestCase() { underTest.onShown() - assertThat(message).isEqualTo(ENTER_YOUR_PATTERN) + assertThat(message?.text).isEqualTo(ENTER_YOUR_PATTERN) assertThat(selectedDots).isEmpty() assertThat(currentDot).isNull() assertThat(isUnlocked).isFalse() @@ -115,7 +115,7 @@ class PatternBouncerViewModelTest : SysuiTestCase() { underTest.onDragStart() - assertThat(message).isEmpty() + assertThat(message?.text).isEmpty() assertThat(selectedDots).isEmpty() assertThat(currentDot).isNull() assertThat(isUnlocked).isFalse() @@ -202,7 +202,7 @@ class PatternBouncerViewModelTest : SysuiTestCase() { assertThat(selectedDots).isEmpty() assertThat(currentDot).isNull() - assertThat(message).isEqualTo(WRONG_PATTERN) + assertThat(message?.text).isEqualTo(WRONG_PATTERN) assertThat(isUnlocked).isFalse() assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer)) } @@ -235,7 +235,7 @@ class PatternBouncerViewModelTest : SysuiTestCase() { underTest.onDragEnd() assertThat(selectedDots).isEmpty() assertThat(currentDot).isNull() - assertThat(message).isEqualTo(WRONG_PATTERN) + assertThat(message?.text).isEqualTo(WRONG_PATTERN) assertThat(isUnlocked).isFalse() assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer)) diff --git a/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/PinBouncerViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/PinBouncerViewModelTest.kt index 83f9687d7ac5..3bdaf0590888 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/PinBouncerViewModelTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/PinBouncerViewModelTest.kt @@ -94,7 +94,7 @@ class PinBouncerViewModelTest : SysuiTestCase() { underTest.onShown() - assertThat(message).isEqualTo(ENTER_YOUR_PIN) + assertThat(message?.text).isEqualTo(ENTER_YOUR_PIN) assertThat(pinLengths).isEqualTo(0 to 0) assertThat(isUnlocked).isFalse() assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer)) @@ -116,7 +116,7 @@ class PinBouncerViewModelTest : SysuiTestCase() { underTest.onPinButtonClicked(1) - assertThat(message).isEmpty() + assertThat(message?.text).isEmpty() assertThat(pinLengths).isEqualTo(0 to 1) assertThat(isUnlocked).isFalse() assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer)) @@ -140,7 +140,7 @@ class PinBouncerViewModelTest : SysuiTestCase() { underTest.onBackspaceButtonClicked() - assertThat(message).isEmpty() + assertThat(message?.text).isEmpty() assertThat(pinLengths).isEqualTo(1 to 0) assertThat(isUnlocked).isFalse() assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer)) @@ -170,7 +170,7 @@ class PinBouncerViewModelTest : SysuiTestCase() { advanceTimeBy(PinBouncerViewModel.BACKSPACE_LONG_PRESS_DELAY_MS) } - assertThat(message).isEmpty() + assertThat(message?.text).isEmpty() assertThat(pinLengths).isEqualTo(1 to 0) assertThat(isUnlocked).isFalse() assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer)) @@ -220,7 +220,7 @@ class PinBouncerViewModelTest : SysuiTestCase() { underTest.onAuthenticateButtonClicked() assertThat(pinLengths).isEqualTo(0 to 0) - assertThat(message).isEqualTo(WRONG_PIN) + assertThat(message?.text).isEqualTo(WRONG_PIN) assertThat(isUnlocked).isFalse() assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer)) } @@ -244,7 +244,7 @@ class PinBouncerViewModelTest : SysuiTestCase() { underTest.onPinButtonClicked(4) underTest.onPinButtonClicked(5) // PIN is now wrong! underTest.onAuthenticateButtonClicked() - assertThat(message).isEqualTo(WRONG_PIN) + assertThat(message?.text).isEqualTo(WRONG_PIN) assertThat(pinLengths).isEqualTo(0 to 0) assertThat(isUnlocked).isFalse() assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer)) @@ -254,7 +254,7 @@ class PinBouncerViewModelTest : SysuiTestCase() { underTest.onPinButtonClicked(2) underTest.onPinButtonClicked(3) underTest.onPinButtonClicked(4) - assertThat(message).isEmpty() + assertThat(message?.text).isEmpty() underTest.onAuthenticateButtonClicked() diff --git a/packages/SystemUI/tests/src/com/android/systemui/controls/management/ControlsListingControllerImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/controls/management/ControlsListingControllerImplTest.kt index 10bfc1b292d3..ee213f7c4257 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/controls/management/ControlsListingControllerImplTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/controls/management/ControlsListingControllerImplTest.kt @@ -39,6 +39,7 @@ import com.android.systemui.flags.FeatureFlags import com.android.systemui.flags.Flags.APP_PANELS_ALL_APPS_ALLOWED import com.android.systemui.flags.Flags.USE_APP_PANELS import com.android.systemui.settings.UserTracker +import com.android.systemui.util.ActivityTaskManagerProxy import com.android.systemui.util.concurrency.FakeExecutor import com.android.systemui.util.mockito.any import com.android.systemui.util.mockito.argThat @@ -88,6 +89,8 @@ class ControlsListingControllerImplTest : SysuiTestCase() { private lateinit var packageManager: PackageManager @Mock private lateinit var featureFlags: FeatureFlags + @Mock + private lateinit var activityTaskManagerProxy: ActivityTaskManagerProxy private var componentName = ComponentName("pkg", "class1") private var activityName = ComponentName("pkg", "activity") @@ -112,6 +115,7 @@ class ControlsListingControllerImplTest : SysuiTestCase() { // Return disabled by default `when`(packageManager.getComponentEnabledSetting(any())) .thenReturn(PackageManager.COMPONENT_ENABLED_STATE_DISABLED) + `when`(activityTaskManagerProxy.supportsMultiWindow(any())).thenReturn(true) mContext.setMockPackageManager(packageManager) mContext.orCreateTestableResources @@ -136,6 +140,7 @@ class ControlsListingControllerImplTest : SysuiTestCase() { executor, { mockSL }, userTracker, + activityTaskManagerProxy, dumpManager, featureFlags ) @@ -171,6 +176,7 @@ class ControlsListingControllerImplTest : SysuiTestCase() { exec, { mockServiceListing }, userTracker, + activityTaskManagerProxy, dumpManager, featureFlags ) @@ -637,7 +643,34 @@ class ControlsListingControllerImplTest : SysuiTestCase() { assertThat(services[0].serviceInfo.componentName).isEqualTo(componentName) } + @Test + fun testNoPanelIfMultiWindowNotSupported() { + `when`(activityTaskManagerProxy.supportsMultiWindow(any())).thenReturn(false) + + val serviceInfo = ServiceInfo( + componentName, + activityName + ) + + `when`(packageManager.getComponentEnabledSetting(eq(activityName))) + .thenReturn(PackageManager.COMPONENT_ENABLED_STATE_DEFAULT) + + setUpQueryResult(listOf( + ActivityInfo( + activityName, + enabled = true, + exported = true, + permission = Manifest.permission.BIND_CONTROLS + ) + )) + + val list = listOf(serviceInfo) + serviceListingCallbackCaptor.value.onServicesReloaded(list) + executor.runAllReady() + + assertNull(controller.getCurrentServices()[0].panelActivity) + } private fun ServiceInfo( componentName: ComponentName, diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardUnlockAnimationControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardUnlockAnimationControllerTest.kt index 688c2db044e4..477e076669b7 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardUnlockAnimationControllerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardUnlockAnimationControllerTest.kt @@ -10,6 +10,7 @@ import android.testing.TestableLooper.RunWithLooper import android.view.RemoteAnimationTarget import android.view.SurfaceControl import android.view.SyncRtSurfaceTransactionApplier +import android.view.View import android.view.ViewRootImpl import androidx.test.filters.SmallTest import com.android.keyguard.KeyguardViewController @@ -32,6 +33,7 @@ import org.junit.runner.RunWith import org.mockito.Mock import org.mockito.Mockito.atLeastOnce import org.mockito.Mockito.mock +import org.mockito.Mockito.never import org.mockito.Mockito.times import org.mockito.Mockito.verify import org.mockito.Mockito.verifyNoMoreInteractions @@ -374,6 +376,83 @@ class KeyguardUnlockAnimationControllerTest : SysuiTestCase() { verifyNoMoreInteractions(surfaceTransactionApplier) } + @Test + fun unlockToLauncherWithInWindowAnimations_ssViewIsVisible() { + val mockLockscreenSmartspaceView = mock(View::class.java) + whenever(mockLockscreenSmartspaceView.visibility).thenReturn(View.VISIBLE) + keyguardUnlockAnimationController.lockscreenSmartspace = mockLockscreenSmartspaceView + + keyguardUnlockAnimationController.unlockToLauncherWithInWindowAnimations() + + verify(mockLockscreenSmartspaceView).visibility = View.INVISIBLE + } + + @Test + fun unlockToLauncherWithInWindowAnimations_ssViewIsInvisible() { + val mockLockscreenSmartspaceView = mock(View::class.java) + whenever(mockLockscreenSmartspaceView.visibility).thenReturn(View.INVISIBLE) + keyguardUnlockAnimationController.lockscreenSmartspace = mockLockscreenSmartspaceView + + keyguardUnlockAnimationController.unlockToLauncherWithInWindowAnimations() + + verify(mockLockscreenSmartspaceView, never()).visibility = View.INVISIBLE + } + + @Test + fun unlockToLauncherWithInWindowAnimations_ssViewIsGone() { + val mockLockscreenSmartspaceView = mock(View::class.java) + whenever(mockLockscreenSmartspaceView.visibility).thenReturn(View.GONE) + keyguardUnlockAnimationController.lockscreenSmartspace = mockLockscreenSmartspaceView + + keyguardUnlockAnimationController.unlockToLauncherWithInWindowAnimations() + + verify(mockLockscreenSmartspaceView, never()).visibility = View.INVISIBLE + } + + @Test + fun notifyFinishedKeyguardExitAnimation_ssViewIsInvisibleAndCancelledIsTrue() { + val mockLockscreenSmartspaceView = mock(View::class.java) + whenever(mockLockscreenSmartspaceView.visibility).thenReturn(View.INVISIBLE) + keyguardUnlockAnimationController.lockscreenSmartspace = mockLockscreenSmartspaceView + + keyguardUnlockAnimationController.notifyFinishedKeyguardExitAnimation(true) + + verify(mockLockscreenSmartspaceView).visibility = View.VISIBLE + } + + @Test + fun notifyFinishedKeyguardExitAnimation_ssViewIsGoneAndCancelledIsTrue() { + val mockLockscreenSmartspaceView = mock(View::class.java) + whenever(mockLockscreenSmartspaceView.visibility).thenReturn(View.GONE) + keyguardUnlockAnimationController.lockscreenSmartspace = mockLockscreenSmartspaceView + + keyguardUnlockAnimationController.notifyFinishedKeyguardExitAnimation(true) + + verify(mockLockscreenSmartspaceView, never()).visibility = View.VISIBLE + } + + @Test + fun notifyFinishedKeyguardExitAnimation_ssViewIsInvisibleAndCancelledIsFalse() { + val mockLockscreenSmartspaceView = mock(View::class.java) + whenever(mockLockscreenSmartspaceView.visibility).thenReturn(View.INVISIBLE) + keyguardUnlockAnimationController.lockscreenSmartspace = mockLockscreenSmartspaceView + + keyguardUnlockAnimationController.notifyFinishedKeyguardExitAnimation(false) + + verify(mockLockscreenSmartspaceView).visibility = View.VISIBLE + } + + @Test + fun notifyFinishedKeyguardExitAnimation_ssViewIsGoneAndCancelledIsFalse() { + val mockLockscreenSmartspaceView = mock(View::class.java) + whenever(mockLockscreenSmartspaceView.visibility).thenReturn(View.GONE) + keyguardUnlockAnimationController.lockscreenSmartspace = mockLockscreenSmartspaceView + + keyguardUnlockAnimationController.notifyFinishedKeyguardExitAnimation(false) + + verify(mockLockscreenSmartspaceView, never()).visibility = View.VISIBLE + } + private class ArgThatCaptor<T> { private var allArgs: MutableList<T> = mutableListOf() 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/shade/QuickSettingsControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/shade/QuickSettingsControllerTest.java index 34d09a912c87..ff047aa2faf2 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/shade/QuickSettingsControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/shade/QuickSettingsControllerTest.java @@ -580,6 +580,30 @@ public class QuickSettingsControllerTest extends SysuiTestCase { verify(mQs).setQsVisible(true); } + @Test + public void calculateBottomCornerRadius_scrimScaleMax() { + when(mScrimController.getBackScaling()).thenReturn(1.0f); + assertThat(mQsController.calculateBottomCornerRadius(0.0f)).isEqualTo(0); + } + + @Test + public void calculateBottomCornerRadius_scrimScaleMin() { + when(mScrimController.getBackScaling()) + .thenReturn(mNotificationPanelViewController.SHADE_BACK_ANIM_MIN_SCALE); + assertThat(mQsController.calculateBottomCornerRadius(0.0f)) + .isEqualTo(mQsController.getScrimCornerRadius()); + } + + @Test + public void calculateBottomCornerRadius_scrimScaleCutoff() { + float ratio = 1 / mQsController.calculateBottomRadiusProgress(); + float cutoffScale = 1 - mNotificationPanelViewController.SHADE_BACK_ANIM_MIN_SCALE / ratio; + when(mScrimController.getBackScaling()) + .thenReturn(cutoffScale); + assertThat(mQsController.calculateBottomCornerRadius(0.0f)) + .isEqualTo(mQsController.getScrimCornerRadius()); + } + private void lockScreen() { mQsController.setBarState(KEYGUARD); } diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java index e56f0d6b3b0f..3eea93c6ec2c 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java @@ -352,6 +352,17 @@ public class StatusBarKeyguardViewManagerTest extends SysuiTestCase { } @Test + public void onPanelExpansionChanged_neverTranslatesBouncerWhenGoingAway() { + when(mKeyguardStateController.isKeyguardGoingAway()).thenReturn(true); + mStatusBarKeyguardViewManager.onPanelExpansionChanged( + expansionEvent( + /* fraction= */ EXPANSION_VISIBLE, + /* expanded= */ true, + /* tracking= */ false)); + verify(mPrimaryBouncerInteractor, never()).setPanelExpansion(anyFloat()); + } + + @Test public void onPanelExpansionChanged_neverTranslatesBouncerWhenShowBouncer() { // Since KeyguardBouncer.EXPANSION_VISIBLE = 0 panel expansion, if the unlock is dismissing // the bouncer, there may be an onPanelExpansionChanged(0) call to collapse the panel diff --git a/packages/services/CameraExtensionsProxy/src/com/android/cameraextensions/CameraExtensionsProxyService.java b/packages/services/CameraExtensionsProxy/src/com/android/cameraextensions/CameraExtensionsProxyService.java index f31ca8178eb6..c2ebddf00fb4 100644 --- a/packages/services/CameraExtensionsProxy/src/com/android/cameraextensions/CameraExtensionsProxyService.java +++ b/packages/services/CameraExtensionsProxy/src/com/android/cameraextensions/CameraExtensionsProxyService.java @@ -2057,7 +2057,11 @@ public class CameraExtensionsProxyService extends Service { mIsImageValid = false; if (mGraphicBuffer != null) { - ImageReader.unlockGraphicBuffer(mGraphicBuffer); + try { + ImageReader.unlockGraphicBuffer(mGraphicBuffer); + } catch (RuntimeException e) { + e.printStackTrace(); + } mGraphicBuffer.destroy(); mGraphicBuffer = null; } diff --git a/services/autofill/java/com/android/server/autofill/AutofillManagerService.java b/services/autofill/java/com/android/server/autofill/AutofillManagerService.java index fc758cba617f..1a57bc1b7a0d 100644 --- a/services/autofill/java/com/android/server/autofill/AutofillManagerService.java +++ b/services/autofill/java/com/android/server/autofill/AutofillManagerService.java @@ -761,6 +761,18 @@ public final class AutofillManagerService } // Called by Shell command + boolean isFieldDetectionServiceEnabledForUser(@UserIdInt int userId) { + enforceCallingPermissionForManagement(); + synchronized (mLock) { + final AutofillManagerServiceImpl service = getServiceForUserLocked(userId); + if (service != null) { + return service.isPccClassificationEnabled(); + } + } + return false; + } + + // Called by Shell command String getFieldDetectionServiceName(@UserIdInt int userId) { enforceCallingPermissionForManagement(); return mFieldClassificationResolver.readServiceName(userId); diff --git a/services/autofill/java/com/android/server/autofill/AutofillManagerServiceShellCommand.java b/services/autofill/java/com/android/server/autofill/AutofillManagerServiceShellCommand.java index cd6de8749f47..c66fb81f51c6 100644 --- a/services/autofill/java/com/android/server/autofill/AutofillManagerServiceShellCommand.java +++ b/services/autofill/java/com/android/server/autofill/AutofillManagerServiceShellCommand.java @@ -26,7 +26,6 @@ import android.os.RemoteCallback; import android.os.ShellCommand; import android.os.UserHandle; import android.service.autofill.AutofillFieldClassificationService.Scores; -import android.text.TextUtils; import android.view.autofill.AutofillManager; import com.android.internal.os.IResultReceiver; @@ -348,9 +347,7 @@ public final class AutofillManagerServiceShellCommand extends ShellCommand { private int isFieldDetectionServiceEnabled(PrintWriter pw) { final int userId = getNextIntArgRequired(); - String name = mService.getFieldDetectionServiceName(userId); - boolean pccFlagEnabled = mService.isPccClassificationFlagEnabled(); - boolean enabled = (!TextUtils.isEmpty(name)) && pccFlagEnabled; + boolean enabled = mService.isFieldDetectionServiceEnabledForUser(userId); pw.println(enabled); return 0; } diff --git a/services/autofill/java/com/android/server/autofill/InlineSuggestionRendorInfoCallbackOnResultListener.java b/services/autofill/java/com/android/server/autofill/InlineSuggestionRendorInfoCallbackOnResultListener.java new file mode 100644 index 000000000000..7351ef59feca --- /dev/null +++ b/services/autofill/java/com/android/server/autofill/InlineSuggestionRendorInfoCallbackOnResultListener.java @@ -0,0 +1,65 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.autofill; + +import android.annotation.Nullable; +import android.os.Bundle; +import android.os.RemoteCallback; +import android.util.Slog; +import android.view.autofill.AutofillId; +import android.view.inputmethod.InlineSuggestionsRequest; + +import java.lang.ref.WeakReference; +import java.util.function.Consumer; + +final class InlineSuggestionRendorInfoCallbackOnResultListener implements + RemoteCallback.OnResultListener{ + private static final String TAG = "InlineSuggestionRendorInfoCallbackOnResultListener"; + + private final int mRequestIdCopy; + private final AutofillId mFocusedId; + private final WeakReference<Session> mSessionWeakReference; + private final Consumer<InlineSuggestionsRequest> mInlineSuggestionsRequestConsumer; + + InlineSuggestionRendorInfoCallbackOnResultListener(WeakReference<Session> sessionWeakReference, + int requestIdCopy, + Consumer<InlineSuggestionsRequest> inlineSuggestionsRequestConsumer, + AutofillId focusedId) { + this.mRequestIdCopy = requestIdCopy; + this.mInlineSuggestionsRequestConsumer = inlineSuggestionsRequestConsumer; + this.mSessionWeakReference = sessionWeakReference; + this.mFocusedId = focusedId; + } + public void onResult(@Nullable Bundle result) { + Session session = this.mSessionWeakReference.get(); + if (session == null) { + Slog.wtf(TAG, "Session is null before trying to call onResult"); + return; + } + synchronized (session.mLock) { + if (session.mDestroyed) { + Slog.wtf(TAG, "Session is destroyed before trying to call onResult"); + return; + } + session.mInlineSessionController.onCreateInlineSuggestionsRequestLocked( + this.mFocusedId, + session.inlineSuggestionsRequestCacheDecorator( + this.mInlineSuggestionsRequestConsumer, this.mRequestIdCopy), + result); + } + } +} diff --git a/services/autofill/java/com/android/server/autofill/InlineSuggestionRequestConsumer.java b/services/autofill/java/com/android/server/autofill/InlineSuggestionRequestConsumer.java new file mode 100644 index 000000000000..a3efb25a5735 --- /dev/null +++ b/services/autofill/java/com/android/server/autofill/InlineSuggestionRequestConsumer.java @@ -0,0 +1,55 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.autofill; + +import android.util.Slog; +import android.view.inputmethod.InlineSuggestionsRequest; + +import java.lang.ref.WeakReference; +import java.util.function.Consumer; + +class InlineSuggestionRequestConsumer implements Consumer<InlineSuggestionsRequest> { + + static final String TAG = "InlineSuggestionRequestConsumer"; + + private final WeakReference<Session.AssistDataReceiverImpl> mAssistDataReceiverWeakReference; + private final WeakReference<ViewState> mViewStateWeakReference; + + InlineSuggestionRequestConsumer(WeakReference<Session.AssistDataReceiverImpl> + assistDataReceiverWeakReference, + WeakReference<ViewState> viewStateWeakReference) { + mAssistDataReceiverWeakReference = assistDataReceiverWeakReference; + mViewStateWeakReference = viewStateWeakReference; + } + + @Override + public void accept(InlineSuggestionsRequest inlineSuggestionsRequest) { + Session.AssistDataReceiverImpl assistDataReceiver = mAssistDataReceiverWeakReference.get(); + ViewState viewState = mViewStateWeakReference.get(); + if (assistDataReceiver == null) { + Slog.wtf(TAG, "assistDataReceiver is null when accepting new inline suggestion" + + "requests"); + return; + } + + if (viewState == null) { + Slog.wtf(TAG, "view state is null when accepting new inline suggestion requests"); + return; + } + assistDataReceiver.handleInlineSuggestionRequest(inlineSuggestionsRequest, viewState); + } +} diff --git a/services/autofill/java/com/android/server/autofill/Session.java b/services/autofill/java/com/android/server/autofill/Session.java index 0a8f474fd4cc..4576abb4a730 100644 --- a/services/autofill/java/com/android/server/autofill/Session.java +++ b/services/autofill/java/com/android/server/autofill/Session.java @@ -184,6 +184,7 @@ import com.android.server.wm.ActivityTaskManagerInternal; import java.io.PrintWriter; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; +import java.lang.ref.WeakReference; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; @@ -224,6 +225,7 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState private static final String EXTRA_REQUEST_ID = "android.service.autofill.extra.REQUEST_ID"; private static final String PCC_HINTS_DELIMITER = ","; + public static final String EXTRA_KEY_DETECTIONS = "detections"; final Object mLock; @@ -321,7 +323,7 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState * Id of the View currently being displayed. */ @GuardedBy("mLock") - @Nullable private AutofillId mCurrentViewId; + private @Nullable AutofillId mCurrentViewId; @GuardedBy("mLock") private IAutoFillManagerClient mClient; @@ -369,7 +371,7 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState private Bundle mClientState; @GuardedBy("mLock") - private boolean mDestroyed; + boolean mDestroyed; /** * Helper used to handle state of Save UI when it must be hiding to show a custom description @@ -448,7 +450,7 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState private ArrayList<AutofillId> mAugmentedAutofillableIds; @NonNull - private final AutofillInlineSessionController mInlineSessionController; + final AutofillInlineSessionController mInlineSessionController; /** * Receiver of assist data from the app's {@link Activity}. @@ -612,7 +614,7 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState * TODO(b/151867668): improve how asynchronous data dependencies are handled, without using * CountDownLatch. */ - private final class AssistDataReceiverImpl extends IAssistDataReceiver.Stub { + final class AssistDataReceiverImpl extends IAssistDataReceiver.Stub { @GuardedBy("mLock") private boolean mWaitForInlineRequest; @GuardedBy("mLock") @@ -627,17 +629,28 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState mPendingFillRequest = null; mWaitForInlineRequest = isInlineRequest; mPendingInlineSuggestionsRequest = null; - return isInlineRequest ? (inlineSuggestionsRequest) -> { - synchronized (mLock) { - if (!mWaitForInlineRequest || mPendingInlineSuggestionsRequest != null) { - return; - } - mWaitForInlineRequest = inlineSuggestionsRequest != null; - mPendingInlineSuggestionsRequest = inlineSuggestionsRequest; - maybeRequestFillLocked(); - viewState.resetState(ViewState.STATE_PENDING_CREATE_INLINE_REQUEST); + if (isInlineRequest) { + WeakReference<AssistDataReceiverImpl> assistDataReceiverWeakReference = + new WeakReference<AssistDataReceiverImpl>(this); + WeakReference<ViewState> viewStateWeakReference = + new WeakReference<ViewState>(viewState); + return new InlineSuggestionRequestConsumer(assistDataReceiverWeakReference, + viewStateWeakReference); + } + return null; + } + + void handleInlineSuggestionRequest(InlineSuggestionsRequest inlineSuggestionsRequest, + ViewState viewState) { + synchronized (mLock) { + if (!mWaitForInlineRequest || mPendingInlineSuggestionsRequest != null) { + return; } - } : null; + mWaitForInlineRequest = inlineSuggestionsRequest != null; + mPendingInlineSuggestionsRequest = inlineSuggestionsRequest; + maybeRequestFillLocked(); + viewState.resetState(ViewState.STATE_PENDING_CREATE_INLINE_REQUEST); + } } @GuardedBy("mLock") @@ -1224,24 +1237,30 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState final RemoteInlineSuggestionRenderService remoteRenderService = mService.getRemoteInlineSuggestionRenderServiceLocked(); if (mSessionFlags.mInlineSupportedByService - && remoteRenderService != null - && (isViewFocusedLocked(flags) || isRequestSupportFillDialog(flags))) { + && remoteRenderService != null + && (isViewFocusedLocked(flags) || isRequestSupportFillDialog(flags))) { + Consumer<InlineSuggestionsRequest> inlineSuggestionsRequestConsumer = mAssistReceiver.newAutofillRequestLocked(viewState, /* isInlineRequest= */ true); + if (inlineSuggestionsRequestConsumer != null) { - final AutofillId focusedId = mCurrentViewId; final int requestIdCopy = requestId; + final AutofillId focusedId = mCurrentViewId; + + WeakReference sessionWeakReference = new WeakReference<Session>(this); + InlineSuggestionRendorInfoCallbackOnResultListener + inlineSuggestionRendorInfoCallbackOnResultListener = + new InlineSuggestionRendorInfoCallbackOnResultListener( + sessionWeakReference, + requestIdCopy, + inlineSuggestionsRequestConsumer, + focusedId); + RemoteCallback inlineSuggestionRendorInfoCallback = new RemoteCallback( + inlineSuggestionRendorInfoCallbackOnResultListener, mHandler); + remoteRenderService.getInlineSuggestionsRendererInfo( - new RemoteCallback((extras) -> { - synchronized (mLock) { - mInlineSessionController.onCreateInlineSuggestionsRequestLocked( - focusedId, inlineSuggestionsRequestCacheDecorator( - inlineSuggestionsRequestConsumer, requestIdCopy), - extras); - } - }, mHandler) - ); + inlineSuggestionRendorInfoCallback); viewState.setState(ViewState.STATE_PENDING_CREATE_INLINE_REQUEST); } } else { @@ -3646,6 +3665,17 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState final ArrayList<FillContext> contexts = mergePreviousSessionLocked( /* forSave= */ true); + FieldClassificationResponse fieldClassificationResponse = + mClassificationState.mLastFieldClassificationResponse; + if (mService.isPccClassificationEnabled() + && fieldClassificationResponse != null + && !fieldClassificationResponse.getClassifications().isEmpty()) { + if (mClientState == null) { + mClientState = new Bundle(); + } + mClientState.putParcelableArrayList(EXTRA_KEY_DETECTIONS, new ArrayList<>( + fieldClassificationResponse.getClassifications())); + } final SaveRequest saveRequest = new SaveRequest(contexts, mClientState, mSelectedDatasetIds); mRemoteFillService.onSaveRequest(saveRequest); @@ -5151,7 +5181,7 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState } @NonNull - private Consumer<InlineSuggestionsRequest> inlineSuggestionsRequestCacheDecorator( + Consumer<InlineSuggestionsRequest> inlineSuggestionsRequestCacheDecorator( @NonNull Consumer<InlineSuggestionsRequest> consumer, int requestId) { return inlineSuggestionsRequest -> { consumer.accept(inlineSuggestionsRequest); diff --git a/services/core/java/com/android/server/am/ActiveServices.java b/services/core/java/com/android/server/am/ActiveServices.java index 31d60f25ef24..00dbb97191fb 100644 --- a/services/core/java/com/android/server/am/ActiveServices.java +++ b/services/core/java/com/android/server/am/ActiveServices.java @@ -3797,7 +3797,9 @@ public final class ActiveServices { } clientPsr.addConnection(c); c.startAssociationIfNeeded(); - if (c.hasFlag(Context.BIND_ABOVE_CLIENT)) { + // Don't set hasAboveClient if binding to self to prevent modifyRawOomAdj() from + // dropping the process' adjustment level. + if (b.client != s.app && c.hasFlag(Context.BIND_ABOVE_CLIENT)) { clientPsr.setHasAboveClient(true); } if (c.hasFlag(BIND_ALLOW_WHITELIST_MANAGEMENT)) { diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java index 3d02c96ae386..460ce4484a4b 100644 --- a/services/core/java/com/android/server/am/ActivityManagerService.java +++ b/services/core/java/com/android/server/am/ActivityManagerService.java @@ -3393,7 +3393,7 @@ public class ActivityManagerService extends IActivityManager.Stub mProcessList.noteAppKill(app, ApplicationExitInfo.REASON_OTHER, ApplicationExitInfo.SUBREASON_UNKNOWN, reason); } - ProcessList.killProcessGroup(app.uid, pid); + app.killProcessGroupIfNecessaryLocked(true); synchronized (mProcLock) { app.setKilled(true); } diff --git a/services/core/java/com/android/server/am/OomAdjuster.java b/services/core/java/com/android/server/am/OomAdjuster.java index 85a01851187e..6c3f01e6cd3a 100644 --- a/services/core/java/com/android/server/am/OomAdjuster.java +++ b/services/core/java/com/android/server/am/OomAdjuster.java @@ -3253,7 +3253,11 @@ public class OomAdjuster { // {@link SCHED_GROUP_TOP_APP}. We don't check render thread because it // is not ready when attaching. app.getWindowProcessController().onTopProcChanged(); - setThreadPriority(app.getPid(), THREAD_PRIORITY_TOP_APP_BOOST); + if (mService.mUseFifoUiScheduling) { + mService.scheduleAsFifoPriority(app.getPid(), true); + } else { + setThreadPriority(app.getPid(), THREAD_PRIORITY_TOP_APP_BOOST); + } initialSchedGroup = SCHED_GROUP_TOP_APP; } catch (Exception e) { Slog.w(TAG, "Failed to pre-set top priority to " + app + " " + e); diff --git a/services/core/java/com/android/server/am/ProcessRecord.java b/services/core/java/com/android/server/am/ProcessRecord.java index 335d6768c37b..d6495c78574c 100644 --- a/services/core/java/com/android/server/am/ProcessRecord.java +++ b/services/core/java/com/android/server/am/ProcessRecord.java @@ -1197,26 +1197,7 @@ class ProcessRecord implements WindowProcessListener { EventLog.writeEvent(EventLogTags.AM_KILL, userId, mPid, processName, mState.getSetAdj(), reason); Process.killProcessQuiet(mPid); - final boolean killProcessGroup; - if (mHostingRecord != null - && (mHostingRecord.usesWebviewZygote() || mHostingRecord.usesAppZygote())) { - synchronized (ProcessRecord.this) { - killProcessGroup = mProcessGroupCreated; - if (!killProcessGroup) { - // The process group hasn't been created, request to skip it. - mSkipProcessGroupCreation = true; - } - } - } else { - killProcessGroup = true; - } - if (killProcessGroup) { - if (asyncKPG) { - ProcessList.killProcessGroup(uid, mPid); - } else { - Process.sendSignalToProcessGroup(uid, mPid, OsConstants.SIGKILL); - } - } + killProcessGroupIfNecessaryLocked(asyncKPG); } else { mPendingStart = false; } @@ -1231,6 +1212,30 @@ class ProcessRecord implements WindowProcessListener { } } + @GuardedBy("mService") + void killProcessGroupIfNecessaryLocked(boolean async) { + final boolean killProcessGroup; + if (mHostingRecord != null + && (mHostingRecord.usesWebviewZygote() || mHostingRecord.usesAppZygote())) { + synchronized (ProcessRecord.this) { + killProcessGroup = mProcessGroupCreated; + if (!killProcessGroup) { + // The process group hasn't been created, request to skip it. + mSkipProcessGroupCreation = true; + } + } + } else { + killProcessGroup = true; + } + if (killProcessGroup) { + if (async) { + ProcessList.killProcessGroup(uid, mPid); + } else { + Process.sendSignalToProcessGroup(uid, mPid, OsConstants.SIGKILL); + } + } + } + @Override public void dumpDebug(ProtoOutputStream proto, long fieldId) { dumpDebug(proto, fieldId, -1); diff --git a/services/core/java/com/android/server/am/ProcessServiceRecord.java b/services/core/java/com/android/server/am/ProcessServiceRecord.java index 81d0b6ac700b..7ff6d116baaf 100644 --- a/services/core/java/com/android/server/am/ProcessServiceRecord.java +++ b/services/core/java/com/android/server/am/ProcessServiceRecord.java @@ -341,7 +341,8 @@ final class ProcessServiceRecord { mHasAboveClient = false; for (int i = mConnections.size() - 1; i >= 0; i--) { ConnectionRecord cr = mConnections.valueAt(i); - if (cr.hasFlag(Context.BIND_ABOVE_CLIENT)) { + if (cr.binding.service.app.mServices != this + && cr.hasFlag(Context.BIND_ABOVE_CLIENT)) { mHasAboveClient = true; break; } diff --git a/services/core/java/com/android/server/am/UserController.java b/services/core/java/com/android/server/am/UserController.java index 12bb5d23e4cf..76a994ec63e9 100644 --- a/services/core/java/com/android/server/am/UserController.java +++ b/services/core/java/com/android/server/am/UserController.java @@ -151,6 +151,8 @@ import java.util.Arrays; import java.util.Iterator; import java.util.List; import java.util.Objects; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicInteger; import java.util.function.Consumer; @@ -1707,7 +1709,8 @@ class UserController implements Handler.Callback { mInjector.getWindowManager().setSwitchingUser(true); // Only lock if the user has a secure keyguard PIN/Pattern/Pwd if (mInjector.getKeyguardManager().isDeviceSecure(userId)) { - mInjector.getWindowManager().lockNow(null); + // Make sure the device is locked before moving on with the user switch + mInjector.lockDeviceNowAndWaitForKeyguardShown(); } } @@ -3444,6 +3447,11 @@ class UserController implements Handler.Callback { WindowManagerService getWindowManager() { return mService.mWindowManager; } + + ActivityTaskManagerInternal getActivityTaskManagerInternal() { + return mService.mAtmInternal; + } + void activityManagerOnUserStopped(@UserIdInt int userId) { LocalServices.getService(ActivityTaskManagerInternal.class).onUserStopped(userId); } @@ -3667,5 +3675,43 @@ class UserController implements Handler.Callback { void onSystemUserVisibilityChanged(boolean visible) { getUserManagerInternal().onSystemUserVisibilityChanged(visible); } + + void lockDeviceNowAndWaitForKeyguardShown() { + if (getWindowManager().isKeyguardLocked()) { + return; + } + + final TimingsTraceAndSlog t = new TimingsTraceAndSlog(); + t.traceBegin("lockDeviceNowAndWaitForKeyguardShown"); + + final CountDownLatch latch = new CountDownLatch(1); + ActivityTaskManagerInternal.ScreenObserver screenObserver = + new ActivityTaskManagerInternal.ScreenObserver() { + @Override + public void onAwakeStateChanged(boolean isAwake) { + + } + + @Override + public void onKeyguardStateChanged(boolean isShowing) { + if (isShowing) { + latch.countDown(); + } + } + }; + + getActivityTaskManagerInternal().registerScreenObserver(screenObserver); + getWindowManager().lockDeviceNow(); + try { + if (!latch.await(20, TimeUnit.SECONDS)) { + throw new RuntimeException("Keyguard is not shown in 20 seconds"); + } + } catch (InterruptedException e) { + throw new RuntimeException(e); + } finally { + getActivityTaskManagerInternal().unregisterScreenObserver(screenObserver); + t.traceEnd(); + } + } } } diff --git a/services/core/java/com/android/server/biometrics/log/BiometricContextProvider.java b/services/core/java/com/android/server/biometrics/log/BiometricContextProvider.java index 21ade1bbbc4a..fc3d7c8114b0 100644 --- a/services/core/java/com/android/server/biometrics/log/BiometricContextProvider.java +++ b/services/core/java/com/android/server/biometrics/log/BiometricContextProvider.java @@ -156,7 +156,7 @@ public final class BiometricContextProvider implements BiometricContext { @Override public OperationContextExt updateContext(@NonNull OperationContextExt operationContext, boolean isCryptoOperation) { - return operationContext.update(this); + return operationContext.update(this, isCryptoOperation); } @Nullable @@ -238,7 +238,7 @@ public final class BiometricContextProvider implements BiometricContext { private void notifySubscribers() { mSubscribers.forEach((context, consumer) -> { - consumer.accept(context.update(this).toAidlContext()); + consumer.accept(context.update(this, context.isCrypto()).toAidlContext()); }); } diff --git a/services/core/java/com/android/server/biometrics/log/OperationContextExt.java b/services/core/java/com/android/server/biometrics/log/OperationContextExt.java index 4d821e9819b4..4a10e8e428a3 100644 --- a/services/core/java/com/android/server/biometrics/log/OperationContextExt.java +++ b/services/core/java/com/android/server/biometrics/log/OperationContextExt.java @@ -241,9 +241,10 @@ public class OperationContextExt { } /** Update this object with the latest values from the given context. */ - OperationContextExt update(@NonNull BiometricContext biometricContext) { + OperationContextExt update(@NonNull BiometricContext biometricContext, boolean isCrypto) { mAidlContext.isAod = biometricContext.isAod(); mAidlContext.displayState = toAidlDisplayState(biometricContext.getDisplayState()); + mAidlContext.isCrypto = isCrypto; setFirstSessionId(biometricContext); mIsDisplayOn = biometricContext.isDisplayOn(); diff --git a/services/core/java/com/android/server/biometrics/sensors/BiometricScheduler.java b/services/core/java/com/android/server/biometrics/sensors/BiometricScheduler.java index b474cad962c3..aa5f9fa96875 100644 --- a/services/core/java/com/android/server/biometrics/sensors/BiometricScheduler.java +++ b/services/core/java/com/android/server/biometrics/sensors/BiometricScheduler.java @@ -32,6 +32,7 @@ import android.util.Slog; import android.util.proto.ProtoOutputStream; import com.android.internal.annotations.VisibleForTesting; +import com.android.modules.expresslog.Counter; import com.android.server.biometrics.BiometricSchedulerProto; import com.android.server.biometrics.BiometricsProto; import com.android.server.biometrics.sensors.fingerprint.GestureAvailabilityDispatcher; @@ -569,9 +570,10 @@ public class BiometricScheduler { if (mCurrentOperation == null) { return; } - final BiometricSchedulerOperation mOperation = mCurrentOperation; + final BiometricSchedulerOperation operation = mCurrentOperation; mHandler.postDelayed(() -> { - if (mOperation == mCurrentOperation) { + if (operation == mCurrentOperation) { + Counter.logIncrement("biometric.scheduler_watchdog_triggered_count"); clearScheduler(); } }, 10000); diff --git a/services/core/java/com/android/server/biometrics/sensors/face/FaceService.java b/services/core/java/com/android/server/biometrics/sensors/face/FaceService.java index c2994a953f31..6f26e7b3e4b4 100644 --- a/services/core/java/com/android/server/biometrics/sensors/face/FaceService.java +++ b/services/core/java/com/android/server/biometrics/sensors/face/FaceService.java @@ -415,6 +415,11 @@ public class FaceService extends SystemService { } } } + + @Override + public void onError(int error, int vendorCode) throws RemoteException { + receiver.onError(error, vendorCode); + } }; // This effectively iterates through all sensors, but has to do so by finding all diff --git a/services/core/java/com/android/server/connectivity/Vpn.java b/services/core/java/com/android/server/connectivity/Vpn.java index 4208a12f91d4..d4bb445d66ac 100644 --- a/services/core/java/com/android/server/connectivity/Vpn.java +++ b/services/core/java/com/android/server/connectivity/Vpn.java @@ -3828,10 +3828,27 @@ public class Vpn { }, retryDelayMs, TimeUnit.MILLISECONDS); } + private boolean significantCapsChange(@Nullable final NetworkCapabilities left, + @Nullable final NetworkCapabilities right) { + if (left == right) return false; + return null == left + || null == right + || !Arrays.equals(left.getTransportTypes(), right.getTransportTypes()) + || !Arrays.equals(left.getCapabilities(), right.getCapabilities()) + || !Arrays.equals(left.getEnterpriseIds(), right.getEnterpriseIds()) + || !Objects.equals(left.getTransportInfo(), right.getTransportInfo()) + || !Objects.equals(left.getAllowedUids(), right.getAllowedUids()) + || !Objects.equals(left.getUnderlyingNetworks(), right.getUnderlyingNetworks()) + || !Objects.equals(left.getNetworkSpecifier(), right.getNetworkSpecifier()); + } + /** Called when the NetworkCapabilities of underlying network is changed */ public void onDefaultNetworkCapabilitiesChanged(@NonNull NetworkCapabilities nc) { - mEventChanges.log("[UnderlyingNW] Cap changed from " - + mUnderlyingNetworkCapabilities + " to " + nc); + if (significantCapsChange(mUnderlyingNetworkCapabilities, nc)) { + // TODO : make this log terser + mEventChanges.log("[UnderlyingNW] Cap changed from " + + mUnderlyingNetworkCapabilities + " to " + nc); + } final NetworkCapabilities oldNc = mUnderlyingNetworkCapabilities; mUnderlyingNetworkCapabilities = nc; if (oldNc == null || !nc.getSubscriptionIds().equals(oldNc.getSubscriptionIds())) { diff --git a/services/core/java/com/android/server/display/AutomaticBrightnessController.java b/services/core/java/com/android/server/display/AutomaticBrightnessController.java index 774087c63f0f..75709fbb365a 100644 --- a/services/core/java/com/android/server/display/AutomaticBrightnessController.java +++ b/services/core/java/com/android/server/display/AutomaticBrightnessController.java @@ -333,11 +333,8 @@ public class AutomaticBrightnessController { // Initialize to active (normal) screen brightness mode switchToInteractiveScreenBrightnessMode(); - if (userLux != BrightnessMappingStrategy.NO_USER_LUX - && userBrightness != BrightnessMappingStrategy.NO_USER_BRIGHTNESS) { - // Use the given short-term model - setScreenBrightnessByUser(userLux, userBrightness); - } + // Use the given short-term model + setScreenBrightnessByUser(userLux, userBrightness); } /** @@ -520,6 +517,10 @@ public class AutomaticBrightnessController { } private boolean setScreenBrightnessByUser(float lux, float brightness) { + if (lux == BrightnessMappingStrategy.NO_USER_LUX + || brightness == BrightnessMappingStrategy.NO_USER_BRIGHTNESS) { + return false; + } mCurrentBrightnessMapper.addUserDataPoint(lux, brightness); mShortTermModel.setUserBrightness(lux, brightness); return true; @@ -1234,14 +1235,14 @@ public class AutomaticBrightnessController { // light. // The anchor determines what were the light levels when the user has set their preference, // and we use a relative threshold to determine when to revert to the OEM curve. - private float mAnchor = -1f; - private float mBrightness; - private boolean mIsValid = true; + private float mAnchor = BrightnessMappingStrategy.NO_USER_LUX; + private float mBrightness = BrightnessMappingStrategy.NO_USER_BRIGHTNESS; + private boolean mIsValid = false; private void reset() { - mAnchor = -1f; - mBrightness = -1f; - mIsValid = true; + mAnchor = BrightnessMappingStrategy.NO_USER_LUX; + mBrightness = BrightnessMappingStrategy.NO_USER_BRIGHTNESS; + mIsValid = false; } private void invalidate() { diff --git a/services/core/java/com/android/server/hdmi/HdmiControlService.java b/services/core/java/com/android/server/hdmi/HdmiControlService.java index be9df4aecf43..2c54e1cea3fa 100644 --- a/services/core/java/com/android/server/hdmi/HdmiControlService.java +++ b/services/core/java/com/android/server/hdmi/HdmiControlService.java @@ -4725,13 +4725,11 @@ public class HdmiControlService extends SystemService { Slog.w(TAG, "Tried to update eARC status on a port that doesn't support eARC."); return; } - // If eARC is disabled, the local device is null. In this case, the HAL shouldn't have - // reported connection state changes, but even if it did, it won't take effect. if (mEarcLocalDevice != null) { mEarcLocalDevice.handleEarcStateChange(status); } else if (status == HDMI_EARC_STATUS_ARC_PENDING) { - // If the local device is null we notify the Audio Service that eARC connection - // is disabled. + // If eARC is disabled, the local device is null. This is why we notify + // AudioService here that the eARC connection is terminated. notifyEarcStatusToAudioService(false, new ArrayList<>()); startArcAction(true, null); } diff --git a/services/core/java/com/android/server/input/InputManagerService.java b/services/core/java/com/android/server/input/InputManagerService.java index 662591e3d264..4cb22dbcf308 100644 --- a/services/core/java/com/android/server/input/InputManagerService.java +++ b/services/core/java/com/android/server/input/InputManagerService.java @@ -500,8 +500,6 @@ public class InputManagerService extends IInputManager.Stub // Add ourselves to the Watchdog monitors. Watchdog.getInstance().addMonitor(this); - - mSettingsObserver.registerAndUpdate(); } // TODO(BT) Pass in parameter for bluetooth system @@ -512,6 +510,8 @@ public class InputManagerService extends IInputManager.Stub mDisplayManagerInternal = LocalServices.getService(DisplayManagerInternal.class); + mSettingsObserver.registerAndUpdate(); + synchronized (mLidSwitchLock) { mSystemReady = true; diff --git a/services/core/java/com/android/server/media/MediaRoute2Provider.java b/services/core/java/com/android/server/media/MediaRoute2Provider.java index c076c0574afe..c59b73350865 100644 --- a/services/core/java/com/android/server/media/MediaRoute2Provider.java +++ b/services/core/java/com/android/server/media/MediaRoute2Provider.java @@ -31,6 +31,7 @@ import java.io.PrintWriter; import java.util.ArrayList; import java.util.List; import java.util.Objects; +import java.util.Set; abstract class MediaRoute2Provider { final ComponentName mComponentName; @@ -56,7 +57,9 @@ abstract class MediaRoute2Provider { public abstract void requestCreateSession(long requestId, String packageName, String routeId, @Nullable Bundle sessionHints); public abstract void releaseSession(long requestId, String sessionId); - public abstract void updateDiscoveryPreference(RouteDiscoveryPreference discoveryPreference); + + public abstract void updateDiscoveryPreference( + Set<String> activelyScanningPackages, RouteDiscoveryPreference discoveryPreference); public abstract void selectRoute(long requestId, String sessionId, String routeId); public abstract void deselectRoute(long requestId, String sessionId, String routeId); diff --git a/services/core/java/com/android/server/media/MediaRoute2ProviderServiceProxy.java b/services/core/java/com/android/server/media/MediaRoute2ProviderServiceProxy.java index 72b843672ae8..3cf0786b7e28 100644 --- a/services/core/java/com/android/server/media/MediaRoute2ProviderServiceProxy.java +++ b/services/core/java/com/android/server/media/MediaRoute2ProviderServiceProxy.java @@ -49,6 +49,7 @@ import java.util.ArrayList; import java.util.Collections; import java.util.List; import java.util.Objects; +import java.util.Set; /** * Maintains a connection to a particular {@link MediaRoute2ProviderService}. @@ -61,6 +62,7 @@ final class MediaRoute2ProviderServiceProxy extends MediaRoute2Provider private final Context mContext; private final int mUserId; private final Handler mHandler; + private final boolean mIsSelfScanOnlyProvider; // Connection state private boolean mRunning; @@ -70,14 +72,19 @@ final class MediaRoute2ProviderServiceProxy extends MediaRoute2Provider private boolean mIsManagerScanning; private RouteDiscoveryPreference mLastDiscoveryPreference = null; + private boolean mLastDiscoveryPreferenceIncludesThisPackage = false; @GuardedBy("mLock") final List<RoutingSessionInfo> mReleasingSessions = new ArrayList<>(); - MediaRoute2ProviderServiceProxy(@NonNull Context context, @NonNull ComponentName componentName, + MediaRoute2ProviderServiceProxy( + @NonNull Context context, + @NonNull ComponentName componentName, + boolean isSelfScanOnlyProvider, int userId) { super(componentName); mContext = Objects.requireNonNull(context, "Context must not be null."); + mIsSelfScanOnlyProvider = isSelfScanOnlyProvider; mUserId = userId; mHandler = new Handler(Looper.myLooper()); } @@ -107,8 +114,11 @@ final class MediaRoute2ProviderServiceProxy extends MediaRoute2Provider } @Override - public void updateDiscoveryPreference(RouteDiscoveryPreference discoveryPreference) { + public void updateDiscoveryPreference( + Set<String> activelyScanningPackages, RouteDiscoveryPreference discoveryPreference) { mLastDiscoveryPreference = discoveryPreference; + mLastDiscoveryPreferenceIncludesThisPackage = + activelyScanningPackages.contains(mComponentName.getPackageName()); if (mConnectionReady) { mActiveConnection.updateDiscoveryPreference(discoveryPreference); } @@ -209,11 +219,15 @@ final class MediaRoute2ProviderServiceProxy extends MediaRoute2Provider private boolean shouldBind() { if (mRunning) { - // Bind when there is a discovery preference or an active route session. - return (mLastDiscoveryPreference != null - && !mLastDiscoveryPreference.getPreferredFeatures().isEmpty()) - || !getSessionInfos().isEmpty() - || mIsManagerScanning; + boolean shouldBind = + mLastDiscoveryPreference != null + && !mLastDiscoveryPreference.getPreferredFeatures().isEmpty(); + if (mIsSelfScanOnlyProvider) { + shouldBind &= mLastDiscoveryPreferenceIncludesThisPackage; + } + shouldBind |= mIsManagerScanning; + shouldBind |= !getSessionInfos().isEmpty(); + return shouldBind; } return false; } @@ -301,7 +315,11 @@ final class MediaRoute2ProviderServiceProxy extends MediaRoute2Provider if (mActiveConnection == connection) { mConnectionReady = true; if (mLastDiscoveryPreference != null) { - updateDiscoveryPreference(mLastDiscoveryPreference); + updateDiscoveryPreference( + mLastDiscoveryPreferenceIncludesThisPackage + ? Set.of(mComponentName.getPackageName()) + : Set.of(), + mLastDiscoveryPreference); } } } diff --git a/services/core/java/com/android/server/media/MediaRoute2ProviderWatcher.java b/services/core/java/com/android/server/media/MediaRoute2ProviderWatcher.java index 46bccaf314d9..bd252e7fdfd2 100644 --- a/services/core/java/com/android/server/media/MediaRoute2ProviderWatcher.java +++ b/services/core/java/com/android/server/media/MediaRoute2ProviderWatcher.java @@ -16,6 +16,8 @@ package com.android.server.media; +import static android.content.pm.PackageManager.GET_RESOLVED_FILTER; + import android.annotation.NonNull; import android.content.BroadcastReceiver; import android.content.ComponentName; @@ -34,6 +36,7 @@ import android.util.Slog; import java.io.PrintWriter; import java.util.ArrayList; import java.util.Collections; +import java.util.Iterator; /** * Watches changes of packages, or scan them for finding media route providers. @@ -41,8 +44,8 @@ import java.util.Collections; final class MediaRoute2ProviderWatcher { private static final String TAG = "MR2ProviderWatcher"; private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG); - private static final PackageManager.ResolveInfoFlags RESOLVE_INFO_FLAGS_NONE = - PackageManager.ResolveInfoFlags.of(0); + private static final PackageManager.ResolveInfoFlags RESOLVE_INFO_FLAGS = + PackageManager.ResolveInfoFlags.of(GET_RESOLVED_FILTER); private final Context mContext; private final Callback mCallback; @@ -118,16 +121,26 @@ final class MediaRoute2ProviderWatcher { int targetIndex = 0; Intent intent = new Intent(MediaRoute2ProviderService.SERVICE_INTERFACE); for (ResolveInfo resolveInfo : - mPackageManager.queryIntentServicesAsUser( - intent, RESOLVE_INFO_FLAGS_NONE, mUserId)) { + mPackageManager.queryIntentServicesAsUser(intent, RESOLVE_INFO_FLAGS, mUserId)) { ServiceInfo serviceInfo = resolveInfo.serviceInfo; if (serviceInfo != null) { + boolean isSelfScanOnlyProvider = false; + Iterator<String> categoriesIterator = resolveInfo.filter.categoriesIterator(); + if (categoriesIterator != null) { + while (categoriesIterator.hasNext()) { + isSelfScanOnlyProvider |= + MediaRoute2ProviderService.CATEGORY_SELF_SCAN_ONLY.equals( + categoriesIterator.next()); + } + } int sourceIndex = findProvider(serviceInfo.packageName, serviceInfo.name); if (sourceIndex < 0) { MediaRoute2ProviderServiceProxy proxy = - new MediaRoute2ProviderServiceProxy(mContext, - new ComponentName(serviceInfo.packageName, serviceInfo.name), - mUserId); + new MediaRoute2ProviderServiceProxy( + mContext, + new ComponentName(serviceInfo.packageName, serviceInfo.name), + isSelfScanOnlyProvider, + mUserId); proxy.start(); mProxies.add(targetIndex++, proxy); mCallback.onAddProviderService(proxy); diff --git a/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java b/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java index b79991e5d013..1059c1981ce5 100644 --- a/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java +++ b/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java @@ -1478,6 +1478,7 @@ class MediaRouter2ServiceImpl { final ArrayList<RouterRecord> mRouterRecords = new ArrayList<>(); final ArrayList<ManagerRecord> mManagerRecords = new ArrayList<>(); RouteDiscoveryPreference mCompositeDiscoveryPreference = RouteDiscoveryPreference.EMPTY; + Set<String> mActivelyScanningPackages = Set.of(); final UserHandler mHandler; UserRecord(int userId) { @@ -1525,7 +1526,12 @@ class MediaRouter2ServiceImpl { pw.println(indent + "<no manager records>"); } - mCompositeDiscoveryPreference.dump(pw, indent); + pw.println(indent + "Composite discovery preference:"); + mCompositeDiscoveryPreference.dump(pw, indent + " "); + pw.println( + indent + + "Packages actively scanning: " + + String.join(", ", mActivelyScanningPackages)); if (!mHandler.runWithScissors(() -> mHandler.dump(pw, indent), 1000)) { pw.println(indent + "<could not dump handler state>"); @@ -1834,7 +1840,9 @@ class MediaRouter2ServiceImpl { public void onAddProviderService(@NonNull MediaRoute2ProviderServiceProxy proxy) { proxy.setCallback(this); mRouteProviders.add(proxy); - proxy.updateDiscoveryPreference(mUserRecord.mCompositeDiscoveryPreference); + proxy.updateDiscoveryPreference( + mUserRecord.mActivelyScanningPackages, + mUserRecord.mCompositeDiscoveryPreference); } @Override @@ -2341,8 +2349,8 @@ class MediaRouter2ServiceImpl { return; } notifySessionInfoChangedToRouters(getRouterRecords(true), sessionInfo); - notifySessionInfoChangedToRouters(getRouterRecords(false), - mSystemProvider.getDefaultSessionInfo()); + notifySessionInfoChangedToRouters( + getRouterRecords(false), mSystemProvider.getDefaultSessionInfo()); return; } @@ -2711,8 +2719,8 @@ class MediaRouter2ServiceImpl { if (service == null) { return; } - List<RouteDiscoveryPreference> discoveryPreferences = Collections.emptyList(); - List<RouterRecord> routerRecords = getRouterRecords(); + List<RouterRecord> activeRouterRecords = Collections.emptyList(); + List<RouterRecord> allRouterRecords = getRouterRecords(); List<ManagerRecord> managerRecords = getManagerRecords(); boolean isManagerScanning = false; @@ -2723,15 +2731,16 @@ class MediaRouter2ServiceImpl { <= sPackageImportanceForScanning); if (isManagerScanning) { - discoveryPreferences = routerRecords.stream() - .map(record -> record.mDiscoveryPreference) - .collect(Collectors.toList()); + activeRouterRecords = allRouterRecords; } else { - discoveryPreferences = routerRecords.stream().filter(record -> - service.mActivityManager.getPackageImportance(record.mPackageName) - <= sPackageImportanceForScanning) - .map(record -> record.mDiscoveryPreference) - .collect(Collectors.toList()); + activeRouterRecords = + allRouterRecords.stream() + .filter( + record -> + service.mActivityManager.getPackageImportance( + record.mPackageName) + <= sPackageImportanceForScanning) + .collect(Collectors.toList()); } } @@ -2748,22 +2757,30 @@ class MediaRouter2ServiceImpl { // to query route providers once to obtain all of the routes of interest, which // can be subsequently filtered for the individual discovery preferences. Set<String> preferredFeatures = new HashSet<>(); + Set<String> activelyScanningPackages = new HashSet<>(); boolean activeScan = false; - for (RouteDiscoveryPreference preference : discoveryPreferences) { + for (RouterRecord activeRouterRecord : activeRouterRecords) { + RouteDiscoveryPreference preference = activeRouterRecord.mDiscoveryPreference; preferredFeatures.addAll(preference.getPreferredFeatures()); - activeScan |= preference.shouldPerformActiveScan(); + if (preference.shouldPerformActiveScan()) { + activeScan = true; + activelyScanningPackages.add(activeRouterRecord.mPackageName); + } } RouteDiscoveryPreference newPreference = new RouteDiscoveryPreference.Builder( List.copyOf(preferredFeatures), activeScan || isManagerScanning).build(); synchronized (service.mLock) { - if (newPreference.equals(mUserRecord.mCompositeDiscoveryPreference)) { + if (newPreference.equals(mUserRecord.mCompositeDiscoveryPreference) + && activelyScanningPackages.equals(mUserRecord.mActivelyScanningPackages)) { return; } mUserRecord.mCompositeDiscoveryPreference = newPreference; + mUserRecord.mActivelyScanningPackages = activelyScanningPackages; } for (MediaRoute2Provider provider : mRouteProviders) { - provider.updateDiscoveryPreference(mUserRecord.mCompositeDiscoveryPreference); + provider.updateDiscoveryPreference( + activelyScanningPackages, mUserRecord.mCompositeDiscoveryPreference); } } diff --git a/services/core/java/com/android/server/media/SystemMediaRoute2Provider.java b/services/core/java/com/android/server/media/SystemMediaRoute2Provider.java index 426bc5eed051..f4e6abd47b44 100644 --- a/services/core/java/com/android/server/media/SystemMediaRoute2Provider.java +++ b/services/core/java/com/android/server/media/SystemMediaRoute2Provider.java @@ -42,6 +42,7 @@ import com.android.internal.annotations.GuardedBy; import java.util.List; import java.util.Objects; +import java.util.Set; /** * Provides routes for local playbacks such as phone speaker, wired headset, or Bluetooth speakers. @@ -196,7 +197,8 @@ class SystemMediaRoute2Provider extends MediaRoute2Provider { } @Override - public void updateDiscoveryPreference(RouteDiscoveryPreference discoveryPreference) { + public void updateDiscoveryPreference( + Set<String> activelyScanningPackages, RouteDiscoveryPreference discoveryPreference) { // Do nothing } diff --git a/services/core/java/com/android/server/policy/PhoneWindowManager.java b/services/core/java/com/android/server/policy/PhoneWindowManager.java index d7eff52af9b4..9c780bb90b22 100644 --- a/services/core/java/com/android/server/policy/PhoneWindowManager.java +++ b/services/core/java/com/android/server/policy/PhoneWindowManager.java @@ -1086,6 +1086,8 @@ public class PhoneWindowManager implements WindowManagerPolicy { final DreamManagerInternal dreamManagerInternal = getDreamManagerInternal(); if (dreamManagerInternal == null || !dreamManagerInternal.canStartDreaming(isScreenOn)) { + Slog.d(TAG, "Can't start dreaming when attempting to dream from short power" + + " press (isScreenOn=" + isScreenOn + ")"); noDreamAction.run(); return; } @@ -6010,6 +6012,12 @@ public class PhoneWindowManager implements WindowManagerPolicy { } @Override + @WindowManagerFuncs.LidState + public int getLidState() { + return mDefaultDisplayPolicy.getLidState(); + } + + @Override public void dumpDebug(ProtoOutputStream proto, long fieldId) { final long token = proto.start(fieldId); proto.write(ROTATION_MODE, mDefaultDisplayRotation.getUserRotationMode()); diff --git a/services/core/java/com/android/server/policy/WindowManagerPolicy.java b/services/core/java/com/android/server/policy/WindowManagerPolicy.java index 887f9461bdce..3da78123016b 100644 --- a/services/core/java/com/android/server/policy/WindowManagerPolicy.java +++ b/services/core/java/com/android/server/policy/WindowManagerPolicy.java @@ -218,6 +218,14 @@ public interface WindowManagerPolicy extends WindowManagerPolicyConstants { * between it and the policy. */ public interface WindowManagerFuncs { + @IntDef(prefix = { "LID_" }, value = { + LID_ABSENT, + LID_CLOSED, + LID_OPEN, + }) + @Retention(RetentionPolicy.SOURCE) + @interface LidState{} + public static final int LID_ABSENT = -1; public static final int LID_CLOSED = 0; public static final int LID_OPEN = 1; @@ -231,8 +239,9 @@ public interface WindowManagerPolicy extends WindowManagerPolicyConstants { public static final int CAMERA_LENS_COVERED = 1; /** - * Returns a code that describes the current state of the lid switch. + * Returns a {@link LidState} that describes the current state of the lid switch. */ + @LidState public int getLidState(); /** @@ -282,7 +291,7 @@ public interface WindowManagerPolicy extends WindowManagerPolicyConstants { /** * Convert the lid state to a human readable format. */ - static String lidStateToString(int lid) { + static String lidStateToString(@LidState int lid) { switch (lid) { case LID_ABSENT: return "LID_ABSENT"; @@ -1241,4 +1250,11 @@ public interface WindowManagerPolicy extends WindowManagerPolicyConstants { * @return {@code true} if the key will be handled globally. */ boolean isGlobalKey(int keyCode); + + /** + * Returns a {@link WindowManagerFuncs.LidState} that describes the current state of + * the lid switch. + */ + @WindowManagerFuncs.LidState + int getLidState(); } diff --git a/services/core/java/com/android/server/power/PowerManagerService.java b/services/core/java/com/android/server/power/PowerManagerService.java index b8c5b3f5524a..712be36e51c6 100644 --- a/services/core/java/com/android/server/power/PowerManagerService.java +++ b/services/core/java/com/android/server/power/PowerManagerService.java @@ -33,6 +33,7 @@ import static android.os.PowerManagerInternal.isInteractive; import static android.os.PowerManagerInternal.wakefulnessToString; import static com.android.internal.util.LatencyTracker.ACTION_TURN_ON_SCREEN; +import static com.android.server.policy.WindowManagerPolicy.WindowManagerFuncs.LID_CLOSED; import android.annotation.IntDef; import android.annotation.NonNull; @@ -5733,6 +5734,11 @@ public final class PowerManagerService extends SystemService @Override // Binder call public void wakeUp(long eventTime, @WakeReason int reason, String details, String opPackageName) { + if (mPolicy.getLidState() == LID_CLOSED) { + Slog.d(TAG, "Ignoring wake up call due to the lid being closed"); + return; + } + final long now = mClock.uptimeMillis(); if (eventTime > now) { Slog.e(TAG, "Event time " + eventTime + " cannot be newer than " + now); diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java index 3db031510317..2b2100e56f44 100644 --- a/services/core/java/com/android/server/wm/ActivityRecord.java +++ b/services/core/java/com/android/server/wm/ActivityRecord.java @@ -5630,11 +5630,18 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A setClientVisible(visible); } + final DisplayContent displayContent = getDisplayContent(); if (!visible) { mImeInsetsFrozenUntilStartInput = true; + if (usingShellTransitions) { + final WindowState wallpaperTarget = + displayContent.mWallpaperController.getWallpaperTarget(); + if (wallpaperTarget != null && wallpaperTarget.mActivityRecord == this) { + displayContent.mWallpaperController.hideWallpapers(wallpaperTarget); + } + } } - final DisplayContent displayContent = getDisplayContent(); if (!displayContent.mClosingApps.contains(this) && !displayContent.mOpeningApps.contains(this) && !fromTransition) { diff --git a/services/core/java/com/android/server/wm/ActivityStarter.java b/services/core/java/com/android/server/wm/ActivityStarter.java index 3e3eb570d0e3..0ebec11ea36f 100644 --- a/services/core/java/com/android/server/wm/ActivityStarter.java +++ b/services/core/java/com/android/server/wm/ActivityStarter.java @@ -1821,7 +1821,8 @@ class ActivityStarter { // If Activity's launching into PiP, move the mStartActivity immediately to pinned mode. // Note that mStartActivity and source should be in the same Task at this point. if (mOptions != null && mOptions.isLaunchIntoPip() - && sourceRecord != null && sourceRecord.getTask() == mStartActivity.getTask()) { + && sourceRecord != null && sourceRecord.getTask() == mStartActivity.getTask() + && balCode != BAL_BLOCK) { mRootWindowContainer.moveActivityToPinnedRootTask(mStartActivity, sourceRecord, "launch-into-pip"); } diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerInternal.java b/services/core/java/com/android/server/wm/ActivityTaskManagerInternal.java index e07c65426278..32f1f42aacb9 100644 --- a/services/core/java/com/android/server/wm/ActivityTaskManagerInternal.java +++ b/services/core/java/com/android/server/wm/ActivityTaskManagerInternal.java @@ -262,9 +262,17 @@ public abstract class ActivityTaskManagerInternal { */ public abstract void setVr2dDisplayId(int vr2dDisplayId); + /** + * Registers a {@link ScreenObserver}. + */ public abstract void registerScreenObserver(ScreenObserver observer); /** + * Unregisters the given {@link ScreenObserver}. + */ + public abstract void unregisterScreenObserver(ScreenObserver observer); + + /** * Returns is the caller has the same uid as the Recents component */ public abstract boolean isCallerRecents(int callingUid); diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java index 750ed986f567..a650d7709b67 100644 --- a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java +++ b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java @@ -296,6 +296,7 @@ import java.text.DateFormat; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; +import java.util.Collections; import java.util.Date; import java.util.HashSet; import java.util.List; @@ -652,7 +653,8 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub { */ float mMinPercentageMultiWindowSupportWidth; - final List<ActivityTaskManagerInternal.ScreenObserver> mScreenObservers = new ArrayList<>(); + final List<ActivityTaskManagerInternal.ScreenObserver> mScreenObservers = + Collections.synchronizedList(new ArrayList<>()); // VR Vr2d Display Id. int mVr2dDisplayId = INVALID_DISPLAY; @@ -3558,10 +3560,10 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub { mRootWindowContainer.forAllDisplays(displayContent -> { mKeyguardController.keyguardGoingAway(displayContent.getDisplayId(), flags); }); - WallpaperManagerInternal wallpaperManagerInternal = getWallpaperManagerInternal(); - if (wallpaperManagerInternal != null) { - wallpaperManagerInternal.onKeyguardGoingAway(); - } + } + WallpaperManagerInternal wallpaperManagerInternal = getWallpaperManagerInternal(); + if (wallpaperManagerInternal != null) { + wallpaperManagerInternal.onKeyguardGoingAway(); } } finally { Binder.restoreCallingIdentity(token); @@ -3649,6 +3651,8 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub { Slog.e(TAG, "Skip enterPictureInPictureMode, destroyed " + r); return; } + EventLogTags.writeWmEnterPip(r.mUserId, System.identityHashCode(r), + r.shortComponentName, Boolean.toString(isAutoEnter)); r.setPictureInPictureParams(params); r.mAutoEnteringPip = isAutoEnter; mRootWindowContainer.moveActivityToPinnedRootTask(r, @@ -5867,6 +5871,11 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub { } @Override + public void unregisterScreenObserver(ScreenObserver observer) { + mScreenObservers.remove(observer); + } + + @Override public boolean isCallerRecents(int callingUid) { return ActivityTaskManagerService.this.isCallerRecents(callingUid); } diff --git a/services/core/java/com/android/server/wm/BackgroundLaunchProcessController.java b/services/core/java/com/android/server/wm/BackgroundLaunchProcessController.java index e88cfbf6986e..527edc13931a 100644 --- a/services/core/java/com/android/server/wm/BackgroundLaunchProcessController.java +++ b/services/core/java/com/android/server/wm/BackgroundLaunchProcessController.java @@ -190,6 +190,11 @@ class BackgroundLaunchProcessController { return false; } List<IBinder> binderTokens = getOriginatingTokensThatAllowBal(); + if (binderTokens.isEmpty()) { + // no tokens to allow anything + return false; + } + // The callback will decide. return mBackgroundActivityStartCallback.isActivityStartAllowed( binderTokens, uid, packageName); diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java index e44d27911262..6aec96988a77 100644 --- a/services/core/java/com/android/server/wm/DisplayContent.java +++ b/services/core/java/com/android/server/wm/DisplayContent.java @@ -6511,6 +6511,22 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp .getKeyguardController().isDisplayOccluded(mDisplayId); } + /** + * @return the task that is occluding the keyguard + */ + @Nullable + Task getTaskOccludingKeyguard() { + final KeyguardController keyguardController = mRootWindowContainer.mTaskSupervisor + .getKeyguardController(); + if (keyguardController.getTopOccludingActivity(mDisplayId) != null) { + return keyguardController.getTopOccludingActivity(mDisplayId).getRootTask(); + } + if (keyguardController.getDismissKeyguardActivity(mDisplayId) != null) { + return keyguardController.getDismissKeyguardActivity(mDisplayId).getRootTask(); + } + return null; + } + @VisibleForTesting void removeAllTasks() { forAllTasks((t) -> { t.getRootTask().removeChild(t, "removeAllTasks"); }); diff --git a/services/core/java/com/android/server/wm/DisplayPolicy.java b/services/core/java/com/android/server/wm/DisplayPolicy.java index 77e70a25d497..da102d0713ae 100644 --- a/services/core/java/com/android/server/wm/DisplayPolicy.java +++ b/services/core/java/com/android/server/wm/DisplayPolicy.java @@ -225,6 +225,7 @@ public class DisplayPolicy { /** Currently it can only be non-null when physical display switch happens. */ private DecorInsets.Cache mCachedDecorInsets; + @WindowManagerFuncs.LidState private volatile int mLidState = LID_ABSENT; private volatile int mDockMode = Intent.EXTRA_DOCK_STATE_UNDOCKED; private volatile boolean mHdmiPlugged; @@ -752,10 +753,11 @@ public class DisplayPolicy { return mNavigationBarCanMove; } - public void setLidState(int lidState) { + public void setLidState(@WindowManagerFuncs.LidState int lidState) { mLidState = lidState; } + @WindowManagerFuncs.LidState public int getLidState() { return mLidState; } @@ -1854,6 +1856,9 @@ public class DisplayPolicy { */ final Rect mConfigFrame = new Rect(); + /** The count of insets sources when calculating this info. */ + int mLastInsetsSourceCount; + private boolean mNeedUpdate = true; void update(DisplayContent dc, int rotation, int w, int h) { @@ -1875,6 +1880,7 @@ public class DisplayPolicy { mNonDecorFrame.inset(mNonDecorInsets); mConfigFrame.set(displayFrame); mConfigFrame.inset(mConfigInsets); + mLastInsetsSourceCount = dc.getDisplayPolicy().mInsetsSourceWindowsExceptIme.size(); mNeedUpdate = false; } @@ -1883,6 +1889,7 @@ public class DisplayPolicy { mConfigInsets.set(other.mConfigInsets); mNonDecorFrame.set(other.mNonDecorFrame); mConfigFrame.set(other.mConfigFrame); + mLastInsetsSourceCount = other.mLastInsetsSourceCount; mNeedUpdate = false; } @@ -1981,6 +1988,19 @@ public class DisplayPolicy { newInfo.update(mDisplayContent, rotation, dw, dh); final DecorInsets.Info currentInfo = getDecorInsetsInfo(rotation, dw, dh); if (newInfo.mConfigFrame.equals(currentInfo.mConfigFrame)) { + // Even if the config frame is not changed in current rotation, it may change the + // insets in other rotations if the source count is changed. + if (newInfo.mLastInsetsSourceCount != currentInfo.mLastInsetsSourceCount) { + for (int i = mDecorInsets.mInfoForRotation.length - 1; i >= 0; i--) { + if (i != rotation) { + final boolean flipSize = (i + rotation) % 2 == 1; + final int w = flipSize ? dh : dw; + final int h = flipSize ? dw : dh; + mDecorInsets.mInfoForRotation[i].update(mDisplayContent, i, w, h); + } + } + mDecorInsets.mInfoForRotation[rotation].set(newInfo); + } return false; } if (mCachedDecorInsets != null && !mCachedDecorInsets.canPreserve() diff --git a/services/core/java/com/android/server/wm/DisplayRotation.java b/services/core/java/com/android/server/wm/DisplayRotation.java index 87de0f6e4aa0..bc1ddf8f66bc 100644 --- a/services/core/java/com/android/server/wm/DisplayRotation.java +++ b/services/core/java/com/android/server/wm/DisplayRotation.java @@ -513,6 +513,7 @@ public class DisplayRotation { } if (mDisplayContent.inTransition() + && mDisplayContent.getDisplayPolicy().isScreenOnFully() && !mDisplayContent.mTransitionController.useShellTransitionsRotation()) { // Rotation updates cannot be performed while the previous rotation change animation // is still in progress. Skip this update. We will try updating again after the diff --git a/services/core/java/com/android/server/wm/EventLogTags.logtags b/services/core/java/com/android/server/wm/EventLogTags.logtags index 244656ca68e6..d957591ab7f9 100644 --- a/services/core/java/com/android/server/wm/EventLogTags.logtags +++ b/services/core/java/com/android/server/wm/EventLogTags.logtags @@ -80,3 +80,6 @@ option java_package com.android.server.wm # Request surface flinger to show / hide the wallpaper surface. 33001 wm_wallpaper_surface (Display Id|1|5),(Visible|1),(Target|3) + +# Entering pip called +38000 wm_enter_pip (User|1|5),(Token|1|5),(Component Name|3),(is Auto Enter|3) diff --git a/services/core/java/com/android/server/wm/ImeInsetsSourceProvider.java b/services/core/java/com/android/server/wm/ImeInsetsSourceProvider.java index ff2985c98421..e8a4c1c5c3d3 100644 --- a/services/core/java/com/android/server/wm/ImeInsetsSourceProvider.java +++ b/services/core/java/com/android/server/wm/ImeInsetsSourceProvider.java @@ -91,6 +91,21 @@ final class ImeInsetsSourceProvider extends InsetsSourceProvider { } @Override + void setClientVisible(boolean clientVisible) { + final boolean wasClientVisible = isClientVisible(); + super.setClientVisible(clientVisible); + // The layer of ImePlaceholder needs to be updated on a higher z-order for + // non-activity window (For activity window, IME is already on top of it). + if (!wasClientVisible && isClientVisible()) { + final InsetsControlTarget imeControlTarget = getControlTarget(); + if (imeControlTarget != null && imeControlTarget.getWindow() != null + && imeControlTarget.getWindow().mActivityRecord == null) { + mDisplayContent.assignWindowLayers(false /* setLayoutNeeded */); + } + } + } + + @Override void setServerVisible(boolean serverVisible) { mServerVisible = serverVisible; if (!mFrozen) { diff --git a/services/core/java/com/android/server/wm/KeyguardController.java b/services/core/java/com/android/server/wm/KeyguardController.java index 5f6d66011768..99878a3d0ffe 100644 --- a/services/core/java/com/android/server/wm/KeyguardController.java +++ b/services/core/java/com/android/server/wm/KeyguardController.java @@ -17,7 +17,6 @@ package com.android.server.wm; import static android.app.WindowConfiguration.ACTIVITY_TYPE_DREAM; -import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN; import static android.os.Trace.TRACE_TAG_WINDOW_MANAGER; import static android.view.Display.DEFAULT_DISPLAY; import static android.view.WindowManager.TRANSIT_FLAG_KEYGUARD_GOING_AWAY; @@ -411,19 +410,20 @@ class KeyguardController { if (waitAppTransition) { mService.deferWindowLayout(); try { - mRootWindowContainer.getDefaultDisplay() - .requestTransitionAndLegacyPrepare( - isDisplayOccluded(DEFAULT_DISPLAY) - ? TRANSIT_KEYGUARD_OCCLUDE - : TRANSIT_KEYGUARD_UNOCCLUDE, 0 /* flags */); + if (isDisplayOccluded(DEFAULT_DISPLAY)) { + mRootWindowContainer.getDefaultDisplay().requestTransitionAndLegacyPrepare( + TRANSIT_KEYGUARD_OCCLUDE, + topActivity != null ? topActivity.getRootTask() : null); + } else { + mRootWindowContainer.getDefaultDisplay().requestTransitionAndLegacyPrepare( + TRANSIT_KEYGUARD_UNOCCLUDE, 0 /* flags */); + } updateKeyguardSleepToken(DEFAULT_DISPLAY); mWindowManager.executeAppTransition(); } finally { mService.continueWindowLayout(); } } - dismissMultiWindowModeForTaskIfNeeded(displayId, topActivity != null - ? topActivity.getRootTask() : null); } /** @@ -473,6 +473,14 @@ class KeyguardController { return getDisplayState(displayId).mOccluded; } + ActivityRecord getTopOccludingActivity(int displayId) { + return getDisplayState(displayId).mTopOccludesActivity; + } + + ActivityRecord getDismissKeyguardActivity(int displayId) { + return getDisplayState(displayId).mDismissingKeyguardActivity; + } + /** * @return true if Keyguard can be currently dismissed without entering credentials. */ @@ -488,22 +496,6 @@ class KeyguardController { return getDisplayState(DEFAULT_DISPLAY).mShowingDream; } - private void dismissMultiWindowModeForTaskIfNeeded(int displayId, - @Nullable Task currentTaskControllingOcclusion) { - // TODO(b/113840485): Handle docked stack for individual display. - if (!getDisplayState(displayId).mKeyguardShowing || !isDisplayOccluded(DEFAULT_DISPLAY)) { - return; - } - - // Dismiss freeform windowing mode - if (currentTaskControllingOcclusion == null) { - return; - } - if (currentTaskControllingOcclusion.inFreeformWindowingMode()) { - currentTaskControllingOcclusion.setWindowingMode(WINDOWING_MODE_FULLSCREEN); - } - } - private void updateKeyguardSleepToken() { for (int displayNdx = mRootWindowContainer.getChildCount() - 1; displayNdx >= 0; displayNdx--) { diff --git a/services/core/java/com/android/server/wm/RootWindowContainer.java b/services/core/java/com/android/server/wm/RootWindowContainer.java index 536915929e88..7e839596c59b 100644 --- a/services/core/java/com/android/server/wm/RootWindowContainer.java +++ b/services/core/java/com/android/server/wm/RootWindowContainer.java @@ -2376,6 +2376,7 @@ class RootWindowContainer extends WindowContainer<DisplayContent> if (!displayShouldSleep && display.mTransitionController.isShellTransitionsEnabled() && !display.mTransitionController.isCollecting()) { int transit = TRANSIT_NONE; + Task startTask = null; if (!display.getDisplayPolicy().isAwake()) { // Note that currently this only happens on default display because non-default // display is always awake. @@ -2383,12 +2384,12 @@ class RootWindowContainer extends WindowContainer<DisplayContent> } else if (display.isKeyguardOccluded()) { // The display was awake so this is resuming activity for occluding keyguard. transit = WindowManager.TRANSIT_KEYGUARD_OCCLUDE; + startTask = display.getTaskOccludingKeyguard(); } if (transit != TRANSIT_NONE) { display.mTransitionController.requestStartTransition( display.mTransitionController.createTransition(transit), - null /* startTask */, null /* remoteTransition */, - null /* displayChange */); + startTask, null /* remoteTransition */, null /* displayChange */); } } // Set the sleeping state of the root tasks on the display. diff --git a/services/core/java/com/android/server/wm/Transition.java b/services/core/java/com/android/server/wm/Transition.java index 663db861b79e..b938b9e4ec51 100644 --- a/services/core/java/com/android/server/wm/Transition.java +++ b/services/core/java/com/android/server/wm/Transition.java @@ -687,7 +687,11 @@ class Transition implements BLASTSyncEngine.TransactionReadyListener { // All windows are synced already. return; } - if (!isInTransition(wc)) return; + if (wc.mDisplayContent == null || !isInTransition(wc)) return; + if (!wc.mDisplayContent.getDisplayPolicy().isScreenOnFully() + || wc.mDisplayContent.getDisplayInfo().state == Display.STATE_OFF) { + mFlags |= WindowManager.TRANSIT_FLAG_INVISIBLE; + } if (mContainerFreezer == null) { mContainerFreezer = new ScreenshotFreezer(); @@ -2245,8 +2249,10 @@ class Transition implements BLASTSyncEngine.TransactionReadyListener { while (leashReference.getParent() != ancestor) { leashReference = leashReference.getParent(); } + final SurfaceControl rootLeash = leashReference.makeAnimationLeash().setName( "Transition Root: " + leashReference.getName()).build(); + rootLeash.setUnreleasedWarningCallSite("Transition.calculateTransitionRoots"); startT.setLayer(rootLeash, leashReference.getLastLayer()); outInfo.addRootLeash(endDisplayId, rootLeash, ancestor.getBounds().left, ancestor.getBounds().top); diff --git a/services/core/java/com/android/server/wm/WallpaperController.java b/services/core/java/com/android/server/wm/WallpaperController.java index edafe0606b13..20ce98ca2fae 100644 --- a/services/core/java/com/android/server/wm/WallpaperController.java +++ b/services/core/java/com/android/server/wm/WallpaperController.java @@ -146,11 +146,10 @@ class WallpaperController { } } else { final ActivityRecord ar = w.mActivityRecord; - final TransitionController tc = w.mTransitionController; // The animating window can still be visible on screen if it is in transition, so we // should check whether this window can be wallpaper target even when visibleRequested // is false. - if (ar != null && !ar.isVisibleRequested() && !tc.inTransition(ar)) { + if (ar != null && !ar.isVisibleRequested() && !ar.isVisible()) { // An activity that is not going to remain visible shouldn't be the target. return false; } diff --git a/services/core/java/com/android/server/wm/WindowOrganizerController.java b/services/core/java/com/android/server/wm/WindowOrganizerController.java index 7e34d15ddcfc..8e85e5bca240 100644 --- a/services/core/java/com/android/server/wm/WindowOrganizerController.java +++ b/services/core/java/com/android/server/wm/WindowOrganizerController.java @@ -960,20 +960,6 @@ class WindowOrganizerController extends IWindowOrganizerController.Stub errorCallbackToken, organizer); break; } - default: { - // The other operations may change task order so they are skipped while in lock - // task mode. The above operations are still allowed because they don't move - // tasks. And it may be necessary such as clearing launch root after entering - // lock task mode. - if (isInLockTaskMode) { - Slog.w(TAG, "Skip applying hierarchy operation " + hop - + " while in lock task mode"); - return effects; - } - } - } - - switch (type) { case HIERARCHY_OP_TYPE_PENDING_INTENT: { final Bundle launchOpts = hop.getLaunchOptions(); ActivityOptions activityOptions = launchOpts != null @@ -1012,6 +998,20 @@ class WindowOrganizerController extends IWindowOrganizerController.Stub } break; } + default: { + // The other operations may change task order so they are skipped while in lock + // task mode. The above operations are still allowed because they don't move + // tasks. And it may be necessary such as clearing launch root after entering + // lock task mode. + if (isInLockTaskMode) { + Slog.w(TAG, "Skip applying hierarchy operation " + hop + + " while in lock task mode"); + return effects; + } + } + } + + switch (type) { case HIERARCHY_OP_TYPE_START_SHORTCUT: { final Bundle launchOpts = hop.getLaunchOptions(); final String callingPackage = launchOpts.getString( diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java index a93ab074f05f..edb8e0c5aa64 100644 --- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java +++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java @@ -2191,7 +2191,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { private void suspendAppsForQuietProfiles(boolean toSuspend) { PackageManagerInternal pmi = mInjector.getPackageManagerInternal(); - List<UserInfo> users = mUserManager.getUsers(); + List<UserInfo> users = mUserManagerInternal.getUsers(true /* excludeDying */); for (UserInfo user : users) { if (user.isManagedProfile() && user.isQuietModeEnabled()) { pmi.setPackagesSuspendedForQuietMode(user.id, toSuspend); @@ -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"); } } @@ -15100,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; } diff --git a/services/tests/mockingservicestests/src/com/android/server/am/MockingOomAdjusterTests.java b/services/tests/mockingservicestests/src/com/android/server/am/MockingOomAdjusterTests.java index cda5456723fb..770f04a2db5e 100644 --- a/services/tests/mockingservicestests/src/com/android/server/am/MockingOomAdjusterTests.java +++ b/services/tests/mockingservicestests/src/com/android/server/am/MockingOomAdjusterTests.java @@ -68,6 +68,7 @@ import static com.android.server.am.ProcessList.UNKNOWN_ADJ; import static com.android.server.am.ProcessList.VISIBLE_APP_ADJ; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotEquals; import static org.junit.Assert.assertTrue; import static org.mockito.AdditionalAnswers.answer; @@ -2489,6 +2490,28 @@ public class MockingOomAdjusterTests { assertProcStates(app2, false, PROCESS_STATE_SERVICE, SERVICE_ADJ, "started-services"); } + @SuppressWarnings("GuardedBy") + @Test + public void testUpdateOomAdj_DoOne_AboveClient_SameProcess() { + ProcessRecord app = spy(makeDefaultProcessRecord(MOCKAPP_PID, MOCKAPP_UID, + MOCKAPP_PROCESSNAME, MOCKAPP_PACKAGENAME, true)); + doReturn(PROCESS_STATE_TOP).when(sService.mAtmInternal).getTopProcessState(); + doReturn(app).when(sService).getTopApp(); + sService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE); + sService.mOomAdjuster.updateOomAdjLocked(app, OOM_ADJ_REASON_NONE); + + assertEquals(FOREGROUND_APP_ADJ, app.mState.getSetAdj()); + + // Simulate binding to a service in the same process using BIND_ABOVE_CLIENT and + // verify that its OOM adjustment level is unaffected. + bindService(app, app, null, Context.BIND_ABOVE_CLIENT, mock(IBinder.class)); + app.mServices.updateHasAboveClientLocked(); + assertFalse(app.mServices.hasAboveClient()); + + sService.mOomAdjuster.updateOomAdjLocked(app, OOM_ADJ_REASON_NONE); + assertEquals(FOREGROUND_APP_ADJ, app.mState.getSetAdj()); + } + private ProcessRecord makeDefaultProcessRecord(int pid, int uid, String processName, String packageName, boolean hasShownUi) { long now = SystemClock.uptimeMillis(); diff --git a/services/tests/servicestests/src/com/android/server/am/UserControllerTest.java b/services/tests/servicestests/src/com/android/server/am/UserControllerTest.java index 317fd58e0c76..dccacb4d301a 100644 --- a/services/tests/servicestests/src/com/android/server/am/UserControllerTest.java +++ b/services/tests/servicestests/src/com/android/server/am/UserControllerTest.java @@ -58,6 +58,7 @@ import static org.mockito.Matchers.anyBoolean; import static org.mockito.Matchers.anyInt; import static org.mockito.Matchers.eq; import static org.mockito.Mockito.doAnswer; +import static org.mockito.Mockito.doCallRealMethod; import static org.mockito.Mockito.doNothing; import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.mock; @@ -103,11 +104,13 @@ import com.android.server.am.UserState.KeyEvictedCallback; import com.android.server.pm.UserJourneyLogger; import com.android.server.pm.UserManagerInternal; import com.android.server.pm.UserManagerService; +import com.android.server.wm.ActivityTaskManagerInternal; import com.android.server.wm.WindowManagerService; import org.junit.After; import org.junit.Before; import org.junit.Test; +import org.mockito.ArgumentCaptor; import java.util.ArrayList; import java.util.Arrays; @@ -187,6 +190,7 @@ public class UserControllerTest { doNothing().when(mInjector).activityManagerOnUserStopped(anyInt()); doNothing().when(mInjector).clearBroadcastQueueForUser(anyInt()); doNothing().when(mInjector).taskSupervisorRemoveUser(anyInt()); + doNothing().when(mInjector).lockDeviceNowAndWaitForKeyguardShown(); mockIsUsersOnSecondaryDisplaysEnabled(false); // All UserController params are set to default. @@ -951,6 +955,45 @@ public class UserControllerTest { .systemServiceManagerOnUserCompletedEvent(eq(user2), eq(event2a)); } + @Test + public void testStallUserSwitchUntilTheKeyguardIsShown() throws Exception { + // enable user switch ui, because keyguard is only shown then + mUserController.setInitialConfig(/* userSwitchUiEnabled= */ true, + /* maxRunningUsers= */ 3, /* delayUserDataLocking= */ false); + + // mock the device to be secure in order to expect the keyguard to be shown + when(mInjector.mKeyguardManagerMock.isDeviceSecure(anyInt())).thenReturn(true); + + // call real lockDeviceNowAndWaitForKeyguardShown method for this test + doCallRealMethod().when(mInjector).lockDeviceNowAndWaitForKeyguardShown(); + + // call startUser on a thread because we're expecting it to be blocked + Thread threadStartUser = new Thread(()-> { + mUserController.startUser(TEST_USER_ID, USER_START_MODE_FOREGROUND); + }); + threadStartUser.start(); + + // make sure the switch is stalled... + Thread.sleep(2000); + // by checking REPORT_USER_SWITCH_MSG is not sent yet + assertNull(mInjector.mHandler.getMessageForCode(REPORT_USER_SWITCH_MSG)); + // and the thread is still alive + assertTrue(threadStartUser.isAlive()); + + // mock send the keyguard shown event + ArgumentCaptor<ActivityTaskManagerInternal.ScreenObserver> captor = ArgumentCaptor.forClass( + ActivityTaskManagerInternal.ScreenObserver.class); + verify(mInjector.mActivityTaskManagerInternal).registerScreenObserver(captor.capture()); + captor.getValue().onKeyguardStateChanged(true); + + // verify the switch now moves on... + Thread.sleep(1000); + // by checking REPORT_USER_SWITCH_MSG is sent + assertNotNull(mInjector.mHandler.getMessageForCode(REPORT_USER_SWITCH_MSG)); + // and the thread is finished + assertFalse(threadStartUser.isAlive()); + } + private void setUpAndStartUserInBackground(int userId) throws Exception { setUpUser(userId, 0); mUserController.startUser(userId, USER_START_MODE_BACKGROUND); @@ -1092,6 +1135,7 @@ public class UserControllerTest { private final IStorageManager mStorageManagerMock; private final UserManagerInternal mUserManagerInternalMock; private final WindowManagerService mWindowManagerMock; + private final ActivityTaskManagerInternal mActivityTaskManagerInternal; private final KeyguardManager mKeyguardManagerMock; private final LockPatternUtils mLockPatternUtilsMock; @@ -1111,6 +1155,7 @@ public class UserControllerTest { mUserManagerMock = mock(UserManagerService.class); mUserManagerInternalMock = mock(UserManagerInternal.class); mWindowManagerMock = mock(WindowManagerService.class); + mActivityTaskManagerInternal = mock(ActivityTaskManagerInternal.class); mStorageManagerMock = mock(IStorageManager.class); mKeyguardManagerMock = mock(KeyguardManager.class); when(mKeyguardManagerMock.isDeviceSecure(anyInt())).thenReturn(true); @@ -1172,6 +1217,11 @@ public class UserControllerTest { } @Override + ActivityTaskManagerInternal getActivityTaskManagerInternal() { + return mActivityTaskManagerInternal; + } + + @Override KeyguardManager getKeyguardManager() { return mKeyguardManagerMock; } diff --git a/services/tests/servicestests/src/com/android/server/biometrics/log/BiometricContextProviderTest.java b/services/tests/servicestests/src/com/android/server/biometrics/log/BiometricContextProviderTest.java index fb3a5f64d808..a4423038a072 100644 --- a/services/tests/servicestests/src/com/android/server/biometrics/log/BiometricContextProviderTest.java +++ b/services/tests/servicestests/src/com/android/server/biometrics/log/BiometricContextProviderTest.java @@ -316,13 +316,13 @@ public class BiometricContextProviderTest { assertThat(aidlContext.isAod).isEqualTo(false); assertThat(aidlContext.isCrypto).isEqualTo(false); - context = mProvider.updateContext(mOpContext, false /* crypto */); + context = mProvider.updateContext(mOpContext, true /* crypto */); aidlContext = context.toAidlContext(); assertThat(context).isSameInstanceAs(mOpContext); assertThat(aidlContext.id).isEqualTo(0); assertThat(aidlContext.reason).isEqualTo(OperationReason.UNKNOWN); assertThat(aidlContext.isAod).isEqualTo(false); - assertThat(aidlContext.isCrypto).isEqualTo(false); + assertThat(aidlContext.isCrypto).isEqualTo(true); } @Test diff --git a/services/tests/servicestests/src/com/android/server/biometrics/log/OperationContextExtTest.java b/services/tests/servicestests/src/com/android/server/biometrics/log/OperationContextExtTest.java index 5cf59602a43b..32284fd7541a 100644 --- a/services/tests/servicestests/src/com/android/server/biometrics/log/OperationContextExtTest.java +++ b/services/tests/servicestests/src/com/android/server/biometrics/log/OperationContextExtTest.java @@ -98,7 +98,7 @@ public class OperationContextExtTest { for (Map.Entry<Integer, Integer> entry : map.entrySet()) { final OperationContextExt context = new OperationContextExt(newAidlContext(), true); when(mBiometricContext.getDisplayState()).thenReturn(entry.getKey()); - assertThat(context.update(mBiometricContext).getDisplayState()) + assertThat(context.update(mBiometricContext, context.isCrypto()).getDisplayState()) .isEqualTo(entry.getValue()); } } @@ -139,7 +139,7 @@ public class OperationContextExtTest { final OperationContextExt context = new OperationContextExt(newAidlContext(), sessionType == OperationReason.BIOMETRIC_PROMPT); - assertThat(context.update(mBiometricContext)).isSameInstanceAs(context); + assertThat(context.update(mBiometricContext, context.isCrypto())).isSameInstanceAs(context); if (sessionInfo != null) { assertThat(context.getId()).isEqualTo(sessionInfo.getId()); diff --git a/services/tests/servicestests/src/com/android/server/contentcapture/OWNERS b/services/tests/servicestests/src/com/android/server/contentcapture/OWNERS new file mode 100644 index 000000000000..b3583a7f6ab1 --- /dev/null +++ b/services/tests/servicestests/src/com/android/server/contentcapture/OWNERS @@ -0,0 +1,4 @@ +# Bug component: 544200 + +include /core/java/android/view/contentcapture/OWNERS + diff --git a/services/tests/servicestests/src/com/android/server/display/AutomaticBrightnessControllerTest.java b/services/tests/servicestests/src/com/android/server/display/AutomaticBrightnessControllerTest.java index 3bef413a3e72..962e86776ea2 100644 --- a/services/tests/servicestests/src/com/android/server/display/AutomaticBrightnessControllerTest.java +++ b/services/tests/servicestests/src/com/android/server/display/AutomaticBrightnessControllerTest.java @@ -473,6 +473,42 @@ public class AutomaticBrightnessControllerTest { } @Test + public void testSwitchBetweenModesNoUserInteractions() throws Exception { + ArgumentCaptor<SensorEventListener> listenerCaptor = + ArgumentCaptor.forClass(SensorEventListener.class); + verify(mSensorManager).registerListener(listenerCaptor.capture(), eq(mLightSensor), + eq(INITIAL_LIGHT_SENSOR_RATE * 1000), any(Handler.class)); + SensorEventListener listener = listenerCaptor.getValue(); + + // Sensor reads 123 lux, + listener.onSensorChanged(TestUtils.createSensorEvent(mLightSensor, 123)); + when(mBrightnessMappingStrategy.getShortTermModelTimeout()).thenReturn(2000L); + when(mBrightnessMappingStrategy.getUserBrightness()).thenReturn(-1.0f); + when(mBrightnessMappingStrategy.getUserLux()).thenReturn(-1.0f); + + // No user brightness interaction. + + mController.switchToIdleMode(); + when(mIdleBrightnessMappingStrategy.isForIdleMode()).thenReturn(true); + when(mIdleBrightnessMappingStrategy.getUserBrightness()).thenReturn(-1.0f); + when(mIdleBrightnessMappingStrategy.getUserLux()).thenReturn(-1.0f); + + // Sensor reads 1000 lux, + listener.onSensorChanged(TestUtils.createSensorEvent(mLightSensor, 1000)); + // Do not fast-forward time. + mTestLooper.dispatchAll(); + + mController.switchToInteractiveScreenBrightnessMode(); + // Do not fast-forward time + mTestLooper.dispatchAll(); + + // Ensure that there are no data points added, since the user has never adjusted the + // brightness + verify(mBrightnessMappingStrategy, times(0)) + .addUserDataPoint(anyFloat(), anyFloat()); + } + + @Test public void testSwitchToIdleMappingStrategy() throws Exception { ArgumentCaptor<SensorEventListener> listenerCaptor = ArgumentCaptor.forClass(SensorEventListener.class); diff --git a/services/tests/servicestests/src/com/android/server/power/PowerManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/power/PowerManagerServiceTest.java index 933f00231313..7aec04568e0f 100644 --- a/services/tests/servicestests/src/com/android/server/power/PowerManagerServiceTest.java +++ b/services/tests/servicestests/src/com/android/server/power/PowerManagerServiceTest.java @@ -26,6 +26,9 @@ import static android.os.PowerManagerInternal.WAKEFULNESS_AWAKE; import static android.os.PowerManagerInternal.WAKEFULNESS_DOZING; import static android.os.PowerManagerInternal.WAKEFULNESS_DREAMING; +import static com.android.server.policy.WindowManagerPolicy.WindowManagerFuncs.LID_ABSENT; +import static com.android.server.policy.WindowManagerPolicy.WindowManagerFuncs.LID_CLOSED; + import static com.google.common.truth.Truth.assertThat; import static org.junit.Assert.assertFalse; @@ -145,6 +148,7 @@ public class PowerManagerServiceTest { @Mock private ActivityManagerInternal mActivityManagerInternalMock; @Mock private AttentionManagerInternal mAttentionManagerInternalMock; @Mock private DreamManagerInternal mDreamManagerInternalMock; + @Mock private WindowManagerPolicy mPolicyMock; @Mock private PowerManagerService.NativeWrapper mNativeWrapperMock; @Mock private Notifier mNotifierMock; @Mock private WirelessChargerDetector mWirelessChargerDetectorMock; @@ -205,6 +209,7 @@ public class PowerManagerServiceTest { .thenReturn(true); when(mSystemPropertiesMock.get(eq(SYSTEM_PROPERTY_QUIESCENT), anyString())).thenReturn(""); when(mAmbientDisplayConfigurationMock.ambientDisplayAvailable()).thenReturn(true); + when(mPolicyMock.getLidState()).thenReturn(LID_ABSENT); addLocalServiceMock(LightsManager.class, mLightsManagerMock); addLocalServiceMock(DisplayManagerInternal.class, mDisplayManagerInternalMock); @@ -212,6 +217,7 @@ public class PowerManagerServiceTest { addLocalServiceMock(ActivityManagerInternal.class, mActivityManagerInternalMock); addLocalServiceMock(AttentionManagerInternal.class, mAttentionManagerInternalMock); addLocalServiceMock(DreamManagerInternal.class, mDreamManagerInternalMock); + addLocalServiceMock(WindowManagerPolicy.class, mPolicyMock); mContextSpy = spy(new ContextWrapper(ApplicationProvider.getApplicationContext())); mResourcesSpy = spy(mContextSpy.getResources()); @@ -678,6 +684,20 @@ public class PowerManagerServiceTest { assertThat(mService.getGlobalWakefulnessLocked()).isEqualTo(WAKEFULNESS_AWAKE); } + @Test + public void testWakefulnessAwake_ShouldNotWakeUpWhenLidClosed() { + when(mPolicyMock.getLidState()).thenReturn(LID_CLOSED); + createService(); + startSystem(); + forceSleep(); + + mService.getBinderServiceInstance().wakeUp(mClock.now(), + PowerManager.WAKE_REASON_POWER_BUTTON, + "testing IPowerManager.wakeUp()", "pkg.name"); + + assertThat(mService.getGlobalWakefulnessLocked()).isEqualTo(WAKEFULNESS_ASLEEP); + } + /** * Tests a series of variants that control whether a device wakes-up when it is plugged in * or docked. diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationVisitUrisTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationVisitUrisTest.java index 4c10ddc76894..6668f8520820 100644 --- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationVisitUrisTest.java +++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationVisitUrisTest.java @@ -87,7 +87,6 @@ public class NotificationVisitUrisTest extends UiServiceTestCase { // This list should be emptied! Items can be removed as bugs are fixed. private static final Multimap<Class<?>, String> KNOWN_BAD = ImmutableMultimap.<Class<?>, String>builder() - .put(RemoteViews.class, "setIcon") // b/281018094 .put(Notification.WearableExtender.class, "addAction") // TODO: b/281044385 .put(Person.Builder.class, "setUri") // TODO: b/281044385 .put(RemoteViews.class, "setRemoteAdapter") // TODO: b/281044385 diff --git a/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyTests.java b/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyTests.java index 5ec36048234b..b0609ddfddfd 100644 --- a/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyTests.java @@ -51,14 +51,18 @@ import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.spy; import static org.mockito.Mockito.when; +import android.graphics.Insets; import android.graphics.PixelFormat; import android.graphics.Rect; import android.platform.test.annotations.Presubmit; import android.view.DisplayInfo; import android.view.DisplayShape; +import android.view.InsetsFrameProvider; import android.view.InsetsSource; import android.view.InsetsState; import android.view.PrivacyIndicatorBounds; +import android.view.Surface; +import android.view.WindowInsets; import android.view.WindowInsets.Side; import android.view.WindowManager; @@ -350,6 +354,36 @@ public class DisplayPolicyTests extends WindowTestsBase { && displayPolicy.updateDecorInsetsInfo()); assertEquals(STATUS_BAR_HEIGHT, displayPolicy.getDecorInsetsInfo(di.rotation, di.logicalWidth, di.logicalHeight).mConfigInsets.top); + + // Add a window that provides the same insets in current rotation. But it specifies + // different insets in other rotations. + final WindowState bar2 = createWindow(null, statusBar.mAttrs.type, "bar2"); + bar2.mAttrs.providedInsets = new InsetsFrameProvider[] { + new InsetsFrameProvider(bar2, 0, WindowInsets.Type.statusBars()) + .setInsetsSize(Insets.of(0, STATUS_BAR_HEIGHT, 0, 0)) + }; + bar2.mAttrs.setFitInsetsTypes(0); + bar2.mAttrs.paramsForRotation = new WindowManager.LayoutParams[4]; + final int doubleHeightFor90 = STATUS_BAR_HEIGHT * 2; + for (int i = ROTATION_0; i <= Surface.ROTATION_270; i++) { + final WindowManager.LayoutParams params = new WindowManager.LayoutParams(); + params.setFitInsetsTypes(0); + if (i == Surface.ROTATION_90) { + params.providedInsets = new InsetsFrameProvider[] { + new InsetsFrameProvider(bar2, 0, WindowInsets.Type.statusBars()) + .setInsetsSize(Insets.of(0, doubleHeightFor90, 0, 0)) + }; + } else { + params.providedInsets = bar2.mAttrs.providedInsets; + } + bar2.mAttrs.paramsForRotation[i] = params; + } + displayPolicy.addWindowLw(bar2, bar2.mAttrs); + // Current rotation is 0 and the top insets is still STATUS_BAR_HEIGHT, so no change. + assertFalse(displayPolicy.updateDecorInsetsInfo()); + // The insets in other rotations should be still updated. + assertEquals(doubleHeightFor90, displayPolicy.getDecorInsetsInfo(Surface.ROTATION_90, + di.logicalHeight, di.logicalWidth).mConfigInsets.top); } @SetupWindows(addWindows = { W_NAVIGATION_BAR, W_INPUT_METHOD }) diff --git a/services/tests/wmtests/src/com/android/server/wm/InsetsStateControllerTest.java b/services/tests/wmtests/src/com/android/server/wm/InsetsStateControllerTest.java index d1d83f62934d..d7e736df064b 100644 --- a/services/tests/wmtests/src/com/android/server/wm/InsetsStateControllerTest.java +++ b/services/tests/wmtests/src/com/android/server/wm/InsetsStateControllerTest.java @@ -25,6 +25,7 @@ import static android.view.WindowInsets.Type.navigationBars; import static android.view.WindowInsets.Type.statusBars; import static android.view.WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM; import static android.view.WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE; +import static android.view.WindowManager.LayoutParams.LAST_APPLICATION_WINDOW; import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION; import static android.view.WindowManager.LayoutParams.TYPE_INPUT_METHOD; @@ -38,6 +39,7 @@ import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyBoolean; import static org.mockito.Mockito.atLeastOnce; import static org.mockito.Mockito.clearInvocations; import static org.mockito.Mockito.spy; @@ -420,10 +422,10 @@ public class InsetsStateControllerTest extends WindowTestsBase { @Test public void testUpdateAboveInsetsState_zOrderChanged() { - final WindowState ime = createTestWindow("ime"); - final WindowState app = createTestWindow("app"); - final WindowState statusBar = createTestWindow("statusBar"); - final WindowState navBar = createTestWindow("navBar"); + final WindowState ime = createNonAppWindow("ime"); + final WindowState app = createNonAppWindow("app"); + final WindowState statusBar = createNonAppWindow("statusBar"); + final WindowState navBar = createNonAppWindow("navBar"); final InsetsSourceProvider imeSourceProvider = getController().getOrCreateSourceProvider(ID_IME, ime()); @@ -431,7 +433,9 @@ public class InsetsStateControllerTest extends WindowTestsBase { waitUntilHandlersIdle(); clearInvocations(mDisplayContent); + imeSourceProvider.updateControlForTarget(app, false /* force */); imeSourceProvider.setClientVisible(true); + verify(mDisplayContent).assignWindowLayers(anyBoolean()); waitUntilHandlersIdle(); // The visibility change should trigger a traversal to notify the change. verify(mDisplayContent).notifyInsetsChanged(any()); @@ -547,6 +551,7 @@ public class InsetsStateControllerTest extends WindowTestsBase { control2.getInsetsHint().bottom); } + /** Creates a window which is associated with ActivityRecord. */ private WindowState createTestWindow(String name) { final WindowState win = createWindow(null, TYPE_APPLICATION, name); win.setHasSurface(true); @@ -554,6 +559,14 @@ public class InsetsStateControllerTest extends WindowTestsBase { return win; } + /** Creates a non-activity window. */ + private WindowState createNonAppWindow(String name) { + final WindowState win = createWindow(null, LAST_APPLICATION_WINDOW + 1, name); + win.setHasSurface(true); + spyOn(win); + return win; + } + private InsetsStateController getController() { return mDisplayContent.getInsetsStateController(); } diff --git a/services/tests/wmtests/src/com/android/server/wm/TestWindowManagerPolicy.java b/services/tests/wmtests/src/com/android/server/wm/TestWindowManagerPolicy.java index adf3f3976f38..192632ce0277 100644 --- a/services/tests/wmtests/src/com/android/server/wm/TestWindowManagerPolicy.java +++ b/services/tests/wmtests/src/com/android/server/wm/TestWindowManagerPolicy.java @@ -18,6 +18,8 @@ package com.android.server.wm; import static android.view.WindowManager.LayoutParams.TYPE_NOTIFICATION_SHADE; +import static com.android.server.policy.WindowManagerPolicy.WindowManagerFuncs.LID_ABSENT; + import android.annotation.Nullable; import android.content.Context; import android.content.res.Configuration; @@ -354,4 +356,9 @@ class TestWindowManagerPolicy implements WindowManagerPolicy { public boolean isGlobalKey(int keyCode) { return false; } + + @Override + public int getLidState() { + return LID_ABSENT; + } } diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java b/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java index 863523f826b2..ddc729f773b2 100644 --- a/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java +++ b/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java @@ -485,6 +485,7 @@ class WindowTestsBase extends SystemServiceTestsBase { new InsetsFrameProvider(owner, 0, WindowInsets.Type.statusBars()) .setInsetsSize(Insets.of(0, STATUS_BAR_HEIGHT, 0, 0)) }; + statusBar.mAttrs.setFitInsetsTypes(0); dc.getDisplayPolicy().addWindowLw(statusBar, statusBar.mAttrs); return statusBar; } diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/CommonAssertions.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/CommonAssertions.kt index 1f120d47a1ac..ed9e14fd86da 100644 --- a/tests/FlickerTests/src/com/android/server/wm/flicker/CommonAssertions.kt +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/CommonAssertions.kt @@ -97,19 +97,19 @@ fun FlickerTest.entireScreenCovered(allStates: Boolean = true) { if (allStates) { assertLayers { this.invoke("entireScreenCovered") { entry -> - entry.entry.displays.forEach { display -> + entry.entry.displays.filter { it.isOn }.forEach { display -> entry.visibleRegion().coversAtLeast(display.layerStackSpace) } } } } else { assertLayersStart { - this.entry.displays.forEach { display -> + this.entry.displays.filter { it.isOn }.forEach { display -> this.visibleRegion().coversAtLeast(display.layerStackSpace) } } assertLayersEnd { - this.entry.displays.forEach { display -> + this.entry.displays.filter { it.isOn }.forEach { display -> this.visibleRegion().coversAtLeast(display.layerStackSpace) } } diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeOnGoHomeTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeOnGoHomeTest.kt index 7f496d828996..122a6cbbb98f 100644 --- a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeOnGoHomeTest.kt +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeOnGoHomeTest.kt @@ -17,6 +17,7 @@ package com.android.server.wm.flicker.ime import android.platform.test.annotations.IwTest +import android.platform.test.annotations.PlatinumTest import android.platform.test.annotations.Presubmit import android.tools.common.Rotation import android.tools.common.traces.component.ComponentNameMatcher @@ -102,6 +103,7 @@ open class CloseImeOnGoHomeTest(flicker: FlickerTest) : BaseTest(flicker) { @Presubmit @Test + @PlatinumTest(focusArea = "ime") @IwTest(focusArea = "ime") override fun cujCompleted() { super.cujCompleted() diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeToAppOnPressBackTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeToAppOnPressBackTest.kt index c693ca759bdd..5aa43820e5cb 100644 --- a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeToAppOnPressBackTest.kt +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeToAppOnPressBackTest.kt @@ -17,6 +17,7 @@ package com.android.server.wm.flicker.ime import android.platform.test.annotations.IwTest +import android.platform.test.annotations.PlatinumTest import android.platform.test.annotations.Presubmit import android.tools.common.traces.component.ComponentNameMatcher import android.tools.device.flicker.junit.FlickerParametersRunnerFactory @@ -103,6 +104,7 @@ open class CloseImeToAppOnPressBackTest(flicker: FlickerTest) : BaseTest(flicker @Presubmit @Test + @PlatinumTest(focusArea = "ime") @IwTest(focusArea = "ime") override fun cujCompleted() { super.cujCompleted() diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeToHomeOnFinishActivityTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeToHomeOnFinishActivityTest.kt index d5208e060883..6731089f8903 100644 --- a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeToHomeOnFinishActivityTest.kt +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeToHomeOnFinishActivityTest.kt @@ -18,6 +18,7 @@ package com.android.server.wm.flicker.ime import android.platform.test.annotations.FlakyTest import android.platform.test.annotations.IwTest +import android.platform.test.annotations.PlatinumTest import android.platform.test.annotations.Presubmit import android.tools.common.Rotation import android.tools.device.flicker.junit.FlickerParametersRunnerFactory @@ -71,6 +72,7 @@ open class CloseImeToHomeOnFinishActivityTest(flicker: FlickerTest) : BaseTest(f @Presubmit @Test + @PlatinumTest(focusArea = "ime") @IwTest(focusArea = "ime") override fun cujCompleted() { runAndIgnoreAssumptionViolation { entireScreenCovered() } diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/ShowImeWhenFocusingOnInputFieldTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/ShowImeWhenFocusingOnInputFieldTest.kt index d1335294f629..c70e3cb773d4 100644 --- a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/ShowImeWhenFocusingOnInputFieldTest.kt +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/ShowImeWhenFocusingOnInputFieldTest.kt @@ -17,6 +17,7 @@ package com.android.server.wm.flicker.ime import android.platform.test.annotations.IwTest +import android.platform.test.annotations.PlatinumTest import android.platform.test.annotations.Presubmit import android.tools.common.Rotation import android.tools.device.flicker.junit.FlickerParametersRunnerFactory @@ -52,6 +53,7 @@ open class ShowImeWhenFocusingOnInputFieldTest(flicker: FlickerTest) : BaseTest( @Presubmit @Test + @PlatinumTest(focusArea = "ime") @IwTest(focusArea = "ime") override fun cujCompleted() { super.cujCompleted() diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/rotation/ChangeAppRotationTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/rotation/ChangeAppRotationTest.kt index 855ea3e38b3e..7fbcfec40121 100644 --- a/tests/FlickerTests/src/com/android/server/wm/flicker/rotation/ChangeAppRotationTest.kt +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/rotation/ChangeAppRotationTest.kt @@ -17,6 +17,7 @@ package com.android.server.wm.flicker.rotation import android.platform.test.annotations.IwTest +import android.platform.test.annotations.PlatinumTest import android.platform.test.annotations.Presubmit import android.tools.common.traces.component.ComponentNameMatcher import android.tools.device.flicker.junit.FlickerParametersRunnerFactory @@ -129,6 +130,7 @@ open class ChangeAppRotationTest(flicker: FlickerTest) : RotationTransition(flic } @Test + @PlatinumTest(focusArea = "framework") @IwTest(focusArea = "framework") override fun cujCompleted() { super.cujCompleted() diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/rotation/SeamlessAppRotationTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/rotation/SeamlessAppRotationTest.kt index 0cbbb83b48ee..44ae14a8e4bb 100644 --- a/tests/FlickerTests/src/com/android/server/wm/flicker/rotation/SeamlessAppRotationTest.kt +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/rotation/SeamlessAppRotationTest.kt @@ -17,6 +17,7 @@ package com.android.server.wm.flicker.rotation import android.platform.test.annotations.IwTest +import android.platform.test.annotations.PlatinumTest import android.platform.test.annotations.Presubmit import android.tools.common.ScenarioBuilder import android.tools.common.traces.component.ComponentNameMatcher @@ -213,6 +214,7 @@ open class SeamlessAppRotationTest(flicker: FlickerTest) : RotationTransition(fl } @Test + @PlatinumTest(focusArea = "framework") @IwTest(focusArea = "framework") override fun cujCompleted() { appWindowFullScreen() |