diff options
109 files changed, 2280 insertions, 545 deletions
diff --git a/core/java/android/companion/virtual/flags/flags.aconfig b/core/java/android/companion/virtual/flags/flags.aconfig index 006226eb8c31..ed5d66227574 100644 --- a/core/java/android/companion/virtual/flags/flags.aconfig +++ b/core/java/android/companion/virtual/flags/flags.aconfig @@ -52,4 +52,15 @@ flag { description: "Makes MediaDrm APIs device-aware" bug: "303535376" is_fixed_read_only: true -}
\ No newline at end of file +} + +flag { + namespace: "virtual_devices" + name: "virtual_display_multi_window_mode_support" + description: "Add support for WINDOWING_MODE_MULTI_WINDOW to virtual displays by default" + is_fixed_read_only: true + bug: "341151395" + metadata { + purpose: PURPOSE_BUGFIX + } +} diff --git a/core/java/android/view/InsetsFlags.java b/core/java/android/view/InsetsFlags.java index ca8a7a8cf175..2fa57688f0cb 100644 --- a/core/java/android/view/InsetsFlags.java +++ b/core/java/android/view/InsetsFlags.java @@ -17,6 +17,7 @@ package android.view; import static android.view.WindowInsetsController.APPEARANCE_FORCE_LIGHT_NAVIGATION_BARS; +import static android.view.WindowInsetsController.APPEARANCE_LIGHT_CAPTION_BARS; import static android.view.WindowInsetsController.APPEARANCE_LIGHT_NAVIGATION_BARS; import static android.view.WindowInsetsController.APPEARANCE_LIGHT_STATUS_BARS; import static android.view.WindowInsetsController.APPEARANCE_LOW_PROFILE_BARS; @@ -24,6 +25,7 @@ import static android.view.WindowInsetsController.APPEARANCE_OPAQUE_NAVIGATION_B import static android.view.WindowInsetsController.APPEARANCE_OPAQUE_STATUS_BARS; import static android.view.WindowInsetsController.APPEARANCE_SEMI_TRANSPARENT_NAVIGATION_BARS; import static android.view.WindowInsetsController.APPEARANCE_SEMI_TRANSPARENT_STATUS_BARS; +import static android.view.WindowInsetsController.APPEARANCE_TRANSPARENT_CAPTION_BAR_BACKGROUND; import static android.view.WindowInsetsController.BEHAVIOR_DEFAULT; import static android.view.WindowInsetsController.BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE; @@ -69,7 +71,15 @@ public class InsetsFlags { @ViewDebug.FlagToString( mask = APPEARANCE_FORCE_LIGHT_NAVIGATION_BARS, equals = APPEARANCE_FORCE_LIGHT_NAVIGATION_BARS, - name = "FORCE_LIGHT_NAVIGATION_BARS") + name = "FORCE_LIGHT_NAVIGATION_BARS"), + @ViewDebug.FlagToString( + mask = APPEARANCE_TRANSPARENT_CAPTION_BAR_BACKGROUND, + equals = APPEARANCE_TRANSPARENT_CAPTION_BAR_BACKGROUND, + name = "APPEARANCE_TRANSPARENT_CAPTION_BAR_BACKGROUND"), + @ViewDebug.FlagToString( + mask = APPEARANCE_LIGHT_CAPTION_BARS, + equals = APPEARANCE_LIGHT_CAPTION_BARS, + name = "APPEARANCE_LIGHT_CAPTION_BARS") }) public @Appearance int appearance; diff --git a/core/java/android/view/NativeVectorDrawableAnimator.java b/core/java/android/view/NativeVectorDrawableAnimator.java index b0556a3f8a91..e92bd1f5d6b8 100644 --- a/core/java/android/view/NativeVectorDrawableAnimator.java +++ b/core/java/android/view/NativeVectorDrawableAnimator.java @@ -16,6 +16,8 @@ package android.view; +import android.animation.Animator; + /** * Exists just to allow for android.graphics & android.view package separation * @@ -26,4 +28,7 @@ package android.view; public interface NativeVectorDrawableAnimator { /** @hide */ long getAnimatorNativePtr(); + + /** @hide */ + void setThreadedRendererAnimatorListener(Animator.AnimatorListener listener); } diff --git a/core/java/android/view/ViewAnimationHostBridge.java b/core/java/android/view/ViewAnimationHostBridge.java index e0fae21bbdf6..62b2b6c053c4 100644 --- a/core/java/android/view/ViewAnimationHostBridge.java +++ b/core/java/android/view/ViewAnimationHostBridge.java @@ -16,14 +16,19 @@ package android.view; +import android.animation.Animator; +import android.animation.AnimatorListenerAdapter; import android.graphics.RenderNode; +import androidx.annotation.NonNull; + /** * Maps a View to a RenderNode's AnimationHost * * @hide */ -public class ViewAnimationHostBridge implements RenderNode.AnimationHost { +public class ViewAnimationHostBridge extends AnimatorListenerAdapter + implements RenderNode.AnimationHost { private final View mView; /** @@ -34,17 +39,35 @@ public class ViewAnimationHostBridge implements RenderNode.AnimationHost { } @Override - public void registerAnimatingRenderNode(RenderNode animator) { - mView.mAttachInfo.mViewRootImpl.registerAnimatingRenderNode(animator); + public void registerAnimatingRenderNode(RenderNode renderNode, Animator animator) { + mView.mAttachInfo.mViewRootImpl.registerAnimatingRenderNode(renderNode); + animator.addListener(this); } @Override public void registerVectorDrawableAnimator(NativeVectorDrawableAnimator animator) { mView.mAttachInfo.mViewRootImpl.registerVectorDrawableAnimator(animator); + animator.setThreadedRendererAnimatorListener(this); } @Override public boolean isAttached() { return mView.mAttachInfo != null; } + + @Override + public void onAnimationStart(@NonNull Animator animation) { + ViewRootImpl viewRoot = mView.getViewRootImpl(); + if (viewRoot != null) { + viewRoot.addThreadedRendererView(mView); + } + } + + @Override + public void onAnimationEnd(@NonNull Animator animation) { + ViewRootImpl viewRoot = mView.getViewRootImpl(); + if (viewRoot != null) { + viewRoot.removeThreadedRendererView(mView); + } + } } diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java index bdada11c7073..139285a44817 100644 --- a/core/java/android/view/ViewRootImpl.java +++ b/core/java/android/view/ViewRootImpl.java @@ -427,6 +427,12 @@ public final class ViewRootImpl implements ViewParent, private static final long NANOS_PER_SEC = 1000000000; + // If the ViewRootImpl has been idle for more than 750ms, clear the preferred + // frame rate category and frame rate. + private static final int IDLE_TIME_MILLIS = 750; + + private static final long NANOS_PER_MILLI = 1_000_000; + @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) static final ThreadLocal<HandlerActionQueue> sRunQueues = new ThreadLocal<HandlerActionQueue>(); @@ -659,6 +665,10 @@ public final class ViewRootImpl implements ViewParent, private int mMinusOneFrameIntervalMillis = 0; // VRR interval between the previous and the frame before private int mMinusTwoFrameIntervalMillis = 0; + // VRR has the invalidation idle message been posted? + private boolean mInvalidationIdleMessagePosted = false; + // VRR: List of all Views that are animating with the threaded render + private ArrayList<View> mThreadedRendererViews = new ArrayList(); /** * Update the Choreographer's FrameInfo object with the timing information for the current @@ -1184,6 +1194,8 @@ public final class ViewRootImpl implements ViewParent, toolkitFrameRateVelocityMappingReadOnly(); private static boolean sToolkitEnableInvalidateCheckThreadFlagValue = Flags.enableInvalidateCheckThread(); + private static boolean sSurfaceFlingerBugfixFlagValue = + com.android.graphics.surfaceflinger.flags.Flags.vrrBugfix24q4(); static { sToolkitSetFrameRateReadOnlyFlagValue = toolkitSetFrameRateReadOnly(); @@ -4261,8 +4273,13 @@ public final class ViewRootImpl implements ViewParent, // when the values are applicable. if (mDrawnThisFrame) { mDrawnThisFrame = false; + if (!mInvalidationIdleMessagePosted && sSurfaceFlingerBugfixFlagValue) { + mInvalidationIdleMessagePosted = true; + mHandler.sendEmptyMessageDelayed(MSG_CHECK_INVALIDATION_IDLE, IDLE_TIME_MILLIS); + } setCategoryFromCategoryCounts(); updateInfrequentCount(); + updateFrameRateFromThreadedRendererViews(); setPreferredFrameRate(mPreferredFrameRate); setPreferredFrameRateCategory(mPreferredFrameRateCategory); if (mPreferredFrameRate > 0 @@ -6499,6 +6516,8 @@ public final class ViewRootImpl implements ViewParent, return "MSG_WINDOW_TOUCH_MODE_CHANGED"; case MSG_KEEP_CLEAR_RECTS_CHANGED: return "MSG_KEEP_CLEAR_RECTS_CHANGED"; + case MSG_CHECK_INVALIDATION_IDLE: + return "MSG_CHECK_INVALIDATION_IDLE"; case MSG_REFRESH_POINTER_ICON: return "MSG_REFRESH_POINTER_ICON"; case MSG_TOUCH_BOOST_TIMEOUT: @@ -6759,6 +6778,31 @@ public final class ViewRootImpl implements ViewParent, mNumPausedForSync = 0; scheduleTraversals(); break; + case MSG_CHECK_INVALIDATION_IDLE: { + long delta; + if (mIsTouchBoosting || mIsFrameRateBoosting || mInsetsAnimationRunning) { + delta = 0; + } else { + delta = System.nanoTime() / NANOS_PER_MILLI - mLastUpdateTimeMillis; + } + if (delta >= IDLE_TIME_MILLIS) { + mFrameRateCategoryHighCount = 0; + mFrameRateCategoryHighHintCount = 0; + mFrameRateCategoryNormalCount = 0; + mFrameRateCategoryLowCount = 0; + mPreferredFrameRate = 0; + mPreferredFrameRateCategory = FRAME_RATE_CATEGORY_NO_PREFERENCE; + updateFrameRateFromThreadedRendererViews(); + setPreferredFrameRate(mPreferredFrameRate); + setPreferredFrameRateCategory(mPreferredFrameRateCategory); + mInvalidationIdleMessagePosted = false; + } else { + mInvalidationIdleMessagePosted = true; + mHandler.sendEmptyMessageDelayed(MSG_CHECK_INVALIDATION_IDLE, + IDLE_TIME_MILLIS - delta); + } + break; + } case MSG_TOUCH_BOOST_TIMEOUT: /** * Lower the frame rate after the boosting period (FRAME_RATE_TOUCH_BOOST_TIME). @@ -12581,6 +12625,24 @@ public final class ViewRootImpl implements ViewParent, } /** + * Views that are animating with the ThreadedRenderer don't use the normal invalidation + * path, so the value won't be updated through performTraversals. This reads the votes + * from those views. + */ + private void updateFrameRateFromThreadedRendererViews() { + ArrayList<View> views = mThreadedRendererViews; + for (int i = views.size() - 1; i >= 0; i--) { + View view = views.get(i); + View.AttachInfo attachInfo = view.mAttachInfo; + if (attachInfo == null || attachInfo.mViewRootImpl != this) { + views.remove(i); + } else { + view.votePreferredFrameRate(); + } + } + } + + /** * Sets the mPreferredFrameRateCategory from the high, high_hint, normal, and low counts. */ private void setCategoryFromCategoryCounts() { @@ -12761,6 +12823,31 @@ public final class ViewRootImpl implements ViewParent, } /** + * Mark a View as having an active ThreadedRenderer animation. This is used for + * RenderNodeAnimators and AnimatedVectorDrawables. When the animation stops, + * {@link #removeThreadedRendererView(View)} must be called. + * @param view The View with the ThreadedRenderer animation that started. + */ + public void addThreadedRendererView(View view) { + if (!mThreadedRendererViews.contains(view)) { + mThreadedRendererViews.add(view); + } + } + + /** + * When a ThreadedRenderer animation ends, the View that is associated with it using + * {@link #addThreadedRendererView(View)} must be removed with a call to this method. + * @param view The View whose ThreadedRender animation has stopped. + */ + public void removeThreadedRendererView(View view) { + mThreadedRendererViews.remove(view); + if (!mInvalidationIdleMessagePosted && sSurfaceFlingerBugfixFlagValue) { + mInvalidationIdleMessagePosted = true; + mHandler.sendEmptyMessageDelayed(MSG_CHECK_INVALIDATION_IDLE, IDLE_TIME_MILLIS); + } + } + + /** * Returns {@link #INTERMITTENT_STATE_INTERMITTENT} when the ViewRootImpl has only been * updated intermittently, {@link #INTERMITTENT_STATE_NOT_INTERMITTENT} when it is * not updated intermittently, and {@link #INTERMITTENT_STATE_IN_TRANSITION} when it @@ -12983,6 +13070,10 @@ public final class ViewRootImpl implements ViewParent, private void removeVrrMessages() { mHandler.removeMessages(MSG_TOUCH_BOOST_TIMEOUT); mHandler.removeMessages(MSG_FRAME_RATE_SETTING); + if (mInvalidationIdleMessagePosted && sSurfaceFlingerBugfixFlagValue) { + mInvalidationIdleMessagePosted = false; + mHandler.removeMessages(MSG_CHECK_INVALIDATION_IDLE); + } } /** @@ -13001,7 +13092,7 @@ public final class ViewRootImpl implements ViewParent, mMinusOneFrameIntervalMillis = timeIntervalMillis; mLastUpdateTimeMillis = currentTimeMillis; - if (timeIntervalMillis + mMinusTwoFrameIntervalMillis + if (mThreadedRendererViews.isEmpty() && timeIntervalMillis + mMinusTwoFrameIntervalMillis >= INFREQUENT_UPDATE_INTERVAL_MILLIS) { int infrequentUpdateCount = mInfrequentUpdateCount; mInfrequentUpdateCount = infrequentUpdateCount == INFREQUENT_UPDATE_COUNTS diff --git a/core/java/android/window/DisplayWindowPolicyController.java b/core/java/android/window/DisplayWindowPolicyController.java index 8d71a8e998bd..9cd2a716e498 100644 --- a/core/java/android/window/DisplayWindowPolicyController.java +++ b/core/java/android/window/DisplayWindowPolicyController.java @@ -23,6 +23,7 @@ import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.UserIdInt; import android.app.WindowConfiguration; +import android.companion.virtualdevice.flags.Flags; import android.content.ComponentName; import android.content.Intent; import android.content.pm.ActivityInfo; @@ -66,6 +67,9 @@ public abstract class DisplayWindowPolicyController { public DisplayWindowPolicyController() { synchronized (mSupportedWindowingModes) { mSupportedWindowingModes.add(WindowConfiguration.WINDOWING_MODE_FULLSCREEN); + if (Flags.virtualDisplayMultiWindowModeSupport()) { + mSupportedWindowingModes.add(WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW); + } } } diff --git a/core/java/android/window/flags/large_screen_experiences_app_compat.aconfig b/core/java/android/window/flags/large_screen_experiences_app_compat.aconfig index 4b2beb903325..983f46c58c4b 100644 --- a/core/java/android/window/flags/large_screen_experiences_app_compat.aconfig +++ b/core/java/android/window/flags/large_screen_experiences_app_compat.aconfig @@ -2,13 +2,6 @@ package: "com.android.window.flags" container: "system" flag { - name: "disable_thin_letterboxing_reachability" - namespace: "large_screen_experiences_app_compat" - description: "Whether reachability is disabled in case of thin letterboxing" - bug: "334077350" -} - -flag { name: "disable_thin_letterboxing_policy" namespace: "large_screen_experiences_app_compat" description: "Whether reachability is disabled in case of thin letterboxing" diff --git a/core/java/android/window/flags/windowing_sdk.aconfig b/core/java/android/window/flags/windowing_sdk.aconfig index 6af3d9e50d75..21aa4800237c 100644 --- a/core/java/android/window/flags/windowing_sdk.aconfig +++ b/core/java/android/window/flags/windowing_sdk.aconfig @@ -147,4 +147,15 @@ flag { metadata { purpose: PURPOSE_BUGFIX } +} + +flag { + namespace: "windowing_sdk" + name: "insets_control_seq" + description: "Add seqId to InsetsControls to ensure the stale update is ignored" + bug: "339380439" + is_fixed_read_only: true + metadata { + purpose: PURPOSE_BUGFIX + } }
\ No newline at end of file diff --git a/core/java/com/android/internal/policy/ScreenDecorationsUtils.java b/core/java/com/android/internal/policy/ScreenDecorationsUtils.java index ec6283922807..067e5e8813a7 100644 --- a/core/java/com/android/internal/policy/ScreenDecorationsUtils.java +++ b/core/java/com/android/internal/policy/ScreenDecorationsUtils.java @@ -18,6 +18,9 @@ package com.android.internal.policy; import android.content.Context; import android.content.res.Resources; +import android.util.DisplayUtils; +import android.view.Display; +import android.view.DisplayInfo; import android.view.RoundedCorners; import com.android.internal.R; @@ -57,11 +60,31 @@ public class ScreenDecorationsUtils { bottomRadius = defaultRadius; } + // If the physical pixels are scaled, apply it here + float scale = getPhysicalPixelDisplaySizeRatio(context); + if (scale != 1f) { + topRadius = topRadius * scale; + bottomRadius = bottomRadius * scale; + } + // Always use the smallest radius to make sure the rounded corners will // completely cover the display. return Math.min(topRadius, bottomRadius); } + static float getPhysicalPixelDisplaySizeRatio(Context context) { + DisplayInfo displayInfo = new DisplayInfo(); + context.getDisplay().getDisplayInfo(displayInfo); + final Display.Mode maxDisplayMode = + DisplayUtils.getMaximumResolutionDisplayMode(displayInfo.supportedModes); + if (maxDisplayMode == null) { + return 1f; + } + return DisplayUtils.getPhysicalPixelDisplaySizeRatio( + maxDisplayMode.getPhysicalWidth(), maxDisplayMode.getPhysicalHeight(), + displayInfo.getNaturalWidth(), displayInfo.getNaturalHeight()); + } + /** * If live rounded corners are supported on windows. */ diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml index 7b9235cdc691..6dbe44b483d2 100644 --- a/core/res/AndroidManifest.xml +++ b/core/res/AndroidManifest.xml @@ -4702,6 +4702,11 @@ <permission android:name="android.permission.REQUEST_UNIQUE_ID_ATTESTATION" android:protectionLevel="signature" /> + <!-- Allows an application to use the RemoteKeyProvisioningService. + @hide --> + <permission android:name="android.permission.BIND_RKP_SERVICE" + android:protectionLevel="signature" /> + <!-- Allows an application to get enabled credential manager providers. @hide --> <permission android:name="android.permission.LIST_ENABLED_CREDENTIAL_PROVIDERS" diff --git a/core/res/res/values/config_telephony.xml b/core/res/res/values/config_telephony.xml index 04f6f5214e74..dcda5d8669a4 100644 --- a/core/res/res/values/config_telephony.xml +++ b/core/res/res/values/config_telephony.xml @@ -403,4 +403,10 @@ <integer name="config_wait_for_datagram_sending_response_for_last_message_timeout_millis">60000</integer> <java-symbol type="integer" name="config_wait_for_datagram_sending_response_for_last_message_timeout_millis" /> + <!-- Boolean indicating whether Telephony should force PhoneGlobals creation + regardless of FEATURE_TELEPHONY presence. + --> + <bool name="config_force_phone_globals_creation">false</bool> + <java-symbol type="bool" name="config_force_phone_globals_creation" /> + </resources> diff --git a/core/tests/coretests/src/android/view/ViewFrameRateTest.java b/core/tests/coretests/src/android/view/ViewFrameRateTest.java index 0b1b40c8ba8b..07446e7617aa 100644 --- a/core/tests/coretests/src/android/view/ViewFrameRateTest.java +++ b/core/tests/coretests/src/android/view/ViewFrameRateTest.java @@ -41,11 +41,13 @@ import android.app.Instrumentation; import android.os.Handler; import android.os.Looper; import android.os.SystemClock; +import android.platform.test.annotations.LargeTest; import android.platform.test.annotations.RequiresFlagsEnabled; import android.platform.test.flag.junit.CheckFlagsRule; import android.platform.test.flag.junit.DeviceFlagsValueProvider; import android.util.DisplayMetrics; import android.widget.FrameLayout; +import android.widget.ProgressBar; import androidx.test.annotation.UiThreadTest; import androidx.test.filters.SmallTest; @@ -623,6 +625,162 @@ public class ViewFrameRateTest { assertEquals(FRAME_RATE_CATEGORY_HIGH_HINT, mViewRoot.getLastPreferredFrameRateCategory()); } + @LargeTest + @Test + @RequiresFlagsEnabled({FLAG_TOOLKIT_SET_FRAME_RATE_READ_ONLY, + FLAG_TOOLKIT_FRAME_RATE_VIEW_ENABLING_READ_ONLY, + com.android.graphics.surfaceflinger.flags.Flags.FLAG_VRR_BUGFIX_24Q4 + }) + public void idleDetected() throws Throwable { + waitForFrameRateCategoryToSettle(); + mActivityRule.runOnUiThread(() -> { + mMovingView.setRequestedFrameRate(View.REQUESTED_FRAME_RATE_CATEGORY_HIGH); + mMovingView.setFrameContentVelocity(Float.MAX_VALUE); + mMovingView.invalidate(); + runAfterDraw(() -> assertEquals(FRAME_RATE_CATEGORY_HIGH, + mViewRoot.getLastPreferredFrameRateCategory())); + }); + waitForAfterDraw(); + + // Wait for idle timeout + Thread.sleep(1000); + assertEquals(0f, mViewRoot.getLastPreferredFrameRate()); + assertEquals(FRAME_RATE_CATEGORY_NO_PREFERENCE, + mViewRoot.getLastPreferredFrameRateCategory()); + } + + @LargeTest + @Test + @RequiresFlagsEnabled({FLAG_TOOLKIT_SET_FRAME_RATE_READ_ONLY, + FLAG_TOOLKIT_FRAME_RATE_VIEW_ENABLING_READ_ONLY, + com.android.graphics.surfaceflinger.flags.Flags.FLAG_VRR_BUGFIX_24Q4 + }) + public void vectorDrawableFrameRate() throws Throwable { + final ProgressBar[] progressBars = new ProgressBar[3]; + final ViewGroup[] parents = new ViewGroup[1]; + mActivityRule.runOnUiThread(() -> { + ViewGroup parent = (ViewGroup) mMovingView.getParent(); + parents[0] = parent; + ProgressBar progressBar1 = new ProgressBar(mActivity); + parent.addView(progressBar1); + progressBar1.setRequestedFrameRate(View.REQUESTED_FRAME_RATE_CATEGORY_LOW); + progressBar1.setIndeterminate(true); + progressBars[0] = progressBar1; + + ProgressBar progressBar2 = new ProgressBar(mActivity); + parent.addView(progressBar2); + progressBar2.setRequestedFrameRate(View.REQUESTED_FRAME_RATE_CATEGORY_NORMAL); + progressBar2.setIndeterminate(true); + progressBars[1] = progressBar2; + + ProgressBar progressBar3 = new ProgressBar(mActivity); + parent.addView(progressBar3); + progressBar3.setRequestedFrameRate(45f); + progressBar3.setIndeterminate(true); + progressBars[2] = progressBar3; + }); + waitForFrameRateCategoryToSettle(); + + // Wait for idle timeout + Thread.sleep(1000); + assertEquals(45f, mViewRoot.getLastPreferredFrameRate()); + assertEquals(FRAME_RATE_CATEGORY_NORMAL, mViewRoot.getLastPreferredFrameRateCategory()); + + // Removing the vector drawable with NORMAL should drop the category to LOW + mActivityRule.runOnUiThread(() -> parents[0].removeView(progressBars[1])); + Thread.sleep(1000); + assertEquals(45f, mViewRoot.getLastPreferredFrameRate()); + assertEquals(FRAME_RATE_CATEGORY_LOW, + mViewRoot.getLastPreferredFrameRateCategory()); + // Removing the one voting for frame rate should leave only the category + mActivityRule.runOnUiThread(() -> parents[0].removeView(progressBars[2])); + Thread.sleep(1000); + assertEquals(0f, mViewRoot.getLastPreferredFrameRate()); + assertEquals(FRAME_RATE_CATEGORY_LOW, + mViewRoot.getLastPreferredFrameRateCategory()); + // Removing the last one should leave it with no preference + mActivityRule.runOnUiThread(() -> parents[0].removeView(progressBars[0])); + Thread.sleep(1000); + assertEquals(0f, mViewRoot.getLastPreferredFrameRate()); + assertEquals(FRAME_RATE_CATEGORY_NO_PREFERENCE, + mViewRoot.getLastPreferredFrameRateCategory()); + } + + @LargeTest + @Test + @RequiresFlagsEnabled({FLAG_TOOLKIT_SET_FRAME_RATE_READ_ONLY, + FLAG_TOOLKIT_FRAME_RATE_VIEW_ENABLING_READ_ONLY, + com.android.graphics.surfaceflinger.flags.Flags.FLAG_VRR_BUGFIX_24Q4 + }) + public void renderNodeAnimatorFrameRateCanceled() throws Throwable { + mMovingView.setRequestedFrameRate(View.REQUESTED_FRAME_RATE_CATEGORY_NO_PREFERENCE); + waitForFrameRateCategoryToSettle(); + + RenderNodeAnimator[] renderNodeAnimator = new RenderNodeAnimator[1]; + renderNodeAnimator[0] = new RenderNodeAnimator(RenderNodeAnimator.ALPHA, 0f); + renderNodeAnimator[0].setDuration(100000); + + mActivityRule.runOnUiThread(() -> { + renderNodeAnimator[0].setTarget(mMovingView); + renderNodeAnimator[0].start(); + mMovingView.setRequestedFrameRate(View.REQUESTED_FRAME_RATE_CATEGORY_LOW); + runAfterDraw(() -> { + assertEquals(0f, mViewRoot.getLastPreferredFrameRate()); + assertEquals(FRAME_RATE_CATEGORY_LOW, + mViewRoot.getLastPreferredFrameRateCategory()); + }); + }); + waitForAfterDraw(); + + mActivityRule.runOnUiThread(() -> { + renderNodeAnimator[0].cancel(); + }); + + // Wait for idle timeout + Thread.sleep(1000); + assertEquals(0f, mViewRoot.getLastPreferredFrameRate()); + assertEquals(FRAME_RATE_CATEGORY_NO_PREFERENCE, + mViewRoot.getLastPreferredFrameRateCategory()); + } + + @LargeTest + @Test + @RequiresFlagsEnabled({FLAG_TOOLKIT_SET_FRAME_RATE_READ_ONLY, + FLAG_TOOLKIT_FRAME_RATE_VIEW_ENABLING_READ_ONLY, + com.android.graphics.surfaceflinger.flags.Flags.FLAG_VRR_BUGFIX_24Q4 + }) + public void renderNodeAnimatorFrameRateRemoved() throws Throwable { + mMovingView.setRequestedFrameRate(View.REQUESTED_FRAME_RATE_CATEGORY_NO_PREFERENCE); + waitForFrameRateCategoryToSettle(); + + RenderNodeAnimator[] renderNodeAnimator = new RenderNodeAnimator[1]; + renderNodeAnimator[0] = new RenderNodeAnimator(RenderNodeAnimator.ALPHA, 0f); + renderNodeAnimator[0].setDuration(100000); + + mActivityRule.runOnUiThread(() -> { + renderNodeAnimator[0].setTarget(mMovingView); + renderNodeAnimator[0].start(); + mMovingView.setRequestedFrameRate(View.REQUESTED_FRAME_RATE_CATEGORY_LOW); + runAfterDraw(() -> { + assertEquals(0f, mViewRoot.getLastPreferredFrameRate()); + assertEquals(FRAME_RATE_CATEGORY_LOW, + mViewRoot.getLastPreferredFrameRateCategory()); + }); + }); + waitForAfterDraw(); + + mActivityRule.runOnUiThread(() -> { + ViewGroup parent = (ViewGroup) mMovingView.getParent(); + assert parent != null; + parent.removeView(mMovingView); + }); + + Thread.sleep(1000); + assertEquals(0f, mViewRoot.getLastPreferredFrameRate()); + assertEquals(FRAME_RATE_CATEGORY_NO_PREFERENCE, + mViewRoot.getLastPreferredFrameRateCategory()); + } + private void runAfterDraw(@NonNull Runnable runnable) { Handler handler = new Handler(Looper.getMainLooper()); mAfterDrawLatch = new CountDownLatch(1); diff --git a/data/etc/preinstalled-packages-platform.xml b/data/etc/preinstalled-packages-platform.xml index f9fb84d2b31d..782327713fdc 100644 --- a/data/etc/preinstalled-packages-platform.xml +++ b/data/etc/preinstalled-packages-platform.xml @@ -134,19 +134,4 @@ to pre-existing users, but cannot uninstall pre-existing system packages from pr <install-in-user-type package="com.android.avatarpicker"> <install-in user-type="FULL" /> </install-in-user-type> - - <!-- AiLabs Warp app pre-installed in hardware/google/pixel/common/pixel-common-device.mk --> - <install-in-user-type package="com.google.android.apps.warp"> - <install-in user-type="FULL" /> - <install-in user-type="PROFILE" /> - <do-not-install-in user-type="android.os.usertype.profile.PRIVATE" /> - </install-in-user-type> - - <!-- Google Home app pre-installed on tangor devices in vendor/google/products/tangor_common.mk - --> - <install-in-user-type package="com.google.android.apps.chromecast.app"> - <install-in user-type="FULL" /> - <install-in user-type="PROFILE" /> - <do-not-install-in user-type="android.os.usertype.profile.PRIVATE" /> - </install-in-user-type> </config> diff --git a/graphics/java/android/graphics/RenderNode.java b/graphics/java/android/graphics/RenderNode.java index 0650b7817729..211f74a47bdd 100644 --- a/graphics/java/android/graphics/RenderNode.java +++ b/graphics/java/android/graphics/RenderNode.java @@ -16,6 +16,7 @@ package android.graphics; +import android.animation.Animator; import android.annotation.BytesLong; import android.annotation.ColorInt; import android.annotation.FloatRange; @@ -1639,7 +1640,7 @@ public final class RenderNode { */ public interface AnimationHost { /** @hide */ - void registerAnimatingRenderNode(RenderNode animator); + void registerAnimatingRenderNode(RenderNode renderNode, Animator animator); /** @hide */ void registerVectorDrawableAnimator(NativeVectorDrawableAnimator animator); @@ -1654,7 +1655,7 @@ public final class RenderNode { throw new IllegalStateException("Cannot start this animator on a detached view!"); } nAddAnimator(mNativeRenderNode, animator.getNativeAnimator()); - mAnimationHost.registerAnimatingRenderNode(this); + mAnimationHost.registerAnimatingRenderNode(this, animator); } /** @hide */ diff --git a/graphics/java/android/graphics/drawable/AnimatedVectorDrawable.java b/graphics/java/android/graphics/drawable/AnimatedVectorDrawable.java index 55f205bb14a6..d4bb461c284e 100644 --- a/graphics/java/android/graphics/drawable/AnimatedVectorDrawable.java +++ b/graphics/java/android/graphics/drawable/AnimatedVectorDrawable.java @@ -1266,6 +1266,7 @@ public class AnimatedVectorDrawable extends Drawable implements Animatable2 { private final IntArray mPendingAnimationActions = new IntArray(); private final AnimatedVectorDrawable mDrawable; private long mTotalDuration; + private AnimatorListener mThreadedRendererAnimatorListener; VectorDrawableAnimatorRT(AnimatedVectorDrawable drawable) { mDrawable = drawable; @@ -1689,6 +1690,9 @@ public class AnimatedVectorDrawable extends Drawable implements Animatable2 { if (mListener != null) { mListener.onAnimationStart(null); } + if (mThreadedRendererAnimatorListener != null) { + mThreadedRendererAnimatorListener.onAnimationStart(null); + } } // This should only be called after animator has been added to the RenderNode target. @@ -1717,6 +1721,9 @@ public class AnimatedVectorDrawable extends Drawable implements Animatable2 { if (mListener != null) { mListener.onAnimationStart(null); } + if (mThreadedRendererAnimatorListener != null) { + mThreadedRendererAnimatorListener.onAnimationStart(null); + } } @Override @@ -1725,6 +1732,11 @@ public class AnimatedVectorDrawable extends Drawable implements Animatable2 { } @Override + public void setThreadedRendererAnimatorListener(AnimatorListener animatorListener) { + mThreadedRendererAnimatorListener = animatorListener; + } + + @Override public boolean canReverse() { return mIsReversible; } @@ -1788,6 +1800,9 @@ public class AnimatedVectorDrawable extends Drawable implements Animatable2 { if (mListener != null) { mListener.onAnimationEnd(null); } + if (mThreadedRendererAnimatorListener != null) { + mThreadedRendererAnimatorListener.onAnimationEnd(null); + } } // onFinished: should be called from native diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java index fb0a1ab3062e..12bbd51b968d 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java @@ -522,14 +522,16 @@ public abstract class WMShellModule { RecentsTransitionHandler recentsTransitionHandler, MultiInstanceHelper multiInstanceHelper, @ShellMainThread ShellExecutor mainExecutor, - Optional<DesktopTasksLimiter> desktopTasksLimiter) { + Optional<DesktopTasksLimiter> desktopTasksLimiter, + Optional<RecentTasksController> recentTasksController) { return new DesktopTasksController(context, shellInit, shellCommandHandler, shellController, displayController, shellTaskOrganizer, syncQueue, rootTaskDisplayAreaOrganizer, dragAndDropController, transitions, enterDesktopTransitionHandler, exitDesktopTransitionHandler, toggleResizeDesktopTaskTransitionHandler, dragToDesktopTransitionHandler, desktopModeTaskRepository, desktopModeLoggerTransitionObserver, launchAdjacentController, - recentsTransitionHandler, multiInstanceHelper, mainExecutor, desktopTasksLimiter); + recentsTransitionHandler, multiInstanceHelper, + mainExecutor, desktopTasksLimiter, recentTasksController.orElse(null)); } @WMSingleton diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt index 6e45397411d7..ef384c74cb5e 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt @@ -70,6 +70,7 @@ import com.android.wm.shell.desktopmode.DesktopModeTaskRepository.VisibleTasksLi import com.android.wm.shell.desktopmode.DragToDesktopTransitionHandler.DragToDesktopStateListener import com.android.wm.shell.draganddrop.DragAndDropController import com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_DESKTOP_MODE +import com.android.wm.shell.recents.RecentTasksController import com.android.wm.shell.recents.RecentsTransitionHandler import com.android.wm.shell.recents.RecentsTransitionStateListener import com.android.wm.shell.shared.DesktopModeStatus @@ -118,6 +119,7 @@ class DesktopTasksController( private val multiInstanceHelper: MultiInstanceHelper, @ShellMainThread private val mainExecutor: ShellExecutor, private val desktopTasksLimiter: Optional<DesktopTasksLimiter>, + private val recentTasksController: RecentTasksController? ) : RemoteCallable<DesktopTasksController>, Transitions.TransitionHandler, @@ -293,24 +295,49 @@ class DesktopTasksController( taskId: Int, wct: WindowContainerTransaction = WindowContainerTransaction() ): Boolean { - shellTaskOrganizer.getRunningTaskInfo(taskId)?.let { task -> moveToDesktop(task, wct) } - ?: return false + shellTaskOrganizer.getRunningTaskInfo(taskId)?.let { + moveToDesktop(it, wct) + } ?: moveToDesktopFromNonRunningTask(taskId, wct) return true } - /** Move a task to desktop */ + private fun moveToDesktopFromNonRunningTask( + taskId: Int, + wct: WindowContainerTransaction + ): Boolean { + recentTasksController?.findTaskInBackground(taskId)?.let { + KtProtoLog.v( + WM_SHELL_DESKTOP_MODE, + "DesktopTasksController: moveToDesktopFromNonRunningTask taskId=%d", + taskId + ) + // TODO(342378842): Instead of using default display, support multiple displays + val taskToMinimize = + bringDesktopAppsToFrontBeforeShowingNewTask(DEFAULT_DISPLAY, wct, taskId) + addMoveToDesktopChangesNonRunningTask(wct, taskId) + // TODO(343149901): Add DPI changes for task launch + val transition = enterDesktopTaskTransitionHandler.moveToDesktop(wct) + addPendingMinimizeTransition(transition, taskToMinimize) + return true + } ?: return false + } + + private fun addMoveToDesktopChangesNonRunningTask( + wct: WindowContainerTransaction, + taskId: Int + ) { + val options = ActivityOptions.makeBasic() + options.launchWindowingMode = WINDOWING_MODE_FREEFORM + wct.startTask(taskId, options.toBundle()) + } + + /** + * Move a task to desktop + */ fun moveToDesktop( task: RunningTaskInfo, wct: WindowContainerTransaction = WindowContainerTransaction() ) { - if (!DesktopModeStatus.canEnterDesktopMode(context)) { - KtProtoLog.w( - WM_SHELL_DESKTOP_MODE, - "DesktopTasksController: Cannot enter desktop, " + - "display does not meet minimum size requirements" - ) - return - } if (Flags.enableDesktopWindowingModalsPolicy() && isSingleTopActivityTranslucent(task)) { KtProtoLog.w( WM_SHELL_DESKTOP_MODE, diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasksController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasksController.java index d8f2c02b5399..863202d5e1c3 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasksController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasksController.java @@ -446,6 +446,25 @@ public class RecentTasksController implements TaskStackListenerCallback, return null; } + /** + * Find the background task that match the given taskId. + */ + @Nullable + public ActivityManager.RecentTaskInfo findTaskInBackground(int taskId) { + List<ActivityManager.RecentTaskInfo> tasks = mActivityTaskManager.getRecentTasks( + Integer.MAX_VALUE, ActivityManager.RECENT_IGNORE_UNAVAILABLE, + ActivityManager.getCurrentUser()); + for (int i = 0; i < tasks.size(); i++) { + final ActivityManager.RecentTaskInfo task = tasks.get(i); + if (task.isVisible) { + continue; + } + if (taskId == task.taskId) { + return task; + } + } + return null; + } public void dump(@NonNull PrintWriter pw, String prefix) { final String innerPrefix = prefix + " "; pw.println(prefix + TAG); diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt index ac67bd1fedd8..cf6cea2b34a7 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt @@ -16,6 +16,7 @@ package com.android.wm.shell.desktopmode +import android.app.ActivityManager.RecentTaskInfo import android.app.ActivityManager.RunningTaskInfo import android.app.WindowConfiguration.ACTIVITY_TYPE_HOME import android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD @@ -47,13 +48,16 @@ import android.view.WindowManager.TRANSIT_OPEN import android.view.WindowManager.TRANSIT_TO_BACK import android.view.WindowManager.TRANSIT_TO_FRONT import android.window.DisplayAreaInfo +import android.window.IWindowContainerToken import android.window.RemoteTransition import android.window.TransitionRequestInfo import android.window.WindowContainerToken import android.window.WindowContainerTransaction +import android.window.WindowContainerTransaction.HierarchyOp.HIERARCHY_OP_TYPE_LAUNCH_TASK import android.window.WindowContainerTransaction.HierarchyOp.HIERARCHY_OP_TYPE_PENDING_INTENT import android.window.WindowContainerTransaction.HierarchyOp.HIERARCHY_OP_TYPE_REMOVE_TASK import android.window.WindowContainerTransaction.HierarchyOp.HIERARCHY_OP_TYPE_REORDER +import android.window.WindowContainerTransaction.HierarchyOp.LAUNCH_KEY_TASK_ID import androidx.test.filters.SmallTest import com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn import com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession @@ -78,6 +82,7 @@ import com.android.wm.shell.desktopmode.DesktopTestHelpers.Companion.createFulls import com.android.wm.shell.desktopmode.DesktopTestHelpers.Companion.createHomeTask import com.android.wm.shell.desktopmode.DesktopTestHelpers.Companion.createSplitScreenTask import com.android.wm.shell.draganddrop.DragAndDropController +import com.android.wm.shell.recents.RecentTasksController import com.android.wm.shell.recents.RecentsTransitionHandler import com.android.wm.shell.recents.RecentsTransitionStateListener import com.android.wm.shell.shared.DesktopModeStatus @@ -93,6 +98,7 @@ import com.android.wm.shell.transition.Transitions.TRANSIT_EXIT_DESKTOP_MODE import com.android.wm.shell.transition.Transitions.TransitionHandler import com.google.common.truth.Truth.assertThat import com.google.common.truth.Truth.assertWithMessage +import java.util.Optional import org.junit.After import org.junit.Assume.assumeTrue import org.junit.Before @@ -115,7 +121,6 @@ import org.mockito.kotlin.anyOrNull import org.mockito.kotlin.atLeastOnce import org.mockito.kotlin.capture import org.mockito.quality.Strictness -import java.util.Optional import junit.framework.Assert.assertFalse import junit.framework.Assert.assertTrue import org.mockito.Mockito.`when` as whenever @@ -154,6 +159,7 @@ class DesktopTasksControllerTest : ShellTestCase() { @Mock lateinit var multiInstanceHelper: MultiInstanceHelper @Mock lateinit var desktopModeLoggerTransitionObserver: DesktopModeLoggerTransitionObserver @Mock lateinit var desktopModeVisualIndicator: DesktopModeVisualIndicator + @Mock lateinit var recentTasksController: RecentTasksController private lateinit var mockitoSession: StaticMockitoSession private lateinit var controller: DesktopTasksController @@ -233,6 +239,7 @@ class DesktopTasksControllerTest : ShellTestCase() { multiInstanceHelper, shellExecutor, Optional.of(desktopTasksLimiter), + recentTasksController ) } @@ -622,7 +629,7 @@ class DesktopTasksControllerTest : ShellTestCase() { controller.moveToDesktop(task) val wct = getLatestMoveToDesktopWct() assertThat(wct.changes[task.token.asBinder()]?.windowingMode) - .isEqualTo(WINDOWING_MODE_FREEFORM) + .isEqualTo(WINDOWING_MODE_FREEFORM) } @Test @@ -643,14 +650,17 @@ class DesktopTasksControllerTest : ShellTestCase() { } @Test - fun moveToDesktop_deviceNotSupported_doesNothing() { - val task = setUpFullscreenTask() + fun moveToDesktop_nonRunningTask_launchesInFreeform() { + whenever(shellTaskOrganizer.getRunningTaskInfo(anyInt())).thenReturn(null) - // Simulate non compatible device - doReturn(false).`when` { DesktopModeStatus.isDesktopModeSupported(any()) } + val task = createTaskInfo(1) - controller.moveToDesktop(task) - verifyWCTNotExecuted() + whenever(recentTasksController.findTaskInBackground(anyInt())).thenReturn(task) + + controller.moveToDesktop(task.taskId) + with(getLatestMoveToDesktopWct()){ + assertLaunchTaskAt(0, task.taskId, WINDOWING_MODE_FREEFORM) + } } @Test @@ -666,6 +676,17 @@ class DesktopTasksControllerTest : ShellTestCase() { } @Test + fun moveToDesktop_deviceNotSupported_doesNothing() { + val task = setUpFullscreenTask() + + // Simulate non compatible device + doReturn(false).`when` { DesktopModeStatus.isDesktopModeSupported(any()) } + + controller.moveToDesktop(task) + verifyWCTNotExecuted() + } + + @Test fun moveToDesktop_deviceNotSupported_deviceRestrictionsOverridden_taskIsMovedToDesktop() { val task = setUpFullscreenTask() @@ -1834,6 +1855,20 @@ private fun WindowContainerTransaction.assertPendingIntentAt(index: Int, intent: assertThat(op.pendingIntent?.intent?.component).isEqualTo(intent.component) } +private fun WindowContainerTransaction.assertLaunchTaskAt( + index: Int, + taskId: Int, + windowingMode: Int +) { + val keyLaunchWindowingMode = "android.activity.windowingMode" + + assertIndexInBounds(index) + val op = hierarchyOps[index] + assertThat(op.type).isEqualTo(HIERARCHY_OP_TYPE_LAUNCH_TASK) + assertThat(op.launchOptions?.getInt(LAUNCH_KEY_TASK_ID)).isEqualTo(taskId) + assertThat(op.launchOptions?.getInt(keyLaunchWindowingMode, WINDOWING_MODE_UNDEFINED)) + .isEqualTo(windowingMode) +} private fun WindowContainerTransaction?.anyDensityConfigChange( token: WindowContainerToken ): Boolean { @@ -1841,3 +1876,7 @@ private fun WindowContainerTransaction?.anyDensityConfigChange( change.key == token.asBinder() && ((change.value.configSetMask and CONFIG_DENSITY) != 0) } ?: false } +private fun createTaskInfo(id: Int) = RecentTaskInfo().apply { + taskId = id + token = WindowContainerToken(mock(IWindowContainerToken::class.java)) +} diff --git a/media/tests/MediaRouter/Android.bp b/media/tests/MediaRouter/Android.bp index 61b18c88e734..d21cb9319885 100644 --- a/media/tests/MediaRouter/Android.bp +++ b/media/tests/MediaRouter/Android.bp @@ -9,6 +9,7 @@ package { android_test { name: "mediaroutertest", + team: "trendy_team_android_media_solutions", srcs: ["**/*.java"], diff --git a/packages/SystemUI/aconfig/systemui.aconfig b/packages/SystemUI/aconfig/systemui.aconfig index 36bad5e622cf..1df9c88e48ac 100644 --- a/packages/SystemUI/aconfig/systemui.aconfig +++ b/packages/SystemUI/aconfig/systemui.aconfig @@ -184,6 +184,13 @@ flag { } flag { + name: "notification_avalanche_throttle_hun" + namespace: "systemui" + description: "(currently unused) During notification avalanche, throttle HUNs showing in fast succession." + bug: "307288824" +} + +flag { name: "notification_avalanche_suppression" namespace: "systemui" description: "After notification avalanche floodgate event, suppress HUNs completely." @@ -990,6 +997,13 @@ flag { } flag { + name: "glanceable_hub_shortcut_button" + namespace: "systemui" + description: "Shows a button over the dream and lock screen to open the glanceable hub" + bug: "339667383" +} + +flag { name: "glanceable_hub_gesture_handle" namespace: "systemui" description: "Shows a vertical bar at the right edge to indicate the user can swipe to open the glanceable hub" diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/TextAnimator.kt b/packages/SystemUI/animation/src/com/android/systemui/animation/TextAnimator.kt index b1240252796f..978943ae7f7c 100644 --- a/packages/SystemUI/animation/src/com/android/systemui/animation/TextAnimator.kt +++ b/packages/SystemUI/animation/src/com/android/systemui/animation/TextAnimator.kt @@ -179,8 +179,15 @@ class TextAnimator( private val fontVariationUtils = FontVariationUtils() - fun updateLayout(layout: Layout) { + fun updateLayout(layout: Layout, textSize: Float = -1f) { textInterpolator.layout = layout + + if (textSize >= 0) { + textInterpolator.targetPaint.textSize = textSize + textInterpolator.basePaint.textSize = textSize + textInterpolator.onTargetPaintModified() + textInterpolator.onBasePaintModified() + } } fun isRunning(): Boolean { diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/Element.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/Element.kt index d2a1de3e4695..f0fb9f62fdad 100644 --- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/Element.kt +++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/Element.kt @@ -371,96 +371,57 @@ private fun prepareInterruption( transition: TransitionState.Transition, previousTransition: TransitionState.Transition, ) { - val previousUniqueState = reconcileStates(element, previousTransition) - if (previousUniqueState == null) { - reconcileStates(element, transition) - return - } - - val fromSceneState = element.sceneStates[transition.fromScene] - val toSceneState = element.sceneStates[transition.toScene] - - if ( - fromSceneState == null || - toSceneState == null || - sharedElementTransformation(element.key, transition)?.enabled != false - ) { - // If there is only one copy of the element or if the element is shared, animate deltas in - // both scenes. - fromSceneState?.updateValuesBeforeInterruption(previousUniqueState) - toSceneState?.updateValuesBeforeInterruption(previousUniqueState) - } + val sceneStates = element.sceneStates + sceneStates[previousTransition.fromScene]?.selfUpdateValuesBeforeInterruption() + sceneStates[previousTransition.toScene]?.selfUpdateValuesBeforeInterruption() + sceneStates[transition.fromScene]?.selfUpdateValuesBeforeInterruption() + sceneStates[transition.toScene]?.selfUpdateValuesBeforeInterruption() + + reconcileStates(element, previousTransition) + reconcileStates(element, transition) } /** * Reconcile the state of [element] in the fromScene and toScene of [transition] so that the values * before interruption have their expected values, taking shared transitions into account. - * - * If the element had a unique state, i.e. it is shared in [transition] or it is only present in one - * of the scenes, return it. */ private fun reconcileStates( element: Element, transition: TransitionState.Transition, -): Element.SceneState? { - val fromSceneState = element.sceneStates[transition.fromScene] - val toSceneState = element.sceneStates[transition.toScene] - when { - // Element is in both scenes. - fromSceneState != null && toSceneState != null -> { - val isSharedTransformationDisabled = - sharedElementTransformation(element.key, transition)?.enabled == false - when { - // Element shared transition is disabled so the element is placed in both scenes. - isSharedTransformationDisabled -> { - fromSceneState.updateValuesBeforeInterruption(fromSceneState) - toSceneState.updateValuesBeforeInterruption(toSceneState) - return null - } - - // Element is shared and placed in fromScene only. - fromSceneState.lastOffset != Offset.Unspecified -> { - fromSceneState.updateValuesBeforeInterruption(fromSceneState) - toSceneState.updateValuesBeforeInterruption(fromSceneState) - return fromSceneState - } - - // Element is shared and placed in toScene only. - toSceneState.lastOffset != Offset.Unspecified -> { - fromSceneState.updateValuesBeforeInterruption(toSceneState) - toSceneState.updateValuesBeforeInterruption(toSceneState) - return toSceneState - } - - // Element is in none of the scenes. - else -> { - fromSceneState.updateValuesBeforeInterruption(null) - toSceneState.updateValuesBeforeInterruption(null) - return null - } - } - } - - // Element is only in fromScene. - fromSceneState != null -> { - fromSceneState.updateValuesBeforeInterruption(fromSceneState) - return fromSceneState - } +) { + val fromSceneState = element.sceneStates[transition.fromScene] ?: return + val toSceneState = element.sceneStates[transition.toScene] ?: return + if (!isSharedElementEnabled(element.key, transition)) { + return + } - // Element is only in toScene. - toSceneState != null -> { - toSceneState.updateValuesBeforeInterruption(toSceneState) - return toSceneState - } - else -> return null + if ( + fromSceneState.offsetBeforeInterruption != Offset.Unspecified && + toSceneState.offsetBeforeInterruption == Offset.Unspecified + ) { + // Element is shared and placed in fromScene only. + toSceneState.updateValuesBeforeInterruption(fromSceneState) + } else if ( + toSceneState.offsetBeforeInterruption != Offset.Unspecified && + fromSceneState.offsetBeforeInterruption == Offset.Unspecified + ) { + // Element is shared and placed in toScene only. + fromSceneState.updateValuesBeforeInterruption(toSceneState) } } -private fun Element.SceneState.updateValuesBeforeInterruption(lastState: Element.SceneState?) { - offsetBeforeInterruption = lastState?.lastOffset ?: Offset.Unspecified - sizeBeforeInterruption = lastState?.lastSize ?: Element.SizeUnspecified - scaleBeforeInterruption = lastState?.lastScale ?: Scale.Unspecified - alphaBeforeInterruption = lastState?.lastAlpha ?: Element.AlphaUnspecified +private fun Element.SceneState.selfUpdateValuesBeforeInterruption() { + offsetBeforeInterruption = lastOffset + sizeBeforeInterruption = lastSize + scaleBeforeInterruption = lastScale + alphaBeforeInterruption = lastAlpha +} + +private fun Element.SceneState.updateValuesBeforeInterruption(lastState: Element.SceneState) { + offsetBeforeInterruption = lastState.offsetBeforeInterruption + sizeBeforeInterruption = lastState.sizeBeforeInterruption + scaleBeforeInterruption = lastState.scaleBeforeInterruption + alphaBeforeInterruption = lastState.alphaBeforeInterruption clearInterruptionDeltas() } diff --git a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/AnimatableClockView.kt b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/AnimatableClockView.kt index bdeab797d165..079c1d922275 100644 --- a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/AnimatableClockView.kt +++ b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/AnimatableClockView.kt @@ -17,7 +17,6 @@ package com.android.systemui.shared.clocks import android.animation.TimeInterpolator import android.annotation.ColorInt -import android.annotation.FloatRange import android.annotation.IntRange import android.annotation.SuppressLint import android.content.Context @@ -27,7 +26,7 @@ import android.text.TextUtils import android.text.format.DateFormat import android.util.AttributeSet import android.util.MathUtils.constrainedMap -import android.util.TypedValue +import android.util.TypedValue.COMPLEX_UNIT_PX import android.view.View import android.view.View.MeasureSpec.EXACTLY import android.widget.TextView @@ -219,9 +218,7 @@ constructor( override fun setTextSize(type: Int, size: Float) { super.setTextSize(type, size) - if (type == TypedValue.COMPLEX_UNIT_PX) { - lastUnconstrainedTextSize = size - } + lastUnconstrainedTextSize = if (type == COMPLEX_UNIT_PX) size else Float.MAX_VALUE } @SuppressLint("DrawAllocation") @@ -234,23 +231,19 @@ constructor( MeasureSpec.getMode(heightMeasureSpec) == EXACTLY ) { // Call straight into TextView.setTextSize to avoid setting lastUnconstrainedTextSize - super.setTextSize( - TypedValue.COMPLEX_UNIT_PX, - min(lastUnconstrainedTextSize, MeasureSpec.getSize(heightMeasureSpec) / 2F) - ) + val size = min(lastUnconstrainedTextSize, MeasureSpec.getSize(heightMeasureSpec) / 2F) + super.setTextSize(COMPLEX_UNIT_PX, size) } super.onMeasure(widthMeasureSpec, heightMeasureSpec) - val animator = textAnimator - if (animator == null) { - textAnimator = - textAnimatorFactory(layout, ::invalidate)?.also { - onTextAnimatorInitialized?.invoke(it) - onTextAnimatorInitialized = null - } - } else { - animator.updateLayout(layout) - } + textAnimator?.let { animator -> animator.updateLayout(layout, textSize) } + ?: run { + textAnimator = + textAnimatorFactory(layout, ::invalidate).also { + onTextAnimatorInitialized?.invoke(it) + onTextAnimatorInitialized = null + } + } if (migratedClocks && hasCustomPositionUpdatedAnimation) { // Expand width to avoid clock being clipped during stepping animation @@ -307,18 +300,18 @@ constructor( logger.d("animateColorChange") setTextStyle( weight = lockScreenWeight, - textSize = -1f, color = null, /* using current color */ animate = false, + interpolator = null, duration = 0, delay = 0, onAnimationEnd = null ) setTextStyle( weight = lockScreenWeight, - textSize = -1f, color = lockScreenColor, animate = true, + interpolator = null, duration = COLOR_ANIM_DURATION, delay = 0, onAnimationEnd = null @@ -329,16 +322,15 @@ constructor( logger.d("animateAppearOnLockscreen") setTextStyle( weight = dozingWeight, - textSize = -1f, color = lockScreenColor, animate = false, + interpolator = null, duration = 0, delay = 0, onAnimationEnd = null ) setTextStyle( weight = lockScreenWeight, - textSize = -1f, color = lockScreenColor, animate = true, duration = APPEAR_ANIM_DURATION, @@ -356,16 +348,15 @@ constructor( logger.d("animateFoldAppear") setTextStyle( weight = lockScreenWeightInternal, - textSize = -1f, color = lockScreenColor, animate = false, + interpolator = null, duration = 0, delay = 0, onAnimationEnd = null ) setTextStyle( weight = dozingWeightInternal, - textSize = -1f, color = dozingColor, animate = animate, interpolator = Interpolators.EMPHASIZED_DECELERATE, @@ -385,9 +376,9 @@ constructor( val startAnimPhase2 = Runnable { setTextStyle( weight = if (isDozing()) dozingWeight else lockScreenWeight, - textSize = -1f, color = null, animate = true, + interpolator = null, duration = CHARGE_ANIM_DURATION_PHASE_1, delay = 0, onAnimationEnd = null @@ -395,9 +386,9 @@ constructor( } setTextStyle( weight = if (isDozing()) lockScreenWeight else dozingWeight, - textSize = -1f, color = null, animate = true, + interpolator = null, duration = CHARGE_ANIM_DURATION_PHASE_0, delay = chargeAnimationDelay.toLong(), onAnimationEnd = startAnimPhase2 @@ -408,9 +399,9 @@ constructor( logger.d("animateDoze") setTextStyle( weight = if (isDozing) dozingWeight else lockScreenWeight, - textSize = -1f, color = if (isDozing) dozingColor else lockScreenColor, animate = animate, + interpolator = null, duration = DOZE_ANIM_DURATION, delay = 0, onAnimationEnd = null @@ -448,7 +439,6 @@ constructor( */ private fun setTextStyle( @IntRange(from = 0, to = 1000) weight: Int, - @FloatRange(from = 0.0) textSize: Float, color: Int?, animate: Boolean, interpolator: TimeInterpolator?, @@ -459,7 +449,6 @@ constructor( textAnimator?.let { it.setTextStyle( weight = weight, - textSize = textSize, color = color, animate = animate && isAnimationEnabled, duration = duration, @@ -474,7 +463,6 @@ constructor( onTextAnimatorInitialized = { textAnimator -> textAnimator.setTextStyle( weight = weight, - textSize = textSize, color = color, animate = false, duration = duration, @@ -487,27 +475,6 @@ constructor( } } - private fun setTextStyle( - @IntRange(from = 0, to = 1000) weight: Int, - @FloatRange(from = 0.0) textSize: Float, - color: Int?, - animate: Boolean, - duration: Long, - delay: Long, - onAnimationEnd: Runnable? - ) { - setTextStyle( - weight = weight, - textSize = textSize, - color = color, - animate = animate, - interpolator = null, - duration = duration, - delay = delay, - onAnimationEnd = onAnimationEnd - ) - } - fun refreshFormat() = refreshFormat(DateFormat.is24HourFormat(context)) fun refreshFormat(use24HourFormat: Boolean) { Patterns.update(context) diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/touch/CommunalTouchHandlerTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/touch/CommunalTouchHandlerTest.java index 29fbee01a18b..7936ccc1ddd1 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/touch/CommunalTouchHandlerTest.java +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/touch/CommunalTouchHandlerTest.java @@ -108,7 +108,7 @@ public class CommunalTouchHandlerTest extends SysuiTestCase { mTouchHandler.onSessionStart(mTouchSession); verify(mTouchSession).registerInputListener(inputEventListenerArgumentCaptor.capture()); inputEventListenerArgumentCaptor.getValue().onInputEvent(motionEvent); - verify(mCentralSurfaces).handleDreamTouch(motionEvent); + verify(mCentralSurfaces).handleCommunalHubTouch(motionEvent); } @Test diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardQuickAffordanceLegacySettingSyncerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardQuickAffordanceLegacySettingSyncerTest.kt index 26fcb234843d..49d039970a24 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardQuickAffordanceLegacySettingSyncerTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardQuickAffordanceLegacySettingSyncerTest.kt @@ -90,6 +90,7 @@ class KeyguardQuickAffordanceLegacySettingSyncerTest : SysuiTestCase() { .thenReturn(FakeSharedPreferences()) }, userTracker = FakeUserTracker(), + systemSettings = FakeSettings(), broadcastDispatcher = fakeBroadcastDispatcher, ) settings = FakeSettings() diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardQuickAffordanceLocalUserSelectionManagerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardQuickAffordanceLocalUserSelectionManagerTest.kt index 99a01858471c..9ab1ac116b0d 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardQuickAffordanceLocalUserSelectionManagerTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardQuickAffordanceLocalUserSelectionManagerTest.kt @@ -22,13 +22,14 @@ import android.content.SharedPreferences import android.content.pm.UserInfo import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest -import com.android.systemui.res.R import com.android.systemui.SysuiTestCase import com.android.systemui.backup.BackupHelper +import com.android.systemui.res.R import com.android.systemui.settings.FakeUserTracker import com.android.systemui.settings.UserFileManager import com.android.systemui.util.FakeSharedPreferences import com.android.systemui.util.mockito.whenever +import com.android.systemui.util.settings.FakeSettings import com.google.common.truth.Truth.assertThat import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.ExperimentalCoroutinesApi @@ -80,6 +81,7 @@ class KeyguardQuickAffordanceLocalUserSelectionManagerTest : SysuiTestCase() { context = context, userFileManager = userFileManager, userTracker = userTracker, + systemSettings = FakeSettings(), broadcastDispatcher = fakeBroadcastDispatcher, ) } diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/repository/KeyguardQuickAffordanceRepositoryTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/repository/KeyguardQuickAffordanceRepositoryTest.kt index 567e0a9717fc..159ce36bea2d 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/repository/KeyguardQuickAffordanceRepositoryTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/repository/KeyguardQuickAffordanceRepositoryTest.kt @@ -21,7 +21,6 @@ import android.content.pm.UserInfo import android.os.UserHandle import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest -import com.android.systemui.res.R import com.android.systemui.SysuiTestCase import com.android.systemui.coroutines.collectLastValue import com.android.systemui.keyguard.data.quickaffordance.FakeKeyguardQuickAffordanceConfig @@ -32,6 +31,7 @@ import com.android.systemui.keyguard.data.quickaffordance.KeyguardQuickAffordanc import com.android.systemui.keyguard.data.quickaffordance.KeyguardQuickAffordanceRemoteUserSelectionManager import com.android.systemui.keyguard.shared.model.KeyguardQuickAffordancePickerRepresentation import com.android.systemui.keyguard.shared.model.KeyguardSlotPickerRepresentation +import com.android.systemui.res.R import com.android.systemui.settings.FakeUserTracker import com.android.systemui.settings.UserFileManager import com.android.systemui.shared.customization.data.content.FakeCustomizationProviderClient @@ -91,6 +91,7 @@ class KeyguardQuickAffordanceRepositoryTest : SysuiTestCase() { .thenReturn(FakeSharedPreferences()) }, userTracker = userTracker, + systemSettings = FakeSettings(), broadcastDispatcher = fakeBroadcastDispatcher, ) client1 = FakeCustomizationProviderClient() diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorTest.kt index 2d77f4f1c436..78a116737349 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorTest.kt @@ -148,6 +148,7 @@ class KeyguardQuickAffordanceInteractorTest : SysuiTestCase() { .thenReturn(FakeSharedPreferences()) }, userTracker = userTracker, + systemSettings = FakeSettings(), broadcastDispatcher = fakeBroadcastDispatcher, ) val remoteUserSelectionManager = diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/WindowManagerLockscreenVisibilityInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/WindowManagerLockscreenVisibilityInteractorTest.kt index 2b8a644162c7..9dc930babc10 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/WindowManagerLockscreenVisibilityInteractorTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/WindowManagerLockscreenVisibilityInteractorTest.kt @@ -27,18 +27,22 @@ import com.android.systemui.coroutines.collectValues import com.android.systemui.deviceentry.domain.interactor.deviceUnlockedInteractor import com.android.systemui.flags.DisableSceneContainer import com.android.systemui.flags.EnableSceneContainer +import com.android.systemui.keyguard.data.repository.deviceEntryFingerprintAuthRepository import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository import com.android.systemui.keyguard.shared.model.KeyguardState +import com.android.systemui.keyguard.shared.model.SuccessFingerprintAuthenticationStatus import com.android.systemui.keyguard.shared.model.TransitionState import com.android.systemui.keyguard.shared.model.TransitionStep import com.android.systemui.kosmos.testScope import com.android.systemui.scene.data.repository.sceneContainerRepository +import com.android.systemui.scene.data.repository.setSceneTransition import com.android.systemui.scene.domain.interactor.sceneInteractor import com.android.systemui.scene.shared.model.Scenes import com.android.systemui.testKosmos import com.android.systemui.util.mockito.mock import com.android.systemui.util.mockito.whenever import com.google.common.truth.Truth.assertThat +import com.google.common.truth.Truth.assertWithMessage import junit.framework.Assert.assertEquals import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.flowOf @@ -192,6 +196,175 @@ class WindowManagerLockscreenVisibilityInteractorTest : SysuiTestCase() { } @Test + @EnableSceneContainer + fun surfaceBehindVisibility_fromLockscreenToGone_trueThroughout() = + testScope.runTest { + val isSurfaceBehindVisible by collectLastValue(underTest.value.surfaceBehindVisibility) + val currentScene by collectLastValue(kosmos.sceneInteractor.currentScene) + + // Before the transition, we start on Lockscreen so the surface should start invisible. + kosmos.setSceneTransition(ObservableTransitionState.Idle(Scenes.Lockscreen)) + assertThat(currentScene).isEqualTo(Scenes.Lockscreen) + assertThat(isSurfaceBehindVisible).isFalse() + + // Unlocked with fingerprint. + kosmos.deviceEntryFingerprintAuthRepository.setAuthenticationStatus( + SuccessFingerprintAuthenticationStatus(0, true) + ) + + // Start the transition to Gone, the surface should become immediately visible. + kosmos.setSceneTransition( + ObservableTransitionState.Transition( + fromScene = Scenes.Lockscreen, + toScene = Scenes.Gone, + isInitiatedByUserInput = false, + isUserInputOngoing = flowOf(false), + progress = flowOf(0.3f), + currentScene = flowOf(Scenes.Lockscreen), + ) + ) + assertThat(currentScene).isEqualTo(Scenes.Lockscreen) + assertThat(isSurfaceBehindVisible).isTrue() + + // Towards the end of the transition, the surface should continue to be visible. + kosmos.setSceneTransition( + ObservableTransitionState.Transition( + fromScene = Scenes.Lockscreen, + toScene = Scenes.Gone, + isInitiatedByUserInput = false, + isUserInputOngoing = flowOf(false), + progress = flowOf(0.9f), + currentScene = flowOf(Scenes.Gone), + ) + ) + assertThat(currentScene).isEqualTo(Scenes.Lockscreen) + assertThat(isSurfaceBehindVisible).isTrue() + + // After the transition, settles on Gone. Surface behind should stay visible now. + kosmos.setSceneTransition(ObservableTransitionState.Idle(Scenes.Gone)) + kosmos.sceneInteractor.changeScene(Scenes.Gone, "") + assertThat(currentScene).isEqualTo(Scenes.Gone) + assertThat(isSurfaceBehindVisible).isTrue() + } + + @Test + @EnableSceneContainer + fun surfaceBehindVisibility_fromBouncerToGone_becomesTrue() = + testScope.runTest { + val isSurfaceBehindVisible by collectLastValue(underTest.value.surfaceBehindVisibility) + val currentScene by collectLastValue(kosmos.sceneInteractor.currentScene) + + // Before the transition, we start on Bouncer so the surface should start invisible. + kosmos.setSceneTransition(ObservableTransitionState.Idle(Scenes.Bouncer)) + kosmos.sceneInteractor.changeScene(Scenes.Bouncer, "") + assertThat(currentScene).isEqualTo(Scenes.Bouncer) + assertThat(isSurfaceBehindVisible).isFalse() + + // Unlocked with fingerprint. + kosmos.deviceEntryFingerprintAuthRepository.setAuthenticationStatus( + SuccessFingerprintAuthenticationStatus(0, true) + ) + + // Start the transition to Gone, the surface should remain invisible prior to hitting + // the + // threshold. + kosmos.setSceneTransition( + ObservableTransitionState.Transition( + fromScene = Scenes.Bouncer, + toScene = Scenes.Gone, + isInitiatedByUserInput = false, + isUserInputOngoing = flowOf(false), + progress = + flowOf( + FromPrimaryBouncerTransitionInteractor + .TO_GONE_SURFACE_BEHIND_VISIBLE_THRESHOLD + ), + currentScene = flowOf(Scenes.Bouncer), + ) + ) + assertThat(currentScene).isEqualTo(Scenes.Bouncer) + assertThat(isSurfaceBehindVisible).isFalse() + + // Once the transition passes the threshold, the surface should become visible. + kosmos.setSceneTransition( + ObservableTransitionState.Transition( + fromScene = Scenes.Bouncer, + toScene = Scenes.Gone, + isInitiatedByUserInput = false, + isUserInputOngoing = flowOf(false), + progress = + flowOf( + FromPrimaryBouncerTransitionInteractor + .TO_GONE_SURFACE_BEHIND_VISIBLE_THRESHOLD + 0.01f + ), + currentScene = flowOf(Scenes.Gone), + ) + ) + assertThat(currentScene).isEqualTo(Scenes.Bouncer) + assertThat(isSurfaceBehindVisible).isTrue() + + // After the transition, settles on Gone. Surface behind should stay visible now. + kosmos.setSceneTransition(ObservableTransitionState.Idle(Scenes.Gone)) + kosmos.sceneInteractor.changeScene(Scenes.Gone, "") + assertThat(currentScene).isEqualTo(Scenes.Gone) + assertThat(isSurfaceBehindVisible).isTrue() + } + + @Test + @EnableSceneContainer + fun surfaceBehindVisibility_idleWhileUnlocked_alwaysTrue() = + testScope.runTest { + val isSurfaceBehindVisible by collectLastValue(underTest.value.surfaceBehindVisibility) + val currentScene by collectLastValue(kosmos.sceneInteractor.currentScene) + + // Unlocked with fingerprint. + kosmos.deviceEntryFingerprintAuthRepository.setAuthenticationStatus( + SuccessFingerprintAuthenticationStatus(0, true) + ) + kosmos.setSceneTransition(ObservableTransitionState.Idle(Scenes.Gone)) + kosmos.sceneInteractor.changeScene(Scenes.Gone, "") + assertThat(currentScene).isEqualTo(Scenes.Gone) + + listOf( + Scenes.Shade, + Scenes.QuickSettings, + Scenes.Shade, + Scenes.Gone, + ) + .forEach { scene -> + kosmos.setSceneTransition(ObservableTransitionState.Idle(scene)) + kosmos.sceneInteractor.changeScene(scene, "") + assertThat(currentScene).isEqualTo(scene) + assertWithMessage("Unexpected visibility for scene \"${scene.debugName}\"") + .that(isSurfaceBehindVisible) + .isTrue() + } + } + + @Test + @EnableSceneContainer + fun surfaceBehindVisibility_idleWhileLocked_alwaysFalse() = + testScope.runTest { + val isSurfaceBehindVisible by collectLastValue(underTest.value.surfaceBehindVisibility) + val currentScene by collectLastValue(kosmos.sceneInteractor.currentScene) + assertThat(currentScene).isEqualTo(Scenes.Lockscreen) + + listOf( + Scenes.Shade, + Scenes.QuickSettings, + Scenes.Shade, + Scenes.Lockscreen, + ) + .forEach { scene -> + kosmos.setSceneTransition(ObservableTransitionState.Idle(scene)) + kosmos.sceneInteractor.changeScene(scene, "") + assertWithMessage("Unexpected visibility for scene \"${scene.debugName}\"") + .that(isSurfaceBehindVisible) + .isFalse() + } + } + + @Test @DisableSceneContainer fun testUsingGoingAwayAnimation_duringTransitionToGone() = testScope.runTest { diff --git a/packages/SystemUI/res/drawable/ic_widgets.xml b/packages/SystemUI/res/drawable/ic_widgets.xml new file mode 100644 index 000000000000..b21d047f9386 --- /dev/null +++ b/packages/SystemUI/res/drawable/ic_widgets.xml @@ -0,0 +1,27 @@ +<!-- + ~ Copyright (C) 2024 The Android Open Source Project + ~ + ~ Licensed under the Apache License, Version 2.0 (the "License"); + ~ you may not use this file except in compliance with the License. + ~ You may obtain a copy of the License at + ~ + ~ http://www.apache.org/licenses/LICENSE-2.0 + ~ + ~ Unless required by applicable law or agreed to in writing, software + ~ distributed under the License is distributed on an "AS IS" BASIS, + ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + ~ See the License for the specific language governing permissions and + ~ limitations under the License. + --> + +<!-- go/gm2-icons, from gs_widgets_vd_theme_24.xml --> +<vector xmlns:android="http://schemas.android.com/apk/res/android" + android:width="24dp" + android:height="24dp" + android:tint="?attr/colorControlNormal" + android:viewportHeight="960" + android:viewportWidth="960"> + <path + android:fillColor="@android:color/black" + android:pathData="M666,520L440,294L666,68L892,294L666,520ZM120,440L120,120L440,120L440,440L120,440ZM520,840L520,520L840,520L840,840L520,840ZM120,840L120,520L440,520L440,840L120,840ZM200,360L360,360L360,200L200,200L200,360ZM667,408L780,295L667,182L554,295L667,408ZM600,760L760,760L760,600L600,600L600,760ZM200,760L360,760L360,600L200,600L200,760ZM360,360L360,360L360,360L360,360L360,360ZM554,295L554,295L554,295L554,295L554,295ZM360,600L360,600L360,600L360,600L360,600ZM600,600L600,600L600,600L600,600L600,600Z" /> +</vector>
\ No newline at end of file diff --git a/packages/SystemUI/res/layout/dream_overlay_open_hub_chip.xml b/packages/SystemUI/res/layout/dream_overlay_open_hub_chip.xml new file mode 100644 index 000000000000..be063a9631e8 --- /dev/null +++ b/packages/SystemUI/res/layout/dream_overlay_open_hub_chip.xml @@ -0,0 +1,26 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + ~ Copyright (C) 2024 The Android Open Source Project + ~ + ~ Licensed under the Apache License, Version 2.0 (the "License"); + ~ you may not use this file except in compliance with the License. + ~ You may obtain a copy of the License at + ~ + ~ http://www.apache.org/licenses/LICENSE-2.0 + ~ + ~ Unless required by applicable law or agreed to in writing, software + ~ distributed under the License is distributed on an "AS IS" BASIS, + ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + ~ See the License for the specific language governing permissions and + ~ limitations under the License. +--> +<com.android.systemui.animation.view.LaunchableImageView + xmlns:android="http://schemas.android.com/apk/res/android" + android:layout_height="@dimen/dream_overlay_bottom_affordance_height" + android:layout_width="@dimen/dream_overlay_bottom_affordance_width" + android:layout_gravity="bottom|start" + android:padding="@dimen/dream_overlay_bottom_affordance_padding" + android:scaleType="fitCenter" + android:tint="?android:attr/textColorPrimary" + android:src="@drawable/ic_widgets" + android:contentDescription="@string/accessibility_action_open_communal_hub" /> diff --git a/packages/SystemUI/src/com/android/keyguard/ClockEventController.kt b/packages/SystemUI/src/com/android/keyguard/ClockEventController.kt index 3f3bb0bc94b6..86c807bf9d07 100644 --- a/packages/SystemUI/src/com/android/keyguard/ClockEventController.kt +++ b/packages/SystemUI/src/com/android/keyguard/ClockEventController.kt @@ -15,12 +15,12 @@ */ package com.android.keyguard -import android.os.Trace import android.content.BroadcastReceiver import android.content.Context import android.content.Intent import android.content.IntentFilter import android.content.res.Resources +import android.os.Trace import android.text.format.DateFormat import android.util.Log import android.util.TypedValue @@ -466,15 +466,11 @@ constructor( largeRegionSampler?.stopRegionSampler() smallTimeListener?.stop() largeTimeListener?.stop() - clock - ?.smallClock - ?.view - ?.removeOnAttachStateChangeListener(smallClockOnAttachStateChangeListener) + clock?.apply { + smallClock.view.removeOnAttachStateChangeListener(smallClockOnAttachStateChangeListener) + largeClock.view.removeOnAttachStateChangeListener(largeClockOnAttachStateChangeListener) + } smallClockFrame?.viewTreeObserver?.removeOnGlobalLayoutListener(onGlobalLayoutListener) - clock - ?.largeClock - ?.view - ?.removeOnAttachStateChangeListener(largeClockOnAttachStateChangeListener) } /** @@ -505,15 +501,17 @@ constructor( } } - private fun updateFontSizes() { + fun updateFontSizes() { clock?.run { - smallClock.events.onFontSettingChanged( - resources.getDimensionPixelSize(R.dimen.small_clock_text_size).toFloat() - ) + smallClock.events.onFontSettingChanged(getSmallClockSizePx()) largeClock.events.onFontSettingChanged(getLargeClockSizePx()) } } + private fun getSmallClockSizePx(): Float { + return resources.getDimensionPixelSize(R.dimen.small_clock_text_size).toFloat() + } + private fun getLargeClockSizePx(): Float { return if (largeClockOnSecondaryDisplay) { resources.getDimensionPixelSize(R.dimen.presentation_clock_text_size).toFloat() @@ -549,9 +547,8 @@ constructor( it.copy(value = 1f - it.value) }, keyguardTransitionInteractor.transition(Edge.create(LOCKSCREEN, AOD)), - ).filter { - it.transitionState != TransitionState.FINISHED - } + ) + .filter { it.transitionState != TransitionState.FINISHED } .collect { handleDoze(it.value) } } } @@ -574,28 +571,27 @@ constructor( internal fun listenForAnyStateToLockscreenTransition(scope: CoroutineScope): Job { return scope.launch { keyguardTransitionInteractor - .transitionStepsToState(LOCKSCREEN) - .filter { it.transitionState == TransitionState.STARTED } - .filter { it.from != AOD } - .collect { handleDoze(0f) } + .transitionStepsToState(LOCKSCREEN) + .filter { it.transitionState == TransitionState.STARTED } + .filter { it.from != AOD } + .collect { handleDoze(0f) } } } /** - * When keyguard is displayed due to pulsing notifications when AOD is off, - * we should make sure clock is in dozing state instead of LS state + * When keyguard is displayed due to pulsing notifications when AOD is off, we should make sure + * clock is in dozing state instead of LS state */ @VisibleForTesting internal fun listenForAnyStateToDozingTransition(scope: CoroutineScope): Job { return scope.launch { keyguardTransitionInteractor - .transitionStepsToState(DOZING) - .filter { it.transitionState == TransitionState.FINISHED } - .collect { handleDoze(1f) } + .transitionStepsToState(DOZING) + .filter { it.transitionState == TransitionState.FINISHED } + .collect { handleDoze(1f) } } } - @VisibleForTesting internal fun listenForDozing(scope: CoroutineScope): Job { return scope.launch { diff --git a/packages/SystemUI/src/com/android/systemui/SwipeHelper.java b/packages/SystemUI/src/com/android/systemui/SwipeHelper.java index 27b2b92ab899..63ad41a808dc 100644 --- a/packages/SystemUI/src/com/android/systemui/SwipeHelper.java +++ b/packages/SystemUI/src/com/android/systemui/SwipeHelper.java @@ -20,7 +20,6 @@ import static androidx.dynamicanimation.animation.DynamicAnimation.TRANSLATION_X import static androidx.dynamicanimation.animation.FloatPropertyCompat.createFloatPropertyCompat; import static com.android.systemui.classifier.Classifier.NOTIFICATION_DISMISS; -import static com.android.systemui.flags.Flags.SWIPE_UNCLEARED_TRANSIENT_VIEW_FIX; import static com.android.systemui.statusbar.notification.NotificationUtils.logKey; import android.animation.Animator; @@ -481,16 +480,11 @@ public class SwipeHelper implements Gefingerpoken, Dumpable { updateSwipeProgressFromOffset(animView, canBeDismissed); mDismissPendingMap.remove(animView); boolean wasRemoved = false; - if (animView instanceof ExpandableNotificationRow) { - ExpandableNotificationRow row = (ExpandableNotificationRow) animView; - if (mFeatureFlags.isEnabled(SWIPE_UNCLEARED_TRANSIENT_VIEW_FIX)) { - // If the view is already removed from its parent and added as Transient, - // we need to clean the transient view upon animation end - wasRemoved = row.getTransientContainer() != null - || row.getParent() == null || row.isRemoved(); - } else { - wasRemoved = row.isRemoved(); - } + if (animView instanceof ExpandableNotificationRow row) { + // If the view is already removed from its parent and added as Transient, + // we need to clean the transient view upon animation end + wasRemoved = row.getTransientContainer() != null + || row.getParent() == null || row.isRemoved(); } if (!mCancelled || wasRemoved) { mCallback.onChildDismissed(animView); diff --git a/packages/SystemUI/src/com/android/systemui/complication/DreamHomeControlsComplication.java b/packages/SystemUI/src/com/android/systemui/complication/DreamHomeControlsComplication.java index afa23755d937..e284bc7752cf 100644 --- a/packages/SystemUI/src/com/android/systemui/complication/DreamHomeControlsComplication.java +++ b/packages/SystemUI/src/com/android/systemui/complication/DreamHomeControlsComplication.java @@ -18,6 +18,7 @@ package com.android.systemui.complication; import static com.android.systemui.complication.dagger.DreamHomeControlsComplicationComponent.DreamHomeControlsModule.DREAM_HOME_CONTROLS_CHIP_VIEW; import static com.android.systemui.complication.dagger.RegisteredComplicationsModule.DREAM_HOME_CONTROLS_CHIP_LAYOUT_PARAMS; +import static com.android.systemui.complication.dagger.RegisteredComplicationsModule.OPEN_HUB_CHIP_REPLACE_HOME_CONTROLS; import static com.android.systemui.controls.dagger.ControlsComponent.Visibility.AVAILABLE; import static com.android.systemui.controls.dagger.ControlsComponent.Visibility.AVAILABLE_AFTER_UNLOCK; import static com.android.systemui.controls.dagger.ControlsComponent.Visibility.UNAVAILABLE; @@ -35,6 +36,7 @@ import androidx.annotation.Nullable; import com.android.internal.logging.UiEventLogger; import com.android.settingslib.Utils; import com.android.systemui.CoreStartable; +import com.android.systemui.Flags; import com.android.systemui.animation.ActivityTransitionAnimator; import com.android.systemui.complication.dagger.DreamHomeControlsComplicationComponent; import com.android.systemui.controls.ControlsServiceInfo; @@ -89,6 +91,7 @@ public class DreamHomeControlsComplication implements Complication { private final DreamHomeControlsComplication mComplication; private final DreamOverlayStateController mDreamOverlayStateController; private final ControlsComponent mControlsComponent; + private final boolean mReplacedByOpenHub; private boolean mOverlayActive = false; @@ -116,11 +119,13 @@ public class DreamHomeControlsComplication implements Complication { public Registrant(DreamHomeControlsComplication complication, DreamOverlayStateController dreamOverlayStateController, ControlsComponent controlsComponent, - @SystemUser Monitor monitor) { + @SystemUser Monitor monitor, + @Named(OPEN_HUB_CHIP_REPLACE_HOME_CONTROLS) boolean replacedByOpenHub) { super(monitor); mComplication = complication; mControlsComponent = controlsComponent; mDreamOverlayStateController = dreamOverlayStateController; + mReplacedByOpenHub = replacedByOpenHub; } @Override @@ -132,7 +137,9 @@ public class DreamHomeControlsComplication implements Complication { private void updateHomeControlsComplication() { mControlsComponent.getControlsListingController().ifPresent(c -> { - if (isHomeControlsAvailable(c.getCurrentServices())) { + final boolean replacedWithOpenHub = + Flags.glanceableHubShortcutButton() && mReplacedByOpenHub; + if (isHomeControlsAvailable(c.getCurrentServices()) && !replacedWithOpenHub) { mDreamOverlayStateController.addComplication(mComplication); } else { mDreamOverlayStateController.removeComplication(mComplication); diff --git a/packages/SystemUI/src/com/android/systemui/complication/OpenHubComplication.java b/packages/SystemUI/src/com/android/systemui/complication/OpenHubComplication.java new file mode 100644 index 000000000000..3cf22b1b55e4 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/complication/OpenHubComplication.java @@ -0,0 +1,212 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.complication; + +import static com.android.systemui.complication.dagger.OpenHubComplicationComponent.OpenHubModule.OPEN_HUB_CHIP_VIEW; +import static com.android.systemui.complication.dagger.RegisteredComplicationsModule.OPEN_HUB_CHIP_LAYOUT_PARAMS; + +import android.content.Context; +import android.content.res.Resources; +import android.graphics.drawable.Drawable; +import android.util.Log; +import android.view.View; +import android.widget.ImageView; + +import com.android.settingslib.Utils; +import com.android.systemui.CoreStartable; +import com.android.systemui.Flags; +import com.android.systemui.communal.domain.interactor.CommunalInteractor; +import com.android.systemui.communal.shared.model.CommunalScenes; +import com.android.systemui.complication.dagger.OpenHubComplicationComponent; +import com.android.systemui.dagger.qualifiers.Main; +import com.android.systemui.dagger.qualifiers.SystemUser; +import com.android.systemui.dreams.DreamOverlayStateController; +import com.android.systemui.shared.condition.Monitor; +import com.android.systemui.statusbar.policy.ConfigurationController; +import com.android.systemui.util.ViewController; +import com.android.systemui.util.condition.ConditionalCoreStartable; + +import javax.inject.Inject; +import javax.inject.Named; + +/** + * A dream complication that shows a chip to open the glanceable hub. + */ +// TODO(b/339667383): delete or properly implement this once a product decision is made +public class OpenHubComplication implements Complication { + private final Resources mResources; + private final OpenHubComplicationComponent.Factory mComponentFactory; + + @Inject + public OpenHubComplication( + @Main Resources resources, + OpenHubComplicationComponent.Factory componentFactory) { + mResources = resources; + mComponentFactory = componentFactory; + } + + @Override + public ViewHolder createView(ComplicationViewModel model) { + return mComponentFactory.create(mResources).getViewHolder(); + } + + @Override + public int getRequiredTypeAvailability() { + // TODO(b/339667383): create a new complication type if we decide to productionize this + return COMPLICATION_TYPE_HOME_CONTROLS; + } + + /** + * {@link CoreStartable} for registering the complication with SystemUI on startup. + */ + public static class Registrant extends ConditionalCoreStartable { + private final OpenHubComplication mComplication; + private final DreamOverlayStateController mDreamOverlayStateController; + + private boolean mOverlayActive = false; + + private final DreamOverlayStateController.Callback mOverlayStateCallback = + new DreamOverlayStateController.Callback() { + @Override + public void onStateChanged() { + if (mOverlayActive == mDreamOverlayStateController.isOverlayActive()) { + return; + } + + mOverlayActive = !mOverlayActive; + + if (mOverlayActive) { + updateOpenHubComplication(); + } + } + }; + + @Inject + public Registrant(OpenHubComplication complication, + DreamOverlayStateController dreamOverlayStateController, + @SystemUser Monitor monitor) { + super(monitor); + mComplication = complication; + mDreamOverlayStateController = dreamOverlayStateController; + } + + @Override + public void onStart() { + mDreamOverlayStateController.addCallback(mOverlayStateCallback); + } + + private void updateOpenHubComplication() { + // TODO(b/339667383): don't show the complication if glanceable hub is disabled + if (Flags.glanceableHubShortcutButton()) { + mDreamOverlayStateController.addComplication(mComplication); + } else { + mDreamOverlayStateController.removeComplication(mComplication); + } + } + } + + /** + * Contains values/logic associated with the dream complication view. + */ + public static class OpenHubChipViewHolder implements ViewHolder { + private final ImageView mView; + private final ComplicationLayoutParams mLayoutParams; + private final OpenHubChipViewController mViewController; + + @Inject + OpenHubChipViewHolder( + OpenHubChipViewController dreamOpenHubChipViewController, + @Named(OPEN_HUB_CHIP_VIEW) ImageView view, + @Named(OPEN_HUB_CHIP_LAYOUT_PARAMS) ComplicationLayoutParams layoutParams + ) { + mView = view; + mLayoutParams = layoutParams; + mViewController = dreamOpenHubChipViewController; + mViewController.init(); + } + + @Override + public ImageView getView() { + return mView; + } + + @Override + public ComplicationLayoutParams getLayoutParams() { + return mLayoutParams; + } + } + + /** + * Controls behavior of the dream complication. + */ + static class OpenHubChipViewController extends ViewController<ImageView> { + private static final String TAG = "OpenHubCtrl"; + private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG); + + private final Context mContext; + private final ConfigurationController mConfigurationController; + + private final ConfigurationController.ConfigurationListener mConfigurationListener = + new ConfigurationController.ConfigurationListener() { + @Override + public void onUiModeChanged() { + reloadResources(); + } + }; + private final CommunalInteractor mCommunalInteractor; + + @Inject + OpenHubChipViewController( + @Named(OPEN_HUB_CHIP_VIEW) ImageView view, + Context context, + ConfigurationController configurationController, + CommunalInteractor communalInteractor) { + super(view); + + mContext = context; + mConfigurationController = configurationController; + mCommunalInteractor = communalInteractor; + } + + @Override + protected void onViewAttached() { + reloadResources(); + mView.setOnClickListener(this::onClickOpenHub); + mConfigurationController.addCallback(mConfigurationListener); + } + + @Override + protected void onViewDetached() { + mConfigurationController.removeCallback(mConfigurationListener); + } + + private void reloadResources() { + mView.setImageTintList(Utils.getColorAttr(mContext, android.R.attr.textColorPrimary)); + final Drawable background = mView.getBackground(); + if (background != null) { + background.setTintList( + Utils.getColorAttr(mContext, com.android.internal.R.attr.colorSurface)); + } + } + + private void onClickOpenHub(View v) { + if (DEBUG) Log.d(TAG, "open hub complication tapped"); + + mCommunalInteractor.changeScene(CommunalScenes.Communal, null); + } + } +} diff --git a/packages/SystemUI/src/com/android/systemui/complication/dagger/OpenHubComplicationComponent.java b/packages/SystemUI/src/com/android/systemui/complication/dagger/OpenHubComplicationComponent.java new file mode 100644 index 000000000000..501601ee32ba --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/complication/dagger/OpenHubComplicationComponent.java @@ -0,0 +1,138 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.complication.dagger; + +import static java.lang.annotation.RetentionPolicy.RUNTIME; + +import android.content.Context; +import android.content.res.Resources; +import android.graphics.drawable.Drawable; +import android.view.LayoutInflater; +import android.widget.ImageView; + +import com.android.systemui.complication.OpenHubComplication; +import com.android.systemui.res.R; +import com.android.systemui.shared.shadow.DoubleShadowIconDrawable; +import com.android.systemui.shared.shadow.DoubleShadowTextHelper; + +import dagger.BindsInstance; +import dagger.Module; +import dagger.Provides; +import dagger.Subcomponent; + +import java.lang.annotation.Documented; +import java.lang.annotation.Retention; + +import javax.inject.Named; +import javax.inject.Scope; + +/** + * Responsible for generating dependencies for the {@link OpenHubComplication}. + */ +@Subcomponent(modules = OpenHubComplicationComponent.OpenHubModule.class) +@OpenHubComplicationComponent.OpenHubComplicationScope +public interface OpenHubComplicationComponent { + /** + * Creates a view holder for the open hub complication. + */ + OpenHubComplication.OpenHubChipViewHolder getViewHolder(); + + /** + * Scope of the open hub complication. + */ + @Documented + @Retention(RUNTIME) + @Scope + @interface OpenHubComplicationScope { + } + + /** + * Factory that generates a {@link OpenHubComplicationComponent}. + */ + @Subcomponent.Factory + interface Factory { + /** + * Creates an instance of {@link OpenHubComplicationComponent}. + */ + OpenHubComplicationComponent create(@BindsInstance Resources resources); + } + + /** + * Scoped injected values for the {@link OpenHubComplicationComponent}. + */ + @Module + interface OpenHubModule { + String OPEN_HUB_CHIP_VIEW = "open_hub_chip_view"; + String OPEN_HUB_BACKGROUND_DRAWABLE = "open_hub_background_drawable"; + + /** + * Provides the dream open hub chip view. + */ + @Provides + @OpenHubComplicationScope + @Named(OPEN_HUB_CHIP_VIEW) + static ImageView provideOpenHubChipView( + LayoutInflater layoutInflater, + @Named(OPEN_HUB_BACKGROUND_DRAWABLE) Drawable backgroundDrawable) { + final ImageView chip = + (ImageView) layoutInflater.inflate(R.layout.dream_overlay_open_hub_chip, + null, false); + chip.setBackground(backgroundDrawable); + + return chip; + } + + /** + * Provides the background drawable for the open hub chip. + */ + @Provides + @OpenHubComplicationScope + @Named(OPEN_HUB_BACKGROUND_DRAWABLE) + static Drawable providesOpenHubBackground(Context context, Resources resources) { + return new DoubleShadowIconDrawable(createShadowInfo( + resources, + R.dimen.dream_overlay_bottom_affordance_key_text_shadow_radius, + R.dimen.dream_overlay_bottom_affordance_key_text_shadow_dx, + R.dimen.dream_overlay_bottom_affordance_key_text_shadow_dy, + R.dimen.dream_overlay_bottom_affordance_key_shadow_alpha + ), + createShadowInfo( + resources, + R.dimen.dream_overlay_bottom_affordance_ambient_text_shadow_radius, + R.dimen.dream_overlay_bottom_affordance_ambient_text_shadow_dx, + R.dimen.dream_overlay_bottom_affordance_ambient_text_shadow_dy, + R.dimen.dream_overlay_bottom_affordance_ambient_shadow_alpha + ), + resources.getDrawable(R.drawable.dream_overlay_bottom_affordance_bg), + resources.getDimensionPixelOffset( + R.dimen.dream_overlay_bottom_affordance_width), + resources.getDimensionPixelSize(R.dimen.dream_overlay_bottom_affordance_inset) + ); + } + + private static DoubleShadowTextHelper.ShadowInfo createShadowInfo(Resources resources, + int blurId, int offsetXId, int offsetYId, int alphaId) { + + return new DoubleShadowTextHelper.ShadowInfo( + resources.getDimension(blurId), + resources.getDimension(offsetXId), + resources.getDimension(offsetYId), + resources.getFloat(alphaId) + ); + } + } +} diff --git a/packages/SystemUI/src/com/android/systemui/complication/dagger/RegisteredComplicationsModule.java b/packages/SystemUI/src/com/android/systemui/complication/dagger/RegisteredComplicationsModule.java index 6f1b09829671..edb5ff7799be 100644 --- a/packages/SystemUI/src/com/android/systemui/complication/dagger/RegisteredComplicationsModule.java +++ b/packages/SystemUI/src/com/android/systemui/complication/dagger/RegisteredComplicationsModule.java @@ -25,6 +25,7 @@ import com.android.systemui.dagger.qualifiers.Main; import com.android.systemui.flags.FeatureFlags; import com.android.systemui.flags.Flags; import com.android.systemui.res.R; +import com.android.systemui.util.settings.SystemSettings; import dagger.Module; import dagger.Provides; @@ -39,6 +40,7 @@ import javax.inject.Named; subcomponents = { DreamClockTimeComplicationComponent.class, DreamHomeControlsComplicationComponent.class, + OpenHubComplicationComponent.class, DreamMediaEntryComplicationComponent.class }) public interface RegisteredComplicationsModule { @@ -46,6 +48,8 @@ public interface RegisteredComplicationsModule { String DREAM_SMARTSPACE_LAYOUT_PARAMS = "smartspace_layout_params"; String DREAM_HOME_CONTROLS_CHIP_LAYOUT_PARAMS = "home_controls_chip_layout_params"; String DREAM_MEDIA_ENTRY_LAYOUT_PARAMS = "media_entry_layout_params"; + String OPEN_HUB_CHIP_LAYOUT_PARAMS = "open_hub_chip_layout_params"; + String OPEN_HUB_CHIP_REPLACE_HOME_CONTROLS = "open_hub_chip_replace_home_controls"; int DREAM_CLOCK_TIME_COMPLICATION_WEIGHT = 1; int DREAM_CLOCK_TIME_COMPLICATION_WEIGHT_NO_SMARTSPACE = 2; @@ -109,6 +113,26 @@ public interface RegisteredComplicationsModule { } /** + * Provides layout parameters for the open hub complication. + */ + @Provides + @Named(OPEN_HUB_CHIP_LAYOUT_PARAMS) + static ComplicationLayoutParams provideOpenHubLayoutParams( + @Named(OPEN_HUB_CHIP_REPLACE_HOME_CONTROLS) boolean replaceHomeControls) { + int position = ComplicationLayoutParams.POSITION_BOTTOM | (replaceHomeControls + ? ComplicationLayoutParams.POSITION_START + : ComplicationLayoutParams.POSITION_END); + int direction = replaceHomeControls ? ComplicationLayoutParams.DIRECTION_END + : ComplicationLayoutParams.DIRECTION_START; + return new ComplicationLayoutParams( + ViewGroup.LayoutParams.WRAP_CONTENT, + ViewGroup.LayoutParams.WRAP_CONTENT, + position, + direction, + DREAM_HOME_CONTROLS_CHIP_COMPLICATION_WEIGHT); + } + + /** * Provides layout parameters for the smartspace complication. */ @Provides @@ -124,4 +148,14 @@ public interface RegisteredComplicationsModule { res.getDimensionPixelSize(R.dimen.dream_overlay_complication_smartspace_padding), res.getDimensionPixelSize(R.dimen.dream_overlay_complication_smartspace_max_width)); } + + /** + * If true, the home controls chip should not be shown and the open hub chip should be shown in + * its place. + */ + @Provides + @Named(OPEN_HUB_CHIP_REPLACE_HOME_CONTROLS) + static boolean providesOpenHubChipReplaceHomeControls(SystemSettings systemSettings) { + return systemSettings.getBool(OPEN_HUB_CHIP_REPLACE_HOME_CONTROLS, false); + } } diff --git a/packages/SystemUI/src/com/android/systemui/dagger/ReferenceSystemUIModule.java b/packages/SystemUI/src/com/android/systemui/dagger/ReferenceSystemUIModule.java index 3e98fc1b4a2a..7aab37e12b8c 100644 --- a/packages/SystemUI/src/com/android/systemui/dagger/ReferenceSystemUIModule.java +++ b/packages/SystemUI/src/com/android/systemui/dagger/ReferenceSystemUIModule.java @@ -32,6 +32,8 @@ import com.android.systemui.dock.DockManager; import com.android.systemui.dock.DockManagerImpl; import com.android.systemui.doze.DozeHost; import com.android.systemui.keyguard.ui.composable.blueprint.DefaultBlueprintModule; +import com.android.systemui.keyguard.ui.view.layout.blueprints.KeyguardBlueprintModule; +import com.android.systemui.keyguard.ui.view.layout.sections.KeyguardSectionsModule; import com.android.systemui.media.dagger.MediaModule; import com.android.systemui.media.muteawait.MediaMuteAwaitConnectionCli; import com.android.systemui.media.nearby.NearbyMediaDevicesManager; @@ -112,6 +114,8 @@ import javax.inject.Named; GestureModule.class, HeadsUpModule.class, KeyboardShortcutsModule.class, + KeyguardBlueprintModule.class, + KeyguardSectionsModule.class, MediaModule.class, MediaMuteAwaitConnectionCli.StartableModule.class, MultiUserUtilsModule.class, diff --git a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java index 339e8f06e853..2ebb94f8bcf4 100644 --- a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java +++ b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java @@ -70,8 +70,6 @@ import com.android.systemui.inputmethod.InputMethodModule; import com.android.systemui.keyboard.KeyboardModule; import com.android.systemui.keyevent.data.repository.KeyEventRepositoryModule; import com.android.systemui.keyguard.ui.composable.LockscreenContent; -import com.android.systemui.keyguard.ui.view.layout.blueprints.KeyguardBlueprintModule; -import com.android.systemui.keyguard.ui.view.layout.sections.KeyguardSectionsModule; import com.android.systemui.log.dagger.LogModule; import com.android.systemui.log.dagger.MonitorLog; import com.android.systemui.log.table.TableLogBuffer; @@ -222,8 +220,6 @@ import javax.inject.Named; InputMethodModule.class, KeyEventRepositoryModule.class, KeyboardModule.class, - KeyguardBlueprintModule.class, - KeyguardSectionsModule.class, LetterboxModule.class, LogModule.class, MediaProjectionActivitiesModule.class, diff --git a/packages/SystemUI/src/com/android/systemui/dreams/touch/CommunalTouchHandler.java b/packages/SystemUI/src/com/android/systemui/dreams/touch/CommunalTouchHandler.java index 1c047ddcd3d8..04fda3313df6 100644 --- a/packages/SystemUI/src/com/android/systemui/dreams/touch/CommunalTouchHandler.java +++ b/packages/SystemUI/src/com/android/systemui/dreams/touch/CommunalTouchHandler.java @@ -98,7 +98,7 @@ public class CommunalTouchHandler implements TouchHandler { // Notification shade window has its own logic to be visible if the hub is open, no need to // do anything here other than send touch events over. session.registerInputListener(ev -> { - surfaces.handleDreamTouch((MotionEvent) ev); + surfaces.handleCommunalHubTouch((MotionEvent) ev); if (ev != null && ((MotionEvent) ev).getAction() == MotionEvent.ACTION_UP) { var unused = session.pop(); } diff --git a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt index 2e49919d489b..c08434015ab1 100644 --- a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt +++ b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt @@ -42,14 +42,6 @@ object Flags { @JvmField val NULL_FLAG = unreleasedFlag("null_flag") // 100 - notification - // TODO(b/297792660): Tracking Bug - @JvmField val UNCLEARED_TRANSIENT_HUN_FIX = - releasedFlag("uncleared_transient_hun_fix") - - // TODO(b/298308067): Tracking Bug - @JvmField val SWIPE_UNCLEARED_TRANSIENT_VIEW_FIX = - releasedFlag("swipe_uncleared_transient_view_fix") - // TODO(b/254512751): Tracking Bug val NOTIFICATION_PIPELINE_DEVELOPER_LOGGING = unreleasedFlag("notification_pipeline_developer_logging") @@ -170,12 +162,6 @@ object Flags { val WALLPAPER_PICKER_GRID_APPLY_BUTTON = unreleasedFlag("wallpaper_picker_grid_apply_button") - /** Keyguard Migration */ - - // TODO(b/297037052): Tracking bug. - @JvmField - val REMOVE_NPVC_BOTTOM_AREA_USAGE = unreleasedFlag("remove_npvc_bottom_area_usage") - /** Flag meant to guard the talkback fix for the KeyguardIndicationTextView */ // TODO(b/286563884): Tracking bug @JvmField val KEYGUARD_TALKBACK_FIX = unreleasedFlag("keyguard_talkback_fix") diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewConfigurator.kt b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewConfigurator.kt index c32c226441fe..a50cc8fbacb3 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewConfigurator.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewConfigurator.kt @@ -53,7 +53,6 @@ import com.android.systemui.keyguard.ui.composable.LockscreenContent import com.android.systemui.keyguard.ui.composable.blueprint.ComposableLockscreenSceneBlueprint import com.android.systemui.keyguard.ui.view.KeyguardIndicationArea import com.android.systemui.keyguard.ui.view.KeyguardRootView -import com.android.systemui.keyguard.ui.view.layout.KeyguardBlueprintCommandListener import com.android.systemui.keyguard.ui.viewmodel.KeyguardBlueprintViewModel import com.android.systemui.keyguard.ui.viewmodel.KeyguardClockViewModel import com.android.systemui.keyguard.ui.viewmodel.KeyguardIndicationAreaViewModel @@ -89,7 +88,6 @@ constructor( private val screenOffAnimationController: ScreenOffAnimationController, private val occludingAppDeviceEntryMessageViewModel: OccludingAppDeviceEntryMessageViewModel, private val chipbarCoordinator: ChipbarCoordinator, - private val keyguardBlueprintCommandListener: KeyguardBlueprintCommandListener, private val keyguardBlueprintViewModel: KeyguardBlueprintViewModel, private val keyguardStatusViewComponentFactory: KeyguardStatusViewComponent.Factory, private val configuration: ConfigurationState, @@ -160,7 +158,6 @@ constructor( ) } } - keyguardBlueprintCommandListener.start() } fun bindIndicationArea() { diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/BuiltInKeyguardQuickAffordanceKeys.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/BuiltInKeyguardQuickAffordanceKeys.kt index 80675d373b8e..0863cd737529 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/BuiltInKeyguardQuickAffordanceKeys.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/BuiltInKeyguardQuickAffordanceKeys.kt @@ -28,6 +28,8 @@ object BuiltInKeyguardQuickAffordanceKeys { const val CREATE_NOTE = "create_note" const val DO_NOT_DISTURB = "do_not_disturb" const val FLASHLIGHT = "flashlight" + // TODO(b/339667383): delete or properly implement this once a product decision is made + const val GLANCEABLE_HUB = "glanceable_hub" const val HOME_CONTROLS = "home" const val MUTE = "mute" const val QR_CODE_SCANNER = "qr_code_scanner" diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/GlanceableHubQuickAffordanceConfig.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/GlanceableHubQuickAffordanceConfig.kt new file mode 100644 index 000000000000..d09b9f68ea60 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/GlanceableHubQuickAffordanceConfig.kt @@ -0,0 +1,61 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.keyguard.data.quickaffordance + +import com.android.systemui.Flags +import com.android.systemui.animation.Expandable +import com.android.systemui.common.shared.model.ContentDescription +import com.android.systemui.common.shared.model.Icon +import com.android.systemui.communal.data.repository.CommunalSceneRepository +import com.android.systemui.communal.shared.model.CommunalScenes +import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.res.R +import javax.inject.Inject +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.flowOf + +/** Shortcut that opens the glanceable hub. */ +// TODO(b/339667383): delete or properly implement this once a product decision is made +@SysUISingleton +class GlanceableHubQuickAffordanceConfig +@Inject +constructor( + private val communalRepository: CommunalSceneRepository, +) : KeyguardQuickAffordanceConfig { + + override val key: String = BuiltInKeyguardQuickAffordanceKeys.GLANCEABLE_HUB + override fun pickerName(): String = "Glanceable hub" + + override val pickerIconResourceId = R.drawable.ic_widgets + + override val lockScreenState: Flow<KeyguardQuickAffordanceConfig.LockScreenState> by lazy { + if (Flags.glanceableHubShortcutButton()) { + val contentDescription = ContentDescription.Loaded(pickerName()) + val icon = Icon.Resource(pickerIconResourceId, contentDescription) + flowOf(KeyguardQuickAffordanceConfig.LockScreenState.Visible(icon)) + } else { + flowOf(KeyguardQuickAffordanceConfig.LockScreenState.Hidden) + } + } + + override fun onTriggered( + expandable: Expandable? + ): KeyguardQuickAffordanceConfig.OnTriggeredResult { + communalRepository.changeScene(CommunalScenes.Communal, null) + return KeyguardQuickAffordanceConfig.OnTriggeredResult.Handled + } +} diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardDataQuickAffordanceModule.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardDataQuickAffordanceModule.kt index 45561959a7df..93296f0ca24b 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardDataQuickAffordanceModule.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardDataQuickAffordanceModule.kt @@ -36,6 +36,7 @@ interface KeyguardDataQuickAffordanceModule { camera: CameraQuickAffordanceConfig, doNotDisturb: DoNotDisturbQuickAffordanceConfig, flashlight: FlashlightQuickAffordanceConfig, + glanceableHub: GlanceableHubQuickAffordanceConfig, home: HomeControlsKeyguardQuickAffordanceConfig, mute: MuteQuickAffordanceConfig, quickAccessWallet: QuickAccessWalletKeyguardQuickAffordanceConfig, @@ -46,6 +47,7 @@ interface KeyguardDataQuickAffordanceModule { camera, doNotDisturb, flashlight, + glanceableHub, home, mute, quickAccessWallet, 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 deedbdb9e72b..0748979a2465 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 @@ -20,15 +20,17 @@ package com.android.systemui.keyguard.data.quickaffordance import android.content.Context import android.content.IntentFilter import android.content.SharedPreferences -import com.android.systemui.res.R +import com.android.systemui.Flags import com.android.systemui.backup.BackupHelper import com.android.systemui.broadcast.BroadcastDispatcher import com.android.systemui.common.coroutine.ChannelExt.trySendWithFailureLogging import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Application +import com.android.systemui.res.R import com.android.systemui.settings.UserFileManager import com.android.systemui.settings.UserTracker +import com.android.systemui.util.settings.SystemSettings import javax.inject.Inject import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.channels.awaitClose @@ -50,6 +52,7 @@ constructor( @Application private val context: Context, private val userFileManager: UserFileManager, private val userTracker: UserTracker, + private val systemSettings: SystemSettings, broadcastDispatcher: BroadcastDispatcher, ) : KeyguardQuickAffordanceSelectionManager { @@ -70,6 +73,22 @@ constructor( } private val defaults: Map<String, List<String>> by lazy { + // Quick hack to allow testing out a lock screen shortcut to open the glanceable hub. This + // flag will not be rolled out and is only used for local testing. + // TODO(b/339667383): delete or properly implement this once a product decision is made + if (Flags.glanceableHubShortcutButton()) { + if (systemSettings.getBool("open_hub_chip_replace_home_controls", false)) { + return@lazy mapOf( + "bottom_start" to listOf("glanceable_hub"), + "bottom_end" to listOf("create_note") + ) + } else { + return@lazy mapOf( + "bottom_start" to listOf("home"), + "bottom_end" to listOf("glanceable_hub") + ) + } + } context.resources .getStringArray(R.array.config_keyguardQuickAffordanceDefaults) .associate { item -> diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardBlueprintRepository.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardBlueprintRepository.kt index c11c49c7a8a0..b826a002b9d9 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardBlueprintRepository.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardBlueprintRepository.kt @@ -91,9 +91,9 @@ constructor( */ fun refreshBlueprint(config: Config = Config.DEFAULT) { fun scheduleCallback() { - // We use a handler here instead of a CoroutineDipsatcher because the one provided by + // We use a handler here instead of a CoroutineDispatcher because the one provided by // @Main CoroutineDispatcher is currently Dispatchers.Main.immediate, which doesn't - // delay the callback, and instead runs it imemdiately. + // delay the callback, and instead runs it immediately. handler.post { assert.isMainThread() targetTransitionConfig?.let { diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromPrimaryBouncerTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromPrimaryBouncerTransitionInteractor.kt index 53a0c3200f3d..76a822369b0c 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromPrimaryBouncerTransitionInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromPrimaryBouncerTransitionInteractor.kt @@ -86,7 +86,7 @@ constructor( return@combine null } - fromBouncerStep.value > 0.5f + fromBouncerStep.value > TO_GONE_SURFACE_BEHIND_VISIBLE_THRESHOLD } .onStart { // Default to null ("don't care, use a reasonable default"). @@ -232,5 +232,6 @@ constructor( val TO_AOD_DURATION = DEFAULT_DURATION val TO_LOCKSCREEN_DURATION = DEFAULT_DURATION val TO_DOZING_DURATION = DEFAULT_DURATION + val TO_GONE_SURFACE_BEHIND_VISIBLE_THRESHOLD = 0.5f } } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardBlueprintInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardBlueprintInteractor.kt index 7cee258dc39f..41c39597dc02 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardBlueprintInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardBlueprintInteractor.kt @@ -20,6 +20,7 @@ package com.android.systemui.keyguard.domain.interactor import android.content.Context +import com.android.systemui.CoreStartable import com.android.systemui.biometrics.domain.interactor.FingerprintPropertyInteractor import com.android.systemui.common.ui.domain.interactor.ConfigurationInteractor import com.android.systemui.dagger.SysUISingleton @@ -31,17 +32,17 @@ import com.android.systemui.keyguard.ui.view.layout.blueprints.DefaultKeyguardBl import com.android.systemui.keyguard.ui.view.layout.blueprints.SplitShadeKeyguardBlueprint import com.android.systemui.keyguard.ui.view.layout.blueprints.transitions.IntraBlueprintTransition.Config import com.android.systemui.keyguard.ui.view.layout.blueprints.transitions.IntraBlueprintTransition.Type +import com.android.systemui.keyguard.ui.view.layout.sections.ClockSection +import com.android.systemui.keyguard.ui.view.layout.sections.SmartspaceSection import com.android.systemui.shade.domain.interactor.ShadeInteractor import com.android.systemui.shade.shared.model.ShadeMode import javax.inject.Inject import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.ExperimentalCoroutinesApi -import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.collect import kotlinx.coroutines.flow.filter import kotlinx.coroutines.flow.map -import kotlinx.coroutines.flow.merge import kotlinx.coroutines.launch @SysUISingleton @@ -53,9 +54,11 @@ constructor( private val context: Context, private val shadeInteractor: ShadeInteractor, private val clockInteractor: KeyguardClockInteractor, - configurationInteractor: ConfigurationInteractor, - fingerprintPropertyInteractor: FingerprintPropertyInteractor, -) { + private val configurationInteractor: ConfigurationInteractor, + private val fingerprintPropertyInteractor: FingerprintPropertyInteractor, + private val smartspaceSection: SmartspaceSection, + private val clockSection: ClockSection, +) : CoreStartable { /** The current blueprint for the lockscreen. */ val blueprint: StateFlow<KeyguardBlueprint> = keyguardBlueprintRepository.blueprint @@ -75,15 +78,23 @@ constructor( } } - private val refreshEvents: Flow<Unit> = - merge( - configurationInteractor.onAnyConfigurationChange, - fingerprintPropertyInteractor.propertiesInitialized.filter { it }.map {}, - ) - - init { + override fun start() { applicationScope.launch { blueprintId.collect { transitionToBlueprint(it) } } - applicationScope.launch { refreshEvents.collect { refreshBlueprint() } } + applicationScope.launch { + fingerprintPropertyInteractor.propertiesInitialized + .filter { it } + .collect { refreshBlueprint() } + } + applicationScope.launch { + val refreshConfig = + Config( + Type.NoTransition, + rebuildSections = listOf(smartspaceSection), + ) + configurationInteractor.onAnyConfigurationChange.collect { + refreshBlueprint(refreshConfig) + } + } } /** @@ -120,4 +131,8 @@ constructor( fun getCurrentBlueprint(): KeyguardBlueprint { return keyguardBlueprintRepository.blueprint.value } + + companion object { + private val TAG = "KeyguardBlueprintInteractor" + } } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/WindowManagerLockscreenVisibilityInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/WindowManagerLockscreenVisibilityInteractor.kt index fb65a6dacbe3..88e6602e56b7 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/WindowManagerLockscreenVisibilityInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/WindowManagerLockscreenVisibilityInteractor.kt @@ -18,6 +18,7 @@ package com.android.systemui.keyguard.domain.interactor +import com.android.compose.animation.scene.ObservableTransitionState import com.android.systemui.dagger.SysUISingleton import com.android.systemui.deviceentry.domain.interactor.DeviceEntryInteractor import com.android.systemui.keyguard.shared.model.BiometricUnlockMode @@ -29,6 +30,7 @@ import com.android.systemui.scene.shared.flag.SceneContainerFlag import com.android.systemui.scene.shared.model.Scenes import com.android.systemui.statusbar.notification.domain.interactor.NotificationLaunchAnimationInteractor import com.android.systemui.util.kotlin.sample +import com.android.systemui.utils.coroutines.flow.flatMapLatestConflated import dagger.Lazy import javax.inject.Inject import kotlinx.coroutines.ExperimentalCoroutinesApi @@ -84,25 +86,52 @@ constructor( } .distinctUntilChanged() + private val isDeviceEntered: Flow<Boolean> by lazy { + deviceEntryInteractor.get().isDeviceEntered + } + + private val isDeviceNotEntered: Flow<Boolean> by lazy { isDeviceEntered.map { !it } } + /** - * Surface visibility, which is either determined by the default visibility in the FINISHED - * KeyguardState, or the transition-specific visibility used during certain RUNNING transitions. + * Surface visibility, which is either determined by the default visibility when not + * transitioning between [KeyguardState]s or [Scenes] or the transition-specific visibility used + * during certain ongoing transitions. */ @OptIn(ExperimentalCoroutinesApi::class) val surfaceBehindVisibility: Flow<Boolean> = - transitionInteractor.isInTransitionToAnyState - .flatMapLatest { isInTransition -> - if (!isInTransition) { - defaultSurfaceBehindVisibility - } else { - combine( - transitionSpecificSurfaceBehindVisibility, - defaultSurfaceBehindVisibility, - ) { transitionVisibility, defaultVisibility -> - // Defer to the transition-specific visibility since we're RUNNING a - // transition, but fall back to the default visibility if the current - // transition's interactor did not specify a visibility. - transitionVisibility ?: defaultVisibility + if (SceneContainerFlag.isEnabled) { + sceneInteractor.get().transitionState.flatMapLatestConflated { transitionState -> + when (transitionState) { + is ObservableTransitionState.Transition -> + when { + transitionState.fromScene == Scenes.Lockscreen && + transitionState.toScene == Scenes.Gone -> flowOf(true) + transitionState.fromScene == Scenes.Bouncer && + transitionState.toScene == Scenes.Gone -> + transitionState.progress.map { progress -> + progress > + FromPrimaryBouncerTransitionInteractor + .TO_GONE_SURFACE_BEHIND_VISIBLE_THRESHOLD + } + else -> isDeviceEntered + } + is ObservableTransitionState.Idle -> isDeviceEntered + } + } + } else { + transitionInteractor.isInTransitionToAnyState.flatMapLatest { isInTransition -> + if (!isInTransition) { + defaultSurfaceBehindVisibility + } else { + combine( + transitionSpecificSurfaceBehindVisibility, + defaultSurfaceBehindVisibility, + ) { transitionVisibility, defaultVisibility -> + // Defer to the transition-specific visibility since we're RUNNING a + // transition, but fall back to the default visibility if the current + // transition's interactor did not specify a visibility. + transitionVisibility ?: defaultVisibility + } } } } @@ -162,7 +191,7 @@ constructor( */ val lockscreenVisibility: Flow<Boolean> = if (SceneContainerFlag.isEnabled) { - deviceEntryInteractor.get().isDeviceEntered.map { !it } + isDeviceNotEntered } else { transitionInteractor.currentKeyguardState .sample(transitionInteractor.startedStepWithPrecedingStep, ::Pair) diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/KeyguardBlueprint.kt b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/KeyguardBlueprint.kt index 7ca2ebaeab20..6d579f3b2513 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/KeyguardBlueprint.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/KeyguardBlueprint.kt @@ -37,16 +37,32 @@ interface KeyguardBlueprint { fun replaceViews( constraintLayout: ConstraintLayout, previousBlueprint: KeyguardBlueprint? = null, + rebuildSections: List<KeyguardSection> = listOf(), bindData: Boolean = true ) { - val prevSections = - previousBlueprint?.let { prev -> - prev.sections.subtract(sections).forEach { it.removeViews(constraintLayout) } - prev.sections + val prevSections = previousBlueprint?.sections ?: listOf() + val skipSections = sections.intersect(prevSections).subtract(rebuildSections) + prevSections.subtract(skipSections).forEach { it.removeViews(constraintLayout) } + sections.subtract(skipSections).forEach { + it.addViews(constraintLayout) + if (bindData) { + it.bindData(constraintLayout) } - ?: listOf() + } + } + + /** Rebuilds views for the target sections, or all of them if unspecified. */ + fun rebuildViews( + constraintLayout: ConstraintLayout, + rebuildSections: List<KeyguardSection> = sections, + bindData: Boolean = true + ) { + if (rebuildSections.isEmpty()) { + return + } - sections.subtract(prevSections).forEach { + rebuildSections.forEach { it.removeViews(constraintLayout) } + rebuildSections.forEach { it.addViews(constraintLayout) if (bindData) { it.bindData(constraintLayout) diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBlueprintViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBlueprintViewBinder.kt index 52d7519c2e3c..816033561e94 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBlueprintViewBinder.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBlueprintViewBinder.kt @@ -95,17 +95,8 @@ constructor( null as KeyguardBlueprint?, ) .collect { (prevBlueprint, blueprint) -> - val cs = - ConstraintSet().apply { - clone(constraintLayout) - val emptyLayout = ConstraintSet.Layout() - knownIds.forEach { - getConstraint(it).layout.copyFrom(emptyLayout) - } - blueprint.applyConstraints(this) - } - - var transition = + val config = Config.DEFAULT + val transition = if ( !KeyguardBottomAreaRefactor.isEnabled && prevBlueprint != null && @@ -114,23 +105,37 @@ constructor( BaseBlueprintTransition(clockViewModel) .addTransition( IntraBlueprintTransition( - Config.DEFAULT, + config, clockViewModel, smartspaceViewModel ) ) } else { IntraBlueprintTransition( - Config.DEFAULT, + config, clockViewModel, smartspaceViewModel ) } - runTransition(constraintLayout, transition, Config.DEFAULT) { - // Add and remove views of sections that are not contained by the - // other. - blueprint.replaceViews(constraintLayout, prevBlueprint) + runTransition(constraintLayout, transition, config) { + // Replace sections from the previous blueprint with the new ones + blueprint.replaceViews( + constraintLayout, + prevBlueprint, + config.rebuildSections + ) + + val cs = + ConstraintSet().apply { + clone(constraintLayout) + val emptyLayout = ConstraintSet.Layout() + knownIds.forEach { + getConstraint(it).layout.copyFrom(emptyLayout) + } + blueprint.applyConstraints(this) + } + logAlphaVisibilityOfAppliedConstraintSet(cs, clockViewModel) cs.applyTo(constraintLayout) } @@ -138,22 +143,21 @@ constructor( } launch("$TAG#viewModel.refreshTransition") { - viewModel.refreshTransition.collect { transition -> - val cs = - ConstraintSet().apply { - clone(constraintLayout) - viewModel.blueprint.value.applyConstraints(this) - } + viewModel.refreshTransition.collect { config -> + val blueprint = viewModel.blueprint.value runTransition( constraintLayout, - IntraBlueprintTransition( - transition, - clockViewModel, - smartspaceViewModel - ), - transition, + IntraBlueprintTransition(config, clockViewModel, smartspaceViewModel), + config, ) { + blueprint.rebuildViews(constraintLayout, config.rebuildSections) + + val cs = + ConstraintSet().apply { + clone(constraintLayout) + blueprint.applyConstraints(this) + } logAlphaVisibilityOfAppliedConstraintSet(cs, clockViewModel) cs.applyTo(constraintLayout) } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/KeyguardBlueprintCommandListener.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/KeyguardBlueprintCommandListener.kt index 962cdf10cf86..c0266567a63f 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/KeyguardBlueprintCommandListener.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/KeyguardBlueprintCommandListener.kt @@ -17,6 +17,7 @@ package com.android.systemui.keyguard.ui.view.layout import androidx.core.text.isDigitsOnly +import com.android.systemui.CoreStartable import com.android.systemui.keyguard.data.repository.KeyguardBlueprintRepository import com.android.systemui.keyguard.domain.interactor.KeyguardBlueprintInteractor import com.android.systemui.statusbar.commandline.Command @@ -31,10 +32,10 @@ constructor( private val commandRegistry: CommandRegistry, private val keyguardBlueprintRepository: KeyguardBlueprintRepository, private val keyguardBlueprintInteractor: KeyguardBlueprintInteractor, -) { +) : CoreStartable { private val layoutCommand = KeyguardLayoutManagerCommand() - fun start() { + override fun start() { commandRegistry.registerCommand(COMMAND) { layoutCommand } } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/blueprints/KeyguardBlueprintModule.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/blueprints/KeyguardBlueprintModule.kt index 04ac7bf1178e..2dc930121a71 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/blueprints/KeyguardBlueprintModule.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/blueprints/KeyguardBlueprintModule.kt @@ -17,9 +17,14 @@ package com.android.systemui.keyguard.ui.view.layout.blueprints +import com.android.systemui.CoreStartable +import com.android.systemui.keyguard.domain.interactor.KeyguardBlueprintInteractor import com.android.systemui.keyguard.shared.model.KeyguardBlueprint +import com.android.systemui.keyguard.ui.view.layout.KeyguardBlueprintCommandListener import dagger.Binds import dagger.Module +import dagger.multibindings.ClassKey +import dagger.multibindings.IntoMap import dagger.multibindings.IntoSet @Module @@ -41,4 +46,18 @@ abstract class KeyguardBlueprintModule { abstract fun bindShortcutsBesideUdfpsLockscreenBlueprint( shortcutsBesideUdfpsLockscreenBlueprint: ShortcutsBesideUdfpsKeyguardBlueprint ): KeyguardBlueprint + + @Binds + @IntoMap + @ClassKey(KeyguardBlueprintInteractor::class) + abstract fun bindsKeyguardBlueprintInteractor( + keyguardBlueprintInteractor: KeyguardBlueprintInteractor + ): CoreStartable + + @Binds + @IntoMap + @ClassKey(KeyguardBlueprintCommandListener::class) + abstract fun bindsKeyguardBlueprintCommandListener( + keyguardBlueprintCommandListener: KeyguardBlueprintCommandListener + ): CoreStartable } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/blueprints/transitions/IntraBlueprintTransition.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/blueprints/transitions/IntraBlueprintTransition.kt index c69d868866d0..02e9ca5b6821 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/blueprints/transitions/IntraBlueprintTransition.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/blueprints/transitions/IntraBlueprintTransition.kt @@ -17,6 +17,7 @@ package com.android.systemui.keyguard.ui.view.layout.blueprints.transitions import android.transition.TransitionSet +import com.android.systemui.keyguard.shared.model.KeyguardSection import com.android.systemui.keyguard.ui.view.layout.sections.transitions.ClockSizeTransition import com.android.systemui.keyguard.ui.view.layout.sections.transitions.DefaultClockSteppingTransition import com.android.systemui.keyguard.ui.viewmodel.KeyguardClockViewModel @@ -46,6 +47,7 @@ class IntraBlueprintTransition( val type: Type, val checkPriority: Boolean = true, val terminatePrevious: Boolean = true, + val rebuildSections: List<KeyguardSection> = listOf(), ) { companion object { val DEFAULT = Config(Type.NoTransition) diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/ClockSection.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/ClockSection.kt index b367715f529e..34a1da54c123 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/ClockSection.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/ClockSection.kt @@ -32,6 +32,7 @@ import androidx.constraintlayout.widget.ConstraintSet.TOP import androidx.constraintlayout.widget.ConstraintSet.VISIBLE import androidx.constraintlayout.widget.ConstraintSet.WRAP_CONTENT import com.android.systemui.customization.R as custR +import com.android.systemui.dagger.SysUISingleton import com.android.systemui.keyguard.MigrateClocksToBlueprint import com.android.systemui.keyguard.domain.interactor.KeyguardBlueprintInteractor import com.android.systemui.keyguard.domain.interactor.KeyguardClockInteractor @@ -57,6 +58,7 @@ internal fun ConstraintSet.setAlpha( alpha: Float, ) = views.forEach { view -> this.setAlpha(view.id, alpha) } +@SysUISingleton class ClockSection @Inject constructor( @@ -72,6 +74,7 @@ constructor( if (!MigrateClocksToBlueprint.isEnabled) { return } + KeyguardClockViewBinder.bind( this, constraintLayout, @@ -86,6 +89,7 @@ constructor( if (!MigrateClocksToBlueprint.isEnabled) { return } + keyguardClockViewModel.currentClock.value?.let { clock -> constraintSet.applyDeltaFrom(buildConstraints(clock, constraintSet)) } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultDeviceEntrySection.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultDeviceEntrySection.kt index 0b8376af811c..c5fab8f57822 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultDeviceEntrySection.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultDeviceEntrySection.kt @@ -219,5 +219,37 @@ constructor( sensorRect.left ) } + + // This is only intended to be here until the KeyguardBottomAreaRefactor flag is enabled + // Without this logic, the lock icon location changes but the KeyguardBottomAreaView is not + // updated and visible ui layout jank occurs. This is due to AmbientIndicationContainer + // being in NPVC and laying out prior to the KeyguardRootView. + // Remove when both DeviceEntryUdfpsRefactor and KeyguardBottomAreaRefactor are enabled. + if (DeviceEntryUdfpsRefactor.isEnabled && !KeyguardBottomAreaRefactor.isEnabled) { + with(notificationPanelView) { + val isUdfpsSupported = deviceEntryIconViewModel.get().isUdfpsSupported.value + val bottomAreaViewRight = findViewById<View>(R.id.keyguard_bottom_area)?.right ?: 0 + findViewById<View>(R.id.ambient_indication_container)?.let { + val (ambientLeft, ambientTop) = it.locationOnScreen + if (isUdfpsSupported) { + // make top of ambient indication view the bottom of the lock icon + it.layout( + ambientLeft, + sensorRect.bottom, + bottomAreaViewRight - ambientLeft, + ambientTop + it.measuredHeight + ) + } else { + // make bottom of ambient indication view the top of the lock icon + it.layout( + ambientLeft, + sensorRect.top - it.measuredHeight, + bottomAreaViewRight - ambientLeft, + sensorRect.top + ) + } + } + } + } } } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/SmartspaceSection.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/SmartspaceSection.kt index 487c2e918e5f..2d6690fbbc99 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/SmartspaceSection.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/SmartspaceSection.kt @@ -22,6 +22,7 @@ import android.view.ViewTreeObserver.OnGlobalLayoutListener import androidx.constraintlayout.widget.Barrier import androidx.constraintlayout.widget.ConstraintLayout import androidx.constraintlayout.widget.ConstraintSet +import com.android.systemui.dagger.SysUISingleton import com.android.systemui.keyguard.KeyguardUnlockAnimationController import com.android.systemui.keyguard.MigrateClocksToBlueprint import com.android.systemui.keyguard.domain.interactor.KeyguardBlueprintInteractor @@ -36,6 +37,7 @@ import com.android.systemui.statusbar.lockscreen.LockscreenSmartspaceController import dagger.Lazy import javax.inject.Inject +@SysUISingleton open class SmartspaceSection @Inject constructor( diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/MediaDeviceManager.kt b/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/MediaDeviceManager.kt index 486d4d46c767..aa93df7f1474 100644 --- a/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/MediaDeviceManager.kt +++ b/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/MediaDeviceManager.kt @@ -23,6 +23,7 @@ import android.graphics.drawable.Drawable import android.media.MediaRouter2Manager import android.media.RoutingSessionInfo import android.media.session.MediaController +import android.media.session.MediaController.PlaybackInfo import android.text.TextUtils import android.util.Log import androidx.annotation.AnyThread @@ -74,6 +75,11 @@ constructor( private val listeners: MutableSet<Listener> = mutableSetOf() private val entries: MutableMap<String, Entry> = mutableMapOf() + companion object { + private val EMPTY_AND_DISABLED_MEDIA_DEVICE_DATA = + MediaDeviceData(enabled = false, icon = null, name = null, showBroadcastButton = false) + } + /** Add a listener for changes to the media route (ie. device). */ fun addListener(listener: Listener) = listeners.add(listener) @@ -333,28 +339,32 @@ constructor( @WorkerThread private fun updateCurrent() { if (isLeAudioBroadcastEnabled()) { - if (enableLeAudioSharing()) { - current = - MediaDeviceData( - enabled = false, - icon = - context.getDrawable( - com.android.settingslib.R.drawable.ic_bt_le_audio_sharing - ), - name = context.getString(R.string.audio_sharing_description), - intent = null, - showBroadcastButton = false - ) + current = getLeAudioBroadcastDeviceData() + } else if (Flags.usePlaybackInfoForRoutingControls()) { + val activeDevice: MediaDeviceData? + + // LocalMediaManager provides the connected device based on PlaybackInfo. + // TODO (b/342197065): Simplify nullability once we make currentConnectedDevice + // non-null. + val connectedDevice = localMediaManager.currentConnectedDevice?.toMediaDeviceData() + + if (controller?.playbackInfo?.playbackType == PlaybackInfo.PLAYBACK_TYPE_REMOTE) { + val routingSession = + mr2manager.get().getRoutingSessionForMediaController(controller) + + activeDevice = + routingSession?.let { + // For a remote session, always use the current device from + // LocalMediaManager. Override with routing session name if available to + // show dynamic group name. + connectedDevice?.copy(name = it.name ?: connectedDevice.name) + } } else { - current = - MediaDeviceData( - /* enabled */ true, - /* icon */ context.getDrawable(R.drawable.settings_input_antenna), - /* name */ broadcastDescription, - /* intent */ null, - /* showBroadcastButton */ showBroadcastButton = true - ) + // Prefer SASS if available when playback is local. + activeDevice = getSassDevice() ?: connectedDevice } + + current = activeDevice ?: EMPTY_AND_DISABLED_MEDIA_DEVICE_DATA } else { val aboutToConnect = aboutToConnectDeviceOverride if ( @@ -389,6 +399,43 @@ constructor( } } + private fun getSassDevice(): MediaDeviceData? { + val sassDevice = aboutToConnectDeviceOverride ?: return null + return sassDevice.fullMediaDevice?.toMediaDeviceData() + ?: sassDevice.backupMediaDeviceData + } + + private fun MediaDevice.toMediaDeviceData() = + MediaDeviceData( + enabled = true, + icon = iconWithoutBackground, + name = name, + id = id, + showBroadcastButton = false + ) + + private fun getLeAudioBroadcastDeviceData(): MediaDeviceData { + return if (enableLeAudioSharing()) { + MediaDeviceData( + enabled = false, + icon = + context.getDrawable( + com.android.settingslib.R.drawable.ic_bt_le_audio_sharing + ), + name = context.getString(R.string.audio_sharing_description), + intent = null, + showBroadcastButton = false + ) + } else { + MediaDeviceData( + enabled = true, + icon = context.getDrawable(R.drawable.settings_input_antenna), + name = broadcastDescription, + intent = null, + showBroadcastButton = true + ) + } + } /** Return a display name for the current device / route, or null if not possible */ private fun getDeviceName( device: MediaDevice?, diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/data/repository/QSPreferencesRepository.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/data/repository/QSPreferencesRepository.kt new file mode 100644 index 000000000000..f3e5b8f0c214 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/qs/panels/data/repository/QSPreferencesRepository.kt @@ -0,0 +1,73 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.qs.panels.data.repository + +import android.content.Context +import android.content.SharedPreferences +import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.dagger.qualifiers.Background +import com.android.systemui.settings.UserFileManager +import com.android.systemui.user.data.repository.UserRepository +import com.android.systemui.util.kotlin.SharedPreferencesExt.observe +import com.android.systemui.util.kotlin.emitOnStart +import javax.inject.Inject +import kotlinx.coroutines.CoroutineDispatcher +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.flatMapLatest +import kotlinx.coroutines.flow.flowOn +import kotlinx.coroutines.flow.map + +/** Repository for QS user preferences. */ +@OptIn(ExperimentalCoroutinesApi::class) +@SysUISingleton +class QSPreferencesRepository +@Inject +constructor( + private val userFileManager: UserFileManager, + private val userRepository: UserRepository, + @Background private val backgroundDispatcher: CoroutineDispatcher, +) { + /** Whether to show the labels on icon tiles for the current user. */ + val showLabels: Flow<Boolean> = + userRepository.selectedUserInfo + .flatMapLatest { userInfo -> + val prefs = getSharedPrefs(userInfo.id) + prefs.observe().emitOnStart().map { prefs.getBoolean(ICON_LABELS_KEY, false) } + } + .flowOn(backgroundDispatcher) + + /** Sets for the current user whether to show the labels on icon tiles. */ + fun setShowLabels(showLabels: Boolean) { + with(getSharedPrefs(userRepository.getSelectedUserInfo().id)) { + edit().putBoolean(ICON_LABELS_KEY, showLabels).apply() + } + } + + private fun getSharedPrefs(userId: Int): SharedPreferences { + return userFileManager.getSharedPreferences( + FILE_NAME, + Context.MODE_PRIVATE, + userId, + ) + } + + companion object { + private const val ICON_LABELS_KEY = "show_icon_labels" + const val FILE_NAME = "quick_settings_prefs" + } +} diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/domain/interactor/IconLabelVisibilityInteractor.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/domain/interactor/IconLabelVisibilityInteractor.kt index a871531f283a..6a899b07b2a0 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/panels/domain/interactor/IconLabelVisibilityInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/panels/domain/interactor/IconLabelVisibilityInteractor.kt @@ -20,7 +20,6 @@ import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Application import com.android.systemui.log.LogBuffer import com.android.systemui.log.core.LogLevel -import com.android.systemui.qs.panels.data.repository.IconLabelVisibilityRepository import com.android.systemui.qs.panels.shared.model.IconLabelVisibilityLog import javax.inject.Inject import kotlinx.coroutines.CoroutineScope @@ -33,17 +32,17 @@ import kotlinx.coroutines.flow.stateIn class IconLabelVisibilityInteractor @Inject constructor( - private val repo: IconLabelVisibilityRepository, + private val preferencesInteractor: QSPreferencesInteractor, @IconLabelVisibilityLog private val logBuffer: LogBuffer, @Application scope: CoroutineScope, ) { val showLabels: StateFlow<Boolean> = - repo.showLabels + preferencesInteractor.showLabels .onEach { logChange(it) } - .stateIn(scope, SharingStarted.WhileSubscribed(), repo.showLabels.value) + .stateIn(scope, SharingStarted.WhileSubscribed(), false) fun setShowLabels(showLabels: Boolean) { - repo.setShowLabels(showLabels) + preferencesInteractor.setShowLabels(showLabels) } private fun logChange(showLabels: Boolean) { diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/data/repository/IconLabelVisibilityRepository.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/domain/interactor/QSPreferencesInteractor.kt index 686e5f49442b..811be80d23fa 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/panels/data/repository/IconLabelVisibilityRepository.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/panels/domain/interactor/QSPreferencesInteractor.kt @@ -14,22 +14,18 @@ * limitations under the License. */ -package com.android.systemui.qs.panels.data.repository +package com.android.systemui.qs.panels.domain.interactor import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.qs.panels.data.repository.QSPreferencesRepository import javax.inject.Inject -import kotlinx.coroutines.flow.MutableStateFlow -import kotlinx.coroutines.flow.StateFlow -import kotlinx.coroutines.flow.asStateFlow +import kotlinx.coroutines.flow.Flow -/** Repository for whether to show the labels of icon tiles. */ @SysUISingleton -class IconLabelVisibilityRepository @Inject constructor() { - // TODO(b/341735914): Persist and back up showLabels - private val _showLabels = MutableStateFlow(false) - val showLabels: StateFlow<Boolean> = _showLabels.asStateFlow() +class QSPreferencesInteractor @Inject constructor(private val repo: QSPreferencesRepository) { + val showLabels: Flow<Boolean> = repo.showLabels fun setShowLabels(showLabels: Boolean) { - _showLabels.value = showLabels + repo.setShowLabels(showLabels) } } diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/viewmodel/IconLabelVisibilityViewModel.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/viewmodel/IconLabelVisibilityViewModel.kt index 5d4b8f1773f2..12cbde2cbc91 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/viewmodel/IconLabelVisibilityViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/viewmodel/IconLabelVisibilityViewModel.kt @@ -30,7 +30,9 @@ interface IconLabelVisibilityViewModel { @SysUISingleton class IconLabelVisibilityViewModelImpl @Inject -constructor(private val interactor: IconLabelVisibilityInteractor) : IconLabelVisibilityViewModel { +constructor( + private val interactor: IconLabelVisibilityInteractor, +) : IconLabelVisibilityViewModel { override val showLabels: StateFlow<Boolean> = interactor.showLabels override fun setShowLabels(showLabels: Boolean) { diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialogController.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialogController.java index 7bc76af68c6b..c091ac3dea50 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialogController.java +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialogController.java @@ -936,6 +936,10 @@ public class InternetDialogController implements AccessPointController.AccessPoi mHasActiveSubIdOnDds = false; Log.e(TAG, "Can't get DDS subscriptionInfo"); return; + } else if (ddsSubInfo.isOnlyNonTerrestrialNetwork()) { + mHasActiveSubIdOnDds = false; + Log.d(TAG, "This is NTN, so do not show mobile data"); + return; } mHasActiveSubIdOnDds = isEmbeddedSubscriptionVisible(ddsSubInfo); diff --git a/packages/SystemUI/src/com/android/systemui/shade/GlanceableHubContainerController.kt b/packages/SystemUI/src/com/android/systemui/shade/GlanceableHubContainerController.kt index 22aa492dbfe8..1d8b7e5b6155 100644 --- a/packages/SystemUI/src/com/android/systemui/shade/GlanceableHubContainerController.kt +++ b/packages/SystemUI/src/com/android/systemui/shade/GlanceableHubContainerController.kt @@ -43,6 +43,7 @@ import com.android.systemui.communal.ui.compose.CommunalContainer import com.android.systemui.communal.ui.compose.CommunalContent import com.android.systemui.communal.ui.viewmodel.CommunalViewModel import com.android.systemui.communal.util.CommunalColors +import com.android.systemui.dagger.SysUISingleton import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor import com.android.systemui.keyguard.shared.model.KeyguardState @@ -64,6 +65,7 @@ import kotlinx.coroutines.launch * * This will be used until the glanceable hub is integrated into Flexiglass. */ +@SysUISingleton class GlanceableHubContainerController @Inject constructor( diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java index 84b74782d09f..fe22cc628b5f 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java @@ -24,7 +24,6 @@ import static com.android.internal.jank.InteractionJankMonitor.CUJ_NOTIFICATION_ import static com.android.internal.jank.InteractionJankMonitor.CUJ_SHADE_CLEAR_ALL; import static com.android.systemui.Flags.newAodTransition; import static com.android.systemui.Flags.notificationOverExpansionClippingFix; -import static com.android.systemui.flags.Flags.UNCLEARED_TRANSIENT_HUN_FIX; import static com.android.systemui.statusbar.notification.stack.NotificationPriorityBucketKt.BUCKET_SILENT; import static com.android.systemui.statusbar.notification.stack.StackStateAnimator.ANIMATION_DURATION_SWIPE; import static com.android.systemui.util.DumpUtilsKt.println; @@ -2844,23 +2843,15 @@ public class NotificationStackScrollLayout mAddedHeadsUpChildren.remove(child); return false; } - if (mFeatureFlags.isEnabled(UNCLEARED_TRANSIENT_HUN_FIX)) { - // Skip adding animation for clicked heads up notifications when the - // Shade is closed, because the animation event is generated in - // generateHeadsUpAnimationEvents. Only report that an animation was - // actually generated (thus requesting the transient view be added) - // if a removal animation is in progress. - if (!isExpanded() && isClickedHeadsUp(child)) { - // An animation is already running, add it transiently - mClearTransientViewsWhenFinished.add(child); - return child.inRemovalAnimation(); - } - } else { - if (isClickedHeadsUp(child)) { - // An animation is already running, add it transiently - mClearTransientViewsWhenFinished.add(child); - return true; - } + // Skip adding animation for clicked heads up notifications when the + // Shade is closed, because the animation event is generated in + // generateHeadsUpAnimationEvents. Only report that an animation was + // actually generated (thus requesting the transient view be added) + // if a removal animation is in progress. + if (!isExpanded() && isClickedHeadsUp(child)) { + // An animation is already running, add it transiently + mClearTransientViewsWhenFinished.add(child); + return child.inRemovalAnimation(); } if (mDebugRemoveAnimation) { Log.d(TAG, "generateRemove " + key diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfaces.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfaces.java index 7b7a35b4928f..05a43917f7e0 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfaces.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfaces.java @@ -327,6 +327,11 @@ public interface CentralSurfaces extends Dumpable, LifecycleOwner, CoreStartable @Deprecated float getDisplayDensity(); + /** + * Forwards touch events to communal hub + */ + void handleCommunalHubTouch(MotionEvent event); + public static class KeyboardShortcutsMessage { final int mDeviceId; diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesEmptyImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesEmptyImpl.kt index 5ab56ae4be4d..a7b54847cdf9 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesEmptyImpl.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesEmptyImpl.kt @@ -81,6 +81,7 @@ abstract class CentralSurfacesEmptyImpl : CentralSurfaces { override fun shouldIgnoreTouch() = false override fun isDeviceInteractive() = false override fun handleDreamTouch(event: MotionEvent?) {} + override fun handleCommunalHubTouch(event: MotionEvent?) {} override fun awakenDreams() {} override fun isBouncerShowing() = false override fun isBouncerShowingScrimmed() = false diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java index e0da2fe584b6..78a803618c8b 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java @@ -172,6 +172,7 @@ import com.android.systemui.settings.UserTracker; import com.android.systemui.settings.brightness.BrightnessSliderController; import com.android.systemui.settings.brightness.domain.interactor.BrightnessMirrorShowingInteractor; import com.android.systemui.shade.CameraLauncher; +import com.android.systemui.shade.GlanceableHubContainerController; import com.android.systemui.shade.NotificationShadeWindowView; import com.android.systemui.shade.NotificationShadeWindowViewController; import com.android.systemui.shade.QuickSettingsController; @@ -595,6 +596,7 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces { private final ColorExtractor.OnColorsChangedListener mOnColorsChangedListener = (extractor, which) -> updateTheme(); private final BrightnessMirrorShowingInteractor mBrightnessMirrorShowingInteractor; + private final GlanceableHubContainerController mGlanceableHubContainerController; /** * Public constructor for CentralSurfaces. @@ -707,7 +709,8 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces { UserTracker userTracker, Provider<FingerprintManager> fingerprintManager, ActivityStarter activityStarter, - BrightnessMirrorShowingInteractor brightnessMirrorShowingInteractor + BrightnessMirrorShowingInteractor brightnessMirrorShowingInteractor, + GlanceableHubContainerController glanceableHubContainerController ) { mContext = context; mNotificationsController = notificationsController; @@ -802,6 +805,7 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces { mFingerprintManager = fingerprintManager; mActivityStarter = activityStarter; mBrightnessMirrorShowingInteractor = brightnessMirrorShowingInteractor; + mGlanceableHubContainerController = glanceableHubContainerController; mLockscreenShadeTransitionController = lockscreenShadeTransitionController; mStartingSurfaceOptional = startingSurfaceOptional; @@ -2951,6 +2955,11 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces { } @Override + public void handleCommunalHubTouch(MotionEvent event) { + mGlanceableHubContainerController.onTouchEvent(event); + } + + @Override public void awakenDreams() { mUiBgExecutor.execute(() -> { try { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/IndividualSensorPrivacyControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/IndividualSensorPrivacyControllerImpl.java index 58b82f166623..da928a364984 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/IndividualSensorPrivacyControllerImpl.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/IndividualSensorPrivacyControllerImpl.java @@ -32,6 +32,7 @@ import android.util.SparseBooleanArray; import androidx.annotation.NonNull; import com.android.internal.camera.flags.Flags; +import com.android.systemui.util.ListenerSet; import java.util.Set; @@ -43,7 +44,7 @@ public class IndividualSensorPrivacyControllerImpl implements IndividualSensorPr private final SparseBooleanArray mSoftwareToggleState = new SparseBooleanArray(); private final SparseBooleanArray mHardwareToggleState = new SparseBooleanArray(); private Boolean mRequiresAuthentication; - private final Set<Callback> mCallbacks = new ArraySet<>(); + private final ListenerSet<Callback> mCallbacks = new ListenerSet<>(); public IndividualSensorPrivacyControllerImpl( @NonNull SensorPrivacyManager sensorPrivacyManager) { @@ -115,7 +116,7 @@ public class IndividualSensorPrivacyControllerImpl implements IndividualSensorPr @Override public void addCallback(@NonNull Callback listener) { - mCallbacks.add(listener); + mCallbacks.addIfAbsent(listener); } @Override diff --git a/packages/SystemUI/src/com/android/systemui/volume/ui/navigation/VolumeNavigator.kt b/packages/SystemUI/src/com/android/systemui/volume/ui/navigation/VolumeNavigator.kt index 03c8af9020e1..99f956489bc3 100644 --- a/packages/SystemUI/src/com/android/systemui/volume/ui/navigation/VolumeNavigator.kt +++ b/packages/SystemUI/src/com/android/systemui/volume/ui/navigation/VolumeNavigator.kt @@ -97,7 +97,14 @@ constructor( } private fun showNewVolumePanel() { - volumePanelGlobalStateInteractor.setVisible(true) + activityStarter.dismissKeyguardThenExecute( + { + volumePanelGlobalStateInteractor.setVisible(true) + false + }, + {}, + true + ) } private fun createNewVolumePanelDialog(): Dialog { diff --git a/packages/SystemUI/tests/src/com/android/systemui/complication/DreamHomeControlsComplicationTest.java b/packages/SystemUI/tests/src/com/android/systemui/complication/DreamHomeControlsComplicationTest.java index 07c980bb6656..18bd960b30a5 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/complication/DreamHomeControlsComplicationTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/complication/DreamHomeControlsComplicationTest.java @@ -132,7 +132,7 @@ public class DreamHomeControlsComplicationTest extends SysuiTestCase { public void complicationAvailability_serviceNotAvailable_noFavorites_doNotAddComplication() { final DreamHomeControlsComplication.Registrant registrant = new DreamHomeControlsComplication.Registrant(mComplication, - mDreamOverlayStateController, mControlsComponent, mMonitor); + mDreamOverlayStateController, mControlsComponent, mMonitor, false); registrant.start(); setHaveFavorites(false); @@ -145,7 +145,7 @@ public class DreamHomeControlsComplicationTest extends SysuiTestCase { public void complicationAvailability_serviceAvailable_noFavorites_doNotAddComplication() { final DreamHomeControlsComplication.Registrant registrant = new DreamHomeControlsComplication.Registrant(mComplication, - mDreamOverlayStateController, mControlsComponent, mMonitor); + mDreamOverlayStateController, mControlsComponent, mMonitor, false); registrant.start(); setHaveFavorites(false); @@ -158,7 +158,7 @@ public class DreamHomeControlsComplicationTest extends SysuiTestCase { public void complicationAvailability_serviceAvailable_noFavorites_panel_addComplication() { final DreamHomeControlsComplication.Registrant registrant = new DreamHomeControlsComplication.Registrant(mComplication, - mDreamOverlayStateController, mControlsComponent, mMonitor); + mDreamOverlayStateController, mControlsComponent, mMonitor, false); registrant.start(); setHaveFavorites(false); @@ -171,7 +171,7 @@ public class DreamHomeControlsComplicationTest extends SysuiTestCase { public void complicationAvailability_serviceNotAvailable_haveFavorites_doNotAddComplication() { final DreamHomeControlsComplication.Registrant registrant = new DreamHomeControlsComplication.Registrant(mComplication, - mDreamOverlayStateController, mControlsComponent, mMonitor); + mDreamOverlayStateController, mControlsComponent, mMonitor, false); registrant.start(); setHaveFavorites(true); @@ -184,7 +184,7 @@ public class DreamHomeControlsComplicationTest extends SysuiTestCase { public void complicationAvailability_serviceAvailable_haveFavorites_addComplication() { final DreamHomeControlsComplication.Registrant registrant = new DreamHomeControlsComplication.Registrant(mComplication, - mDreamOverlayStateController, mControlsComponent, mMonitor); + mDreamOverlayStateController, mControlsComponent, mMonitor, false); registrant.start(); setHaveFavorites(true); @@ -197,7 +197,7 @@ public class DreamHomeControlsComplicationTest extends SysuiTestCase { public void complicationAvailability_checkAvailabilityWhenDreamOverlayBecomesActive() { final DreamHomeControlsComplication.Registrant registrant = new DreamHomeControlsComplication.Registrant(mComplication, - mDreamOverlayStateController, mControlsComponent, mMonitor); + mDreamOverlayStateController, mControlsComponent, mMonitor, false); registrant.start(); setServiceAvailable(true); diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/CustomizationProviderTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/CustomizationProviderTest.kt index 36bfa092c042..90ac05fc1b2e 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/CustomizationProviderTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/CustomizationProviderTest.kt @@ -135,6 +135,7 @@ class CustomizationProviderTest : SysuiTestCase() { .thenReturn(FakeSharedPreferences()) }, userTracker = userTracker, + systemSettings = FakeSettings(), broadcastDispatcher = fakeBroadcastDispatcher, ) val remoteUserSelectionManager = diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardBlueprintInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardBlueprintInteractorTest.kt index 0bdf47a51670..f0ad5103e9a6 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardBlueprintInteractorTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardBlueprintInteractorTest.kt @@ -38,6 +38,7 @@ import com.android.systemui.plugins.clocks.ClockController import com.android.systemui.shade.data.repository.shadeRepository import com.android.systemui.shade.shared.model.ShadeMode import com.android.systemui.testKosmos +import com.android.systemui.util.mockito.any import com.android.systemui.util.mockito.whenever import com.google.common.truth.Truth.assertThat import kotlinx.coroutines.ExperimentalCoroutinesApi @@ -48,6 +49,9 @@ import org.junit.Before import org.junit.Test import org.junit.runner.RunWith import org.mockito.Mock +import org.mockito.Mockito.reset +import org.mockito.Mockito.times +import org.mockito.Mockito.verify import org.mockito.MockitoAnnotations @ExperimentalCoroutinesApi @@ -57,6 +61,7 @@ class KeyguardBlueprintInteractorTest : SysuiTestCase() { private val kosmos = testKosmos() private val testScope = kosmos.testScope private val underTest = kosmos.keyguardBlueprintInteractor + private val keyguardBlueprintRepository = kosmos.keyguardBlueprintRepository private val clockRepository by lazy { kosmos.fakeKeyguardClockRepository } private val configurationRepository by lazy { kosmos.fakeConfigurationRepository } private val fingerprintPropertyRepository by lazy { kosmos.fakeFingerprintPropertyRepository } @@ -103,44 +108,46 @@ class KeyguardBlueprintInteractorTest : SysuiTestCase() { } @Test - @DisableFlags(Flags.FLAG_COMPOSE_LOCKSCREEN) - fun fingerprintPropertyInitialized_updatesBlueprint() { + @EnableFlags(Flags.FLAG_COMPOSE_LOCKSCREEN) + fun testDoesNotApplySplitShadeBlueprint() { testScope.runTest { - assertThat(kosmos.keyguardBlueprintRepository.targetTransitionConfig).isNull() - - fingerprintPropertyRepository.supportsUdfps() // initialize properties + val blueprintId by collectLastValue(underTest.blueprintId) + kosmos.shadeRepository.setShadeMode(ShadeMode.Split) + clockRepository.setCurrentClock(clockController) + configurationRepository.onConfigurationChange() runCurrent() advanceUntilIdle() - assertThat(kosmos.keyguardBlueprintRepository.targetTransitionConfig).isNotNull() + assertThat(blueprintId).isEqualTo(DefaultKeyguardBlueprint.DEFAULT) } } @Test - @EnableFlags(Flags.FLAG_COMPOSE_LOCKSCREEN) - fun testDoesNotApplySplitShadeBlueprint() { + @DisableFlags(Flags.FLAG_COMPOSE_LOCKSCREEN) + fun fingerprintPropertyInitialized_updatesBlueprint() { testScope.runTest { - val blueprintId by collectLastValue(underTest.blueprintId) - kosmos.shadeRepository.setShadeMode(ShadeMode.Split) - clockRepository.setCurrentClock(clockController) - configurationRepository.onConfigurationChange() + underTest.start() + reset(keyguardBlueprintRepository) + + fingerprintPropertyRepository.supportsUdfps() // initialize properties runCurrent() advanceUntilIdle() - assertThat(blueprintId).isEqualTo(DefaultKeyguardBlueprint.DEFAULT) + verify(keyguardBlueprintRepository, times(2)).refreshBlueprint(any()) } } @Test fun testRefreshFromConfigChange() { testScope.runTest { - assertThat(kosmos.keyguardBlueprintRepository.targetTransitionConfig).isNull() + underTest.start() + reset(keyguardBlueprintRepository) configurationRepository.onConfigurationChange() runCurrent() advanceUntilIdle() - assertThat(kosmos.keyguardBlueprintRepository.targetTransitionConfig).isNotNull() + verify(keyguardBlueprintRepository, times(2)).refreshBlueprint(any()) } } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorParameterizedTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorParameterizedTest.kt index 35659c12fad5..14d954873f0f 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorParameterizedTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorParameterizedTest.kt @@ -281,6 +281,7 @@ class KeyguardQuickAffordanceInteractorParameterizedTest : SysuiTestCase() { .thenReturn(FakeSharedPreferences()) }, userTracker = userTracker, + systemSettings = FakeSettings(), broadcastDispatcher = fakeBroadcastDispatcher, ) val remoteUserSelectionManager = diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorSceneContainerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorSceneContainerTest.kt index ef3183a8891f..ced3526f40be 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorSceneContainerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorSceneContainerTest.kt @@ -281,6 +281,7 @@ class KeyguardQuickAffordanceInteractorSceneContainerTest : SysuiTestCase() { .thenReturn(FakeSharedPreferences()) }, userTracker = userTracker, + systemSettings = FakeSettings(), broadcastDispatcher = fakeBroadcastDispatcher, ) val remoteUserSelectionManager = diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/blueprints/DefaultKeyguardBlueprintTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/blueprints/DefaultKeyguardBlueprintTest.kt index 616aac7ce460..344e0fc2bc6a 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/blueprints/DefaultKeyguardBlueprintTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/blueprints/DefaultKeyguardBlueprintTest.kt @@ -44,6 +44,7 @@ import com.android.systemui.keyguard.ui.view.layout.sections.SmartspaceSection import com.android.systemui.keyguard.ui.view.layout.sections.SplitShadeGuidelines import com.android.systemui.util.mockito.whenever import java.util.Optional +import kotlinx.coroutines.ExperimentalCoroutinesApi import org.junit.Before import org.junit.Test import org.junit.runner.RunWith @@ -55,6 +56,7 @@ import org.mockito.MockitoAnnotations @RunWith(AndroidTestingRunner::class) @RunWithLooper(setAsMainLooper = true) +@ExperimentalCoroutinesApi @SmallTest class DefaultKeyguardBlueprintTest : SysuiTestCase() { private lateinit var underTest: DefaultKeyguardBlueprint @@ -112,17 +114,66 @@ class DefaultKeyguardBlueprintTest : SysuiTestCase() { @Test fun replaceViews_withPrevBlueprint() { val prevBlueprint = mock(KeyguardBlueprint::class.java) - val someSection = mock(KeyguardSection::class.java) - whenever(prevBlueprint.sections) - .thenReturn(underTest.sections.minus(mDefaultDeviceEntrySection).plus(someSection)) + val removedSection = mock(KeyguardSection::class.java) + val addedSection = mDefaultDeviceEntrySection + val rebuildSection = clockSection + val prevSections = underTest.sections.minus(addedSection).plus(removedSection) + val unchangedSections = underTest.sections.subtract(listOf(addedSection, rebuildSection)) + whenever(prevBlueprint.sections).thenReturn(prevSections) + val constraintLayout = ConstraintLayout(context, null) underTest.replaceViews(constraintLayout, prevBlueprint) - underTest.sections.minus(mDefaultDeviceEntrySection).forEach { - verify(it, never())?.addViews(constraintLayout) + + unchangedSections.forEach { + verify(it, never()).addViews(constraintLayout) + verify(it, never()).removeViews(constraintLayout) + } + + verify(addedSection).addViews(constraintLayout) + verify(removedSection).removeViews(constraintLayout) + } + + @Test + fun replaceViews_withPrevBlueprint_withRebuildTargets() { + val prevBlueprint = mock(KeyguardBlueprint::class.java) + val removedSection = mock(KeyguardSection::class.java) + val addedSection = mDefaultDeviceEntrySection + val rebuildSection = clockSection + val prevSections = underTest.sections.minus(addedSection).plus(removedSection) + val unchangedSections = underTest.sections.subtract(listOf(addedSection, rebuildSection)) + whenever(prevBlueprint.sections).thenReturn(prevSections) + + val constraintLayout = ConstraintLayout(context, null) + underTest.replaceViews(constraintLayout, prevBlueprint, listOf(rebuildSection)) + + unchangedSections.forEach { + verify(it, never()).addViews(constraintLayout) + verify(it, never()).removeViews(constraintLayout) + } + + verify(addedSection).addViews(constraintLayout) + verify(rebuildSection).addViews(constraintLayout) + verify(rebuildSection).removeViews(constraintLayout) + verify(removedSection).removeViews(constraintLayout) + } + + @Test + fun rebuildViews() { + val rebuildSections = listOf(mDefaultDeviceEntrySection, clockSection) + val unchangedSections = underTest.sections.subtract(rebuildSections) + + val constraintLayout = ConstraintLayout(context, null) + underTest.rebuildViews(constraintLayout, rebuildSections) + + unchangedSections.forEach { + verify(it, never()).addViews(constraintLayout) + verify(it, never()).removeViews(constraintLayout) } - verify(mDefaultDeviceEntrySection).addViews(constraintLayout) - verify(someSection).removeViews(constraintLayout) + rebuildSections.forEach { + verify(it).addViews(constraintLayout) + verify(it).removeViews(constraintLayout) + } } @Test diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBottomAreaViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBottomAreaViewModelTest.kt index 16421a0f83b4..bdc5fc34158f 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBottomAreaViewModelTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBottomAreaViewModelTest.kt @@ -178,6 +178,7 @@ class KeyguardBottomAreaViewModelTest(flags: FlagsParameterization) : SysuiTestC .thenReturn(FakeSharedPreferences()) }, userTracker = userTracker, + systemSettings = FakeSettings(), broadcastDispatcher = fakeBroadcastDispatcher, ) val remoteUserSelectionManager = diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardQuickAffordancesCombinedViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardQuickAffordancesCombinedViewModelTest.kt index 833822076620..8a12a90efc4c 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardQuickAffordancesCombinedViewModelTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardQuickAffordancesCombinedViewModelTest.kt @@ -221,6 +221,7 @@ class KeyguardQuickAffordancesCombinedViewModelTest : SysuiTestCase() { .thenReturn(FakeSharedPreferences()) }, userTracker = userTracker, + systemSettings = FakeSettings(), broadcastDispatcher = fakeBroadcastDispatcher, ) val remoteUserSelectionManager = diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/controls/domain/pipeline/MediaDeviceManagerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/controls/domain/pipeline/MediaDeviceManagerTest.kt index d2701dd0d3a3..16d8819f13fb 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/media/controls/domain/pipeline/MediaDeviceManagerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/media/controls/domain/pipeline/MediaDeviceManagerTest.kt @@ -29,6 +29,7 @@ import android.media.session.MediaController.PlaybackInfo import android.media.session.MediaSession import android.platform.test.annotations.DisableFlags import android.platform.test.annotations.EnableFlags +import android.platform.test.annotations.RequiresFlagsDisabled import android.platform.test.flag.junit.DeviceFlagsValueProvider import android.testing.AndroidTestingRunner import android.testing.TestableLooper @@ -40,6 +41,7 @@ import com.android.settingslib.flags.Flags import com.android.settingslib.media.LocalMediaManager import com.android.settingslib.media.MediaDevice import com.android.settingslib.media.PhoneMediaDevice +import com.android.settingslib.media.flags.Flags.FLAG_USE_PLAYBACK_INFO_FOR_ROUTING_CONTROLS import com.android.systemui.SysuiTestCase import com.android.systemui.media.controls.MediaTestUtils import com.android.systemui.media.controls.shared.model.MediaData @@ -101,7 +103,7 @@ public class MediaDeviceManagerTest : SysuiTestCase() { @Mock private lateinit var listener: MediaDeviceManager.Listener @Mock private lateinit var device: MediaDevice @Mock private lateinit var icon: Drawable - @Mock private lateinit var route: RoutingSessionInfo + @Mock private lateinit var routingSession: RoutingSessionInfo @Mock private lateinit var selectedRoute: MediaRoute2Info @Mock private lateinit var controller: MediaController @Mock private lateinit var playbackInfo: PlaybackInfo @@ -141,7 +143,10 @@ public class MediaDeviceManagerTest : SysuiTestCase() { whenever(lmmFactory.create(PACKAGE)).thenReturn(lmm) whenever(muteAwaitFactory.create(lmm)).thenReturn(muteAwaitManager) whenever(lmm.getCurrentConnectedDevice()).thenReturn(device) - whenever(mr2.getRoutingSessionForMediaController(any())).thenReturn(route) + whenever(mr2.getRoutingSessionForMediaController(any())).thenReturn(routingSession) + + whenever(playbackInfo.playbackType).thenReturn(PlaybackInfo.PLAYBACK_TYPE_LOCAL) + whenever(controller.playbackInfo).thenReturn(playbackInfo) // Create a media sesssion and notification for testing. session = MediaSession(context, SESSION_KEY) @@ -235,6 +240,7 @@ public class MediaDeviceManagerTest : SysuiTestCase() { reset(listener) // WHEN media data is loaded with a different token // AND that token results in a null route + whenever(playbackInfo.playbackType).thenReturn(PlaybackInfo.PLAYBACK_TYPE_REMOTE) whenever(mr2.getRoutingSessionForMediaController(any())).thenReturn(null) manager.onMediaDataLoaded(KEY, null, mediaData) fakeBgExecutor.runAllReady() @@ -394,9 +400,10 @@ public class MediaDeviceManagerTest : SysuiTestCase() { } @Test - fun deviceNameFromMR2RouteInfo() { + fun onMediaDataLoaded_withRemotePlaybackType_usesNonNullRoutingSessionName() { // GIVEN that MR2Manager returns a valid routing session - whenever(route.name).thenReturn(REMOTE_DEVICE_NAME) + whenever(routingSession.name).thenReturn(REMOTE_DEVICE_NAME) + whenever(playbackInfo.playbackType).thenReturn(PlaybackInfo.PLAYBACK_TYPE_REMOTE) // WHEN a notification is added manager.onMediaDataLoaded(KEY, null, mediaData) fakeBgExecutor.runAllReady() @@ -408,8 +415,9 @@ public class MediaDeviceManagerTest : SysuiTestCase() { } @Test - fun deviceDisabledWhenMR2ReturnsNullRouteInfo() { + fun onMediaDataLoaded_withRemotePlaybackInfo_noMatchingRoutingSession_setsDisabledDevice() { // GIVEN that MR2Manager returns null for routing session + whenever(playbackInfo.playbackType).thenReturn(PlaybackInfo.PLAYBACK_TYPE_REMOTE) whenever(mr2.getRoutingSessionForMediaController(any())).thenReturn(null) // WHEN a notification is added manager.onMediaDataLoaded(KEY, null, mediaData) @@ -422,13 +430,14 @@ public class MediaDeviceManagerTest : SysuiTestCase() { } @Test - fun deviceDisabledWhenMR2ReturnsNullRouteInfoOnDeviceChanged() { + fun onSelectedDeviceStateChanged_withRemotePlaybackInfo_noMatchingRoutingSession_setsDisabledDevice() { // GIVEN a notif is added manager.onMediaDataLoaded(KEY, null, mediaData) fakeBgExecutor.runAllReady() fakeFgExecutor.runAllReady() reset(listener) // AND MR2Manager returns null for routing session + whenever(playbackInfo.playbackType).thenReturn(PlaybackInfo.PLAYBACK_TYPE_REMOTE) whenever(mr2.getRoutingSessionForMediaController(any())).thenReturn(null) // WHEN the selected device changes state val deviceCallback = captureCallback() @@ -442,13 +451,14 @@ public class MediaDeviceManagerTest : SysuiTestCase() { } @Test - fun deviceDisabledWhenMR2ReturnsNullRouteInfoOnDeviceListUpdate() { + fun onDeviceListUpdate_withRemotePlaybackInfo_noMatchingRoutingSession_setsDisabledDevice() { // GIVEN a notif is added manager.onMediaDataLoaded(KEY, null, mediaData) fakeBgExecutor.runAllReady() fakeFgExecutor.runAllReady() reset(listener) // GIVEN that MR2Manager returns null for routing session + whenever(playbackInfo.playbackType).thenReturn(PlaybackInfo.PLAYBACK_TYPE_REMOTE) whenever(mr2.getRoutingSessionForMediaController(any())).thenReturn(null) // WHEN the selected device changes state val deviceCallback = captureCallback() @@ -461,15 +471,17 @@ public class MediaDeviceManagerTest : SysuiTestCase() { assertThat(data.name).isNull() } + // With the flag enabled, MediaDeviceManager no longer gathers device name information directly. + @RequiresFlagsDisabled(FLAG_USE_PLAYBACK_INFO_FOR_ROUTING_CONTROLS) @Test fun mr2ReturnsSystemRouteWithNullName_isPhone_usePhoneName() { // When the routing session name is null, and is a system session for a PhoneMediaDevice val phoneDevice = mock(PhoneMediaDevice::class.java) whenever(phoneDevice.iconWithoutBackground).thenReturn(icon) whenever(lmm.currentConnectedDevice).thenReturn(phoneDevice) - whenever(route.isSystemSession).thenReturn(true) + whenever(routingSession.isSystemSession).thenReturn(true) - whenever(route.name).thenReturn(null) + whenever(routingSession.name).thenReturn(null) whenever(mr2.getSelectedRoutes(any())).thenReturn(listOf(selectedRoute)) whenever(selectedRoute.name).thenReturn(REMOTE_DEVICE_NAME) whenever(selectedRoute.type).thenReturn(MediaRoute2Info.TYPE_BUILTIN_SPEAKER) @@ -483,13 +495,15 @@ public class MediaDeviceManagerTest : SysuiTestCase() { assertThat(data.name).isEqualTo(PhoneMediaDevice.getMediaTransferThisDeviceName(context)) } + // With the flag enabled, MediaDeviceManager no longer gathers device name information directly. + @RequiresFlagsDisabled(FLAG_USE_PLAYBACK_INFO_FOR_ROUTING_CONTROLS) @Test fun mr2ReturnsSystemRouteWithNullName_useSelectedRouteName() { // When the routing session does not have a name, and is a system session - whenever(route.name).thenReturn(null) + whenever(routingSession.name).thenReturn(null) whenever(mr2.getSelectedRoutes(any())).thenReturn(listOf(selectedRoute)) whenever(selectedRoute.name).thenReturn(REMOTE_DEVICE_NAME) - whenever(route.isSystemSession).thenReturn(true) + whenever(routingSession.isSystemSession).thenReturn(true) manager.onMediaDataLoaded(KEY, null, mediaData) fakeBgExecutor.runAllReady() @@ -503,8 +517,8 @@ public class MediaDeviceManagerTest : SysuiTestCase() { @Test fun mr2ReturnsNonSystemRouteWithNullName_useLocalDeviceName() { // GIVEN that MR2Manager returns a routing session that does not have a name - whenever(route.name).thenReturn(null) - whenever(route.isSystemSession).thenReturn(false) + whenever(routingSession.name).thenReturn(null) + whenever(routingSession.isSystemSession).thenReturn(false) // WHEN a notification is added manager.onMediaDataLoaded(KEY, null, mediaData) fakeBgExecutor.runAllReady() @@ -534,7 +548,7 @@ public class MediaDeviceManagerTest : SysuiTestCase() { } @Test - fun audioInfoVolumeControlIdChanged() { + fun onAudioInfoChanged_withRemotePlaybackInfo_queriesRoutingSession() { whenever(playbackInfo.getPlaybackType()).thenReturn(PlaybackInfo.PLAYBACK_TYPE_LOCAL) whenever(playbackInfo.getVolumeControlId()).thenReturn(null) whenever(controller.getPlaybackInfo()).thenReturn(playbackInfo) @@ -545,10 +559,11 @@ public class MediaDeviceManagerTest : SysuiTestCase() { reset(mr2) // WHEN onAudioInfoChanged fires with a volume control id change whenever(playbackInfo.getVolumeControlId()).thenReturn("placeholder id") + whenever(playbackInfo.playbackType).thenReturn(PlaybackInfo.PLAYBACK_TYPE_REMOTE) val captor = ArgumentCaptor.forClass(MediaController.Callback::class.java) verify(controller).registerCallback(captor.capture()) captor.value.onAudioInfoChanged(playbackInfo) - // THEN the route is checked + // THEN the routing session is checked verify(mr2).getRoutingSessionForMediaController(eq(controller)) } @@ -654,7 +669,7 @@ public class MediaDeviceManagerTest : SysuiTestCase() { @Test fun testRemotePlaybackDeviceOverride() { - whenever(route.name).thenReturn(DEVICE_NAME) + whenever(routingSession.name).thenReturn(DEVICE_NAME) val deviceData = MediaDeviceData(false, null, REMOTE_DEVICE_NAME, null, showBroadcastButton = false) val mediaDataWithDevice = mediaData.copy(device = deviceData) diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/panels/data/QSPreferencesRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/panels/data/QSPreferencesRepositoryTest.kt new file mode 100644 index 000000000000..b0aa6ddf44ce --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/panels/data/QSPreferencesRepositoryTest.kt @@ -0,0 +1,130 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.qs.panels.data + +import android.content.Context +import android.content.SharedPreferences +import android.content.pm.UserInfo +import android.testing.AndroidTestingRunner +import androidx.test.filters.SmallTest +import com.android.systemui.SysuiTestCase +import com.android.systemui.coroutines.collectLastValue +import com.android.systemui.kosmos.testScope +import com.android.systemui.qs.panels.data.repository.QSPreferencesRepository +import com.android.systemui.qs.panels.data.repository.qsPreferencesRepository +import com.android.systemui.settings.userFileManager +import com.android.systemui.testKosmos +import com.android.systemui.user.data.repository.fakeUserRepository +import com.android.systemui.user.data.repository.userRepository +import com.google.common.truth.Truth.assertThat +import kotlinx.coroutines.test.runTest +import org.junit.Test +import org.junit.runner.RunWith + +@SmallTest +@RunWith(AndroidTestingRunner::class) +class QSPreferencesRepositoryTest : SysuiTestCase() { + private val kosmos = testKosmos() + private val underTest = with(kosmos) { qsPreferencesRepository } + + @Test + fun showLabels_updatesFromSharedPreferences() = + with(kosmos) { + testScope.runTest { + val latest by collectLastValue(underTest.showLabels) + assertThat(latest).isFalse() + + setShowLabelsInSharedPreferences(true) + assertThat(latest).isTrue() + + setShowLabelsInSharedPreferences(false) + assertThat(latest).isFalse() + } + } + + @Test + fun showLabels_updatesFromUserChange() = + with(kosmos) { + testScope.runTest { + fakeUserRepository.setUserInfos(USERS) + val latest by collectLastValue(underTest.showLabels) + + fakeUserRepository.setSelectedUserInfo(PRIMARY_USER) + setShowLabelsInSharedPreferences(false) + + fakeUserRepository.setSelectedUserInfo(ANOTHER_USER) + setShowLabelsInSharedPreferences(true) + + fakeUserRepository.setSelectedUserInfo(PRIMARY_USER) + assertThat(latest).isFalse() + } + } + + @Test + fun setShowLabels_inSharedPreferences() { + underTest.setShowLabels(false) + assertThat(getShowLabelsFromSharedPreferences(true)).isFalse() + + underTest.setShowLabels(true) + assertThat(getShowLabelsFromSharedPreferences(false)).isTrue() + } + + @Test + fun setShowLabels_forDifferentUser() = + with(kosmos) { + testScope.runTest { + fakeUserRepository.setUserInfos(USERS) + + fakeUserRepository.setSelectedUserInfo(PRIMARY_USER) + underTest.setShowLabels(false) + assertThat(getShowLabelsFromSharedPreferences(true)).isFalse() + + fakeUserRepository.setSelectedUserInfo(ANOTHER_USER) + underTest.setShowLabels(true) + assertThat(getShowLabelsFromSharedPreferences(false)).isTrue() + + fakeUserRepository.setSelectedUserInfo(PRIMARY_USER) + assertThat(getShowLabelsFromSharedPreferences(true)).isFalse() + } + } + + private fun getSharedPreferences(): SharedPreferences = + with(kosmos) { + return userFileManager.getSharedPreferences( + QSPreferencesRepository.FILE_NAME, + Context.MODE_PRIVATE, + userRepository.getSelectedUserInfo().id, + ) + } + + private fun setShowLabelsInSharedPreferences(value: Boolean) { + getSharedPreferences().edit().putBoolean(ICON_LABELS_KEY, value).apply() + } + + private fun getShowLabelsFromSharedPreferences(defaultValue: Boolean): Boolean { + return getSharedPreferences().getBoolean(ICON_LABELS_KEY, defaultValue) + } + + companion object { + private const val ICON_LABELS_KEY = "show_icon_labels" + private const val PRIMARY_USER_ID = 0 + private val PRIMARY_USER = UserInfo(PRIMARY_USER_ID, "user 0", UserInfo.FLAG_MAIN) + private const val ANOTHER_USER_ID = 1 + private val ANOTHER_USER = UserInfo(ANOTHER_USER_ID, "user 1", UserInfo.FLAG_FULL) + private val USERS = listOf(PRIMARY_USER, ANOTHER_USER) + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/panels/domain/interactor/IconLabelVisibilityInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/panels/domain/interactor/IconLabelVisibilityInteractorTest.kt new file mode 100644 index 000000000000..9b08432e290f --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/panels/domain/interactor/IconLabelVisibilityInteractorTest.kt @@ -0,0 +1,90 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.qs.panels.domain.interactor + +import android.content.pm.UserInfo +import android.testing.AndroidTestingRunner +import androidx.test.filters.SmallTest +import com.android.systemui.SysuiTestCase +import com.android.systemui.coroutines.collectLastValue +import com.android.systemui.kosmos.testScope +import com.android.systemui.testKosmos +import com.android.systemui.user.data.repository.fakeUserRepository +import com.google.common.truth.Truth.assertThat +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.test.runCurrent +import kotlinx.coroutines.test.runTest +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith + +@OptIn(ExperimentalCoroutinesApi::class) +@SmallTest +@RunWith(AndroidTestingRunner::class) +class IconLabelVisibilityInteractorTest : SysuiTestCase() { + private val kosmos = testKosmos() + private val underTest = with(kosmos) { iconLabelVisibilityInteractor } + + @Before + fun setUp() { + with(kosmos) { fakeUserRepository.setUserInfos(USERS) } + } + + @Test + fun changingShowLabels_receivesCorrectShowLabels() = + with(kosmos) { + testScope.runTest { + val showLabels by collectLastValue(underTest.showLabels) + + underTest.setShowLabels(false) + runCurrent() + assertThat(showLabels).isFalse() + + underTest.setShowLabels(true) + runCurrent() + assertThat(showLabels).isTrue() + } + } + + @Test + fun changingUser_receivesCorrectShowLabels() = + with(kosmos) { + testScope.runTest { + val showLabels by collectLastValue(underTest.showLabels) + + fakeUserRepository.setSelectedUserInfo(PRIMARY_USER) + underTest.setShowLabels(false) + runCurrent() + assertThat(showLabels).isFalse() + + fakeUserRepository.setSelectedUserInfo(ANOTHER_USER) + underTest.setShowLabels(true) + runCurrent() + assertThat(showLabels).isTrue() + + fakeUserRepository.setSelectedUserInfo(PRIMARY_USER) + runCurrent() + assertThat(showLabels).isFalse() + } + } + + companion object { + private val PRIMARY_USER = UserInfo(0, "user 0", UserInfo.FLAG_MAIN) + private val ANOTHER_USER = UserInfo(1, "user 1", UserInfo.FLAG_FULL) + private val USERS = listOf(PRIMARY_USER, ANOTHER_USER) + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/InternetDialogDelegateControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/InternetDialogDelegateControllerTest.java index 9798562ab5a8..29487cdace2d 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/InternetDialogDelegateControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/InternetDialogDelegateControllerTest.java @@ -1116,6 +1116,34 @@ public class InternetDialogDelegateControllerTest extends SysuiTestCase { assertThat(mInternetDialogController.hasActiveSubIdOnDds()).isFalse(); } + @Test + public void hasActiveSubIdOnDds_activeDdsAndIsOnlyNonTerrestrialNetwork_returnFalse() { + when(SubscriptionManager.getDefaultDataSubscriptionId()) + .thenReturn(SUB_ID); + SubscriptionInfo info = mock(SubscriptionInfo.class); + when(info.isEmbedded()).thenReturn(true); + when(info.isOnlyNonTerrestrialNetwork()).thenReturn(true); + when(mSubscriptionManager.getActiveSubscriptionInfo(SUB_ID)).thenReturn(info); + + mInternetDialogController.mOnSubscriptionsChangedListener.onSubscriptionsChanged(); + + assertFalse(mInternetDialogController.hasActiveSubIdOnDds()); + } + + @Test + public void hasActiveSubIdOnDds_activeDdsAndIsNotOnlyNonTerrestrialNetwork_returnTrue() { + when(SubscriptionManager.getDefaultDataSubscriptionId()) + .thenReturn(SUB_ID); + SubscriptionInfo info = mock(SubscriptionInfo.class); + when(info.isEmbedded()).thenReturn(true); + when(info.isOnlyNonTerrestrialNetwork()).thenReturn(false); + when(mSubscriptionManager.getActiveSubscriptionInfo(SUB_ID)).thenReturn(info); + + mInternetDialogController.mOnSubscriptionsChangedListener.onSubscriptionsChanged(); + + assertTrue(mInternetDialogController.hasActiveSubIdOnDds()); + } + private String getResourcesString(String name) { return mContext.getResources().getString(getResourcesId(name)); } diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java index f461e2f67d20..12f3ef3cf553 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java @@ -171,7 +171,6 @@ public class NotificationStackScrollLayoutTest extends SysuiTestCase { // and then we would test both configurations, but currently they are all read // in the constructor. mSetFlagsRule.enableFlags(FLAG_NEW_AOD_TRANSITION); - mFeatureFlags.setDefault(Flags.UNCLEARED_TRANSIENT_HUN_FIX); // Inject dependencies before initializing the layout mDependency.injectTestDependency(FeatureFlags.class, mFeatureFlags); diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java index 62804ed1412e..cb9790b2495a 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java @@ -138,6 +138,7 @@ import com.android.systemui.settings.UserTracker; import com.android.systemui.settings.brightness.BrightnessSliderController; import com.android.systemui.settings.brightness.domain.interactor.BrightnessMirrorShowingInteractor; import com.android.systemui.shade.CameraLauncher; +import com.android.systemui.shade.GlanceableHubContainerController; import com.android.systemui.shade.NotificationPanelView; import com.android.systemui.shade.NotificationPanelViewController; import com.android.systemui.shade.NotificationShadeWindowViewController; @@ -337,6 +338,7 @@ public class CentralSurfacesImplTest extends SysuiTestCase { @Mock private KeyboardShortcuts mKeyboardShortcuts; @Mock private KeyboardShortcutListSearch mKeyboardShortcutListSearch; @Mock private PackageManager mPackageManager; + @Mock private GlanceableHubContainerController mGlanceableHubContainerController; private ShadeController mShadeController; private final FakeSystemClock mFakeSystemClock = new FakeSystemClock(); @@ -590,7 +592,8 @@ public class CentralSurfacesImplTest extends SysuiTestCase { mUserTracker, () -> mFingerprintManager, mActivityStarter, - mBrightnessMirrorShowingInteractor + mBrightnessMirrorShowingInteractor, + mGlanceableHubContainerController ); mScreenLifecycle.addObserver(mCentralSurfaces.mScreenObserver); mCentralSurfaces.initShadeVisibilityListener(); diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/KeyguardBlueprintRepositoryKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/KeyguardBlueprintRepositoryKosmos.kt index a6b40df8e81b..fb12897ead19 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/KeyguardBlueprintRepositoryKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/KeyguardBlueprintRepositoryKosmos.kt @@ -23,12 +23,14 @@ import com.android.systemui.keyguard.domain.interactor.keyguardClockInteractor import com.android.systemui.keyguard.ui.view.layout.blueprints.DefaultKeyguardBlueprint import com.android.systemui.keyguard.ui.view.layout.blueprints.SplitShadeKeyguardBlueprint import com.android.systemui.keyguard.ui.view.layout.sections.ClockSection +import com.android.systemui.keyguard.ui.view.layout.sections.SmartspaceSection import com.android.systemui.keyguard.ui.viewmodel.keyguardClockViewModel import com.android.systemui.keyguard.ui.viewmodel.keyguardRootViewModel import com.android.systemui.keyguard.ui.viewmodel.keyguardSmartspaceViewModel import com.android.systemui.kosmos.Kosmos import com.android.systemui.util.mockito.mock import java.util.Optional +import org.mockito.Mockito.spy val Kosmos.keyguardClockSection: ClockSection by Kosmos.Fixture { @@ -42,6 +44,9 @@ val Kosmos.keyguardClockSection: ClockSection by ) } +val Kosmos.keyguardSmartspaceSection: SmartspaceSection by + Kosmos.Fixture { mock<SmartspaceSection>() } + val Kosmos.defaultKeyguardBlueprint by Kosmos.Fixture { DefaultKeyguardBlueprint( @@ -57,7 +62,7 @@ val Kosmos.defaultKeyguardBlueprint by aodBurnInSection = mock(), communalTutorialIndicatorSection = mock(), clockSection = keyguardClockSection, - smartspaceSection = mock(), + smartspaceSection = keyguardSmartspaceSection, keyguardSliceViewSection = mock(), udfpsAccessibilityOverlaySection = mock(), accessibilityActionsSection = mock(), @@ -80,7 +85,7 @@ val Kosmos.splitShadeBlueprint by aodBurnInSection = mock(), communalTutorialIndicatorSection = mock(), clockSection = keyguardClockSection, - smartspaceSection = mock(), + smartspaceSection = keyguardSmartspaceSection, mediaSection = mock(), accessibilityActionsSection = mock(), ) @@ -88,13 +93,15 @@ val Kosmos.splitShadeBlueprint by val Kosmos.keyguardBlueprintRepository by Kosmos.Fixture { - KeyguardBlueprintRepository( - blueprints = - setOf( - defaultKeyguardBlueprint, - splitShadeBlueprint, - ), - handler = fakeExecutorHandler, - assert = mock(), + spy( + KeyguardBlueprintRepository( + blueprints = + setOf( + defaultKeyguardBlueprint, + splitShadeBlueprint, + ), + handler = fakeExecutorHandler, + assert = mock(), + ) ) } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardBlueprintInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardBlueprintInteractorKosmos.kt index 5256ce4b9adf..4328ca153374 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardBlueprintInteractorKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardBlueprintInteractorKosmos.kt @@ -20,6 +20,8 @@ import android.content.applicationContext import com.android.systemui.biometrics.domain.interactor.fingerprintPropertyInteractor import com.android.systemui.common.ui.domain.interactor.configurationInteractor import com.android.systemui.keyguard.data.repository.keyguardBlueprintRepository +import com.android.systemui.keyguard.data.repository.keyguardClockSection +import com.android.systemui.keyguard.data.repository.keyguardSmartspaceSection import com.android.systemui.kosmos.Kosmos import com.android.systemui.kosmos.applicationCoroutineScope import com.android.systemui.shade.domain.interactor.shadeInteractor @@ -34,5 +36,7 @@ val Kosmos.keyguardBlueprintInteractor by clockInteractor = keyguardClockInteractor, configurationInteractor = configurationInteractor, fingerprintPropertyInteractor = fingerprintPropertyInteractor, + clockSection = keyguardClockSection, + smartspaceSection = keyguardSmartspaceSection, ) } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/data/repository/QSPreferencesRepositoryKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/data/repository/QSPreferencesRepositoryKosmos.kt new file mode 100644 index 000000000000..39ae5eb44c65 --- /dev/null +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/data/repository/QSPreferencesRepositoryKosmos.kt @@ -0,0 +1,25 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.qs.panels.data.repository + +import com.android.systemui.kosmos.Kosmos +import com.android.systemui.kosmos.testDispatcher +import com.android.systemui.settings.userFileManager +import com.android.systemui.user.data.repository.userRepository + +val Kosmos.qsPreferencesRepository by + Kosmos.Fixture { QSPreferencesRepository(userFileManager, userRepository, testDispatcher) } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/domain/interactor/IconLabelVisibilityInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/domain/interactor/IconLabelVisibilityInteractorKosmos.kt index 7b9e4a17e998..954084b874a0 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/domain/interactor/IconLabelVisibilityInteractorKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/domain/interactor/IconLabelVisibilityInteractorKosmos.kt @@ -19,12 +19,11 @@ package com.android.systemui.qs.panels.domain.interactor import com.android.systemui.kosmos.Kosmos import com.android.systemui.kosmos.applicationCoroutineScope import com.android.systemui.log.core.FakeLogBuffer -import com.android.systemui.qs.panels.data.repository.iconLabelVisibilityRepository val Kosmos.iconLabelVisibilityInteractor by Kosmos.Fixture { IconLabelVisibilityInteractor( - iconLabelVisibilityRepository, + qsPreferencesInteractor, FakeLogBuffer.Factory.create(), applicationCoroutineScope ) diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/data/repository/IconLabelVisibilityRepositoryKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/domain/interactor/QSPreferencesInteractorKosmos.kt index 277dbb7016ad..eb83e325d79b 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/data/repository/IconLabelVisibilityRepositoryKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/domain/interactor/QSPreferencesInteractorKosmos.kt @@ -14,8 +14,10 @@ * limitations under the License. */ -package com.android.systemui.qs.panels.data.repository +package com.android.systemui.qs.panels.domain.interactor import com.android.systemui.kosmos.Kosmos +import com.android.systemui.qs.panels.data.repository.qsPreferencesRepository -val Kosmos.iconLabelVisibilityRepository by Kosmos.Fixture { IconLabelVisibilityRepository() } +val Kosmos.qsPreferencesInteractor by + Kosmos.Fixture { QSPreferencesInteractor(qsPreferencesRepository) } diff --git a/services/backup/java/com/android/server/backup/UserBackupManagerService.java b/services/backup/java/com/android/server/backup/UserBackupManagerService.java index dc1cfb92c3b8..ddccb3731cc1 100644 --- a/services/backup/java/com/android/server/backup/UserBackupManagerService.java +++ b/services/backup/java/com/android/server/backup/UserBackupManagerService.java @@ -4008,20 +4008,10 @@ public class UserBackupManagerService { } private PackageInfo getPackageInfoForBMMLogging(String packageName) { - try { - return mPackageManager.getPackageInfoAsUser(packageName, 0, mUserId); - } catch (NameNotFoundException e) { - Slog.w( - TAG, - addUserIdToLogMessage( - mUserId, "Asked to get PackageInfo for BMM logging of nonexistent pkg " - + packageName)); - - PackageInfo packageInfo = new PackageInfo(); - packageInfo.packageName = packageName; + PackageInfo packageInfo = new PackageInfo(); + packageInfo.packageName = packageName; - return packageInfo; - } + return packageInfo; } /** Hand off a restore session. */ diff --git a/services/core/java/com/android/server/StorageManagerService.java b/services/core/java/com/android/server/StorageManagerService.java index 4dd3a8f67b0d..b35959f1a6e8 100644 --- a/services/core/java/com/android/server/StorageManagerService.java +++ b/services/core/java/com/android/server/StorageManagerService.java @@ -3980,7 +3980,7 @@ class StorageManagerService extends IStorageManager.Stub if (resUuids.contains(rec.fsUuid)) continue; // Treat as recent if mounted within the last week - if (rec.lastSeenMillis > 0 && rec.lastSeenMillis < lastWeek) { + if (rec.lastSeenMillis > 0 && rec.lastSeenMillis >= lastWeek) { final StorageVolume userVol = rec.buildStorageVolume(mContext); res.add(userVol); resUuids.add(userVol.getUuid()); diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java index baa7f2f389e8..2addf6f9ec96 100644 --- a/services/core/java/com/android/server/audio/AudioService.java +++ b/services/core/java/com/android/server/audio/AudioService.java @@ -1901,7 +1901,6 @@ public class AudioService extends IAudioService.Stub } mSpatializerHelper.reset(/* featureEnabled */ mHasSpatializerEffect); - mSoundDoseHelper.reset(); // Restore rotation information. if (mMonitorRotation) { @@ -1912,6 +1911,8 @@ public class AudioService extends IAudioService.Stub // indicate the end of reconfiguration phase to audio HAL AudioSystem.setParameters("restarting=false"); + mSoundDoseHelper.reset(/*resetISoundDose=*/true); + sendMsg(mAudioHandler, MSG_DISPATCH_AUDIO_SERVER_STATE, SENDMSG_QUEUE, 1, 0, null, 0); diff --git a/services/core/java/com/android/server/audio/SoundDoseHelper.java b/services/core/java/com/android/server/audio/SoundDoseHelper.java index 9610034caf01..e28ae952e65a 100644 --- a/services/core/java/com/android/server/audio/SoundDoseHelper.java +++ b/services/core/java/com/android/server/audio/SoundDoseHelper.java @@ -856,11 +856,12 @@ public class SoundDoseHelper { pw.println(); } - /*package*/void reset() { + /*package*/void reset(boolean resetISoundDose) { Log.d(TAG, "Reset the sound dose helper"); - mSoundDose.compareAndExchange(/*expectedValue=*/null, - AudioSystem.getSoundDoseInterface(mSoundDoseCallback)); + if (resetISoundDose) { + mSoundDose.set(AudioSystem.getSoundDoseInterface(mSoundDoseCallback)); + } synchronized (mCsdStateLock) { try { @@ -972,7 +973,7 @@ public class SoundDoseHelper { } } - reset(); + reset(/*resetISoundDose=*/false); } private void onConfigureSafeMedia(boolean force, String caller) { diff --git a/services/core/java/com/android/server/hdmi/HdmiCecMessageValidator.java b/services/core/java/com/android/server/hdmi/HdmiCecMessageValidator.java index d876a381ca7a..3c3bdd5b69f6 100644 --- a/services/core/java/com/android/server/hdmi/HdmiCecMessageValidator.java +++ b/services/core/java/com/android/server/hdmi/HdmiCecMessageValidator.java @@ -269,8 +269,7 @@ public class HdmiCecMessageValidator { addValidationInfo(Constants.MESSAGE_REQUEST_SHORT_AUDIO_DESCRIPTOR, oneByteValidator, ADDR_NOT_UNREGISTERED, ADDR_DIRECT); addValidationInfo(Constants.MESSAGE_SET_SYSTEM_AUDIO_MODE, - new MinimumOneByteRangeValidator(0x00, 0x01), - ADDR_NOT_UNREGISTERED, ADDR_ALL); + new SingleByteRangeValidator(0x00, 0x01), ADDR_AUDIO_SYSTEM, ADDR_ALL); addValidationInfo(Constants.MESSAGE_SYSTEM_AUDIO_MODE_STATUS, new SingleByteRangeValidator(0x00, 0x01), ADDR_NOT_UNREGISTERED, ADDR_DIRECT); diff --git a/services/core/java/com/android/server/hdmi/HdmiUtils.java b/services/core/java/com/android/server/hdmi/HdmiUtils.java index 5646e1b9a9ef..0688fbf358ad 100644 --- a/services/core/java/com/android/server/hdmi/HdmiUtils.java +++ b/services/core/java/com/android/server/hdmi/HdmiUtils.java @@ -175,14 +175,15 @@ final class HdmiUtils { * * @param logicalAddress the logical address to verify * @param deviceType the device type to check - * @throws IllegalArgumentException */ - static void verifyAddressType(int logicalAddress, int deviceType) { + static boolean verifyAddressType(int logicalAddress, int deviceType) { List<Integer> actualDeviceTypes = getTypeFromAddress(logicalAddress); if (!actualDeviceTypes.contains(deviceType)) { - throw new IllegalArgumentException("Device type missmatch:[Expected:" + deviceType - + ", Actual:" + actualDeviceTypes); + Slog.w(TAG,"Device type mismatch:[Expected:" + deviceType + + ", Actual:" + actualDeviceTypes + "]"); + return false; } + return true; } /** diff --git a/services/core/java/com/android/server/hdmi/RequestArcAction.java b/services/core/java/com/android/server/hdmi/RequestArcAction.java index 54c8c00b8889..58e146ecaa78 100644 --- a/services/core/java/com/android/server/hdmi/RequestArcAction.java +++ b/services/core/java/com/android/server/hdmi/RequestArcAction.java @@ -19,6 +19,7 @@ package com.android.server.hdmi; import android.hardware.hdmi.HdmiControlManager; import android.hardware.hdmi.HdmiDeviceInfo; import android.hardware.hdmi.IHdmiControlCallback; +import android.util.Slog; /** * Base feature action class for <Request ARC Initiation>/<Request ARC Termination>. @@ -38,13 +39,14 @@ abstract class RequestArcAction extends HdmiCecFeatureAction { * @param source {@link HdmiCecLocalDevice} instance * @param avrAddress address of AV receiver. It should be AUDIO_SYSTEM type * @param callback callback to inform about the status of the action - * @throws IllegalArgumentException if device type of sourceAddress and avrAddress - * is invalid */ RequestArcAction(HdmiCecLocalDevice source, int avrAddress, IHdmiControlCallback callback) { super(source, callback); - HdmiUtils.verifyAddressType(getSourceAddress(), HdmiDeviceInfo.DEVICE_TV); - HdmiUtils.verifyAddressType(avrAddress, HdmiDeviceInfo.DEVICE_AUDIO_SYSTEM); + if (!HdmiUtils.verifyAddressType(getSourceAddress(), HdmiDeviceInfo.DEVICE_TV) || + !HdmiUtils.verifyAddressType(avrAddress, HdmiDeviceInfo.DEVICE_AUDIO_SYSTEM)) { + Slog.w(TAG, "Device type mismatch, stop the action."); + finish(); + } mAvrAddress = avrAddress; } diff --git a/services/core/java/com/android/server/hdmi/SetArcTransmissionStateAction.java b/services/core/java/com/android/server/hdmi/SetArcTransmissionStateAction.java index 32e274ece9ab..5ab22e1dcd61 100644 --- a/services/core/java/com/android/server/hdmi/SetArcTransmissionStateAction.java +++ b/services/core/java/com/android/server/hdmi/SetArcTransmissionStateAction.java @@ -47,8 +47,11 @@ final class SetArcTransmissionStateAction extends HdmiCecFeatureAction { SetArcTransmissionStateAction(HdmiCecLocalDevice source, int avrAddress, boolean enabled) { super(source); - HdmiUtils.verifyAddressType(getSourceAddress(), HdmiDeviceInfo.DEVICE_TV); - HdmiUtils.verifyAddressType(avrAddress, HdmiDeviceInfo.DEVICE_AUDIO_SYSTEM); + if (!HdmiUtils.verifyAddressType(getSourceAddress(), HdmiDeviceInfo.DEVICE_TV) || + !HdmiUtils.verifyAddressType(avrAddress, HdmiDeviceInfo.DEVICE_AUDIO_SYSTEM)) { + Slog.w(TAG, "Device type mismatch, stop the action."); + finish(); + } mAvrAddress = avrAddress; mEnabled = enabled; } diff --git a/services/core/java/com/android/server/hdmi/SystemAudioAction.java b/services/core/java/com/android/server/hdmi/SystemAudioAction.java index e96963b9ae3f..f14cda1e6509 100644 --- a/services/core/java/com/android/server/hdmi/SystemAudioAction.java +++ b/services/core/java/com/android/server/hdmi/SystemAudioAction.java @@ -20,6 +20,7 @@ import android.hardware.hdmi.HdmiControlManager; import android.hardware.hdmi.HdmiDeviceInfo; import android.hardware.hdmi.IHdmiControlCallback; import android.hardware.tv.cec.V1_0.SendMessageResult; +import android.util.Slog; import java.util.List; @@ -56,12 +57,14 @@ abstract class SystemAudioAction extends HdmiCecFeatureAction { * @param avrAddress logical address of AVR device * @param targetStatus Whether to enable the system audio mode or not * @param callback callback interface to be notified when it's done - * @throws IllegalArgumentException if device type of sourceAddress and avrAddress is invalid */ SystemAudioAction(HdmiCecLocalDevice source, int avrAddress, boolean targetStatus, IHdmiControlCallback callback) { super(source, callback); - HdmiUtils.verifyAddressType(avrAddress, HdmiDeviceInfo.DEVICE_AUDIO_SYSTEM); + if (!HdmiUtils.verifyAddressType(avrAddress, HdmiDeviceInfo.DEVICE_AUDIO_SYSTEM)) { + Slog.w(TAG, "Device type mismatch, stop the action."); + finish(); + } mAvrLogicalAddress = avrAddress; mTargetAudioStatus = targetStatus; } diff --git a/services/core/java/com/android/server/hdmi/SystemAudioActionFromAvr.java b/services/core/java/com/android/server/hdmi/SystemAudioActionFromAvr.java index 99148c4ea114..08a938731dae 100644 --- a/services/core/java/com/android/server/hdmi/SystemAudioActionFromAvr.java +++ b/services/core/java/com/android/server/hdmi/SystemAudioActionFromAvr.java @@ -19,12 +19,14 @@ package com.android.server.hdmi; import android.hardware.hdmi.HdmiControlManager; import android.hardware.hdmi.HdmiDeviceInfo; import android.hardware.hdmi.IHdmiControlCallback; +import android.util.Slog; /** * Feature action that handles System Audio initiated by AVR devices. */ // Seq #33 final class SystemAudioActionFromAvr extends SystemAudioAction { + private static final String TAG = "SystemAudioActionFromAvr"; /** * Constructor * @@ -32,12 +34,14 @@ final class SystemAudioActionFromAvr extends SystemAudioAction { * @param avrAddress logical address of AVR device * @param targetStatus Whether to enable the system audio mode or not * @param callback callback interface to be notified when it's done - * @throws IllegalArgumentException if device type of tvAddress and avrAddress is invalid */ SystemAudioActionFromAvr(HdmiCecLocalDevice source, int avrAddress, boolean targetStatus, IHdmiControlCallback callback) { super(source, avrAddress, targetStatus, callback); - HdmiUtils.verifyAddressType(getSourceAddress(), HdmiDeviceInfo.DEVICE_TV); + if (!HdmiUtils.verifyAddressType(getSourceAddress(), HdmiDeviceInfo.DEVICE_TV)) { + Slog.w(TAG, "Device type mismatch, stop the action."); + finish(); + } } @Override diff --git a/services/core/java/com/android/server/hdmi/SystemAudioActionFromTv.java b/services/core/java/com/android/server/hdmi/SystemAudioActionFromTv.java index 5c0c272f59e0..675aa3171fbd 100644 --- a/services/core/java/com/android/server/hdmi/SystemAudioActionFromTv.java +++ b/services/core/java/com/android/server/hdmi/SystemAudioActionFromTv.java @@ -18,13 +18,14 @@ package com.android.server.hdmi; import android.hardware.hdmi.HdmiDeviceInfo; import android.hardware.hdmi.IHdmiControlCallback; +import android.util.Slog; /** * Feature action that handles System Audio initiated by TV devices. */ final class SystemAudioActionFromTv extends SystemAudioAction { - + private static final String TAG = "SystemAudioActionFromTv"; /** * Constructor * @@ -32,12 +33,14 @@ final class SystemAudioActionFromTv extends SystemAudioAction { * @param avrAddress logical address of AVR device * @param targetStatus Whether to enable the system audio mode or not * @param callback callback interface to be notified when it's done - * @throws IllegalArgumentException if device type of tvAddress is invalid */ SystemAudioActionFromTv(HdmiCecLocalDevice sourceAddress, int avrAddress, boolean targetStatus, IHdmiControlCallback callback) { super(sourceAddress, avrAddress, targetStatus, callback); - HdmiUtils.verifyAddressType(getSourceAddress(), HdmiDeviceInfo.DEVICE_TV); + if (!HdmiUtils.verifyAddressType(getSourceAddress(), HdmiDeviceInfo.DEVICE_TV)) { + Slog.w(TAG, "Device type mismatch, stop the action."); + finish(); + } } @Override diff --git a/services/core/java/com/android/server/media/MediaRoute2Provider.java b/services/core/java/com/android/server/media/MediaRoute2Provider.java index 1bc2a5eb1351..363684f618cc 100644 --- a/services/core/java/com/android/server/media/MediaRoute2Provider.java +++ b/services/core/java/com/android/server/media/MediaRoute2Provider.java @@ -21,6 +21,7 @@ import android.annotation.Nullable; import android.content.ComponentName; import android.media.MediaRoute2Info; import android.media.MediaRoute2ProviderInfo; +import android.media.MediaRouter2; import android.media.RouteDiscoveryPreference; import android.media.RoutingSessionInfo; import android.os.Bundle; @@ -172,4 +173,59 @@ abstract class MediaRoute2Provider { @NonNull RoutingSessionInfo sessionInfo); void onRequestFailed(@NonNull MediaRoute2Provider provider, long requestId, int reason); } + + /** + * Holds session creation or transfer initiation information for a transfer in flight. + * + * <p>The initiator app is typically also the {@link RoutingSessionInfo#getClientPackageName() + * client app}, with the exception of the {@link MediaRouter2#getSystemController() system + * routing session} which is exceptional in that it's shared among all apps. + * + * <p>For the system routing session, the initiator app is the one that programmatically + * triggered the transfer (for example, via {@link MediaRouter2#transferTo}), or the target app + * of the proxy router that did the transfer. + * + * @see MediaRouter2.RoutingController#wasTransferInitiatedBySelf() + * @see RoutingSessionInfo#getTransferInitiatorPackageName() + * @see RoutingSessionInfo#getTransferInitiatorUserHandle() + */ + protected static class SessionCreationOrTransferRequest { + + /** + * The id of the request, or {@link + * android.media.MediaRoute2ProviderService#REQUEST_ID_NONE} if unknown. + */ + public final long mRequestId; + + /** The {@link MediaRoute2Info#getId() id} of the target route. */ + @NonNull public final String mTargetRouteId; + + @RoutingSessionInfo.TransferReason public final int mTransferReason; + + /** The {@link android.os.UserHandle} on which the initiator app is running. */ + @NonNull public final UserHandle mTransferInitiatorUserHandle; + + @NonNull public final String mTransferInitiatorPackageName; + + SessionCreationOrTransferRequest( + long requestId, + @NonNull String routeId, + @RoutingSessionInfo.TransferReason int transferReason, + @NonNull UserHandle transferInitiatorUserHandle, + @NonNull String transferInitiatorPackageName) { + mRequestId = requestId; + mTargetRouteId = routeId; + mTransferReason = transferReason; + mTransferInitiatorUserHandle = transferInitiatorUserHandle; + mTransferInitiatorPackageName = transferInitiatorPackageName; + } + + public boolean isTargetRoute(@Nullable MediaRoute2Info route2Info) { + return route2Info != null && mTargetRouteId.equals(route2Info.getId()); + } + + public boolean isTargetRouteIdInList(@NonNull List<String> routesList) { + return routesList.stream().anyMatch(mTargetRouteId::equals); + } + } } diff --git a/services/core/java/com/android/server/media/SystemMediaRoute2Provider.java b/services/core/java/com/android/server/media/SystemMediaRoute2Provider.java index 6ce3ab4b2d65..76930a003e46 100644 --- a/services/core/java/com/android/server/media/SystemMediaRoute2Provider.java +++ b/services/core/java/com/android/server/media/SystemMediaRoute2Provider.java @@ -79,12 +79,15 @@ class SystemMediaRoute2Provider extends MediaRoute2Provider { new AudioManagerBroadcastReceiver(); private final Object mRequestLock = new Object(); + @GuardedBy("mRequestLock") - private volatile SessionCreationRequest mPendingSessionCreationRequest; + private volatile SessionCreationOrTransferRequest mPendingSessionCreationOrTransferRequest; private final Object mTransferLock = new Object(); + @GuardedBy("mTransferLock") - @Nullable private volatile SessionCreationRequest mPendingTransferRequest; + @Nullable + private volatile SessionCreationOrTransferRequest mPendingTransferRequest; SystemMediaRoute2Provider(Context context, UserHandle user, Looper looper) { super(COMPONENT_NAME); @@ -180,12 +183,14 @@ class SystemMediaRoute2Provider extends MediaRoute2Provider { synchronized (mRequestLock) { // Handle the previous request as a failure if exists. - if (mPendingSessionCreationRequest != null) { - mCallback.onRequestFailed(this, mPendingSessionCreationRequest.mRequestId, + if (mPendingSessionCreationOrTransferRequest != null) { + mCallback.onRequestFailed( + /* provider= */ this, + mPendingSessionCreationOrTransferRequest.mRequestId, MediaRoute2ProviderService.REASON_UNKNOWN_ERROR); } - mPendingSessionCreationRequest = - new SessionCreationRequest( + mPendingSessionCreationOrTransferRequest = + new SessionCreationOrTransferRequest( requestId, routeId, RoutingSessionInfo.TRANSFER_REASON_FALLBACK, @@ -247,7 +252,7 @@ class SystemMediaRoute2Provider extends MediaRoute2Provider { if (Flags.enableBuiltInSpeakerRouteSuitabilityStatuses()) { synchronized (mTransferLock) { mPendingTransferRequest = - new SessionCreationRequest( + new SessionCreationOrTransferRequest( requestId, routeId, transferReason, @@ -438,7 +443,7 @@ class SystemMediaRoute2Provider extends MediaRoute2Provider { boolean isTransferringToTheSelectedRoute = mPendingTransferRequest.isTargetRoute(selectedRoute); boolean canBePotentiallyTransferred = - mPendingTransferRequest.isInsideOfRoutesList(transferableRoutes); + mPendingTransferRequest.isTargetRouteIdInList(transferableRoutes); if (isTransferringToTheSelectedRoute) { transferReason = mPendingTransferRequest.mTransferReason; @@ -492,20 +497,20 @@ class SystemMediaRoute2Provider extends MediaRoute2Provider { @GuardedBy("mRequestLock") private void reportPendingSessionRequestResultLockedIfNeeded( RoutingSessionInfo newSessionInfo) { - if (mPendingSessionCreationRequest == null) { + if (mPendingSessionCreationOrTransferRequest == null) { // No pending request, nothing to report. return; } - long pendingRequestId = mPendingSessionCreationRequest.mRequestId; - if (TextUtils.equals(mSelectedRouteId, mPendingSessionCreationRequest.mRouteId)) { + long pendingRequestId = mPendingSessionCreationOrTransferRequest.mRequestId; + if (mPendingSessionCreationOrTransferRequest.mTargetRouteId.equals(mSelectedRouteId)) { if (DEBUG) { Slog.w( TAG, "Session creation success to route " - + mPendingSessionCreationRequest.mRouteId); + + mPendingSessionCreationOrTransferRequest.mTargetRouteId); } - mPendingSessionCreationRequest = null; + mPendingSessionCreationOrTransferRequest = null; mCallback.onSessionCreated(this, pendingRequestId, newSessionInfo); } else { boolean isRequestedRouteConnectedBtRoute = isRequestedRouteConnectedBtRoute(); @@ -515,16 +520,16 @@ class SystemMediaRoute2Provider extends MediaRoute2Provider { Slog.w( TAG, "Session creation failed to route " - + mPendingSessionCreationRequest.mRouteId); + + mPendingSessionCreationOrTransferRequest.mTargetRouteId); } - mPendingSessionCreationRequest = null; + mPendingSessionCreationOrTransferRequest = null; mCallback.onRequestFailed( this, pendingRequestId, MediaRoute2ProviderService.REASON_UNKNOWN_ERROR); } else if (DEBUG) { Slog.w( TAG, "Session creation waiting state to route " - + mPendingSessionCreationRequest.mRouteId); + + mPendingSessionCreationOrTransferRequest.mTargetRouteId); } } } @@ -535,7 +540,8 @@ class SystemMediaRoute2Provider extends MediaRoute2Provider { // where two BT routes are active so the transferable routes list is empty. // See b/307723189 for context for (MediaRoute2Info btRoute : mBluetoothRouteController.getAllBluetoothRoutes()) { - if (TextUtils.equals(btRoute.getId(), mPendingSessionCreationRequest.mRouteId)) { + if (TextUtils.equals( + btRoute.getId(), mPendingSessionCreationOrTransferRequest.mTargetRouteId)) { return true; } } @@ -585,51 +591,6 @@ class SystemMediaRoute2Provider extends MediaRoute2Provider { mBluetoothRouteController.getClass().getSimpleName()); } - private static class SessionCreationRequest { - private final long mRequestId; - @NonNull private final String mRouteId; - - @RoutingSessionInfo.TransferReason private final int mTransferReason; - - @NonNull private final UserHandle mTransferInitiatorUserHandle; - @NonNull private final String mTransferInitiatorPackageName; - - SessionCreationRequest( - long requestId, - @NonNull String routeId, - @RoutingSessionInfo.TransferReason int transferReason, - @NonNull UserHandle transferInitiatorUserHandle, - @NonNull String transferInitiatorPackageName) { - mRequestId = requestId; - mRouteId = routeId; - mTransferReason = transferReason; - mTransferInitiatorUserHandle = transferInitiatorUserHandle; - mTransferInitiatorPackageName = transferInitiatorPackageName; - } - - private boolean isTargetRoute(@Nullable MediaRoute2Info route2Info) { - if (route2Info == null) { - return false; - } - - return isTargetRoute(route2Info.getId()); - } - - private boolean isTargetRoute(@Nullable String routeId) { - return mRouteId.equals(routeId); - } - - private boolean isInsideOfRoutesList(@NonNull List<String> routesList) { - for (String routeId : routesList) { - if (isTargetRoute(routeId)) { - return true; - } - } - - return false; - } - } - void updateVolume() { int devices = mAudioManager.getDevicesForStream(AudioManager.STREAM_MUSIC); int volume = mAudioManager.getStreamVolume(AudioManager.STREAM_MUSIC); diff --git a/services/core/java/com/android/server/pm/PackageInstallerSession.java b/services/core/java/com/android/server/pm/PackageInstallerSession.java index 57f6d2789dc5..a90473865ce5 100644 --- a/services/core/java/com/android/server/pm/PackageInstallerSession.java +++ b/services/core/java/com/android/server/pm/PackageInstallerSession.java @@ -5153,6 +5153,8 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { } // Okay to proceed synchronized (mLock) { + assertCallerIsOwnerOrRoot(); + assertPreparedAndNotSealedLocked("setPreVerifiedDomains"); mPreVerifiedDomains = preVerifiedDomains; } } diff --git a/services/core/java/com/android/server/wm/BackNavigationController.java b/services/core/java/com/android/server/wm/BackNavigationController.java index 108c7fb5c3b5..f91ef1d41a0c 100644 --- a/services/core/java/com/android/server/wm/BackNavigationController.java +++ b/services/core/java/com/android/server/wm/BackNavigationController.java @@ -1099,10 +1099,6 @@ class BackNavigationController { } void finishPresentAnimations() { - if (!mComposed) { - return; - } - if (mCloseAdaptor != null) { mCloseAdaptor.mTarget.cancelAnimation(); mCloseAdaptor = null; @@ -1131,8 +1127,10 @@ class BackNavigationController { } void clearBackAnimateTarget() { - finishPresentAnimations(); - mComposed = false; + if (mComposed) { + mComposed = false; + finishPresentAnimations(); + } mWaitTransition = false; mStartingSurfaceTargetMatch = false; mSwitchType = UNKNOWN; diff --git a/services/tests/mockingservicestests/src/com/android/server/power/batterysaver/BatterySaverStateMachineTest.java b/services/tests/mockingservicestests/src/com/android/server/power/batterysaver/BatterySaverStateMachineTest.java index ad68de84eace..9d8d520fcb53 100644 --- a/services/tests/mockingservicestests/src/com/android/server/power/batterysaver/BatterySaverStateMachineTest.java +++ b/services/tests/mockingservicestests/src/com/android/server/power/batterysaver/BatterySaverStateMachineTest.java @@ -15,6 +15,8 @@ */ package com.android.server.power.batterysaver; +import static android.platform.test.flag.junit.SetFlagsRule.DefaultInitValueType.NULL_DEFAULT; + import static com.android.dx.mockito.inline.extended.ExtendedMockito.doNothing; import static com.android.dx.mockito.inline.extended.ExtendedMockito.inOrder; import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn; @@ -35,12 +37,14 @@ import android.content.ContentResolver; import android.content.Context; import android.content.res.Resources; import android.os.PowerManager; +import android.platform.test.flag.junit.SetFlagsRule; import android.provider.Settings.Global; +import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.filters.SmallTest; -import androidx.test.runner.AndroidJUnit4; import org.junit.Before; +import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.InOrder; @@ -65,6 +69,9 @@ public class BatterySaverStateMachineTest { private DevicePersistedState mPersistedState; + @Rule + public final SetFlagsRule mSetFlagsRule = new SetFlagsRule(NULL_DEFAULT); + private class DevicePersistedState { // Current battery level. public int batteryLevel = 100; @@ -171,6 +178,11 @@ public class BatterySaverStateMachineTest { void triggerDynamicModeNotification() { // Do nothing } + + @Override + void triggerDynamicModeNotificationV2() { + // Do nothing + } } @Before diff --git a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecMessageValidatorTest.java b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecMessageValidatorTest.java index 98e119cf0dad..473d1dc22d7a 100644 --- a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecMessageValidatorTest.java +++ b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecMessageValidatorTest.java @@ -139,12 +139,13 @@ public class HdmiCecMessageValidatorTest { @Test public void isValid_setSystemAudioMode() { - assertMessageValidity("40:72:00").isEqualTo(OK); - assertMessageValidity("4F:72:01:03").isEqualTo(OK); + assertMessageValidity("50:72:00").isEqualTo(OK); + assertMessageValidity("50:72:01").isEqualTo(OK); + assertMessageValidity("5F:72:01:03").isEqualTo(ERROR_PARAMETER_LONG); - assertMessageValidity("F0:72").isEqualTo(ERROR_SOURCE); - assertMessageValidity("40:72").isEqualTo(ERROR_PARAMETER_SHORT); - assertMessageValidity("40:72:02").isEqualTo(ERROR_PARAMETER); + assertMessageValidity("40:72:00").isEqualTo(ERROR_SOURCE); + assertMessageValidity("50:72").isEqualTo(ERROR_PARAMETER_SHORT); + assertMessageValidity("50:72:02").isEqualTo(ERROR_PARAMETER); } @Test diff --git a/services/tests/servicestests/src/com/android/server/hdmi/HdmiUtilsTest.java b/services/tests/servicestests/src/com/android/server/hdmi/HdmiUtilsTest.java index c89c32a03553..74583dd619c7 100644 --- a/services/tests/servicestests/src/com/android/server/hdmi/HdmiUtilsTest.java +++ b/services/tests/servicestests/src/com/android/server/hdmi/HdmiUtilsTest.java @@ -709,4 +709,18 @@ public class HdmiUtilsTest { assertThat(HdmiUtils.buildMessage("40:32:65:6E:67").getParams()).isEqualTo( new byte[]{0x65, 0x6E, 0x67}); } + + @Test + public void testVerifyAddressType() { + assertTrue(HdmiUtils.verifyAddressType(Constants.ADDR_TV, + HdmiDeviceInfo.DEVICE_TV)); + assertTrue(HdmiUtils.verifyAddressType(Constants.ADDR_AUDIO_SYSTEM, + HdmiDeviceInfo.DEVICE_AUDIO_SYSTEM)); + assertTrue(HdmiUtils.verifyAddressType(Constants.ADDR_PLAYBACK_1, + HdmiDeviceInfo.DEVICE_PLAYBACK)); + assertFalse(HdmiUtils.verifyAddressType(Constants.ADDR_SPECIFIC_USE, + HdmiDeviceInfo.DEVICE_AUDIO_SYSTEM)); + assertFalse(HdmiUtils.verifyAddressType(Constants.ADDR_PLAYBACK_2, + HdmiDeviceInfo.DEVICE_VIDEO_PROCESSOR)); + } } |