diff options
265 files changed, 5615 insertions, 1674 deletions
diff --git a/apct-tests/perftests/packagemanager/AndroidTest.xml b/apct-tests/perftests/packagemanager/AndroidTest.xml index c9d45a6bda74..db938e4373a3 100644 --- a/apct-tests/perftests/packagemanager/AndroidTest.xml +++ b/apct-tests/perftests/packagemanager/AndroidTest.xml @@ -76,11 +76,6 @@ <option name="test-file-name" value="QueriesAll49.apk"/> </target_preparer> - <test class="com.android.tradefed.testtype.AndroidJUnitTest"> - <option name="package" value="com.android.perftests.packagemanager"/> - <option name="hidden-api-checks" value="false"/> - </test> - <metrics_collector class="com.android.tradefed.device.metric.FilePullerLogCollector"> <option name="directory-keys" value="/data/local/PackageManagerPerfTests"/> <option name="collect-on-run-ended-only" value="true"/> diff --git a/core/api/current.txt b/core/api/current.txt index c189a24c84ae..948eb681f7d9 100644 --- a/core/api/current.txt +++ b/core/api/current.txt @@ -282,7 +282,7 @@ package android { field public static final String REQUEST_IGNORE_BATTERY_OPTIMIZATIONS = "android.permission.REQUEST_IGNORE_BATTERY_OPTIMIZATIONS"; field public static final String REQUEST_INSTALL_PACKAGES = "android.permission.REQUEST_INSTALL_PACKAGES"; field public static final String REQUEST_OBSERVE_COMPANION_DEVICE_PRESENCE = "android.permission.REQUEST_OBSERVE_COMPANION_DEVICE_PRESENCE"; - field @FlaggedApi("android.companion.flags.device_presence") public static final String REQUEST_OBSERVE_DEVICE_UUID_PRESENCE = "android.permission.REQUEST_OBSERVE_DEVICE_UUID_PRESENCE"; + field @FlaggedApi("android.companion.device_presence") public static final String REQUEST_OBSERVE_DEVICE_UUID_PRESENCE = "android.permission.REQUEST_OBSERVE_DEVICE_UUID_PRESENCE"; field public static final String REQUEST_PASSWORD_COMPLEXITY = "android.permission.REQUEST_PASSWORD_COMPLEXITY"; field @Deprecated public static final String RESTART_PACKAGES = "android.permission.RESTART_PACKAGES"; field public static final String RUN_USER_INITIATED_JOBS = "android.permission.RUN_USER_INITIATED_JOBS"; diff --git a/core/api/system-current.txt b/core/api/system-current.txt index b767c52ea9ba..138a92447d29 100644 --- a/core/api/system-current.txt +++ b/core/api/system-current.txt @@ -1144,6 +1144,7 @@ package android.app { public static final class StatusBarManager.DisableInfo implements android.os.Parcelable { method public boolean areAllComponentsEnabled(); method public int describeContents(); + method public boolean isBackDisabled(); method public boolean isNavigateToHomeDisabled(); method public boolean isNotificationPeekingDisabled(); method public boolean isRecentsDisabled(); diff --git a/core/java/android/app/StatusBarManager.java b/core/java/android/app/StatusBarManager.java index 301fef877d6c..e6e46ddaa420 100644 --- a/core/java/android/app/StatusBarManager.java +++ b/core/java/android/app/StatusBarManager.java @@ -1448,6 +1448,7 @@ public class StatusBarManager { * * @hide */ + @SystemApi public boolean isBackDisabled() { return mBack; } @@ -1861,38 +1862,38 @@ public class StatusBarManager { }; @DataClass.Generated( - time = 1708625947132L, + time = 1707345957771L, codegenVersion = "1.0.23", sourceFile = "frameworks/base/core/java/android/app/StatusBarManager.java", - inputSignatures = "private boolean mStatusBarExpansion\nprivate boolean " - + "mNavigateHome\nprivate boolean mNotificationPeeking\nprivate " - + "boolean mRecents\nprivate boolean mBack\nprivate boolean mSearch\n" - + "private boolean mSystemIcons\nprivate boolean mClock\nprivate " - + "boolean mNotificationIcons\nprivate boolean mRotationSuggestion\n" + inputSignatures = "private boolean mStatusBarExpansion\nprivate " + + "boolean mNavigateHome\nprivate boolean mNotificationPeeking\nprivate " + + "boolean mRecents\nprivate boolean mBack\nprivate boolean " + + "mSearch\nprivate boolean mSystemIcons\nprivate boolean mClock\nprivate" + + " boolean mNotificationIcons\nprivate boolean mRotationSuggestion\n" + "private boolean mNotificationTicker\npublic " + "@android.annotation.SystemApi boolean isStatusBarExpansionDisabled()\n" + "public void setStatusBarExpansionDisabled(boolean)\npublic " - + "@android.annotation.SystemApi boolean isNavigateToHomeDisabled()\npublic" - + " void setNavigationHomeDisabled(boolean)\npublic " - + "@android.annotation.SystemApi boolean isNotificationPeekingDisabled()" - + "\npublic void setNotificationPeekingDisabled(boolean)\npublic " + + "@android.annotation.SystemApi boolean isNavigateToHomeDisabled()\n" + + "public void setNavigationHomeDisabled(boolean)\npublic " + + "@android.annotation.SystemApi boolean isNotificationPeekingDisabled()\n" + + "public void setNotificationPeekingDisabled(boolean)\npublic " + "@android.annotation.SystemApi boolean isRecentsDisabled()\npublic " - + "void setRecentsDisabled(boolean)\npublic boolean isBackDisabled()" - + "\npublic void setBackDisabled(boolean)\npublic " + + "void setRecentsDisabled(boolean)\npublic @android.annotation.SystemApi " + + "boolean isBackDisabled()\npublic void setBackDisabled(boolean)\npublic " + "@android.annotation.SystemApi boolean isSearchDisabled()\npublic " + "void setSearchDisabled(boolean)\npublic boolean " - + "areSystemIconsDisabled()\npublic void setSystemIconsDisabled(boolean)\n" - + "public boolean isClockDisabled()\npublic " - + "void setClockDisabled(boolean)\npublic boolean " - + "areNotificationIconsDisabled()\npublic void " - + "setNotificationIconsDisabled(boolean)\npublic boolean " + + "areSystemIconsDisabled()\npublic void setSystemIconsDisabled(boolean)" + + "\npublic boolean isClockDisabled()\npublic " + + "void setClockDisabled(boolean)\npublic " + + "boolean areNotificationIconsDisabled()\npublic " + + "void setNotificationIconsDisabled(boolean)\npublic boolean " + "isNotificationTickerDisabled()\npublic void " + "setNotificationTickerDisabled(boolean)\npublic " + "@android.annotation.TestApi boolean isRotationSuggestionDisabled()\n" + "public void setRotationSuggestionDisabled(boolean)\npublic " - + "@android.annotation.SystemApi boolean areAllComponentsEnabled()\npublic" - + " void setEnableAll()\npublic boolean areAllComponentsDisabled()\n" - + "public void setDisableAll()\npublic @android.annotation.NonNull " + + "@android.annotation.SystemApi boolean areAllComponentsEnabled()\n" + + "public void setEnableAll()\npublic boolean areAllComponentsDisabled()" + + "\npublic void setDisableAll()\npublic @android.annotation.NonNull " + "@java.lang.Override java.lang.String toString()\npublic " + "android.util.Pair<java.lang.Integer,java.lang.Integer> toFlags()\n" + "class DisableInfo extends java.lang.Object implements " diff --git a/core/java/android/net/vcn/VcnManager.java b/core/java/android/net/vcn/VcnManager.java index 6246dd77fd6d..91cdf8d8fcae 100644 --- a/core/java/android/net/vcn/VcnManager.java +++ b/core/java/android/net/vcn/VcnManager.java @@ -124,6 +124,22 @@ public class VcnManager { "vcn_network_selection_ipsec_packet_loss_percent_threshold"; /** + * Key for detecting unusually large increases in IPsec packet sequence numbers. + * + * <p>If the sequence number increases by more than this value within a second, it may indicate + * an intentional leap on the server's downlink. To avoid false positives, the packet loss + * detector will suppress loss reporting. + * + * <p>By default, there's no maximum limit enforced, prioritizing detection of lossy networks. + * To reduce false positives, consider setting an appropriate maximum threshold. + * + * @hide + */ + @NonNull + public static final String VCN_NETWORK_SELECTION_MAX_SEQ_NUM_INCREASE_PER_SECOND_KEY = + "vcn_network_selection_max_seq_num_increase_per_second"; + + /** * Key for the list of timeouts in minute to stop penalizing an underlying network candidate * * @hide @@ -180,6 +196,7 @@ public class VcnManager { VCN_NETWORK_SELECTION_WIFI_EXIT_RSSI_THRESHOLD_KEY, VCN_NETWORK_SELECTION_POLL_IPSEC_STATE_INTERVAL_SECONDS_KEY, VCN_NETWORK_SELECTION_IPSEC_PACKET_LOSS_PERCENT_THRESHOLD_KEY, + VCN_NETWORK_SELECTION_MAX_SEQ_NUM_INCREASE_PER_SECOND_KEY, VCN_NETWORK_SELECTION_PENALTY_TIMEOUT_MINUTES_LIST_KEY, VCN_RESTRICTED_TRANSPORTS_INT_ARRAY_KEY, VCN_SAFE_MODE_TIMEOUT_SECONDS_KEY, diff --git a/core/java/android/net/vcn/flags.aconfig b/core/java/android/net/vcn/flags.aconfig index e64823af84cb..6fde39852844 100644 --- a/core/java/android/net/vcn/flags.aconfig +++ b/core/java/android/net/vcn/flags.aconfig @@ -34,4 +34,14 @@ flag{ namespace: "vcn" description: "Re-evaluate IPsec packet loss on LinkProperties or NetworkCapabilities change" bug: "323238888" +} + +flag{ + name: "handle_seq_num_leap" + namespace: "vcn" + description: "Do not report bad network when there is a suspected sequence number leap" + bug: "332598276" + metadata { + purpose: PURPOSE_BUGFIX + } }
\ No newline at end of file diff --git a/core/java/android/view/SurfaceView.java b/core/java/android/view/SurfaceView.java index c95d6ffe6268..a23df799da59 100644 --- a/core/java/android/view/SurfaceView.java +++ b/core/java/android/view/SurfaceView.java @@ -951,7 +951,7 @@ public class SurfaceView extends View implements ViewRootImpl.SurfaceChangedCall private boolean performSurfaceTransaction(ViewRootImpl viewRoot, Translator translator, boolean creating, boolean sizeChanged, boolean hintChanged, boolean relativeZChanged, - Transaction surfaceUpdateTransaction) { + boolean hdrHeadroomChanged, Transaction surfaceUpdateTransaction) { boolean realSizeChanged = false; mSurfaceLock.lock(); @@ -986,7 +986,7 @@ public class SurfaceView extends View implements ViewRootImpl.SurfaceChangedCall updateBackgroundVisibility(surfaceUpdateTransaction); updateBackgroundColor(surfaceUpdateTransaction); - if (mLimitedHdrEnabled) { + if (mLimitedHdrEnabled && hdrHeadroomChanged) { surfaceUpdateTransaction.setDesiredHdrHeadroom( mBlastSurfaceControl, mHdrHeadroom); } @@ -1203,7 +1203,7 @@ public class SurfaceView extends View implements ViewRootImpl.SurfaceChangedCall } final boolean realSizeChanged = performSurfaceTransaction(viewRoot, translator, - creating, sizeChanged, hintChanged, relativeZChanged, + creating, sizeChanged, hintChanged, relativeZChanged, hdrHeadroomChanged, surfaceUpdateTransaction); try { diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java index cacf0d201b06..ac1f646d9f3f 100644 --- a/core/java/android/view/View.java +++ b/core/java/android/view/View.java @@ -2428,6 +2428,26 @@ public class View implements Drawable.Callback, KeyEvent.Callback, */ public static final int FRAME_RATE_CATEGORY_REASON_IDLE = 0x0700_0000; + /** + * This indicates that the frame rate category was chosen because it is currently boosting. + * @hide + */ + public static final int FRAME_RATE_CATEGORY_REASON_BOOST = 0x0800_0000; + + /** + * This indicates that the frame rate category was chosen because it is currently having + * touch boost. + * @hide + */ + public static final int FRAME_RATE_CATEGORY_REASON_TOUCH = 0x0900_0000; + + /** + * This indicates that the frame rate category was chosen because it is currently having + * touch boost. + * @hide + */ + public static final int FRAME_RATE_CATEGORY_REASON_CONFLICTED = 0x0A00_0000; + private static final int FRAME_RATE_CATEGORY_REASON_MASK = 0xFFFF_0000; /** @@ -5742,7 +5762,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, */ private static final float FRAME_RATE_SIZE_PERCENTAGE_THRESHOLD = 0.07f; - private static final float MAX_FRAME_RATE = 140; + static final float MAX_FRAME_RATE = 140; private static final int INFREQUENT_UPDATE_INTERVAL_MILLIS = 100; private static final int INFREQUENT_UPDATE_COUNTS = 2; @@ -33897,36 +33917,41 @@ public class View implements Drawable.Callback, KeyEvent.Callback, int height = mBottom - mTop; if (viewRootImpl != null && (width != 0 && height != 0)) { - if (mAttachInfo.mViewVelocityApi) { - float velocity = mFrameContentVelocity; - int mask = PFLAG4_HAS_MOVED | PFLAG4_HAS_DRAWN; - float frameRate = 0; - - if (velocity < 0f - && (mPrivateFlags4 & mask) == mask - && mParent instanceof View - && ((View) mParent).mFrameContentVelocity <= 0 - ) { - // This current calculation is very simple. If something on the screen moved, - // then it votes for the highest velocity. If it doesn't move, then return 0. - velocity = Float.POSITIVE_INFINITY; - frameRate = MAX_FRAME_RATE; + if (viewRootImpl.shouldCheckFrameRate(mPreferredFrameRate > 0f)) { + float velocityFrameRate = 0f; + if (mAttachInfo.mViewVelocityApi) { + float velocity = mFrameContentVelocity; + + if (velocity < 0f + && (mPrivateFlags4 & (PFLAG4_HAS_MOVED | PFLAG4_HAS_DRAWN)) == ( + PFLAG4_HAS_MOVED | PFLAG4_HAS_DRAWN) + && mParent instanceof View + && ((View) mParent).mFrameContentVelocity <= 0 + ) { + // This current calculation is very simple. If something on the screen + // moved, then it votes for the highest velocity. + velocityFrameRate = MAX_FRAME_RATE; + } else if (velocity > 0f) { + velocityFrameRate = convertVelocityToFrameRate(velocity); + } } - if (velocity > 0f) { - if (sToolkitFrameRateVelocityMappingReadOnlyFlagValue) { - frameRate = convertVelocityToFrameRate(velocity); + if (velocityFrameRate > 0f || mPreferredFrameRate > 0f) { + int compatibility = FRAME_RATE_COMPATIBILITY_GTE; + float frameRate = velocityFrameRate; + if (mPreferredFrameRate > velocityFrameRate) { + compatibility = FRAME_RATE_COMPATIBILITY_FIXED_SOURCE; + frameRate = mPreferredFrameRate; } - viewRootImpl.votePreferredFrameRate(frameRate, FRAME_RATE_COMPATIBILITY_GTE); - return; + viewRootImpl.votePreferredFrameRate(frameRate, compatibility); } } - if (!willNotDraw() && isDirty()) { + if (!willNotDraw() && isDirty() && viewRootImpl.shouldCheckFrameRateCategory()) { if (sToolkitMetricsForFrameRateDecisionFlagValue) { float sizePercentage = width * height / mAttachInfo.mDisplayPixelCount; viewRootImpl.recordViewPercentage(sizePercentage); } - int frameRateCategory; + int frameRateCategory = FRAME_RATE_CATEGORY_NO_PREFERENCE; if (Float.isNaN(mPreferredFrameRate)) { frameRateCategory = calculateFrameRateCategory(); } else if (mPreferredFrameRate < 0) { @@ -33951,10 +33976,6 @@ public class View implements Drawable.Callback, KeyEvent.Callback, | FRAME_RATE_CATEGORY_REASON_INVALID; } } - } else { - viewRootImpl.votePreferredFrameRate(mPreferredFrameRate, - mFrameRateCompatibility); - return; } int category = frameRateCategory & ~FRAME_RATE_CATEGORY_REASON_MASK; diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java index 4024e3cd34a2..87dfa0290d34 100644 --- a/core/java/android/view/ViewRootImpl.java +++ b/core/java/android/view/ViewRootImpl.java @@ -35,14 +35,18 @@ import static android.view.Surface.FRAME_RATE_CATEGORY_NORMAL; import static android.view.Surface.FRAME_RATE_CATEGORY_NO_PREFERENCE; import static android.view.Surface.FRAME_RATE_COMPATIBILITY_FIXED_SOURCE; import static android.view.Surface.FRAME_RATE_COMPATIBILITY_GTE; +import static android.view.View.FRAME_RATE_CATEGORY_REASON_BOOST; +import static android.view.View.FRAME_RATE_CATEGORY_REASON_CONFLICTED; import static android.view.View.FRAME_RATE_CATEGORY_REASON_IDLE; import static android.view.View.FRAME_RATE_CATEGORY_REASON_INTERMITTENT; import static android.view.View.FRAME_RATE_CATEGORY_REASON_INVALID; import static android.view.View.FRAME_RATE_CATEGORY_REASON_LARGE; import static android.view.View.FRAME_RATE_CATEGORY_REASON_REQUESTED; import static android.view.View.FRAME_RATE_CATEGORY_REASON_SMALL; +import static android.view.View.FRAME_RATE_CATEGORY_REASON_TOUCH; import static android.view.View.FRAME_RATE_CATEGORY_REASON_UNKNOWN; import static android.view.View.FRAME_RATE_CATEGORY_REASON_VELOCITY; +import static android.view.View.MAX_FRAME_RATE; import static android.view.View.PFLAG_DRAW_ANIMATION; import static android.view.View.SYSTEM_UI_FLAG_FULLSCREEN; import static android.view.View.SYSTEM_UI_FLAG_HIDE_NAVIGATION; @@ -4191,8 +4195,15 @@ public final class ViewRootImpl implements ViewParent, // For the variable refresh rate project. // We set the preferred frame rate and frame rate category at the end of performTraversals // when the values are applicable. + setCategoryFromCategoryCounts(); setPreferredFrameRate(mPreferredFrameRate); setPreferredFrameRateCategory(mPreferredFrameRateCategory); + if (!mIsFrameRateConflicted) { + mHandler.removeMessages(MSG_FRAME_RATE_SETTING); + mHandler.sendEmptyMessageDelayed(MSG_FRAME_RATE_SETTING, + FRAME_RATE_SETTING_REEVALUATE_TIME); + } + checkIdleness(); mFrameRateCategoryHighCount = mFrameRateCategoryHighCount > 0 ? mFrameRateCategoryHighCount - 1 : mFrameRateCategoryHighCount; mFrameRateCategoryNormalCount = mFrameRateCategoryNormalCount > 0 @@ -4201,7 +4212,6 @@ public final class ViewRootImpl implements ViewParent, ? mFrameRateCategoryLowCount - 1 : mFrameRateCategoryLowCount; mPreferredFrameRateCategory = FRAME_RATE_CATEGORY_DEFAULT; mPreferredFrameRate = -1; - mFrameRateCompatibility = FRAME_RATE_COMPATIBILITY_FIXED_SOURCE; mIsFrameRateConflicted = false; mFrameRateCategoryChangeReason = FRAME_RATE_CATEGORY_REASON_UNKNOWN; } @@ -6630,8 +6640,6 @@ public final class ViewRootImpl implements ViewParent, */ mIsFrameRateBoosting = false; mIsTouchBoosting = false; - setPreferredFrameRateCategory(Math.max(mPreferredFrameRateCategory, - mLastPreferredFrameRateCategory)); break; case MSG_CHECK_INVALIDATION_IDLE: if (!mHasInvalidation && !mIsFrameRateBoosting && !mIsTouchBoosting) { @@ -7677,7 +7685,6 @@ public final class ViewRootImpl implements ViewParent, mWindowAttributes.type)) { // set the frame rate to the maximum value. mIsTouchBoosting = true; - setPreferredFrameRateCategory(mPreferredFrameRateCategory); } /** * We want to lower the refresh rate when MotionEvent.ACTION_UP, @@ -12469,59 +12476,50 @@ public final class ViewRootImpl implements ViewParent, EventLog.writeEvent(LOGTAG_VIEWROOT_DRAW_EVENT, mTag, msg); } + /** + * Sets the mPreferredFrameRateCategory from the high, high_hint, normal, and low counts. + */ + private void setCategoryFromCategoryCounts() { + if (mFrameRateCategoryHighCount > 0) { + mPreferredFrameRateCategory = FRAME_RATE_CATEGORY_HIGH; + } else if (mFrameRateCategoryHighHintCount > 0) { + mPreferredFrameRateCategory = FRAME_RATE_CATEGORY_HIGH_HINT; + } else if (mFrameRateCategoryNormalCount > 0) { + mPreferredFrameRateCategory = FRAME_RATE_CATEGORY_NORMAL; + } else if (mFrameRateCategoryLowCount > 0) { + mPreferredFrameRateCategory = FRAME_RATE_CATEGORY_LOW; + } + } + private void setPreferredFrameRateCategory(int preferredFrameRateCategory) { - if (!shouldSetFrameRateCategory() - || (mFrameRateCompatibility == FRAME_RATE_COMPATIBILITY_GTE - && mPreferredFrameRate > 0 - && sToolkitFrameRateVelocityMappingReadOnlyFlagValue)) { + if (!shouldSetFrameRateCategory()) { return; } - int categoryFromConflictedFrameRates = FRAME_RATE_CATEGORY_DEFAULT; - if (mIsFrameRateConflicted) { - categoryFromConflictedFrameRates = mPreferredFrameRate > 60 - ? FRAME_RATE_CATEGORY_HIGH : FRAME_RATE_CATEGORY_NORMAL; - } - int frameRateCategory = mIsTouchBoosting - ? FRAME_RATE_CATEGORY_HIGH_HINT - : Math.max(preferredFrameRateCategory, categoryFromConflictedFrameRates); + int frameRateCategory; + int frameRateReason; + String view; - // FRAME_RATE_CATEGORY_HIGH has a higher precedence than FRAME_RATE_CATEGORY_HIGH_HINT - // For now, FRAME_RATE_CATEGORY_HIGH_HINT is used for boosting with user interaction. - // FRAME_RATE_CATEGORY_HIGH is for boosting without user interaction - // (e.g., Window Initialization). - if (mIsFrameRateBoosting || mInsetsAnimationRunning - || (mFrameRateCompatibility == FRAME_RATE_COMPATIBILITY_GTE - && mPreferredFrameRate > 0)) { + if (mIsFrameRateBoosting || mInsetsAnimationRunning) { frameRateCategory = FRAME_RATE_CATEGORY_HIGH; - if (mFrameRateCompatibility == FRAME_RATE_COMPATIBILITY_GTE) { - // We've received a velocity, so we'll let the velocity control the - // frame rate unless we receive additional motion events. - mIsTouchBoosting = false; - mFrameRateCategoryChangeReason = FRAME_RATE_CATEGORY_REASON_VELOCITY; - mFrameRateCategoryView = null; - } else { - mFrameRateCategoryChangeReason = FRAME_RATE_CATEGORY_REASON_UNKNOWN; - } + frameRateReason = FRAME_RATE_CATEGORY_REASON_BOOST; + view = null; + } else if (mIsTouchBoosting && preferredFrameRateCategory < FRAME_RATE_CATEGORY_HIGH_HINT) { + frameRateCategory = FRAME_RATE_CATEGORY_HIGH_HINT; + frameRateReason = FRAME_RATE_CATEGORY_REASON_TOUCH; + view = null; + } else { + frameRateCategory = preferredFrameRateCategory; + frameRateReason = mFrameRateCategoryChangeReason; + view = mFrameRateCategoryView; } try { if (frameRateCategory != FRAME_RATE_CATEGORY_DEFAULT && mLastPreferredFrameRateCategory != frameRateCategory) { if (Trace.isTagEnabled(Trace.TRACE_TAG_VIEW)) { - String reason = reasonToString(mFrameRateCategoryChangeReason); - String sourceView = mFrameRateCategoryView == null ? "-" - : mFrameRateCategoryView; - if (preferredFrameRateCategory == FRAME_RATE_CATEGORY_HIGH_HINT) { - reason = "touch boost"; - sourceView = "-"; - } else if (categoryFromConflictedFrameRates == frameRateCategory - && frameRateCategory != preferredFrameRateCategory - && mIsFrameRateConflicted - ) { - reason = "conflict"; - sourceView = "-"; - } + String reason = reasonToString(frameRateReason); + String sourceView = view == null ? "-" : view; String category = categoryToString(frameRateCategory); Trace.traceBegin( Trace.TRACE_TAG_VIEW, "ViewRootImpl#setFrameRateCategory " @@ -12565,24 +12563,21 @@ public final class ViewRootImpl implements ViewParent, case FRAME_RATE_CATEGORY_REASON_VELOCITY -> str = "velocity"; case FRAME_RATE_CATEGORY_REASON_IDLE -> str = "idle"; case FRAME_RATE_CATEGORY_REASON_UNKNOWN -> str = "unknown"; + case FRAME_RATE_CATEGORY_REASON_BOOST -> str = "boost"; + case FRAME_RATE_CATEGORY_REASON_TOUCH -> str = "touch"; + case FRAME_RATE_CATEGORY_REASON_CONFLICTED -> str = "conflicted"; default -> str = String.valueOf(reason); } return str; } private void setPreferredFrameRate(float preferredFrameRate) { - if (!shouldSetFrameRate()) { - return; - } - if (mFrameRateCompatibility == FRAME_RATE_COMPATIBILITY_GTE - && preferredFrameRate > 0 && !sToolkitFrameRateVelocityMappingReadOnlyFlagValue) { - mIsTouchBoosting = false; + if (!shouldSetFrameRate() || preferredFrameRate < 0) { return; } try { - if (mLastPreferredFrameRate != preferredFrameRate - && preferredFrameRate >= 0) { + if (mLastPreferredFrameRate != preferredFrameRate) { if (Trace.isTagEnabled(Trace.TRACE_TAG_VIEW)) { Trace.traceBegin( Trace.TRACE_TAG_VIEW, "ViewRootImpl#setFrameRate " @@ -12591,7 +12586,7 @@ public final class ViewRootImpl implements ViewParent, } if (sToolkitFrameRateFunctionEnablingReadOnlyFlagValue) { mFrameRateTransaction.setFrameRate(mSurfaceControl, preferredFrameRate, - mFrameRateCompatibility).applyAsyncUnsafe(); + mFrameRateCompatibility).applyAsyncUnsafe(); } mLastPreferredFrameRate = preferredFrameRate; } @@ -12602,12 +12597,6 @@ public final class ViewRootImpl implements ViewParent, } } - private void sendDelayedEmptyMessage(int message, int delayedTime) { - mHandler.removeMessages(message); - - mHandler.sendEmptyMessageDelayed(message, delayedTime); - } - private boolean shouldSetFrameRateCategory() { // use toolkitSetFrameRate flag to gate the change return mSurface.isValid() && shouldEnableDvrr(); @@ -12645,28 +12634,34 @@ public final class ViewRootImpl implements ViewParent, case FRAME_RATE_CATEGORY_HIGH -> mFrameRateCategoryHighCount = FRAME_RATE_CATEGORY_COUNT; } - - int oldCategory = mPreferredFrameRateCategory; - // For View that votes NO_PREFERENCE - mPreferredFrameRateCategory = frameRateCategory; - - if (mFrameRateCategoryHighCount > 0) { - mPreferredFrameRateCategory = FRAME_RATE_CATEGORY_HIGH; - } else if (mFrameRateCategoryHighHintCount > 0) { - mPreferredFrameRateCategory = FRAME_RATE_CATEGORY_HIGH_HINT; - } else if (mFrameRateCategoryNormalCount > 0) { - mPreferredFrameRateCategory = FRAME_RATE_CATEGORY_NORMAL; - } else if (mFrameRateCategoryLowCount > 0) { - mPreferredFrameRateCategory = FRAME_RATE_CATEGORY_LOW; - } - mHasInvalidation = true; - checkIdleness(); - if (mPreferredFrameRateCategory != oldCategory - && mPreferredFrameRateCategory == frameRateCategory - ) { + if (frameRateCategory > mPreferredFrameRateCategory) { + mPreferredFrameRateCategory = frameRateCategory; mFrameRateCategoryChangeReason = reason; - mFrameRateCategoryView = view == null ? "null" : view.getClass().getSimpleName(); + mFrameRateCategoryView = view == null ? "-" : view.getClass().getSimpleName(); } + mHasInvalidation = true; + } + + /** + * Returns whether a View should vote for frame rate category. When the category is HIGH + * already, there's no need to calculate the category on the View and vote. + */ + public boolean shouldCheckFrameRateCategory() { + return mPreferredFrameRateCategory < FRAME_RATE_CATEGORY_HIGH; + } + + /** + * Returns whether a View should vote for frame rate. When the maximum frame rate has already + * been voted for, there's no point in calculating and voting for the frame rate. When + * isDirect is false, then it will return false when the velocity-calculated frame rate + * can be avoided. + * @param isDirect true when the frame rate has been set directly on the View or false if + * the calculation is based only on velocity. + */ + public boolean shouldCheckFrameRate(boolean isDirect) { + return mPreferredFrameRate < MAX_FRAME_RATE + || (!isDirect && !sToolkitFrameRateVelocityMappingReadOnlyFlagValue + && mPreferredFrameRateCategory < FRAME_RATE_CATEGORY_HIGH); } /** @@ -12692,24 +12687,44 @@ public final class ViewRootImpl implements ViewParent, if (frameRate <= 0) { return; } + if (frameRateCompatibility == FRAME_RATE_COMPATIBILITY_GTE) { + mIsTouchBoosting = false; + if (!sToolkitFrameRateVelocityMappingReadOnlyFlagValue) { + mPreferredFrameRateCategory = FRAME_RATE_CATEGORY_HIGH; + mFrameRateCategoryHighCount = FRAME_RATE_CATEGORY_COUNT; + mFrameRateCategoryChangeReason = FRAME_RATE_CATEGORY_REASON_VELOCITY; + mFrameRateCategoryView = null; + return; + } + } + float nextFrameRate; + int nextFrameRateCompatibility; + if (frameRate > mPreferredFrameRate) { + nextFrameRate = frameRate; + nextFrameRateCompatibility = frameRateCompatibility; + } else { + nextFrameRate = mPreferredFrameRate; + nextFrameRateCompatibility = mFrameRateCompatibility; + } + if (mPreferredFrameRate > 0 && mPreferredFrameRate % frameRate != 0 && frameRate % mPreferredFrameRate != 0) { mIsFrameRateConflicted = true; - mFrameRateCompatibility = FRAME_RATE_COMPATIBILITY_FIXED_SOURCE; - } - if (frameRate > mPreferredFrameRate) { - mFrameRateCompatibility = frameRateCompatibility; + if (nextFrameRate > 60 && mFrameRateCategoryHighCount != FRAME_RATE_CATEGORY_COUNT) { + mFrameRateCategoryHighCount = FRAME_RATE_CATEGORY_COUNT; + mFrameRateCategoryChangeReason = FRAME_RATE_CATEGORY_REASON_CONFLICTED; + mFrameRateCategoryView = null; + } else if (mFrameRateCategoryHighCount == 0 && mFrameRateCategoryHighHintCount == 0 + && mFrameRateCategoryNormalCount < FRAME_RATE_CATEGORY_COUNT) { + mFrameRateCategoryNormalCount = FRAME_RATE_CATEGORY_COUNT; + mFrameRateCategoryChangeReason = FRAME_RATE_CATEGORY_REASON_CONFLICTED; + mFrameRateCategoryView = null; + } } - mPreferredFrameRate = Math.max(mPreferredFrameRate, frameRate); + mPreferredFrameRate = nextFrameRate; + mFrameRateCompatibility = nextFrameRateCompatibility; mHasInvalidation = true; - - if (!mIsFrameRateConflicted) { - mHandler.removeMessages(MSG_FRAME_RATE_SETTING); - mHandler.sendEmptyMessageDelayed(MSG_FRAME_RATE_SETTING, - FRAME_RATE_SETTING_REEVALUATE_TIME); - } - checkIdleness(); } /** @@ -12779,7 +12794,6 @@ public final class ViewRootImpl implements ViewParent, private void boostFrameRate(int boostTimeOut) { mIsFrameRateBoosting = true; - setPreferredFrameRateCategory(mPreferredFrameRateCategory); mHandler.removeMessages(MSG_TOUCH_BOOST_TIMEOUT); mHandler.sendEmptyMessageDelayed(MSG_TOUCH_BOOST_TIMEOUT, boostTimeOut); diff --git a/core/jni/com_android_internal_content_FileSystemUtils.cpp b/core/jni/com_android_internal_content_FileSystemUtils.cpp index 4bd2d72a1eb4..01920de88496 100644 --- a/core/jni/com_android_internal_content_FileSystemUtils.cpp +++ b/core/jni/com_android_internal_content_FileSystemUtils.cpp @@ -42,7 +42,11 @@ namespace android { bool punchHoles(const char *filePath, const uint64_t offset, const std::vector<Elf64_Phdr> &programHeaders) { struct stat64 beforePunch; - lstat64(filePath, &beforePunch); + if (int result = lstat64(filePath, &beforePunch); result != 0) { + ALOGE("lstat64 failed for filePath %s, error:%d", filePath, errno); + return false; + } + uint64_t blockSize = beforePunch.st_blksize; IF_ALOGD() { ALOGD("Total number of LOAD segments %zu", programHeaders.size()); @@ -152,7 +156,10 @@ bool punchHoles(const char *filePath, const uint64_t offset, IF_ALOGD() { struct stat64 afterPunch; - lstat64(filePath, &afterPunch); + if (int result = lstat64(filePath, &afterPunch); result != 0) { + ALOGD("lstat64 failed for filePath %s, error:%d", filePath, errno); + return false; + } ALOGD("Size after punching holes st_blocks: %" PRIu64 ", st_blksize: %ld, st_size: %" PRIu64 "", afterPunch.st_blocks, afterPunch.st_blksize, @@ -177,7 +184,7 @@ bool punchHolesInElf64(const char *filePath, const uint64_t offset) { // only consider elf64 for punching holes if (ehdr.e_ident[EI_CLASS] != ELFCLASS64) { - ALOGE("Provided file is not ELF64"); + ALOGW("Provided file is not ELF64"); return false; } @@ -215,4 +222,108 @@ bool punchHolesInElf64(const char *filePath, const uint64_t offset) { return punchHoles(filePath, offset, programHeaders); } +bool punchHolesInZip(const char *filePath, uint64_t offset, uint16_t extraFieldLen) { + android::base::unique_fd fd(open(filePath, O_RDWR | O_CLOEXEC)); + if (!fd.ok()) { + ALOGE("Can't open file to punch %s", filePath); + return false; + } + + struct stat64 beforePunch; + if (int result = lstat64(filePath, &beforePunch); result != 0) { + ALOGE("lstat64 failed for filePath %s, error:%d", filePath, errno); + return false; + } + + uint64_t blockSize = beforePunch.st_blksize; + IF_ALOGD() { + ALOGD("Extra field length: %hu, Size before punching holes st_blocks: %" PRIu64 + ", st_blksize: %ld, st_size: %" PRIu64 "", + extraFieldLen, beforePunch.st_blocks, beforePunch.st_blksize, + static_cast<uint64_t>(beforePunch.st_size)); + } + + if (extraFieldLen < blockSize) { + ALOGD("Skipping punching apk as extra field length is less than block size"); + return false; + } + + // content is preceded by extra field. Zip offset is offset of exact content. + // move back by extraFieldLen so that scan can be started at start of extra field. + uint64_t extraFieldStart; + if (__builtin_sub_overflow(offset, extraFieldLen, &extraFieldStart)) { + ALOGE("Overflow occurred when calculating start of extra field"); + return false; + } + + constexpr uint64_t kMaxSize = 64 * 1024; + // Use malloc to gracefully handle any oom conditions + std::unique_ptr<uint8_t, decltype(&free)> buffer(static_cast<uint8_t *>(malloc(kMaxSize)), + &free); + if (buffer == nullptr) { + ALOGE("Failed to allocate read buffer"); + return false; + } + + // Read the entire extra fields at once and punch file according to zero stretches. + if (!ReadFullyAtOffset(fd, buffer.get(), extraFieldLen, extraFieldStart)) { + ALOGE("Failed to read extra field content"); + return false; + } + + IF_ALOGD() { + ALOGD("Extra field length: %hu content near offset: %s", extraFieldLen, + HexString(buffer.get(), extraFieldLen).c_str()); + } + + uint64_t currentSize = 0; + while (currentSize < extraFieldLen) { + uint64_t end = currentSize; + // find zero ranges + while (end < extraFieldLen && *(buffer.get() + end) == 0) { + ++end; + } + + uint64_t punchLen; + if (__builtin_sub_overflow(end, currentSize, &punchLen)) { + ALOGW("Overflow occurred when calculating punching length"); + return false; + } + + // Don't punch for every stretch of zero which is found + if (punchLen > blockSize) { + uint64_t punchOffset; + if (__builtin_add_overflow(extraFieldStart, currentSize, &punchOffset)) { + ALOGW("Overflow occurred when calculating punch start offset"); + return false; + } + + ALOGD("Punching hole in apk start: %" PRIu64 " len:%" PRIu64 "", punchOffset, punchLen); + + // Punch hole for this entire stretch. + int result = fallocate(fd, FALLOC_FL_PUNCH_HOLE | FALLOC_FL_KEEP_SIZE, punchOffset, + punchLen); + if (result < 0) { + ALOGE("fallocate failed to punch hole inside apk, error:%d", errno); + return false; + } + } + currentSize = end; + ++currentSize; + } + + IF_ALOGD() { + struct stat64 afterPunch; + if (int result = lstat64(filePath, &afterPunch); result != 0) { + ALOGD("lstat64 failed for filePath %s, error:%d", filePath, errno); + return false; + } + ALOGD("punchHolesInApk:: Size after punching holes st_blocks: %" PRIu64 + ", st_blksize: %ld, st_size: %" PRIu64 "", + afterPunch.st_blocks, afterPunch.st_blksize, + static_cast<uint64_t>(afterPunch.st_size)); + } + return true; +} + }; // namespace android diff --git a/core/jni/com_android_internal_content_FileSystemUtils.h b/core/jni/com_android_internal_content_FileSystemUtils.h index a6b145c690d1..52445e2b4229 100644 --- a/core/jni/com_android_internal_content_FileSystemUtils.h +++ b/core/jni/com_android_internal_content_FileSystemUtils.h @@ -28,4 +28,11 @@ namespace android { */ bool punchHolesInElf64(const char* filePath, uint64_t offset); +/* + * This function punches holes in zero segments of Apk file which are introduced during the + * alignment. Alignment tools add padding inside of extra field in local file header. punch holes in + * extra field for zero stretches till the actual file content. + */ +bool punchHolesInZip(const char* filePath, uint64_t offset, uint16_t extraFieldLen); + } // namespace android
\ No newline at end of file diff --git a/core/jni/com_android_internal_content_NativeLibraryHelper.cpp b/core/jni/com_android_internal_content_NativeLibraryHelper.cpp index faa83f8017f7..9b8dab78b342 100644 --- a/core/jni/com_android_internal_content_NativeLibraryHelper.cpp +++ b/core/jni/com_android_internal_content_NativeLibraryHelper.cpp @@ -28,6 +28,7 @@ #include <stdlib.h> #include <string.h> #include <sys/stat.h> +#include <sys/statfs.h> #include <sys/types.h> #include <time.h> #include <unistd.h> @@ -145,8 +146,9 @@ copyFileIfChanged(JNIEnv *env, void* arg, ZipFileRO* zipFile, ZipEntryRO zipEntr uint16_t method; off64_t offset; - - if (!zipFile->getEntryInfo(zipEntry, &method, &uncompLen, nullptr, &offset, &when, &crc)) { + uint16_t extraFieldLength; + if (!zipFile->getEntryInfo(zipEntry, &method, &uncompLen, nullptr, &offset, &when, &crc, + &extraFieldLength)) { ALOGE("Couldn't read zip entry info\n"); return INSTALL_FAILED_INVALID_APK; } @@ -177,6 +179,12 @@ copyFileIfChanged(JNIEnv *env, void* arg, ZipFileRO* zipFile, ZipEntryRO zipEntr "%" PRIu64 "", fileName, zipFile->getZipFileName(), offset); } + + // if extra field for this zip file is present with some length, possibility is that it is + // padding added for zip alignment. Punch holes there too. + if (!punchHolesInZip(zipFile->getZipFileName(), offset, extraFieldLength)) { + ALOGW("Failed to punch apk : %s at extra field", zipFile->getZipFileName()); + } #endif // ENABLE_PUNCH_HOLES return INSTALL_SUCCEEDED; @@ -279,6 +287,25 @@ copyFileIfChanged(JNIEnv *env, void* arg, ZipFileRO* zipFile, ZipEntryRO zipEntr return INSTALL_FAILED_CONTAINER_ERROR; } +#ifdef ENABLE_PUNCH_HOLES + // punch extracted elf files as well. This will fail where compression is on (like f2fs) but it + // will be useful for ext4 based systems + struct statfs64 fsInfo; + int result = statfs64(localFileName, &fsInfo); + if (result < 0) { + ALOGW("Failed to stat file :%s", localFileName); + } + + if (result == 0 && fsInfo.f_type == EXT4_SUPER_MAGIC) { + ALOGD("Punching extracted elf file %s on fs:%" PRIu64 "", fileName, + static_cast<uint64_t>(fsInfo.f_type)); + if (!punchHolesInElf64(localFileName, 0)) { + ALOGW("Failed to punch extracted elf file :%s from apk : %s", fileName, + zipFile->getZipFileName()); + } + } +#endif // ENABLE_PUNCH_HOLES + ALOGV("Successfully moved %s to %s\n", localTmpFileName, localFileName); return INSTALL_SUCCEEDED; diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml index ab714ad7d807..0864210aad32 100644 --- a/core/res/AndroidManifest.xml +++ b/core/res/AndroidManifest.xml @@ -5890,7 +5890,7 @@ <!-- Allows an application to subscribe to notifications about the nearby devices' presence status change base on the UUIDs. <p>Not for use by third-party applications.</p> - @FlaggedApi("android.companion.flags.device_presence") + @FlaggedApi("android.companion.device_presence") --> <permission android:name="android.permission.REQUEST_OBSERVE_DEVICE_UUID_PRESENCE" android:protectionLevel="signature|privileged" /> diff --git a/core/res/res/layout/notification_template_header.xml b/core/res/res/layout/notification_template_header.xml index be1c939f0ff8..6f06d803fe6f 100644 --- a/core/res/res/layout/notification_template_header.xml +++ b/core/res/res/layout/notification_template_header.xml @@ -80,6 +80,7 @@ android:layout_height="match_parent" android:layout_alignParentStart="true" android:importantForAccessibility="no" + android:focusable="false" /> <include layout="@layout/notification_expand_button" diff --git a/core/res/res/layout/notification_template_material_base.xml b/core/res/res/layout/notification_template_material_base.xml index 710a70a32955..64227d86c616 100644 --- a/core/res/res/layout/notification_template_material_base.xml +++ b/core/res/res/layout/notification_template_material_base.xml @@ -55,6 +55,7 @@ android:layout_height="match_parent" android:layout_gravity="start" android:importantForAccessibility="no" + android:focusable="false" /> <LinearLayout diff --git a/core/res/res/layout/notification_template_material_media.xml b/core/res/res/layout/notification_template_material_media.xml index df32d30918c8..8a94c48e8f58 100644 --- a/core/res/res/layout/notification_template_material_media.xml +++ b/core/res/res/layout/notification_template_material_media.xml @@ -54,6 +54,7 @@ android:layout_height="match_parent" android:layout_gravity="start" android:importantForAccessibility="no" + android:focusable="false" /> <LinearLayout diff --git a/core/res/res/layout/notification_template_material_messaging.xml b/core/res/res/layout/notification_template_material_messaging.xml index 3e82bd1814c6..a83d923933f3 100644 --- a/core/res/res/layout/notification_template_material_messaging.xml +++ b/core/res/res/layout/notification_template_material_messaging.xml @@ -67,6 +67,7 @@ android:layout_height="match_parent" android:layout_gravity="start" android:importantForAccessibility="no" + android:focusable="false" /> <!-- diff --git a/core/tests/coretests/src/android/view/ViewFrameRateTest.java b/core/tests/coretests/src/android/view/ViewFrameRateTest.java index 28343f1faeda..0bf9a4cd47f6 100644 --- a/core/tests/coretests/src/android/view/ViewFrameRateTest.java +++ b/core/tests/coretests/src/android/view/ViewFrameRateTest.java @@ -203,7 +203,9 @@ public class ViewFrameRateTest { mActivityRule.runOnUiThread(() -> { mMovingView.setFrameContentVelocity(1_000_000_000f); mMovingView.invalidate(); - runAfterDraw(() -> assertEquals(140f, mViewRoot.getLastPreferredFrameRate(), 0f)); + runAfterDraw(() -> { + assertEquals(140f, mViewRoot.getLastPreferredFrameRate(), 0f); + }); }); waitForAfterDraw(); } @@ -411,6 +413,26 @@ public class ViewFrameRateTest { waitForAfterDraw(); } + @Test + @RequiresFlagsEnabled({FLAG_TOOLKIT_SET_FRAME_RATE_READ_ONLY, + FLAG_TOOLKIT_FRAME_RATE_VIEW_ENABLING_READ_ONLY, + FLAG_TOOLKIT_FRAME_RATE_VELOCITY_MAPPING_READ_ONLY + }) + public void frameRateAndCategory() throws Throwable { + waitForFrameRateCategoryToSettle(); + mActivityRule.runOnUiThread(() -> { + mMovingView.setRequestedFrameRate(View.REQUESTED_FRAME_RATE_CATEGORY_LOW); + mMovingView.setFrameContentVelocity(1f); + mMovingView.invalidate(); + runAfterDraw(() -> { + assertEquals(FRAME_RATE_CATEGORY_LOW, + mViewRoot.getLastPreferredFrameRateCategory()); + assertEquals(60f, mViewRoot.getLastPreferredFrameRate()); + }); + }); + waitForAfterDraw(); + } + private void runAfterDraw(@NonNull Runnable runnable) { Handler handler = new Handler(Looper.getMainLooper()); mAfterDrawLatch = new CountDownLatch(1); diff --git a/core/tests/coretests/src/android/view/ViewRootImplTest.java b/core/tests/coretests/src/android/view/ViewRootImplTest.java index e0282a4d10a2..80fef6ca801e 100644 --- a/core/tests/coretests/src/android/view/ViewRootImplTest.java +++ b/core/tests/coretests/src/android/view/ViewRootImplTest.java @@ -52,6 +52,7 @@ import static com.google.common.truth.Truth.assertThat; import static com.google.common.truth.Truth.assertWithMessage; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertThrows; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; @@ -806,37 +807,50 @@ public class ViewRootImplTest { assertEquals(0, mViewRootImpl.getPreferredFrameRate(), 0.1); assertEquals(mViewRootImpl.getFrameRateCompatibility(), FRAME_RATE_COMPATIBILITY_FIXED_SOURCE); - assertEquals(false, mViewRootImpl.isFrameRateConflicted()); + assertFalse(mViewRootImpl.isFrameRateConflicted()); mViewRootImpl.votePreferredFrameRate(24, FRAME_RATE_COMPATIBILITY_GTE); - assertEquals(24, mViewRootImpl.getPreferredFrameRate(), 0.1); - assertEquals(FRAME_RATE_COMPATIBILITY_GTE, - mViewRootImpl.getFrameRateCompatibility()); - assertEquals(false, mViewRootImpl.isFrameRateConflicted()); + if (toolkitFrameRateVelocityMappingReadOnly()) { + assertEquals(24, mViewRootImpl.getPreferredFrameRate(), 0.1); + assertEquals(FRAME_RATE_COMPATIBILITY_GTE, + mViewRootImpl.getFrameRateCompatibility()); + assertFalse(mViewRootImpl.isFrameRateConflicted()); + } else { + assertEquals(FRAME_RATE_CATEGORY_HIGH, + mViewRootImpl.getPreferredFrameRateCategory()); + } mViewRootImpl.votePreferredFrameRate(30, FRAME_RATE_COMPATIBILITY_FIXED_SOURCE); assertEquals(30, mViewRootImpl.getPreferredFrameRate(), 0.1); // If there is a conflict, then set compatibility to // FRAME_RATE_COMPATIBILITY_FIXED_SOURCE assertEquals(FRAME_RATE_COMPATIBILITY_FIXED_SOURCE, mViewRootImpl.getFrameRateCompatibility()); - // Should be true since there is a conflict between 24 and 30. - assertTrue(mViewRootImpl.isFrameRateConflicted()); + if (toolkitFrameRateVelocityMappingReadOnly()) { + // Should be true since there is a conflict between 24 and 30. + assertTrue(mViewRootImpl.isFrameRateConflicted()); + } + mView.invalidate(); }); sInstrumentation.waitForIdleSync(); sInstrumentation.runOnMainSync(() -> { - assertEquals(false, mViewRootImpl.isFrameRateConflicted()); + assertFalse(mViewRootImpl.isFrameRateConflicted()); mViewRootImpl.votePreferredFrameRate(60, FRAME_RATE_COMPATIBILITY_GTE); - assertEquals(60, mViewRootImpl.getPreferredFrameRate(), 0.1); - assertEquals(FRAME_RATE_COMPATIBILITY_GTE, - mViewRootImpl.getFrameRateCompatibility()); - assertEquals(mViewRootImpl.isFrameRateConflicted(), false); + if (toolkitFrameRateVelocityMappingReadOnly()) { + assertEquals(60, mViewRootImpl.getPreferredFrameRate(), 0.1); + assertEquals(FRAME_RATE_COMPATIBILITY_GTE, + mViewRootImpl.getFrameRateCompatibility()); + } else { + assertEquals(FRAME_RATE_CATEGORY_HIGH, + mViewRootImpl.getPreferredFrameRateCategory()); + } + assertFalse(mViewRootImpl.isFrameRateConflicted()); mViewRootImpl.votePreferredFrameRate(120, FRAME_RATE_COMPATIBILITY_FIXED_SOURCE); assertEquals(120, mViewRootImpl.getPreferredFrameRate(), 0.1); assertEquals(FRAME_RATE_COMPATIBILITY_FIXED_SOURCE, mViewRootImpl.getFrameRateCompatibility()); // Should be false since 60 is a divisor of 120. - assertEquals(false, mViewRootImpl.isFrameRateConflicted()); + assertFalse(mViewRootImpl.isFrameRateConflicted()); mViewRootImpl.votePreferredFrameRate(60, FRAME_RATE_COMPATIBILITY_GTE); assertEquals(120, mViewRootImpl.getPreferredFrameRate(), 0.1); // compatibility should be remained the same (FRAME_RATE_COMPATIBILITY_FIXED_SOURCE) @@ -844,8 +858,7 @@ public class ViewRootImplTest { assertEquals(FRAME_RATE_COMPATIBILITY_FIXED_SOURCE, mViewRootImpl.getFrameRateCompatibility()); // Should be false since 60 is a divisor of 120. - assertEquals(false, mViewRootImpl.isFrameRateConflicted()); - + assertFalse(mViewRootImpl.isFrameRateConflicted()); }); } @@ -872,7 +885,7 @@ public class ViewRootImplTest { // reset the frame rate category counts for (int i = 0; i < 5; i++) { sInstrumentation.runOnMainSync(() -> { - mView.setRequestedFrameRate(mView.REQUESTED_FRAME_RATE_CATEGORY_NO_PREFERENCE); + mView.setRequestedFrameRate(View.REQUESTED_FRAME_RATE_CATEGORY_NO_PREFERENCE); mView.invalidate(); }); sInstrumentation.waitForIdleSync(); @@ -881,21 +894,21 @@ public class ViewRootImplTest { waitForFrameRateCategoryToSettle(mView); sInstrumentation.runOnMainSync(() -> { - mView.setRequestedFrameRate(mView.REQUESTED_FRAME_RATE_CATEGORY_LOW); + mView.setRequestedFrameRate(View.REQUESTED_FRAME_RATE_CATEGORY_LOW); mView.invalidate(); runAfterDraw(() -> assertEquals(FRAME_RATE_CATEGORY_LOW, mViewRootImpl.getLastPreferredFrameRateCategory())); }); waitForAfterDraw(); sInstrumentation.runOnMainSync(() -> { - mView.setRequestedFrameRate(mView.REQUESTED_FRAME_RATE_CATEGORY_NORMAL); + mView.setRequestedFrameRate(View.REQUESTED_FRAME_RATE_CATEGORY_NORMAL); mView.invalidate(); runAfterDraw(() -> assertEquals(FRAME_RATE_CATEGORY_NORMAL, mViewRootImpl.getLastPreferredFrameRateCategory())); }); waitForAfterDraw(); sInstrumentation.runOnMainSync(() -> { - mView.setRequestedFrameRate(mView.REQUESTED_FRAME_RATE_CATEGORY_HIGH); + mView.setRequestedFrameRate(View.REQUESTED_FRAME_RATE_CATEGORY_HIGH); mView.invalidate(); runAfterDraw(() -> assertEquals(FRAME_RATE_CATEGORY_HIGH, mViewRootImpl.getLastPreferredFrameRateCategory())); @@ -932,24 +945,20 @@ public class ViewRootImplTest { assertEquals(0, mViewRootImpl.getPreferredFrameRate(), 0.1); mView.setFrameContentVelocity(100); mView.invalidate(); - if (toolkitFrameRateVelocityMappingReadOnly()) { - runAfterDraw(() -> assertTrue(mViewRootImpl.getLastPreferredFrameRate() > 0)); - } else { - runAfterDraw(() -> assertEquals(FRAME_RATE_CATEGORY_HIGH, - mViewRootImpl.getLastPreferredFrameRateCategory())); - } + runAfterDraw(() -> { + if (toolkitFrameRateVelocityMappingReadOnly()) { + assertEquals(FRAME_RATE_CATEGORY_LOW, + mViewRootImpl.getLastPreferredFrameRateCategory()); + assertTrue(mViewRootImpl.getLastPreferredFrameRate() >= 60f); + } else { + assertEquals(FRAME_RATE_CATEGORY_HIGH, + mViewRootImpl.getLastPreferredFrameRateCategory()); + assertEquals(0, mViewRootImpl.getLastPreferredFrameRate(), 0.1); + } + }); }); waitForAfterDraw(); sInstrumentation.waitForIdleSync(); - if (toolkitFrameRateVelocityMappingReadOnly()) { - assertEquals(FRAME_RATE_CATEGORY_HIGH, - mViewRootImpl.getLastPreferredFrameRateCategory()); - assertTrue(mViewRootImpl.getLastPreferredFrameRate() >= 60f); - } else { - assertEquals(FRAME_RATE_CATEGORY_HIGH, - mViewRootImpl.getLastPreferredFrameRateCategory()); - assertEquals(0, mViewRootImpl.getLastPreferredFrameRate(), 0.1); - } } /** @@ -1002,9 +1011,9 @@ public class ViewRootImplTest { ViewRootImpl viewRootImpl = mView.getViewRootImpl(); final WindowManager.LayoutParams attrs = viewRootImpl.mWindowAttributes; - assertEquals(true, attrs.getFrameRateBoostOnTouchEnabled()); - assertEquals(viewRootImpl.getFrameRateBoostOnTouchEnabled(), - attrs.getFrameRateBoostOnTouchEnabled()); + assertTrue(attrs.getFrameRateBoostOnTouchEnabled()); + assertEquals(attrs.getFrameRateBoostOnTouchEnabled(), + viewRootImpl.getFrameRateBoostOnTouchEnabled()); sInstrumentation.runOnMainSync(() -> { attrs.setFrameRateBoostOnTouchEnabled(false); @@ -1014,9 +1023,9 @@ public class ViewRootImplTest { sInstrumentation.runOnMainSync(() -> { final WindowManager.LayoutParams newAttrs = viewRootImpl.mWindowAttributes; - assertEquals(false, newAttrs.getFrameRateBoostOnTouchEnabled()); - assertEquals(viewRootImpl.getFrameRateBoostOnTouchEnabled(), - newAttrs.getFrameRateBoostOnTouchEnabled()); + assertFalse(newAttrs.getFrameRateBoostOnTouchEnabled()); + assertEquals(newAttrs.getFrameRateBoostOnTouchEnabled(), + viewRootImpl.getFrameRateBoostOnTouchEnabled()); }); } @@ -1040,24 +1049,24 @@ public class ViewRootImplTest { assertEquals(0, viewRootImpl.getPreferredFrameRate(), 0.1); assertEquals(FRAME_RATE_COMPATIBILITY_FIXED_SOURCE, viewRootImpl.getFrameRateCompatibility()); - assertEquals(false, viewRootImpl.isFrameRateConflicted()); + assertFalse(viewRootImpl.isFrameRateConflicted()); viewRootImpl.votePreferredFrameRate(24, FRAME_RATE_COMPATIBILITY_FIXED_SOURCE); assertEquals(24, viewRootImpl.getPreferredFrameRate(), 0.1); assertEquals(FRAME_RATE_COMPATIBILITY_FIXED_SOURCE, viewRootImpl.getFrameRateCompatibility()); - assertEquals(false, viewRootImpl.isFrameRateConflicted()); + assertFalse(viewRootImpl.isFrameRateConflicted()); mView.invalidate(); assertEquals(24, viewRootImpl.getPreferredFrameRate(), 0.1); assertEquals(FRAME_RATE_COMPATIBILITY_FIXED_SOURCE, viewRootImpl.getFrameRateCompatibility()); - assertEquals(false, viewRootImpl.isFrameRateConflicted()); + assertFalse(viewRootImpl.isFrameRateConflicted()); }); Thread.sleep(delay); assertEquals(0, viewRootImpl.getPreferredFrameRate(), 0.1); assertEquals(FRAME_RATE_COMPATIBILITY_FIXED_SOURCE, viewRootImpl.getFrameRateCompatibility()); - assertEquals(false, viewRootImpl.isFrameRateConflicted()); + assertFalse(viewRootImpl.isFrameRateConflicted()); } /** @@ -1093,14 +1102,14 @@ public class ViewRootImplTest { // reset the frame rate category counts for (int i = 0; i < 5; i++) { sInstrumentation.runOnMainSync(() -> { - mView.setRequestedFrameRate(mView.REQUESTED_FRAME_RATE_CATEGORY_NO_PREFERENCE); + mView.setRequestedFrameRate(View.REQUESTED_FRAME_RATE_CATEGORY_NO_PREFERENCE); mView.invalidate(); }); sInstrumentation.waitForIdleSync(); } sInstrumentation.runOnMainSync(() -> { - mView.setRequestedFrameRate(mView.REQUESTED_FRAME_RATE_CATEGORY_LOW); + mView.setRequestedFrameRate(View.REQUESTED_FRAME_RATE_CATEGORY_LOW); mView.invalidate(); runAfterDraw(() -> assertEquals(FRAME_RATE_CATEGORY_LOW, mViewRootImpl.getLastPreferredFrameRateCategory())); @@ -1153,7 +1162,7 @@ public class ViewRootImplTest { // reset the frame rate category counts for (int i = 0; i < 5; i++) { sInstrumentation.runOnMainSync(() -> { - mView.setRequestedFrameRate(mView.REQUESTED_FRAME_RATE_CATEGORY_NO_PREFERENCE); + mView.setRequestedFrameRate(View.REQUESTED_FRAME_RATE_CATEGORY_NO_PREFERENCE); mView.invalidate(); }); sInstrumentation.waitForIdleSync(); @@ -1162,15 +1171,17 @@ public class ViewRootImplTest { // In transition from frequent update to infrequent update Thread.sleep(delay); sInstrumentation.runOnMainSync(() -> { - mView.setRequestedFrameRate(mView.REQUESTED_FRAME_RATE_CATEGORY_NO_PREFERENCE); + mView.setRequestedFrameRate(View.REQUESTED_FRAME_RATE_CATEGORY_NO_PREFERENCE); mView.invalidate(); - runAfterDraw(() -> assertEquals(FRAME_RATE_CATEGORY_NO_PREFERENCE, - mViewRootImpl.getLastPreferredFrameRateCategory())); + runAfterDraw(() -> { + assertEquals(FRAME_RATE_CATEGORY_NO_PREFERENCE, + mViewRootImpl.getLastPreferredFrameRateCategory()); + }); }); waitForAfterDraw(); Thread.sleep(delay); sInstrumentation.runOnMainSync(() -> { - mView.setRequestedFrameRate(mView.REQUESTED_FRAME_RATE_CATEGORY_DEFAULT); + mView.setRequestedFrameRate(View.REQUESTED_FRAME_RATE_CATEGORY_DEFAULT); mView.invalidate(); runAfterDraw(() -> assertEquals(FRAME_RATE_CATEGORY_NO_PREFERENCE, mViewRootImpl.getLastPreferredFrameRateCategory())); @@ -1180,7 +1191,7 @@ public class ViewRootImplTest { // Infrequent update Thread.sleep(delay); sInstrumentation.runOnMainSync(() -> { - mView.setRequestedFrameRate(mView.REQUESTED_FRAME_RATE_CATEGORY_DEFAULT); + mView.setRequestedFrameRate(View.REQUESTED_FRAME_RATE_CATEGORY_DEFAULT); mView.invalidate(); runAfterDraw(() -> assertEquals(FRAME_RATE_CATEGORY_NORMAL, mViewRootImpl.getLastPreferredFrameRateCategory())); @@ -1201,9 +1212,9 @@ public class ViewRootImplTest { ViewRootImpl viewRoot = mView.getViewRootImpl(); final WindowManager.LayoutParams attrs = viewRoot.mWindowAttributes; - assertEquals(attrs.isFrameRatePowerSavingsBalanced(), true); - assertEquals(viewRoot.isFrameRatePowerSavingsBalanced(), - attrs.isFrameRatePowerSavingsBalanced()); + assertTrue(attrs.isFrameRatePowerSavingsBalanced()); + assertEquals(attrs.isFrameRatePowerSavingsBalanced(), + viewRoot.isFrameRatePowerSavingsBalanced()); sInstrumentation.runOnMainSync(() -> { attrs.setFrameRatePowerSavingsBalanced(false); @@ -1213,9 +1224,9 @@ public class ViewRootImplTest { sInstrumentation.runOnMainSync(() -> { final WindowManager.LayoutParams newAttrs = viewRoot.mWindowAttributes; - assertEquals(false, newAttrs.isFrameRatePowerSavingsBalanced()); - assertEquals(viewRoot.isFrameRatePowerSavingsBalanced(), - newAttrs.isFrameRatePowerSavingsBalanced()); + assertFalse(newAttrs.isFrameRatePowerSavingsBalanced()); + assertEquals(newAttrs.isFrameRatePowerSavingsBalanced(), + viewRoot.isFrameRatePowerSavingsBalanced()); }); } @@ -1266,7 +1277,7 @@ public class ViewRootImplTest { for (int i = 0; i < 5; i++) { Thread.sleep(delay); sInstrumentation.runOnMainSync(() -> { - mView.setRequestedFrameRate(mView.REQUESTED_FRAME_RATE_CATEGORY_NO_PREFERENCE); + mView.setRequestedFrameRate(View.REQUESTED_FRAME_RATE_CATEGORY_NO_PREFERENCE); mView.invalidate(); }); sInstrumentation.waitForIdleSync(); @@ -1274,7 +1285,7 @@ public class ViewRootImplTest { Thread.sleep(delay); sInstrumentation.runOnMainSync(() -> { - mView.setRequestedFrameRate(mView.REQUESTED_FRAME_RATE_CATEGORY_DEFAULT); + mView.setRequestedFrameRate(View.REQUESTED_FRAME_RATE_CATEGORY_DEFAULT); mView.invalidate(); runAfterDraw(() -> assertEquals(FRAME_RATE_CATEGORY_NORMAL, mViewRootImpl.getLastPreferredFrameRateCategory())); @@ -1410,7 +1421,7 @@ public class ViewRootImplTest { mViewRootImpl.dispatchInputEvent(event); }); sInstrumentation.waitForIdleSync(); - assertEquals(mKeyReceived, shouldReceiveKey); + assertEquals(shouldReceiveKey, mKeyReceived); } private void attachViewToWindow(View view) { diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeShellCommandHandler.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeShellCommandHandler.kt index e1e41ee1e64d..f1a475a42452 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeShellCommandHandler.kt +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeShellCommandHandler.kt @@ -36,7 +36,14 @@ class DesktopModeShellCommandHandler(private val controller: DesktopTasksControl true } } - + "moveToNextDisplay" -> { + if (!runMoveToNextDisplay(args, pw)) { + pw.println("Task not found. Please enter a valid taskId.") + false + } else { + true + } + } else -> { pw.println("Invalid command: ${args[0]}") false @@ -61,8 +68,28 @@ class DesktopModeShellCommandHandler(private val controller: DesktopTasksControl return controller.moveToDesktop(taskId, WindowContainerTransaction()) } + private fun runMoveToNextDisplay(args: Array<String>, pw: PrintWriter): Boolean { + if (args.size < 2) { + // First argument is the action name. + pw.println("Error: task id should be provided as arguments") + return false + } + + val taskId = try { + args[1].toInt() + } catch (e: NumberFormatException) { + pw.println("Error: task id should be an integer") + return false + } + + controller.moveToNextDisplay(taskId) + return true + } + override fun printShellCommandHelp(pw: PrintWriter, prefix: String) { pw.println("$prefix moveToDesktop <taskId> ") pw.println("$prefix Move a task with given id to desktop mode.") + pw.println("$prefix moveToNextDisplay <taskId> ") + pw.println("$prefix Move a task with given id to next display.") } }
\ No newline at end of file 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 ec2e67007c4b..487bbfbe112b 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 @@ -885,8 +885,9 @@ class DesktopTasksController( wct: WindowContainerTransaction, taskInfo: RunningTaskInfo ) { - val displayWindowingMode = taskInfo.configuration.windowConfiguration.displayWindowingMode - val targetWindowingMode = if (displayWindowingMode == WINDOWING_MODE_FREEFORM) { + val tdaInfo = rootTaskDisplayAreaOrganizer.getDisplayAreaInfo(taskInfo.displayId)!! + val tdaWindowingMode = tdaInfo.configuration.windowConfiguration.windowingMode + val targetWindowingMode = if (tdaWindowingMode == WINDOWING_MODE_FREEFORM) { // Display windowing is freeform, set to undefined and inherit it WINDOWING_MODE_UNDEFINED } else { @@ -903,8 +904,9 @@ class DesktopTasksController( wct: WindowContainerTransaction, taskInfo: RunningTaskInfo ) { - val displayWindowingMode = taskInfo.configuration.windowConfiguration.displayWindowingMode - val targetWindowingMode = if (displayWindowingMode == WINDOWING_MODE_FULLSCREEN) { + val tdaInfo = rootTaskDisplayAreaOrganizer.getDisplayAreaInfo(taskInfo.displayId)!! + val tdaWindowingMode = tdaInfo.configuration.windowConfiguration.windowingMode + val targetWindowingMode = if (tdaWindowingMode == WINDOWING_MODE_FULLSCREEN) { // Display windowing is fullscreen, set to undefined and inherit it WINDOWING_MODE_UNDEFINED } else { diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecorViewModel.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecorViewModel.java index 32f271b0b896..87dc3915082f 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecorViewModel.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecorViewModel.java @@ -19,14 +19,19 @@ package com.android.wm.shell.windowdecor; import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD; import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM; import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN; +import static android.content.pm.PackageManager.FEATURE_PC; +import static android.provider.Settings.Global.DEVELOPMENT_FORCE_DESKTOP_MODE_ON_EXTERNAL_DISPLAYS; import static android.view.WindowManager.TRANSIT_CHANGE; import android.app.ActivityManager.RunningTaskInfo; +import android.content.ContentResolver; import android.content.Context; import android.graphics.Rect; import android.os.Handler; +import android.provider.Settings; import android.util.SparseArray; import android.view.Choreographer; +import android.view.Display; import android.view.MotionEvent; import android.view.SurfaceControl; import android.view.View; @@ -163,10 +168,33 @@ public class CaptionWindowDecorViewModel implements WindowDecorViewModel { } private boolean shouldShowWindowDecor(RunningTaskInfo taskInfo) { - return taskInfo.getWindowingMode() == WINDOWING_MODE_FREEFORM - || (taskInfo.getActivityType() == ACTIVITY_TYPE_STANDARD - && taskInfo.configuration.windowConfiguration.getDisplayWindowingMode() - == WINDOWING_MODE_FREEFORM); + if (taskInfo.getWindowingMode() == WINDOWING_MODE_FREEFORM) { + return true; + } + if (taskInfo.getActivityType() != ACTIVITY_TYPE_STANDARD) { + return false; + } + final DisplayAreaInfo rootDisplayAreaInfo = + mRootTaskDisplayAreaOrganizer.getDisplayAreaInfo(taskInfo.displayId); + if (rootDisplayAreaInfo != null) { + return rootDisplayAreaInfo.configuration.windowConfiguration.getWindowingMode() + == WINDOWING_MODE_FREEFORM; + } + + // It is possible that the rootDisplayAreaInfo is null when a task appears soon enough after + // a new display shows up, because TDA may appear after task appears in WM shell. Instead of + // fixing the synchronization issues, let's use other signals to "guess" the answer. It is + // OK in this context because no other captions other than the legacy developer option + // freeform and Kingyo/CF PC may use this class. WM shell should have full control over the + // condition where captions should show up in all new cases such as desktop mode, for which + // we should use different window decor view models. Ultimately Kingyo/CF PC may need to + // spin up their own window decor view model when they start to care about multiple + // displays. + if (isPc()) { + return true; + } + return taskInfo.displayId != Display.DEFAULT_DISPLAY + && forcesDesktopModeOnExternalDisplays(); } private void createWindowDecoration( @@ -313,4 +341,17 @@ public class CaptionWindowDecorViewModel implements WindowDecorViewModel { return true; } } + + /** + * Returns if this device is a PC. + */ + private boolean isPc() { + return mContext.getPackageManager().hasSystemFeature(FEATURE_PC); + } + + private boolean forcesDesktopModeOnExternalDisplays() { + final ContentResolver resolver = mContext.getContentResolver(); + return Settings.Global.getInt(resolver, + DEVELOPMENT_FORCE_DESKTOP_MODE_ON_EXTERNAL_DISPLAYS, 0) != 0; + } }
\ No newline at end of file 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 93a967e9bfc9..4d0f11b273dc 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 @@ -161,6 +161,10 @@ class DesktopTasksControllerTest : ShellTestCase() { (i.arguments.first() as Rect).set(STABLE_BOUNDS) } + val tda = DisplayAreaInfo(MockToken().token(), DEFAULT_DISPLAY, 0) + tda.configuration.windowConfiguration.windowingMode = WINDOWING_MODE_FULLSCREEN + whenever(rootTaskDisplayAreaOrganizer.getDisplayAreaInfo(DEFAULT_DISPLAY)).thenReturn(tda) + controller = createController() controller.setSplitScreenController(splitScreenController) @@ -336,9 +340,10 @@ class DesktopTasksControllerTest : ShellTestCase() { } @Test - fun moveToDesktop_displayFullscreen_windowingModeSetToFreeform() { + fun moveToDesktop_tdaFullscreen_windowingModeSetToFreeform() { val task = setUpFullscreenTask() - task.configuration.windowConfiguration.displayWindowingMode = WINDOWING_MODE_FULLSCREEN + val tda = rootTaskDisplayAreaOrganizer.getDisplayAreaInfo(DEFAULT_DISPLAY)!! + tda.configuration.windowConfiguration.windowingMode = WINDOWING_MODE_FULLSCREEN controller.moveToDesktop(task) val wct = getLatestMoveToDesktopWct() assertThat(wct.changes[task.token.asBinder()]?.windowingMode) @@ -346,9 +351,10 @@ class DesktopTasksControllerTest : ShellTestCase() { } @Test - fun moveToDesktop_displayFreeform_windowingModeSetToUndefined() { + fun moveToDesktop_tdaFreeform_windowingModeSetToUndefined() { val task = setUpFullscreenTask() - task.configuration.windowConfiguration.displayWindowingMode = WINDOWING_MODE_FREEFORM + val tda = rootTaskDisplayAreaOrganizer.getDisplayAreaInfo(DEFAULT_DISPLAY)!! + tda.configuration.windowConfiguration.windowingMode = WINDOWING_MODE_FREEFORM controller.moveToDesktop(task) val wct = getLatestMoveToDesktopWct() assertThat(wct.changes[task.token.asBinder()]?.windowingMode) @@ -481,9 +487,10 @@ class DesktopTasksControllerTest : ShellTestCase() { } @Test - fun moveToFullscreen_displayFullscreen_windowingModeSetToUndefined() { + fun moveToFullscreen_tdaFullscreen_windowingModeSetToUndefined() { val task = setUpFreeformTask() - task.configuration.windowConfiguration.displayWindowingMode = WINDOWING_MODE_FULLSCREEN + val tda = rootTaskDisplayAreaOrganizer.getDisplayAreaInfo(DEFAULT_DISPLAY)!! + tda.configuration.windowConfiguration.windowingMode = WINDOWING_MODE_FULLSCREEN controller.moveToFullscreen(task.taskId) val wct = getLatestExitDesktopWct() assertThat(wct.changes[task.token.asBinder()]?.windowingMode) @@ -491,9 +498,10 @@ class DesktopTasksControllerTest : ShellTestCase() { } @Test - fun moveToFullscreen_displayFreeform_windowingModeSetToFullscreen() { + fun moveToFullscreen_tdaFreeform_windowingModeSetToFullscreen() { val task = setUpFreeformTask() - task.configuration.windowConfiguration.displayWindowingMode = WINDOWING_MODE_FREEFORM + val tda = rootTaskDisplayAreaOrganizer.getDisplayAreaInfo(DEFAULT_DISPLAY)!! + tda.configuration.windowConfiguration.windowingMode = WINDOWING_MODE_FREEFORM controller.moveToFullscreen(task.taskId) val wct = getLatestExitDesktopWct() assertThat(wct.changes[task.token.asBinder()]?.windowingMode) @@ -684,7 +692,7 @@ class DesktopTasksControllerTest : ShellTestCase() { createTransition(freeformTask2, type = TRANSIT_TO_FRONT) ) assertThat(result?.changes?.get(freeformTask2.token.asBinder())?.windowingMode) - .isEqualTo(WINDOWING_MODE_FULLSCREEN) + .isEqualTo(WINDOWING_MODE_UNDEFINED) // inherited FULLSCREEN } @Test @@ -694,7 +702,7 @@ class DesktopTasksControllerTest : ShellTestCase() { val task = createFreeformTask() val result = controller.handleRequest(Binder(), createTransition(task)) assertThat(result?.changes?.get(task.token.asBinder())?.windowingMode) - .isEqualTo(WINDOWING_MODE_FULLSCREEN) + .isEqualTo(WINDOWING_MODE_UNDEFINED) // inherited FULLSCREEN } @Test @@ -706,7 +714,7 @@ class DesktopTasksControllerTest : ShellTestCase() { val result = controller.handleRequest(Binder(), createTransition(taskDefaultDisplay)) assertThat(result?.changes?.get(taskDefaultDisplay.token.asBinder())?.windowingMode) - .isEqualTo(WINDOWING_MODE_FULLSCREEN) + .isEqualTo(WINDOWING_MODE_UNDEFINED) // inherited FULLSCREEN } @Test @@ -792,7 +800,7 @@ class DesktopTasksControllerTest : ShellTestCase() { val result = controller.handleRequest(Binder(), createTransition(task)) assertThat(result?.changes?.get(task.token.asBinder())?.windowingMode) - .isEqualTo(WINDOWING_MODE_FULLSCREEN) + .isEqualTo(WINDOWING_MODE_UNDEFINED) // inherited FULLSCREEN } @Test @@ -895,7 +903,7 @@ class DesktopTasksControllerTest : ShellTestCase() { val wct = getLatestExitDesktopWct() assertThat(wct.changes[task2.token.asBinder()]?.windowingMode) - .isEqualTo(WINDOWING_MODE_FULLSCREEN) + .isEqualTo(WINDOWING_MODE_UNDEFINED) // inherited FULLSCREEN } @Test diff --git a/libs/androidfw/ZipFileRO.cpp b/libs/androidfw/ZipFileRO.cpp index 34a6bc27b93f..839c7b6fef37 100644 --- a/libs/androidfw/ZipFileRO.cpp +++ b/libs/androidfw/ZipFileRO.cpp @@ -119,30 +119,41 @@ ZipEntryRO ZipFileRO::findEntryByName(const char* entryName) const * appear to be bogus. */ bool ZipFileRO::getEntryInfo(ZipEntryRO entry, uint16_t* pMethod, + uint32_t* pUncompLen, uint32_t* pCompLen, off64_t* pOffset, + uint32_t* pModWhen, uint32_t* pCrc32) const +{ + return getEntryInfo(entry, pMethod, pUncompLen, pCompLen, pOffset, pModWhen, + pCrc32, nullptr); +} + +bool ZipFileRO::getEntryInfo(ZipEntryRO entry, uint16_t* pMethod, uint32_t* pUncompLen, uint32_t* pCompLen, off64_t* pOffset, - uint32_t* pModWhen, uint32_t* pCrc32) const + uint32_t* pModWhen, uint32_t* pCrc32, uint16_t* pExtraFieldSize) const { const _ZipEntryRO* zipEntry = reinterpret_cast<_ZipEntryRO*>(entry); const ZipEntry& ze = zipEntry->entry; - if (pMethod != NULL) { + if (pMethod != nullptr) { *pMethod = ze.method; } - if (pUncompLen != NULL) { + if (pUncompLen != nullptr) { *pUncompLen = ze.uncompressed_length; } - if (pCompLen != NULL) { + if (pCompLen != nullptr) { *pCompLen = ze.compressed_length; } - if (pOffset != NULL) { + if (pOffset != nullptr) { *pOffset = ze.offset; } - if (pModWhen != NULL) { + if (pModWhen != nullptr) { *pModWhen = ze.mod_time; } - if (pCrc32 != NULL) { + if (pCrc32 != nullptr) { *pCrc32 = ze.crc32; } + if (pExtraFieldSize != nullptr) { + *pExtraFieldSize = ze.extra_field_size; + } return true; } diff --git a/libs/androidfw/include/androidfw/ZipFileRO.h b/libs/androidfw/include/androidfw/ZipFileRO.h index 031d2e8fd48f..f7c5007c80d2 100644 --- a/libs/androidfw/include/androidfw/ZipFileRO.h +++ b/libs/androidfw/include/androidfw/ZipFileRO.h @@ -151,6 +151,10 @@ public: uint32_t* pCompLen, off64_t* pOffset, uint32_t* pModWhen, uint32_t* pCrc32) const; + bool getEntryInfo(ZipEntryRO entry, uint16_t* pMethod, + uint32_t* pUncompLen, uint32_t* pCompLen, off64_t* pOffset, + uint32_t* pModWhen, uint32_t* pCrc32, uint16_t* pExtraFieldSize) const; + /* * Create a new FileMap object that maps a subset of the archive. For * an uncompressed entry this effectively provides a pointer to the diff --git a/libs/hwui/pipeline/skia/SkiaPipeline.cpp b/libs/hwui/pipeline/skia/SkiaPipeline.cpp index 34932b1b1e25..dc669a5eca73 100644 --- a/libs/hwui/pipeline/skia/SkiaPipeline.cpp +++ b/libs/hwui/pipeline/skia/SkiaPipeline.cpp @@ -24,7 +24,6 @@ #include <SkImageAndroid.h> #include <SkImageInfo.h> #include <SkMatrix.h> -#include <SkMultiPictureDocument.h> #include <SkOverdrawCanvas.h> #include <SkOverdrawColorFilter.h> #include <SkPicture.h> @@ -38,6 +37,7 @@ #include <android-base/properties.h> #include <gui/TraceUtils.h> #include <include/android/SkSurfaceAndroid.h> +#include <include/docs/SkMultiPictureDocument.h> #include <include/encode/SkPngEncoder.h> #include <include/gpu/ganesh/SkSurfaceGanesh.h> #include <unistd.h> @@ -185,7 +185,7 @@ bool SkiaPipeline::setupMultiFrameCapture() { // we need to keep it until after mMultiPic.close() // procs is passed as a pointer, but just as a method of having an optional default. // procs doesn't need to outlive this Make call. - mMultiPic = SkMakeMultiPictureDocument(mOpenMultiPicStream.get(), &procs, + mMultiPic = SkMultiPictureDocument::Make(mOpenMultiPicStream.get(), &procs, [sharingCtx = mSerialContext.get()](const SkPicture* pic) { SkSharingSerialContext::collectNonTextureImagesFromPicture(pic, sharingCtx); }); diff --git a/libs/hwui/pipeline/skia/SkiaPipeline.h b/libs/hwui/pipeline/skia/SkiaPipeline.h index cf14b1f9ebe3..823b209017a5 100644 --- a/libs/hwui/pipeline/skia/SkiaPipeline.h +++ b/libs/hwui/pipeline/skia/SkiaPipeline.h @@ -18,7 +18,6 @@ #include <SkColorSpace.h> #include <SkDocument.h> -#include <SkMultiPictureDocument.h> #include <SkSurface.h> #include "Lighting.h" diff --git a/libs/hwui/pipeline/skia/VkFunctorDrawable.cpp b/libs/hwui/pipeline/skia/VkFunctorDrawable.cpp index b62711f50c94..21fe6ff14f56 100644 --- a/libs/hwui/pipeline/skia/VkFunctorDrawable.cpp +++ b/libs/hwui/pipeline/skia/VkFunctorDrawable.cpp @@ -16,10 +16,10 @@ #include "VkFunctorDrawable.h" -#include <GrBackendDrawableInfo.h> #include <SkAndroidFrameworkUtils.h> #include <SkImage.h> #include <SkM44.h> +#include <include/gpu/ganesh/vk/GrBackendDrawableInfo.h> #include <gui/TraceUtils.h> #include <private/hwui/DrawVkInfo.h> #include <utils/Color.h> diff --git a/media/tests/MediaRouter/src/com/android/mediaroutertest/MediaRouter2ManagerTest.java b/media/tests/MediaRouter/src/com/android/mediaroutertest/MediaRouter2ManagerTest.java index c836df3b2c4d..c3edb90dbd59 100644 --- a/media/tests/MediaRouter/src/com/android/mediaroutertest/MediaRouter2ManagerTest.java +++ b/media/tests/MediaRouter/src/com/android/mediaroutertest/MediaRouter2ManagerTest.java @@ -361,6 +361,7 @@ public class MediaRouter2ManagerTest { * Tests if MR2.SessionCallback.onSessionCreated is called * when a route is selected from MR2Manager. */ + @Ignore // Ignored due to flakiness. No plans to fix though, in favor of removal (b/334970551). @Test public void testRouterOnSessionCreated() throws Exception { Map<String, MediaRoute2Info> routes = waitAndGetRoutesWithManager(FEATURES_ALL); @@ -908,6 +909,7 @@ public class MediaRouter2ManagerTest { * Tests if getSelectableRoutes and getDeselectableRoutes filter routes based on * selected routes */ + @Ignore // Ignored due to flakiness. No plans to fix though, in favor of removal (b/334970551). @Test public void testGetSelectableRoutes_notReturnsSelectedRoutes() throws Exception { Map<String, MediaRoute2Info> routes = waitAndGetRoutesWithManager(FEATURES_ALL); diff --git a/packages/CredentialManager/res/values/strings.xml b/packages/CredentialManager/res/values/strings.xml index 9fd386f38684..b6b1a451da12 100644 --- a/packages/CredentialManager/res/values/strings.xml +++ b/packages/CredentialManager/res/values/strings.xml @@ -68,14 +68,6 @@ <string name="choose_create_option_password_title">Save password to sign in to <xliff:g id="app_name" example="Tribank">%1$s</xliff:g>?</string> <!-- This appears as the title of the modal bottom sheet for users to choose the create option inside a provider when the credential type is others. [CHAR LIMIT=200] --> <string name="choose_create_option_sign_in_title">Save sign-in info for <xliff:g id="app_name" example="Tribank">%1$s</xliff:g>?</string> - <!-- This appears as a description of the modal bottom sheet when the single tap sign in flow is used for the create passkey flow. [CHAR LIMIT=200] --> - <string name="choose_create_single_tap_passkey_title">Use your screen lock to create a passkey for <xliff:g id="app_name" example="Shrine">%1$s</xliff:g>?</string> - <!-- This appears as a description of the modal bottom sheet when the single tap sign in flow is used for the create password flow. [CHAR LIMIT=200] --> - <string name="choose_create_single_tap_password_title">Use your screen lock to create a password for <xliff:g id="app_name" example="Shrine">%1$s</xliff:g>?</string> - <!-- This appears as a description of the modal bottom sheet when the single tap sign in flow is used for the create flow when the credential type is others. [CHAR LIMIT=200] --> - <!-- TODO(b/326243891) : Confirm with team on dynamically setting this based on recent product and ux discussions (does not disrupt e2e) --> - <string name="choose_create_single_tap_sign_in_title">Use your screen lock to save sign in info for <xliff:g id="app_name" example="Shrine">%1$s</xliff:g>?</string> - <!-- Types which are inserted as a placeholder as credentialTypes for other strings. [CHAR LIMIT=200] --> <string name="passkey">passkey</string> <string name="password">password</string> <string name="passkeys">passkeys</string> @@ -133,6 +125,12 @@ <string name="get_dialog_title_single_tap_for">Use your screen lock to sign in to <xliff:g id="app_name" example="Shrine">%1$s</xliff:g> with <xliff:g id="username" example="beckett-bakery@gmail.com">%2$s</xliff:g></string> <!-- This appears as the title of the dialog asking for user confirmation to use the single user credential (previously saved or to be created) to sign in to the app. [CHAR LIMIT=200] --> <string name="get_dialog_title_use_sign_in_for">Use your sign-in for <xliff:g id="app_name" example="YouTube">%1$s</xliff:g>?</string> + <!-- This appears as the description of the modal bottom sheet asking for user confirmation to use the biometric screen embedded within credential manager for passkey authentication. [CHAR LIMIT=200] --> + <string name="get_dialog_description_single_tap_passkey">Sign in to <xliff:g id="app_name" example="YouTube">%1$s</xliff:g> with your saved passkey for <xliff:g id="username" example="beckett@gmail.com">%2$s</xliff:g>.</string> + <!-- This appears as the description of the modal bottom sheet asking for user confirmation to use the biometric screen embedded within credential manager for password authentication. [CHAR LIMIT=200] --> + <string name="get_dialog_description_single_tap_password">Sign in to <xliff:g id="app_name" example="YouTube">%1$s</xliff:g> with your saved password for <xliff:g id="username" example="beckett@gmail.com">%2$s</xliff:g>.</string> + <!-- This appears as the description of the modal bottom sheet asking for user confirmation to use the biometric screen embedded within credential manager for saved sign-in authentication. [CHAR LIMIT=200] --> + <string name="get_dialog_description_single_tap_saved_sign_in">Sign in to <xliff:g id="app_name" example="YouTube">%1$s</xliff:g> with your saved sign-in info for <xliff:g id="username" example="beckett@gmail.com">%2$s</xliff:g>.</string> <!-- This appears as the title of the dialog asking for user confirmation to unlock / authenticate (e.g. via fingerprint, faceId, passcode etc.) so that we can retrieve their sign-in options. [CHAR LIMIT=200] --> <string name="get_dialog_title_unlock_options_for">Unlock sign-in options for <xliff:g id="app_name" example="YouTube">%1$s</xliff:g>?</string> <!-- This appears as the title of the dialog asking for user to make a choice from multiple previously saved passkey to sign in to the app. [CHAR LIMIT=200] --> diff --git a/packages/CredentialManager/src/com/android/credentialmanager/CredentialSelectorViewModel.kt b/packages/CredentialManager/src/com/android/credentialmanager/CredentialSelectorViewModel.kt index 36f6ad2f386d..429bdbf5959b 100644 --- a/packages/CredentialManager/src/com/android/credentialmanager/CredentialSelectorViewModel.kt +++ b/packages/CredentialManager/src/com/android/credentialmanager/CredentialSelectorViewModel.kt @@ -270,6 +270,15 @@ class CredentialSelectorViewModel( ) } + fun getFlowOnMoreOptionOnlySelected() { + Log.d(Constants.LOG_TAG, "More Option Only selected") + uiState = uiState.copy( + getCredentialUiState = uiState.getCredentialUiState?.copy( + currentScreenState = GetScreenState.ALL_SIGN_IN_OPTIONS_ONLY + ) + ) + } + fun getFlowOnMoreOptionOnSnackBarSelected(isNoAccount: Boolean) { Log.d(Constants.LOG_TAG, "More Option on snackBar selected") uiState = uiState.copy( diff --git a/packages/CredentialManager/src/com/android/credentialmanager/common/BiometricHandler.kt b/packages/CredentialManager/src/com/android/credentialmanager/common/BiometricHandler.kt index e088d3addaf2..95f49e95d3ce 100644 --- a/packages/CredentialManager/src/com/android/credentialmanager/common/BiometricHandler.kt +++ b/packages/CredentialManager/src/com/android/credentialmanager/common/BiometricHandler.kt @@ -17,9 +17,12 @@ package com.android.credentialmanager.common import android.content.Context +import android.content.DialogInterface import android.graphics.Bitmap import android.hardware.biometrics.BiometricManager +import android.hardware.biometrics.BiometricManager.Authenticators import android.hardware.biometrics.BiometricPrompt +import android.hardware.biometrics.PromptContentViewWithMoreOptionsButton import android.os.CancellationSignal import android.util.Log import androidx.core.content.ContextCompat.getMainExecutor @@ -43,19 +46,23 @@ import java.lang.Exception * Namely, this adds the ability to encapsulate the [providerIcon], the providers icon, the * [providerName], which represents the name of the provider, the [displayTitleText] which is * the large text displaying the flow in progress, and the [descriptionForCredential], which - * describes details of where the credential is being saved, and how. - * (E.g. assume a hypothetical provider 'Any Provider' for *passkey* flows with Your@Email.com: + * describes details of where the credential is being saved, and how. [displaySubtitleText] is only expected + * to be used by the 'create' flow, optionally, and describes the saved name of the creating entity. + * (E.g. assume a hypothetical provider 'Any Provider' for *passkey* flows with Your@Email.com and + * name 'Your', and an rp called 'The App'): * * 'get' flow: * - [providerIcon] and [providerName] = 'Any Provider' (and it's icon) - * - [displayTitleText] = "Use your saved passkey for Any Provider?" - * - [descriptionForCredential] = "Use your screen lock to sign in to Any Provider with - * Your@Email.com" + * - [displayTitleText] = "Use your saved passkey for The App?" + * - [descriptionForCredential] = "Sign in to The App with your saved passkey for + * Your@gmail.com" * * 'create' flow: * - [providerIcon] and [providerName] = 'Any Provider' (and it's icon) * - [displayTitleText] = "Create passkey to sign in to Any Provider?" - * - [descriptionForCredential] = "Use your screen lock to create a passkey for Any Provider?" + * - [subtitle] = "Your" + * - [descriptionForCredential] = "You can use your passkey on other devices. It is saved to + * * Google Password Manager for Your@gmail.com." * ). * * The above are examples; the credential type can change depending on scenario. @@ -65,8 +72,9 @@ data class BiometricDisplayInfo( val providerIcon: Bitmap, val providerName: String, val displayTitleText: String, - val descriptionForCredential: String, + val descriptionForCredential: String?, val biometricRequestInfo: BiometricRequestInfo, + val displaySubtitleText: CharSequence? = null, ) /** @@ -85,7 +93,7 @@ data class BiometricState( * so that should this object exist, the result will be retrievable. */ data class BiometricResult( - val biometricAuthenticationResult: BiometricPrompt.AuthenticationResult + val biometricAuthenticationResult: BiometricPrompt.AuthenticationResult, ) /** @@ -97,15 +105,6 @@ data class BiometricError( ) /** - * Encapsulates the help callback results to easily manage biometric help states in the flow. - * To specify, this allows us to parse the onAuthenticationHelp method in the [BiometricPrompt]. - */ -data class BiometricHelp( - val helpCode: Int, - var helpString: CharSequence? = null -) - -/** * This is the entry point to start the integrated biometric prompt for 'get' flows. It captures * information specific to the get flow, along with required shared callbacks and more general * info across both flows, such as the tapped [EntryInfo] or [sendDataToProvider]. @@ -147,7 +146,7 @@ fun runBiometricFlowForGet( Log.d(TAG, "The BiometricPrompt API call begins.") runBiometricFlow(context, biometricDisplayInfo, callback, openMoreOptionsPage, - onBiometricFailureFallback, BiometricFlowType.GET) + onBiometricFailureFallback, BiometricFlowType.GET, onCancelFlowAndFinish) } /** @@ -191,14 +190,15 @@ fun runBiometricFlowForCreate( Log.d(TAG, "The BiometricPrompt API call begins.") runBiometricFlow(context, biometricDisplayInfo, callback, openMoreOptionsPage, - onBiometricFailureFallback, BiometricFlowType.CREATE) + onBiometricFailureFallback, BiometricFlowType.CREATE, onCancelFlowAndFinish) } /** * This will handle the logic for integrating credential manager with the biometric prompt for the * single account biometric experience. This simultaneously handles both the get and create flows, * by retrieving all the data from credential manager, and properly parsing that data into the - * biometric prompt. + * biometric prompt. It will fallback in cases where the biometric api cannot be called, or when + * only device credentials are requested. */ private fun runBiometricFlow( context: Context, @@ -206,28 +206,98 @@ private fun runBiometricFlow( callback: BiometricPrompt.AuthenticationCallback, openMoreOptionsPage: () -> Unit, onBiometricFailureFallback: (BiometricFlowType) -> Unit, - biometricFlowType: BiometricFlowType + biometricFlowType: BiometricFlowType, + onCancelFlowAndFinish: () -> Unit ) { - val biometricPrompt = setupBiometricPrompt(context, biometricDisplayInfo, openMoreOptionsPage, - biometricDisplayInfo.biometricRequestInfo, biometricFlowType) - - val cancellationSignal = CancellationSignal() - cancellationSignal.setOnCancelListener { - Log.d(TAG, "Your cancellation signal was called.") - // TODO(b/333445112) : Migrate towards passing along the developer cancellation signal - // or validate the necessity for this - } + try { + if (onlyUsingDeviceCredentials(biometricDisplayInfo, context)) { + onBiometricFailureFallback(biometricFlowType) + return + } - val executor = getMainExecutor(context) + val biometricPrompt = setupBiometricPrompt(context, biometricDisplayInfo, + openMoreOptionsPage, biometricDisplayInfo.biometricRequestInfo, onCancelFlowAndFinish) - try { - biometricPrompt.authenticate(cancellationSignal, executor, callback) - } catch (e: IllegalArgumentException) { + val cancellationSignal = CancellationSignal() + cancellationSignal.setOnCancelListener { + Log.d(TAG, "Your cancellation signal was called.") + // TODO(b/333445112) : Migrate towards passing along the developer cancellation signal + // or validate the necessity for this + } + + val executor = getMainExecutor(context) + + val cryptoOpId = getCryptoOpId(biometricDisplayInfo) + if (cryptoOpId != null) { + biometricPrompt.authenticate( + BiometricPrompt.CryptoObject(cryptoOpId.toLong()), + cancellationSignal, executor, callback) + } else { + biometricPrompt.authenticate(cancellationSignal, executor, callback) + } + } catch (e: Exception) { + // TODO(b/334923201) : Specialize exception catching Log.w(TAG, "Calling the biometric prompt API failed with: /n${e.localizedMessage}\n") onBiometricFailureFallback(biometricFlowType) } } +private fun getCryptoOpId(biometricDisplayInfo: BiometricDisplayInfo): Int? { + return biometricDisplayInfo.biometricRequestInfo.opId +} + +/** + * Determines if, given the allowed authenticators, the flow should fallback early. This has + * consistency because for biometrics to exist, **device credentials must exist**. Thus, fallbacks + * occur if *only* device credentials are available, to avoid going right into the PIN screen. + * Note that if device credential is the only available modality but not requested, or if none + * of the requested modalities are available, we propagate the error to the provider instead of + * falling back and expect them to handle it as they would prior. + * // TODO(b/334197980) : Finalize error propagation/not propagation in real use cases + */ +private fun onlyUsingDeviceCredentials( + biometricDisplayInfo: BiometricDisplayInfo, + context: Context +): Boolean { + val allowedAuthenticators = biometricDisplayInfo.biometricRequestInfo.allowedAuthenticators + if (allowedAuthenticators == BiometricManager.Authenticators.DEVICE_CREDENTIAL) { + return true + } + + val allowedAuthContainsDeviceCredential = containsBiometricAuthenticatorWithDeviceCredentials( + allowedAuthenticators) + + if (!allowedAuthContainsDeviceCredential) { + // At this point, allowed authenticators is requesting biometrics without device creds. + // Thus, a fallback mechanism will be displayed via our own negative button - "cancel". + // Beyond this point, fallbacks will occur if none of the stronger authenticators can + // be used. + return false + } + + val biometricManager = context.getSystemService(Context.BIOMETRIC_SERVICE) as BiometricManager + + if (allowedAuthContainsDeviceCredential && + biometricManager.canAuthenticate(Authenticators.BIOMETRIC_WEAK) != + BiometricManager.BIOMETRIC_SUCCESS && + biometricManager.canAuthenticate(Authenticators.BIOMETRIC_STRONG) != + BiometricManager.BIOMETRIC_SUCCESS) { + return true + } + + return false +} + +private fun containsBiometricAuthenticatorWithDeviceCredentials( + allowedAuthenticators: Int +): Boolean { + val allowedAuthContainsDeviceCredential = (allowedAuthenticators == + Authenticators.BIOMETRIC_WEAK or Authenticators.DEVICE_CREDENTIAL) || + (allowedAuthenticators == + Authenticators.BIOMETRIC_STRONG or Authenticators.DEVICE_CREDENTIAL) + return allowedAuthContainsDeviceCredential +} + /** * Sets up the biometric prompt with the UI specific bits. * // TODO(b/333445112) : Pass in opId once dependency is confirmed via CryptoObject @@ -237,49 +307,34 @@ private fun setupBiometricPrompt( biometricDisplayInfo: BiometricDisplayInfo, openMoreOptionsPage: () -> Unit, biometricRequestInfo: BiometricRequestInfo, - biometricFlowType: BiometricFlowType, + onCancelFlowAndFinish: () -> Unit ): BiometricPrompt { - val finalAuthenticators = removeDeviceCredential(biometricRequestInfo.allowedAuthenticators) + val listener = + DialogInterface.OnClickListener { _: DialogInterface?, _: Int -> openMoreOptionsPage() } + + val promptContentViewBuilder = PromptContentViewWithMoreOptionsButton.Builder() + .setMoreOptionsButtonListener(context.mainExecutor, listener) + biometricDisplayInfo.descriptionForCredential?.let { + promptContentViewBuilder.setDescription(it) } - val biometricPrompt = BiometricPrompt.Builder(context) + val biometricPromptBuilder = BiometricPrompt.Builder(context) .setTitle(biometricDisplayInfo.displayTitleText) - // TODO(b/333445112) : Migrate to using new methods and strings recently aligned upon - .setNegativeButton(context.getString(if (biometricFlowType == BiometricFlowType.GET) - R.string - .dropdown_presentation_more_sign_in_options_text else R.string.string_more_options), - getMainExecutor(context)) { _, _ -> - openMoreOptionsPage() - } - .setAllowedAuthenticators(finalAuthenticators) + .setAllowedAuthenticators(biometricRequestInfo.allowedAuthenticators) .setConfirmationRequired(true) .setLogoBitmap(biometricDisplayInfo.providerIcon) .setLogoDescription(biometricDisplayInfo.providerName) - .setDescription(biometricDisplayInfo.descriptionForCredential) - .build() - - return biometricPrompt -} + .setContentView(promptContentViewBuilder.build()) -// TODO(b/333445112) : Remove after larger level alignments made on fallback negative button -// For the time being, we do not support the pin fallback until UX is decided. -private fun removeDeviceCredential(requestAllowedAuthenticators: Int): Int { - var finalAuthenticators = requestAllowedAuthenticators - - if (requestAllowedAuthenticators == (BiometricManager.Authenticators.DEVICE_CREDENTIAL or - BiometricManager.Authenticators.BIOMETRIC_WEAK)) { - finalAuthenticators = BiometricManager.Authenticators.BIOMETRIC_WEAK - } - - if (requestAllowedAuthenticators == (BiometricManager.Authenticators.DEVICE_CREDENTIAL or - BiometricManager.Authenticators.BIOMETRIC_STRONG)) { - finalAuthenticators = BiometricManager.Authenticators.BIOMETRIC_WEAK + if (!containsBiometricAuthenticatorWithDeviceCredentials(biometricDisplayInfo + .biometricRequestInfo.allowedAuthenticators)) { + biometricPromptBuilder.setNegativeButton(context.getString(R.string.string_cancel), + getMainExecutor(context) + ) { _: DialogInterface?, _: Int -> onCancelFlowAndFinish() } } - if (requestAllowedAuthenticators == (BiometricManager.Authenticators.DEVICE_CREDENTIAL)) { - finalAuthenticators = BiometricManager.Authenticators.BIOMETRIC_WEAK - } + biometricDisplayInfo.displaySubtitleText?.let { biometricPromptBuilder.setSubtitle(it) } - return finalAuthenticators + return biometricPromptBuilder.build() } /** @@ -417,15 +472,29 @@ private fun retrieveBiometricGetDisplayValues( } val singleEntryType = selectedEntry.credentialType val username = selectedEntry.userName + + // TODO(b/330396140) : Finalize localization and parsing for specific sign in option flows + // (fingerprint, face, etc...)) displayTitleText = context.getString( generateDisplayTitleTextResCode(singleEntryType), getRequestDisplayInfo.appName ) + descriptionText = context.getString( - R.string.get_dialog_title_single_tap_for, + when (singleEntryType) { + CredentialType.PASSKEY -> + R.string.get_dialog_description_single_tap_passkey + + CredentialType.PASSWORD -> + R.string.get_dialog_description_single_tap_password + + CredentialType.UNKNOWN -> + R.string.get_dialog_description_single_tap_saved_sign_in + }, getRequestDisplayInfo.appName, username ) + return BiometricDisplayInfo(providerIcon = icon, providerName = providerName, displayTitleText = displayTitleText, descriptionForCredential = descriptionText, biometricRequestInfo = selectedEntry.biometricRequest as BiometricRequestInfo) @@ -451,23 +520,12 @@ private fun retrieveBiometricCreateDisplayValues( getCreateTitleResCode(createRequestDisplayInfo), createRequestDisplayInfo.appName ) - val descriptionText: String = context.getString( - when (createRequestDisplayInfo.type) { - CredentialType.PASSKEY -> - R.string.choose_create_single_tap_passkey_title - CredentialType.PASSWORD -> - R.string.choose_create_single_tap_password_title - - CredentialType.UNKNOWN -> - R.string.choose_create_single_tap_sign_in_title - }, - createRequestDisplayInfo.appName, - ) - // TODO(b/333445112) : Add a subtitle and any other recently aligned ideas + // TODO(b/330396140) : If footerDescription is null, determine if we need to fallback return BiometricDisplayInfo(providerIcon = icon, providerName = providerName, - displayTitleText = displayTitleText, descriptionForCredential = descriptionText, - biometricRequestInfo = selectedEntry.biometricRequest as BiometricRequestInfo) + displayTitleText = displayTitleText, descriptionForCredential = selectedEntry + .footerDescription, biometricRequestInfo = selectedEntry.biometricRequest + as BiometricRequestInfo, displaySubtitleText = createRequestDisplayInfo.title) } /** diff --git a/packages/CredentialManager/src/com/android/credentialmanager/common/ui/Entry.kt b/packages/CredentialManager/src/com/android/credentialmanager/common/ui/Entry.kt index d13d86fccc97..149c14a24085 100644 --- a/packages/CredentialManager/src/com/android/credentialmanager/common/ui/Entry.kt +++ b/packages/CredentialManager/src/com/android/credentialmanager/common/ui/Entry.kt @@ -349,6 +349,38 @@ fun MoreOptionTopAppBar( } } +@Composable +fun MoreOptionTopAppBarWithCustomNavigation( + text: String, + onNavigationIconClicked: () -> Unit, + navigationIcon: ImageVector, + navigationIconContentDescription: String, + bottomPadding: Dp, +) { + Row( + modifier = Modifier.padding(top = 12.dp, bottom = bottomPadding), + verticalAlignment = Alignment.CenterVertically, + ) { + IconButton( + modifier = Modifier.padding(top = 8.dp, bottom = 8.dp, start = 4.dp).size(48.dp), + onClick = onNavigationIconClicked + ) { + Box( + modifier = Modifier.size(48.dp), + contentAlignment = Alignment.Center, + ) { + Icon( + imageVector = navigationIcon, + contentDescription = navigationIconContentDescription, + modifier = Modifier.size(24.dp).autoMirrored(), + tint = LocalAndroidColorScheme.current.onSurfaceVariant, + ) + } + } + LargeTitleText(text = text, modifier = Modifier.padding(horizontal = 4.dp)) + } +} + private fun Modifier.autoMirrored() = composed { when (LocalLayoutDirection.current) { LayoutDirection.Rtl -> graphicsLayer(scaleX = -1f) diff --git a/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetCredentialComponents.kt b/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetCredentialComponents.kt index c1120bb356b7..e68baf48475f 100644 --- a/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetCredentialComponents.kt +++ b/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetCredentialComponents.kt @@ -32,6 +32,7 @@ import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.wrapContentHeight import androidx.compose.foundation.lazy.items import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.Close import androidx.compose.material.icons.outlined.QrCodeScanner import androidx.compose.material3.Divider import androidx.compose.material3.TextButton @@ -70,6 +71,7 @@ import com.android.credentialmanager.common.ui.HeadlineText import com.android.credentialmanager.common.ui.LargeLabelTextOnSurfaceVariant import com.android.credentialmanager.common.ui.ModalBottomSheet import com.android.credentialmanager.common.ui.MoreOptionTopAppBar +import com.android.credentialmanager.common.ui.MoreOptionTopAppBarWithCustomNavigation import com.android.credentialmanager.common.ui.SheetContainerCard import com.android.credentialmanager.common.ui.Snackbar import com.android.credentialmanager.common.ui.SnackbarActionText @@ -148,7 +150,7 @@ fun GetCredentialScreen( .currentScreenState == GetScreenState.BIOMETRIC_SELECTION) { BiometricSelectionPage( biometricEntry = getCredentialUiState.activeEntry, - onMoreOptionSelected = viewModel::getFlowOnMoreOptionSelected, + onMoreOptionSelected = viewModel::getFlowOnMoreOptionOnlySelected, onCancelFlowAndFinish = viewModel::onUserCancel, onIllegalStateAndFinish = viewModel::onIllegalUiState, requestDisplayInfo = getCredentialUiState.requestDisplayInfo, @@ -163,6 +165,28 @@ fun GetCredentialScreen( onBiometricPromptStateChange = viewModel::onBiometricPromptStateChange ) + } else if (credmanBiometricApiEnabled() && + getCredentialUiState.currentScreenState + == GetScreenState.ALL_SIGN_IN_OPTIONS_ONLY) { + AllSignInOptionCard( + providerInfoList = getCredentialUiState.providerInfoList, + providerDisplayInfo = getCredentialUiState.providerDisplayInfo, + onEntrySelected = viewModel::getFlowOnEntrySelected, + onBackButtonClicked = viewModel::onUserCancel, + onCancel = viewModel::onUserCancel, + onLog = { viewModel.logUiEvent(it) }, + customTopBar = { MoreOptionTopAppBarWithCustomNavigation( + text = stringResource( + R.string.get_dialog_title_sign_in_options), + onNavigationIconClicked = viewModel::onUserCancel, + navigationIcon = Icons.Filled.Close, + navigationIconContentDescription = + stringResource(R.string.accessibility_close_button), + bottomPadding = 0.dp + ) } + ) + viewModel.uiMetrics.log(GetCredentialEvent + .CREDMAN_GET_CRED_SCREEN_ALL_SIGN_IN_OPTIONS) } else { AllSignInOptionCard( providerInfoList = getCredentialUiState.providerInfoList, @@ -642,7 +666,13 @@ private fun findSingleProviderIdForPrimaryPage( return providerId } -/** Draws the secondary credential selection page, where all sign-in options are listed. */ +/** + * Draws the secondary credential selection page, where all sign-in options are listed. + * + * By default, this card has 'back' navigation whereby user can navigate back to invoke + * [onBackButtonClicked]. However if a different top bar with possibly a different navigation + * is required, then the caller of this Composable can set a [customTopBar]. + */ @Composable fun AllSignInOptionCard( providerInfoList: List<ProviderInfo>, @@ -651,16 +681,21 @@ fun AllSignInOptionCard( onBackButtonClicked: () -> Unit, onCancel: () -> Unit, onLog: @Composable (UiEventEnum) -> Unit, + customTopBar: (@Composable() () -> Unit)? = null ) { val sortedUserNameToCredentialEntryList = providerDisplayInfo.sortedUserNameToCredentialEntryList val authenticationEntryList = providerDisplayInfo.authenticationEntryList SheetContainerCard(topAppBar = { - MoreOptionTopAppBar( - text = stringResource(R.string.get_dialog_title_sign_in_options), - onNavigationIconClicked = onBackButtonClicked, - bottomPadding = 0.dp, - ) + if (customTopBar != null) { + customTopBar() + } else { + MoreOptionTopAppBar( + text = stringResource(R.string.get_dialog_title_sign_in_options), + onNavigationIconClicked = onBackButtonClicked, + bottomPadding = 0.dp, + ) + } }) { var isFirstSection = true // For username diff --git a/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetModel.kt b/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetModel.kt index b03407b9ebea..8e7886119a34 100644 --- a/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetModel.kt +++ b/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetModel.kt @@ -163,7 +163,11 @@ enum class GetScreenState { /** The single tap biometric selection page. */ BIOMETRIC_SELECTION, - /** The secondary credential selection page, where all sign-in options are listed. */ + /** + * The secondary credential selection page, where all sign-in options are listed. + * + * This state is expected to go back to PRIMARY_SELECTION on back navigation + */ ALL_SIGN_IN_OPTIONS, /** The snackbar only page when there's no account but only a remoteEntry. */ @@ -171,6 +175,14 @@ enum class GetScreenState { /** The snackbar when there are only auth entries and all of them turn out to be empty. */ UNLOCKED_AUTH_ENTRIES_ONLY, + + /** + * The secondary credential selection page, where all sign-in options are listed. + * + * This state has no option for the user to navigate back to PRIMARY_SELECTION, and + * instead can be terminated independently. + */ + ALL_SIGN_IN_OPTIONS_ONLY, } @@ -285,7 +297,7 @@ private fun toGetScreenState( providerDisplayInfo.remoteEntry != null) GetScreenState.REMOTE_ONLY else if (isRequestForAllOptions) - GetScreenState.ALL_SIGN_IN_OPTIONS + GetScreenState.ALL_SIGN_IN_OPTIONS_ONLY else if (isBiometricFlow(providerDisplayInfo, isFlowAutoSelectable(providerDisplayInfo))) GetScreenState.BIOMETRIC_SELECTION else GetScreenState.PRIMARY_SELECTION diff --git a/packages/PrintSpooler/res/values-night/themes.xml b/packages/PrintSpooler/res/values-night/themes.xml index 4428dbbbaaae..3cc64a6ef266 100644 --- a/packages/PrintSpooler/res/values-night/themes.xml +++ b/packages/PrintSpooler/res/values-night/themes.xml @@ -30,6 +30,7 @@ <item name="android:windowIsTranslucent">true</item> <item name="android:windowActionBar">false</item> <item name="android:windowNoTitle">true</item> + <item name="android:windowOptOutEdgeToEdgeEnforcement">true</item> </style> </resources> diff --git a/packages/PrintSpooler/res/values/themes.xml b/packages/PrintSpooler/res/values/themes.xml index 4dcad10c793c..bd9602540878 100644 --- a/packages/PrintSpooler/res/values/themes.xml +++ b/packages/PrintSpooler/res/values/themes.xml @@ -31,6 +31,7 @@ <item name="android:windowActionBar">false</item> <item name="android:windowNoTitle">true</item> <item name="android:windowLightStatusBar">true</item> + <item name="android:windowOptOutEdgeToEdgeEnforcement">true</item> </style> </resources> diff --git a/packages/SettingsLib/DataStore/Android.bp b/packages/SettingsLib/DataStore/Android.bp index 9fafcabad81b..86c8f0da83cb 100644 --- a/packages/SettingsLib/DataStore/Android.bp +++ b/packages/SettingsLib/DataStore/Android.bp @@ -2,12 +2,17 @@ package { default_applicable_licenses: ["frameworks_base_license"], } +filegroup { + name: "SettingsLibDataStore-srcs", + srcs: ["src/**/*"], +} + android_library { name: "SettingsLibDataStore", defaults: [ "SettingsLintDefaults", ], - srcs: ["src/**/*"], + srcs: [":SettingsLibDataStore-srcs"], static_libs: [ "androidx.annotation_annotation", "androidx.collection_collection-ktx", diff --git a/packages/SettingsLib/DataStore/src/com/android/settingslib/datastore/BackupRestoreFileArchiver.kt b/packages/SettingsLib/DataStore/src/com/android/settingslib/datastore/BackupRestoreFileArchiver.kt index 9d3fb66c7ce0..7644bc967281 100644 --- a/packages/SettingsLib/DataStore/src/com/android/settingslib/datastore/BackupRestoreFileArchiver.kt +++ b/packages/SettingsLib/DataStore/src/com/android/settingslib/datastore/BackupRestoreFileArchiver.kt @@ -19,6 +19,7 @@ package com.android.settingslib.datastore import android.app.backup.BackupDataInputStream import android.content.Context import android.util.Log +import androidx.annotation.VisibleForTesting import java.io.File import java.io.InputStream import java.io.OutputStream @@ -33,11 +34,9 @@ import java.util.zip.CheckedInputStream */ internal class BackupRestoreFileArchiver( private val context: Context, - private val fileStorages: List<BackupRestoreFileStorage>, + @get:VisibleForTesting internal val fileStorages: List<BackupRestoreFileStorage>, + override val name: String, ) : BackupRestoreStorage() { - override val name: String - get() = "file_archiver" - override fun createBackupRestoreEntities(): List<BackupRestoreEntity> = fileStorages.map { it.toBackupRestoreEntity() } @@ -88,7 +87,8 @@ internal class BackupRestoreFileArchiver( } } -private fun BackupRestoreFileStorage.toBackupRestoreEntity() = +@VisibleForTesting +internal fun BackupRestoreFileStorage.toBackupRestoreEntity() = object : BackupRestoreEntity { override val key: String get() = storageFilePath @@ -107,7 +107,7 @@ private fun BackupRestoreFileStorage.toBackupRestoreEntity() = Log.i(LOG_TAG, "[$name] $key not exist") return EntityBackupResult.DELETE } - val codec = codec() ?: defaultCodec() + val codec = defaultCodec() // MUST close to flush the data wrapBackupOutputStream(codec, outputStream).use { stream -> val bytesCopied = file.inputStream().use { it.copyTo(stream) } diff --git a/packages/SettingsLib/DataStore/src/com/android/settingslib/datastore/BackupRestoreStorage.kt b/packages/SettingsLib/DataStore/src/com/android/settingslib/datastore/BackupRestoreStorage.kt index c4c00cbb8191..935f9ccf6ed9 100644 --- a/packages/SettingsLib/DataStore/src/com/android/settingslib/datastore/BackupRestoreStorage.kt +++ b/packages/SettingsLib/DataStore/src/com/android/settingslib/datastore/BackupRestoreStorage.kt @@ -22,6 +22,7 @@ import android.app.backup.BackupDataOutput import android.app.backup.BackupHelper import android.os.ParcelFileDescriptor import android.util.Log +import androidx.annotation.VisibleForTesting import androidx.collection.MutableScatterMap import com.google.common.io.ByteStreams import java.io.ByteArrayOutputStream @@ -60,10 +61,11 @@ abstract class BackupRestoreStorage : BackupHelper { * * Map key is the entity key, map value is the checksum of backup data. */ - protected val entityStates = MutableScatterMap<String, Long>() + @VisibleForTesting(otherwise = VisibleForTesting.PROTECTED) + val entityStates = MutableScatterMap<String, Long>() /** Entities created by [createBackupRestoreEntities]. This field is for restore only. */ - private var entities: List<BackupRestoreEntity>? = null + @VisibleForTesting internal var entities: List<BackupRestoreEntity>? = null /** Entities to back up and restore. */ abstract fun createBackupRestoreEntities(): List<BackupRestoreEntity> @@ -76,7 +78,7 @@ abstract class BackupRestoreStorage : BackupHelper { data: BackupDataOutput, newState: ParcelFileDescriptor, ) { - oldState.readEntityStates(entityStates) + readEntityStates(oldState, entityStates) val backupContext = BackupContext(data) if (!enableBackup(backupContext)) { Log.i(LOG_TAG, "[$name] Backup disabled") @@ -94,7 +96,10 @@ abstract class BackupRestoreStorage : BackupHelper { val codec = entity.codec() ?: defaultCodec() val result = try { - entity.backup(backupContext, wrapBackupOutputStream(codec, checkedOutputStream)) + // MUST close to flush all data + wrapBackupOutputStream(codec, checkedOutputStream).use { + entity.backup(backupContext, it) + } } catch (exception: Exception) { Log.e(LOG_TAG, "[$name] Fail to backup entity $key", exception) continue @@ -191,9 +196,13 @@ abstract class BackupRestoreStorage : BackupHelper { /** Callbacks when restore finished. */ open fun onRestoreFinished() {} - private fun ParcelFileDescriptor?.readEntityStates(state: MutableScatterMap<String, Long>) { + @VisibleForTesting + internal fun readEntityStates( + parcelFileDescriptor: ParcelFileDescriptor?, + state: MutableScatterMap<String, Long>, + ) { state.clear() - if (this == null) return + val fileDescriptor = parcelFileDescriptor?.fileDescriptor ?: return // do not close the streams val fileInputStream = FileInputStream(fileDescriptor) val dataInputStream = DataInputStream(fileInputStream) @@ -233,6 +242,7 @@ abstract class BackupRestoreStorage : BackupHelper { dataOutputStream.writeUTF(key) dataOutputStream.writeLong(value) } + dataOutputStream.flush() } catch (exception: Exception) { Log.e(LOG_TAG, "[$name] Fail to write state file", exception) } @@ -241,7 +251,7 @@ abstract class BackupRestoreStorage : BackupHelper { } companion object { - private const val STATE_VERSION: Byte = 0 + internal const val STATE_VERSION: Byte = 0 /** Checksum for entity backup data. */ fun createChecksum(): Checksum = CRC32() diff --git a/packages/SettingsLib/DataStore/src/com/android/settingslib/datastore/BackupRestoreStorageManager.kt b/packages/SettingsLib/DataStore/src/com/android/settingslib/datastore/BackupRestoreStorageManager.kt index cfdcaff4d34c..82423473e682 100644 --- a/packages/SettingsLib/DataStore/src/com/android/settingslib/datastore/BackupRestoreStorageManager.kt +++ b/packages/SettingsLib/DataStore/src/com/android/settingslib/datastore/BackupRestoreStorageManager.kt @@ -21,23 +21,32 @@ import android.app.backup.BackupAgentHelper import android.app.backup.BackupManager import android.content.Context import android.util.Log +import androidx.annotation.VisibleForTesting import com.google.common.util.concurrent.MoreExecutors import java.util.concurrent.ConcurrentHashMap /** Manager of [BackupRestoreStorage]. */ class BackupRestoreStorageManager private constructor(private val application: Application) { - private val storageWrappers = ConcurrentHashMap<String, StorageWrapper>() + @VisibleForTesting internal val storageWrappers = ConcurrentHashMap<String, StorageWrapper>() private val executor = MoreExecutors.directExecutor() /** * Adds all the registered [BackupRestoreStorage] as the helpers of given [BackupAgentHelper]. * - * All [BackupRestoreFileStorage]s will be wrapped as a single [BackupRestoreFileArchiver]. + * All [BackupRestoreFileStorage]s will be wrapped as a single [BackupRestoreFileArchiver], + * specify [fileArchiverName] to avoid key prefix conflict if needed. * + * @param backupAgentHelper backup agent helper to add helpers + * @param fileArchiverName key prefix of the [BackupRestoreFileArchiver], the value must not be + * changed in future * @see BackupAgentHelper.addHelper */ - fun addBackupAgentHelpers(backupAgentHelper: BackupAgentHelper) { + @JvmOverloads + fun addBackupAgentHelpers( + backupAgentHelper: BackupAgentHelper, + fileArchiverName: String = "file_archiver", + ) { val fileStorages = mutableListOf<BackupRestoreFileStorage>() for ((keyPrefix, storageWrapper) in storageWrappers) { val storage = storageWrapper.storage @@ -48,7 +57,7 @@ class BackupRestoreStorageManager private constructor(private val application: A } } // Always add file archiver even fileStorages is empty to handle forward compatibility - val fileArchiver = BackupRestoreFileArchiver(application, fileStorages) + val fileArchiver = BackupRestoreFileArchiver(application, fileStorages, fileArchiverName) backupAgentHelper.addHelper(fileArchiver.name, fileArchiver) } @@ -106,7 +115,8 @@ class BackupRestoreStorageManager private constructor(private val application: A /** Returns storage with given name, exception is raised if not found. */ fun getOrThrow(name: String): BackupRestoreStorage = storageWrappers[name]!!.storage - private inner class StorageWrapper(val storage: BackupRestoreStorage) : + @VisibleForTesting + internal inner class StorageWrapper(val storage: BackupRestoreStorage) : Observer, KeyedObserver<Any?> { init { when (storage) { @@ -139,7 +149,7 @@ class BackupRestoreStorageManager private constructor(private val application: A LOG_TAG, "Notify BackupManager dataChanged: storage=$name key=$key reason=$reason" ) - BackupManager.dataChanged(application.packageName) + BackupManager(application).dataChanged() } fun removeObserver() { diff --git a/packages/SettingsLib/DataStore/src/com/android/settingslib/datastore/SharedPreferencesStorage.kt b/packages/SettingsLib/DataStore/src/com/android/settingslib/datastore/SharedPreferencesStorage.kt index 0c1b41799f09..9f9c0d839744 100644 --- a/packages/SettingsLib/DataStore/src/com/android/settingslib/datastore/SharedPreferencesStorage.kt +++ b/packages/SettingsLib/DataStore/src/com/android/settingslib/datastore/SharedPreferencesStorage.kt @@ -20,9 +20,11 @@ import android.content.Context import android.content.SharedPreferences import android.os.Build import android.util.Log -import androidx.core.content.ContextCompat +import androidx.annotation.VisibleForTesting import java.io.File +private fun defaultVerbose() = Build.TYPE == "eng" + /** * [SharedPreferences] based storage. * @@ -43,24 +45,35 @@ import java.io.File * @param verbose Verbose logging on key/value pairs during backup/restore. Enable for dev only! * @param filter Filter of key/value pairs for backup and restore. */ -class SharedPreferencesStorage +open class SharedPreferencesStorage @JvmOverloads constructor( context: Context, override val name: String, - mode: Int, - private val verbose: Boolean = (Build.TYPE == "eng"), + @get:VisibleForTesting internal val sharedPreferences: SharedPreferences, + private val codec: BackupCodec? = null, + private val verbose: Boolean = defaultVerbose(), private val filter: (String, Any?) -> Boolean = { _, _ -> true }, ) : BackupRestoreFileStorage(context, context.getSharedPreferencesFilePath(name)), KeyedObservable<String> by KeyedDataObservable() { - private val sharedPreferences = context.getSharedPreferences(name, mode) + @JvmOverloads + constructor( + context: Context, + name: String, + mode: Int, + codec: BackupCodec? = null, + verbose: Boolean = defaultVerbose(), + filter: (String, Any?) -> Boolean = { _, _ -> true }, + ) : this(context, name, context.getSharedPreferences(name, mode), codec, verbose, filter) /** Name of the intermediate SharedPreferences. */ - private val intermediateName: String + @VisibleForTesting + internal val intermediateName: String get() = "_br_$name" + @Suppress("DEPRECATION") private val intermediateSharedPreferences: SharedPreferences get() { // use MODE_MULTI_PROCESS to ensure a reload @@ -82,12 +95,15 @@ constructor( sharedPreferences.registerOnSharedPreferenceChangeListener(sharedPreferencesListener) } + override fun defaultCodec() = codec ?: super.defaultCodec() + override val backupFile: File // use a different file to avoid multi-thread file write get() = context.getSharedPreferencesFile(intermediateName) override fun prepareBackup(file: File) { - val editor = intermediateSharedPreferences.merge(sharedPreferences.all, "Backup") + val editor = + mergeSharedPreferences(intermediateSharedPreferences, sharedPreferences.all, "Backup") // commit to ensure data is write to disk synchronously if (!editor.commit()) { Log.w(LOG_TAG, "[$name] fail to commit") @@ -104,8 +120,8 @@ constructor( // observers consistently once restore finished. sharedPreferences.unregisterOnSharedPreferenceChangeListener(sharedPreferencesListener) val restored = intermediateSharedPreferences - val editor = sharedPreferences.merge(restored.all, "Restore") - editor.apply() // apply to avoid blocking + val editor = mergeSharedPreferences(sharedPreferences, restored.all, "Restore") + editor.commit() // commit to avoid race condition sharedPreferences.registerOnSharedPreferenceChangeListener(sharedPreferencesListener) // clear the intermediate SharedPreferences restored.delete(intermediateName) @@ -115,7 +131,7 @@ constructor( if (deleteSharedPreferences(name)) { Log.i(LOG_TAG, "SharedPreferences $name deleted") } else { - edit().clear().apply() + edit().clear().commit() // commit to avoid potential race condition } } @@ -126,11 +142,13 @@ constructor( false } - private fun SharedPreferences.merge( + @VisibleForTesting + internal open fun mergeSharedPreferences( + sharedPreferences: SharedPreferences, entries: Map<String, Any?>, - operation: String + operation: String, ): SharedPreferences.Editor { - val editor = edit() + val editor = sharedPreferences.edit() for ((key, value) in entries) { if (!filter.invoke(key, value)) { if (verbose) Log.v(LOG_TAG, "[$name] $operation skips $key=$value") @@ -184,7 +202,7 @@ constructor( companion object { private fun Context.getSharedPreferencesFilePath(name: String): String { val file = getSharedPreferencesFile(name) - return file.relativeTo(ContextCompat.getDataDir(this)!!).toString() + return file.relativeTo(dataDirCompat).toString() } /** Returns the absolute path of shared preferences file. */ diff --git a/packages/SettingsLib/DataStore/tests/Android.bp b/packages/SettingsLib/DataStore/tests/Android.bp index 8770dfa013d0..5d000ebe9417 100644 --- a/packages/SettingsLib/DataStore/tests/Android.bp +++ b/packages/SettingsLib/DataStore/tests/Android.bp @@ -9,11 +9,16 @@ android_app { android_robolectric_test { name: "SettingsLibDataStoreTest", - srcs: ["src/**/*"], + srcs: [ + ":SettingsLibDataStore-srcs", // b/240432457 + "src/**/*", + ], static_libs: [ - "SettingsLibDataStore", + "androidx.collection_collection-ktx", + "androidx.core_core-ktx", "androidx.test.ext.junit", "guava", + "kotlin-test", "mockito-robolectric-prebuilt", // mockito deps order matters! "mockito-kotlin2", ], diff --git a/packages/SettingsLib/DataStore/tests/src/com/android/settingslib/datastore/BackupCodecTest.kt b/packages/SettingsLib/DataStore/tests/src/com/android/settingslib/datastore/BackupCodecTest.kt new file mode 100644 index 000000000000..867831b7f2bd --- /dev/null +++ b/packages/SettingsLib/DataStore/tests/src/com/android/settingslib/datastore/BackupCodecTest.kt @@ -0,0 +1,75 @@ +/* + * 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.settingslib.datastore + +import androidx.test.ext.junit.runners.AndroidJUnit4 +import com.google.common.truth.Truth.assertThat +import com.google.common.truth.Truth.assertWithMessage +import java.io.ByteArrayInputStream +import java.io.ByteArrayOutputStream +import kotlin.random.Random +import kotlin.test.assertFailsWith +import org.junit.Test +import org.junit.runner.RunWith + +/** Tests of [BackupCodec]. */ +@RunWith(AndroidJUnit4::class) +class BackupCodecTest { + @Test + fun name() { + val names = mutableSetOf<String>() + for (codec in allCodecs()) { + assertThat(names).doesNotContain(codec.name) + names.add(codec.name) + } + } + + @Test + fun fromId() { + for (codec in allCodecs()) { + assertThat(BackupCodec.fromId(codec.id)).isInstanceOf(codec::class.java) + } + } + + @Test + fun fromId_unknownId() { + assertFailsWith(IllegalArgumentException::class) { BackupCodec.fromId(-1) } + } + + @Test + fun encode_decode() { + val random = Random.Default + fun test(codec: BackupCodec, size: Int) { + val data = random.nextBytes(size) + + // encode + val outputStream = ByteArrayOutputStream() + codec.encode(outputStream).use { it.write(data) } + + // decode + val inputStream = ByteArrayInputStream(outputStream.toByteArray()) + val result = codec.decode(inputStream).use { it.readBytes() } + + assertWithMessage("$size bytes: $data").that(result).isEqualTo(data) + } + + for (codec in allCodecs()) { + test(codec, 0) + repeat(10) { test(codec, random.nextInt(1, 1024)) } + } + } +} diff --git a/packages/SettingsLib/DataStore/tests/src/com/android/settingslib/datastore/BackupRestoreContextTest.kt b/packages/SettingsLib/DataStore/tests/src/com/android/settingslib/datastore/BackupRestoreContextTest.kt new file mode 100644 index 000000000000..911665a28809 --- /dev/null +++ b/packages/SettingsLib/DataStore/tests/src/com/android/settingslib/datastore/BackupRestoreContextTest.kt @@ -0,0 +1,44 @@ +/* + * 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.settingslib.datastore + +import android.app.backup.BackupDataOutput +import android.os.Build +import androidx.test.ext.junit.runners.AndroidJUnit4 +import com.google.common.truth.Truth.assertThat +import org.junit.Test +import org.junit.runner.RunWith +import org.mockito.kotlin.doReturn +import org.mockito.kotlin.mock + +/** Tests of [BackupContext] and [RestoreContext]. */ +@RunWith(AndroidJUnit4::class) +class BackupRestoreContextTest { + @Test + fun backupContext_quota() { + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) return + val data = mock<BackupDataOutput> { on { quota } doReturn 10L } + assertThat(BackupContext(data).quota).isEqualTo(10) + } + + @Test + fun backupContext_transportFlags() { + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.P) return + val data = mock<BackupDataOutput> { on { transportFlags } doReturn 5 } + assertThat(BackupContext(data).transportFlags).isEqualTo(5) + } +} diff --git a/packages/SettingsLib/DataStore/tests/src/com/android/settingslib/datastore/BackupRestoreFileArchiverTest.kt b/packages/SettingsLib/DataStore/tests/src/com/android/settingslib/datastore/BackupRestoreFileArchiverTest.kt new file mode 100644 index 000000000000..6cce45320a04 --- /dev/null +++ b/packages/SettingsLib/DataStore/tests/src/com/android/settingslib/datastore/BackupRestoreFileArchiverTest.kt @@ -0,0 +1,275 @@ +/* + * 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.settingslib.datastore + +import android.app.Application +import androidx.test.core.app.ApplicationProvider.getApplicationContext +import androidx.test.ext.junit.runners.AndroidJUnit4 +import com.google.common.truth.Truth.assertThat +import java.io.ByteArrayInputStream +import java.io.ByteArrayOutputStream +import java.io.File +import java.io.IOException +import java.io.InputStream +import kotlin.random.Random +import org.junit.Rule +import org.junit.Test +import org.junit.rules.TemporaryFolder +import org.junit.runner.RunWith +import org.mockito.kotlin.any +import org.mockito.kotlin.doReturn +import org.mockito.kotlin.mock +import org.mockito.kotlin.never +import org.mockito.kotlin.verify + +/** Tests of [BackupRestoreFileArchiver]. */ +@RunWith(AndroidJUnit4::class) +class BackupRestoreFileArchiverTest { + private val random = Random.Default + private val application: Application = getApplicationContext() + @get:Rule val temporaryFolder = TemporaryFolder(application.dataDirCompat) + + @Test + fun createBackupRestoreEntities() { + val fileStorages = mutableListOf<BackupRestoreFileStorage>() + for (count in 0 until 3) { + val fileArchiver = BackupRestoreFileArchiver(application, fileStorages, "") + fileArchiver.createBackupRestoreEntities().apply { + assertThat(this).hasSize(fileStorages.size) + for (index in 0 until count) { + assertThat(get(index).key).isEqualTo(fileStorages[index].storageFilePath) + } + } + fileStorages.add(FileStorage("storage", "path$count")) + } + } + + @Test + fun wrapBackupOutputStream() { + val fileArchiver = BackupRestoreFileArchiver(application, listOf(), "") + val outputStream = ByteArrayOutputStream() + assertThat(fileArchiver.wrapBackupOutputStream(BackupZipCodec.BEST_SPEED, outputStream)) + .isSameInstanceAs(outputStream) + } + + @Test + fun wrapRestoreInputStream() { + val fileArchiver = BackupRestoreFileArchiver(application, listOf(), "") + val inputStream = ByteArrayInputStream(byteArrayOf()) + assertThat(fileArchiver.wrapRestoreInputStream(BackupZipCodec.BEST_SPEED, inputStream)) + .isSameInstanceAs(inputStream) + } + + @Test + fun restoreEntity_disabled() { + val file = temporaryFolder.newFile() + val key = file.name + val fileStorage = FileStorage("fs", key, restoreEnabled = false) + + BackupRestoreFileArchiver(application, listOf(fileStorage), "archiver").apply { + restoreEntity(newBackupDataInputStream(key, byteArrayOf())) + assertThat(entityStates.asMap()).isEmpty() + } + } + + @Test + fun restoreEntity_raiseIOException() { + val key = "key" + val fileStorage = FileStorage("fs", key) + BackupRestoreFileArchiver(application, listOf(fileStorage), "archiver").apply { + restoreEntity(newBackupDataInputStream(key, byteArrayOf(), IOException())) + assertThat(entityStates.asMap()).isEmpty() + } + } + + @Test + fun restoreEntity_onRestoreFinished_raiseException() { + val key = "key" + val fileStorage = FileStorage("fs", key, restoreException = IllegalStateException()) + BackupRestoreFileArchiver(application, listOf(fileStorage), "archiver").apply { + val data = random.nextBytes(random.nextInt(10)) + val outputStream = ByteArrayOutputStream() + fileStorage.wrapBackupOutputStream(fileStorage.defaultCodec(), outputStream).use { + it.write(data) + } + val payload = outputStream.toByteArray() + restoreEntity(newBackupDataInputStream(key, payload)) + assertThat(entityStates.asMap()).isEmpty() + } + } + + @Test + fun restoreEntity_forwardCompatibility() { + val key = "key" + val fileStorage = FileStorage("fs", key) + for (codec in allCodecs()) { + BackupRestoreFileArchiver(application, listOf(), "archiver").apply { + val data = random.nextBytes(random.nextInt(MAX_DATA_SIZE)) + val outputStream = ByteArrayOutputStream() + fileStorage.wrapBackupOutputStream(codec, outputStream).use { it.write(data) } + val payload = outputStream.toByteArray() + + restoreEntity(newBackupDataInputStream(key, payload)) + + assertThat(entityStates.asMap()).apply { + hasSize(1) + containsKey(key) + } + assertThat(fileStorage.restoreFile.readBytes()).isEqualTo(data) + } + } + } + + @Test + fun restoreEntity() { + val folder = File(application.dataDirCompat, "backup") + val file = File(folder, "file") + val key = "${folder.name}${File.separator}${file.name}" + fun test(codec: BackupCodec, size: Int) { + val fileStorage = FileStorage("fs", key, if (size % 2 == 0) codec else null) + val data = random.nextBytes(size) + val outputStream = ByteArrayOutputStream() + fileStorage.wrapBackupOutputStream(codec, outputStream).use { it.write(data) } + val payload = outputStream.toByteArray() + + val fileArchiver = + BackupRestoreFileArchiver(application, listOf(fileStorage), "archiver") + fileArchiver.restoreEntity(newBackupDataInputStream(key, payload)) + + assertThat(fileArchiver.entityStates.asMap()).apply { + hasSize(1) + containsKey(key) + } + assertThat(file.readBytes()).isEqualTo(data) + } + + for (codec in allCodecs()) { + for (size in 0 until 100) test(codec, size) + repeat(10) { test(codec, random.nextInt(100, MAX_DATA_SIZE)) } + } + } + + @Test + fun onRestoreFinished() { + val fileStorage = mock<BackupRestoreFileStorage>() + val fileArchiver = BackupRestoreFileArchiver(application, listOf(fileStorage), "") + + fileArchiver.onRestoreFinished() + + verify(fileStorage).onRestoreFinished() + } + + @Test + fun toBackupRestoreEntity_backup_disabled() { + val context = BackupContext(mock()) + val fileStorage = + mock<BackupRestoreFileStorage> { on { enableBackup(context) } doReturn false } + + assertThat(fileStorage.toBackupRestoreEntity().backup(context, ByteArrayOutputStream())) + .isEqualTo(EntityBackupResult.INTACT) + + verify(fileStorage, never()).prepareBackup(any()) + } + + @Test + fun toBackupRestoreEntity_backup_fileNotExist() { + val context = BackupContext(mock()) + val file = File("NotExist") + val fileStorage = + mock<BackupRestoreFileStorage> { + on { enableBackup(context) } doReturn true + on { backupFile } doReturn file + } + + assertThat(fileStorage.toBackupRestoreEntity().backup(context, ByteArrayOutputStream())) + .isEqualTo(EntityBackupResult.DELETE) + + verify(fileStorage).prepareBackup(file) + verify(fileStorage, never()).defaultCodec() + } + + @Test + fun toBackupRestoreEntity_backup() { + val context = BackupContext(mock()) + val file = temporaryFolder.newFile() + + fun test(codec: BackupCodec, size: Int) { + val data = random.nextBytes(size) + file.outputStream().use { it.write(data) } + + val outputStream = ByteArrayOutputStream() + val fileStorage = + mock<BackupRestoreFileStorage> { + on { enableBackup(context) } doReturn true + on { backupFile } doReturn file + on { defaultCodec() } doReturn codec + on { wrapBackupOutputStream(any(), any()) }.thenCallRealMethod() + on { wrapRestoreInputStream(any(), any()) }.thenCallRealMethod() + on { prepareBackup(any()) }.thenCallRealMethod() + on { onBackupFinished(any()) }.thenCallRealMethod() + } + + assertThat(fileStorage.toBackupRestoreEntity().backup(context, outputStream)) + .isEqualTo(EntityBackupResult.UPDATE) + + verify(fileStorage).prepareBackup(file) + verify(fileStorage).onBackupFinished(file) + + val decodedData = + fileStorage + .wrapRestoreInputStream(codec, ByteArrayInputStream(outputStream.toByteArray())) + .readBytes() + assertThat(decodedData).isEqualTo(data) + } + + for (codec in allCodecs()) { + // test small data to ensure correctness + for (size in 0 until 100) test(codec, size) + repeat(10) { test(codec, random.nextInt(100, MAX_DATA_SIZE)) } + } + } + + @Test + fun toBackupRestoreEntity_restore() { + val restoreContext = RestoreContext("storage") + val inputStream = + object : InputStream() { + override fun read() = throw IllegalStateException() + + override fun read(b: ByteArray, off: Int, len: Int) = throw IllegalStateException() + } + FileStorage("storage", "path").toBackupRestoreEntity().restore(restoreContext, inputStream) + } + + private open class FileStorage( + override val name: String, + filePath: String, + private val codec: BackupCodec? = null, + private val restoreEnabled: Boolean? = null, + private val restoreException: Exception? = null, + ) : BackupRestoreFileStorage(getApplicationContext(), filePath) { + + override fun defaultCodec() = codec ?: super.defaultCodec() + + override fun enableRestore() = restoreEnabled ?: super.enableRestore() + + override fun onRestoreFinished(file: File) { + super.onRestoreFinished(file) + if (restoreException != null) throw restoreException + } + } +} diff --git a/packages/SettingsLib/DataStore/tests/src/com/android/settingslib/datastore/BackupRestoreFileStorageTest.kt b/packages/SettingsLib/DataStore/tests/src/com/android/settingslib/datastore/BackupRestoreFileStorageTest.kt new file mode 100644 index 000000000000..422273d9ce14 --- /dev/null +++ b/packages/SettingsLib/DataStore/tests/src/com/android/settingslib/datastore/BackupRestoreFileStorageTest.kt @@ -0,0 +1,105 @@ +/* + * 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.settingslib.datastore + +import android.app.Application +import android.os.Build +import androidx.test.core.app.ApplicationProvider.getApplicationContext +import androidx.test.ext.junit.runners.AndroidJUnit4 +import com.google.common.truth.Truth.assertThat +import java.io.File +import kotlin.test.assertFailsWith +import org.junit.Test +import org.junit.runner.RunWith + +/** Tests of [BackupRestoreFileStorage]. */ +@RunWith(AndroidJUnit4::class) +class BackupRestoreFileStorageTest { + private val application: Application = getApplicationContext() + + @Test + fun dataDirCompat() { + val expected = + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { + application.dataDir + } else { + File(application.applicationInfo.dataDir) + } + assertThat(application.dataDirCompat).isEqualTo(expected) + } + + @Test + fun backupFile() { + assertThat(FileStorage("path").backupFile.toString()) + .startsWith(application.dataDirCompat.toString()) + } + + @Test + fun restoreFile() { + FileStorage("path").apply { assertThat(restoreFile).isEqualTo(backupFile) } + } + + @Test + fun checkFilePaths() { + FileStorage("path").checkFilePaths() + } + + @Test + fun checkFilePaths_emptyFilePath() { + assertFailsWith(IllegalArgumentException::class) { FileStorage("").checkFilePaths() } + } + + @Test + fun checkFilePaths_absoluteFilePath() { + assertFailsWith(IllegalArgumentException::class) { + FileStorage("${File.separatorChar}file").checkFilePaths() + } + } + + @Test + fun checkFilePaths_backupFile() { + assertFailsWith(IllegalArgumentException::class) { + FileStorage("path", fileForBackup = File("path")).checkFilePaths() + } + } + + @Test + fun checkFilePaths_restoreFile() { + assertFailsWith(IllegalArgumentException::class) { + FileStorage("path", fileForRestore = File("path")).checkFilePaths() + } + } + + @Test + fun createBackupRestoreEntities() { + assertThat(FileStorage("path").createBackupRestoreEntities()).isEmpty() + } + + private class FileStorage( + filePath: String, + val fileForBackup: File? = null, + val fileForRestore: File? = null, + ) : BackupRestoreFileStorage(getApplicationContext(), filePath) { + override val name = "storage" + + override val backupFile: File + get() = fileForBackup ?: super.backupFile + + override val restoreFile: File + get() = fileForRestore ?: super.restoreFile + } +} diff --git a/packages/SettingsLib/DataStore/tests/src/com/android/settingslib/datastore/BackupRestoreStorageManagerTest.kt b/packages/SettingsLib/DataStore/tests/src/com/android/settingslib/datastore/BackupRestoreStorageManagerTest.kt new file mode 100644 index 000000000000..d8f502854402 --- /dev/null +++ b/packages/SettingsLib/DataStore/tests/src/com/android/settingslib/datastore/BackupRestoreStorageManagerTest.kt @@ -0,0 +1,237 @@ +/* + * 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.settingslib.datastore + +import android.app.Application +import android.app.backup.BackupAgentHelper +import android.app.backup.BackupHelper +import android.app.backup.BackupManager +import androidx.test.core.app.ApplicationProvider.getApplicationContext +import androidx.test.ext.junit.runners.AndroidJUnit4 +import com.google.common.truth.Truth.assertThat +import com.google.common.util.concurrent.MoreExecutors.directExecutor +import kotlin.test.assertFailsWith +import org.junit.After +import org.junit.Test +import org.junit.runner.RunWith +import org.mockito.kotlin.any +import org.mockito.kotlin.doReturn +import org.mockito.kotlin.mock +import org.mockito.kotlin.never +import org.mockito.kotlin.reset +import org.mockito.kotlin.verify +import org.robolectric.Shadows +import org.robolectric.shadows.ShadowBackupManager + +/** Tests of [BackupRestoreStorageManager]. */ +@RunWith(AndroidJUnit4::class) +class BackupRestoreStorageManagerTest { + private val application: Application = getApplicationContext() + private val manager = BackupRestoreStorageManager.getInstance(application) + private val fileStorage = FileStorage("fileStorage") + private val keyedStorage = KeyedStorage("keyedStorage") + + private val storage1 = mock<ObservableBackupRestoreStorage> { on { name } doReturn "1" } + private val storage2 = mock<ObservableBackupRestoreStorage> { on { name } doReturn "1" } + + @After + fun tearDown() { + manager.removeAll() + ShadowBackupManager.reset() + } + + @Test + fun getInstance() { + assertThat(BackupRestoreStorageManager.getInstance(application)).isSameInstanceAs(manager) + } + + @Test + fun addBackupAgentHelpers() { + val fs = FileStorage("fs") + manager.add(keyedStorage, fileStorage, storage1, fs) + val backupAgentHelper = DummyBackupAgentHelper() + manager.addBackupAgentHelpers(backupAgentHelper) + backupAgentHelper.backupHelpers.apply { + assertThat(size).isEqualTo(3) + assertThat(remove(keyedStorage.name)).isSameInstanceAs(keyedStorage) + assertThat(remove(storage1.name)).isSameInstanceAs(storage1) + val fileArchiver = entries.first().value as BackupRestoreFileArchiver + assertThat(fileArchiver.fileStorages.toSet()).containsExactly(fs, fileStorage) + } + } + + @Test + fun addBackupAgentHelpers_withoutFileStorage() { + manager.add(keyedStorage, storage1) + val backupAgentHelper = DummyBackupAgentHelper() + manager.addBackupAgentHelpers(backupAgentHelper) + backupAgentHelper.backupHelpers.apply { + assertThat(size).isEqualTo(3) + assertThat(remove(keyedStorage.name)).isSameInstanceAs(keyedStorage) + assertThat(remove(storage1.name)).isSameInstanceAs(storage1) + val fileArchiver = entries.first().value as BackupRestoreFileArchiver + assertThat(fileArchiver.fileStorages).isEmpty() + } + } + + @Test + fun add() { + manager.add(keyedStorage, fileStorage, storage1) + assertThat(manager.storageWrappers).apply { + hasSize(3) + containsKey(keyedStorage.name) + containsKey(fileStorage.name) + containsKey(storage1.name) + } + } + + @Test + fun add_identicalName() { + manager.add(storage1) + assertFailsWith(IllegalStateException::class) { manager.add(storage1) } + assertFailsWith(IllegalStateException::class) { manager.add(storage2) } + } + + @Test + fun add_nonObservable() { + assertFailsWith(IllegalArgumentException::class) { + manager.add(mock<BackupRestoreStorage>()) + } + } + + @Test + fun removeAll() { + add() + manager.removeAll() + assertThat(manager.storageWrappers).isEmpty() + } + + @Test + fun remove() { + manager.add(keyedStorage, fileStorage) + assertThat(manager.remove(storage1.name)).isNull() + assertThat(manager.remove(keyedStorage.name)).isSameInstanceAs(keyedStorage) + assertThat(manager.remove(fileStorage.name)).isSameInstanceAs(fileStorage) + } + + @Test + fun get() { + manager.add(keyedStorage, fileStorage) + assertThat(manager.get(storage1.name)).isNull() + assertThat(manager.get(keyedStorage.name)).isSameInstanceAs(keyedStorage) + assertThat(manager.get(fileStorage.name)).isSameInstanceAs(fileStorage) + } + + @Test + fun getOrThrow() { + manager.add(keyedStorage, fileStorage) + assertFailsWith(NullPointerException::class) { manager.getOrThrow(storage1.name) } + assertThat(manager.getOrThrow(keyedStorage.name)).isSameInstanceAs(keyedStorage) + assertThat(manager.getOrThrow(fileStorage.name)).isSameInstanceAs(fileStorage) + } + + @Test + fun notifyRestoreFinished() { + manager.add(keyedStorage, fileStorage) + val keyedObserver = mock<KeyedObserver<String>>() + val anyKeyObserver = mock<KeyedObserver<String?>>() + val observer = mock<Observer>() + val executor = directExecutor() + keyedStorage.addObserver("key", keyedObserver, executor) + keyedStorage.addObserver(anyKeyObserver, executor) + fileStorage.addObserver(observer, executor) + + manager.onRestoreFinished() + + verify(keyedObserver).onKeyChanged("key", ChangeReason.RESTORE) + verify(anyKeyObserver).onKeyChanged(null, ChangeReason.RESTORE) + verify(observer).onChanged(ChangeReason.RESTORE) + if (isRobolectric()) { + Shadows.shadowOf(BackupManager(application)).apply { + assertThat(isDataChanged).isFalse() + assertThat(dataChangedCount).isEqualTo(0) + } + } + } + + @Test + fun notifyBackupManager() { + manager.add(keyedStorage, fileStorage) + val keyedObserver = mock<KeyedObserver<String>>() + val anyKeyObserver = mock<KeyedObserver<String?>>() + val observer = mock<Observer>() + val executor = directExecutor() + keyedStorage.addObserver("key", keyedObserver, executor) + keyedStorage.addObserver(anyKeyObserver, executor) + fileStorage.addObserver(observer, executor) + + val backupManager = + if (isRobolectric()) Shadows.shadowOf(BackupManager(application)) else null + backupManager?.apply { + assertThat(isDataChanged).isFalse() + assertThat(dataChangedCount).isEqualTo(0) + } + + fileStorage.notifyChange(ChangeReason.UPDATE) + verify(observer).onChanged(ChangeReason.UPDATE) + verify(keyedObserver, never()).onKeyChanged(any(), any()) + verify(anyKeyObserver, never()).onKeyChanged(any(), any()) + reset(observer) + backupManager?.apply { + assertThat(isDataChanged).isTrue() + assertThat(dataChangedCount).isEqualTo(1) + } + + keyedStorage.notifyChange("key", ChangeReason.DELETE) + verify(observer, never()).onChanged(any()) + verify(keyedObserver).onKeyChanged("key", ChangeReason.DELETE) + verify(anyKeyObserver).onKeyChanged("key", ChangeReason.DELETE) + backupManager?.apply { + assertThat(isDataChanged).isTrue() + assertThat(dataChangedCount).isEqualTo(2) + } + reset(keyedObserver) + + // backup manager is not notified for restore event + fileStorage.notifyChange(ChangeReason.RESTORE) + keyedStorage.notifyChange("key", ChangeReason.RESTORE) + verify(observer).onChanged(ChangeReason.RESTORE) + verify(keyedObserver).onKeyChanged("key", ChangeReason.RESTORE) + verify(anyKeyObserver).onKeyChanged("key", ChangeReason.RESTORE) + backupManager?.apply { + assertThat(isDataChanged).isTrue() + assertThat(dataChangedCount).isEqualTo(2) + } + } + + private class KeyedStorage(override val name: String) : + BackupRestoreStorage(), KeyedObservable<String> by KeyedDataObservable() { + + override fun createBackupRestoreEntities(): List<BackupRestoreEntity> = listOf() + } + + private class FileStorage(override val name: String) : + BackupRestoreFileStorage(getApplicationContext(), "file"), Observable by DataObservable() + + private class DummyBackupAgentHelper : BackupAgentHelper() { + val backupHelpers = mutableMapOf<String, BackupHelper>() + + override fun addHelper(keyPrefix: String, helper: BackupHelper) { + backupHelpers[keyPrefix] = helper + } + } +} diff --git a/packages/SettingsLib/DataStore/tests/src/com/android/settingslib/datastore/BackupRestoreStorageTest.kt b/packages/SettingsLib/DataStore/tests/src/com/android/settingslib/datastore/BackupRestoreStorageTest.kt new file mode 100644 index 000000000000..99998ffc13ec --- /dev/null +++ b/packages/SettingsLib/DataStore/tests/src/com/android/settingslib/datastore/BackupRestoreStorageTest.kt @@ -0,0 +1,414 @@ +/* + * 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.settingslib.datastore + +import android.app.backup.BackupAgentHelper +import android.app.backup.BackupDataInput +import android.app.backup.BackupDataInputStream +import android.app.backup.BackupDataOutput +import android.os.ParcelFileDescriptor +import android.os.ParcelFileDescriptor.MODE_APPEND +import android.os.ParcelFileDescriptor.MODE_READ_ONLY +import android.os.ParcelFileDescriptor.MODE_WRITE_ONLY +import androidx.collection.MutableScatterMap +import androidx.test.ext.junit.runners.AndroidJUnit4 +import com.google.common.truth.Truth.assertThat +import java.io.DataOutputStream +import java.io.File +import java.io.FileDescriptor +import java.io.FileOutputStream +import java.io.InputStream +import java.io.OutputStream +import kotlin.random.Random +import org.junit.Rule +import org.junit.Test +import org.junit.rules.TemporaryFolder +import org.junit.runner.RunWith +import org.mockito.kotlin.any +import org.mockito.kotlin.argumentCaptor +import org.mockito.kotlin.doReturn +import org.mockito.kotlin.doThrow +import org.mockito.kotlin.mock +import org.mockito.kotlin.never +import org.mockito.kotlin.reset +import org.mockito.kotlin.spy +import org.mockito.kotlin.verify + +/** Tests of [BackupRestoreStorage]. */ +@RunWith(AndroidJUnit4::class) +class BackupRestoreStorageTest { + @get:Rule val temporaryFolder = TemporaryFolder() + + private val entity1 = Entity("key1", "value1".toByteArray()) + private val entity1NoOpCodec = Entity("key1", "value1".toByteArray(), BackupNoOpCodec()) + private val entity2 = Entity("key2", "value2".toByteArray(), BackupZipCodec.BEST_SPEED) + + @Test + fun performBackup_disabled() { + val storage = spy(TestStorage().apply { enabled = false }) + val unused = performBackup { data, newState -> storage.performBackup(null, data, newState) } + verify(storage, never()).createBackupRestoreEntities() + assertThat(storage.entities).isNull() + assertThat(storage.entityStates.size).isEqualTo(0) + } + + @Test + fun performBackup_enabled() { + val storage = spy(TestStorage()) + val unused = performBackup { data, newState -> storage.performBackup(null, data, newState) } + verify(storage).createBackupRestoreEntities() + assertThat(storage.entities).isNull() + assertThat(storage.entityStates.size).isEqualTo(0) + } + + @Test + fun performBackup_entityBackupWithException() { + val entity = + mock<BackupRestoreEntity> { + on { key } doReturn "" + on { backup(any(), any()) } doThrow IllegalStateException() + } + val storage = TestStorage(entity, entity1) + + val (_, stateFile) = + performBackup { data, newState -> storage.performBackup(null, data, newState) } + + assertThat(storage.readEntityStates(stateFile)).apply { + hasSize(1) + containsKey(entity1.key) + } + } + + @Test + fun performBackup_update_unchanged() { + performBackupTest({}) { entityStates, newEntityStates -> + assertThat(entityStates).isEqualTo(newEntityStates) + } + } + + @Test + fun performBackup_intact() { + performBackupTest({ entity1.backupResult = EntityBackupResult.INTACT }) { + entityStates, + newEntityStates -> + assertThat(entityStates).isEqualTo(newEntityStates) + } + } + + @Test + fun performBackup_delete() { + performBackupTest({ entity1.backupResult = EntityBackupResult.DELETE }) { _, newEntityStates + -> + assertThat(newEntityStates.size).isEqualTo(1) + assertThat(newEntityStates).containsKey(entity2.key) + } + } + + private fun performBackupTest( + update: () -> Unit, + verification: (Map<String, Long>, Map<String, Long>) -> Unit, + ) { + val storage = TestStorage(entity1, entity2) + val (_, stateFile) = + performBackup { data, newState -> storage.performBackup(null, data, newState) } + + val entityStates = storage.readEntityStates(stateFile) + assertThat(entityStates).apply { + hasSize(2) + containsKey(entity1.key) + containsKey(entity2.key) + } + + update.invoke() + val (_, newStateFile) = + performBackup { data, newState -> + stateFile.toParcelFileDescriptor(MODE_READ_ONLY).use { + storage.performBackup(it, data, newState) + } + } + verification.invoke(entityStates, storage.readEntityStates(newStateFile)) + } + + @Test + fun restoreEntity_disabled() { + val storage = spy(TestStorage().apply { enabled = false }) + temporaryFolder.newFile().toParcelFileDescriptor(MODE_READ_ONLY).use { + storage.restoreEntity(it.toBackupDataInputStream()) + } + verify(storage, never()).createBackupRestoreEntities() + assertThat(storage.entities).isNull() + assertThat(storage.entityStates.size).isEqualTo(0) + } + + @Test + fun restoreEntity_entityNotFound() { + val storage = TestStorage() + temporaryFolder.newFile().toParcelFileDescriptor(MODE_READ_ONLY).use { + val backupDataInputStream = it.toBackupDataInputStream() + backupDataInputStream.setKey("") + storage.restoreEntity(backupDataInputStream) + } + } + + @Test + fun restoreEntity_exception() { + val storage = TestStorage(entity1) + temporaryFolder.newFile().toParcelFileDescriptor(MODE_READ_ONLY).use { + val backupDataInputStream = it.toBackupDataInputStream() + backupDataInputStream.setKey(entity1.key) + storage.restoreEntity(backupDataInputStream) + } + } + + @Test + fun restoreEntity_codecChanged() { + assertThat(entity1.codec()).isNotEqualTo(entity1NoOpCodec.codec()) + backupAndRestore(entity1) { _, data -> + TestStorage(entity1NoOpCodec).apply { restoreEntity(data) } + } + assertThat(entity1.data).isEqualTo(entity1NoOpCodec.restoredData) + } + + @Test + fun restoreEntity() { + val random = Random.Default + fun test(codec: BackupCodec, size: Int) { + val entity = Entity("key", random.nextBytes(size), codec) + backupAndRestore(entity) + entity.verifyRestoredData() + } + for (codec in allCodecs()) { + // test small data to ensure correctness + for (size in 0 until 100) test(codec, size) + repeat(10) { test(codec, random.nextInt(100, MAX_DATA_SIZE)) } + } + } + + @Test + fun readEntityStates_eof_exception() { + val storage = TestStorage() + val entityStates = MutableScatterMap<String, Long>() + entityStates.put("", 0) // add an item to verify that exiting elements are clear + temporaryFolder.newFile().toParcelFileDescriptor(MODE_READ_ONLY).use { + storage.readEntityStates(it, entityStates) + } + assertThat(entityStates.size).isEqualTo(0) + } + + @Test + fun readEntityStates_other_exception() { + val storage = TestStorage() + val entityStates = MutableScatterMap<String, Long>() + entityStates.put("", 0) // add an item to verify that exiting elements are clear + temporaryFolder.newFile().toParcelFileDescriptor(MODE_READ_ONLY).apply { + close() // cause exception when read state file + storage.readEntityStates(this, entityStates) + } + assertThat(entityStates.size).isEqualTo(0) + } + + @Test + fun readEntityStates_unknownVersion() { + val storage = TestStorage() + val stateFile = temporaryFolder.newFile() + stateFile.toParcelFileDescriptor(MODE_WRITE_ONLY or MODE_APPEND).use { + DataOutputStream(FileOutputStream(it.fileDescriptor)) + .writeByte(BackupRestoreStorage.STATE_VERSION + 1) + } + val entityStates = MutableScatterMap<String, Long>() + entityStates.put("", 0) // add an item to verify that exiting elements are clear + stateFile.toParcelFileDescriptor(MODE_READ_ONLY).use { + storage.readEntityStates(it, entityStates) + } + assertThat(entityStates.size).isEqualTo(0) + } + + @Test + fun writeNewStateDescription() { + val storage = spy(TestStorage()) + // use read only mode to trigger exception when write state file + temporaryFolder.newFile().toParcelFileDescriptor(MODE_READ_ONLY).use { + storage.writeNewStateDescription(it) + } + verify(storage).onRestoreFinished() + } + + @Test + fun backupAndRestore() { + val storage = spy(TestStorage(entity1, entity2)) + val backupAgentHelper = BackupAgentHelper() + backupAgentHelper.addHelper(storage.name, storage) + + // backup + val (dataFile, stateFile) = + performBackup { data, newState -> backupAgentHelper.onBackup(null, data, newState) } + storage.verifyFieldsArePurged() + + // verify state + val entityStates = MutableScatterMap<String, Long>() + entityStates[""] = 1 + storage.readEntityStates(null, entityStates) + assertThat(entityStates.size).isEqualTo(0) + stateFile.toParcelFileDescriptor(MODE_READ_ONLY).use { + storage.readEntityStates(it, entityStates) + } + assertThat(entityStates.asMap()).apply { + hasSize(2) + containsKey(entity1.key) + containsKey(entity2.key) + } + reset(storage) + + // restore + val newStateFile = temporaryFolder.newFile() + dataFile.toParcelFileDescriptor(MODE_READ_ONLY).use { dataPfd -> + newStateFile.toParcelFileDescriptor(MODE_WRITE_ONLY or MODE_APPEND).use { + backupAgentHelper.onRestore(dataPfd.toBackupDataInput(), 0, it) + } + } + verify(storage).onRestoreFinished() + storage.verifyFieldsArePurged() + + // ShadowBackupDataOutput does not write data to file, so restore is bypassed + if (!isRobolectric()) { + entity1.verifyRestoredData() + entity2.verifyRestoredData() + assertThat(entityStates.asMap()).isEqualTo(storage.readEntityStates(newStateFile)) + } + } + + private fun backupAndRestore( + entity: BackupRestoreEntity, + restoreEntity: (TestStorage, BackupDataInputStream) -> TestStorage = { storage, data -> + storage.restoreEntity(data) + storage + }, + ) { + val storage = TestStorage(entity) + val entityKey = argumentCaptor<String>() + val entitySize = argumentCaptor<Int>() + val entityData = argumentCaptor<ByteArray>() + val data = mock<BackupDataOutput>() + + val stateFile = temporaryFolder.newFile() + stateFile.toParcelFileDescriptor(MODE_WRITE_ONLY or MODE_APPEND).use { + storage.performBackup(null, data, it) + } + val entityStates = MutableScatterMap<String, Long>() + stateFile.toParcelFileDescriptor(MODE_READ_ONLY).use { + storage.readEntityStates(it, entityStates) + } + assertThat(entityStates.size).isEqualTo(1) + + verify(data).writeEntityHeader(entityKey.capture(), entitySize.capture()) + verify(data).writeEntityData(entityData.capture(), entitySize.capture()) + assertThat(entityKey.allValues).isEqualTo(listOf(entity.key)) + assertThat(entityData.allValues).hasSize(1) + val payload = entityData.firstValue + assertThat(entitySize.allValues).isEqualTo(listOf(payload.size, payload.size)) + + val dataFile = temporaryFolder.newFile() + dataFile.toParcelFileDescriptor(MODE_WRITE_ONLY or MODE_APPEND).use { + FileOutputStream(it.fileDescriptor).write(payload) + } + + newBackupDataInputStream(entity.key, payload).apply { + restoreEntity.invoke(storage, this).also { + assertThat(it.entityStates).isEqualTo(entityStates) + } + } + } + + fun performBackup(backup: (BackupDataOutput, ParcelFileDescriptor) -> Unit): Pair<File, File> { + val dataFile = temporaryFolder.newFile() + val stateFile = temporaryFolder.newFile() + dataFile.toParcelFileDescriptor(MODE_WRITE_ONLY or MODE_APPEND).use { dataPfd -> + stateFile.toParcelFileDescriptor(MODE_WRITE_ONLY or MODE_APPEND).use { + backup.invoke(dataPfd.toBackupDataOutput(), it) + } + } + return dataFile to stateFile + } + + private fun BackupRestoreStorage.verifyFieldsArePurged() { + assertThat(entities).isNull() + assertThat(entityStates.size).isEqualTo(0) + assertThat(entityStates.capacity).isEqualTo(0) + } + + private fun BackupRestoreStorage.readEntityStates(stateFile: File): Map<String, Long> { + val entityStates = MutableScatterMap<String, Long>() + stateFile.toParcelFileDescriptor(MODE_READ_ONLY).use { readEntityStates(it, entityStates) } + return entityStates.asMap() + } + + private fun File.toParcelFileDescriptor(mode: Int) = ParcelFileDescriptor.open(this, mode) + + private fun ParcelFileDescriptor.toBackupDataOutput() = fileDescriptor.toBackupDataOutput() + + private fun ParcelFileDescriptor.toBackupDataInputStream(): BackupDataInputStream = + BackupDataInputStream::class.java.newInstance(toBackupDataInput()) + + private fun ParcelFileDescriptor.toBackupDataInput() = fileDescriptor.toBackupDataInput() + + private fun FileDescriptor.toBackupDataOutput(): BackupDataOutput = + BackupDataOutput::class.java.newInstance(this) + + private fun FileDescriptor.toBackupDataInput(): BackupDataInput = + BackupDataInput::class.java.newInstance(this) +} + +private open class TestStorage(vararg val backupRestoreEntities: BackupRestoreEntity) : + ObservableBackupRestoreStorage() { + var enabled: Boolean? = null + + override val name + get() = "TestBackup" + + override fun createBackupRestoreEntities() = backupRestoreEntities.toList() + + override fun enableBackup(backupContext: BackupContext) = + enabled ?: super.enableBackup(backupContext) + + override fun enableRestore() = enabled ?: super.enableRestore() +} + +private class Entity( + override val key: String, + val data: ByteArray, + private val codec: BackupCodec? = null, +) : BackupRestoreEntity { + var restoredData: ByteArray? = null + var backupResult = EntityBackupResult.UPDATE + + override fun codec() = codec ?: super.codec() + + override fun backup( + backupContext: BackupContext, + outputStream: OutputStream, + ): EntityBackupResult { + outputStream.write(data) + return backupResult + } + + override fun restore(restoreContext: RestoreContext, inputStream: InputStream) { + restoredData = inputStream.readBytes() + inputStream.close() + } + + fun verifyRestoredData() = assertThat(restoredData).isEqualTo(data) +} diff --git a/packages/SettingsLib/DataStore/tests/src/com/android/settingslib/datastore/KeyedObserverTest.kt b/packages/SettingsLib/DataStore/tests/src/com/android/settingslib/datastore/KeyedObserverTest.kt index b52586c2d8d9..8638b2f20b52 100644 --- a/packages/SettingsLib/DataStore/tests/src/com/android/settingslib/datastore/KeyedObserverTest.kt +++ b/packages/SettingsLib/DataStore/tests/src/com/android/settingslib/datastore/KeyedObserverTest.kt @@ -16,76 +16,58 @@ package com.android.settingslib.datastore +import androidx.test.ext.junit.runners.AndroidJUnit4 import com.google.common.truth.Truth.assertThat -import com.google.common.util.concurrent.MoreExecutors.directExecutor +import com.google.common.util.concurrent.MoreExecutors import java.util.concurrent.Executor import java.util.concurrent.atomic.AtomicInteger import org.junit.Assert -import org.junit.Rule import org.junit.Test import org.junit.runner.RunWith -import org.mockito.Mock -import org.mockito.junit.MockitoJUnit -import org.mockito.junit.MockitoRule -import org.mockito.kotlin.any +import org.mockito.kotlin.mock import org.mockito.kotlin.never import org.mockito.kotlin.reset import org.mockito.kotlin.verify -import org.robolectric.RobolectricTestRunner -@RunWith(RobolectricTestRunner::class) +@RunWith(AndroidJUnit4::class) class KeyedObserverTest { - @get:Rule - val mockitoRule: MockitoRule = MockitoJUnit.rule() + private val observer1 = mock<KeyedObserver<Any?>>() + private val observer2 = mock<KeyedObserver<Any?>>() + private val keyedObserver1 = mock<KeyedObserver<Any>>() + private val keyedObserver2 = mock<KeyedObserver<Any>>() - @Mock - private lateinit var observer1: KeyedObserver<Any?> - - @Mock - private lateinit var observer2: KeyedObserver<Any?> - - @Mock - private lateinit var keyedObserver1: KeyedObserver<Any> - - @Mock - private lateinit var keyedObserver2: KeyedObserver<Any> - - @Mock - private lateinit var key1: Any - - @Mock - private lateinit var key2: Any - - @Mock - private lateinit var executor: Executor + private val key1 = Object() + private val key2 = Object() + private val executor1: Executor = MoreExecutors.directExecutor() + private val executor2: Executor = MoreExecutors.newDirectExecutorService() private val keyedObservable = KeyedDataObservable<Any>() @Test fun addObserver_sameExecutor() { - keyedObservable.addObserver(observer1, executor) - keyedObservable.addObserver(observer1, executor) + keyedObservable.addObserver(observer1, executor1) + keyedObservable.addObserver(observer1, executor1) } @Test fun addObserver_keyedObserver_sameExecutor() { - keyedObservable.addObserver(key1, keyedObserver1, executor) - keyedObservable.addObserver(key1, keyedObserver1, executor) + keyedObservable.addObserver(key1, keyedObserver1, executor1) + keyedObservable.addObserver(key1, keyedObserver1, executor1) } @Test fun addObserver_differentExecutor() { - keyedObservable.addObserver(observer1, executor) + keyedObservable.addObserver(observer1, executor1) Assert.assertThrows(IllegalStateException::class.java) { - keyedObservable.addObserver(observer1, directExecutor()) + keyedObservable.addObserver(observer1, executor2) } } @Test fun addObserver_keyedObserver_differentExecutor() { - keyedObservable.addObserver(key1, keyedObserver1, executor) + keyedObservable.addObserver(key1, keyedObserver1, executor1) Assert.assertThrows(IllegalStateException::class.java) { - keyedObservable.addObserver(key1, keyedObserver1, directExecutor()) + keyedObservable.addObserver(key1, keyedObserver1, executor2) } } @@ -93,7 +75,7 @@ class KeyedObserverTest { fun addObserver_weaklyReferenced() { val counter = AtomicInteger() var observer: KeyedObserver<Any?>? = KeyedObserver { _, _ -> counter.incrementAndGet() } - keyedObservable.addObserver(observer!!, directExecutor()) + keyedObservable.addObserver(observer!!, executor1) keyedObservable.notifyChange(ChangeReason.UPDATE) assertThat(counter.get()).isEqualTo(1) @@ -111,7 +93,7 @@ class KeyedObserverTest { fun addObserver_keyedObserver_weaklyReferenced() { val counter = AtomicInteger() var keyObserver: KeyedObserver<Any>? = KeyedObserver { _, _ -> counter.incrementAndGet() } - keyedObservable.addObserver(key1, keyObserver!!, directExecutor()) + keyedObservable.addObserver(key1, keyObserver!!, executor1) keyedObservable.notifyChange(key1, ChangeReason.UPDATE) assertThat(counter.get()).isEqualTo(1) @@ -127,45 +109,43 @@ class KeyedObserverTest { @Test fun addObserver_notifyObservers_removeObserver() { - keyedObservable.addObserver(observer1, directExecutor()) - keyedObservable.addObserver(observer2, executor) + keyedObservable.addObserver(observer1, executor1) + keyedObservable.addObserver(observer2, executor2) keyedObservable.notifyChange(ChangeReason.UPDATE) verify(observer1).onKeyChanged(null, ChangeReason.UPDATE) - verify(observer2, never()).onKeyChanged(any(), any()) - verify(executor).execute(any()) + verify(observer2).onKeyChanged(null, ChangeReason.UPDATE) - reset(observer1, executor) + reset(observer1, observer2) keyedObservable.removeObserver(observer2) keyedObservable.notifyChange(ChangeReason.DELETE) verify(observer1).onKeyChanged(null, ChangeReason.DELETE) - verify(executor, never()).execute(any()) + verify(observer2, never()).onKeyChanged(null, ChangeReason.DELETE) } @Test fun addObserver_keyedObserver_notifyObservers_removeObserver() { - keyedObservable.addObserver(key1, keyedObserver1, directExecutor()) - keyedObservable.addObserver(key2, keyedObserver2, executor) + keyedObservable.addObserver(key1, keyedObserver1, executor1) + keyedObservable.addObserver(key2, keyedObserver2, executor2) keyedObservable.notifyChange(key1, ChangeReason.UPDATE) verify(keyedObserver1).onKeyChanged(key1, ChangeReason.UPDATE) - verify(keyedObserver2, never()).onKeyChanged(any(), any()) - verify(executor, never()).execute(any()) + verify(keyedObserver2, never()).onKeyChanged(key2, ChangeReason.UPDATE) - reset(keyedObserver1, executor) - keyedObservable.removeObserver(key2, keyedObserver2) + reset(keyedObserver1, keyedObserver2) + keyedObservable.removeObserver(key1, keyedObserver1) keyedObservable.notifyChange(key1, ChangeReason.DELETE) - verify(keyedObserver1).onKeyChanged(key1, ChangeReason.DELETE) - verify(executor, never()).execute(any()) + verify(keyedObserver1, never()).onKeyChanged(key1, ChangeReason.DELETE) + verify(keyedObserver2, never()).onKeyChanged(key2, ChangeReason.DELETE) } @Test fun notifyChange_addMoreTypeObservers_checkOnKeyChanged() { - keyedObservable.addObserver(observer1, directExecutor()) - keyedObservable.addObserver(key1, keyedObserver1, directExecutor()) - keyedObservable.addObserver(key2, keyedObserver2, directExecutor()) + keyedObservable.addObserver(observer1, executor1) + keyedObservable.addObserver(key1, keyedObserver1, executor1) + keyedObservable.addObserver(key2, keyedObserver2, executor1) keyedObservable.notifyChange(ChangeReason.UPDATE) verify(observer1).onKeyChanged(null, ChangeReason.UPDATE) @@ -191,10 +171,10 @@ class KeyedObserverTest { fun notifyChange_addObserverWithinCallback() { // ConcurrentModificationException is raised if it is not implemented correctly val observer: KeyedObserver<Any?> = KeyedObserver { _, _ -> - keyedObservable.addObserver(observer1, executor) + keyedObservable.addObserver(observer1, executor1) } - keyedObservable.addObserver(observer, directExecutor()) + keyedObservable.addObserver(observer, executor1) keyedObservable.notifyChange(ChangeReason.UPDATE) keyedObservable.removeObserver(observer) @@ -204,12 +184,12 @@ class KeyedObserverTest { fun notifyChange_KeyedObserver_addObserverWithinCallback() { // ConcurrentModificationException is raised if it is not implemented correctly val keyObserver: KeyedObserver<Any?> = KeyedObserver { _, _ -> - keyedObservable.addObserver(key1, keyedObserver1, executor) + keyedObservable.addObserver(key1, keyedObserver1, executor1) } - keyedObservable.addObserver(key1, keyObserver, directExecutor()) + keyedObservable.addObserver(key1, keyObserver, executor1) keyedObservable.notifyChange(key1, ChangeReason.UPDATE) keyedObservable.removeObserver(key1, keyObserver) } -}
\ No newline at end of file +} diff --git a/packages/SettingsLib/DataStore/tests/src/com/android/settingslib/datastore/ObserverTest.kt b/packages/SettingsLib/DataStore/tests/src/com/android/settingslib/datastore/ObserverTest.kt index f0658290beb0..173c2b1d4b81 100644 --- a/packages/SettingsLib/DataStore/tests/src/com/android/settingslib/datastore/ObserverTest.kt +++ b/packages/SettingsLib/DataStore/tests/src/com/android/settingslib/datastore/ObserverTest.kt @@ -22,40 +22,33 @@ import com.google.common.util.concurrent.MoreExecutors import java.util.concurrent.Executor import java.util.concurrent.atomic.AtomicInteger import org.junit.Assert.assertThrows -import org.junit.Rule import org.junit.Test import org.junit.runner.RunWith -import org.mockito.Mock -import org.mockito.junit.MockitoJUnit -import org.mockito.junit.MockitoRule -import org.mockito.kotlin.any +import org.mockito.kotlin.mock import org.mockito.kotlin.never import org.mockito.kotlin.reset import org.mockito.kotlin.verify @RunWith(AndroidJUnit4::class) class ObserverTest { - @get:Rule val mockitoRule: MockitoRule = MockitoJUnit.rule() - - @Mock private lateinit var observer1: Observer - - @Mock private lateinit var observer2: Observer - - @Mock private lateinit var executor: Executor + private val observer1 = mock<Observer>() + private val observer2 = mock<Observer>() + private val executor1: Executor = MoreExecutors.directExecutor() + private val executor2: Executor = MoreExecutors.newDirectExecutorService() private val observable = DataObservable() @Test fun addObserver_sameExecutor() { - observable.addObserver(observer1, executor) - observable.addObserver(observer1, executor) + observable.addObserver(observer1, executor1) + observable.addObserver(observer1, executor1) } @Test fun addObserver_differentExecutor() { - observable.addObserver(observer1, executor) + observable.addObserver(observer1, executor1) assertThrows(IllegalStateException::class.java) { - observable.addObserver(observer1, MoreExecutors.directExecutor()) + observable.addObserver(observer1, executor2) } } @@ -63,7 +56,7 @@ class ObserverTest { fun addObserver_weaklyReferenced() { val counter = AtomicInteger() var observer: Observer? = Observer { counter.incrementAndGet() } - observable.addObserver(observer!!, MoreExecutors.directExecutor()) + observable.addObserver(observer!!, executor1) observable.notifyChange(ChangeReason.UPDATE) assertThat(counter.get()).isEqualTo(1) @@ -79,31 +72,27 @@ class ObserverTest { @Test fun addObserver_notifyObservers_removeObserver() { - observable.addObserver(observer1, MoreExecutors.directExecutor()) - observable.addObserver(observer2, executor) + observable.addObserver(observer1, executor1) + observable.addObserver(observer2, executor2) observable.notifyChange(ChangeReason.DELETE) verify(observer1).onChanged(ChangeReason.DELETE) - verify(observer2, never()).onChanged(any()) - verify(executor).execute(any()) + verify(observer2).onChanged(ChangeReason.DELETE) - reset(observer1, executor) + reset(observer1, observer2) observable.removeObserver(observer2) observable.notifyChange(ChangeReason.UPDATE) verify(observer1).onChanged(ChangeReason.UPDATE) - verify(executor, never()).execute(any()) + verify(observer2, never()).onChanged(ChangeReason.UPDATE) } @Test fun notifyChange_addObserverWithinCallback() { // ConcurrentModificationException is raised if it is not implemented correctly - val observer = Observer { observable.addObserver(observer1, executor) } - observable.addObserver( - observer, - MoreExecutors.directExecutor() - ) + val observer = Observer { observable.addObserver(observer1, executor1) } + observable.addObserver(observer, executor1) observable.notifyChange(ChangeReason.UPDATE) observable.removeObserver(observer) } diff --git a/packages/SettingsLib/DataStore/tests/src/com/android/settingslib/datastore/SharedPreferencesStorageTest.kt b/packages/SettingsLib/DataStore/tests/src/com/android/settingslib/datastore/SharedPreferencesStorageTest.kt new file mode 100644 index 000000000000..fec7d758b893 --- /dev/null +++ b/packages/SettingsLib/DataStore/tests/src/com/android/settingslib/datastore/SharedPreferencesStorageTest.kt @@ -0,0 +1,175 @@ +/* + * 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.settingslib.datastore + +import android.app.Application +import android.content.Context +import android.content.SharedPreferences +import android.os.Build +import androidx.test.core.app.ApplicationProvider +import androidx.test.ext.junit.runners.AndroidJUnit4 +import androidx.test.platform.app.InstrumentationRegistry +import com.google.common.truth.Truth.assertThat +import com.google.common.util.concurrent.MoreExecutors +import java.io.ByteArrayOutputStream +import java.io.File +import java.util.concurrent.Executor +import kotlin.random.Random +import org.junit.After +import org.junit.Test +import org.junit.runner.RunWith +import org.mockito.kotlin.any +import org.mockito.kotlin.doReturn +import org.mockito.kotlin.mock +import org.mockito.kotlin.spy +import org.mockito.kotlin.verify + +/** Tests of [SharedPreferencesStorage]. */ +@RunWith(AndroidJUnit4::class) +class SharedPreferencesStorageTest { + private val random = Random.Default + private val application: Application = ApplicationProvider.getApplicationContext() + private val map = + mapOf( + "boolean" to true, + "float" to random.nextFloat(), + "int" to random.nextInt(), + "long" to random.nextLong(), + "string" to "string", + "set" to setOf("string"), + ) + + @After + fun tearDown() { + application.getSharedPreferences(NAME, MODE).edit().clear().applySync() + } + + @Test + fun constructors() { + val storage1 = SharedPreferencesStorage(application, NAME, MODE) + val storage2 = + SharedPreferencesStorage( + application, + NAME, + application.getSharedPreferences(NAME, MODE), + ) + assertThat(storage1.sharedPreferences).isSameInstanceAs(storage2.sharedPreferences) + } + + @Test + fun observer() { + val observer = mock<KeyedObserver<Any?>>() + val keyedObserver = mock<KeyedObserver<Any>>() + val storage = SharedPreferencesStorage(application, NAME, MODE) + val executor: Executor = MoreExecutors.directExecutor() + storage.addObserver(observer, executor) + storage.addObserver("key", keyedObserver, executor) + + storage.sharedPreferences.edit().putString("key", "string").applySync() + verify(observer).onKeyChanged("key", ChangeReason.UPDATE) + verify(keyedObserver).onKeyChanged("key", ChangeReason.UPDATE) + + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) { + storage.sharedPreferences.edit().clear().applySync() + verify(observer).onKeyChanged(null, ChangeReason.DELETE) + verify(keyedObserver).onKeyChanged("key", ChangeReason.DELETE) + } + } + + @Test + fun prepareBackup_commitFailed() { + val editor = mock<SharedPreferences.Editor> { on { commit() } doReturn false } + val storage = + spy(SharedPreferencesStorage(application, NAME, MODE)) { + onGeneric { mergeSharedPreferences(any(), any(), any()) } doReturn editor + } + storage.prepareBackup(File("")) + } + + @Test + fun backupAndRestore() { + fun test(codec: BackupCodec) { + val storage = SharedPreferencesStorage(application, NAME, MODE, codec) + storage.mergeSharedPreferences(storage.sharedPreferences, map, "op").commit() + assertThat(storage.sharedPreferences.all).isEqualTo(map) + + val outputStream = ByteArrayOutputStream() + assertThat(storage.toBackupRestoreEntity().backup(BackupContext(mock()), outputStream)) + .isEqualTo(EntityBackupResult.UPDATE) + val payload = outputStream.toByteArray() + + storage.sharedPreferences.edit().clear().commit() + assertThat(storage.sharedPreferences.all).isEmpty() + + BackupRestoreFileArchiver(application, listOf(storage), "archiver") + .restoreEntity(newBackupDataInputStream(storage.storageFilePath, payload)) + assertThat(storage.sharedPreferences.all).isEqualTo(map) + } + + for (codec in allCodecs()) test(codec) + } + + @Test + fun mergeSharedPreferences_filter() { + val storage = + SharedPreferencesStorage(application, NAME, MODE) { key, value -> + key == "float" || value is String + } + storage.mergeSharedPreferences(storage.sharedPreferences, map, "op").apply() + assertThat(storage.sharedPreferences.all) + .containsExactly("float", map["float"], "string", map["string"]) + } + + @Test + fun mergeSharedPreferences_invalidSet() { + val storage = SharedPreferencesStorage(application, NAME, MODE, verbose = true) + storage + .mergeSharedPreferences( + storage.sharedPreferences, + mapOf<String, Any>("set" to setOf(Any())), + "op" + ) + .apply() + assertThat(storage.sharedPreferences.all).isEmpty() + } + + @Test + fun mergeSharedPreferences_unknownType() { + val storage = SharedPreferencesStorage(application, NAME, MODE) + storage + .mergeSharedPreferences(storage.sharedPreferences, map + ("key" to Any()), "op") + .apply() + assertThat(storage.sharedPreferences.all).isEqualTo(map) + } + + @Test + fun mergeSharedPreferences() { + val storage = SharedPreferencesStorage(application, NAME, MODE, verbose = true) + storage.mergeSharedPreferences(storage.sharedPreferences, map, "op").apply() + assertThat(storage.sharedPreferences.all).isEqualTo(map) + } + + private fun SharedPreferences.Editor.applySync() { + apply() + InstrumentationRegistry.getInstrumentation().waitForIdleSync() + } + + companion object { + private const val NAME = "pref" + private const val MODE = Context.MODE_PRIVATE + } +} diff --git a/packages/SettingsLib/DataStore/tests/src/com/android/settingslib/datastore/TestUtils.kt b/packages/SettingsLib/DataStore/tests/src/com/android/settingslib/datastore/TestUtils.kt new file mode 100644 index 000000000000..823d222b2305 --- /dev/null +++ b/packages/SettingsLib/DataStore/tests/src/com/android/settingslib/datastore/TestUtils.kt @@ -0,0 +1,83 @@ +/* + * 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.settingslib.datastore + +import android.app.backup.BackupDataInput +import android.app.backup.BackupDataInputStream +import android.os.Build +import java.io.ByteArrayInputStream +import org.mockito.kotlin.any +import org.mockito.kotlin.doAnswer +import org.mockito.kotlin.mock + +internal const val MAX_DATA_SIZE = 1 shl 12 + +internal fun allCodecs() = + arrayOf<BackupCodec>( + BackupNoOpCodec(), + ) + zipCodecs() + +internal fun zipCodecs() = + arrayOf<BackupCodec>( + BackupZipCodec.DEFAULT_COMPRESSION, + BackupZipCodec.BEST_COMPRESSION, + BackupZipCodec.BEST_SPEED, + ) + +internal fun <T : Any> Class<T>.newInstance(arg: Any, type: Class<*> = arg.javaClass): T = + getDeclaredConstructor(type).apply { isAccessible = true }.newInstance(arg) + +internal fun newBackupDataInputStream( + key: String, + data: ByteArray, + e: Exception? = null, +): BackupDataInputStream { + // ShadowBackupDataOutput does not write data to file, so mock for reading data + val inputStream = ByteArrayInputStream(data) + val backupDataInput = + mock<BackupDataInput> { + on { readEntityData(any(), any(), any()) } doAnswer + { + if (e != null) throw e + val buf = it.arguments[0] as ByteArray + val offset = it.arguments[1] as Int + val size = it.arguments[2] as Int + inputStream.read(buf, offset, size) + } + } + return BackupDataInputStream::class + .java + .newInstance(backupDataInput, BackupDataInput::class.java) + .apply { + setKey(key) + setDataSize(data.size) + } +} + +internal fun BackupDataInputStream.setKey(value: Any) { + val field = javaClass.getDeclaredField("key") + field.isAccessible = true + field.set(this, value) +} + +internal fun BackupDataInputStream.setDataSize(dataSize: Int) { + val field = javaClass.getDeclaredField("dataSize") + field.isAccessible = true + field.setInt(this, dataSize) +} + +internal fun isRobolectric() = Build.FINGERPRINT.contains("robolectric") diff --git a/packages/SettingsLib/aconfig/OWNERS b/packages/SettingsLib/aconfig/OWNERS new file mode 100644 index 000000000000..ba02d20b1ae4 --- /dev/null +++ b/packages/SettingsLib/aconfig/OWNERS @@ -0,0 +1,2 @@ +# go/android-fwk-media-solutions for info on areas of ownership. +per-file settingslib_media_flag_declarations.aconfig = file:platform/frameworks/av:/media/janitors/media_solutions_OWNERS diff --git a/packages/SettingsLib/aconfig/settingslib_media_flag_declarations.aconfig b/packages/SettingsLib/aconfig/settingslib_media_flag_declarations.aconfig index 4d70aec9fa5c..7aae1a6b4a59 100644 --- a/packages/SettingsLib/aconfig/settingslib_media_flag_declarations.aconfig +++ b/packages/SettingsLib/aconfig/settingslib_media_flag_declarations.aconfig @@ -21,3 +21,13 @@ flag { description: "Enable Output Switcher when no media is playing." bug: "284227163" } + +flag { + name: "remove_unnecessary_route_scanning" + namespace: "media_solutions" + description: "Avoid active scan requests on UI components that only display route status information." + bug: "332515672" + metadata { + purpose: PURPOSE_BUGFIX + } +} diff --git a/packages/SettingsLib/src/com/android/settingslib/media/InfoMediaManager.java b/packages/SettingsLib/src/com/android/settingslib/media/InfoMediaManager.java index 5403fe912afb..eae58adb5381 100644 --- a/packages/SettingsLib/src/com/android/settingslib/media/InfoMediaManager.java +++ b/packages/SettingsLib/src/com/android/settingslib/media/InfoMediaManager.java @@ -194,11 +194,7 @@ public abstract class InfoMediaManager { } public void startScan() { - mMediaDevices.clear(); - registerRouter(); startScanOnRouter(); - updateRouteListingPreference(); - refreshDevices(); } private void updateRouteListingPreference() { @@ -212,7 +208,6 @@ public abstract class InfoMediaManager { public final void stopScan() { stopScanOnRouter(); - unregisterRouter(); } protected abstract void stopScanOnRouter(); @@ -299,14 +294,37 @@ public abstract class InfoMediaManager { return null; } - protected final void registerCallback(MediaDeviceCallback callback) { + /** + * Registers the specified {@code callback} to receive state updates about routing information. + * + * <p>As long as there is a registered {@link MediaDeviceCallback}, {@link InfoMediaManager} + * will receive state updates from the platform. + * + * <p>Call {@link #unregisterCallback(MediaDeviceCallback)} once you no longer need platform + * updates. + */ + public final void registerCallback(@NonNull MediaDeviceCallback callback) { + boolean wasEmpty = mCallbacks.isEmpty(); if (!mCallbacks.contains(callback)) { mCallbacks.add(callback); + if (wasEmpty) { + mMediaDevices.clear(); + registerRouter(); + updateRouteListingPreference(); + refreshDevices(); + } } } - protected final void unregisterCallback(MediaDeviceCallback callback) { - mCallbacks.remove(callback); + /** + * Unregisters the specified {@code callback}. + * + * @see #registerCallback(MediaDeviceCallback) + */ + public final void unregisterCallback(@NonNull MediaDeviceCallback callback) { + if (mCallbacks.remove(callback) && mCallbacks.isEmpty()) { + unregisterRouter(); + } } private void dispatchDeviceListAdded(@NonNull List<MediaDevice> devices) { diff --git a/packages/SettingsLib/src/com/android/settingslib/media/LocalMediaManager.java b/packages/SettingsLib/src/com/android/settingslib/media/LocalMediaManager.java index 0c2414c6a7a8..a08a3dc4f7cc 100644 --- a/packages/SettingsLib/src/com/android/settingslib/media/LocalMediaManager.java +++ b/packages/SettingsLib/src/com/android/settingslib/media/LocalMediaManager.java @@ -17,7 +17,6 @@ package com.android.settingslib.media; import static android.media.MediaRoute2ProviderService.REASON_UNKNOWN_ERROR; -import android.app.Notification; import android.bluetooth.BluetoothAdapter; import android.bluetooth.BluetoothDevice; import android.content.ComponentName; @@ -106,14 +105,23 @@ public class LocalMediaManager implements BluetoothCallback { * Register to start receiving callbacks for MediaDevice events. */ public void registerCallback(DeviceCallback callback) { - mCallbacks.add(callback); + boolean wasEmpty = mCallbacks.isEmpty(); + if (!mCallbacks.contains(callback)) { + mCallbacks.add(callback); + if (wasEmpty) { + mInfoMediaManager.registerCallback(mMediaDeviceCallback); + } + } } /** * Unregister to stop receiving callbacks for MediaDevice events */ public void unregisterCallback(DeviceCallback callback) { - mCallbacks.remove(callback); + if (mCallbacks.remove(callback) && mCallbacks.isEmpty()) { + mInfoMediaManager.unregisterCallback(mMediaDeviceCallback); + unRegisterDeviceAttributeChangeCallback(); + } } /** @@ -125,7 +133,7 @@ public class LocalMediaManager implements BluetoothCallback { * * It will use {@link BluetoothAdapter#getDefaultAdapter()] for setting the bluetooth adapter. */ - public LocalMediaManager(Context context, String packageName, Notification notification) { + public LocalMediaManager(Context context, String packageName) { mContext = context; mPackageName = packageName; mLocalBluetoothManager = @@ -228,10 +236,6 @@ public class LocalMediaManager implements BluetoothCallback { * Start scan connected MediaDevice */ public void startScan() { - synchronized (mMediaDevicesLock) { - mMediaDevices.clear(); - } - mInfoMediaManager.registerCallback(mMediaDeviceCallback); mInfoMediaManager.startScan(); } @@ -281,9 +285,7 @@ public class LocalMediaManager implements BluetoothCallback { * Stop scan MediaDevice */ public void stopScan() { - mInfoMediaManager.unregisterCallback(mMediaDeviceCallback); mInfoMediaManager.stopScan(); - unRegisterDeviceAttributeChangeCallback(); } /** diff --git a/packages/SettingsLib/src/com/android/settingslib/volume/data/repository/LocalMediaRepository.kt b/packages/SettingsLib/src/com/android/settingslib/volume/data/repository/LocalMediaRepository.kt index 724dd51b8fe4..869fb7f4043c 100644 --- a/packages/SettingsLib/src/com/android/settingslib/volume/data/repository/LocalMediaRepository.kt +++ b/packages/SettingsLib/src/com/android/settingslib/volume/data/repository/LocalMediaRepository.kt @@ -17,6 +17,7 @@ package com.android.settingslib.volume.data.repository import com.android.settingslib.media.LocalMediaManager import com.android.settingslib.media.MediaDevice +import com.android.settingslib.media.flags.Flags import com.android.settingslib.volume.shared.AudioManagerEventsReceiver import com.android.settingslib.volume.shared.model.AudioManagerEvent import kotlinx.coroutines.CoroutineScope @@ -69,10 +70,14 @@ class LocalMediaRepositoryImpl( } } localMediaManager.registerCallback(callback) - localMediaManager.startScan() + if (!Flags.removeUnnecessaryRouteScanning()) { + localMediaManager.startScan() + } awaitClose { - localMediaManager.stopScan() + if (!Flags.removeUnnecessaryRouteScanning()) { + localMediaManager.stopScan() + } localMediaManager.unregisterCallback(callback) } } diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/InfoMediaManagerTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/InfoMediaManagerTest.java index a4b87da9f7f6..69faddf48f19 100644 --- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/InfoMediaManagerTest.java +++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/InfoMediaManagerTest.java @@ -35,6 +35,7 @@ import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; @@ -751,35 +752,31 @@ public class InfoMediaManagerTest { @Test public void onTransferred_getAvailableRoutes_shouldAddMediaDevice() { - final List<RoutingSessionInfo> routingSessionInfos = new ArrayList<>(); - final RoutingSessionInfo sessionInfo = mock(RoutingSessionInfo.class); - routingSessionInfos.add(sessionInfo); - final List<String> selectedRoutes = new ArrayList<>(); - selectedRoutes.add(TEST_ID); - when(sessionInfo.getSelectedRoutes()).thenReturn(selectedRoutes); - mShadowRouter2Manager.setRoutingSessions(routingSessionInfos); + mInfoMediaManager.mRouterManager = mRouterManager; + // Since test is running in Robolectric, return a fake session to avoid NPE. + when(mRouterManager.getRoutingSessions(anyString())) + .thenReturn(List.of(TEST_SYSTEM_ROUTING_SESSION)); + when(mRouterManager.getSelectedRoutes(any())) + .thenReturn(List.of(TEST_SELECTED_SYSTEM_ROUTE)); - final MediaRoute2Info info = mock(MediaRoute2Info.class); mInfoMediaManager.registerCallback(mCallback); - when(info.getDeduplicationIds()).thenReturn(Set.of()); - when(info.getId()).thenReturn(TEST_ID); - when(info.getClientPackageName()).thenReturn(TEST_PACKAGE_NAME); - - final List<MediaRoute2Info> routes = new ArrayList<>(); - routes.add(info); - mShadowRouter2Manager.setTransferableRoutes(routes); + MediaDevice mediaDevice = mInfoMediaManager.getCurrentConnectedDevice(); + assertThat(mediaDevice).isNotNull(); + assertThat(mediaDevice.getId()).isEqualTo(TEST_SYSTEM_ROUTE_ID); - final MediaDevice mediaDevice = mInfoMediaManager.findMediaDevice(TEST_ID); - assertThat(mediaDevice).isNull(); + when(mRouterManager.getRoutingSessions(anyString())) + .thenReturn(List.of(TEST_SYSTEM_ROUTING_SESSION, TEST_REMOTE_ROUTING_SESSION)); + when(mRouterManager.getSelectedRoutes(any())).thenReturn(List.of(TEST_REMOTE_ROUTE)); - mInfoMediaManager.mMediaRouterCallback.onTransferred(sessionInfo, sessionInfo); + mInfoMediaManager.mMediaRouterCallback.onTransferred( + TEST_SYSTEM_ROUTING_SESSION, TEST_REMOTE_ROUTING_SESSION); final MediaDevice infoDevice = mInfoMediaManager.mMediaDevices.get(0); - assertThat(infoDevice.getId()).isEqualTo(TEST_ID); + assertThat(infoDevice).isNotNull(); + assertThat(infoDevice.getId()).isEqualTo(TEST_REMOTE_ROUTE.getId()); assertThat(mInfoMediaManager.getCurrentConnectedDevice()).isEqualTo(infoDevice); - assertThat(mInfoMediaManager.mMediaDevices).hasSize(routes.size()); - verify(mCallback).onConnectedDeviceChanged(TEST_ID); + verify(mCallback).onConnectedDeviceChanged(TEST_REMOTE_ROUTE.getId()); } @Test @@ -795,7 +792,8 @@ public class InfoMediaManagerTest { mInfoMediaManager.mMediaRouterCallback.onSessionUpdated(TEST_SYSTEM_ROUTING_SESSION); - verify(mCallback).onDeviceListAdded(any()); + // Expecting 1st call after registerCallback() and 2nd call after onSessionUpdated(). + verify(mCallback, times(2)).onDeviceListAdded(any()); } @Test diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/LocalMediaManagerTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/LocalMediaManagerTest.java index 693b7d0faa79..ddb5419509d7 100644 --- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/LocalMediaManagerTest.java +++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/LocalMediaManagerTest.java @@ -21,6 +21,8 @@ import static android.media.MediaRoute2ProviderService.REASON_UNKNOWN_ERROR; import static com.google.common.truth.Truth.assertThat; import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.clearInvocations; +import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; import static org.mockito.Mockito.spy; @@ -58,6 +60,7 @@ import org.robolectric.annotation.Config; import org.robolectric.shadow.api.Shadow; import java.util.ArrayList; +import java.util.Collections; import java.util.List; @RunWith(RobolectricTestRunner.class) @@ -117,6 +120,13 @@ public class LocalMediaManagerTest { mInfoMediaManager = mock( InfoMediaManager.class, withSettings().useConstructor(mContext, TEST_PACKAGE_NAME, mLocalBluetoothManager)); + doReturn( + List.of( + new RoutingSessionInfo.Builder(TEST_SESSION_ID, TEST_PACKAGE_NAME) + .addSelectedRoute(TEST_DEVICE_ID_1) + .build())) + .when(mInfoMediaManager) + .getRoutingSessionsForPackage(); mInfoMediaDevice1 = spy(new InfoMediaDevice(mContext, mRouteInfo1)); mInfoMediaDevice2 = new InfoMediaDevice(mContext, mRouteInfo2); @@ -124,15 +134,16 @@ public class LocalMediaManagerTest { new LocalMediaManager( mContext, mLocalBluetoothManager, mInfoMediaManager, TEST_PACKAGE_NAME); mLocalMediaManager.mAudioManager = mAudioManager; + mLocalMediaManager.registerCallback(mCallback); + clearInvocations(mCallback); } @Test - public void startScan_mediaDevicesListShouldBeClear() { + public void onDeviceListAdded_shouldClearDeviceList() { final MediaDevice device = mock(MediaDevice.class); mLocalMediaManager.mMediaDevices.add(device); - assertThat(mLocalMediaManager.mMediaDevices).hasSize(1); - mLocalMediaManager.startScan(); + mLocalMediaManager.mMediaDeviceCallback.onDeviceListAdded(Collections.emptyList()); assertThat(mLocalMediaManager.mMediaDevices).isEmpty(); } @@ -147,7 +158,6 @@ public class LocalMediaManagerTest { when(device.getId()).thenReturn(TEST_DEVICE_ID_1); when(currentDevice.getId()).thenReturn(TEST_CURRENT_DEVICE_ID); - mLocalMediaManager.registerCallback(mCallback); assertThat(mLocalMediaManager.connectDevice(device)).isTrue(); verify(mInfoMediaManager).connectToDevice(device); } @@ -158,7 +168,6 @@ public class LocalMediaManagerTest { mLocalMediaManager.mMediaDevices.add(mInfoMediaDevice2); mLocalMediaManager.mCurrentConnectedDevice = mInfoMediaDevice1; - mLocalMediaManager.registerCallback(mCallback); assertThat(mLocalMediaManager.connectDevice(mInfoMediaDevice2)).isTrue(); assertThat(mInfoMediaDevice2.getState()).isEqualTo(LocalMediaManager.MediaDeviceState @@ -171,7 +180,6 @@ public class LocalMediaManagerTest { mLocalMediaManager.mMediaDevices.add(mInfoMediaDevice2); mLocalMediaManager.mCurrentConnectedDevice = mInfoMediaDevice1; - mLocalMediaManager.registerCallback(mCallback); assertThat(mLocalMediaManager.connectDevice(mInfoMediaDevice1)).isFalse(); assertThat(mInfoMediaDevice1.getState()).isNotEqualTo(LocalMediaManager.MediaDeviceState @@ -189,7 +197,6 @@ public class LocalMediaManagerTest { when(cachedDevice.isConnected()).thenReturn(false); when(cachedDevice.isBusy()).thenReturn(false); - mLocalMediaManager.registerCallback(mCallback); assertThat(mLocalMediaManager.connectDevice(device)).isTrue(); verify(cachedDevice).connect(); @@ -252,7 +259,6 @@ public class LocalMediaManagerTest { when(device2.getId()).thenReturn(TEST_DEVICE_ID_2); assertThat(mLocalMediaManager.mMediaDevices).isEmpty(); - mLocalMediaManager.registerCallback(mCallback); mLocalMediaManager.mMediaDeviceCallback.onDeviceListAdded(devices); assertThat(mLocalMediaManager.mMediaDevices).hasSize(2); @@ -274,7 +280,6 @@ public class LocalMediaManagerTest { when(device3.getId()).thenReturn(TEST_DEVICE_ID_3); assertThat(mLocalMediaManager.mMediaDevices).hasSize(1); - mLocalMediaManager.registerCallback(mCallback); mLocalMediaManager.mMediaDeviceCallback.onDeviceListAdded(devices); assertThat(mLocalMediaManager.mMediaDevices).hasSize(2); @@ -292,7 +297,6 @@ public class LocalMediaManagerTest { mLocalMediaManager.mMediaDevices.add(device2); assertThat(mLocalMediaManager.mMediaDevices).hasSize(2); - mLocalMediaManager.registerCallback(mCallback); mLocalMediaManager.mMediaDeviceCallback .onDeviceListRemoved(mLocalMediaManager.mMediaDevices); @@ -302,19 +306,21 @@ public class LocalMediaManagerTest { @Test public void onDeviceListRemoved_phoneDeviceNotLastDeviceAfterRemoveDeviceList_removeList() { - final List<MediaDevice> devices = new ArrayList<>(); final MediaDevice device1 = mock(MediaDevice.class); final MediaDevice device2 = mock(MediaDevice.class); final MediaDevice device3 = mock(MediaDevice.class); - devices.add(device1); - devices.add(device3); + + mLocalMediaManager.mMediaDevices.clear(); mLocalMediaManager.mMediaDevices.add(device1); mLocalMediaManager.mMediaDevices.add(device2); mLocalMediaManager.mMediaDevices.add(device3); assertThat(mLocalMediaManager.mMediaDevices).hasSize(3); - mLocalMediaManager.registerCallback(mCallback); - mLocalMediaManager.mMediaDeviceCallback.onDeviceListRemoved(devices); + + final List<MediaDevice> devicesToRemove = new ArrayList<>(); + devicesToRemove.add(device1); + devicesToRemove.add(device3); + mLocalMediaManager.mMediaDeviceCallback.onDeviceListRemoved(devicesToRemove); assertThat(mLocalMediaManager.mMediaDevices).hasSize(1); verify(mCallback).onDeviceListUpdate(any()); @@ -332,7 +338,6 @@ public class LocalMediaManagerTest { when(device1.getId()).thenReturn(TEST_DEVICE_ID_1); when(device2.getId()).thenReturn(TEST_DEVICE_ID_2); - mLocalMediaManager.registerCallback(mCallback); mLocalMediaManager.mMediaDeviceCallback.onConnectedDeviceChanged(TEST_DEVICE_ID_2); assertThat(mLocalMediaManager.getCurrentConnectedDevice()).isEqualTo(device2); @@ -352,7 +357,6 @@ public class LocalMediaManagerTest { when(device1.getId()).thenReturn(TEST_DEVICE_ID_1); when(device2.getId()).thenReturn(TEST_DEVICE_ID_2); - mLocalMediaManager.registerCallback(mCallback); mLocalMediaManager.mMediaDeviceCallback.onConnectedDeviceChanged(TEST_DEVICE_ID_1); verify(mCallback, never()).onDeviceAttributesChanged(); @@ -366,7 +370,6 @@ public class LocalMediaManagerTest { assertThat(mInfoMediaDevice1.getState()).isEqualTo(LocalMediaManager.MediaDeviceState .STATE_DISCONNECTED); - mLocalMediaManager.registerCallback(mCallback); mLocalMediaManager.mMediaDeviceCallback.onConnectedDeviceChanged(TEST_DEVICE_ID_1); assertThat(mInfoMediaDevice1.getState()).isEqualTo(LocalMediaManager.MediaDeviceState @@ -375,7 +378,6 @@ public class LocalMediaManagerTest { @Test public void onConnectedDeviceChanged_nullConnectedDevice_noException() { - mLocalMediaManager.registerCallback(mCallback); mLocalMediaManager.mMediaDeviceCallback.onConnectedDeviceChanged(TEST_DEVICE_ID_2); } @@ -392,7 +394,6 @@ public class LocalMediaManagerTest { when(cachedDevice.isConnected()).thenReturn(false); when(cachedDevice.isBusy()).thenReturn(false); - mLocalMediaManager.registerCallback(mCallback); mLocalMediaManager.connectDevice(device); mLocalMediaManager.mDeviceAttributeChangeCallback.onDeviceAttributesChanged(); @@ -410,7 +411,6 @@ public class LocalMediaManagerTest { .STATE_CONNECTING); assertThat(mInfoMediaDevice2.getState()).isEqualTo(LocalMediaManager.MediaDeviceState .STATE_CONNECTED); - mLocalMediaManager.registerCallback(mCallback); mLocalMediaManager.mMediaDeviceCallback.onRequestFailed(REASON_UNKNOWN_ERROR); assertThat(mInfoMediaDevice1.getState()).isEqualTo(LocalMediaManager.MediaDeviceState @@ -421,8 +421,6 @@ public class LocalMediaManagerTest { @Test public void onDeviceAttributesChanged_shouldBeCalled() { - mLocalMediaManager.registerCallback(mCallback); - mLocalMediaManager.mDeviceAttributeChangeCallback.onDeviceAttributesChanged(); verify(mCallback).onDeviceAttributesChanged(); @@ -475,7 +473,6 @@ public class LocalMediaManagerTest { when(device1.getDeviceType()).thenReturn(MediaDevice.MediaDeviceType.TYPE_PHONE_DEVICE); assertThat(mLocalMediaManager.mMediaDevices).hasSize(0); - mLocalMediaManager.registerCallback(mCallback); mLocalMediaManager.mMediaDeviceCallback.onDeviceListAdded(devices); assertThat(mLocalMediaManager.mMediaDevices).hasSize(2); @@ -497,7 +494,6 @@ public class LocalMediaManagerTest { when(cachedDevice.isConnected()).thenReturn(false); when(cachedDevice.isBusy()).thenReturn(false); - mLocalMediaManager.registerCallback(mCallback); mLocalMediaManager.connectDevice(device); verify(cachedDevice).connect(); @@ -512,8 +508,6 @@ public class LocalMediaManagerTest { @Test public void onRequestFailed_shouldDispatchOnRequestFailed() { - mLocalMediaManager.registerCallback(mCallback); - mLocalMediaManager.mMediaDeviceCallback.onRequestFailed(1); verify(mCallback).onRequestFailed(1); @@ -532,7 +526,6 @@ public class LocalMediaManagerTest { mShadowBluetoothAdapter = null; assertThat(mLocalMediaManager.mMediaDevices).hasSize(1); - mLocalMediaManager.registerCallback(mCallback); mLocalMediaManager.mMediaDeviceCallback.onDeviceListAdded(devices); assertThat(mLocalMediaManager.mMediaDevices).hasSize(2); diff --git a/packages/SettingsProvider/src/com/android/providers/settings/DeviceConfigService.java b/packages/SettingsProvider/src/com/android/providers/settings/DeviceConfigService.java index ad3eb92b0519..e77cf2fa6543 100644 --- a/packages/SettingsProvider/src/com/android/providers/settings/DeviceConfigService.java +++ b/packages/SettingsProvider/src/com/android/providers/settings/DeviceConfigService.java @@ -531,13 +531,22 @@ public final class DeviceConfigService extends Binder { pw.println(" put NAMESPACE KEY VALUE [default]"); pw.println(" Change the contents of KEY to VALUE for the given NAMESPACE."); pw.println(" {default} to set as the default value."); + pw.println(" override NAMESPACE KEY VALUE"); + pw.println(" Set flag NAMESPACE/KEY to the given VALUE, and ignores " + + "server-updates for"); + pw.println(" this flag. This can still be called even if there is no underlying " + + "value set."); pw.println(" delete NAMESPACE KEY"); pw.println(" Delete the entry for KEY for the given NAMESPACE."); + pw.println(" clear_override NAMESPACE KEY"); + pw.println(" Clear local sticky flag override for KEY in the given NAMESPACE."); pw.println(" list_namespaces [--public]"); pw.println(" Prints the name of all (or just the public) namespaces."); pw.println(" list [NAMESPACE]"); pw.println(" Print all keys and values defined, optionally for the given " + "NAMESPACE."); + pw.println(" list_local_overrides"); + pw.println(" Print all flags that have been overridden."); pw.println(" reset RESET_MODE [NAMESPACE]"); pw.println(" Reset all flag values, optionally for a NAMESPACE, according to " + "RESET_MODE."); @@ -547,8 +556,9 @@ public final class DeviceConfigService extends Binder { + "flags are reset"); pw.println(" set_sync_disabled_for_tests SYNC_DISABLED_MODE"); pw.println(" Modifies bulk property setting behavior for tests. When in one of the" - + " disabled modes this ensures that config isn't overwritten."); - pw.println(" SYNC_DISABLED_MODE is one of:"); + + " disabled modes"); + pw.println(" this ensures that config isn't overwritten. SYNC_DISABLED_MODE is " + + "one of:"); pw.println(" none: Sync is not disabled. A reboot may be required to restart" + " syncing."); pw.println(" persistent: Sync is disabled, this state will survive a reboot."); diff --git a/packages/SystemUI/Android.bp b/packages/SystemUI/Android.bp index 40db52eec81b..c88c3731aa10 100644 --- a/packages/SystemUI/Android.bp +++ b/packages/SystemUI/Android.bp @@ -117,6 +117,7 @@ android_library { "SystemUILogLib", "SystemUIPluginLib", "SystemUISharedLib", + "SystemUI-shared-utils", "SystemUI-statsd", "SettingsLib", "com_android_systemui_flags_lib", @@ -263,6 +264,7 @@ android_library { "SystemUISharedLib", "SystemUICustomizationLib", "SystemUICustomizationTestUtils", + "SystemUI-shared-utils", "SystemUI-statsd", "SettingsLib", "com_android_systemui_flags_lib", diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/fold/ui/composable/UnfoldModifiers.kt b/packages/SystemUI/compose/features/src/com/android/systemui/fold/ui/composable/UnfoldModifiers.kt new file mode 100644 index 000000000000..c2a2696777e5 --- /dev/null +++ b/packages/SystemUI/compose/features/src/com/android/systemui/fold/ui/composable/UnfoldModifiers.kt @@ -0,0 +1,77 @@ +/* + * 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.fold.ui.composable + +import androidx.annotation.FloatRange +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.graphicsLayer +import androidx.compose.ui.platform.LocalLayoutDirection +import androidx.compose.ui.unit.Dp +import androidx.compose.ui.unit.LayoutDirection +import com.android.compose.modifiers.padding +import kotlin.math.roundToInt + +/** + * Applies a translation that feeds off of the unfold transition that's active while the device is + * being folded or unfolded, effectively shifting the element towards the fold hinge. + * + * @param startSide `true` if the affected element is on the start side (left-hand side in + * left-to-right layouts), `false` otherwise. + * @param fullTranslation The maximum translation to apply when the element is the most shifted. The + * modifier will never apply more than this much translation on the element. + * @param unfoldProgress A provider for the amount of progress of the unfold transition. This should + * be sourced from the `UnfoldTransitionInteractor`, ideally through a view-model. + */ +@Composable +fun Modifier.unfoldTranslation( + startSide: Boolean, + fullTranslation: Dp, + @FloatRange(from = 0.0, to = 1.0) unfoldProgress: () -> Float, +): Modifier { + val translateToTheRight = startSide && LocalLayoutDirection.current == LayoutDirection.Ltr + return this.graphicsLayer { + translationX = + fullTranslation.toPx() * + if (translateToTheRight) { + 1 - unfoldProgress() + } else { + unfoldProgress() - 1 + } + } +} + +/** + * Applies horizontal padding that feeds off of the unfold transition that's active while the device + * is being folded or unfolded, effectively "squishing" the element on both sides. + * + * This is horizontal padding so it's applied on both the start and end sides of the element. + * + * @param fullPadding The maximum padding to apply when the element is the most padded. The modifier + * will never apply more than this much horizontal padding on the element. + * @param unfoldProgress A provider for the amount of progress of the unfold transition. This should + * be sourced from the `UnfoldTransitionInteractor`, ideally through a view-model. + */ +@Composable +fun Modifier.unfoldHorizontalPadding( + fullPadding: Dp, + @FloatRange(from = 0.0, to = 1.0) unfoldProgress: () -> Float, +): Modifier { + return this.padding( + horizontal = { (fullPadding.toPx() * (1 - unfoldProgress())).roundToInt() }, + ) +} diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/ShadeScene.kt b/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/ShadeScene.kt index 9bd6f817cff3..01c27a4dcc2a 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/ShadeScene.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/ShadeScene.kt @@ -65,6 +65,8 @@ import com.android.compose.animation.scene.animateSceneFloatAsState import com.android.compose.modifiers.thenIf import com.android.systemui.battery.BatteryMeterViewController import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.fold.ui.composable.unfoldHorizontalPadding +import com.android.systemui.fold.ui.composable.unfoldTranslation import com.android.systemui.media.controls.ui.composable.MediaCarousel import com.android.systemui.media.controls.ui.controller.MediaCarouselController import com.android.systemui.media.controls.ui.controller.MediaHierarchyManager @@ -289,6 +291,7 @@ private fun SceneScope.SplitShade( remember(lifecycleOwner, viewModel) { viewModel.getFooterActionsViewModel(lifecycleOwner) } val tileSquishiness by animateSceneFloatAsState(value = 1f, key = QuickSettings.SharedValues.TilesSquishiness) + val unfoldTransitionProgress by viewModel.unfoldTransitionProgress.collectAsState() val navBarBottomHeight = WindowInsets.navigationBars.asPaddingValues().calculateBottomPadding() val density = LocalDensity.current @@ -337,10 +340,23 @@ private fun SceneScope.SplitShade( modifier = Modifier.padding(horizontal = Shade.Dimensions.HorizontalPadding) .then(brightnessMirrorShowingModifier) + .unfoldHorizontalPadding( + fullPadding = dimensionResource(R.dimen.notification_side_paddings), + ) { + unfoldTransitionProgress + } ) Row(modifier = Modifier.fillMaxWidth().weight(1f)) { - Box(modifier = Modifier.weight(1f)) { + Box( + modifier = + Modifier.weight(1f).unfoldTranslation( + startSide = true, + fullTranslation = dimensionResource(R.dimen.notification_side_paddings), + ) { + unfoldTransitionProgress + }, + ) { BrightnessMirror( viewModel = viewModel.brightnessMirrorViewModel, qsSceneAdapter = viewModel.qsSceneAdapter, @@ -407,7 +423,16 @@ private fun SceneScope.SplitShade( Modifier.weight(1f) .fillMaxHeight() .padding(bottom = navBarBottomHeight) - .then(brightnessMirrorShowingModifier), + .then(brightnessMirrorShowingModifier) + .unfoldTranslation( + startSide = false, + fullTranslation = + dimensionResource( + R.dimen.notification_side_paddings, + ), + ) { + unfoldTransitionProgress + }, ) } } diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/ui/composable/VolumePanelRoot.kt b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/ui/composable/VolumePanelRoot.kt index 910cd5ec107b..1bf541a92070 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/ui/composable/VolumePanelRoot.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/ui/composable/VolumePanelRoot.kt @@ -16,7 +16,7 @@ package com.android.systemui.volume.panel.ui.composable -import androidx.compose.foundation.clickable +import androidx.compose.foundation.gestures.detectTapGestures import androidx.compose.foundation.isSystemInDarkTheme import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box @@ -38,6 +38,7 @@ import androidx.compose.runtime.collectAsState import androidx.compose.runtime.getValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier +import androidx.compose.ui.input.pointer.pointerInput import androidx.compose.ui.platform.LocalDensity import androidx.compose.ui.platform.LocalLayoutDirection import androidx.compose.ui.res.dimensionResource @@ -75,21 +76,13 @@ fun VolumePanelRoot( modifier = modifier .fillMaxSize() - .clickable(onClick = onDismiss) + .volumePanelClick(onDismiss) .volumePanelPaddings(isPortrait = isPortrait), contentAlignment = Alignment.BottomCenter, ) { val radius = dimensionResource(R.dimen.volume_panel_corner_radius) Surface( - modifier = - Modifier.clickable( - interactionSource = null, - indication = null, - onClick = { - // prevent windowCloseOnTouchOutside from dismissing when tapped - // on the panel itself. - }, - ), + modifier = Modifier.volumePanelClick {}, shape = RoundedCornerShape(topStart = radius, topEnd = radius), color = MaterialTheme.colorScheme.surfaceContainer, ) { @@ -185,3 +178,13 @@ private fun Modifier.volumePanelPaddings(isPortrait: Boolean): Modifier { ) } } + +/** + * For some reason adding clickable modifier onto the VolumePanel affects the traversal order: + * b/331155283. + * + * TODO(b/334870995) revert this to Modifier.clickable + */ +@Composable +private fun Modifier.volumePanelClick(onClick: () -> Unit) = + pointerInput(onClick) { detectTapGestures { onClick() } } diff --git a/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.kt index 2d4b63ef2c27..ae9794a3dbda 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.kt @@ -50,6 +50,7 @@ import com.android.systemui.classifier.FalsingCollector import com.android.systemui.deviceentry.domain.interactor.DeviceEntryFaceAuthInteractor import com.android.systemui.deviceentry.domain.interactor.DeviceEntryInteractor import com.android.systemui.deviceentry.domain.interactor.deviceEntryInteractor +import com.android.systemui.flags.EnableSceneContainer import com.android.systemui.flags.FakeFeatureFlags import com.android.systemui.flags.Flags import com.android.systemui.keyboard.data.repository.FakeKeyboardRepository @@ -65,8 +66,6 @@ import com.android.systemui.plugins.FalsingManager import com.android.systemui.res.R import com.android.systemui.scene.domain.interactor.SceneInteractor import com.android.systemui.scene.domain.interactor.sceneInteractor -import com.android.systemui.scene.shared.flag.fakeSceneContainerFlags -import com.android.systemui.scene.shared.flag.sceneContainerFlags import com.android.systemui.scene.shared.model.FakeSceneDataSource import com.android.systemui.scene.shared.model.Scenes import com.android.systemui.scene.shared.model.fakeSceneDataSource @@ -87,7 +86,6 @@ import com.android.systemui.util.mockito.mock import com.android.systemui.util.mockito.whenever import com.android.systemui.util.settings.GlobalSettings import com.google.common.truth.Truth -import dagger.Lazy import java.util.Optional import junit.framework.Assert import kotlinx.coroutines.ExperimentalCoroutinesApi @@ -171,7 +169,7 @@ class KeyguardSecurityContainerControllerTest : SysuiTestCase() { private lateinit var sceneInteractor: SceneInteractor private lateinit var keyguardTransitionInteractor: KeyguardTransitionInteractor private lateinit var deviceEntryInteractor: DeviceEntryInteractor - @Mock private lateinit var primaryBouncerInteractor: Lazy<PrimaryBouncerInteractor> + @Mock private lateinit var primaryBouncerInteractor: PrimaryBouncerInteractor private lateinit var sceneTransitionStateFlow: MutableStateFlow<ObservableTransitionState> private lateinit var fakeSceneDataSource: FakeSceneDataSource @@ -217,9 +215,13 @@ class KeyguardSecurityContainerControllerTest : SysuiTestCase() { ) mSetFlagsRule.disableFlags( FLAG_SIDEFPS_CONTROLLER_REFACTOR, - AConfigFlags.FLAG_KEYGUARD_WM_STATE_REFACTOR, - AConfigFlags.FLAG_REFACTOR_KEYGUARD_DISMISS_INTENT, ) + if (!com.android.systemui.Flags.sceneContainer()) { + mSetFlagsRule.disableFlags( + AConfigFlags.FLAG_KEYGUARD_WM_STATE_REFACTOR, + AConfigFlags.FLAG_REFACTOR_KEYGUARD_DISMISS_INTENT, + ) + } keyguardPasswordViewController = KeyguardPasswordViewController( @@ -268,7 +270,6 @@ class KeyguardSecurityContainerControllerTest : SysuiTestCase() { falsingManager, userSwitcherController, featureFlags, - kosmos.sceneContainerFlags, globalSettings, sessionTracker, Optional.of(sideFpsController), @@ -283,7 +284,7 @@ class KeyguardSecurityContainerControllerTest : SysuiTestCase() { deviceProvisionedController, faceAuthAccessibilityDelegate, keyguardTransitionInteractor, - primaryBouncerInteractor, + { primaryBouncerInteractor }, ) { deviceEntryInteractor } @@ -804,17 +805,17 @@ class KeyguardSecurityContainerControllerTest : SysuiTestCase() { } @Test + @EnableSceneContainer fun dismissesKeyguard_whenSceneChangesToGone() = kosmos.testScope.runTest { - kosmos.fakeSceneContainerFlags.enabled = true // Upon init, we have never dismisses the keyguard. underTest.onInit() runCurrent() - verify(viewMediatorCallback, never()).keyguardDone(anyInt()) + verify(primaryBouncerInteractor, never()) + .notifyKeyguardAuthenticatedPrimaryAuth(anyInt()) // Once the view is attached, we start listening but simply going to the bouncer scene - // is - // not enough to trigger a dismissal of the keyguard. + // is not enough to trigger a dismissal of the keyguard. underTest.onViewAttached() fakeSceneDataSource.pause() sceneInteractor.changeScene(Scenes.Bouncer, "reason") @@ -830,7 +831,8 @@ class KeyguardSecurityContainerControllerTest : SysuiTestCase() { fakeSceneDataSource.unpause(expectedScene = Scenes.Bouncer) sceneTransitionStateFlow.value = ObservableTransitionState.Idle(Scenes.Bouncer) runCurrent() - verify(viewMediatorCallback, never()).keyguardDone(anyInt()) + verify(primaryBouncerInteractor, never()) + .notifyKeyguardAuthenticatedPrimaryAuth(anyInt()) // While listening, going from the bouncer scene to the gone scene, does dismiss the // keyguard. @@ -852,11 +854,11 @@ class KeyguardSecurityContainerControllerTest : SysuiTestCase() { fakeSceneDataSource.unpause(expectedScene = Scenes.Gone) sceneTransitionStateFlow.value = ObservableTransitionState.Idle(Scenes.Gone) runCurrent() - verify(viewMediatorCallback).keyguardDone(anyInt()) + verify(primaryBouncerInteractor).notifyKeyguardAuthenticatedPrimaryAuth(anyInt()) // While listening, moving back to the bouncer scene does not dismiss the keyguard // again. - clearInvocations(viewMediatorCallback) + clearInvocations(primaryBouncerInteractor) fakeSceneDataSource.pause() sceneInteractor.changeScene(Scenes.Bouncer, "reason") sceneTransitionStateFlow.value = @@ -871,7 +873,8 @@ class KeyguardSecurityContainerControllerTest : SysuiTestCase() { fakeSceneDataSource.unpause(expectedScene = Scenes.Bouncer) sceneTransitionStateFlow.value = ObservableTransitionState.Idle(Scenes.Bouncer) runCurrent() - verify(viewMediatorCallback, never()).keyguardDone(anyInt()) + verify(primaryBouncerInteractor, never()) + .notifyKeyguardAuthenticatedPrimaryAuth(anyInt()) // Detaching the view stops listening, so moving from the bouncer scene to the gone // scene @@ -891,7 +894,8 @@ class KeyguardSecurityContainerControllerTest : SysuiTestCase() { fakeSceneDataSource.unpause(expectedScene = Scenes.Gone) sceneTransitionStateFlow.value = ObservableTransitionState.Idle(Scenes.Gone) runCurrent() - verify(viewMediatorCallback, never()).keyguardDone(anyInt()) + verify(primaryBouncerInteractor, never()) + .notifyKeyguardAuthenticatedPrimaryAuth(anyInt()) // While not listening, moving to the lockscreen does not dismiss the keyguard. fakeSceneDataSource.pause() @@ -908,7 +912,8 @@ class KeyguardSecurityContainerControllerTest : SysuiTestCase() { fakeSceneDataSource.unpause(expectedScene = Scenes.Lockscreen) sceneTransitionStateFlow.value = ObservableTransitionState.Idle(Scenes.Lockscreen) runCurrent() - verify(viewMediatorCallback, never()).keyguardDone(anyInt()) + verify(primaryBouncerInteractor, never()) + .notifyKeyguardAuthenticatedPrimaryAuth(anyInt()) // Reattaching the view starts listening again so moving from the bouncer scene to the // gone scene now does dismiss the keyguard again, this time from lockscreen. @@ -927,7 +932,7 @@ class KeyguardSecurityContainerControllerTest : SysuiTestCase() { fakeSceneDataSource.unpause(expectedScene = Scenes.Gone) sceneTransitionStateFlow.value = ObservableTransitionState.Idle(Scenes.Gone) runCurrent() - verify(viewMediatorCallback).keyguardDone(anyInt()) + verify(primaryBouncerInteractor).notifyKeyguardAuthenticatedPrimaryAuth(anyInt()) } @Test diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/authentication/data/repository/AuthenticationRepositoryTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/authentication/data/repository/AuthenticationRepositoryTest.kt index caf92199737c..1cd9d76a189e 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/authentication/data/repository/AuthenticationRepositoryTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/authentication/data/repository/AuthenticationRepositoryTest.kt @@ -32,7 +32,6 @@ import com.android.systemui.coroutines.collectValues import com.android.systemui.kosmos.testDispatcher import com.android.systemui.kosmos.testScope import com.android.systemui.log.table.TableLogBuffer -import com.android.systemui.scene.shared.flag.sceneContainerFlags import com.android.systemui.statusbar.pipeline.mobile.data.repository.FakeMobileConnectionsRepository import com.android.systemui.statusbar.pipeline.mobile.util.FakeMobileMappingsProxy import com.android.systemui.testKosmos @@ -86,7 +85,6 @@ class AuthenticationRepositoryTest : SysuiTestCase() { AuthenticationRepositoryImpl( applicationScope = testScope.backgroundScope, backgroundDispatcher = kosmos.testDispatcher, - flags = kosmos.sceneContainerFlags, clock = clock, getSecurityMode = getSecurityMode, userRepository = userRepository, diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/UdfpsControllerOverlayTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/UdfpsControllerOverlayTest.kt index 85774c67bccb..60b48f28fc23 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/UdfpsControllerOverlayTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/UdfpsControllerOverlayTest.kt @@ -571,7 +571,7 @@ class UdfpsControllerOverlayTest : SysuiTestCase() { // THEN the view layout is never updated verify(windowManager, never()).updateViewLayout(any(), any()) - // CLEANUPL we hide to end the job that listens for the finishedGoingToSleep signal + // CLEANUP we hide to end the job that listens for the finishedGoingToSleep signal controllerOverlay.hide() } } @@ -595,7 +595,7 @@ class UdfpsControllerOverlayTest : SysuiTestCase() { controllerOverlay.updateOverlayParams(overlayParams) // THEN the view layout is updated - verify(windowManager, never()).updateViewLayout(any(), any()) + verify(windowManager).updateViewLayout(any(), any()) } } } diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/domain/interactor/BouncerActionButtonInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/domain/interactor/BouncerActionButtonInteractorTest.kt index 741cde82354a..d850f17cd89a 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/domain/interactor/BouncerActionButtonInteractorTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/domain/interactor/BouncerActionButtonInteractorTest.kt @@ -29,10 +29,10 @@ import com.android.systemui.authentication.data.repository.fakeAuthenticationRep import com.android.systemui.authentication.shared.model.AuthenticationMethodModel import com.android.systemui.common.ui.data.repository.fakeConfigurationRepository import com.android.systemui.coroutines.collectLastValue +import com.android.systemui.flags.EnableSceneContainer import com.android.systemui.flags.Flags.REFACTOR_GETCURRENTUSER import com.android.systemui.flags.fakeFeatureFlagsClassic import com.android.systemui.kosmos.testScope -import com.android.systemui.scene.shared.flag.fakeSceneContainerFlags import com.android.systemui.statusbar.pipeline.mobile.data.repository.FakeMobileConnectionsRepository import com.android.systemui.statusbar.pipeline.mobile.data.repository.fakeMobileConnectionsRepository import com.android.systemui.telephony.data.repository.fakeTelephonyRepository @@ -56,6 +56,7 @@ import org.mockito.MockitoAnnotations @OptIn(ExperimentalCoroutinesApi::class) @SmallTest @RunWith(AndroidJUnit4::class) +@EnableSceneContainer class BouncerActionButtonInteractorTest : SysuiTestCase() { @Mock private lateinit var selectedUserInteractor: SelectedUserInteractor @@ -75,7 +76,6 @@ class BouncerActionButtonInteractorTest : SysuiTestCase() { @Before fun setUp() { MockitoAnnotations.initMocks(this) - kosmos.fakeSceneContainerFlags.enabled = true mobileConnectionsRepository = kosmos.fakeMobileConnectionsRepository diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/domain/interactor/BouncerInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/domain/interactor/BouncerInteractorTest.kt index cbdb71bf9040..361b078ebd30 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/domain/interactor/BouncerInteractorTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/domain/interactor/BouncerInteractorTest.kt @@ -30,11 +30,11 @@ import com.android.systemui.bouncer.shared.logging.BouncerUiEvent import com.android.systemui.coroutines.collectLastValue import com.android.systemui.coroutines.collectValues import com.android.systemui.deviceentry.domain.interactor.deviceEntryFaceAuthInteractor +import com.android.systemui.flags.EnableSceneContainer import com.android.systemui.keyguard.data.repository.fakeDeviceEntryFaceAuthRepository import com.android.systemui.kosmos.testScope import com.android.systemui.power.data.repository.fakePowerRepository import com.android.systemui.res.R -import com.android.systemui.scene.shared.flag.fakeSceneContainerFlags import com.android.systemui.testKosmos import com.google.common.truth.Truth.assertThat import kotlin.time.Duration.Companion.seconds @@ -50,9 +50,10 @@ import org.mockito.MockitoAnnotations @OptIn(ExperimentalCoroutinesApi::class) @SmallTest @RunWith(AndroidJUnit4::class) +@EnableSceneContainer class BouncerInteractorTest : SysuiTestCase() { - private val kosmos = testKosmos().apply { fakeSceneContainerFlags.enabled = true } + private val kosmos = testKosmos() private val testScope = kosmos.testScope private val authenticationInteractor = kosmos.authenticationInteractor private val uiEventLoggerFake = kosmos.uiEventLoggerFake diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/BouncerViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/BouncerViewModelTest.kt index 0db0e0767767..b83c0ce702a7 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/BouncerViewModelTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/BouncerViewModelTest.kt @@ -34,10 +34,10 @@ import com.android.systemui.authentication.shared.model.AuthenticationMethodMode import com.android.systemui.authentication.shared.model.AuthenticationMethodModel.Sim import com.android.systemui.bouncer.domain.interactor.bouncerInteractor import com.android.systemui.coroutines.collectLastValue +import com.android.systemui.flags.EnableSceneContainer import com.android.systemui.flags.Flags import com.android.systemui.flags.fakeFeatureFlagsClassic import com.android.systemui.kosmos.testScope -import com.android.systemui.scene.shared.flag.fakeSceneContainerFlags import com.android.systemui.scene.shared.model.Scenes import com.android.systemui.scene.shared.model.fakeSceneDataSource import com.android.systemui.testKosmos @@ -60,6 +60,7 @@ import org.junit.runner.RunWith @OptIn(ExperimentalCoroutinesApi::class) @SmallTest @RunWith(AndroidJUnit4::class) +@EnableSceneContainer class BouncerViewModelTest : SysuiTestCase() { private val kosmos = testKosmos() @@ -70,7 +71,6 @@ class BouncerViewModelTest : SysuiTestCase() { @Before fun setUp() { - kosmos.fakeSceneContainerFlags.enabled = true underTest = kosmos.bouncerViewModel } diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalInteractorTest.kt index f21e9697c91f..497180b60151 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalInteractorTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalInteractorTest.kt @@ -52,6 +52,7 @@ import com.android.systemui.communal.shared.model.CommunalScenes import com.android.systemui.communal.shared.model.CommunalWidgetContentModel import com.android.systemui.communal.widgets.EditWidgetsActivityStarter import com.android.systemui.coroutines.collectLastValue +import com.android.systemui.flags.EnableSceneContainer import com.android.systemui.flags.Flags import com.android.systemui.flags.fakeFeatureFlagsClassic import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository @@ -61,7 +62,6 @@ import com.android.systemui.plugins.ActivityStarter import com.android.systemui.plugins.activityStarter import com.android.systemui.scene.domain.interactor.SceneInteractor import com.android.systemui.scene.domain.interactor.sceneInteractor -import com.android.systemui.scene.shared.flag.fakeSceneContainerFlags import com.android.systemui.scene.shared.model.Scenes import com.android.systemui.settings.FakeUserTracker import com.android.systemui.settings.fakeUserTracker @@ -698,10 +698,9 @@ class CommunalInteractorTest : SysuiTestCase() { } @Test + @EnableSceneContainer fun isCommunalShowing_whenSceneContainerEnabled() = testScope.runTest { - kosmos.fakeSceneContainerFlags.enabled = true - // Verify default is false val isCommunalShowing by collectLastValue(underTest.isCommunalShowing) runCurrent() diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryInteractorTest.kt index 5caf35ba90d4..37a6ac6adac7 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryInteractorTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryInteractorTest.kt @@ -38,6 +38,7 @@ import com.android.systemui.deviceentry.shared.model.DeviceEntryRestrictionReaso import com.android.systemui.deviceentry.shared.model.DeviceEntryRestrictionReason.TrustAgentDisabled import com.android.systemui.deviceentry.shared.model.DeviceEntryRestrictionReason.UnattendedUpdate import com.android.systemui.deviceentry.shared.model.DeviceEntryRestrictionReason.UserLockdown +import com.android.systemui.flags.EnableSceneContainer import com.android.systemui.flags.fakeSystemPropertiesHelper import com.android.systemui.keyguard.data.repository.fakeBiometricSettingsRepository import com.android.systemui.keyguard.data.repository.fakeDeviceEntryFaceAuthRepository @@ -47,7 +48,6 @@ import com.android.systemui.keyguard.shared.model.AuthenticationFlags import com.android.systemui.keyguard.shared.model.SuccessFingerprintAuthenticationStatus import com.android.systemui.kosmos.testScope import com.android.systemui.scene.domain.interactor.sceneInteractor -import com.android.systemui.scene.shared.flag.fakeSceneContainerFlags import com.android.systemui.scene.shared.model.Scenes import com.android.systemui.testKosmos import com.google.common.truth.Truth.assertThat @@ -62,6 +62,7 @@ import org.junit.runner.RunWith @OptIn(ExperimentalCoroutinesApi::class) @SmallTest @RunWith(AndroidJUnit4::class) +@EnableSceneContainer class DeviceEntryInteractorTest : SysuiTestCase() { private val kosmos = testKosmos() @@ -74,7 +75,6 @@ class DeviceEntryInteractorTest : SysuiTestCase() { @Before fun setUp() { - kosmos.fakeSceneContainerFlags.enabled = true underTest = kosmos.deviceEntryInteractor } diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractorTest.kt index 1dd5d073bef3..12f891835d0e 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractorTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractorTest.kt @@ -28,6 +28,7 @@ import com.android.systemui.bouncer.data.repository.FakeKeyguardBouncerRepositor import com.android.systemui.common.ui.data.repository.FakeConfigurationRepository import com.android.systemui.common.ui.domain.interactor.ConfigurationInteractor import com.android.systemui.coroutines.collectLastValue +import com.android.systemui.flags.EnableSceneContainer import com.android.systemui.keyguard.data.repository.FakeCommandQueue import com.android.systemui.keyguard.data.repository.fakeKeyguardRepository import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository @@ -37,8 +38,6 @@ import com.android.systemui.keyguard.shared.model.StatusBarState import com.android.systemui.kosmos.testScope import com.android.systemui.power.domain.interactor.PowerInteractorFactory import com.android.systemui.scene.domain.interactor.sceneInteractor -import com.android.systemui.scene.shared.flag.fakeSceneContainerFlags -import com.android.systemui.scene.shared.flag.sceneContainerFlags import com.android.systemui.scene.shared.model.Scenes import com.android.systemui.shade.data.repository.FakeShadeRepository import com.android.systemui.statusbar.notification.stack.domain.interactor.sharedNotificationContainerInteractor @@ -76,7 +75,6 @@ class KeyguardInteractorTest : SysuiTestCase() { repository = repository, commandQueue = commandQueue, powerInteractor = PowerInteractorFactory.create().powerInteractor, - sceneContainerFlags = kosmos.sceneContainerFlags, bouncerRepository = bouncerRepository, configurationInteractor = ConfigurationInteractor(FakeConfigurationRepository()), shadeRepository = shadeRepository, @@ -249,9 +247,9 @@ class KeyguardInteractorTest : SysuiTestCase() { } @Test + @EnableSceneContainer fun animationDozingTransitions() = testScope.runTest { - kosmos.fakeSceneContainerFlags.enabled = true val isAnimate by collectLastValue(underTest.animateDozingTransitions) underTest.setAnimateDozingTransitions(true) diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/SceneFrameworkIntegrationTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/SceneFrameworkIntegrationTest.kt index a277fe03daba..65fd1010ec38 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/SceneFrameworkIntegrationTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/SceneFrameworkIntegrationTest.kt @@ -51,6 +51,7 @@ import com.android.systemui.deviceentry.data.repository.fakeDeviceEntryRepositor import com.android.systemui.deviceentry.domain.interactor.deviceEntryFaceAuthInteractor import com.android.systemui.deviceentry.domain.interactor.deviceEntryInteractor import com.android.systemui.deviceentry.domain.interactor.deviceUnlockedInteractor +import com.android.systemui.flags.EnableSceneContainer import com.android.systemui.flags.Flags import com.android.systemui.flags.fakeFeatureFlagsClassic import com.android.systemui.keyguard.domain.interactor.keyguardInteractor @@ -69,7 +70,6 @@ import com.android.systemui.qs.ui.adapter.FakeQSSceneAdapter import com.android.systemui.scene.domain.interactor.sceneContainerOcclusionInteractor import com.android.systemui.scene.domain.interactor.sceneInteractor import com.android.systemui.scene.domain.startable.SceneContainerStartable -import com.android.systemui.scene.shared.flag.fakeSceneContainerFlags import com.android.systemui.scene.shared.model.Scenes import com.android.systemui.scene.shared.model.fakeSceneDataSource import com.android.systemui.scene.ui.viewmodel.SceneContainerViewModel @@ -85,6 +85,7 @@ import com.android.systemui.statusbar.pipeline.mobile.data.repository.fakeMobile import com.android.systemui.statusbar.policy.domain.interactor.deviceProvisioningInteractor import com.android.systemui.telephony.data.repository.fakeTelephonyRepository import com.android.systemui.testKosmos +import com.android.systemui.unfold.domain.interactor.unfoldTransitionInteractor import com.android.systemui.util.mockito.any import com.android.systemui.util.mockito.mock import com.android.systemui.util.mockito.whenever @@ -128,9 +129,10 @@ import org.mockito.MockitoAnnotations @SmallTest @RunWith(AndroidJUnit4::class) @RunWithLooper +@EnableSceneContainer class SceneFrameworkIntegrationTest : SysuiTestCase() { - private val kosmos = testKosmos().apply { fakeSceneContainerFlags.enabled = true } + private val kosmos = testKosmos() private val testScope = kosmos.testScope private val sceneContainerConfig by lazy { kosmos.sceneContainerConfig } private val sceneInteractor by lazy { kosmos.sceneInteractor } @@ -227,6 +229,7 @@ class SceneFrameworkIntegrationTest : SysuiTestCase() { footerActionsController = kosmos.footerActionsController, footerActionsViewModelFactory = kosmos.footerActionsViewModelFactory, sceneInteractor = sceneInteractor, + unfoldTransitionInteractor = kosmos.unfoldTransitionInteractor, ) val displayTracker = FakeDisplayTracker(context) @@ -236,15 +239,15 @@ class SceneFrameworkIntegrationTest : SysuiTestCase() { applicationScope = testScope.backgroundScope, sceneInteractor = sceneInteractor, deviceEntryInteractor = deviceEntryInteractor, + deviceUnlockedInteractor = kosmos.deviceUnlockedInteractor, + bouncerInteractor = bouncerInteractor, keyguardInteractor = keyguardInteractor, - flags = kosmos.fakeSceneContainerFlags, sysUiState = sysUiState, displayId = displayTracker.defaultDisplayId, sceneLogger = mock(), falsingCollector = kosmos.falsingCollector, falsingManager = kosmos.falsingManager, powerInteractor = powerInteractor, - bouncerInteractor = bouncerInteractor, simBouncerInteractor = dagger.Lazy { kosmos.simBouncerInteractor }, authenticationInteractor = dagger.Lazy { kosmos.authenticationInteractor }, windowController = mock(), @@ -253,7 +256,6 @@ class SceneFrameworkIntegrationTest : SysuiTestCase() { headsUpInteractor = kosmos.headsUpNotificationInteractor, occlusionInteractor = kosmos.sceneContainerOcclusionInteractor, faceUnlockInteractor = kosmos.deviceEntryFaceAuthInteractor, - deviceUnlockedInteractor = kosmos.deviceUnlockedInteractor, shadeInteractor = kosmos.shadeInteractor, ) startable.start() diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/data/repository/SceneContainerRepositoryTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/data/repository/SceneContainerRepositoryTest.kt index 7f7c24e6efa4..8e2eea178708 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/data/repository/SceneContainerRepositoryTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/data/repository/SceneContainerRepositoryTest.kt @@ -23,10 +23,10 @@ import androidx.test.filters.SmallTest import com.android.compose.animation.scene.ObservableTransitionState import com.android.systemui.SysuiTestCase import com.android.systemui.coroutines.collectLastValue +import com.android.systemui.flags.EnableSceneContainer import com.android.systemui.kosmos.testScope import com.android.systemui.scene.sceneContainerConfig import com.android.systemui.scene.sceneKeys -import com.android.systemui.scene.shared.flag.fakeSceneContainerFlags import com.android.systemui.scene.shared.model.Scenes import com.android.systemui.testKosmos import com.google.common.truth.Truth.assertThat @@ -39,9 +39,10 @@ import org.junit.runner.RunWith @SmallTest @RunWith(AndroidJUnit4::class) +@EnableSceneContainer class SceneContainerRepositoryTest : SysuiTestCase() { - private val kosmos = testKosmos().apply { fakeSceneContainerFlags.enabled = true } + private val kosmos = testKosmos() private val testScope = kosmos.testScope @Test diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/interactor/SceneInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/interactor/SceneInteractorTest.kt index b179c30e60b9..63f481695232 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/interactor/SceneInteractorTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/interactor/SceneInteractorTest.kt @@ -23,13 +23,13 @@ import androidx.test.filters.SmallTest import com.android.compose.animation.scene.ObservableTransitionState import com.android.systemui.SysuiTestCase import com.android.systemui.coroutines.collectLastValue +import com.android.systemui.flags.EnableSceneContainer import com.android.systemui.keyguard.data.repository.fakeDeviceEntryFingerprintAuthRepository import com.android.systemui.keyguard.shared.model.SuccessFingerprintAuthenticationStatus import com.android.systemui.kosmos.testScope import com.android.systemui.scene.data.repository.sceneContainerRepository import com.android.systemui.scene.sceneContainerConfig import com.android.systemui.scene.sceneKeys -import com.android.systemui.scene.shared.flag.fakeSceneContainerFlags import com.android.systemui.scene.shared.model.Scenes import com.android.systemui.scene.shared.model.fakeSceneDataSource import com.android.systemui.testKosmos @@ -45,6 +45,7 @@ import org.junit.runner.RunWith @SmallTest @RunWith(AndroidJUnit4::class) +@EnableSceneContainer class SceneInteractorTest : SysuiTestCase() { private val kosmos = testKosmos() @@ -55,7 +56,6 @@ class SceneInteractorTest : SysuiTestCase() { @Before fun setUp() { - kosmos.fakeSceneContainerFlags.enabled = true underTest = kosmos.sceneInteractor } diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/interactor/WindowRootViewVisibilityInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/interactor/WindowRootViewVisibilityInteractorTest.kt index d5e43f44426b..bfe5ef7acbce 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/interactor/WindowRootViewVisibilityInteractorTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/interactor/WindowRootViewVisibilityInteractorTest.kt @@ -31,7 +31,6 @@ import com.android.systemui.power.domain.interactor.PowerInteractor.Companion.se import com.android.systemui.power.domain.interactor.PowerInteractor.Companion.setAwakeForTest import com.android.systemui.power.domain.interactor.PowerInteractorFactory import com.android.systemui.scene.data.repository.WindowRootViewVisibilityRepository -import com.android.systemui.scene.shared.flag.sceneContainerFlags import com.android.systemui.statusbar.NotificationPresenter import com.android.systemui.statusbar.notification.data.repository.ActiveNotificationListRepository import com.android.systemui.statusbar.notification.data.repository.setActiveNotifs @@ -82,7 +81,6 @@ class WindowRootViewVisibilityInteractorTest : SysuiTestCase() { headsUpManager, powerInteractor, activeNotificationsInteractor, - kosmos.sceneContainerFlags, kosmos::sceneInteractor, ) .apply { setUp(notificationPresenter, notificationsController) } diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/startable/SceneContainerStartableTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/startable/SceneContainerStartableTest.kt index 61adcd2e2c25..1472a4d0a620 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/startable/SceneContainerStartableTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/startable/SceneContainerStartableTest.kt @@ -54,7 +54,6 @@ import com.android.systemui.power.domain.interactor.PowerInteractorFactory import com.android.systemui.power.shared.model.WakefulnessState import com.android.systemui.scene.domain.interactor.sceneContainerOcclusionInteractor import com.android.systemui.scene.domain.interactor.sceneInteractor -import com.android.systemui.scene.shared.flag.fakeSceneContainerFlags import com.android.systemui.scene.shared.model.Scenes import com.android.systemui.scene.shared.model.fakeSceneDataSource import com.android.systemui.shade.domain.interactor.shadeInteractor @@ -102,7 +101,6 @@ class SceneContainerStartableTest : SysuiTestCase() { private val kosmos = testKosmos() private val testScope = kosmos.testScope private val sceneInteractor by lazy { kosmos.sceneInteractor } - private val sceneContainerFlags by lazy { kosmos.fakeSceneContainerFlags } private val authenticationInteractor by lazy { kosmos.authenticationInteractor } private val bouncerInteractor by lazy { kosmos.bouncerInteractor } private val faceAuthRepository by lazy { kosmos.fakeDeviceEntryFaceAuthRepository } @@ -124,15 +122,15 @@ class SceneContainerStartableTest : SysuiTestCase() { applicationScope = testScope.backgroundScope, sceneInteractor = sceneInteractor, deviceEntryInteractor = deviceEntryInteractor, + deviceUnlockedInteractor = kosmos.deviceUnlockedInteractor, + bouncerInteractor = bouncerInteractor, keyguardInteractor = keyguardInteractor, - flags = sceneContainerFlags, sysUiState = sysUiState, displayId = Display.DEFAULT_DISPLAY, sceneLogger = mock(), falsingCollector = falsingCollector, falsingManager = kosmos.falsingManager, powerInteractor = powerInteractor, - bouncerInteractor = bouncerInteractor, simBouncerInteractor = { kosmos.simBouncerInteractor }, authenticationInteractor = { authenticationInteractor }, windowController = windowController, @@ -141,7 +139,6 @@ class SceneContainerStartableTest : SysuiTestCase() { headsUpInteractor = kosmos.headsUpNotificationInteractor, occlusionInteractor = kosmos.sceneContainerOcclusionInteractor, faceUnlockInteractor = kosmos.deviceEntryFaceAuthInteractor, - deviceUnlockedInteractor = kosmos.deviceUnlockedInteractor, shadeInteractor = kosmos.shadeInteractor, ) } @@ -1245,7 +1242,6 @@ class SceneContainerStartableTest : SysuiTestCase() { "Cannot start on the Gone scene and have the device be locked at the same time." } - sceneContainerFlags.enabled = true kosmos.fakeDeviceEntryRepository.setBypassEnabled(isBypassEnabled) authenticationMethod?.let { kosmos.fakeAuthenticationRepository.setAuthenticationMethod(authenticationMethod) diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/shared/flag/SceneContainerFlagsTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/shared/flag/SceneContainerFlagsTest.kt index 2938acf293b3..ae5bf073a9f8 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/shared/flag/SceneContainerFlagsTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/shared/flag/SceneContainerFlagsTest.kt @@ -21,7 +21,6 @@ import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.flags.DisableSceneContainer import com.android.systemui.flags.EnableSceneContainer -import com.android.systemui.kosmos.Kosmos import com.google.common.truth.Truth import org.junit.Test import org.junit.runner.RunWith @@ -34,15 +33,11 @@ internal class SceneContainerFlagsTest : SysuiTestCase() { @DisableSceneContainer fun isNotEnabled_withoutAconfigFlags() { Truth.assertThat(SceneContainerFlag.isEnabled).isEqualTo(false) - Truth.assertThat(SceneContainerFlagsImpl().isEnabled()).isEqualTo(false) - Truth.assertThat(Kosmos().sceneContainerFlags.isEnabled()).isEqualTo(false) } @Test @EnableSceneContainer fun isEnabled_withAconfigFlags() { Truth.assertThat(SceneContainerFlag.isEnabled).isEqualTo(true) - Truth.assertThat(SceneContainerFlagsImpl().isEnabled()).isEqualTo(true) - Truth.assertThat(Kosmos().sceneContainerFlags.isEnabled()).isEqualTo(true) } } diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/ui/viewmodel/SceneContainerViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/ui/viewmodel/SceneContainerViewModelTest.kt index 7b0127e94fb7..427b66bcff8a 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/ui/viewmodel/SceneContainerViewModelTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/ui/viewmodel/SceneContainerViewModelTest.kt @@ -23,13 +23,13 @@ import com.android.systemui.SysuiTestCase import com.android.systemui.classifier.domain.interactor.falsingInteractor import com.android.systemui.classifier.fakeFalsingManager import com.android.systemui.coroutines.collectLastValue +import com.android.systemui.flags.EnableSceneContainer import com.android.systemui.kosmos.testScope import com.android.systemui.power.data.repository.fakePowerRepository import com.android.systemui.power.domain.interactor.powerInteractor import com.android.systemui.scene.domain.interactor.sceneInteractor import com.android.systemui.scene.sceneContainerConfig import com.android.systemui.scene.sceneKeys -import com.android.systemui.scene.shared.flag.fakeSceneContainerFlags import com.android.systemui.scene.shared.model.Scenes import com.android.systemui.scene.shared.model.fakeSceneDataSource import com.android.systemui.testKosmos @@ -45,6 +45,7 @@ import org.junit.runner.RunWith @SmallTest @RunWith(AndroidJUnit4::class) +@EnableSceneContainer class SceneContainerViewModelTest : SysuiTestCase() { private val kosmos = testKosmos() @@ -58,7 +59,6 @@ class SceneContainerViewModelTest : SysuiTestCase() { @Before fun setUp() { - kosmos.fakeSceneContainerFlags.enabled = true underTest = SceneContainerViewModel( sceneInteractor = sceneInteractor, diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/ShadeControllerSceneImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/ShadeControllerSceneImplTest.kt index cbbcce96873b..420418b60d07 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/ShadeControllerSceneImplTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/ShadeControllerSceneImplTest.kt @@ -22,6 +22,7 @@ import com.android.compose.animation.scene.ObservableTransitionState import com.android.compose.animation.scene.SceneKey import com.android.systemui.SysuiTestCase import com.android.systemui.deviceentry.domain.interactor.deviceEntryInteractor +import com.android.systemui.flags.EnableSceneContainer import com.android.systemui.flags.Flags import com.android.systemui.flags.fakeFeatureFlagsClassic import com.android.systemui.keyguard.data.repository.fakeDeviceEntryFingerprintAuthRepository @@ -30,7 +31,6 @@ import com.android.systemui.kosmos.Kosmos import com.android.systemui.kosmos.testCase import com.android.systemui.kosmos.testScope import com.android.systemui.scene.domain.interactor.sceneInteractor -import com.android.systemui.scene.shared.flag.fakeSceneContainerFlags import com.android.systemui.scene.shared.model.Scenes import com.android.systemui.shade.domain.interactor.ShadeInteractor import com.android.systemui.shade.domain.interactor.shadeInteractor @@ -52,6 +52,7 @@ import org.mockito.Mockito.verify @ExperimentalCoroutinesApi @SmallTest @RunWith(AndroidJUnit4::class) +@EnableSceneContainer class ShadeControllerSceneImplTest : SysuiTestCase() { private val kosmos = Kosmos() private val testScope = kosmos.testScope @@ -64,7 +65,6 @@ class ShadeControllerSceneImplTest : SysuiTestCase() { @Before fun setup() { kosmos.testCase = this - kosmos.fakeSceneContainerFlags.enabled = true kosmos.fakeFeatureFlagsClassic.apply { set(Flags.FULL_SCREEN_USER_SWITCHER, false) set(Flags.NSSL_DEBUG_LINES, false) diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/domain/interactor/ShadeBackActionInteractorImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/domain/interactor/ShadeBackActionInteractorImplTest.kt index e759b504d5c3..26f342aa05ce 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/domain/interactor/ShadeBackActionInteractorImplTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/domain/interactor/ShadeBackActionInteractorImplTest.kt @@ -22,9 +22,9 @@ import androidx.test.filters.SmallTest import com.android.compose.animation.scene.ObservableTransitionState import com.android.compose.animation.scene.SceneKey import com.android.systemui.SysuiTestCase +import com.android.systemui.flags.EnableSceneContainer import com.android.systemui.kosmos.testScope import com.android.systemui.scene.domain.interactor.sceneInteractor -import com.android.systemui.scene.shared.flag.fakeSceneContainerFlags import com.android.systemui.scene.shared.model.Scenes import com.android.systemui.shared.recents.utilities.Utilities import com.android.systemui.testKosmos @@ -43,8 +43,9 @@ import org.junit.runner.RunWith @SmallTest @RunWith(AndroidJUnit4::class) @Ignore("b/328827631") +@EnableSceneContainer class ShadeBackActionInteractorImplTest : SysuiTestCase() { - val kosmos = testKosmos().apply { fakeSceneContainerFlags.enabled = true } + val kosmos = testKosmos() val testScope = kosmos.testScope val sceneInteractor = kosmos.sceneInteractor val underTest = kosmos.shadeBackActionInteractor diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneViewModelTest.kt index ab95e2c2e449..2727af64733c 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneViewModelTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneViewModelTest.kt @@ -44,6 +44,8 @@ import com.android.systemui.shade.domain.startable.shadeStartable import com.android.systemui.shade.shared.model.ShadeMode import com.android.systemui.statusbar.notification.stack.ui.viewmodel.notificationsPlaceholderViewModel import com.android.systemui.testKosmos +import com.android.systemui.unfold.domain.interactor.unfoldTransitionInteractor +import com.android.systemui.unfold.fakeUnfoldTransitionProgressProvider import com.android.systemui.util.mockito.mock import com.android.systemui.util.mockito.whenever import com.google.common.truth.Truth.assertThat @@ -91,6 +93,7 @@ class ShadeSceneViewModelTest : SysuiTestCase() { footerActionsViewModelFactory = kosmos.footerActionsViewModelFactory, footerActionsController = kosmos.footerActionsController, sceneInteractor = kosmos.sceneInteractor, + unfoldTransitionInteractor = kosmos.unfoldTransitionInteractor, ) } @@ -254,4 +257,26 @@ class ShadeSceneViewModelTest : SysuiTestCase() { shadeRepository.setShadeMode(ShadeMode.Split) assertThat(shadeMode).isEqualTo(ShadeMode.Split) } + + @Test + fun unfoldTransitionProgress() = + testScope.runTest { + val unfoldProvider = kosmos.fakeUnfoldTransitionProgressProvider + val progress by collectLastValue(underTest.unfoldTransitionProgress) + + unfoldProvider.onTransitionStarted() + assertThat(progress).isEqualTo(1f) + + repeat(10) { repetition -> + val transitionProgress = 0.1f * (repetition + 1) + unfoldProvider.onTransitionProgress(transitionProgress) + assertThat(progress).isEqualTo(transitionProgress) + } + + unfoldProvider.onTransitionFinishing() + assertThat(progress).isEqualTo(1f) + + unfoldProvider.onTransitionFinished() + assertThat(progress).isEqualTo(1f) + } } diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/NotificationStackAppearanceIntegrationTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/NotificationStackAppearanceIntegrationTest.kt index a3cf92986bd6..01e1aa59912f 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/NotificationStackAppearanceIntegrationTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/NotificationStackAppearanceIntegrationTest.kt @@ -23,11 +23,11 @@ import androidx.test.filters.SmallTest import com.android.compose.animation.scene.ObservableTransitionState import com.android.systemui.SysuiTestCase import com.android.systemui.coroutines.collectLastValue +import com.android.systemui.flags.EnableSceneContainer import com.android.systemui.flags.Flags import com.android.systemui.flags.fakeFeatureFlagsClassic import com.android.systemui.kosmos.testScope import com.android.systemui.scene.domain.interactor.sceneInteractor -import com.android.systemui.scene.shared.flag.fakeSceneContainerFlags import com.android.systemui.scene.shared.model.Scenes import com.android.systemui.scene.shared.model.fakeSceneDataSource import com.android.systemui.statusbar.notification.stack.shared.model.ShadeScrimBounds @@ -46,11 +46,11 @@ import org.junit.runner.RunWith @SmallTest @RunWith(AndroidJUnit4::class) +@EnableSceneContainer class NotificationStackAppearanceIntegrationTest : SysuiTestCase() { private val kosmos = testKosmos().apply { - fakeSceneContainerFlags.enabled = true fakeFeatureFlagsClassic.apply { set(Flags.FULL_SCREEN_USER_SWITCHER, false) set(Flags.NSSL_DEBUG_LINES, false) diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModelTest.kt index 8f7a56de0040..a023033e3aa3 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModelTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModelTest.kt @@ -52,7 +52,6 @@ import com.android.systemui.keyguard.ui.viewmodel.keyguardRootViewModel import com.android.systemui.kosmos.testScope import com.android.systemui.res.R import com.android.systemui.scene.shared.flag.SceneContainerFlag -import com.android.systemui.scene.shared.flag.sceneContainerFlags import com.android.systemui.shade.data.repository.shadeRepository import com.android.systemui.shade.mockLargeScreenHeaderHelper import com.android.systemui.statusbar.notification.stack.domain.interactor.sharedNotificationContainerInteractor @@ -128,7 +127,7 @@ class SharedNotificationContainerViewModelTest(flags: FlagsParameterization?) : @Before fun setUp() { - assertThat(kosmos.sceneContainerFlags.isEnabled()).isEqualTo(SceneContainerFlag.isEnabled) + assertThat(SceneContainerFlag.isEnabled).isEqualTo(SceneContainerFlag.isEnabled) overrideResource(R.bool.config_use_split_notification_shade, false) movementFlow = MutableStateFlow(BurnInModel()) whenever(aodBurnInViewModel.movement(any())).thenReturn(movementFlow) diff --git a/packages/SystemUI/tests/src/com/android/systemui/unfold/domain/interactor/UnfoldTransitionInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/unfold/domain/interactor/UnfoldTransitionInteractorTest.kt index 6a801e01a4a5..3b4cce448da9 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/unfold/domain/interactor/UnfoldTransitionInteractorTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/unfold/domain/interactor/UnfoldTransitionInteractorTest.kt @@ -15,42 +15,31 @@ */ package com.android.systemui.unfold.domain.interactor -import android.testing.AndroidTestingRunner +import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase -import com.android.systemui.unfold.TestUnfoldTransitionProvider -import com.android.systemui.unfold.data.repository.UnfoldTransitionRepositoryImpl +import com.android.systemui.coroutines.collectLastValue +import com.android.systemui.kosmos.testScope +import com.android.systemui.testKosmos +import com.android.systemui.unfold.fakeUnfoldTransitionProgressProvider import com.google.common.truth.Truth.assertThat -import java.util.Optional import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.async -import kotlinx.coroutines.test.TestScope import kotlinx.coroutines.test.runCurrent import kotlinx.coroutines.test.runTest -import org.junit.Before import org.junit.Test import org.junit.runner.RunWith -import org.mockito.MockitoAnnotations @OptIn(ExperimentalCoroutinesApi::class) @SmallTest -@RunWith(AndroidTestingRunner::class) -open class UnfoldTransitionInteractorTest : SysuiTestCase() { +@RunWith(AndroidJUnit4::class) +class UnfoldTransitionInteractorTest : SysuiTestCase() { - private val testScope = TestScope() + private val kosmos = testKosmos() + private val testScope = kosmos.testScope + private val unfoldTransitionProgressProvider = kosmos.fakeUnfoldTransitionProgressProvider - private val unfoldTransitionProgressProvider = TestUnfoldTransitionProvider() - private val unfoldTransitionRepository = - UnfoldTransitionRepositoryImpl(Optional.of(unfoldTransitionProgressProvider)) - - private lateinit var underTest: UnfoldTransitionInteractor - - @Before - fun setUp() { - MockitoAnnotations.initMocks(this) - - underTest = UnfoldTransitionInteractorImpl(unfoldTransitionRepository) - } + private val underTest: UnfoldTransitionInteractor = kosmos.unfoldTransitionInteractor @Test fun waitForTransitionFinish_noEvents_doesNotComplete() = @@ -88,4 +77,26 @@ open class UnfoldTransitionInteractorTest : SysuiTestCase() { assertThat(deferred.isCompleted).isFalse() deferred.cancel() } + + @Test + fun unfoldProgress() = + testScope.runTest { + val progress by collectLastValue(underTest.unfoldProgress) + runCurrent() + + unfoldTransitionProgressProvider.onTransitionStarted() + assertThat(progress).isEqualTo(1f) + + repeat(10) { repetition -> + val transitionProgress = 0.1f * (repetition + 1) + unfoldTransitionProgressProvider.onTransitionProgress(transitionProgress) + assertThat(progress).isEqualTo(transitionProgress) + } + + unfoldTransitionProgressProvider.onTransitionFinishing() + assertThat(progress).isEqualTo(1f) + + unfoldTransitionProgressProvider.onTransitionFinished() + assertThat(progress).isEqualTo(1f) + } } diff --git a/packages/SystemUI/plugin/src/com/android/systemui/plugins/clocks/ClockProviderPlugin.kt b/packages/SystemUI/plugin/src/com/android/systemui/plugins/clocks/ClockProviderPlugin.kt index 8e2bd9b2562b..79bf5f19997b 100644 --- a/packages/SystemUI/plugin/src/com/android/systemui/plugins/clocks/ClockProviderPlugin.kt +++ b/packages/SystemUI/plugin/src/com/android/systemui/plugins/clocks/ClockProviderPlugin.kt @@ -267,6 +267,9 @@ data class ClockConfig( /** True if the clock will react to tone changes in the seed color. */ val isReactiveToTone: Boolean = true, + + /** True if the clock is large frame clock, which will use weather in compose. */ + val useCustomClockScene: Boolean = false, ) /** Render configuration options for a clock face. Modifies the way SystemUI behaves. */ @@ -283,6 +286,9 @@ data class ClockFaceConfig( * animation will be used (e.g. a simple translation). */ val hasCustomPositionUpdatedAnimation: Boolean = false, + + /** True if the clock is large frame clock, which will use weatherBlueprint in compose. */ + val useCustomClockScene: Boolean = false, ) /** Structure for keeping clock-specific settings */ diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardInputViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardInputViewController.java index 458a21c5c426..75d925dc4ab0 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardInputViewController.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardInputViewController.java @@ -107,7 +107,10 @@ public abstract class KeyguardInputViewController<T extends KeyguardInputView> } } - private void updateMessageAreaVisibility() { + /** + * Determines whether to show the message area controlled by MessageAreaController. + */ + public void updateMessageAreaVisibility() { if (mMessageAreaController == null) return; if (Flags.revampedBouncerMessages()) { mMessageAreaController.disable(); diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java index c509356c4e63..e8e1cab4b932 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java @@ -90,7 +90,7 @@ import com.android.systemui.log.SessionTracker; import com.android.systemui.plugins.ActivityStarter; import com.android.systemui.plugins.FalsingManager; import com.android.systemui.res.R; -import com.android.systemui.scene.shared.flag.SceneContainerFlags; +import com.android.systemui.scene.shared.flag.SceneContainerFlag; import com.android.systemui.shared.system.SysUiStatsLog; import com.android.systemui.statusbar.policy.ConfigurationController; import com.android.systemui.statusbar.policy.DeviceProvisionedController; @@ -134,7 +134,6 @@ public class KeyguardSecurityContainerController extends ViewController<Keyguard private final UserSwitcherController mUserSwitcherController; private final GlobalSettings mGlobalSettings; private final FeatureFlags mFeatureFlags; - private final SceneContainerFlags mSceneContainerFlags; private final SessionTracker mSessionTracker; private final Optional<SideFpsController> mSideFpsController; private final FalsingA11yDelegate mFalsingA11yDelegate; @@ -456,7 +455,6 @@ public class KeyguardSecurityContainerController extends ViewController<Keyguard FalsingManager falsingManager, UserSwitcherController userSwitcherController, FeatureFlags featureFlags, - SceneContainerFlags sceneContainerFlags, GlobalSettings globalSettings, SessionTracker sessionTracker, Optional<SideFpsController> sideFpsController, @@ -491,7 +489,6 @@ public class KeyguardSecurityContainerController extends ViewController<Keyguard mFalsingManager = falsingManager; mUserSwitcherController = userSwitcherController; mFeatureFlags = featureFlags; - mSceneContainerFlags = sceneContainerFlags; mGlobalSettings = globalSettings; mSessionTracker = sessionTracker; if (SideFpsControllerRefactor.isEnabled()) { @@ -534,7 +531,7 @@ public class KeyguardSecurityContainerController extends ViewController<Keyguard showPrimarySecurityScreen(false); - if (mSceneContainerFlags.isEnabled()) { + if (SceneContainerFlag.isEnabled()) { // When the scene framework says that the lockscreen has been dismissed, dismiss the // keyguard here, revealing the underlying app or launcher: mSceneTransitionCollectionJob = mJavaAdapter.get().alwaysCollectFlow( diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSimPinViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSimPinViewController.java index 558679e993e1..3ef3418bfed4 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardSimPinViewController.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSimPinViewController.java @@ -118,6 +118,12 @@ public class KeyguardSimPinViewController } @Override + public void updateMessageAreaVisibility() { + if (mMessageAreaController == null) return; + mMessageAreaController.setIsVisible(true); + } + + @Override void resetState() { super.resetState(); if (DEBUG) Log.v(TAG, "Resetting state"); diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSimPukViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSimPukViewController.java index cb1c4b3064ce..46225c7ea58a 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardSimPukViewController.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSimPukViewController.java @@ -115,6 +115,12 @@ public class KeyguardSimPukViewController } @Override + public void updateMessageAreaVisibility() { + if (mMessageAreaController == null) return; + mMessageAreaController.setIsVisible(true); + } + + @Override public void onResume(int reason) { super.onResume(reason); if (mShowDefaultMessage) { diff --git a/packages/SystemUI/src/com/android/keyguard/LegacyLockIconViewController.java b/packages/SystemUI/src/com/android/keyguard/LegacyLockIconViewController.java index 4e5df3543451..cf2675b46418 100644 --- a/packages/SystemUI/src/com/android/keyguard/LegacyLockIconViewController.java +++ b/packages/SystemUI/src/com/android/keyguard/LegacyLockIconViewController.java @@ -74,7 +74,7 @@ import com.android.systemui.keyguard.shared.model.TransitionStep; import com.android.systemui.plugins.FalsingManager; import com.android.systemui.plugins.statusbar.StatusBarStateController; import com.android.systemui.res.R; -import com.android.systemui.scene.shared.flag.SceneContainerFlags; +import com.android.systemui.scene.shared.flag.SceneContainerFlag; import com.android.systemui.statusbar.StatusBarState; import com.android.systemui.statusbar.VibratorHelper; import com.android.systemui.statusbar.policy.ConfigurationController; @@ -131,7 +131,6 @@ public class LegacyLockIconViewController implements Dumpable, LockIconViewContr @NonNull private final KeyguardInteractor mKeyguardInteractor; @NonNull private final View.AccessibilityDelegate mAccessibilityDelegate; @NonNull private final Lazy<DeviceEntryInteractor> mDeviceEntryInteractor; - @NonNull private final SceneContainerFlags mSceneContainerFlags; // Tracks the velocity of a touch to help filter out the touches that move too fast. private VelocityTracker mVelocityTracker; @@ -208,8 +207,7 @@ public class LegacyLockIconViewController implements Dumpable, LockIconViewContr @NonNull FeatureFlags featureFlags, PrimaryBouncerInteractor primaryBouncerInteractor, Context context, - Lazy<DeviceEntryInteractor> deviceEntryInteractor, - SceneContainerFlags sceneContainerFlags + Lazy<DeviceEntryInteractor> deviceEntryInteractor ) { mStatusBarStateController = statusBarStateController; mKeyguardUpdateMonitor = keyguardUpdateMonitor; @@ -236,7 +234,6 @@ public class LegacyLockIconViewController implements Dumpable, LockIconViewContr mResources = resources; mContext = context; mDeviceEntryInteractor = deviceEntryInteractor; - mSceneContainerFlags = sceneContainerFlags; mAccessibilityDelegate = new View.AccessibilityDelegate() { private final AccessibilityNodeInfo.AccessibilityAction mAccessibilityAuthenticateHint = @@ -746,7 +743,7 @@ public class LegacyLockIconViewController implements Dumpable, LockIconViewContr // play device entry haptic (consistent with UDFPS controller longpress) vibrateOnLongPress(); - if (mSceneContainerFlags.isEnabled()) { + if (SceneContainerFlag.isEnabled()) { mDeviceEntryInteractor.get().attemptDeviceEntry(); } else { mKeyguardViewController.showPrimaryBouncer(/* scrim */ true); diff --git a/packages/SystemUI/src/com/android/systemui/authentication/data/repository/AuthenticationRepository.kt b/packages/SystemUI/src/com/android/systemui/authentication/data/repository/AuthenticationRepository.kt index 454ed27161a2..a9f985f0955b 100644 --- a/packages/SystemUI/src/com/android/systemui/authentication/data/repository/AuthenticationRepository.kt +++ b/packages/SystemUI/src/com/android/systemui/authentication/data/repository/AuthenticationRepository.kt @@ -36,7 +36,7 @@ import com.android.systemui.broadcast.BroadcastDispatcher import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Application import com.android.systemui.dagger.qualifiers.Background -import com.android.systemui.scene.shared.flag.SceneContainerFlags +import com.android.systemui.scene.shared.flag.SceneContainerFlag import com.android.systemui.statusbar.pipeline.mobile.data.repository.MobileConnectionsRepository import com.android.systemui.user.data.repository.UserRepository import com.android.systemui.util.kotlin.onSubscriberAdded @@ -186,7 +186,6 @@ class AuthenticationRepositoryImpl constructor( @Application private val applicationScope: CoroutineScope, @Background private val backgroundDispatcher: CoroutineDispatcher, - flags: SceneContainerFlags, private val clock: SystemClock, private val getSecurityMode: Function<Int, KeyguardSecurityModel.SecurityMode>, private val userRepository: UserRepository, @@ -255,7 +254,7 @@ constructor( override val hasLockoutOccurred: StateFlow<Boolean> = _hasLockoutOccurred.asStateFlow() init { - if (flags.isEnabled()) { + if (SceneContainerFlag.isEnabled) { // Hydrate failedAuthenticationAttempts initially and whenever the selected user // changes. applicationScope.launch { diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsControllerOverlay.kt b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsControllerOverlay.kt index 61d1c713fb77..4a60d195ea36 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsControllerOverlay.kt +++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsControllerOverlay.kt @@ -323,7 +323,7 @@ class UdfpsControllerOverlay @JvmOverloads constructor( overlayParams = updatedOverlayParams sensorBounds = updatedOverlayParams.sensorBounds getTouchOverlay()?.let { - if (addViewRunnable != null) { + if (addViewRunnable == null) { // Only updateViewLayout if there's no pending view to add to WM. // If there is a pending view, that means the view hasn't been added yet so there's // no need to update any layouts. Instead the correct params will be used when the diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/shared/flag/ComposeBouncerFlags.kt b/packages/SystemUI/src/com/android/systemui/bouncer/shared/flag/ComposeBouncerFlags.kt index e789475b7877..62ef365345b7 100644 --- a/packages/SystemUI/src/com/android/systemui/bouncer/shared/flag/ComposeBouncerFlags.kt +++ b/packages/SystemUI/src/com/android/systemui/bouncer/shared/flag/ComposeBouncerFlags.kt @@ -18,7 +18,7 @@ package com.android.systemui.bouncer.shared.flag import com.android.systemui.Flags import com.android.systemui.dagger.SysUISingleton -import com.android.systemui.scene.shared.flag.SceneContainerFlags +import com.android.systemui.scene.shared.flag.SceneContainerFlag import dagger.Module import dagger.Provides @@ -42,11 +42,10 @@ interface ComposeBouncerFlags { fun isOnlyComposeBouncerEnabled(): Boolean } -class ComposeBouncerFlagsImpl(private val sceneContainerFlags: SceneContainerFlags) : - ComposeBouncerFlags { +class ComposeBouncerFlagsImpl() : ComposeBouncerFlags { override fun isComposeBouncerOrSceneContainerEnabled(): Boolean { - return sceneContainerFlags.isEnabled() || Flags.composeBouncer() + return SceneContainerFlag.isEnabled || Flags.composeBouncer() } @Deprecated( @@ -55,7 +54,7 @@ class ComposeBouncerFlagsImpl(private val sceneContainerFlags: SceneContainerFla replaceWith = ReplaceWith("isComposeBouncerOrSceneContainerEnabled()") ) override fun isOnlyComposeBouncerEnabled(): Boolean { - return !sceneContainerFlags.isEnabled() && Flags.composeBouncer() + return !SceneContainerFlag.isEnabled && Flags.composeBouncer() } } @@ -63,7 +62,7 @@ class ComposeBouncerFlagsImpl(private val sceneContainerFlags: SceneContainerFla object ComposeBouncerFlagsModule { @Provides @SysUISingleton - fun impl(sceneContainerFlags: SceneContainerFlags): ComposeBouncerFlags { - return ComposeBouncerFlagsImpl(sceneContainerFlags) + fun impl(): ComposeBouncerFlags { + return ComposeBouncerFlagsImpl() } } diff --git a/packages/SystemUI/src/com/android/systemui/classifier/FalsingModule.java b/packages/SystemUI/src/com/android/systemui/classifier/FalsingModule.java index af467ef1319b..613280c3ba5d 100644 --- a/packages/SystemUI/src/com/android/systemui/classifier/FalsingModule.java +++ b/packages/SystemUI/src/com/android/systemui/classifier/FalsingModule.java @@ -22,7 +22,7 @@ import android.view.ViewConfiguration; import com.android.systemui.dagger.SysUISingleton; import com.android.systemui.dagger.qualifiers.Main; import com.android.systemui.res.R; -import com.android.systemui.scene.shared.flag.SceneContainerFlags; +import com.android.systemui.scene.shared.flag.SceneContainerFlag; import com.android.systemui.statusbar.phone.NotificationTapHelper; import dagger.Binds; @@ -51,9 +51,8 @@ public interface FalsingModule { @SysUISingleton static FalsingCollector providesFalsingCollectorLegacy( FalsingCollectorImpl impl, - FalsingCollectorNoOp noOp, - SceneContainerFlags flags) { - return flags.isEnabled() ? noOp : impl; + FalsingCollectorNoOp noOp) { + return SceneContainerFlag.isEnabled() ? noOp : impl; } /** Provides the actual {@link FalsingCollector}. */ diff --git a/packages/SystemUI/src/com/android/systemui/common/coroutine/ConflatedCallbackFlow.kt b/packages/SystemUI/src/com/android/systemui/common/coroutine/ConflatedCallbackFlow.kt index d4a1f74234ef..0c181e99b21c 100644 --- a/packages/SystemUI/src/com/android/systemui/common/coroutine/ConflatedCallbackFlow.kt +++ b/packages/SystemUI/src/com/android/systemui/common/coroutine/ConflatedCallbackFlow.kt @@ -16,14 +16,14 @@ package com.android.systemui.common.coroutine +import com.android.systemui.utils.coroutines.flow.conflatedCallbackFlow as wrapped import kotlin.experimental.ExperimentalTypeInference -import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.channels.Channel import kotlinx.coroutines.channels.ProducerScope import kotlinx.coroutines.flow.Flow -import kotlinx.coroutines.flow.buffer import kotlinx.coroutines.flow.callbackFlow +@Deprecated("Use com.android.systemui.utils.coroutines.flow.conflatedCallbackFlow instead") object ConflatedCallbackFlow { /** @@ -32,9 +32,15 @@ object ConflatedCallbackFlow { * consumer(s) of the values in the flow), the values are buffered and, if the buffer fills up, * we drop the oldest values automatically instead of suspending the producer. */ - @Suppress("EXPERIMENTAL_IS_NOT_ENABLED") - @OptIn(ExperimentalTypeInference::class, ExperimentalCoroutinesApi::class) + @Deprecated( + "Use com.android.systemui.utils.coroutines.flow.conflatedCallbackFlow instead", + ReplaceWith( + "conflatedCallbackFlow", + "com.android.systemui.utils.coroutines.flow.conflatedCallbackFlow" + ) + ) + @OptIn(ExperimentalTypeInference::class) fun <T> conflatedCallbackFlow( @BuilderInference block: suspend ProducerScope<T>.() -> Unit, - ): Flow<T> = callbackFlow(block).buffer(capacity = Channel.CONFLATED) + ): Flow<T> = wrapped(block) } diff --git a/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalInteractor.kt b/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalInteractor.kt index 373e1c9daa7b..619e0525acd8 100644 --- a/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalInteractor.kt @@ -55,7 +55,7 @@ import com.android.systemui.log.table.TableLogBuffer import com.android.systemui.log.table.logDiffsForTable import com.android.systemui.plugins.ActivityStarter import com.android.systemui.scene.domain.interactor.SceneInteractor -import com.android.systemui.scene.shared.flag.SceneContainerFlags +import com.android.systemui.scene.shared.flag.SceneContainerFlag import com.android.systemui.scene.shared.model.Scenes import com.android.systemui.settings.UserTracker import com.android.systemui.smartspace.data.repository.SmartspaceRepository @@ -107,7 +107,6 @@ constructor( private val userManager: UserManager, private val dockManager: DockManager, sceneInteractor: SceneInteractor, - sceneContainerFlags: SceneContainerFlags, @CommunalLog logBuffer: LogBuffer, @CommunalTableLog tableLogBuffer: TableLogBuffer, ) { @@ -216,7 +215,7 @@ constructor( */ // TODO(b/323215860): rename to something more appropriate after cleaning up usages val isCommunalShowing: Flow<Boolean> = - flow { emit(sceneContainerFlags.isEnabled()) } + flow { emit(SceneContainerFlag.isEnabled) } .flatMapLatest { sceneContainerEnabled -> if (sceneContainerEnabled) { sceneInteractor.currentScene.map { it == Scenes.Communal } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java index 165de7cea9e6..21af0a07a6d8 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java +++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java @@ -1373,7 +1373,7 @@ public class KeyguardViewMediator implements CoreStartable, Dumpable, private final Lazy<DreamViewModel> mDreamViewModel; private final Lazy<CommunalTransitionViewModel> mCommunalTransitionViewModel; private RemoteAnimationTarget mRemoteAnimationTarget; - private Boolean mShowCommunalByDefault; + private boolean mShowCommunalByDefault = false; private final Lazy<WindowManagerLockscreenVisibilityManager> mWmLockscreenVisibilityManager; diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/DeviceEntrySideFpsOverlayInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/DeviceEntrySideFpsOverlayInteractor.kt index bf1f07479f34..eef4b97ae34d 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/DeviceEntrySideFpsOverlayInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/DeviceEntrySideFpsOverlayInteractor.kt @@ -27,7 +27,7 @@ import com.android.systemui.keyguard.data.repository.BiometricType import com.android.systemui.keyguard.data.repository.DeviceEntryFingerprintAuthRepository import com.android.systemui.res.R import com.android.systemui.scene.domain.interactor.SceneInteractor -import com.android.systemui.scene.shared.flag.SceneContainerFlags +import com.android.systemui.scene.shared.flag.SceneContainerFlag import com.android.systemui.scene.shared.model.Scenes import javax.inject.Inject import kotlinx.coroutines.CoroutineScope @@ -52,7 +52,6 @@ constructor( @Application private val applicationScope: CoroutineScope, @Application private val context: Context, deviceEntryFingerprintAuthRepository: DeviceEntryFingerprintAuthRepository, - private val sceneContainerFlags: SceneContainerFlags, private val sceneInteractor: SceneInteractor, private val primaryBouncerInteractor: PrimaryBouncerInteractor, alternateBouncerInteractor: AlternateBouncerInteractor, @@ -75,7 +74,7 @@ constructor( get() = context.resources.getBoolean(R.bool.config_show_sidefps_hint_on_bouncer) private val isBouncerSceneActive: Flow<Boolean> = - if (sceneContainerFlags.isEnabled()) { + if (SceneContainerFlag.isEnabled) { sceneInteractor.currentScene.map { it == Scenes.Bouncer }.distinctUntilChanged() } else { flowOf(false) @@ -115,7 +114,7 @@ constructor( .distinctUntilChanged() private fun isBouncerActive(): Boolean { - if (sceneContainerFlags.isEnabled()) { + if (SceneContainerFlag.isEnabled) { return sceneInteractor.currentScene.value == Scenes.Bouncer } return primaryBouncerInteractor.isBouncerShowing() && diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt index c4769488646d..7224536cfe70 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt @@ -44,7 +44,7 @@ import com.android.systemui.keyguard.shared.model.StatusBarState import com.android.systemui.power.domain.interactor.PowerInteractor import com.android.systemui.res.R import com.android.systemui.scene.domain.interactor.SceneInteractor -import com.android.systemui.scene.shared.flag.SceneContainerFlags +import com.android.systemui.scene.shared.flag.SceneContainerFlag import com.android.systemui.scene.shared.model.Scenes import com.android.systemui.shade.data.repository.ShadeRepository import com.android.systemui.statusbar.CommandQueue @@ -84,7 +84,6 @@ constructor( private val repository: KeyguardRepository, private val commandQueue: CommandQueue, powerInteractor: PowerInteractor, - sceneContainerFlags: SceneContainerFlags, bouncerRepository: KeyguardBouncerRepository, configurationInteractor: ConfigurationInteractor, shadeRepository: ShadeRepository, @@ -331,7 +330,7 @@ constructor( /** Whether to animate the next doze mode transition. */ val animateDozingTransitions: Flow<Boolean> by lazy { - if (sceneContainerFlags.isEnabled()) { + if (SceneContainerFlag.isEnabled) { sceneInteractorProvider .get() .transitioningTo diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardSurfaceBehindInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardSurfaceBehindInteractor.kt index 80e94a27bec5..20b7b2a91ade 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardSurfaceBehindInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardSurfaceBehindInteractor.kt @@ -23,12 +23,14 @@ import com.android.systemui.keyguard.domain.interactor.WindowManagerLockscreenVi import com.android.systemui.keyguard.shared.model.KeyguardState import com.android.systemui.keyguard.shared.model.KeyguardSurfaceBehindModel import com.android.systemui.statusbar.notification.domain.interactor.NotificationLaunchAnimationInteractor +import com.android.systemui.util.kotlin.sample import com.android.systemui.util.kotlin.toPx import dagger.Lazy import javax.inject.Inject import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.distinctUntilChanged +import kotlinx.coroutines.flow.map /** * Distance over which the surface behind the keyguard is animated in during a Y-translation @@ -96,13 +98,21 @@ constructor( .distinctUntilChanged() /** + * Whether a notification launch animation is running when we're not already in the GONE state. + */ + private val isNotificationLaunchAnimationRunningOnKeyguard = + notificationLaunchInteractor.isLaunchAnimationRunning + .sample(transitionInteractor.finishedKeyguardState) + .map { it != KeyguardState.GONE } + + /** * Whether we're animating the surface, or a notification launch animation is running (which * means we're going to animate the surface, even if animators aren't yet running). */ val isAnimatingSurface = combine( repository.isAnimatingSurface, - notificationLaunchInteractor.isLaunchAnimationRunning + isNotificationLaunchAnimationRunningOnKeyguard, ) { animatingSurface, animatingLaunch -> animatingSurface || animatingLaunch } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardClockViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardClockViewBinder.kt index 6255f0d44609..7178e1bd9357 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardClockViewBinder.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardClockViewBinder.kt @@ -36,7 +36,6 @@ import com.android.systemui.keyguard.ui.view.layout.sections.ClockSection import com.android.systemui.keyguard.ui.viewmodel.KeyguardClockViewModel import com.android.systemui.lifecycle.repeatWhenAttached import com.android.systemui.plugins.clocks.ClockController -import com.android.systemui.shared.clocks.DEFAULT_CLOCK_ID import kotlinx.coroutines.launch object KeyguardClockViewBinder { @@ -76,13 +75,13 @@ object KeyguardClockViewBinder { } launch { if (!MigrateClocksToBlueprint.isEnabled) return@launch - viewModel.clockShouldBeCentered.collect { clockShouldBeCentered -> + viewModel.clockShouldBeCentered.collect { viewModel.currentClock.value?.let { - // Weather clock also has hasCustomPositionUpdatedAnimation as true - // TODO(b/323020908): remove ID check + // TODO(b/301502635): remove "!it.config.useCustomClockScene" when + // migrate clocks to blueprint is fully rolled out if ( it.largeClock.config.hasCustomPositionUpdatedAnimation && - it.config.id == DEFAULT_CLOCK_ID + !it.config.useCustomClockScene ) { blueprintInteractor.refreshBlueprint(Type.DefaultClockStepping) } else { @@ -93,12 +92,9 @@ object KeyguardClockViewBinder { } launch { if (!MigrateClocksToBlueprint.isEnabled) return@launch - viewModel.isAodIconsVisible.collect { isAodIconsVisible -> + viewModel.isAodIconsVisible.collect { viewModel.currentClock.value?.let { - // Weather clock also has hasCustomPositionUpdatedAnimation as true - if ( - viewModel.useLargeClock && it.config.id == "DIGITAL_CLOCK_WEATHER" - ) { + if (viewModel.useLargeClock && it.config.useCustomClockScene) { blueprintInteractor.refreshBlueprint(Type.DefaultTransition) } } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardRootViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardRootViewBinder.kt index 5ee35e4f8eb6..cc54920236da 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardRootViewBinder.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardRootViewBinder.kt @@ -30,6 +30,9 @@ import android.view.ViewGroup import android.view.ViewGroup.OnHierarchyChangeListener import android.view.ViewPropertyAnimator import android.view.WindowInsets +import androidx.activity.OnBackPressedDispatcher +import androidx.activity.OnBackPressedDispatcherOwner +import androidx.activity.setViewTreeOnBackPressedDispatcherOwner import androidx.lifecycle.Lifecycle import androidx.lifecycle.repeatOnLifecycle import com.android.app.animation.Interpolators @@ -49,6 +52,7 @@ import com.android.systemui.keyguard.KeyguardBottomAreaRefactor import com.android.systemui.keyguard.KeyguardViewMediator import com.android.systemui.keyguard.MigrateClocksToBlueprint import com.android.systemui.keyguard.domain.interactor.KeyguardClockInteractor +import com.android.systemui.keyguard.shared.ComposeLockscreen import com.android.systemui.keyguard.shared.model.KeyguardState import com.android.systemui.keyguard.shared.model.TransitionState import com.android.systemui.keyguard.ui.viewmodel.BurnInParameters @@ -125,6 +129,21 @@ object KeyguardRootViewBinder { disposables += view.repeatWhenAttached { repeatOnLifecycle(Lifecycle.State.CREATED) { + if (ComposeLockscreen.isEnabled) { + view.setViewTreeOnBackPressedDispatcherOwner( + object : OnBackPressedDispatcherOwner { + override val onBackPressedDispatcher = + OnBackPressedDispatcher().apply { + setOnBackInvokedDispatcher( + view.viewRootImpl.onBackInvokedDispatcher + ) + } + + override val lifecycle: Lifecycle = + this@repeatWhenAttached.lifecycle + } + ) + } launch { occludingAppDeviceEntryMessageViewModel.message.collect { biometricMessage -> diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerViewModel.kt index 06a0c7291f92..5a559fc3aa45 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerViewModel.kt @@ -18,61 +18,29 @@ package com.android.systemui.keyguard.ui.viewmodel import android.graphics.Color -import com.android.systemui.keyguard.domain.interactor.FromAlternateBouncerTransitionInteractor.Companion.TRANSITION_DURATION_MS +import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor import com.android.systemui.keyguard.shared.model.KeyguardState.ALTERNATE_BOUNCER -import com.android.systemui.keyguard.ui.KeyguardTransitionAnimationFlow import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager -import com.android.wm.shell.animation.Interpolators import javax.inject.Inject import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.distinctUntilChanged import kotlinx.coroutines.flow.flowOf import kotlinx.coroutines.flow.map -import kotlinx.coroutines.flow.merge @ExperimentalCoroutinesApi class AlternateBouncerViewModel @Inject constructor( private val statusBarKeyguardViewManager: StatusBarKeyguardViewManager, - animationFlow: KeyguardTransitionAnimationFlow, + keyguardTransitionInteractor: KeyguardTransitionInteractor, ) { // When we're fully transitioned to the AlternateBouncer, the alpha of the scrim should be: private val alternateBouncerScrimAlpha = .66f - private val toAlternateBouncerTransition = - animationFlow - .setup( - duration = TRANSITION_DURATION_MS, - from = null, - to = ALTERNATE_BOUNCER, - ) - .sharedFlow( - duration = TRANSITION_DURATION_MS, - onStep = { it }, - onFinish = { 1f }, - // Reset on cancel - onCancel = { 0f }, - interpolator = Interpolators.FAST_OUT_SLOW_IN, - ) - private val fromAlternateBouncerTransition = - animationFlow - .setup( - TRANSITION_DURATION_MS, - from = ALTERNATE_BOUNCER, - to = null, - ) - .sharedFlow( - duration = TRANSITION_DURATION_MS, - onStep = { 1f - it }, - // Reset on cancel - onCancel = { 0f }, - interpolator = Interpolators.FAST_OUT_SLOW_IN, - ) /** Progress to a fully transitioned alternate bouncer. 1f represents fully transitioned. */ val transitionToAlternateBouncerProgress = - merge(fromAlternateBouncerTransition, toAlternateBouncerTransition) + keyguardTransitionInteractor.transitionValue(ALTERNATE_BOUNCER) val forcePluginOpen: Flow<Boolean> = transitionToAlternateBouncerProgress.map { it > 0f }.distinctUntilChanged() diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryIconViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryIconViewModel.kt index 49fffdd9ce9b..45dca99b7acc 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryIconViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryIconViewModel.kt @@ -30,7 +30,7 @@ import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInterac import com.android.systemui.keyguard.shared.model.KeyguardState import com.android.systemui.keyguard.ui.transitions.DeviceEntryIconTransition import com.android.systemui.keyguard.ui.view.DeviceEntryIconView -import com.android.systemui.scene.shared.flag.SceneContainerFlags +import com.android.systemui.scene.shared.flag.SceneContainerFlag import com.android.systemui.shade.domain.interactor.ShadeInteractor import com.android.systemui.util.kotlin.sample import dagger.Lazy @@ -64,7 +64,6 @@ constructor( transitionInteractor: KeyguardTransitionInteractor, val keyguardInteractor: KeyguardInteractor, val viewModel: AodToLockscreenTransitionViewModel, - private val sceneContainerFlags: SceneContainerFlags, private val keyguardViewController: Lazy<KeyguardViewController>, private val deviceEntryInteractor: DeviceEntryInteractor, private val deviceEntrySourceInteractor: DeviceEntrySourceInteractor, @@ -242,7 +241,7 @@ constructor( } suspend fun onLongPress() { - if (sceneContainerFlags.isEnabled()) { + if (SceneContainerFlag.isEnabled) { deviceEntryInteractor.attemptDeviceEntry() } else { keyguardViewController.get().showPrimaryBouncer(/* scrim */ true) diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardClockViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardClockViewModel.kt index f6da033f1bd3..a6d3312fd6d2 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardClockViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardClockViewModel.kt @@ -118,8 +118,7 @@ constructor( currentClock ) { isLargeClockVisible, clockShouldBeCentered, shadeMode, currentClock -> val shouldUseSplitShade = shadeMode == ShadeMode.Split - // TODO(b/326098079): make id a constant field in config - if (currentClock?.config?.id == "DIGITAL_CLOCK_WEATHER") { + if (currentClock?.config?.useCustomClockScene == true) { val weatherClockLayout = when { shouldUseSplitShade && clockShouldBeCentered -> diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/MediaDataFilterImpl.kt b/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/MediaDataFilterImpl.kt index e8d32746086d..5432a189cf7c 100644 --- a/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/MediaDataFilterImpl.kt +++ b/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/MediaDataFilterImpl.kt @@ -69,7 +69,11 @@ constructor( private val mediaFlags: MediaFlags, private val mediaFilterRepository: MediaFilterRepository, ) : MediaDataManager.Listener { - lateinit var mediaDataManager: MediaDataManager + /** Non-UI listeners to media changes. */ + private val _listeners: MutableSet<MediaDataProcessor.Listener> = mutableSetOf() + val listeners: Set<MediaDataProcessor.Listener> + get() = _listeners.toSet() + lateinit var mediaDataProcessor: MediaDataProcessor // Ensure the field (and associated reference) isn't removed during optimization. @KeepForWeakReference @@ -113,6 +117,9 @@ constructor( mediaFilterRepository.addMediaDataLoadingState( MediaDataLoadingModel.Loaded(data.instanceId) ) + + // Notify listeners + listeners.forEach { it.onMediaDataLoaded(key, oldKey, data) } } override fun onSmartspaceMediaDataLoaded( @@ -171,6 +178,20 @@ constructor( mediaFilterRepository.addMediaDataLoadingState( MediaDataLoadingModel.Loaded(lastActiveId) ) + listeners.forEach { listener -> + getKey(lastActiveId)?.let { lastActiveKey -> + listener.onMediaDataLoaded( + lastActiveKey, + lastActiveKey, + mediaData, + receivedSmartspaceCardLatency = + (systemClock.currentTimeMillis() - + data.headphoneConnectionTimeMillis) + .toInt(), + isSsReactivated = true + ) + } + } } } else if (data.isActive) { // Mark to prioritize Smartspace card if no recent media. @@ -189,6 +210,7 @@ constructor( mediaFilterRepository.setRecommendationsLoadingState( SmartspaceMediaLoadingModel.Loaded(key, shouldPrioritizeMutable) ) + listeners.forEach { it.onSmartspaceMediaDataLoaded(key, data, shouldPrioritizeMutable) } } override fun onMediaDataRemoved(key: String) { @@ -198,6 +220,8 @@ constructor( mediaFilterRepository.addMediaDataLoadingState( MediaDataLoadingModel.Removed(instanceId) ) + // Only notify listeners if something actually changed + listeners.forEach { it.onMediaDataRemoved(key) } } } } @@ -212,6 +236,11 @@ constructor( mediaFilterRepository.addMediaDataLoadingState( MediaDataLoadingModel.Loaded(lastActiveId, immediately) ) + listeners.forEach { listener -> + getKey(lastActiveId)?.let { lastActiveKey -> + listener.onMediaDataLoaded(lastActiveKey, lastActiveKey, it, immediately) + } + } } } @@ -227,6 +256,7 @@ constructor( mediaFilterRepository.setRecommendationsLoadingState( SmartspaceMediaLoadingModel.Removed(key, immediately) ) + listeners.forEach { it.onSmartspaceMediaDataRemoved(key, immediately) } } @VisibleForTesting @@ -240,6 +270,7 @@ constructor( mediaFilterRepository.addMediaDataLoadingState( MediaDataLoadingModel.Removed(data.instanceId) ) + listeners.forEach { listener -> listener.onMediaDataRemoved(key) } } } } @@ -247,6 +278,7 @@ constructor( @VisibleForTesting internal fun handleUserSwitched() { // If the user changes, remove all current MediaData objects. + val listenersCopy = listeners val keyCopy = mediaFilterRepository.selectedUserEntries.value.keys.toMutableList() // Clear the list first and update loading state to remove media from UI. mediaFilterRepository.clearSelectedUserMedia() @@ -255,6 +287,9 @@ constructor( mediaFilterRepository.addMediaDataLoadingState( MediaDataLoadingModel.Removed(instanceId) ) + getKey(instanceId)?.let { + listenersCopy.forEach { listener -> listener.onMediaDataRemoved(it) } + } } mediaFilterRepository.allUserEntries.value.forEach { (key, data) -> @@ -268,6 +303,7 @@ constructor( mediaFilterRepository.addMediaDataLoadingState( MediaDataLoadingModel.Loaded(data.instanceId) ) + listenersCopy.forEach { listener -> listener.onMediaDataLoaded(key, null, data) } } } } @@ -279,7 +315,7 @@ constructor( mediaEntries.forEach { (key, data) -> if (mediaFilterRepository.selectedUserEntries.value.containsKey(data.instanceId)) { // Force updates to listeners, needed for re-activated card - mediaDataManager.setInactive(key, timedOut = true, forceUpdate = true) + mediaDataProcessor.setInactive(key, timedOut = true, forceUpdate = true) } } val smartspaceMediaData = mediaFilterRepository.smartspaceMediaData.value @@ -301,7 +337,7 @@ constructor( if (mediaFlags.isPersistentSsCardEnabled()) { mediaFilterRepository.setRecommendation(smartspaceMediaData.copy(isActive = false)) - mediaDataManager.setRecommendationInactive(smartspaceMediaData.targetId) + mediaDataProcessor.setRecommendationInactive(smartspaceMediaData.targetId) } else { mediaFilterRepository.setRecommendation( EMPTY_SMARTSPACE_MEDIA_DATA.copy( @@ -309,7 +345,7 @@ constructor( instanceId = smartspaceMediaData.instanceId, ) ) - mediaDataManager.dismissSmartspaceRecommendation( + mediaDataProcessor.dismissSmartspaceRecommendation( smartspaceMediaData.targetId, delay = 0L, ) @@ -317,6 +353,12 @@ constructor( } } + /** Add a listener for filtered [MediaData] changes */ + fun addListener(listener: MediaDataProcessor.Listener) = _listeners.add(listener) + + /** Remove a listener that was registered with addListener */ + fun removeListener(listener: MediaDataProcessor.Listener) = _listeners.remove(listener) + /** * Return the time since last active for the most-recent media. * @@ -336,6 +378,16 @@ constructor( return sortedEntries[lastActiveInstanceId]?.let { now - it.lastActive } ?: Long.MAX_VALUE } + private fun getKey(instanceId: InstanceId): String? { + val allEntries = mediaFilterRepository.allUserEntries.value + val filteredEntries = allEntries.filter { (_, data) -> data.instanceId == instanceId } + return if (filteredEntries.isNotEmpty()) { + filteredEntries.keys.first() + } else { + null + } + } + companion object { /** * Maximum age of a media control to re-activate on smartspace signal. If there is no media 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 c7cfb0b7d775..0e2814b3d91e 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 @@ -35,6 +35,7 @@ import com.android.settingslib.flags.Flags.legacyLeAudioSharing 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 import com.android.systemui.dagger.qualifiers.Background import com.android.systemui.dagger.qualifiers.Main import com.android.systemui.media.controls.shared.model.MediaData @@ -178,7 +179,9 @@ constructor( bgExecutor.execute { if (!started) { localMediaManager.registerCallback(this) - localMediaManager.startScan() + if (!Flags.removeUnnecessaryRouteScanning()) { + localMediaManager.startScan() + } muteAwaitConnectionManager.startListening() playbackType = controller?.playbackInfo?.playbackType ?: PLAYBACK_TYPE_UNKNOWN playbackVolumeControlId = controller?.playbackInfo?.volumeControlId @@ -195,7 +198,9 @@ constructor( if (started) { started = false controller?.unregisterCallback(this) - localMediaManager.stopScan() + if (!Flags.removeUnnecessaryRouteScanning()) { + localMediaManager.stopScan() + } localMediaManager.unregisterCallback(this) muteAwaitConnectionManager.stopListening() configurationController.removeCallback(configListener) diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/interactor/MediaCarouselInteractor.kt b/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/interactor/MediaCarouselInteractor.kt index c3ba913c2105..b04e93835418 100644 --- a/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/interactor/MediaCarouselInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/interactor/MediaCarouselInteractor.kt @@ -37,6 +37,7 @@ import com.android.systemui.media.controls.domain.resume.MediaResumeListener import com.android.systemui.media.controls.shared.model.MediaCommonModel import com.android.systemui.media.controls.shared.model.MediaDataLoadingModel import com.android.systemui.media.controls.shared.model.SmartspaceMediaLoadingModel +import com.android.systemui.media.controls.util.MediaControlsRefactorFlag import com.android.systemui.media.controls.util.MediaFlags import java.io.PrintWriter import javax.inject.Inject @@ -156,13 +157,19 @@ constructor( mediaDataProcessor.onSessionDestroyed(key) } mediaResumeListener.setManager(this) - mediaDataFilter.mediaDataManager = this + mediaDataFilter.mediaDataProcessor = mediaDataProcessor } - override fun setInactive(key: String, timedOut: Boolean, forceUpdate: Boolean) { - mediaDataProcessor.setInactive(key, timedOut, forceUpdate) + override fun addListener(listener: MediaDataManager.Listener) { + mediaDataFilter.addListener(listener) } + override fun removeListener(listener: MediaDataManager.Listener) { + mediaDataFilter.removeListener(listener) + } + + override fun setInactive(key: String, timedOut: Boolean, forceUpdate: Boolean) = unsupported + override fun onNotificationAdded(key: String, sbn: StatusBarNotification) { mediaDataProcessor.onNotificationAdded(key, sbn) } @@ -207,9 +214,7 @@ constructor( return mediaDataProcessor.dismissSmartspaceRecommendation(key, delay) } - override fun setRecommendationInactive(key: String) { - mediaDataProcessor.setRecommendationInactive(key) - } + override fun setRecommendationInactive(key: String) = unsupported override fun onNotificationRemoved(key: String) { mediaDataProcessor.onNotificationRemoved(key) @@ -240,4 +245,12 @@ constructor( override fun dump(pw: PrintWriter, args: Array<out String>) { mediaDeviceManager.dump(pw) } + + companion object { + val unsupported: Nothing + get() = + error( + "Code path not supported when ${MediaControlsRefactorFlag.FLAG_NAME} is enabled" + ) + } } diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/binder/MediaControlViewBinder.kt b/packages/SystemUI/src/com/android/systemui/media/controls/ui/binder/MediaControlViewBinder.kt new file mode 100644 index 000000000000..14a917999bb7 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/binder/MediaControlViewBinder.kt @@ -0,0 +1,65 @@ +/* + * 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.media.controls.ui.binder + +import android.widget.ImageButton +import androidx.constraintlayout.widget.ConstraintSet +import com.android.systemui.res.R + +object MediaControlViewBinder { + + fun setVisibleAndAlpha(set: ConstraintSet, resId: Int, visible: Boolean) { + setVisibleAndAlpha(set, resId, visible, ConstraintSet.GONE) + } + + private fun setVisibleAndAlpha( + set: ConstraintSet, + resId: Int, + visible: Boolean, + notVisibleValue: Int + ) { + set.setVisibility(resId, if (visible) ConstraintSet.VISIBLE else notVisibleValue) + set.setAlpha(resId, if (visible) 1.0f else 0.0f) + } + + fun updateSeekBarVisibility(constraintSet: ConstraintSet, isSeekBarEnabled: Boolean) { + if (isSeekBarEnabled) { + constraintSet.setVisibility(R.id.media_progress_bar, ConstraintSet.VISIBLE) + constraintSet.setAlpha(R.id.media_progress_bar, 1.0f) + } else { + constraintSet.setVisibility(R.id.media_progress_bar, ConstraintSet.INVISIBLE) + constraintSet.setAlpha(R.id.media_progress_bar, 0.0f) + } + } + + fun setSemanticButtonVisibleAndAlpha( + button: ImageButton, + expandedSet: ConstraintSet, + collapsedSet: ConstraintSet, + visible: Boolean, + notVisibleValue: Int, + showInCollapsed: Boolean + ) { + if (notVisibleValue == ConstraintSet.INVISIBLE) { + // Since time views should appear instead of buttons. + button.isFocusable = visible + button.isClickable = visible + } + setVisibleAndAlpha(expandedSet, button.id, visible, notVisibleValue) + setVisibleAndAlpha(collapsedSet, button.id, visible = visible && showInCollapsed) + } +} diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaViewController.kt b/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaViewController.kt index b315cac28953..7fced5f8036f 100644 --- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaViewController.kt +++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaViewController.kt @@ -16,41 +16,73 @@ package com.android.systemui.media.controls.ui.controller +import android.animation.Animator +import android.animation.AnimatorInflater +import android.animation.AnimatorSet import android.content.Context import android.content.res.Configuration +import android.graphics.Color +import android.graphics.Paint +import android.graphics.drawable.Drawable +import android.provider.Settings +import android.view.View +import android.view.animation.Interpolator import androidx.annotation.VisibleForTesting import androidx.constraintlayout.widget.ConstraintSet import androidx.constraintlayout.widget.ConstraintSet.MATCH_CONSTRAINT +import com.android.app.animation.Interpolators import com.android.app.tracing.traceSection +import com.android.systemui.Flags +import com.android.systemui.dagger.qualifiers.Main +import com.android.systemui.media.controls.ui.animation.ColorSchemeTransition +import com.android.systemui.media.controls.ui.animation.MetadataAnimationHandler +import com.android.systemui.media.controls.ui.binder.MediaControlViewBinder +import com.android.systemui.media.controls.ui.binder.SeekBarObserver import com.android.systemui.media.controls.ui.controller.MediaCarouselController.Companion.calculateAlpha import com.android.systemui.media.controls.ui.view.GutsViewHolder import com.android.systemui.media.controls.ui.view.MediaHostState import com.android.systemui.media.controls.ui.view.MediaViewHolder import com.android.systemui.media.controls.ui.view.RecommendationViewHolder +import com.android.systemui.media.controls.ui.viewmodel.MediaControlViewModel +import com.android.systemui.media.controls.ui.viewmodel.SeekBarViewModel import com.android.systemui.media.controls.util.MediaFlags import com.android.systemui.res.R import com.android.systemui.statusbar.policy.ConfigurationController +import com.android.systemui.surfaceeffects.PaintDrawCallback +import com.android.systemui.surfaceeffects.loadingeffect.LoadingEffect +import com.android.systemui.surfaceeffects.loadingeffect.LoadingEffectView +import com.android.systemui.surfaceeffects.ripple.MultiRippleController +import com.android.systemui.surfaceeffects.turbulencenoise.TurbulenceNoiseAnimationConfig +import com.android.systemui.surfaceeffects.turbulencenoise.TurbulenceNoiseController +import com.android.systemui.surfaceeffects.turbulencenoise.TurbulenceNoiseShader +import com.android.systemui.surfaceeffects.turbulencenoise.TurbulenceNoiseView import com.android.systemui.util.animation.MeasurementInput import com.android.systemui.util.animation.MeasurementOutput import com.android.systemui.util.animation.TransitionLayout import com.android.systemui.util.animation.TransitionLayoutController import com.android.systemui.util.animation.TransitionViewState +import com.android.systemui.util.concurrency.DelayableExecutor +import com.android.systemui.util.settings.GlobalSettings import java.lang.Float.max import java.lang.Float.min +import java.util.Random import javax.inject.Inject /** * A class responsible for controlling a single instance of a media player handling interactions * with the view instance and keeping the media view states up to date. */ -class MediaViewController +open class MediaViewController @Inject constructor( private val context: Context, private val configurationController: ConfigurationController, private val mediaHostStatesManager: MediaHostStatesManager, private val logger: MediaViewLogger, + private val seekBarViewModel: SeekBarViewModel, + @Main private val mainExecutor: DelayableExecutor, private val mediaFlags: MediaFlags, + private val globalSettings: GlobalSettings, ) { /** @@ -131,6 +163,72 @@ constructor( return transitionLayout?.translationY ?: 0.0f } + /** Whether artwork is bound. */ + var isArtworkBound: Boolean = false + + /** previous background artwork */ + var prevArtwork: Drawable? = null + + /** Whether scrubbing time can show */ + var canShowScrubbingTime: Boolean = false + + /** Whether user is touching the seek bar to change the position */ + var isScrubbing: Boolean = false + + var isSeekBarEnabled: Boolean = false + + /** Not visible value for previous button when scrubbing */ + private var prevNotVisibleValue = ConstraintSet.GONE + private var isPrevButtonAvailable = false + + /** Not visible value for next button when scrubbing */ + private var nextNotVisibleValue = ConstraintSet.GONE + private var isNextButtonAvailable = false + + private lateinit var mediaViewHolder: MediaViewHolder + private lateinit var seekBarObserver: SeekBarObserver + private lateinit var turbulenceNoiseController: TurbulenceNoiseController + private lateinit var loadingEffect: LoadingEffect + private lateinit var turbulenceNoiseAnimationConfig: TurbulenceNoiseAnimationConfig + private lateinit var noiseDrawCallback: PaintDrawCallback + private lateinit var stateChangedCallback: LoadingEffect.AnimationStateChangedCallback + internal lateinit var metadataAnimationHandler: MetadataAnimationHandler + internal lateinit var colorSchemeTransition: ColorSchemeTransition + internal lateinit var multiRippleController: MultiRippleController + + private val scrubbingChangeListener = + object : SeekBarViewModel.ScrubbingChangeListener { + override fun onScrubbingChanged(scrubbing: Boolean) { + if (!mediaFlags.isMediaControlsRefactorEnabled()) return + if (isScrubbing == scrubbing) return + isScrubbing = scrubbing + updateDisplayForScrubbingChange() + } + } + + private val enabledChangeListener = + object : SeekBarViewModel.EnabledChangeListener { + override fun onEnabledChanged(enabled: Boolean) { + if (!mediaFlags.isMediaControlsRefactorEnabled()) return + if (isSeekBarEnabled == enabled) return + isSeekBarEnabled = enabled + MediaControlViewBinder.updateSeekBarVisibility(expandedLayout, isSeekBarEnabled) + } + } + + /** + * Sets the listening state of the player. + * + * Should be set to true when the QS panel is open. Otherwise, false. This is a signal to avoid + * unnecessary work when the QS panel is closed. + * + * @param listening True when player should be active. Otherwise, false. + */ + fun setListening(listening: Boolean) { + if (!mediaFlags.isMediaControlsRefactorEnabled()) return + seekBarViewModel.listening = listening + } + /** A callback for config changes */ private val configurationListener = object : ConfigurationController.ConfigurationListener { @@ -232,6 +330,14 @@ constructor( * Notify this controller that the view has been removed and all listeners should be destroyed */ fun onDestroy() { + if (mediaFlags.isMediaControlsRefactorEnabled()) { + if (this::seekBarObserver.isInitialized) { + seekBarViewModel.progress.removeObserver(seekBarObserver) + } + seekBarViewModel.removeScrubbingChangeListener(scrubbingChangeListener) + seekBarViewModel.removeEnabledChangeListener(enabledChangeListener) + seekBarViewModel.onDestroy() + } mediaHostStatesManager.removeController(this) configurationController.removeCallback(configurationListener) } @@ -546,6 +652,178 @@ constructor( ) } + fun attachPlayer(mediaViewHolder: MediaViewHolder) { + if (!mediaFlags.isMediaControlsRefactorEnabled()) return + this.mediaViewHolder = mediaViewHolder + + // Setting up seek bar. + seekBarObserver = SeekBarObserver(mediaViewHolder) + seekBarViewModel.progress.observeForever(seekBarObserver) + seekBarViewModel.attachTouchHandlers(mediaViewHolder.seekBar) + seekBarViewModel.setScrubbingChangeListener(scrubbingChangeListener) + seekBarViewModel.setEnabledChangeListener(enabledChangeListener) + + val mediaCard = mediaViewHolder.player + attach(mediaViewHolder.player, TYPE.PLAYER) + + val turbulenceNoiseView = mediaViewHolder.turbulenceNoiseView + turbulenceNoiseController = TurbulenceNoiseController(turbulenceNoiseView) + + multiRippleController = MultiRippleController(mediaViewHolder.multiRippleView) + + // Metadata Animation + val titleText = mediaViewHolder.titleText + val artistText = mediaViewHolder.artistText + val explicitIndicator = mediaViewHolder.explicitIndicator + val enter = + loadAnimator( + mediaCard.context, + R.anim.media_metadata_enter, + Interpolators.EMPHASIZED_DECELERATE, + titleText, + artistText, + explicitIndicator + ) + val exit = + loadAnimator( + mediaCard.context, + R.anim.media_metadata_exit, + Interpolators.EMPHASIZED_ACCELERATE, + titleText, + artistText, + explicitIndicator + ) + metadataAnimationHandler = MetadataAnimationHandler(exit, enter) + + colorSchemeTransition = + ColorSchemeTransition( + mediaCard.context, + mediaViewHolder, + multiRippleController, + turbulenceNoiseController + ) + + // For Turbulence noise. + val loadingEffectView = mediaViewHolder.loadingEffectView + turbulenceNoiseAnimationConfig = + createTurbulenceNoiseConfig( + loadingEffectView, + turbulenceNoiseView, + colorSchemeTransition + ) + noiseDrawCallback = + object : PaintDrawCallback { + override fun onDraw(paint: Paint) { + loadingEffectView.draw(paint) + } + } + stateChangedCallback = + object : LoadingEffect.AnimationStateChangedCallback { + override fun onStateChanged( + oldState: LoadingEffect.AnimationState, + newState: LoadingEffect.AnimationState + ) { + if (newState === LoadingEffect.AnimationState.NOT_PLAYING) { + loadingEffectView.visibility = View.INVISIBLE + } else { + loadingEffectView.visibility = View.VISIBLE + } + } + } + } + + fun updateAnimatorDurationScale() { + if (!mediaFlags.isMediaControlsRefactorEnabled()) return + if (this::seekBarObserver.isInitialized) { + seekBarObserver.animationEnabled = + globalSettings.getFloat(Settings.Global.ANIMATOR_DURATION_SCALE, 1f) > 0f + } + } + + /** update view with the needed UI changes when user touches seekbar. */ + private fun updateDisplayForScrubbingChange() { + mainExecutor.execute { + val isTimeVisible = canShowScrubbingTime && isScrubbing + MediaControlViewBinder.setVisibleAndAlpha( + expandedLayout, + mediaViewHolder.scrubbingTotalTimeView.id, + isTimeVisible + ) + MediaControlViewBinder.setVisibleAndAlpha( + expandedLayout, + mediaViewHolder.scrubbingElapsedTimeView.id, + isTimeVisible + ) + + MediaControlViewModel.SEMANTIC_ACTIONS_HIDE_WHEN_SCRUBBING.forEach { id -> + val isButtonVisible: Boolean + val notVisibleValue: Int + when (id) { + R.id.actionPrev -> { + isButtonVisible = isPrevButtonAvailable && !isTimeVisible + notVisibleValue = prevNotVisibleValue + } + R.id.actionNext -> { + isButtonVisible = isNextButtonAvailable && !isTimeVisible + notVisibleValue = nextNotVisibleValue + } + else -> { + isButtonVisible = !isTimeVisible + notVisibleValue = ConstraintSet.GONE + } + } + MediaControlViewBinder.setSemanticButtonVisibleAndAlpha( + mediaViewHolder.getAction(id), + expandedLayout, + collapsedLayout, + isButtonVisible, + notVisibleValue, + showInCollapsed = true + ) + } + + if (!metadataAnimationHandler.isRunning) { + refreshState() + } + } + } + + fun bindSeekBar(onSeek: () -> Unit, onBindSeekBar: (SeekBarViewModel) -> Unit) { + if (!mediaFlags.isMediaControlsRefactorEnabled()) return + seekBarViewModel.logSeek = onSeek + onBindSeekBar.invoke(seekBarViewModel) + } + + fun setUpTurbulenceNoise() { + if (!mediaFlags.isMediaControlsRefactorEnabled()) return + if (Flags.shaderlibLoadingEffectRefactor()) { + if (!this::loadingEffect.isInitialized) { + loadingEffect = + LoadingEffect( + TurbulenceNoiseShader.Companion.Type.SIMPLEX_NOISE, + turbulenceNoiseAnimationConfig, + noiseDrawCallback, + stateChangedCallback + ) + } + colorSchemeTransition.loadingEffect = loadingEffect + loadingEffect.play() + mainExecutor.executeDelayed( + loadingEffect::finish, + MediaControlViewModel.TURBULENCE_NOISE_PLAY_MS_DURATION + ) + } else { + turbulenceNoiseController.play( + TurbulenceNoiseShader.Companion.Type.SIMPLEX_NOISE, + turbulenceNoiseAnimationConfig + ) + mainExecutor.executeDelayed( + turbulenceNoiseController::finish, + MediaControlViewModel.TURBULENCE_NOISE_PLAY_MS_DURATION + ) + } + } + /** * Obtain a measurement for a given location. This makes sure that the state is up to date and * all widgets know their location. Calling this method may create a measurement if we don't @@ -801,6 +1079,75 @@ constructor( applyImmediately = true ) } + + @VisibleForTesting + protected open fun loadAnimator( + context: Context, + animId: Int, + motionInterpolator: Interpolator?, + vararg targets: View? + ): AnimatorSet { + val animators = ArrayList<Animator>() + for (target in targets) { + val animator = AnimatorInflater.loadAnimator(context, animId) as AnimatorSet + animator.childAnimations[0].interpolator = motionInterpolator + animator.setTarget(target) + animators.add(animator) + } + val result = AnimatorSet() + result.playTogether(animators) + return result + } + + private fun createTurbulenceNoiseConfig( + loadingEffectView: LoadingEffectView, + turbulenceNoiseView: TurbulenceNoiseView, + colorSchemeTransition: ColorSchemeTransition + ): TurbulenceNoiseAnimationConfig { + val targetView: View = + if (Flags.shaderlibLoadingEffectRefactor()) { + loadingEffectView + } else { + turbulenceNoiseView + } + val width = targetView.width + val height = targetView.height + val random = Random() + return TurbulenceNoiseAnimationConfig( + gridCount = 2.14f, + TurbulenceNoiseAnimationConfig.DEFAULT_LUMINOSITY_MULTIPLIER, + random.nextFloat(), + random.nextFloat(), + random.nextFloat(), + noiseMoveSpeedX = 0.42f, + noiseMoveSpeedY = 0f, + TurbulenceNoiseAnimationConfig.DEFAULT_NOISE_SPEED_Z, + // Color will be correctly updated in ColorSchemeTransition. + colorSchemeTransition.accentPrimary.currentColor, + screenColor = Color.BLACK, + width.toFloat(), + height.toFloat(), + TurbulenceNoiseAnimationConfig.DEFAULT_MAX_DURATION_IN_MILLIS, + easeInDuration = 1350f, + easeOutDuration = 1350f, + targetView.context.resources.displayMetrics.density, + lumaMatteBlendFactor = 0.26f, + lumaMatteOverallBrightness = 0.09f, + shouldInverseNoiseLuminosity = false + ) + } + + fun setUpPrevButtonInfo(isAvailable: Boolean, notVisibleValue: Int = ConstraintSet.GONE) { + if (!mediaFlags.isMediaControlsRefactorEnabled()) return + isPrevButtonAvailable = isAvailable + prevNotVisibleValue = notVisibleValue + } + + fun setUpNextButtonInfo(isAvailable: Boolean, notVisibleValue: Int = ConstraintSet.GONE) { + if (!mediaFlags.isMediaControlsRefactorEnabled()) return + isNextButtonAvailable = isAvailable + nextNotVisibleValue = notVisibleValue + } } /** An internal key for the cache of mediaViewStates. This is a subset of the full host state. */ diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/viewmodel/MediaActionViewModel.kt b/packages/SystemUI/src/com/android/systemui/media/controls/ui/viewmodel/MediaActionViewModel.kt index 1e67a77250ee..82099e61009f 100644 --- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/viewmodel/MediaActionViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/viewmodel/MediaActionViewModel.kt @@ -24,7 +24,8 @@ data class MediaActionViewModel( val icon: Drawable?, val contentDescription: CharSequence?, val background: Drawable?, - val isVisible: Boolean = true, + /** whether action is visible if user is touching seekbar to change position. */ + val isVisibleWhenScrubbing: Boolean = true, val notVisibleValue: Int = ConstraintSet.GONE, val showInCollapsed: Boolean, val rebindId: Int? = null, diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/viewmodel/MediaControlViewModel.kt b/packages/SystemUI/src/com/android/systemui/media/controls/ui/viewmodel/MediaControlViewModel.kt index 7c599953f9b9..d74506dc2e8d 100644 --- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/viewmodel/MediaControlViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/viewmodel/MediaControlViewModel.kt @@ -18,6 +18,7 @@ package com.android.systemui.media.controls.ui.viewmodel import android.content.Context import android.content.pm.PackageManager +import android.media.session.MediaController import android.media.session.MediaSession.Token import android.text.TextUtils import android.util.Log @@ -40,6 +41,7 @@ import com.android.systemui.monet.ColorScheme import com.android.systemui.monet.Style import com.android.systemui.res.R import com.android.systemui.util.kotlin.sample +import java.util.concurrent.Executor import kotlinx.coroutines.CoroutineDispatcher import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.MutableStateFlow @@ -51,6 +53,7 @@ import kotlinx.coroutines.flow.flowOn class MediaControlViewModel( @Application private val applicationContext: Context, @Background private val backgroundDispatcher: CoroutineDispatcher, + @Background private val backgroundExecutor: Executor, private val interactor: MediaControlInteractor, private val logger: MediaUiEventLogger, ) { @@ -124,13 +127,15 @@ class MediaControlViewModel( } }, backgroundCover = model.artwork, - appIcon = getAppIcon(model.appIcon, model.isResume, model.packageName), + appIcon = model.appIcon, + launcherIcon = getIconFromApp(model.packageName), useGrayColorFilter = model.appIcon == null || model.isResume, artistName = model.artistName ?: "", titleName = model.songName ?: "", isExplicitVisible = model.showExplicit, + shouldAddGradient = wallpaperColors != null, colorScheme = scheme, - isTimeVisible = canShowScrubbingTimeViews(model.semanticActionButtons), + canShowTime = canShowScrubbingTimeViews(model.semanticActionButtons), playTurbulenceNoise = playTurbulenceNoise, useSemanticActions = model.semanticActionButtons != null, actionButtons = toActionViewModels(model), @@ -146,6 +151,21 @@ class MediaControlViewModel( onLongClicked = { logger.logLongPressOpen(model.uid, model.packageName, model.instanceId) }, + onSeek = { + logger.logSeek(model.uid, model.packageName, model.instanceId) + // TODO (b/330897926) log smartspace card reported (SMARTSPACE_CARD_CLICK_EVENT) + }, + onBindSeekbar = { seekBarViewModel -> + if (model.isResume && model.resumeProgress != null) { + seekBarViewModel.updateStaticProgress(model.resumeProgress) + } else { + backgroundExecutor.execute { + seekBarViewModel.updateController( + model.token?.let { MediaController(applicationContext, it) } + ) + } + } + } ) } @@ -278,16 +298,16 @@ class MediaControlViewModel( model: MediaControlModel, mediaAction: MediaAction, buttonId: Int, - isScrubbingTimeEnabled: Boolean + canShowScrubbingTimeViews: Boolean ): MediaActionViewModel { val showInCollapsed = SEMANTIC_ACTIONS_COMPACT.contains(buttonId) val hideWhenScrubbing = SEMANTIC_ACTIONS_HIDE_WHEN_SCRUBBING.contains(buttonId) - val shouldHideDueToScrubbing = isScrubbingTimeEnabled && hideWhenScrubbing + val shouldHideWhenScrubbing = canShowScrubbingTimeViews && hideWhenScrubbing return MediaActionViewModel( icon = mediaAction.icon, contentDescription = mediaAction.contentDescription, background = mediaAction.background, - isVisible = !shouldHideDueToScrubbing, + isVisibleWhenScrubbing = !shouldHideWhenScrubbing, notVisibleValue = if ( (buttonId == R.id.actionPrev && model.semanticActionButtons!!.reservePrev) || @@ -342,19 +362,6 @@ class MediaControlViewModel( action.run() } - private fun getAppIcon( - icon: android.graphics.drawable.Icon?, - isResume: Boolean, - packageName: String - ): Icon { - if (icon != null && !isResume) { - icon.loadDrawable(applicationContext)?.let { drawable -> - return Icon.Loaded(drawable, null) - } - } - return getIconFromApp(packageName) - } - private fun getIconFromApp(packageName: String): Icon { return try { Icon.Loaded(applicationContext.packageManager.getApplicationIcon(packageName), null) @@ -381,17 +388,17 @@ class MediaControlViewModel( private const val DISABLED_ALPHA = 0.38f /** Buttons to show in small player when using semantic actions */ - private val SEMANTIC_ACTIONS_COMPACT = + val SEMANTIC_ACTIONS_COMPACT = listOf(R.id.actionPlayPause, R.id.actionPrev, R.id.actionNext) /** * Buttons that should get hidden when we are scrubbing (they will be replaced with the * views showing scrubbing time) */ - private val SEMANTIC_ACTIONS_HIDE_WHEN_SCRUBBING = listOf(R.id.actionPrev, R.id.actionNext) + val SEMANTIC_ACTIONS_HIDE_WHEN_SCRUBBING = listOf(R.id.actionPrev, R.id.actionNext) /** Buttons to show in player when using semantic actions. */ - private val SEMANTIC_ACTIONS_ALL = + val SEMANTIC_ACTIONS_ALL = listOf( R.id.actionPlayPause, R.id.actionPrev, @@ -399,5 +406,9 @@ class MediaControlViewModel( R.id.action0, R.id.action1 ) + + const val TURBULENCE_NOISE_PLAY_MS_DURATION = 7500L + const val MEDIA_PLAYER_SCRIM_START_ALPHA = 0.25f + const val MEDIA_PLAYER_SCRIM_END_ALPHA = 1.0f } } diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/viewmodel/MediaPlayerViewModel.kt b/packages/SystemUI/src/com/android/systemui/media/controls/ui/viewmodel/MediaPlayerViewModel.kt index 9029a65bd9ee..d1014e83ea11 100644 --- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/viewmodel/MediaPlayerViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/viewmodel/MediaPlayerViewModel.kt @@ -24,13 +24,15 @@ import com.android.systemui.monet.ColorScheme data class MediaPlayerViewModel( val contentDescription: (Boolean) -> CharSequence, val backgroundCover: android.graphics.drawable.Icon?, - val appIcon: Icon, + val appIcon: android.graphics.drawable.Icon?, + val launcherIcon: Icon, val useGrayColorFilter: Boolean, val artistName: CharSequence, val titleName: CharSequence, val isExplicitVisible: Boolean, + val shouldAddGradient: Boolean, val colorScheme: ColorScheme, - val isTimeVisible: Boolean, + val canShowTime: Boolean, val playTurbulenceNoise: Boolean, val useSemanticActions: Boolean, val actionButtons: List<MediaActionViewModel?>, @@ -38,4 +40,6 @@ data class MediaPlayerViewModel( val gutsMenu: GutsViewModel, val onClicked: (Expandable) -> Unit, val onLongClicked: () -> Unit, + val onSeek: () -> Unit, + val onBindSeekbar: (SeekBarViewModel) -> Unit, ) diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/util/MediaFlags.kt b/packages/SystemUI/src/com/android/systemui/media/controls/util/MediaFlags.kt index d4bd6daedfab..4e77d13b3303 100644 --- a/packages/SystemUI/src/com/android/systemui/media/controls/util/MediaFlags.kt +++ b/packages/SystemUI/src/com/android/systemui/media/controls/util/MediaFlags.kt @@ -21,16 +21,11 @@ import android.os.UserHandle import com.android.systemui.dagger.SysUISingleton import com.android.systemui.flags.FeatureFlagsClassic import com.android.systemui.flags.Flags -import com.android.systemui.scene.shared.flag.SceneContainerFlags +import com.android.systemui.scene.shared.flag.SceneContainerFlag import javax.inject.Inject @SysUISingleton -class MediaFlags -@Inject -constructor( - private val featureFlags: FeatureFlagsClassic, - private val sceneContainerFlags: SceneContainerFlags -) { +class MediaFlags @Inject constructor(private val featureFlags: FeatureFlagsClassic) { /** * Check whether media control actions should be based on PlaybackState instead of notification */ @@ -57,7 +52,7 @@ constructor( /** Check whether to use scene framework */ fun isSceneContainerEnabled() = - sceneContainerFlags.isEnabled() && MediaInSceneContainerFlag.isEnabled + SceneContainerFlag.isEnabled && MediaInSceneContainerFlag.isEnabled /** Check whether to use media refactor code */ fun isMediaControlsRefactorEnabled() = MediaControlsRefactorFlag.isEnabled diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSContainerImplController.java b/packages/SystemUI/src/com/android/systemui/qs/QSContainerImplController.java index ffbc56098e26..7ccdf0ac301a 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/QSContainerImplController.java +++ b/packages/SystemUI/src/com/android/systemui/qs/QSContainerImplController.java @@ -24,7 +24,7 @@ import android.view.View; import com.android.systemui.plugins.FalsingManager; import com.android.systemui.qs.dagger.QSScope; -import com.android.systemui.scene.shared.flag.SceneContainerFlags; +import com.android.systemui.scene.shared.flag.SceneContainerFlag; import com.android.systemui.statusbar.policy.ConfigurationController; import com.android.systemui.util.ViewController; @@ -66,15 +66,14 @@ public class QSContainerImplController extends ViewController<QSContainerImpl> { QSPanelController qsPanelController, QuickStatusBarHeaderController quickStatusBarHeaderController, ConfigurationController configurationController, - FalsingManager falsingManager, - SceneContainerFlags sceneContainerFlags) { + FalsingManager falsingManager) { super(view); mQsPanelController = qsPanelController; mQuickStatusBarHeaderController = quickStatusBarHeaderController; mConfigurationController = configurationController; mFalsingManager = falsingManager; mQSPanelContainer = mView.getQSPanelContainer(); - mSceneContainerEnabled = sceneContainerFlags.isEnabled(); + mSceneContainerEnabled = SceneContainerFlag.isEnabled(); } @Override diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSImpl.java b/packages/SystemUI/src/com/android/systemui/qs/QSImpl.java index a0607e9f859a..1f4838e85e79 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/QSImpl.java +++ b/packages/SystemUI/src/com/android/systemui/qs/QSImpl.java @@ -60,7 +60,7 @@ import com.android.systemui.qs.footer.ui.binder.FooterActionsViewBinder; import com.android.systemui.qs.footer.ui.viewmodel.FooterActionsViewModel; import com.android.systemui.qs.logging.QSLogger; import com.android.systemui.res.R; -import com.android.systemui.scene.shared.flag.SceneContainerFlags; +import com.android.systemui.scene.shared.flag.SceneContainerFlag; import com.android.systemui.settings.brightness.MirrorController; import com.android.systemui.shade.transition.LargeScreenShadeInterpolator; import com.android.systemui.statusbar.CommandQueue; @@ -174,8 +174,6 @@ public class QSImpl implements QS, CommandQueue.Callbacks, StatusBarStateControl @Nullable private View mFooterActionsView; - private final SceneContainerFlags mSceneContainerFlags; - @Inject public QSImpl(RemoteInputQuickSettingsDisabler remoteInputQsDisabler, SysuiStatusBarStateController statusBarStateController, CommandQueue commandQueue, @@ -188,8 +186,7 @@ public class QSImpl implements QS, CommandQueue.Callbacks, StatusBarStateControl FooterActionsViewModel.Factory footerActionsViewModelFactory, FooterActionsViewBinder footerActionsViewBinder, LargeScreenShadeInterpolator largeScreenShadeInterpolator, - FeatureFlags featureFlags, - SceneContainerFlags sceneContainerFlags) { + FeatureFlags featureFlags) { mRemoteInputQuickSettingsDisabler = remoteInputQsDisabler; mQsMediaHost = qsMediaHost; mQqsMediaHost = qqsMediaHost; @@ -205,8 +202,7 @@ public class QSImpl implements QS, CommandQueue.Callbacks, StatusBarStateControl mFooterActionsViewModelFactory = footerActionsViewModelFactory; mFooterActionsViewBinder = footerActionsViewBinder; mListeningAndVisibilityLifecycleOwner = new ListeningAndVisibilityLifecycleOwner(); - mSceneContainerFlags = sceneContainerFlags; - if (mSceneContainerFlags.isEnabled()) { + if (SceneContainerFlag.isEnabled()) { mStatusBarState = StatusBarState.SHADE; } } @@ -224,7 +220,7 @@ public class QSImpl implements QS, CommandQueue.Callbacks, StatusBarStateControl mQSPanelController.init(); mQuickQSPanelController.init(); - if (!mSceneContainerFlags.isEnabled()) { + if (!SceneContainerFlag.isEnabled()) { mQSFooterActionsViewModel = mFooterActionsViewModelFactory .create(mListeningAndVisibilityLifecycleOwner); bindFooterActionsView(mRootView); @@ -249,7 +245,7 @@ public class QSImpl implements QS, CommandQueue.Callbacks, StatusBarStateControl mScrollListener.onQsPanelScrollChanged(scrollY); } }); - mQSPanelScrollView.setScrollingEnabled(!mSceneContainerFlags.isEnabled()); + mQSPanelScrollView.setScrollingEnabled(!SceneContainerFlag.isEnabled()); mHeader = mRootView.findViewById(R.id.header); mFooter = qsComponent.getQSFooter(); @@ -509,7 +505,7 @@ public class QSImpl implements QS, CommandQueue.Callbacks, StatusBarStateControl @VisibleForTesting boolean isKeyguardState() { - if (mSceneContainerFlags.isEnabled()) { + if (SceneContainerFlag.isEnabled()) { return false; } else { // We want the freshest state here since otherwise we'll have some weirdness if earlier @@ -573,7 +569,7 @@ public class QSImpl implements QS, CommandQueue.Callbacks, StatusBarStateControl } private void setKeyguardShowing(boolean keyguardShowing) { - if (!mSceneContainerFlags.isEnabled()) { + if (!SceneContainerFlag.isEnabled()) { if (DEBUG) Log.d(TAG, "setKeyguardShowing " + keyguardShowing); mLastQSExpansion = -1; @@ -651,7 +647,7 @@ public class QSImpl implements QS, CommandQueue.Callbacks, StatusBarStateControl @Override public int getHeightDiff() { - if (mSceneContainerFlags.isEnabled()) { + if (SceneContainerFlag.isEnabled()) { return mQSPanelController.getViewBottom() - mHeader.getBottom() + mHeader.getPaddingBottom(); } else { @@ -720,7 +716,7 @@ public class QSImpl implements QS, CommandQueue.Callbacks, StatusBarStateControl mQSPanelController.getTileLayout().setExpansion(expansion, proposedTranslation); mQuickQSPanelController.getTileLayout().setExpansion(expansion, proposedTranslation); - if (!mSceneContainerFlags.isEnabled()) { + if (!SceneContainerFlag.isEnabled()) { float qsScrollViewTranslation = onKeyguard && !mShowCollapsedOnKeyguard ? panelTranslationY : 0; mQSPanelScrollView.setTranslationY(qsScrollViewTranslation); @@ -824,7 +820,7 @@ public class QSImpl implements QS, CommandQueue.Callbacks, StatusBarStateControl mQsBounds.set(-sideMargin, 0, mQSPanelScrollView.getWidth() + sideMargin, mQSPanelScrollView.getHeight()); } - if (!mSceneContainerFlags.isEnabled()) { + if (!SceneContainerFlag.isEnabled()) { mQSPanelScrollView.setClipBounds(mQsBounds); mQSPanelScrollView.getLocationOnScreen(mLocationTemp); @@ -907,7 +903,7 @@ public class QSImpl implements QS, CommandQueue.Callbacks, StatusBarStateControl // The customize state changed, so our height changed. mContainer.updateExpansion(); boolean customizing = isCustomizing(); - if (mSceneContainerFlags.isEnabled()) { + if (SceneContainerFlag.isEnabled()) { mQSPanelController.setVisibility(!customizing ? View.VISIBLE : View.INVISIBLE); } else { mQSPanelScrollView.setVisibility(!customizing ? View.VISIBLE : View.INVISIBLE); @@ -984,7 +980,7 @@ public class QSImpl implements QS, CommandQueue.Callbacks, StatusBarStateControl @Override public void onStateChanged(int newState) { - if (mSceneContainerFlags.isEnabled() || newState == mStatusBarState) { + if (SceneContainerFlag.isEnabled() || newState == mStatusBarState) { return; } mStatusBarState = newState; diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSPanelController.java b/packages/SystemUI/src/com/android/systemui/qs/QSPanelController.java index b8c3c1a2af5f..e24caf19a14b 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/QSPanelController.java +++ b/packages/SystemUI/src/com/android/systemui/qs/QSPanelController.java @@ -37,7 +37,7 @@ import com.android.systemui.plugins.FalsingManager; import com.android.systemui.qs.customize.QSCustomizerController; import com.android.systemui.qs.dagger.QSScope; import com.android.systemui.qs.logging.QSLogger; -import com.android.systemui.scene.shared.flag.SceneContainerFlags; +import com.android.systemui.scene.shared.flag.SceneContainerFlag; import com.android.systemui.settings.brightness.BrightnessController; import com.android.systemui.settings.brightness.BrightnessMirrorHandler; import com.android.systemui.settings.brightness.BrightnessSliderController; @@ -94,7 +94,6 @@ public class QSPanelController extends QSPanelControllerBase<QSPanel> { FalsingManager falsingManager, StatusBarKeyguardViewManager statusBarKeyguardViewManager, SplitShadeStateController splitShadeStateController, - SceneContainerFlags sceneContainerFlags, Provider<QSLongPressEffect> longPRessEffectProvider) { super(view, qsHost, qsCustomizerController, usingMediaPlayer, mediaHost, metricsLogger, uiEventLogger, qsLogger, dumpManager, splitShadeStateController, @@ -113,7 +112,7 @@ public class QSPanelController extends QSPanelControllerBase<QSPanel> { mBrightnessMirrorHandler = new BrightnessMirrorHandler(mBrightnessController); mStatusBarKeyguardViewManager = statusBarKeyguardViewManager; mLastDensity = view.getResources().getConfiguration().densityDpi; - mSceneContainerEnabled = sceneContainerFlags.isEnabled(); + mSceneContainerEnabled = SceneContainerFlag.isEnabled(); } @Override diff --git a/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeaderController.java b/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeaderController.java index 1d92d782be69..bb40d3e69dc1 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeaderController.java +++ b/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeaderController.java @@ -17,7 +17,7 @@ package com.android.systemui.qs; import com.android.systemui.qs.dagger.QSScope; -import com.android.systemui.scene.shared.flag.SceneContainerFlags; +import com.android.systemui.scene.shared.flag.SceneContainerFlag; import com.android.systemui.util.ViewController; import javax.inject.Inject; @@ -34,12 +34,11 @@ class QuickStatusBarHeaderController extends ViewController<QuickStatusBarHeader @Inject QuickStatusBarHeaderController(QuickStatusBarHeader view, - QuickQSPanelController quickQSPanelController, - SceneContainerFlags sceneContainerFlags + QuickQSPanelController quickQSPanelController ) { super(view); mQuickQSPanelController = quickQSPanelController; - mSceneContainerEnabled = sceneContainerFlags.isEnabled(); + mSceneContainerEnabled = SceneContainerFlag.isEnabled(); } @Override protected void onViewAttached() { diff --git a/packages/SystemUI/src/com/android/systemui/qs/customize/QSCustomizerController.java b/packages/SystemUI/src/com/android/systemui/qs/customize/QSCustomizerController.java index 34b1b2d0657a..a222b3cbd08c 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/customize/QSCustomizerController.java +++ b/packages/SystemUI/src/com/android/systemui/qs/customize/QSCustomizerController.java @@ -42,7 +42,7 @@ import com.android.systemui.qs.QSEditEvent; import com.android.systemui.qs.QSHost; import com.android.systemui.qs.dagger.QSScope; import com.android.systemui.res.R; -import com.android.systemui.scene.shared.flag.SceneContainerFlags; +import com.android.systemui.scene.shared.flag.SceneContainerFlag; import com.android.systemui.statusbar.phone.LightBarController; import com.android.systemui.statusbar.policy.ConfigurationController; import com.android.systemui.statusbar.policy.ConfigurationController.ConfigurationListener; @@ -107,8 +107,7 @@ public class QSCustomizerController extends ViewController<QSCustomizer> { protected QSCustomizerController(QSCustomizer view, TileQueryHelper tileQueryHelper, QSHost qsHost, TileAdapter tileAdapter, ScreenLifecycle screenLifecycle, KeyguardStateController keyguardStateController, LightBarController lightBarController, - ConfigurationController configurationController, UiEventLogger uiEventLogger, - SceneContainerFlags sceneContainerFlags) { + ConfigurationController configurationController, UiEventLogger uiEventLogger) { super(view); mTileQueryHelper = tileQueryHelper; mQsHost = qsHost; @@ -118,7 +117,7 @@ public class QSCustomizerController extends ViewController<QSCustomizer> { mLightBarController = lightBarController; mConfigurationController = configurationController; mUiEventLogger = uiEventLogger; - view.setSceneContainerEnabled(sceneContainerFlags.isEnabled()); + view.setSceneContainerEnabled(SceneContainerFlag.isEnabled()); mToolbar = mView.findViewById(com.android.internal.R.id.action_bar); } diff --git a/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java b/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java index 4ece7b6c8990..1ddc0946905c 100644 --- a/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java +++ b/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java @@ -98,7 +98,7 @@ import com.android.systemui.navigationbar.NavigationModeController; import com.android.systemui.navigationbar.buttons.KeyButtonView; import com.android.systemui.recents.OverviewProxyService.OverviewProxyListener; import com.android.systemui.scene.domain.interactor.SceneInteractor; -import com.android.systemui.scene.shared.flag.SceneContainerFlags; +import com.android.systemui.scene.shared.flag.SceneContainerFlag; import com.android.systemui.scene.shared.model.Scenes; import com.android.systemui.settings.DisplayTracker; import com.android.systemui.settings.UserTracker; @@ -146,7 +146,6 @@ public class OverviewProxyService implements CallbackController<OverviewProxyLis private static final long MAX_BACKOFF_MILLIS = 10 * 60 * 1000; private final Context mContext; - private final SceneContainerFlags mSceneContainerFlags; private final Executor mMainExecutor; private final ShellInterface mShellInterface; private final Lazy<ShadeViewController> mShadeViewControllerLazy; @@ -208,7 +207,7 @@ public class OverviewProxyService implements CallbackController<OverviewProxyLis @Override public void onStatusBarTouchEvent(MotionEvent event) { verifyCallerAndClearCallingIdentity("onStatusBarTouchEvent", () -> { - if (mSceneContainerFlags.isEnabled()) { + if (SceneContainerFlag.isEnabled()) { //TODO(b/329863123) implement latency tracking for shade scene Log.i(TAG_OPS, "Scene container enabled. Latency tracking not started."); } else if (event.getActionMasked() == ACTION_DOWN) { @@ -223,7 +222,7 @@ public class OverviewProxyService implements CallbackController<OverviewProxyLis // If scene framework is enabled, set the scene container window to // visible and let the touch "slip" into that window. - if (mSceneContainerFlags.isEnabled()) { + if (SceneContainerFlag.isEnabled()) { mSceneInteractor.get().onRemoteUserInteractionStarted("launcher swipe"); } else { mShadeViewControllerLazy.get().startInputFocusTransfer(); @@ -232,7 +231,7 @@ public class OverviewProxyService implements CallbackController<OverviewProxyLis if (action == ACTION_UP || action == ACTION_CANCEL) { mInputFocusTransferStarted = false; - if (!mSceneContainerFlags.isEnabled()) { + if (!SceneContainerFlag.isEnabled()) { float velocity = (event.getY() - mInputFocusTransferStartY) / (event.getEventTime() - mInputFocusTransferStartMillis); if (action == ACTION_CANCEL) { @@ -612,7 +611,6 @@ public class OverviewProxyService implements CallbackController<OverviewProxyLis KeyguardUnlockAnimationController sysuiUnlockAnimationController, InWindowLauncherUnlockAnimationManager inWindowLauncherUnlockAnimationManager, AssistUtils assistUtils, - SceneContainerFlags sceneContainerFlags, DumpManager dumpManager, Optional<UnfoldTransitionProgressForwarder> unfoldTransitionProgressForwarder, BroadcastDispatcher broadcastDispatcher @@ -624,7 +622,6 @@ public class OverviewProxyService implements CallbackController<OverviewProxyLis } mContext = context; - mSceneContainerFlags = sceneContainerFlags; mMainExecutor = mainExecutor; mShellInterface = shellInterface; mShadeViewControllerLazy = shadeViewControllerLazy; diff --git a/packages/SystemUI/src/com/android/systemui/scene/KeyguardlessSceneContainerFrameworkModule.kt b/packages/SystemUI/src/com/android/systemui/scene/KeyguardlessSceneContainerFrameworkModule.kt index afd0746f4696..8277c73025d8 100644 --- a/packages/SystemUI/src/com/android/systemui/scene/KeyguardlessSceneContainerFrameworkModule.kt +++ b/packages/SystemUI/src/com/android/systemui/scene/KeyguardlessSceneContainerFrameworkModule.kt @@ -16,7 +16,6 @@ package com.android.systemui.scene -import com.android.systemui.scene.shared.flag.SceneContainerFlagsModule import com.android.systemui.scene.shared.model.SceneContainerConfig import com.android.systemui.scene.shared.model.Scenes import dagger.Module @@ -29,7 +28,6 @@ import dagger.Provides EmptySceneModule::class, GoneSceneModule::class, QuickSettingsSceneModule::class, - SceneContainerFlagsModule::class, ShadeSceneModule::class, ], ) diff --git a/packages/SystemUI/src/com/android/systemui/scene/SceneContainerFrameworkModule.kt b/packages/SystemUI/src/com/android/systemui/scene/SceneContainerFrameworkModule.kt index 62b0914fab79..69f9443b334b 100644 --- a/packages/SystemUI/src/com/android/systemui/scene/SceneContainerFrameworkModule.kt +++ b/packages/SystemUI/src/com/android/systemui/scene/SceneContainerFrameworkModule.kt @@ -20,7 +20,6 @@ import com.android.systemui.CoreStartable import com.android.systemui.bouncer.shared.flag.ComposeBouncerFlagsModule import com.android.systemui.scene.domain.interactor.WindowRootViewVisibilityInteractor import com.android.systemui.scene.domain.startable.SceneContainerStartable -import com.android.systemui.scene.shared.flag.SceneContainerFlagsModule import com.android.systemui.scene.shared.model.SceneContainerConfig import com.android.systemui.scene.shared.model.Scenes import dagger.Binds @@ -40,7 +39,6 @@ import dagger.multibindings.IntoMap GoneSceneModule::class, LockscreenSceneModule::class, QuickSettingsSceneModule::class, - SceneContainerFlagsModule::class, ShadeSceneModule::class, ], ) diff --git a/packages/SystemUI/src/com/android/systemui/scene/ShadelessSceneContainerFrameworkModule.kt b/packages/SystemUI/src/com/android/systemui/scene/ShadelessSceneContainerFrameworkModule.kt index 0665c9e1b802..d202c24ae152 100644 --- a/packages/SystemUI/src/com/android/systemui/scene/ShadelessSceneContainerFrameworkModule.kt +++ b/packages/SystemUI/src/com/android/systemui/scene/ShadelessSceneContainerFrameworkModule.kt @@ -16,7 +16,6 @@ package com.android.systemui.scene -import com.android.systemui.scene.shared.flag.SceneContainerFlagsModule import com.android.systemui.scene.shared.model.SceneContainerConfig import com.android.systemui.scene.shared.model.Scenes import dagger.Module @@ -30,7 +29,6 @@ import dagger.Provides EmptySceneModule::class, GoneSceneModule::class, LockscreenSceneModule::class, - SceneContainerFlagsModule::class, ], ) object ShadelessSceneContainerFrameworkModule { diff --git a/packages/SystemUI/src/com/android/systemui/scene/domain/interactor/WindowRootViewVisibilityInteractor.kt b/packages/SystemUI/src/com/android/systemui/scene/domain/interactor/WindowRootViewVisibilityInteractor.kt index c736707ecd2d..1cf1c18749cb 100644 --- a/packages/SystemUI/src/com/android/systemui/scene/domain/interactor/WindowRootViewVisibilityInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/scene/domain/interactor/WindowRootViewVisibilityInteractor.kt @@ -24,7 +24,7 @@ import com.android.systemui.keyguard.data.repository.KeyguardRepository import com.android.systemui.keyguard.shared.model.StatusBarState import com.android.systemui.power.domain.interactor.PowerInteractor import com.android.systemui.scene.data.repository.WindowRootViewVisibilityRepository -import com.android.systemui.scene.shared.flag.SceneContainerFlags +import com.android.systemui.scene.shared.flag.SceneContainerFlag import com.android.systemui.scene.shared.model.Scenes import com.android.systemui.statusbar.NotificationPresenter import com.android.systemui.statusbar.notification.domain.interactor.ActiveNotificationsInteractor @@ -53,7 +53,6 @@ constructor( private val headsUpManager: HeadsUpManager, private val powerInteractor: PowerInteractor, private val activeNotificationsInteractor: ActiveNotificationsInteractor, - sceneContainerFlags: SceneContainerFlags, sceneInteractorProvider: Provider<SceneInteractor>, ) : CoreStartable { @@ -68,7 +67,7 @@ constructor( * false if the bouncer is visible. */ val isLockscreenOrShadeVisible: StateFlow<Boolean> = - if (!sceneContainerFlags.isEnabled()) { + if (!SceneContainerFlag.isEnabled) { windowRootViewVisibilityRepository.isLockscreenOrShadeVisible } else { sceneInteractorProvider diff --git a/packages/SystemUI/src/com/android/systemui/scene/domain/startable/SceneContainerStartable.kt b/packages/SystemUI/src/com/android/systemui/scene/domain/startable/SceneContainerStartable.kt index 0e66c28d4b8d..4774eb32a445 100644 --- a/packages/SystemUI/src/com/android/systemui/scene/domain/startable/SceneContainerStartable.kt +++ b/packages/SystemUI/src/com/android/systemui/scene/domain/startable/SceneContainerStartable.kt @@ -44,7 +44,7 @@ import com.android.systemui.plugins.FalsingManager.FalsingBeliefListener import com.android.systemui.power.domain.interactor.PowerInteractor import com.android.systemui.scene.domain.interactor.SceneContainerOcclusionInteractor import com.android.systemui.scene.domain.interactor.SceneInteractor -import com.android.systemui.scene.shared.flag.SceneContainerFlags +import com.android.systemui.scene.shared.flag.SceneContainerFlag import com.android.systemui.scene.shared.logger.SceneLogger import com.android.systemui.scene.shared.model.Scenes import com.android.systemui.shade.domain.interactor.ShadeInteractor @@ -92,7 +92,6 @@ constructor( private val deviceUnlockedInteractor: DeviceUnlockedInteractor, private val bouncerInteractor: BouncerInteractor, private val keyguardInteractor: KeyguardInteractor, - private val flags: SceneContainerFlags, private val sysUiState: SysUiState, @DisplayId private val displayId: Int, private val sceneLogger: SceneLogger, @@ -111,7 +110,7 @@ constructor( ) : CoreStartable { override fun start() { - if (flags.isEnabled()) { + if (SceneContainerFlag.isEnabled) { sceneLogger.logFrameworkEnabled(isEnabled = true) hydrateVisibility() automaticallySwitchScenes() @@ -124,16 +123,18 @@ constructor( } else { sceneLogger.logFrameworkEnabled( isEnabled = false, - reason = flags.requirementDescription(), + reason = SceneContainerFlag.requirementDescription(), ) } } override fun dump(pw: PrintWriter, args: Array<out String>) = pw.asIndenting().run { - printSection("SceneContainerFlags") { - println("isEnabled", flags.isEnabled()) - printSection("requirementDescription") { println(flags.requirementDescription()) } + printSection("SceneContainerFlag") { + println("isEnabled", SceneContainerFlag.isEnabled) + printSection("requirementDescription") { + println(SceneContainerFlag.requirementDescription()) + } } } @@ -288,7 +289,8 @@ constructor( Scenes.Gone to "device was unlocked in Bouncer scene" } else { val prevScene = previousScene.value - (prevScene ?: Scenes.Gone) to + (prevScene + ?: Scenes.Gone) to "device was unlocked in Bouncer scene, from sceneKey=$prevScene" } isOnLockscreen -> diff --git a/packages/SystemUI/src/com/android/systemui/scene/shared/flag/SceneContainerFlags.kt b/packages/SystemUI/src/com/android/systemui/scene/shared/flag/SceneContainerFlag.kt index cff11a753fe2..234eda8c0988 100644 --- a/packages/SystemUI/src/com/android/systemui/scene/shared/flag/SceneContainerFlags.kt +++ b/packages/SystemUI/src/com/android/systemui/scene/shared/flag/SceneContainerFlag.kt @@ -20,7 +20,6 @@ package com.android.systemui.scene.shared.flag import com.android.systemui.Flags.FLAG_SCENE_CONTAINER import com.android.systemui.Flags.sceneContainer -import com.android.systemui.dagger.SysUISingleton import com.android.systemui.deviceentry.shared.DeviceEntryUdfpsRefactor import com.android.systemui.flags.FlagToken import com.android.systemui.flags.RefactorFlagUtils @@ -32,8 +31,6 @@ import com.android.systemui.keyguard.shared.RefactorKeyguardDismissIntent import com.android.systemui.media.controls.util.MediaInSceneContainerFlag import com.android.systemui.statusbar.notification.shared.NotificationsHeadsUpRefactor import com.android.systemui.statusbar.phone.PredictiveBackSysUiFlag -import dagger.Module -import dagger.Provides /** Helper for reading or using the scene container flag state. */ object SceneContainerFlag { @@ -99,30 +96,12 @@ object SceneContainerFlag { */ @JvmStatic inline fun assertInLegacyMode() = RefactorFlagUtils.assertInLegacyMode(isEnabled, DESCRIPTION) -} - -/** - * Defines interface for classes that can check whether the scene container framework feature is - * enabled. - */ -interface SceneContainerFlags { - - /** Returns `true` if the Scene Container Framework is enabled; `false` otherwise. */ - fun isEnabled(): Boolean /** Returns a developer-readable string that describes the current requirement list. */ - fun requirementDescription(): String -} - -class SceneContainerFlagsImpl : SceneContainerFlags { - - override fun isEnabled(): Boolean { - return SceneContainerFlag.isEnabled - } - - override fun requirementDescription(): String { + @JvmStatic + fun requirementDescription(): String { return buildString { - SceneContainerFlag.getAllRequirements().forEach { requirement -> + getAllRequirements().forEach { requirement -> append('\n') append(if (requirement.isEnabled) " [MET]" else "[NOT MET]") append(" ${requirement.name}") @@ -130,9 +109,3 @@ class SceneContainerFlagsImpl : SceneContainerFlags { } } } - -@Module -object SceneContainerFlagsModule { - - @Provides @SysUISingleton fun impl(): SceneContainerFlags = SceneContainerFlagsImpl() -} diff --git a/packages/SystemUI/src/com/android/systemui/scene/ui/view/SceneWindowRootView.kt b/packages/SystemUI/src/com/android/systemui/scene/ui/view/SceneWindowRootView.kt index 67dc0cc6e03b..259a8bfef175 100644 --- a/packages/SystemUI/src/com/android/systemui/scene/ui/view/SceneWindowRootView.kt +++ b/packages/SystemUI/src/com/android/systemui/scene/ui/view/SceneWindowRootView.kt @@ -4,7 +4,6 @@ import android.content.Context import android.util.AttributeSet import android.view.View import android.view.WindowInsets -import com.android.systemui.scene.shared.flag.SceneContainerFlags import com.android.systemui.scene.shared.model.Scene import com.android.systemui.scene.shared.model.SceneContainerConfig import com.android.systemui.scene.shared.model.SceneDataSourceDelegator @@ -31,7 +30,6 @@ class SceneWindowRootView( viewModel: SceneContainerViewModel, containerConfig: SceneContainerConfig, sharedNotificationContainer: SharedNotificationContainer, - flags: SceneContainerFlags, scenes: Set<Scene>, layoutInsetController: LayoutInsetsController, sceneDataSourceDelegator: SceneDataSourceDelegator, @@ -44,7 +42,6 @@ class SceneWindowRootView( windowInsets = windowInsets, containerConfig = containerConfig, sharedNotificationContainer = sharedNotificationContainer, - flags = flags, scenes = scenes, onVisibilityChangedInternal = { isVisible -> super.setVisibility(if (isVisible) View.VISIBLE else View.INVISIBLE) diff --git a/packages/SystemUI/src/com/android/systemui/scene/ui/view/SceneWindowRootViewBinder.kt b/packages/SystemUI/src/com/android/systemui/scene/ui/view/SceneWindowRootViewBinder.kt index 809ac2ea9f82..2ef9b731502f 100644 --- a/packages/SystemUI/src/com/android/systemui/scene/ui/view/SceneWindowRootViewBinder.kt +++ b/packages/SystemUI/src/com/android/systemui/scene/ui/view/SceneWindowRootViewBinder.kt @@ -39,7 +39,7 @@ import com.android.systemui.common.ui.compose.windowinsets.DisplayCutout import com.android.systemui.common.ui.compose.windowinsets.ScreenDecorProvider import com.android.systemui.lifecycle.repeatWhenAttached import com.android.systemui.res.R -import com.android.systemui.scene.shared.flag.SceneContainerFlags +import com.android.systemui.scene.shared.flag.SceneContainerFlag import com.android.systemui.scene.shared.model.Scene import com.android.systemui.scene.shared.model.SceneContainerConfig import com.android.systemui.scene.shared.model.SceneDataSourceDelegator @@ -63,7 +63,6 @@ object SceneWindowRootViewBinder { windowInsets: StateFlow<WindowInsets?>, containerConfig: SceneContainerConfig, sharedNotificationContainer: SharedNotificationContainer, - flags: SceneContainerFlags, scenes: Set<Scene>, onVisibilityChangedInternal: (isVisible: Boolean) -> Unit, dataSourceDelegator: SceneDataSourceDelegator, @@ -115,7 +114,7 @@ object SceneWindowRootViewBinder { // the SceneContainerView. This SharedNotificationContainer should contain NSSL // due to the NotificationStackScrollLayoutSection (legacy) or // NotificationSection (scene container) moving it there. - if (flags.isEnabled()) { + if (SceneContainerFlag.isEnabled) { (sharedNotificationContainer.parent as? ViewGroup)?.removeView( sharedNotificationContainer ) diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/scroll/LongScreenshotActivity.java b/packages/SystemUI/src/com/android/systemui/screenshot/scroll/LongScreenshotActivity.java index 706ac9c46be1..b43a1d23da24 100644 --- a/packages/SystemUI/src/com/android/systemui/screenshot/scroll/LongScreenshotActivity.java +++ b/packages/SystemUI/src/com/android/systemui/screenshot/scroll/LongScreenshotActivity.java @@ -23,6 +23,7 @@ import android.content.ContentProvider; import android.content.Intent; import android.graphics.Bitmap; import android.graphics.HardwareRenderer; +import android.graphics.Insets; import android.graphics.Matrix; import android.graphics.RecordingCanvas; import android.graphics.Rect; @@ -38,9 +39,11 @@ import android.util.Log; import android.view.Display; import android.view.ScrollCaptureResponse; import android.view.View; +import android.view.WindowInsets; import android.widget.ImageView; import androidx.constraintlayout.widget.ConstraintLayout; +import androidx.core.view.WindowCompat; import com.android.internal.app.ChooserActivity; import com.android.internal.logging.UiEventLogger; @@ -127,6 +130,10 @@ public class LongScreenshotActivity extends Activity { @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); + + // Enable edge-to-edge explicitly. + WindowCompat.setDecorFitsSystemWindows(getWindow(), false); + setContentView(R.layout.long_screenshot); mPreview = requireViewById(R.id.preview); @@ -149,6 +156,13 @@ public class LongScreenshotActivity extends Activity { (v, left, top, right, bottom, oldLeft, oldTop, oldRight, oldBottom) -> updateImageDimensions()); + requireViewById(R.id.root).setOnApplyWindowInsetsListener( + (view, windowInsets) -> { + Insets insets = windowInsets.getInsets(WindowInsets.Type.systemBars()); + view.setPadding(insets.left, insets.top, insets.right, insets.bottom); + return WindowInsets.CONSUMED; + }); + Intent intent = getIntent(); mScrollCaptureResponse = intent.getParcelableExtra(EXTRA_CAPTURE_RESPONSE); mScreenshotUserHandle = intent.getParcelableExtra(EXTRA_SCREENSHOT_USER_HANDLE, diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowControllerImpl.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowControllerImpl.java index fb32b9fce909..adcb14a67983 100644 --- a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowControllerImpl.java +++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowControllerImpl.java @@ -59,7 +59,7 @@ import com.android.systemui.keyguard.KeyguardViewMediator; import com.android.systemui.plugins.statusbar.StatusBarStateController; import com.android.systemui.plugins.statusbar.StatusBarStateController.StateListener; import com.android.systemui.res.R; -import com.android.systemui.scene.shared.flag.SceneContainerFlags; +import com.android.systemui.scene.shared.flag.SceneContainerFlag; import com.android.systemui.scene.ui.view.WindowRootViewComponent; import com.android.systemui.settings.UserTracker; import com.android.systemui.shade.domain.interactor.ShadeInteractor; @@ -117,7 +117,6 @@ public class NotificationShadeWindowControllerImpl implements NotificationShadeW private final AuthController mAuthController; private final Lazy<SelectedUserInteractor> mUserInteractor; private final Lazy<ShadeInteractor> mShadeInteractorLazy; - private final SceneContainerFlags mSceneContainerFlags; private final Lazy<CommunalInteractor> mCommunalInteractor; private ViewGroup mWindowRootView; private LayoutParams mLp; @@ -166,7 +165,6 @@ public class NotificationShadeWindowControllerImpl implements NotificationShadeW ShadeWindowLogger logger, Lazy<SelectedUserInteractor> userInteractor, UserTracker userTracker, - SceneContainerFlags sceneContainerFlags, Lazy<CommunalInteractor> communalInteractor) { mContext = context; mWindowRootViewComponentFactory = windowRootViewComponentFactory; @@ -186,7 +184,6 @@ public class NotificationShadeWindowControllerImpl implements NotificationShadeW dumpManager.registerCriticalDumpable("{slow}NotificationShadeWindowControllerImpl", this); mAuthController = authController; mUserInteractor = userInteractor; - mSceneContainerFlags = sceneContainerFlags; mCommunalInteractor = communalInteractor; mLastKeyguardRotationAllowed = mKeyguardStateController.isKeyguardScreenRotationAllowed(); mLockScreenDisplayTimeout = context.getResources() @@ -289,7 +286,7 @@ public class NotificationShadeWindowControllerImpl implements NotificationShadeW mLp.layoutInDisplayCutoutMode = LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS; mLp.privateFlags |= PRIVATE_FLAG_OPTIMIZE_MEASURE; - if (mSceneContainerFlags.isEnabled()) { + if (SceneContainerFlag.isEnabled()) { // This prevents the appearance and disappearance of the software keyboard (also known // as the "IME") from scrolling/panning the window to make room for the keyboard. // diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowViewController.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowViewController.java index 00bc752c1b83..a763641841d9 100644 --- a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowViewController.java +++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowViewController.java @@ -659,6 +659,9 @@ public class NotificationShadeWindowViewController implements Dumpable { mTouchCancelled = true; } mAmbientState.setSwipingUp(false); + if (MigrateClocksToBlueprint.isEnabled()) { + mDragDownHelper.stopDragging(); + } } @Override diff --git a/packages/SystemUI/src/com/android/systemui/shade/ShadeModule.kt b/packages/SystemUI/src/com/android/systemui/shade/ShadeModule.kt index 648d4b55370e..a0c939107fdb 100644 --- a/packages/SystemUI/src/com/android/systemui/shade/ShadeModule.kt +++ b/packages/SystemUI/src/com/android/systemui/shade/ShadeModule.kt @@ -19,7 +19,7 @@ package com.android.systemui.shade import com.android.systemui.dagger.SysUISingleton import com.android.systemui.plugins.qs.QSContainerController import com.android.systemui.qs.ui.adapter.QSSceneAdapterImpl -import com.android.systemui.scene.shared.flag.SceneContainerFlags +import com.android.systemui.scene.shared.flag.SceneContainerFlag import com.android.systemui.shade.data.repository.PrivacyChipRepository import com.android.systemui.shade.data.repository.PrivacyChipRepositoryImpl import com.android.systemui.shade.data.repository.ShadeRepository @@ -50,11 +50,10 @@ abstract class ShadeModule { @Provides @SysUISingleton fun provideBaseShadeInteractor( - sceneContainerFlags: SceneContainerFlags, sceneContainerOn: Provider<ShadeInteractorSceneContainerImpl>, sceneContainerOff: Provider<ShadeInteractorLegacyImpl> ): BaseShadeInteractor { - return if (sceneContainerFlags.isEnabled()) { + return if (SceneContainerFlag.isEnabled) { sceneContainerOn.get() } else { sceneContainerOff.get() @@ -64,11 +63,10 @@ abstract class ShadeModule { @Provides @SysUISingleton fun provideShadeController( - sceneContainerFlags: SceneContainerFlags, sceneContainerOn: Provider<ShadeControllerSceneImpl>, sceneContainerOff: Provider<ShadeControllerImpl> ): ShadeController { - return if (sceneContainerFlags.isEnabled()) { + return if (SceneContainerFlag.isEnabled) { sceneContainerOn.get() } else { sceneContainerOff.get() @@ -78,11 +76,10 @@ abstract class ShadeModule { @Provides @SysUISingleton fun provideShadeAnimationInteractor( - sceneContainerFlags: SceneContainerFlags, sceneContainerOn: Provider<ShadeAnimationInteractorSceneContainerImpl>, sceneContainerOff: Provider<ShadeAnimationInteractorLegacyImpl> ): ShadeAnimationInteractor { - return if (sceneContainerFlags.isEnabled()) { + return if (SceneContainerFlag.isEnabled) { sceneContainerOn.get() } else { sceneContainerOff.get() @@ -92,11 +89,10 @@ abstract class ShadeModule { @Provides @SysUISingleton fun provideShadeBackActionInteractor( - sceneContainerFlags: SceneContainerFlags, sceneContainerOn: Provider<ShadeBackActionInteractorImpl>, sceneContainerOff: Provider<NotificationPanelViewController> ): ShadeBackActionInteractor { - return if (sceneContainerFlags.isEnabled()) { + return if (SceneContainerFlag.isEnabled) { sceneContainerOn.get() } else { sceneContainerOff.get() @@ -106,11 +102,10 @@ abstract class ShadeModule { @Provides @SysUISingleton fun provideShadeLockscreenInteractor( - sceneContainerFlags: SceneContainerFlags, sceneContainerOn: Provider<ShadeLockscreenInteractorImpl>, sceneContainerOff: Provider<NotificationPanelViewController> ): ShadeLockscreenInteractor { - return if (sceneContainerFlags.isEnabled()) { + return if (SceneContainerFlag.isEnabled) { sceneContainerOn.get() } else { sceneContainerOff.get() @@ -120,11 +115,10 @@ abstract class ShadeModule { @Provides @SysUISingleton fun providePanelExpansionInteractor( - sceneContainerFlags: SceneContainerFlags, sceneContainerOn: Provider<PanelExpansionInteractorImpl>, sceneContainerOff: Provider<NotificationPanelViewController> ): PanelExpansionInteractor { - return if (sceneContainerFlags.isEnabled()) { + return if (SceneContainerFlag.isEnabled) { sceneContainerOn.get() } else { sceneContainerOff.get() @@ -134,11 +128,10 @@ abstract class ShadeModule { @Provides @SysUISingleton fun provideQuickSettingsController( - sceneContainerFlags: SceneContainerFlags, sceneContainerOn: Provider<QuickSettingsControllerSceneImpl>, sceneContainerOff: Provider<QuickSettingsControllerImpl>, ): QuickSettingsController { - return if (sceneContainerFlags.isEnabled()) { + return if (SceneContainerFlag.isEnabled) { sceneContainerOn.get() } else { sceneContainerOff.get() diff --git a/packages/SystemUI/src/com/android/systemui/shade/ShadeViewProviderModule.kt b/packages/SystemUI/src/com/android/systemui/shade/ShadeViewProviderModule.kt index f5dd5e465505..bc2377895101 100644 --- a/packages/SystemUI/src/com/android/systemui/shade/ShadeViewProviderModule.kt +++ b/packages/SystemUI/src/com/android/systemui/shade/ShadeViewProviderModule.kt @@ -33,7 +33,7 @@ import com.android.systemui.flags.FeatureFlags import com.android.systemui.keyguard.ui.view.KeyguardRootView import com.android.systemui.privacy.OngoingPrivacyChip import com.android.systemui.res.R -import com.android.systemui.scene.shared.flag.SceneContainerFlags +import com.android.systemui.scene.shared.flag.SceneContainerFlag import com.android.systemui.scene.shared.model.Scene import com.android.systemui.scene.shared.model.SceneContainerConfig import com.android.systemui.scene.shared.model.SceneDataSourceDelegator @@ -78,15 +78,13 @@ abstract class ShadeViewProviderModule { @SysUISingleton fun providesWindowRootView( layoutInflater: LayoutInflater, - sceneContainerFlags: SceneContainerFlags, viewModelProvider: Provider<SceneContainerViewModel>, containerConfigProvider: Provider<SceneContainerConfig>, - flagsProvider: Provider<SceneContainerFlags>, scenesProvider: Provider<Set<@JvmSuppressWildcards Scene>>, layoutInsetController: NotificationInsetsController, sceneDataSourceDelegator: Provider<SceneDataSourceDelegator>, ): WindowRootView { - return if (sceneContainerFlags.isEnabled()) { + return if (SceneContainerFlag.isEnabled) { checkNoSceneDuplicates(scenesProvider.get()) val sceneWindowRootView = layoutInflater.inflate(R.layout.scene_window_root, null) as SceneWindowRootView @@ -95,7 +93,6 @@ abstract class ShadeViewProviderModule { containerConfig = containerConfigProvider.get(), sharedNotificationContainer = sceneWindowRootView.requireViewById(R.id.shared_notification_container), - flags = flagsProvider.get(), scenes = scenesProvider.get(), layoutInsetController = layoutInsetController, sceneDataSourceDelegator = sceneDataSourceDelegator.get(), @@ -115,9 +112,8 @@ abstract class ShadeViewProviderModule { @SysUISingleton fun providesNotificationShadeWindowView( root: WindowRootView, - sceneContainerFlags: SceneContainerFlags, ): NotificationShadeWindowView { - if (sceneContainerFlags.isEnabled()) { + if (SceneContainerFlag.isEnabled) { return root.requireViewById(R.id.legacy_window_root) } return root as NotificationShadeWindowView? diff --git a/packages/SystemUI/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneViewModel.kt b/packages/SystemUI/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneViewModel.kt index 980f665ae61f..6800c6115080 100644 --- a/packages/SystemUI/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneViewModel.kt @@ -37,6 +37,7 @@ import com.android.systemui.settings.brightness.ui.viewModel.BrightnessMirrorVie import com.android.systemui.shade.domain.interactor.ShadeInteractor import com.android.systemui.shade.shared.model.ShadeMode import com.android.systemui.statusbar.notification.stack.ui.viewmodel.NotificationsPlaceholderViewModel +import com.android.systemui.unfold.domain.interactor.UnfoldTransitionInteractor import java.util.concurrent.atomic.AtomicBoolean import javax.inject.Inject import kotlinx.coroutines.CoroutineScope @@ -64,6 +65,7 @@ constructor( private val footerActionsViewModelFactory: FooterActionsViewModel.Factory, private val footerActionsController: FooterActionsController, private val sceneInteractor: SceneInteractor, + unfoldTransitionInteractor: UnfoldTransitionInteractor, ) { val destinationScenes: StateFlow<Map<UserAction, UserActionResult>> = combine( @@ -106,6 +108,16 @@ constructor( val shadeMode: StateFlow<ShadeMode> = shadeInteractor.shadeMode + /** + * The unfold transition progress. When fully-unfolded, this is `1` and fully folded, it's `0`. + */ + val unfoldTransitionProgress: StateFlow<Float> = + unfoldTransitionInteractor.unfoldProgress.stateIn( + scope = applicationScope, + started = SharingStarted.WhileSubscribed(), + initialValue = 1f + ) + /** Notifies that some content in the shade was clicked. */ fun onContentClicked() { if (!isClickable.value) { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/LockscreenShadeTransitionController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/LockscreenShadeTransitionController.kt index 519d719d1929..e3db62634339 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/LockscreenShadeTransitionController.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/LockscreenShadeTransitionController.kt @@ -959,7 +959,7 @@ class DragDownHelper( anim.start() } - private fun stopDragging() { + fun stopDragging() { if (startingChild != null) { cancelChildExpansion(startingChild!!) startingChild = null diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/dagger/CentralSurfacesDependenciesModule.java b/packages/SystemUI/src/com/android/systemui/statusbar/dagger/CentralSurfacesDependenciesModule.java index e5b64970c5b0..594c1913fd33 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/dagger/CentralSurfacesDependenciesModule.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/dagger/CentralSurfacesDependenciesModule.java @@ -35,7 +35,7 @@ import com.android.systemui.dump.DumpHandler; import com.android.systemui.dump.DumpManager; import com.android.systemui.media.controls.domain.pipeline.MediaDataManager; import com.android.systemui.power.domain.interactor.PowerInteractor; -import com.android.systemui.scene.shared.flag.SceneContainerFlags; +import com.android.systemui.scene.shared.flag.SceneContainerFlag; import com.android.systemui.settings.DisplayTracker; import com.android.systemui.shade.NotificationPanelViewController; import com.android.systemui.shade.ShadeSurface; @@ -61,8 +61,6 @@ import com.android.systemui.statusbar.phone.StatusBarIconList; import com.android.systemui.statusbar.phone.StatusBarRemoteInputCallback; import com.android.systemui.statusbar.policy.KeyguardStateController; -import javax.inject.Provider; - import dagger.Binds; import dagger.Lazy; import dagger.Module; @@ -70,6 +68,8 @@ import dagger.Provides; import dagger.multibindings.ClassKey; import dagger.multibindings.IntoMap; +import javax.inject.Provider; + /** * This module provides instances needed to construct {@link CentralSurfacesImpl}. These are moved to * this separate from {@link CentralSurfacesModule} module so that components that wish to build @@ -185,10 +185,9 @@ public interface CentralSurfacesDependenciesModule { @Provides @SysUISingleton static ShadeSurface provideShadeSurface( - SceneContainerFlags sceneContainerFlags, Provider<ShadeSurfaceImpl> sceneContainerOn, Provider<NotificationPanelViewController> sceneContainerOff) { - if (sceneContainerFlags.isEnabled()) { + if (SceneContainerFlag.isEnabled()) { return sceneContainerOn.get(); } else { return sceneContainerOff.get(); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java index 9e0b16cfb312..feb9afb68ecb 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java @@ -1767,7 +1767,8 @@ public class ExpandableNotificationRow extends ActivatableNotificationView */ public ExpandableNotificationRow(Context context, AttributeSet attrs) { this(context, attrs, context); - Log.e(TAG, "This constructor shouldn't be called"); + // NOTE(b/317503801): Always crash when using the insecure constructor. + throw new UnsupportedOperationException("Insecure constructor"); } /** diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/RowInflaterTask.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/RowInflaterTask.java index 5fbcebda7cd6..35afda7084d9 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/RowInflaterTask.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/RowInflaterTask.java @@ -33,6 +33,8 @@ import com.android.systemui.statusbar.InflationTask; import com.android.systemui.statusbar.notification.collection.NotificationEntry; import com.android.systemui.util.time.SystemClock; +import java.util.concurrent.Executor; + import javax.inject.Inject; /** @@ -58,10 +60,22 @@ public class RowInflaterTask implements InflationTask, AsyncLayoutInflater.OnInf } /** - * Inflates a new notificationView. This should not be called twice on this object + * Inflates a new notificationView asynchronously, calling the {@code listener} on the main + * thread when done. This should not be called twice on this object. */ public void inflate(Context context, ViewGroup parent, NotificationEntry entry, RowInflationFinishedListener listener) { + inflate(context, parent, entry, null, listener); + } + + /** + * Inflates a new notificationView asynchronously, calling the {@code listener} on the supplied + * {@code listenerExecutor} (or the main thread if null) when done. This should not be called + * twice on this object. + */ + @VisibleForTesting + public void inflate(Context context, ViewGroup parent, NotificationEntry entry, + @Nullable Executor listenerExecutor, RowInflationFinishedListener listener) { if (TRACE_ORIGIN) { mInflateOrigin = new Throwable("inflate requested here"); } @@ -72,7 +86,7 @@ public class RowInflaterTask implements InflationTask, AsyncLayoutInflater.OnInf mLogger.logInflateStart(entry); mInflateStartTimeMs = mSystemClock.elapsedRealtime(); - inflater.inflate(R.layout.status_bar_notification_row, parent, this); + inflater.inflate(R.layout.status_bar_notification_row, parent, listenerExecutor, this); } private RowAsyncLayoutInflater makeRowInflater(NotificationEntry entry) { @@ -80,12 +94,12 @@ public class RowInflaterTask implements InflationTask, AsyncLayoutInflater.OnInf } @VisibleForTesting - static class RowAsyncLayoutInflater implements AsyncLayoutFactory { + public static class RowAsyncLayoutInflater implements AsyncLayoutFactory { private final NotificationEntry mEntry; private final SystemClock mSystemClock; private final RowInflaterTaskLogger mLogger; - RowAsyncLayoutInflater(NotificationEntry entry, SystemClock systemClock, + public RowAsyncLayoutInflater(NotificationEntry entry, SystemClock systemClock, RowInflaterTaskLogger logger) { mEntry = entry; mSystemClock = systemClock; diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/SharedNotificationContainerBinder.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/SharedNotificationContainerBinder.kt index 741102bcd574..cf5366b92cda 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/SharedNotificationContainerBinder.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/SharedNotificationContainerBinder.kt @@ -29,7 +29,6 @@ import com.android.systemui.keyguard.ui.viewmodel.BurnInParameters import com.android.systemui.keyguard.ui.viewmodel.ViewStateAccessor import com.android.systemui.lifecycle.repeatWhenAttached import com.android.systemui.scene.shared.flag.SceneContainerFlag -import com.android.systemui.scene.shared.flag.SceneContainerFlags import com.android.systemui.statusbar.notification.footer.shared.FooterViewRefactor import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController import com.android.systemui.statusbar.notification.stack.NotificationStackSizeCalculator @@ -49,7 +48,6 @@ import kotlinx.coroutines.launch class SharedNotificationContainerBinder @Inject constructor( - private val sceneContainerFlags: SceneContainerFlags, private val controller: NotificationStackScrollLayoutController, private val notificationStackSizeCalculator: NotificationStackSizeCalculator, private val notificationScrollViewBinder: NotificationScrollViewBinder, @@ -130,7 +128,7 @@ constructor( .collect { controller.setMaxDisplayedNotifications(it) } } - if (!sceneContainerFlags.isEnabled()) { + if (!SceneContainerFlag.isEnabled) { launch { viewModel.bounds.collect { val animate = @@ -166,7 +164,7 @@ constructor( } } - if (sceneContainerFlags.isEnabled()) { + if (SceneContainerFlag.isEnabled) { disposables += notificationScrollViewBinder.bindWhileAttached() } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationsPlaceholderViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationsPlaceholderViewModel.kt index b284179d42b7..ca19da58a135 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationsPlaceholderViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationsPlaceholderViewModel.kt @@ -22,7 +22,7 @@ import com.android.systemui.dump.DumpManager import com.android.systemui.flags.FeatureFlagsClassic import com.android.systemui.flags.Flags import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor -import com.android.systemui.scene.shared.flag.SceneContainerFlags +import com.android.systemui.scene.shared.flag.SceneContainerFlag import com.android.systemui.shade.domain.interactor.ShadeInteractor import com.android.systemui.statusbar.notification.stack.domain.interactor.NotificationStackAppearanceInteractor import com.android.systemui.statusbar.notification.stack.shared.model.ShadeScrimBounds @@ -43,7 +43,6 @@ constructor( dumpManager: DumpManager, private val interactor: NotificationStackAppearanceInteractor, shadeInteractor: ShadeInteractor, - flags: SceneContainerFlags, featureFlags: FeatureFlagsClassic, private val keyguardInteractor: KeyguardInteractor, ) : FlowDumperImpl(dumpManager) { @@ -51,7 +50,7 @@ constructor( val isVisualDebuggingEnabled: Boolean = featureFlags.isEnabled(Flags.NSSL_DEBUG_LINES) /** DEBUG: whether the debug logging should be output. */ - val isDebugLoggingEnabled: Boolean = flags.isEnabled() + val isDebugLoggingEnabled: Boolean = SceneContainerFlag.isEnabled /** Notifies that the bounds of the notification scrim have changed. */ fun onScrimBoundsChanged(bounds: ShadeScrimBounds?) { 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 08b65e3bb580..cb3c03ebae4d 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java @@ -164,7 +164,6 @@ import com.android.systemui.qs.QSPanelController; import com.android.systemui.res.R; import com.android.systemui.scene.domain.interactor.WindowRootViewVisibilityInteractor; import com.android.systemui.scene.shared.flag.SceneContainerFlag; -import com.android.systemui.scene.shared.flag.SceneContainerFlags; import com.android.systemui.scrim.ScrimView; import com.android.systemui.settings.UserTracker; import com.android.systemui.settings.brightness.BrightnessSliderController; @@ -592,9 +591,6 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces { private final ColorExtractor.OnColorsChangedListener mOnColorsChangedListener = (extractor, which) -> updateTheme(); - - private final SceneContainerFlags mSceneContainerFlags; - private final BrightnessMirrorShowingInteractor mBrightnessMirrorShowingInteractor; /** @@ -708,7 +704,6 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces { UserTracker userTracker, Provider<FingerprintManager> fingerprintManager, ActivityStarter activityStarter, - SceneContainerFlags sceneContainerFlags, BrightnessMirrorShowingInteractor brightnessMirrorShowingInteractor ) { mContext = context; @@ -804,7 +799,6 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces { mUserTracker = userTracker; mFingerprintManager = fingerprintManager; mActivityStarter = activityStarter; - mSceneContainerFlags = sceneContainerFlags; mBrightnessMirrorShowingInteractor = brightnessMirrorShowingInteractor; mLockscreenShadeTransitionController = lockscreenShadeTransitionController; @@ -1088,7 +1082,7 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces { mJavaAdapter.alwaysCollectFlow( mCommunalInteractor.isIdleOnCommunal(), mIdleOnCommunalConsumer); - if (mSceneContainerFlags.isEnabled()) { + if (SceneContainerFlag.isEnabled()) { mJavaAdapter.alwaysCollectFlow( mBrightnessMirrorShowingInteractor.isShowing(), this::setBrightnessMirrorShowing @@ -1277,7 +1271,7 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces { // Set up the quick settings tile panel final View container = getNotificationShadeWindowView().findViewById(R.id.qs_frame); - if (container != null && !mSceneContainerFlags.isEnabled()) { + if (container != null && !SceneContainerFlag.isEnabled()) { FragmentHostManager fragmentHostManager = mFragmentService.getFragmentHostManager(container); ExtensionFragmentListener.attachExtensonToFragment( diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LegacyActivityStarterInternalImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LegacyActivityStarterInternalImpl.kt index ebaeb39efe31..68d54e73774e 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LegacyActivityStarterInternalImpl.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LegacyActivityStarterInternalImpl.kt @@ -131,7 +131,7 @@ constructor( val runnable = Runnable { assistManagerLazy.get().hideAssist() - intent.flags = Intent.FLAG_ACTIVITY_NEW_TASK + intent.flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TOP intent.addFlags(flags) val result = intArrayOf(ActivityManager.START_CANCELED) activityTransitionAnimator.startIntentWithAnimation( diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewController.kt index 92fd90a0859a..5206e46a4580 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewController.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewController.kt @@ -24,10 +24,10 @@ import android.view.View import android.view.ViewGroup import android.view.ViewTreeObserver import com.android.systemui.Gefingerpoken -import com.android.systemui.res.R import com.android.systemui.flags.FeatureFlags import com.android.systemui.flags.Flags -import com.android.systemui.scene.shared.flag.SceneContainerFlags +import com.android.systemui.res.R +import com.android.systemui.scene.shared.flag.SceneContainerFlag import com.android.systemui.scene.ui.view.WindowRootView import com.android.systemui.shade.ShadeController import com.android.systemui.shade.ShadeLogger @@ -65,7 +65,6 @@ private constructor( private val moveFromCenterAnimationController: StatusBarMoveFromCenterAnimationController?, private val userChipViewModel: StatusBarUserChipViewModel, private val viewUtil: ViewUtil, - private val sceneContainerFlags: SceneContainerFlags, private val configurationController: ConfigurationController, private val statusOverlayHoverListenerFactory: StatusOverlayHoverListenerFactory, ) : ViewController<PhoneStatusBarView>(view) { @@ -205,7 +204,7 @@ private constructor( // If scene framework is enabled, route the touch to it and // ignore the rest of the gesture. - if (sceneContainerFlags.isEnabled()) { + if (SceneContainerFlag.isEnabled) { windowRootView.get().dispatchTouchEvent(event) return true } @@ -267,7 +266,6 @@ private constructor( @Named(UNFOLD_STATUS_BAR) private val progressProvider: Optional<ScopedUnfoldTransitionProgressProvider>, private val featureFlags: FeatureFlags, - private val sceneContainerFlags: SceneContainerFlags, private val userChipViewModel: StatusBarUserChipViewModel, private val centralSurfaces: CentralSurfaces, private val statusBarWindowStateController: StatusBarWindowStateController, @@ -301,7 +299,6 @@ private constructor( statusBarMoveFromCenterAnimationController, userChipViewModel, viewUtil, - sceneContainerFlags, configurationController, statusOverlayHoverListenerFactory, ) diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarTouchableRegionManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarTouchableRegionManager.java index 8e8de46957ed..d1189e1e5e1c 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarTouchableRegionManager.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarTouchableRegionManager.java @@ -37,7 +37,7 @@ import com.android.systemui.bouncer.domain.interactor.PrimaryBouncerInteractor; import com.android.systemui.dagger.SysUISingleton; import com.android.systemui.res.R; import com.android.systemui.scene.domain.interactor.SceneInteractor; -import com.android.systemui.scene.shared.flag.SceneContainerFlags; +import com.android.systemui.scene.shared.flag.SceneContainerFlag; import com.android.systemui.shade.ShadeExpansionStateManager; import com.android.systemui.shade.domain.interactor.ShadeInteractor; import com.android.systemui.statusbar.NotificationShadeWindowController; @@ -91,7 +91,6 @@ public final class StatusBarTouchableRegionManager implements Dumpable { ShadeInteractor shadeInteractor, Provider<SceneInteractor> sceneInteractor, JavaAdapter javaAdapter, - SceneContainerFlags sceneContainerFlags, UnlockedScreenOffAnimationController unlockedScreenOffAnimationController, PrimaryBouncerInteractor primaryBouncerInteractor, AlternateBouncerInteractor alternateBouncerInteractor @@ -130,7 +129,7 @@ public final class StatusBarTouchableRegionManager implements Dumpable { mUnlockedScreenOffAnimationController = unlockedScreenOffAnimationController; - if (sceneContainerFlags.isEnabled()) { + if (SceneContainerFlag.isEnabled()) { javaAdapter.alwaysCollectFlow( sceneInteractor.get().isVisible(), this::onSceneContainerVisibilityChanged); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/binder/MobileIconBinder.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/binder/MobileIconBinder.kt index a0c5618041f6..5f08afdee74a 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/binder/MobileIconBinder.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/binder/MobileIconBinder.kt @@ -128,6 +128,8 @@ object MobileIconBinder { mobileGroupView, dotView, ) + + view.requestLayout() } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/satellite/domain/interactor/DeviceBasedSatelliteInteractor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/satellite/domain/interactor/DeviceBasedSatelliteInteractor.kt index 08ed0305bd13..054116dbdfc2 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/satellite/domain/interactor/DeviceBasedSatelliteInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/satellite/domain/interactor/DeviceBasedSatelliteInteractor.kt @@ -22,6 +22,8 @@ import com.android.systemui.dagger.qualifiers.Application import com.android.systemui.statusbar.pipeline.mobile.domain.interactor.MobileIconsInteractor import com.android.systemui.statusbar.pipeline.satellite.data.DeviceBasedSatelliteRepository import com.android.systemui.statusbar.pipeline.satellite.shared.model.SatelliteConnectionState +import com.android.systemui.statusbar.pipeline.wifi.domain.interactor.WifiInteractor +import com.android.systemui.statusbar.pipeline.wifi.shared.model.WifiNetworkModel import com.android.systemui.statusbar.policy.domain.interactor.DeviceProvisioningInteractor import javax.inject.Inject import kotlinx.coroutines.CoroutineScope @@ -41,6 +43,7 @@ constructor( val repo: DeviceBasedSatelliteRepository, iconsInteractor: MobileIconsInteractor, deviceProvisioningInteractor: DeviceProvisioningInteractor, + wifiInteractor: WifiInteractor, @Application scope: CoroutineScope, ) { /** Must be observed by any UI showing Satellite iconography */ @@ -73,6 +76,9 @@ constructor( val isDeviceProvisioned: Flow<Boolean> = deviceProvisioningInteractor.isDeviceProvisioned + val isWifiActive: Flow<Boolean> = + wifiInteractor.wifiNetwork.map { it is WifiNetworkModel.Active } + /** When all connections are considered OOS, satellite connectivity is potentially valid */ val areAllConnectionsOutOfService = if (Flags.oemEnabledSatelliteFlag()) { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/satellite/ui/viewmodel/DeviceBasedSatelliteViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/satellite/ui/viewmodel/DeviceBasedSatelliteViewModel.kt index 40641bed0602..a0291b81c9a1 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/satellite/ui/viewmodel/DeviceBasedSatelliteViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/satellite/ui/viewmodel/DeviceBasedSatelliteViewModel.kt @@ -59,9 +59,10 @@ constructor( combine( interactor.isSatelliteAllowed, interactor.isDeviceProvisioned, + interactor.isWifiActive, airplaneModeRepository.isAirplaneMode - ) { isSatelliteAllowed, isDeviceProvisioned, isAirplaneMode -> - isSatelliteAllowed && isDeviceProvisioned && !isAirplaneMode + ) { isSatelliteAllowed, isDeviceProvisioned, isWifiActive, isAirplaneMode -> + isSatelliteAllowed && isDeviceProvisioned && !isWifiActive && !isAirplaneMode } } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/AvalancheController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/policy/AvalancheController.kt index 1b56702f3907..0c2abd9099f7 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/AvalancheController.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/AvalancheController.kt @@ -153,18 +153,20 @@ class AvalancheController @Inject constructor( // Use default duration, like we did before AvalancheController existed return autoDismissMs } + val showingList: MutableList<HeadsUpEntry> = mutableListOf() headsUpEntryShowing?.let { showingList.add(it) } + nextList.sort() val entryList = showingList + nextList - if (entryList.indexOf(entry) == entryList.size - 1) { - // Use default duration if last entry + val thisEntryIndex = entryList.indexOf(entry) + val nextEntryIndex = thisEntryIndex + 1 + + // If last entry, use default duration + if (nextEntryIndex >= entryList.size) { return autoDismissMs } - - nextList.sort() - val nextEntry = nextList[0] - + val nextEntry = entryList[nextEntryIndex] if (nextEntry.compareNonTimeFields(entry) == -1) { // Next entry is higher priority return 500 diff --git a/packages/SystemUI/src/com/android/systemui/unfold/data/repository/UnfoldTransitionRepository.kt b/packages/SystemUI/src/com/android/systemui/unfold/data/repository/UnfoldTransitionRepository.kt index 0d3682c9a24b..fbbd2b9c5de8 100644 --- a/packages/SystemUI/src/com/android/systemui/unfold/data/repository/UnfoldTransitionRepository.kt +++ b/packages/SystemUI/src/com/android/systemui/unfold/data/repository/UnfoldTransitionRepository.kt @@ -15,9 +15,11 @@ */ package com.android.systemui.unfold.data.repository +import androidx.annotation.FloatRange import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow import com.android.systemui.unfold.UnfoldTransitionProgressProvider import com.android.systemui.unfold.data.repository.UnfoldTransitionStatus.TransitionFinished +import com.android.systemui.unfold.data.repository.UnfoldTransitionStatus.TransitionInProgress import com.android.systemui.unfold.data.repository.UnfoldTransitionStatus.TransitionStarted import com.android.systemui.util.kotlin.getOrNull import java.util.Optional @@ -42,6 +44,10 @@ interface UnfoldTransitionRepository { sealed class UnfoldTransitionStatus { /** Status that is sent when fold or unfold transition is in started state */ data object TransitionStarted : UnfoldTransitionStatus() + /** Status that is sent while fold or unfold transition is in progress */ + data class TransitionInProgress( + @FloatRange(from = 0.0, to = 1.0) val progress: Float, + ) : UnfoldTransitionStatus() /** Status that is sent when fold or unfold transition is finished */ data object TransitionFinished : UnfoldTransitionStatus() } @@ -66,6 +72,10 @@ constructor( trySend(TransitionStarted) } + override fun onTransitionProgress(progress: Float) { + trySend(TransitionInProgress(progress)) + } + override fun onTransitionFinished() { trySend(TransitionFinished) } diff --git a/packages/SystemUI/src/com/android/systemui/unfold/domain/interactor/UnfoldTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/unfold/domain/interactor/UnfoldTransitionInteractor.kt index 3e2e564c307c..a8e4496d7804 100644 --- a/packages/SystemUI/src/com/android/systemui/unfold/domain/interactor/UnfoldTransitionInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/unfold/domain/interactor/UnfoldTransitionInteractor.kt @@ -17,10 +17,14 @@ package com.android.systemui.unfold.domain.interactor import com.android.systemui.unfold.data.repository.UnfoldTransitionRepository import com.android.systemui.unfold.data.repository.UnfoldTransitionStatus.TransitionFinished +import com.android.systemui.unfold.data.repository.UnfoldTransitionStatus.TransitionInProgress import com.android.systemui.unfold.data.repository.UnfoldTransitionStatus.TransitionStarted import javax.inject.Inject +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.distinctUntilChanged import kotlinx.coroutines.flow.filter import kotlinx.coroutines.flow.first +import kotlinx.coroutines.flow.map /** * Contains business-logic related to fold-unfold transitions while interacting with @@ -30,6 +34,8 @@ interface UnfoldTransitionInteractor { /** Returns availability of fold/unfold transitions on the device */ val isAvailable: Boolean + val unfoldProgress: Flow<Float> + /** Suspends and waits for a fold/unfold transition to finish */ suspend fun waitForTransitionFinish() @@ -44,6 +50,11 @@ constructor(private val repository: UnfoldTransitionRepository) : UnfoldTransiti override val isAvailable: Boolean get() = repository.isAvailable + override val unfoldProgress: Flow<Float> = + repository.transitionStatus + .map { (it as? TransitionInProgress)?.progress ?: 1f } + .distinctUntilChanged() + override suspend fun waitForTransitionFinish() { repository.transitionStatus.filter { it is TransitionFinished }.first() } diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchControllerBaseTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchControllerBaseTest.java index a2499615e18c..319b61512f12 100644 --- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchControllerBaseTest.java +++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchControllerBaseTest.java @@ -205,9 +205,9 @@ public class KeyguardClockSwitchControllerBaseTest extends SysuiTestCase { when(mClockRegistry.createCurrentClock()).thenReturn(mClockController); when(mClockEventController.getClock()).thenReturn(mClockController); when(mSmallClockController.getConfig()) - .thenReturn(new ClockFaceConfig(ClockTickRate.PER_MINUTE, false, false)); + .thenReturn(new ClockFaceConfig(ClockTickRate.PER_MINUTE, false, false, false)); when(mLargeClockController.getConfig()) - .thenReturn(new ClockFaceConfig(ClockTickRate.PER_MINUTE, false, false)); + .thenReturn(new ClockFaceConfig(ClockTickRate.PER_MINUTE, false, false, false)); mSliceView = new View(getContext()); when(mView.findViewById(R.id.keyguard_slice_view)).thenReturn(mSliceView); diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchControllerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchControllerTest.java index 9d81b960336f..99b5a4b631a7 100644 --- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchControllerTest.java @@ -272,9 +272,9 @@ public class KeyguardClockSwitchControllerTest extends KeyguardClockSwitchContro assertEquals(View.VISIBLE, mFakeDateView.getVisibility()); when(mSmallClockController.getConfig()) - .thenReturn(new ClockFaceConfig(ClockTickRate.PER_MINUTE, true, false)); + .thenReturn(new ClockFaceConfig(ClockTickRate.PER_MINUTE, true, false, true)); when(mLargeClockController.getConfig()) - .thenReturn(new ClockFaceConfig(ClockTickRate.PER_MINUTE, true, false)); + .thenReturn(new ClockFaceConfig(ClockTickRate.PER_MINUTE, true, false, true)); verify(mClockRegistry).registerClockChangeListener(listenerArgumentCaptor.capture()); listenerArgumentCaptor.getValue().onCurrentClockChanged(); diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSimPinViewControllerTest.kt b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSimPinViewControllerTest.kt index 9b5364ed6e6e..7151c429acf9 100644 --- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSimPinViewControllerTest.kt +++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSimPinViewControllerTest.kt @@ -115,6 +115,7 @@ class KeyguardSimPinViewControllerTest : SysuiTestCase() { @Test fun onViewAttached() { underTest.onViewAttached() + verify(keyguardMessageAreaController).setIsVisible(true) verify(keyguardMessageAreaController) .setMessage(context.resources.getString(R.string.keyguard_enter_your_pin), false) verify(keyguardUpdateMonitor) diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSimPukViewControllerTest.kt b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSimPukViewControllerTest.kt index e71490c73aa1..acae913459b3 100644 --- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSimPukViewControllerTest.kt +++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSimPukViewControllerTest.kt @@ -103,7 +103,9 @@ class KeyguardSimPukViewControllerTest : SysuiTestCase() { @Test fun onViewAttached() { + Mockito.reset(keyguardMessageAreaController) underTest.onViewAttached() + Mockito.verify(keyguardMessageAreaController).setIsVisible(true) Mockito.verify(keyguardUpdateMonitor) .registerCallback(any(KeyguardUpdateMonitorCallback::class.java)) Mockito.verify(keyguardMessageAreaController) diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardStatusViewControllerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardStatusViewControllerTest.java index 11fe86277903..b2828a41c4b0 100644 --- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardStatusViewControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardStatusViewControllerTest.java @@ -98,7 +98,7 @@ public class KeyguardStatusViewControllerTest extends KeyguardStatusViewControll public void updatePosition_primaryClockAnimation() { ClockController mockClock = mock(ClockController.class); when(mKeyguardClockSwitchController.getClock()).thenReturn(mockClock); - when(mockClock.getConfig()).thenReturn(new ClockConfig("MOCK", "", "", false, true)); + when(mockClock.getConfig()).thenReturn(new ClockConfig("MOCK", "", "", false, true, false)); mController.updatePosition(10, 15, 20f, true); @@ -113,7 +113,7 @@ public class KeyguardStatusViewControllerTest extends KeyguardStatusViewControll public void updatePosition_alternateClockAnimation() { ClockController mockClock = mock(ClockController.class); when(mKeyguardClockSwitchController.getClock()).thenReturn(mockClock); - when(mockClock.getConfig()).thenReturn(new ClockConfig("MOCK", "", "", true, true)); + when(mockClock.getConfig()).thenReturn(new ClockConfig("MOCK", "", "", true, true, false)); mController.updatePosition(10, 15, 20f, true); diff --git a/packages/SystemUI/tests/src/com/android/keyguard/LegacyLockIconViewControllerBaseTest.java b/packages/SystemUI/tests/src/com/android/keyguard/LegacyLockIconViewControllerBaseTest.java index f924ab4a4617..bcea4116d169 100644 --- a/packages/SystemUI/tests/src/com/android/keyguard/LegacyLockIconViewControllerBaseTest.java +++ b/packages/SystemUI/tests/src/com/android/keyguard/LegacyLockIconViewControllerBaseTest.java @@ -36,6 +36,7 @@ import android.util.Pair; import android.view.View; import android.view.WindowManager; import android.view.accessibility.AccessibilityManager; +import android.widget.ImageView; import com.android.systemui.Flags; import com.android.systemui.SysuiTestCase; @@ -78,6 +79,7 @@ public class LegacyLockIconViewControllerBaseTest extends SysuiTestCase { protected final KosmosJavaAdapter mKosmos = new KosmosJavaAdapter(this); protected @Mock DeviceEntryInteractor mDeviceEntryInteractor; protected @Mock LockIconView mLockIconView; + protected @Mock ImageView mLockIcon; protected @Mock AnimatedStateListDrawable mIconDrawable; protected @Mock Context mContext; protected @Mock Resources mResources; @@ -146,8 +148,10 @@ public class LegacyLockIconViewControllerBaseTest extends SysuiTestCase { when(mStatusBarStateController.isDozing()).thenReturn(false); when(mStatusBarStateController.getState()).thenReturn(StatusBarState.KEYGUARD); - mSetFlagsRule.disableFlags(Flags.FLAG_KEYGUARD_BOTTOM_AREA_REFACTOR); - mSetFlagsRule.disableFlags(Flags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT); + if (!Flags.sceneContainer()) { + mSetFlagsRule.disableFlags(Flags.FLAG_KEYGUARD_BOTTOM_AREA_REFACTOR); + mSetFlagsRule.disableFlags(Flags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT); + } mFeatureFlags = new FakeFeatureFlags(); mFeatureFlags.set(LOCKSCREEN_WALLPAPER_DREAM_ENABLED, false); @@ -172,8 +176,7 @@ public class LegacyLockIconViewControllerBaseTest extends SysuiTestCase { mFeatureFlags, mPrimaryBouncerInteractor, mContext, - () -> mDeviceEntryInteractor, - mKosmos.getFakeSceneContainerFlags() + () -> mDeviceEntryInteractor ); } @@ -225,6 +228,7 @@ public class LegacyLockIconViewControllerBaseTest extends SysuiTestCase { protected void setupLockIconViewMocks() { when(mLockIconView.getResources()).thenReturn(mResources); when(mLockIconView.getContext()).thenReturn(mContext); + when(mLockIconView.getLockIcon()).thenReturn(mLockIcon); } protected void resetLockIconView() { diff --git a/packages/SystemUI/tests/src/com/android/keyguard/LegacyLockIconViewControllerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/LegacyLockIconViewControllerTest.java index 868984233816..255c7d909194 100644 --- a/packages/SystemUI/tests/src/com/android/keyguard/LegacyLockIconViewControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/keyguard/LegacyLockIconViewControllerTest.java @@ -43,6 +43,7 @@ import androidx.test.filters.SmallTest; import com.android.systemui.biometrics.UdfpsController; import com.android.systemui.biometrics.shared.model.UdfpsOverlayParams; import com.android.systemui.doze.util.BurnInHelperKt; +import com.android.systemui.flags.EnableSceneContainer; import com.android.systemui.statusbar.StatusBarState; import org.junit.Test; @@ -373,7 +374,6 @@ public class LegacyLockIconViewControllerTest extends LegacyLockIconViewControll @Test public void longPress_showBouncer_sceneContainerNotEnabled() { init(/* useMigrationFlag= */ false); - mKosmos.getFakeSceneContainerFlags().setEnabled(false); when(mFalsingManager.isFalseLongTap(anyInt())).thenReturn(false); // WHEN longPress @@ -385,9 +385,9 @@ public class LegacyLockIconViewControllerTest extends LegacyLockIconViewControll } @Test + @EnableSceneContainer public void longPress_showBouncer() { init(/* useMigrationFlag= */ false); - mKosmos.getFakeSceneContainerFlags().setEnabled(true); when(mFalsingManager.isFalseLongTap(anyInt())).thenReturn(false); // WHEN longPress @@ -399,9 +399,9 @@ public class LegacyLockIconViewControllerTest extends LegacyLockIconViewControll } @Test + @EnableSceneContainer public void longPress_falsingTriggered_doesNotShowBouncer() { init(/* useMigrationFlag= */ false); - mKosmos.getFakeSceneContainerFlags().setEnabled(true); when(mFalsingManager.isFalseLongTap(anyInt())).thenReturn(true); // WHEN longPress diff --git a/packages/SystemUI/tests/src/com/android/systemui/back/domain/interactor/BackActionInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/back/domain/interactor/BackActionInteractorTest.kt index f490f3c56987..cbad133ba4f0 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/back/domain/interactor/BackActionInteractorTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/back/domain/interactor/BackActionInteractorTest.kt @@ -41,7 +41,6 @@ import com.android.systemui.power.domain.interactor.PowerInteractorFactory import com.android.systemui.scene.data.repository.WindowRootViewVisibilityRepository import com.android.systemui.scene.domain.interactor.WindowRootViewVisibilityInteractor import com.android.systemui.scene.domain.interactor.sceneInteractor -import com.android.systemui.scene.shared.flag.sceneContainerFlags import com.android.systemui.scene.ui.view.WindowRootView import com.android.systemui.shade.QuickSettingsController import com.android.systemui.shade.ShadeController @@ -109,7 +108,6 @@ class BackActionInteractorTest : SysuiTestCase() { headsUpManager, powerInteractor, activeNotificationsInteractor, - kosmos.sceneContainerFlags, kosmos::sceneInteractor, ) } diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/binder/SideFpsOverlayViewBinderTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/binder/SideFpsOverlayViewBinderTest.kt index 30c5e6ed4812..d3cc23296b46 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/binder/SideFpsOverlayViewBinderTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/binder/SideFpsOverlayViewBinderTest.kt @@ -78,7 +78,6 @@ import com.android.systemui.plugins.statusbar.StatusBarStateController import com.android.systemui.power.domain.interactor.powerInteractor import com.android.systemui.res.R import com.android.systemui.scene.domain.interactor.sceneInteractor -import com.android.systemui.scene.shared.flag.fakeSceneContainerFlags import com.android.systemui.shared.Flags.FLAG_SIDEFPS_CONTROLLER_REFACTOR import com.android.systemui.statusbar.phone.dozeServiceHost import com.android.systemui.statusbar.policy.KeyguardStateController @@ -239,7 +238,6 @@ class SideFpsOverlayViewBinderTest : SysuiTestCase() { testScope.backgroundScope, mContext, deviceEntryFingerprintAuthRepository, - kosmos.fakeSceneContainerFlags, kosmos.sceneInteractor, primaryBouncerInteractor, alternateBouncerInteractor, diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/SideFpsOverlayViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/SideFpsOverlayViewModelTest.kt index 238a76eb7400..415da022cd32 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/SideFpsOverlayViewModelTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/SideFpsOverlayViewModelTest.kt @@ -77,7 +77,6 @@ import com.android.systemui.plugins.statusbar.StatusBarStateController import com.android.systemui.power.domain.interactor.powerInteractor import com.android.systemui.res.R import com.android.systemui.scene.domain.interactor.sceneInteractor -import com.android.systemui.scene.shared.flag.fakeSceneContainerFlags import com.android.systemui.shared.Flags.FLAG_SIDEFPS_CONTROLLER_REFACTOR import com.android.systemui.statusbar.phone.dozeServiceHost import com.android.systemui.statusbar.policy.KeyguardStateController @@ -236,7 +235,6 @@ class SideFpsOverlayViewModelTest : SysuiTestCase() { testScope.backgroundScope, mContext, deviceEntryFingerprintAuthRepository, - kosmos.fakeSceneContainerFlags, kosmos.sceneInteractor, primaryBouncerInteractor, alternateBouncerInteractor, diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java b/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java index a46f7551fa0b..709f7797d2bf 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java @@ -25,8 +25,8 @@ import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.SOM import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_DPM_LOCK_NOW; import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_NON_STRONG_BIOMETRICS_TIMEOUT; import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_USER_LOCKDOWN; -import static com.android.systemui.Flags.FLAG_REFACTOR_GET_CURRENT_USER; import static com.android.systemui.Flags.FLAG_KEYGUARD_WM_STATE_REFACTOR; +import static com.android.systemui.Flags.FLAG_REFACTOR_GET_CURRENT_USER; import static com.android.systemui.keyguard.KeyguardViewMediator.DELAYED_KEYGUARD_ACTION; import static com.android.systemui.keyguard.KeyguardViewMediator.KEYGUARD_LOCK_AFTER_DELAY_DEFAULT; import static com.android.systemui.keyguard.KeyguardViewMediator.REBOOT_MAINLINE_UPDATE; @@ -102,7 +102,6 @@ import com.android.systemui.kosmos.KosmosJavaAdapter; import com.android.systemui.log.SessionTracker; import com.android.systemui.navigationbar.NavigationModeController; import com.android.systemui.scene.FakeWindowRootViewComponent; -import com.android.systemui.scene.shared.flag.SceneContainerFlags; import com.android.systemui.scene.ui.view.WindowRootView; import com.android.systemui.settings.UserTracker; import com.android.systemui.shade.NotificationShadeWindowControllerImpl; @@ -222,7 +221,6 @@ public class KeyguardViewMediatorTest extends SysuiTestCase { private @Mock DreamViewModel mDreamViewModel; private @Mock CommunalTransitionViewModel mCommunalTransitionViewModel; private @Mock SystemPropertiesHelper mSystemPropertiesHelper; - private @Mock SceneContainerFlags mSceneContainerFlags; private FakeFeatureFlags mFeatureFlags; private final int mDefaultUserId = 100; @@ -270,7 +268,6 @@ public class KeyguardViewMediatorTest extends SysuiTestCase { mShadeWindowLogger, () -> mSelectedUserInteractor, mUserTracker, - mSceneContainerFlags, mKosmos::getCommunalInteractor); mFeatureFlags = new FakeFeatureFlags(); mSetFlagsRule.enableFlags(FLAG_REFACTOR_GET_CURRENT_USER); diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ResourceTrimmerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ResourceTrimmerTest.kt index b0aace6f650e..b50d248d6940 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ResourceTrimmerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ResourceTrimmerTest.kt @@ -23,7 +23,6 @@ import com.android.systemui.testKosmos import com.android.systemui.util.mockito.any import com.android.systemui.utils.GlobalWindowManager import kotlinx.coroutines.ExperimentalCoroutinesApi -import kotlinx.coroutines.test.TestScope import kotlinx.coroutines.test.runCurrent import kotlinx.coroutines.test.runTest import org.junit.Before @@ -63,8 +62,8 @@ class ResourceTrimmerTest : SysuiTestCase() { val withDeps = KeyguardInteractorFactory.create( - repository = keyguardRepository, featureFlags = featureFlags, + repository = keyguardRepository, ) val keyguardInteractor = withDeps.keyguardInteractor resourceTrimmer = diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/DeviceEntrySideFpsOverlayInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/DeviceEntrySideFpsOverlayInteractorTest.kt index 9266af452abd..dc7f372b5dfc 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/DeviceEntrySideFpsOverlayInteractorTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/DeviceEntrySideFpsOverlayInteractorTest.kt @@ -32,6 +32,7 @@ import com.android.systemui.coroutines.collectLastValue import com.android.systemui.coroutines.collectValues import com.android.systemui.deviceentry.domain.interactor.DeviceEntryFaceAuthInteractor import com.android.systemui.deviceentry.domain.interactor.DeviceEntryFingerprintAuthInteractor +import com.android.systemui.flags.EnableSceneContainer import com.android.systemui.keyguard.DismissCallbackRegistry import com.android.systemui.keyguard.data.repository.FakeBiometricSettingsRepository import com.android.systemui.keyguard.data.repository.FakeDeviceEntryFingerprintAuthRepository @@ -40,7 +41,6 @@ import com.android.systemui.kosmos.testScope import com.android.systemui.plugins.statusbar.StatusBarStateController import com.android.systemui.res.R import com.android.systemui.scene.domain.interactor.sceneInteractor -import com.android.systemui.scene.shared.flag.fakeSceneContainerFlags import com.android.systemui.scene.shared.model.Scenes import com.android.systemui.shared.Flags.FLAG_SIDEFPS_CONTROLLER_REFACTOR import com.android.systemui.statusbar.policy.KeyguardStateController @@ -87,7 +87,6 @@ class DeviceEntrySideFpsOverlayInteractorTest : SysuiTestCase() { @Before fun setup() { mSetFlagsRule.enableFlags(FLAG_SIDEFPS_CONTROLLER_REFACTOR) - kosmos.fakeSceneContainerFlags.enabled = false primaryBouncerInteractor = PrimaryBouncerInteractor( @@ -127,7 +126,6 @@ class DeviceEntrySideFpsOverlayInteractorTest : SysuiTestCase() { testScope.backgroundScope, mContext, deviceEntryFingerprintAuthRepository, - kosmos.fakeSceneContainerFlags, kosmos.sceneInteractor, primaryBouncerInteractor, alternateBouncerInteractor, @@ -168,15 +166,14 @@ class DeviceEntrySideFpsOverlayInteractorTest : SysuiTestCase() { } @Test + @EnableSceneContainer fun updatesShowIndicatorForDeviceEntry_onBouncerSceneActive() = testScope.runTest { - kosmos.fakeSceneContainerFlags.enabled = true underTest = DeviceEntrySideFpsOverlayInteractor( testScope.backgroundScope, mContext, deviceEntryFingerprintAuthRepository, - kosmos.fakeSceneContainerFlags, kosmos.sceneInteractor, primaryBouncerInteractor, alternateBouncerInteractor, @@ -196,15 +193,14 @@ class DeviceEntrySideFpsOverlayInteractorTest : SysuiTestCase() { } @Test + @EnableSceneContainer fun updatesShowIndicatorForDeviceEntry_onBouncerSceneInactive() = testScope.runTest { - kosmos.fakeSceneContainerFlags.enabled = true underTest = DeviceEntrySideFpsOverlayInteractor( testScope.backgroundScope, mContext, deviceEntryFingerprintAuthRepository, - kosmos.fakeSceneContainerFlags, kosmos.sceneInteractor, primaryBouncerInteractor, alternateBouncerInteractor, diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardSurfaceBehindInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardSurfaceBehindInteractorTest.kt index 3f05bfae6777..9ccf2121b8d2 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardSurfaceBehindInteractorTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardSurfaceBehindInteractorTest.kt @@ -19,6 +19,7 @@ package com.android.systemui.keyguard.domain.interactor import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase +import com.android.systemui.coroutines.collectLastValue import com.android.systemui.coroutines.collectValues import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository import com.android.systemui.keyguard.shared.model.KeyguardState @@ -227,4 +228,50 @@ class KeyguardSurfaceBehindInteractorTest : SysuiTestCase() { { it == KeyguardSurfaceBehindModel(alpha = 0f) }, ) } + + @Test + fun notificationLaunchFromLockscreen_isAnimatingSurfaceTrue() = + testScope.runTest { + val isAnimatingSurface by collectLastValue(underTest.isAnimatingSurface) + transitionRepository.sendTransitionStep( + TransitionStep( + from = KeyguardState.GONE, + to = KeyguardState.LOCKSCREEN, + transitionState = TransitionState.STARTED, + ) + ) + transitionRepository.sendTransitionStep( + TransitionStep( + from = KeyguardState.GONE, + to = KeyguardState.LOCKSCREEN, + transitionState = TransitionState.FINISHED, + ) + ) + kosmos.notificationLaunchAnimationInteractor.setIsLaunchAnimationRunning(true) + runCurrent() + assertThat(isAnimatingSurface).isTrue() + } + + @Test + fun notificationLaunchFromGone_isAnimatingSurfaceFalse() = + testScope.runTest { + val isAnimatingSurface by collectLastValue(underTest.isAnimatingSurface) + transitionRepository.sendTransitionStep( + TransitionStep( + from = KeyguardState.LOCKSCREEN, + to = KeyguardState.GONE, + transitionState = TransitionState.STARTED, + ) + ) + transitionRepository.sendTransitionStep( + TransitionStep( + from = KeyguardState.LOCKSCREEN, + to = KeyguardState.GONE, + transitionState = TransitionState.FINISHED, + ) + ) + kosmos.notificationLaunchAnimationInteractor.setIsLaunchAnimationRunning(true) + runCurrent() + assertThat(isAnimatingSurface).isFalse() + } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/binder/KeyguardClockViewBinderTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/binder/KeyguardClockViewBinderTest.kt index 66aa572dbc48..5e3a142dc348 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/binder/KeyguardClockViewBinderTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/binder/KeyguardClockViewBinderTest.kt @@ -113,7 +113,8 @@ class KeyguardClockViewBinderTest : SysuiTestCase() { id = "WEATHER_CLOCK", name = "", description = "", - useAlternateSmartspaceAODTransition = true + useAlternateSmartspaceAODTransition = true, + useCustomClockScene = true ) whenever(clock.config).thenReturn(clockConfig) } diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerViewModelTest.kt index d410dac1b2e4..f1c93c4652c3 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerViewModelTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerViewModelTest.kt @@ -32,7 +32,6 @@ import com.android.systemui.util.mockito.any import com.google.common.collect.Range import com.google.common.truth.Truth.assertThat import kotlinx.coroutines.ExperimentalCoroutinesApi -import kotlinx.coroutines.test.runCurrent import kotlinx.coroutines.test.runTest import org.junit.Test import org.junit.runner.RunWith @@ -67,7 +66,7 @@ class AlternateBouncerViewModelTest : SysuiTestCase() { fun transitionToAlternateBouncer_scrimAlphaUpdate() = testScope.runTest { val scrimAlphas by collectValues(underTest.scrimAlpha) - runCurrent() + assertThat(scrimAlphas.size).isEqualTo(1) // initial value is 0f transitionRepository.sendTransitionSteps( listOf( @@ -79,7 +78,7 @@ class AlternateBouncerViewModelTest : SysuiTestCase() { testScope, ) - assertThat(scrimAlphas.size).isEqualTo(4) + assertThat(scrimAlphas.size).isEqualTo(5) scrimAlphas.forEach { assertThat(it).isIn(Range.closed(0f, 1f)) } } @@ -87,7 +86,7 @@ class AlternateBouncerViewModelTest : SysuiTestCase() { fun transitionFromAlternateBouncer_scrimAlphaUpdate() = testScope.runTest { val scrimAlphas by collectValues(underTest.scrimAlpha) - runCurrent() + assertThat(scrimAlphas.size).isEqualTo(1) // initial value is 0f transitionRepository.sendTransitionSteps( listOf( @@ -98,7 +97,7 @@ class AlternateBouncerViewModelTest : SysuiTestCase() { ), testScope, ) - assertThat(scrimAlphas.size).isEqualTo(4) + assertThat(scrimAlphas.size).isEqualTo(5) scrimAlphas.forEach { assertThat(it).isIn(Range.closed(0f, 1f)) } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/controls/domain/pipeline/MediaDataFilterImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/controls/domain/pipeline/MediaDataFilterImplTest.kt index 4d20f55d7df2..8f73811199ba 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/media/controls/domain/pipeline/MediaDataFilterImplTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/media/controls/domain/pipeline/MediaDataFilterImplTest.kt @@ -50,6 +50,7 @@ import kotlinx.coroutines.test.runTest import org.junit.Before import org.junit.Test import org.junit.runner.RunWith +import org.mockito.ArgumentMatchers.anyBoolean import org.mockito.ArgumentMatchers.anyInt import org.mockito.ArgumentMatchers.anyLong import org.mockito.Mock @@ -76,9 +77,10 @@ private val SMARTSPACE_INSTANCE_ID = InstanceId.fakeInstanceId(456)!! @TestableLooper.RunWithLooper class MediaDataFilterImplTest : SysuiTestCase() { + @Mock private lateinit var listener: MediaDataProcessor.Listener @Mock private lateinit var userTracker: UserTracker @Mock private lateinit var broadcastSender: BroadcastSender - @Mock private lateinit var mediaDataManager: MediaDataManager + @Mock private lateinit var mediaDataProcessor: MediaDataProcessor @Mock private lateinit var lockscreenUserManager: NotificationLockscreenUserManager @Mock private lateinit var executor: Executor @Mock private lateinit var smartspaceData: SmartspaceMediaData @@ -114,7 +116,8 @@ class MediaDataFilterImplTest : SysuiTestCase() { mediaFlags, repository, ) - mediaDataFilter.mediaDataManager = mediaDataManager + mediaDataFilter.mediaDataProcessor = mediaDataProcessor + mediaDataFilter.addListener(listener) // Start all tests as main user setUser(USER_MAIN) @@ -167,6 +170,8 @@ class MediaDataFilterImplTest : SysuiTestCase() { mediaDataFilter.onMediaDataLoaded(KEY, null, dataMain) + verify(listener) + .onMediaDataLoaded(eq(KEY), eq(null), eq(dataMain), eq(true), eq(0), eq(false)) assertThat(mediaDataLoadedStates).isEqualTo(mediaDataLoadingModel) } @@ -178,6 +183,8 @@ class MediaDataFilterImplTest : SysuiTestCase() { mediaDataFilter.onMediaDataLoaded(KEY, null, dataGuest) + verify(listener, never()) + .onMediaDataLoaded(any(), any(), any(), anyBoolean(), anyInt(), anyBoolean()) assertThat(mediaDataLoadedStates).isNotEqualTo(mediaLoadedStatesModel) } @@ -196,6 +203,7 @@ class MediaDataFilterImplTest : SysuiTestCase() { mediaLoadedStatesModel.remove(MediaDataLoadingModel.Loaded(dataMain.instanceId)) mediaDataFilter.onMediaDataRemoved(KEY) + verify(listener).onMediaDataRemoved(eq(KEY)) assertThat(mediaDataLoadedStates).isEqualTo(mediaLoadedStatesModel) } @@ -208,6 +216,7 @@ class MediaDataFilterImplTest : SysuiTestCase() { mediaDataFilter.onMediaDataLoaded(KEY, null, dataGuest) mediaDataFilter.onMediaDataRemoved(KEY) + verify(listener, never()).onMediaDataRemoved(eq(KEY)) assertThat(mediaDataLoadedStates).isEmpty() } @@ -226,6 +235,7 @@ class MediaDataFilterImplTest : SysuiTestCase() { setUser(USER_GUEST) // THEN we should remove the main user's media + verify(listener).onMediaDataRemoved(eq(KEY)) assertThat(mediaDataLoadedStates).isEmpty() } @@ -243,6 +253,20 @@ class MediaDataFilterImplTest : SysuiTestCase() { // and we switch to guest user setUser(USER_GUEST) + // THEN we should add back the guest user media + verify(listener) + .onMediaDataLoaded(eq(KEY_ALT), eq(null), eq(dataGuest), eq(true), eq(0), eq(false)) + + // but not the main user's + verify(listener, never()) + .onMediaDataLoaded( + eq(KEY), + any(), + eq(dataMain), + anyBoolean(), + anyInt(), + anyBoolean() + ) assertThat(mediaDataLoadedStates).isEqualTo(guestLoadedStatesModel) assertThat(mediaDataLoadedStates).isNotEqualTo(mainLoadedStatesModel) } @@ -261,6 +285,7 @@ class MediaDataFilterImplTest : SysuiTestCase() { val mediaLoadedStatesModel = listOf(MediaDataLoadingModel.Loaded(dataMain.instanceId)) // THEN we should remove the private profile media + verify(listener).onMediaDataRemoved(eq(KEY_ALT)) assertThat(mediaDataLoadedStates).isEqualTo(mediaLoadedStatesModel) } @@ -481,7 +506,7 @@ class MediaDataFilterImplTest : SysuiTestCase() { mediaDataFilter.onMediaDataLoaded(KEY, null, dataMain) mediaDataFilter.onSwipeToDismiss() - verify(mediaDataManager).setInactive(eq(KEY), eq(true), eq(true)) + verify(mediaDataProcessor).setInactive(eq(KEY), eq(true), eq(true)) } @Test @@ -507,6 +532,8 @@ class MediaDataFilterImplTest : SysuiTestCase() { ) .isTrue() assertThat(hasActiveMedia(selectedUserEntries)).isFalse() + verify(listener) + .onSmartspaceMediaDataLoaded(eq(SMARTSPACE_KEY), eq(smartspaceData), eq(true)) verify(logger).logRecommendationAdded(SMARTSPACE_PACKAGE, SMARTSPACE_INSTANCE_ID) verify(logger, never()).logRecommendationActivated(any(), any(), any()) } @@ -534,6 +561,9 @@ class MediaDataFilterImplTest : SysuiTestCase() { ) .isFalse() assertThat(hasActiveMedia(selectedUserEntries)).isFalse() + verify(listener, never()) + .onMediaDataLoaded(any(), any(), any(), anyBoolean(), anyInt(), anyBoolean()) + verify(listener, never()).onSmartspaceMediaDataLoaded(any(), any(), anyBoolean()) verify(logger, never()).logRecommendationAdded(any(), any()) verify(logger, never()).logRecommendationActivated(any(), any(), any()) } @@ -563,6 +593,8 @@ class MediaDataFilterImplTest : SysuiTestCase() { ) .isTrue() assertThat(hasActiveMedia(selectedUserEntries)).isFalse() + verify(listener) + .onSmartspaceMediaDataLoaded(eq(SMARTSPACE_KEY), eq(smartspaceData), eq(true)) verify(logger).logRecommendationAdded(SMARTSPACE_PACKAGE, SMARTSPACE_INSTANCE_ID) verify(logger, never()).logRecommendationActivated(any(), any(), any()) } @@ -592,6 +624,7 @@ class MediaDataFilterImplTest : SysuiTestCase() { ) .isFalse() assertThat(hasActiveMedia(selectedUserEntries)).isFalse() + verify(listener, never()).onSmartspaceMediaDataLoaded(any(), any(), anyBoolean()) verify(logger, never()).logRecommendationAdded(any(), any()) verify(logger, never()).logRecommendationActivated(any(), any(), any()) } @@ -614,6 +647,8 @@ class MediaDataFilterImplTest : SysuiTestCase() { mediaDataFilter.onMediaDataLoaded(KEY, null, dataCurrent) assertThat(mediaDataLoadedStates).isEqualTo(mediaLoadedStatesModel) + verify(listener) + .onMediaDataLoaded(eq(KEY), eq(null), eq(dataCurrent), eq(true), eq(0), eq(false)) // AND we get a smartspace signal mediaDataFilter.onSmartspaceMediaDataLoaded(SMARTSPACE_KEY, smartspaceData) @@ -629,6 +664,9 @@ class MediaDataFilterImplTest : SysuiTestCase() { ) .isFalse() assertThat(hasActiveMedia(selectedUserEntries)).isFalse() + verify(listener, never()) + .onMediaDataLoaded(eq(KEY), eq(KEY), any(), anyBoolean(), anyInt(), anyBoolean()) + verify(listener, never()).onSmartspaceMediaDataLoaded(any(), any(), anyBoolean()) verify(logger, never()).logRecommendationAdded(any(), any()) verify(logger, never()).logRecommendationActivated(any(), any(), any()) } @@ -649,12 +687,15 @@ class MediaDataFilterImplTest : SysuiTestCase() { val mediaLoadedStatesModel = listOf(MediaDataLoadingModel.Loaded(dataMain.instanceId)) mediaDataFilter.onMediaDataLoaded(KEY, null, dataCurrent) assertThat(mediaDataLoadedStates).isEqualTo(mediaLoadedStatesModel) + verify(listener) + .onMediaDataLoaded(eq(KEY), eq(null), eq(dataCurrent), eq(true), eq(0), eq(false)) // AND we get a smartspace signal runCurrent() mediaDataFilter.onSmartspaceMediaDataLoaded(SMARTSPACE_KEY, smartspaceData) // THEN we should treat the media as active instead + val dataCurrentAndActive = dataCurrent.copy(active = true) assertThat(mediaDataLoadedStates).isEqualTo(mediaLoadedStatesModel) assertThat( hasActiveMediaOrRecommendation( @@ -664,8 +705,18 @@ class MediaDataFilterImplTest : SysuiTestCase() { ) ) .isTrue() + verify(listener) + .onMediaDataLoaded( + eq(KEY), + eq(KEY), + eq(dataCurrentAndActive), + eq(true), + eq(100), + eq(true) + ) // Smartspace update shouldn't be propagated for the empty rec list. assertThat(recommendationsLoadingState).isEqualTo(SmartspaceMediaLoadingModel.Unknown) + verify(listener, never()).onSmartspaceMediaDataLoaded(any(), any(), anyBoolean()) verify(logger, never()).logRecommendationAdded(any(), any()) verify(logger).logRecommendationActivated(eq(APP_UID), eq(PACKAGE), eq(INSTANCE_ID)) } @@ -687,12 +738,24 @@ class MediaDataFilterImplTest : SysuiTestCase() { mediaDataFilter.onMediaDataLoaded(KEY, null, dataCurrent) assertThat(mediaDataLoadedStates).isEqualTo(mediaDataLoadingModel) + verify(listener) + .onMediaDataLoaded(eq(KEY), eq(null), eq(dataCurrent), eq(true), eq(0), eq(false)) // AND we get a smartspace signal runCurrent() mediaDataFilter.onSmartspaceMediaDataLoaded(SMARTSPACE_KEY, smartspaceData) // THEN we should treat the media as active instead + val dataCurrentAndActive = dataCurrent.copy(active = true) + verify(listener) + .onMediaDataLoaded( + eq(KEY), + eq(KEY), + eq(dataCurrentAndActive), + eq(true), + eq(100), + eq(true) + ) assertThat(mediaDataLoadedStates).isEqualTo(mediaDataLoadingModel) assertThat( hasActiveMediaOrRecommendation( @@ -704,6 +767,8 @@ class MediaDataFilterImplTest : SysuiTestCase() { .isTrue() // Smartspace update should also be propagated but not prioritized. assertThat(recommendationsLoadingState).isEqualTo(recommendationsLoadingModel) + verify(listener) + .onSmartspaceMediaDataLoaded(eq(SMARTSPACE_KEY), eq(smartspaceData), eq(false)) verify(logger).logRecommendationAdded(SMARTSPACE_PACKAGE, SMARTSPACE_INSTANCE_ID) verify(logger).logRecommendationActivated(eq(APP_UID), eq(PACKAGE), eq(INSTANCE_ID)) } @@ -721,6 +786,7 @@ class MediaDataFilterImplTest : SysuiTestCase() { mediaDataFilter.onSmartspaceMediaDataLoaded(SMARTSPACE_KEY, smartspaceData) mediaDataFilter.onSmartspaceMediaDataRemoved(SMARTSPACE_KEY) + verify(listener).onSmartspaceMediaDataRemoved(SMARTSPACE_KEY) assertThat(recommendationsLoadingState).isEqualTo(recommendationsLoadingModel) assertThat( hasActiveMediaOrRecommendation( @@ -746,15 +812,29 @@ class MediaDataFilterImplTest : SysuiTestCase() { val mediaLoadedStatesModel = listOf(MediaDataLoadingModel.Loaded(dataMain.instanceId)) val dataCurrent = dataMain.copy(active = false, lastActive = clock.elapsedRealtime()) mediaDataFilter.onMediaDataLoaded(KEY, null, dataCurrent) + assertThat(mediaDataLoadedStates).isEqualTo(mediaLoadedStatesModel) + verify(listener) + .onMediaDataLoaded(eq(KEY), eq(null), eq(dataCurrent), eq(true), eq(0), eq(false)) runCurrent() mediaDataFilter.onSmartspaceMediaDataLoaded(SMARTSPACE_KEY, smartspaceData) + val dataCurrentAndActive = dataCurrent.copy(active = true) + verify(listener) + .onMediaDataLoaded( + eq(KEY), + eq(KEY), + eq(dataCurrentAndActive), + eq(true), + eq(100), + eq(true) + ) assertThat(mediaDataLoadedStates).isEqualTo(mediaLoadedStatesModel) mediaDataFilter.onSmartspaceMediaDataRemoved(SMARTSPACE_KEY) + verify(listener).onSmartspaceMediaDataRemoved(SMARTSPACE_KEY) assertThat(recommendationsLoadingState).isEqualTo(recommendationsLoadingModel) assertThat( hasActiveMediaOrRecommendation( @@ -781,6 +861,8 @@ class MediaDataFilterImplTest : SysuiTestCase() { mediaDataFilter.onSmartspaceMediaDataLoaded(SMARTSPACE_KEY, smartspaceData) + verify(listener) + .onSmartspaceMediaDataLoaded(eq(SMARTSPACE_KEY), eq(smartspaceData), eq(false)) assertThat(recommendationsLoadingState).isEqualTo(recommendationsLoadingModel) assertThat( hasActiveMediaOrRecommendation( @@ -813,12 +895,18 @@ class MediaDataFilterImplTest : SysuiTestCase() { val dataCurrent = dataMain.copy(active = false, lastActive = clock.elapsedRealtime()) mediaDataFilter.onMediaDataLoaded(KEY, null, dataCurrent) + verify(listener) + .onMediaDataLoaded(eq(KEY), eq(null), eq(dataCurrent), eq(true), eq(0), eq(false)) assertThat(mediaDataLoadedStates).isEqualTo(mediaLoadedStatesModel) // And an inactive recommendation is loaded mediaDataFilter.onSmartspaceMediaDataLoaded(SMARTSPACE_KEY, smartspaceData) // Smartspace is loaded but the media stays inactive + verify(listener) + .onSmartspaceMediaDataLoaded(eq(SMARTSPACE_KEY), eq(smartspaceData), eq(false)) + verify(listener, never()) + .onMediaDataLoaded(any(), any(), any(), anyBoolean(), anyInt(), anyBoolean()) assertThat(recommendationsLoadingState).isEqualTo(recommendationsLoadingModel) assertThat( hasActiveMediaOrRecommendation( @@ -846,8 +934,8 @@ class MediaDataFilterImplTest : SysuiTestCase() { mediaDataFilter.onSmartspaceMediaDataLoaded(SMARTSPACE_KEY, data) mediaDataFilter.onSwipeToDismiss() - verify(mediaDataManager).setRecommendationInactive(eq(SMARTSPACE_KEY)) - verify(mediaDataManager, never()) + verify(mediaDataProcessor).setRecommendationInactive(eq(SMARTSPACE_KEY)) + verify(mediaDataProcessor, never()) .dismissSmartspaceRecommendation(eq(SMARTSPACE_KEY), anyLong()) } @@ -866,6 +954,8 @@ class MediaDataFilterImplTest : SysuiTestCase() { val dataCurrent = dataMain.copy(active = false, lastActive = clock.elapsedRealtime()) mediaDataFilter.onMediaDataLoaded(KEY, null, dataCurrent) + verify(listener) + .onMediaDataLoaded(eq(KEY), eq(null), eq(dataCurrent), eq(true), eq(0), eq(false)) assertThat(mediaDataLoadedStates).isEqualTo(mediaLoadedStatesModel) // AND we get a smartspace signal with extra to trigger resume @@ -875,6 +965,16 @@ class MediaDataFilterImplTest : SysuiTestCase() { mediaDataFilter.onSmartspaceMediaDataLoaded(SMARTSPACE_KEY, smartspaceData) // THEN we should treat the media as active instead + val dataCurrentAndActive = dataCurrent.copy(active = true) + verify(listener) + .onMediaDataLoaded( + eq(KEY), + eq(KEY), + eq(dataCurrentAndActive), + eq(true), + eq(100), + eq(true) + ) assertThat(mediaDataLoadedStates).isEqualTo(mediaLoadedStatesModel) assertThat( hasActiveMediaOrRecommendation( @@ -886,6 +986,8 @@ class MediaDataFilterImplTest : SysuiTestCase() { .isTrue() // And update the smartspace data state, but not prioritized assertThat(recommendationsLoadingState).isEqualTo(recommendationsLoadingModel) + verify(listener) + .onSmartspaceMediaDataLoaded(eq(SMARTSPACE_KEY), eq(smartspaceData), eq(false)) } @Test @@ -901,6 +1003,8 @@ class MediaDataFilterImplTest : SysuiTestCase() { val dataCurrent = dataMain.copy(active = false, lastActive = clock.elapsedRealtime()) mediaDataFilter.onMediaDataLoaded(KEY, null, dataCurrent) + verify(listener) + .onMediaDataLoaded(eq(KEY), eq(null), eq(dataCurrent), eq(true), eq(0), eq(false)) assertThat(mediaDataLoadedStates).isEqualTo(mediaLoadedStatesModel) // AND we get a smartspace signal with extra to not trigger resume @@ -908,7 +1012,12 @@ class MediaDataFilterImplTest : SysuiTestCase() { whenever(cardAction.extras).thenReturn(extras) mediaDataFilter.onSmartspaceMediaDataLoaded(SMARTSPACE_KEY, smartspaceData) + // THEN listeners are not updated to show media + verify(listener, never()) + .onMediaDataLoaded(eq(KEY), eq(KEY), any(), eq(true), eq(100), eq(true)) // But the smartspace update is still propagated + verify(listener) + .onSmartspaceMediaDataLoaded(eq(SMARTSPACE_KEY), eq(smartspaceData), eq(false)) assertThat(recommendationsLoadingState).isEqualTo(recommendationsLoadingModel) } diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/controller/MediaViewControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/controller/MediaViewControllerTest.kt index a73bb2cdf79a..e5d3082bb245 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/controller/MediaViewControllerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/controller/MediaViewControllerTest.kt @@ -16,29 +16,54 @@ package com.android.systemui.media.controls.ui.controller +import android.animation.AnimatorSet +import android.content.Context import android.content.res.Configuration import android.content.res.Configuration.ORIENTATION_LANDSCAPE +import android.graphics.drawable.Drawable +import android.graphics.drawable.GradientDrawable +import android.graphics.drawable.RippleDrawable import android.testing.AndroidTestingRunner import android.testing.TestableLooper import android.view.View +import android.view.ViewGroup +import android.view.animation.Interpolator +import android.widget.FrameLayout +import android.widget.ImageButton +import android.widget.ImageView +import android.widget.SeekBar +import android.widget.TextView import androidx.constraintlayout.widget.ConstraintSet +import androidx.lifecycle.LiveData import androidx.test.filters.SmallTest +import com.android.internal.widget.CachingIconView import com.android.systemui.SysuiTestCase +import com.android.systemui.media.controls.ui.view.GutsViewHolder import com.android.systemui.media.controls.ui.view.MediaHost import com.android.systemui.media.controls.ui.view.MediaViewHolder import com.android.systemui.media.controls.ui.view.RecommendationViewHolder +import com.android.systemui.media.controls.ui.viewmodel.SeekBarViewModel import com.android.systemui.media.controls.util.MediaFlags import com.android.systemui.res.R +import com.android.systemui.surfaceeffects.loadingeffect.LoadingEffectView +import com.android.systemui.surfaceeffects.ripple.MultiRippleView +import com.android.systemui.surfaceeffects.turbulencenoise.TurbulenceNoiseView import com.android.systemui.util.animation.MeasurementInput import com.android.systemui.util.animation.TransitionLayout import com.android.systemui.util.animation.TransitionViewState import com.android.systemui.util.animation.WidgetState +import com.android.systemui.util.concurrency.FakeExecutor +import com.android.systemui.util.mockito.withArgCaptor +import com.android.systemui.util.settings.GlobalSettings +import com.android.systemui.util.time.FakeSystemClock +import com.google.common.truth.Truth.assertThat import junit.framework.Assert.assertTrue import org.junit.Before import org.junit.Test import org.junit.runner.RunWith import org.mockito.ArgumentMatchers.floatThat import org.mockito.Mock +import org.mockito.Mockito import org.mockito.Mockito.never import org.mockito.Mockito.times import org.mockito.Mockito.verify @@ -55,6 +80,31 @@ class MediaViewControllerTest : SysuiTestCase() { com.android.systemui.statusbar.phone.ConfigurationControllerImpl(context) private var player = TransitionLayout(context, /* attrs */ null, /* defStyleAttr */ 0) private var recommendation = TransitionLayout(context, /* attrs */ null, /* defStyleAttr */ 0) + private val clock = FakeSystemClock() + private lateinit var mainExecutor: FakeExecutor + private lateinit var seekBar: SeekBar + private lateinit var multiRippleView: MultiRippleView + private lateinit var turbulenceNoiseView: TurbulenceNoiseView + private lateinit var loadingEffectView: LoadingEffectView + private lateinit var settings: ImageButton + private lateinit var cancel: View + private lateinit var cancelText: TextView + private lateinit var dismiss: FrameLayout + private lateinit var dismissText: TextView + private lateinit var titleText: TextView + private lateinit var artistText: TextView + private lateinit var explicitIndicator: CachingIconView + private lateinit var seamless: ViewGroup + private lateinit var seamlessButton: View + private lateinit var seamlessIcon: ImageView + private lateinit var seamlessText: TextView + private lateinit var scrubbingElapsedTimeView: TextView + private lateinit var scrubbingTotalTimeView: TextView + private lateinit var actionPlayPause: ImageButton + private lateinit var actionNext: ImageButton + private lateinit var actionPrev: ImageButton + @Mock private lateinit var seamlessBackground: RippleDrawable + @Mock private lateinit var albumView: ImageView @Mock lateinit var logger: MediaViewLogger @Mock private lateinit var mockViewState: TransitionViewState @Mock private lateinit var mockCopiedState: TransitionViewState @@ -64,6 +114,14 @@ class MediaViewControllerTest : SysuiTestCase() { @Mock private lateinit var mediaSubTitleWidgetState: WidgetState @Mock private lateinit var mediaContainerWidgetState: WidgetState @Mock private lateinit var mediaFlags: MediaFlags + @Mock private lateinit var seekBarViewModel: SeekBarViewModel + @Mock private lateinit var seekBarData: LiveData<SeekBarViewModel.Progress> + @Mock private lateinit var globalSettings: GlobalSettings + @Mock private lateinit var viewHolder: MediaViewHolder + @Mock private lateinit var view: TransitionLayout + @Mock private lateinit var mockAnimator: AnimatorSet + @Mock private lateinit var gutsViewHolder: GutsViewHolder + @Mock private lateinit var gutsText: TextView private val delta = 0.1F @@ -72,14 +130,30 @@ class MediaViewControllerTest : SysuiTestCase() { @Before fun setup() { MockitoAnnotations.initMocks(this) + mainExecutor = FakeExecutor(clock) mediaViewController = - MediaViewController( - context, - configurationController, - mediaHostStatesManager, - logger, - mediaFlags, - ) + object : + MediaViewController( + context, + configurationController, + mediaHostStatesManager, + logger, + seekBarViewModel, + mainExecutor, + mediaFlags, + globalSettings, + ) { + override fun loadAnimator( + context: Context, + animId: Int, + motionInterpolator: Interpolator?, + vararg targets: View? + ): AnimatorSet { + return mockAnimator + } + } + initGutsViewHolderMocks() + initMediaViewHolderMocks() } @Test @@ -299,4 +373,270 @@ class MediaViewControllerTest : SysuiTestCase() { verify(mediaTitleWidgetState).alpha = floatThat { kotlin.math.abs(it - 1.0F) < delta } verify(mediaSubTitleWidgetState).alpha = floatThat { kotlin.math.abs(it - 1.0F) < delta } } + + @Test + fun attachPlayer_seekBarDisabled_seekBarVisibilityIsSetToInvisible() { + whenever(mediaFlags.isMediaControlsRefactorEnabled()).thenReturn(true) + + mediaViewController.attachPlayer(viewHolder) + getEnabledChangeListener().onEnabledChanged(enabled = true) + getEnabledChangeListener().onEnabledChanged(enabled = false) + + assertThat(mediaViewController.expandedLayout.getVisibility(R.id.media_progress_bar)) + .isEqualTo(ConstraintSet.INVISIBLE) + } + + @Test + fun attachPlayer_seekBarEnabled_seekBarVisible() { + whenever(mediaFlags.isMediaControlsRefactorEnabled()).thenReturn(true) + + mediaViewController.attachPlayer(viewHolder) + getEnabledChangeListener().onEnabledChanged(enabled = true) + + assertThat(mediaViewController.expandedLayout.getVisibility(R.id.media_progress_bar)) + .isEqualTo(ConstraintSet.VISIBLE) + } + + @Test + fun attachPlayer_seekBarStatusUpdate_seekBarVisibilityChanges() { + whenever(mediaFlags.isMediaControlsRefactorEnabled()).thenReturn(true) + + mediaViewController.attachPlayer(viewHolder) + getEnabledChangeListener().onEnabledChanged(enabled = true) + + assertThat(mediaViewController.expandedLayout.getVisibility(R.id.media_progress_bar)) + .isEqualTo(ConstraintSet.VISIBLE) + + getEnabledChangeListener().onEnabledChanged(enabled = false) + + assertThat(mediaViewController.expandedLayout.getVisibility(R.id.media_progress_bar)) + .isEqualTo(ConstraintSet.INVISIBLE) + } + + @Test + fun attachPlayer_notScrubbing_scrubbingViewsGone() { + whenever(mediaFlags.isMediaControlsRefactorEnabled()).thenReturn(true) + + mediaViewController.attachPlayer(viewHolder) + mediaViewController.canShowScrubbingTime = true + getScrubbingChangeListener().onScrubbingChanged(true) + getScrubbingChangeListener().onScrubbingChanged(false) + mainExecutor.runAllReady() + + assertThat( + mediaViewController.expandedLayout.getVisibility(R.id.media_scrubbing_elapsed_time) + ) + .isEqualTo(ConstraintSet.GONE) + assertThat( + mediaViewController.expandedLayout.getVisibility(R.id.media_scrubbing_total_time) + ) + .isEqualTo(ConstraintSet.GONE) + } + + @Test + fun setIsScrubbing_noSemanticActions_scrubbingViewsGone() { + whenever(mediaFlags.isMediaControlsRefactorEnabled()).thenReturn(true) + + mediaViewController.attachPlayer(viewHolder) + mediaViewController.canShowScrubbingTime = false + getScrubbingChangeListener().onScrubbingChanged(true) + mainExecutor.runAllReady() + + assertThat( + mediaViewController.expandedLayout.getVisibility(R.id.media_scrubbing_elapsed_time) + ) + .isEqualTo(ConstraintSet.GONE) + assertThat( + mediaViewController.expandedLayout.getVisibility(R.id.media_scrubbing_total_time) + ) + .isEqualTo(ConstraintSet.GONE) + } + + @Test + fun setIsScrubbing_noPrevButton_scrubbingTimesNotShown() { + whenever(mediaFlags.isMediaControlsRefactorEnabled()).thenReturn(true) + + mediaViewController.attachPlayer(viewHolder) + mediaViewController.setUpNextButtonInfo(true) + mediaViewController.setUpPrevButtonInfo(false) + getScrubbingChangeListener().onScrubbingChanged(true) + mainExecutor.runAllReady() + + assertThat(mediaViewController.expandedLayout.getVisibility(R.id.actionNext)) + .isEqualTo(ConstraintSet.VISIBLE) + assertThat( + mediaViewController.expandedLayout.getVisibility(R.id.media_scrubbing_elapsed_time) + ) + .isEqualTo(ConstraintSet.GONE) + assertThat( + mediaViewController.expandedLayout.getVisibility(R.id.media_scrubbing_total_time) + ) + .isEqualTo(ConstraintSet.GONE) + } + + @Test + fun setIsScrubbing_noNextButton_scrubbingTimesNotShown() { + whenever(mediaFlags.isMediaControlsRefactorEnabled()).thenReturn(true) + + mediaViewController.attachPlayer(viewHolder) + mediaViewController.setUpNextButtonInfo(false) + mediaViewController.setUpPrevButtonInfo(true) + getScrubbingChangeListener().onScrubbingChanged(true) + mainExecutor.runAllReady() + + assertThat(mediaViewController.expandedLayout.getVisibility(R.id.actionPrev)) + .isEqualTo(ConstraintSet.VISIBLE) + assertThat( + mediaViewController.expandedLayout.getVisibility(R.id.media_scrubbing_elapsed_time) + ) + .isEqualTo(ConstraintSet.GONE) + assertThat( + mediaViewController.expandedLayout.getVisibility(R.id.media_scrubbing_total_time) + ) + .isEqualTo(ConstraintSet.GONE) + } + + @Test + fun setIsScrubbing_scrubbingViewsShownAndPrevNextHiddenOnlyInExpanded() { + whenever(mediaFlags.isMediaControlsRefactorEnabled()).thenReturn(true) + + mediaViewController.attachPlayer(viewHolder) + mediaViewController.setUpNextButtonInfo(true) + mediaViewController.setUpPrevButtonInfo(true) + mediaViewController.canShowScrubbingTime = true + getScrubbingChangeListener().onScrubbingChanged(true) + mainExecutor.runAllReady() + + // Only in expanded, we should show the scrubbing times and hide prev+next + assertThat(mediaViewController.expandedLayout.getVisibility(R.id.actionPrev)) + .isEqualTo(ConstraintSet.GONE) + assertThat(mediaViewController.expandedLayout.getVisibility(R.id.actionNext)) + .isEqualTo(ConstraintSet.GONE) + assertThat( + mediaViewController.expandedLayout.getVisibility(R.id.media_scrubbing_elapsed_time) + ) + .isEqualTo(ConstraintSet.VISIBLE) + assertThat( + mediaViewController.expandedLayout.getVisibility(R.id.media_scrubbing_total_time) + ) + .isEqualTo(ConstraintSet.VISIBLE) + } + + @Test + fun setIsScrubbing_trueThenFalse_reservePrevAndNextButtons() { + whenever(mediaFlags.isMediaControlsRefactorEnabled()).thenReturn(true) + + mediaViewController.attachPlayer(viewHolder) + mediaViewController.setUpNextButtonInfo(true, ConstraintSet.INVISIBLE) + mediaViewController.setUpPrevButtonInfo(true, ConstraintSet.INVISIBLE) + mediaViewController.canShowScrubbingTime = true + + getScrubbingChangeListener().onScrubbingChanged(true) + mainExecutor.runAllReady() + + assertThat(mediaViewController.expandedLayout.getVisibility(R.id.actionPrev)) + .isEqualTo(ConstraintSet.INVISIBLE) + assertThat(mediaViewController.expandedLayout.getVisibility(R.id.actionNext)) + .isEqualTo(ConstraintSet.INVISIBLE) + assertThat( + mediaViewController.expandedLayout.getVisibility(R.id.media_scrubbing_elapsed_time) + ) + .isEqualTo(ConstraintSet.VISIBLE) + assertThat( + mediaViewController.expandedLayout.getVisibility(R.id.media_scrubbing_total_time) + ) + .isEqualTo(ConstraintSet.VISIBLE) + + getScrubbingChangeListener().onScrubbingChanged(false) + mainExecutor.runAllReady() + + // Only in expanded, we should hide the scrubbing times and show prev+next + assertThat(mediaViewController.expandedLayout.getVisibility(R.id.actionPrev)) + .isEqualTo(ConstraintSet.VISIBLE) + assertThat(mediaViewController.expandedLayout.getVisibility(R.id.actionNext)) + .isEqualTo(ConstraintSet.VISIBLE) + assertThat( + mediaViewController.expandedLayout.getVisibility(R.id.media_scrubbing_elapsed_time) + ) + .isEqualTo(ConstraintSet.GONE) + assertThat( + mediaViewController.expandedLayout.getVisibility(R.id.media_scrubbing_total_time) + ) + .isEqualTo(ConstraintSet.GONE) + } + + private fun initGutsViewHolderMocks() { + settings = ImageButton(context) + cancel = View(context) + cancelText = TextView(context) + dismiss = FrameLayout(context) + dismissText = TextView(context) + whenever(gutsViewHolder.gutsText).thenReturn(gutsText) + whenever(gutsViewHolder.settings).thenReturn(settings) + whenever(gutsViewHolder.cancel).thenReturn(cancel) + whenever(gutsViewHolder.cancelText).thenReturn(cancelText) + whenever(gutsViewHolder.dismiss).thenReturn(dismiss) + whenever(gutsViewHolder.dismissText).thenReturn(dismissText) + } + + private fun initMediaViewHolderMocks() { + titleText = TextView(context) + artistText = TextView(context) + explicitIndicator = CachingIconView(context).also { it.id = R.id.media_explicit_indicator } + seamless = FrameLayout(context) + seamless.foreground = seamlessBackground + seamlessButton = View(context) + seamlessIcon = ImageView(context) + seamlessText = TextView(context) + seekBar = SeekBar(context).also { it.id = R.id.media_progress_bar } + + actionPlayPause = ImageButton(context).also { it.id = R.id.actionPlayPause } + actionPrev = ImageButton(context).also { it.id = R.id.actionPrev } + actionNext = ImageButton(context).also { it.id = R.id.actionNext } + scrubbingElapsedTimeView = + TextView(context).also { it.id = R.id.media_scrubbing_elapsed_time } + scrubbingTotalTimeView = TextView(context).also { it.id = R.id.media_scrubbing_total_time } + + multiRippleView = MultiRippleView(context, null) + turbulenceNoiseView = TurbulenceNoiseView(context, null) + loadingEffectView = LoadingEffectView(context, null) + + whenever(viewHolder.player).thenReturn(view) + whenever(view.context).thenReturn(context) + whenever(viewHolder.albumView).thenReturn(albumView) + whenever(albumView.foreground).thenReturn(Mockito.mock(Drawable::class.java)) + whenever(viewHolder.titleText).thenReturn(titleText) + whenever(viewHolder.artistText).thenReturn(artistText) + whenever(viewHolder.explicitIndicator).thenReturn(explicitIndicator) + whenever(seamlessBackground.getDrawable(0)) + .thenReturn(Mockito.mock(GradientDrawable::class.java)) + whenever(viewHolder.seamless).thenReturn(seamless) + whenever(viewHolder.seamlessButton).thenReturn(seamlessButton) + whenever(viewHolder.seamlessIcon).thenReturn(seamlessIcon) + whenever(viewHolder.seamlessText).thenReturn(seamlessText) + whenever(viewHolder.seekBar).thenReturn(seekBar) + whenever(viewHolder.scrubbingElapsedTimeView).thenReturn(scrubbingElapsedTimeView) + whenever(viewHolder.scrubbingTotalTimeView).thenReturn(scrubbingTotalTimeView) + whenever(viewHolder.gutsViewHolder).thenReturn(gutsViewHolder) + whenever(seekBarViewModel.progress).thenReturn(seekBarData) + + // Action buttons + whenever(viewHolder.actionPlayPause).thenReturn(actionPlayPause) + whenever(viewHolder.getAction(R.id.actionNext)).thenReturn(actionNext) + whenever(viewHolder.getAction(R.id.actionPrev)).thenReturn(actionPrev) + whenever(viewHolder.getAction(R.id.actionPlayPause)).thenReturn(actionPlayPause) + + whenever(viewHolder.multiRippleView).thenReturn(multiRippleView) + whenever(viewHolder.turbulenceNoiseView).thenReturn(turbulenceNoiseView) + whenever(viewHolder.loadingEffectView).thenReturn(loadingEffectView) + } + + private fun getScrubbingChangeListener(): SeekBarViewModel.ScrubbingChangeListener = + withArgCaptor { + verify(seekBarViewModel).setScrubbingChangeListener(capture()) + } + + private fun getEnabledChangeListener(): SeekBarViewModel.EnabledChangeListener = withArgCaptor { + verify(seekBarViewModel).setEnabledChangeListener(capture()) + } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/QSImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/QSImplTest.java index e4a4836bcd46..6956beab418e 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/QSImplTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/QSImplTest.java @@ -57,6 +57,7 @@ import androidx.test.filters.SmallTest; import com.android.keyguard.BouncerPanelExpansionCalculator; import com.android.systemui.SysuiTestCase; import com.android.systemui.dump.DumpManager; +import com.android.systemui.flags.EnableSceneContainer; import com.android.systemui.flags.FeatureFlagsClassic; import com.android.systemui.media.controls.ui.view.MediaHost; import com.android.systemui.qs.customize.QSCustomizerController; @@ -66,7 +67,6 @@ import com.android.systemui.qs.footer.ui.binder.FooterActionsViewBinder; import com.android.systemui.qs.footer.ui.viewmodel.FooterActionsViewModel; import com.android.systemui.qs.logging.QSLogger; import com.android.systemui.res.R; -import com.android.systemui.scene.shared.flag.SceneContainerFlags; import com.android.systemui.settings.FakeDisplayTracker; import com.android.systemui.shade.transition.LargeScreenShadeInterpolator; import com.android.systemui.statusbar.CommandQueue; @@ -115,7 +115,6 @@ public class QSImplTest extends SysuiTestCase { @Mock private FooterActionsViewBinder mFooterActionsViewBinder; @Mock private LargeScreenShadeInterpolator mLargeScreenShadeInterpolator; @Mock private FeatureFlagsClassic mFeatureFlags; - @Mock private SceneContainerFlags mSceneContainerFlags; private ViewGroup mQsView; private final CommandQueue mCommandQueue = @@ -127,7 +126,6 @@ public class QSImplTest extends SysuiTestCase { @Before public void setup() { MockitoAnnotations.initMocks(this); - when(mSceneContainerFlags.isEnabled()).thenReturn(false); mUnderTest = instantiate(); @@ -496,8 +494,8 @@ public class QSImplTest extends SysuiTestCase { } @Test + @EnableSceneContainer public void testSceneContainerFlagsEnabled_FooterActionsRemoved_controllerNotStarted() { - when(mSceneContainerFlags.isEnabled()).thenReturn(true); clearInvocations( mFooterActionsViewBinder, mFooterActionsViewModel, mFooterActionsViewModelFactory); QSImpl other = instantiate(); @@ -513,9 +511,8 @@ public class QSImplTest extends SysuiTestCase { } @Test + @EnableSceneContainer public void testSceneContainerFlagsEnabled_statusBarStateIsShade() { - when(mSceneContainerFlags.isEnabled()).thenReturn(true); - mUnderTest.onStateChanged(KEYGUARD); assertThat(mUnderTest.getStatusBarState()).isEqualTo(SHADE); @@ -524,9 +521,8 @@ public class QSImplTest extends SysuiTestCase { } @Test + @EnableSceneContainer public void testSceneContainerFlagsEnabled_isKeyguardState_alwaysFalse() { - when(mSceneContainerFlags.isEnabled()).thenReturn(true); - mUnderTest.onStateChanged(KEYGUARD); assertThat(mUnderTest.isKeyguardState()).isFalse(); @@ -559,8 +555,8 @@ public class QSImplTest extends SysuiTestCase { mFooterActionsViewModelFactory, mFooterActionsViewBinder, mLargeScreenShadeInterpolator, - mFeatureFlags, - mSceneContainerFlags); + mFeatureFlags + ); } private void setUpOther() { diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelControllerTest.kt index a60494f87fb4..0275643b8489 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelControllerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelControllerTest.kt @@ -17,13 +17,13 @@ import com.android.systemui.plugins.qs.QSTile import com.android.systemui.qs.customize.QSCustomizerController import com.android.systemui.qs.logging.QSLogger import com.android.systemui.res.R -import com.android.systemui.scene.shared.flag.FakeSceneContainerFlags import com.android.systemui.settings.brightness.BrightnessController import com.android.systemui.settings.brightness.BrightnessSliderController import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager import com.android.systemui.statusbar.policy.ResourcesSplitShadeStateController import com.android.systemui.tuner.TunerService import com.google.common.truth.Truth.assertThat +import javax.inject.Provider import org.junit.After import org.junit.Before import org.junit.Test @@ -36,7 +36,6 @@ import org.mockito.Mockito.never import org.mockito.Mockito.reset import org.mockito.Mockito.verify import org.mockito.MockitoAnnotations -import javax.inject.Provider import org.mockito.Mockito.`when` as whenever @SmallTest @@ -65,8 +64,6 @@ class QSPanelControllerTest : SysuiTestCase() { @Mock private lateinit var pagedTileLayout: PagedTileLayout @Mock private lateinit var longPressEffectProvider: Provider<QSLongPressEffect> - private val sceneContainerFlags = FakeSceneContainerFlags() - private lateinit var controller: QSPanelController private val testableResources: TestableResources = mContext.orCreateTestableResources @@ -103,7 +100,6 @@ class QSPanelControllerTest : SysuiTestCase() { falsingManager, statusBarKeyguardViewManager, ResourcesSplitShadeStateController(), - sceneContainerFlags, longPressEffectProvider, ) } diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/QuickStatusBarHeaderControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/QuickStatusBarHeaderControllerTest.kt index 8acde3637576..4915e555dcc5 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/QuickStatusBarHeaderControllerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/QuickStatusBarHeaderControllerTest.kt @@ -20,15 +20,14 @@ import android.content.Context import android.testing.AndroidTestingRunner import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase -import com.android.systemui.scene.shared.flag.FakeSceneContainerFlags import org.junit.After import org.junit.Before import org.junit.Test import org.junit.runner.RunWith import org.mockito.Answers import org.mockito.Mock -import org.mockito.Mockito.`when` import org.mockito.Mockito.verify +import org.mockito.Mockito.`when` import org.mockito.MockitoAnnotations @SmallTest @@ -43,8 +42,6 @@ class QuickStatusBarHeaderControllerTest : SysuiTestCase() { @Mock(answer = Answers.RETURNS_DEEP_STUBS) private lateinit var context: Context - private val sceneContainerFlags = FakeSceneContainerFlags() - private lateinit var controller: QuickStatusBarHeaderController @Before @@ -55,9 +52,8 @@ class QuickStatusBarHeaderControllerTest : SysuiTestCase() { `when`(view.context).thenReturn(context) controller = QuickStatusBarHeaderController( - view, - quickQSPanelController, - sceneContainerFlags, + view, + quickQSPanelController, ) } diff --git a/packages/SystemUI/tests/src/com/android/systemui/recents/OverviewProxyServiceTest.kt b/packages/SystemUI/tests/src/com/android/systemui/recents/OverviewProxyServiceTest.kt index 1313227c7f3d..387f27d048ea 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/recents/OverviewProxyServiceTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/recents/OverviewProxyServiceTest.kt @@ -42,7 +42,6 @@ import com.android.systemui.model.sceneContainerPlugin import com.android.systemui.navigationbar.NavigationBarController import com.android.systemui.navigationbar.NavigationModeController import com.android.systemui.recents.OverviewProxyService.ACTION_QUICKSTEP -import com.android.systemui.scene.shared.flag.FakeSceneContainerFlags import com.android.systemui.settings.FakeDisplayTracker import com.android.systemui.settings.UserTracker import com.android.systemui.shade.ShadeViewController @@ -249,7 +248,6 @@ class OverviewProxyServiceTest : SysuiTestCase() { sysuiUnlockAnimationController, inWindowLauncherUnlockAnimationManager, assistUtils, - FakeSceneContainerFlags(), dumpManager, unfoldTransitionProgressForwarder, broadcastDispatcher diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowControllerImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowControllerImplTest.java index cf7c6f4e2174..b89ccefddf6b 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowControllerImplTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowControllerImplTest.java @@ -75,8 +75,6 @@ import com.android.systemui.res.R; import com.android.systemui.scene.FakeWindowRootViewComponent; import com.android.systemui.scene.data.repository.SceneContainerRepository; import com.android.systemui.scene.domain.interactor.SceneInteractor; -import com.android.systemui.scene.shared.flag.FakeSceneContainerFlags; -import com.android.systemui.scene.shared.flag.SceneContainerFlags; import com.android.systemui.scene.shared.logger.SceneLogger; import com.android.systemui.settings.UserTracker; import com.android.systemui.shade.data.repository.FakeShadeRepository; @@ -136,7 +134,6 @@ public class NotificationShadeWindowControllerImplTest extends SysuiTestCase { @Mock private ShadeWindowLogger mShadeWindowLogger; @Mock private SelectedUserInteractor mSelectedUserInteractor; @Mock private UserTracker mUserTracker; - @Mock private SceneContainerFlags mSceneContainerFlags; @Mock private LargeScreenHeaderHelper mLargeScreenHeaderHelper; @Captor private ArgumentCaptor<WindowManager.LayoutParams> mLayoutParameters; @Captor private ArgumentCaptor<StatusBarStateController.StateListener> mStateListener; @@ -185,14 +182,12 @@ public class NotificationShadeWindowControllerImplTest extends SysuiTestCase { mKosmos.getDeviceUnlockedInteractor()); FakeConfigurationRepository configurationRepository = new FakeConfigurationRepository(); - FakeSceneContainerFlags sceneContainerFlags = new FakeSceneContainerFlags(); KeyguardTransitionInteractor keyguardTransitionInteractor = mKosmos.getKeyguardTransitionInteractor(); KeyguardInteractor keyguardInteractor = new KeyguardInteractor( keyguardRepository, new FakeCommandQueue(), powerInteractor, - sceneContainerFlags, new FakeKeyguardBouncerRepository(), new ConfigurationInteractor(configurationRepository), shadeRepository, @@ -256,7 +251,6 @@ public class NotificationShadeWindowControllerImplTest extends SysuiTestCase { mShadeWindowLogger, () -> mSelectedUserInteractor, mUserTracker, - mSceneContainerFlags, () -> communalInteractor) { @Override protected boolean isDebuggable() { diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewControllerTest.kt index b04503b8e031..8c5a4d0bbcae 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewControllerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewControllerTest.kt @@ -578,6 +578,14 @@ class NotificationShadeWindowViewControllerTest : SysuiTestCase() { assertEquals(keyEvent, falsingCollector.lastKeyEvent) } + @Test + fun cancelCurrentTouch_callsDragDownHelper() { + mSetFlagsRule.enableFlags(Flags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT) + underTest.cancelCurrentTouch() + + verify(dragDownHelper).stopDragging() + } + companion object { private val DOWN_EVENT = MotionEvent.obtain(0L, 0L, MotionEvent.ACTION_DOWN, 0f, 0f, 0) private val MOVE_EVENT = MotionEvent.obtain(0L, 0L, MotionEvent.ACTION_MOVE, 0f, 0f, 0) diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/QuickSettingsControllerImplBaseTest.java b/packages/SystemUI/tests/src/com/android/systemui/shade/QuickSettingsControllerImplBaseTest.java index 20d877e3e2c5..77ad17a9e918 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/shade/QuickSettingsControllerImplBaseTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/shade/QuickSettingsControllerImplBaseTest.java @@ -62,7 +62,6 @@ import com.android.systemui.qs.QSFragmentLegacy; import com.android.systemui.res.R; import com.android.systemui.scene.data.repository.SceneContainerRepository; import com.android.systemui.scene.domain.interactor.SceneInteractor; -import com.android.systemui.scene.shared.flag.FakeSceneContainerFlags; import com.android.systemui.scene.shared.logger.SceneLogger; import com.android.systemui.screenrecord.RecordingController; import com.android.systemui.shade.data.repository.FakeShadeRepository; @@ -208,14 +207,12 @@ public class QuickSettingsControllerImplBaseTest extends SysuiTestCase { mock(SceneLogger.class), mKosmos.getDeviceUnlockedInteractor()); - FakeSceneContainerFlags sceneContainerFlags = new FakeSceneContainerFlags(); KeyguardTransitionInteractor keyguardTransitionInteractor = mKosmos.getKeyguardTransitionInteractor(); KeyguardInteractor keyguardInteractor = new KeyguardInteractor( mKeyguardRepository, new FakeCommandQueue(), powerInteractor, - sceneContainerFlags, new FakeKeyguardBouncerRepository(), new ConfigurationInteractor(configurationRepository), mShadeRepository, diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/ShadeControllerImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/ShadeControllerImplTest.kt index 433c95ab5791..6bdd3ef5cebe 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/shade/ShadeControllerImplTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/shade/ShadeControllerImplTest.kt @@ -33,7 +33,6 @@ import com.android.systemui.power.domain.interactor.PowerInteractorFactory import com.android.systemui.scene.data.repository.WindowRootViewVisibilityRepository import com.android.systemui.scene.domain.interactor.WindowRootViewVisibilityInteractor import com.android.systemui.scene.domain.interactor.sceneInteractor -import com.android.systemui.scene.shared.flag.sceneContainerFlags import com.android.systemui.statusbar.CommandQueue import com.android.systemui.statusbar.NotificationShadeWindowController import com.android.systemui.statusbar.notification.data.repository.ActiveNotificationListRepository @@ -95,7 +94,6 @@ class ShadeControllerImplTest : SysuiTestCase() { headsUpManager, PowerInteractorFactory.create().powerInteractor, ActiveNotificationsInteractor(activeNotificationsRepository, testDispatcher), - kosmos.sceneContainerFlags, kosmos::sceneInteractor, ) } diff --git a/packages/SystemUI/tests/src/com/android/systemui/shared/animation/UnfoldConstantTranslateAnimatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shared/animation/UnfoldConstantTranslateAnimatorTest.kt index 56fc0c74dafa..a05a23bb8bb1 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/shared/animation/UnfoldConstantTranslateAnimatorTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/shared/animation/UnfoldConstantTranslateAnimatorTest.kt @@ -21,7 +21,7 @@ import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.shared.animation.UnfoldConstantTranslateAnimator.Direction import com.android.systemui.shared.animation.UnfoldConstantTranslateAnimator.ViewIdToTranslate -import com.android.systemui.unfold.TestUnfoldTransitionProvider +import com.android.systemui.unfold.FakeUnfoldTransitionProvider import org.junit.Assert.assertEquals import org.junit.Before import org.junit.Test @@ -34,21 +34,19 @@ import org.mockito.MockitoAnnotations @RunWith(AndroidTestingRunner::class) class UnfoldConstantTranslateAnimatorTest : SysuiTestCase() { - private val progressProvider = TestUnfoldTransitionProvider() + private val progressProvider = FakeUnfoldTransitionProvider() - @Mock - private lateinit var parent: ViewGroup + @Mock private lateinit var parent: ViewGroup - @Mock - private lateinit var shouldBeAnimated: () -> Boolean + @Mock private lateinit var shouldBeAnimated: () -> Boolean private lateinit var animator: UnfoldConstantTranslateAnimator private val viewsIdToRegister get() = setOf( - ViewIdToTranslate(START_VIEW_ID, Direction.START, shouldBeAnimated), - ViewIdToTranslate(END_VIEW_ID, Direction.END, shouldBeAnimated) + ViewIdToTranslate(START_VIEW_ID, Direction.START, shouldBeAnimated), + ViewIdToTranslate(END_VIEW_ID, Direction.END, shouldBeAnimated) ) @Before @@ -122,11 +120,12 @@ class UnfoldConstantTranslateAnimatorTest : SysuiTestCase() { progressProvider.onTransitionStarted() progressProvider.onTransitionProgress(0f) - val rtlMultiplier = if (layoutDirection == View.LAYOUT_DIRECTION_LTR) { - 1 - } else { - -1 - } + val rtlMultiplier = + if (layoutDirection == View.LAYOUT_DIRECTION_LTR) { + 1 + } else { + -1 + } list.forEach { (view, direction) -> assertEquals( (-MAX_TRANSLATION * direction * rtlMultiplier).toInt(), diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/StatusBarStateControllerImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/StatusBarStateControllerImplTest.kt index d2fc087e44bd..be5af885c895 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/StatusBarStateControllerImplTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/StatusBarStateControllerImplTest.kt @@ -55,7 +55,6 @@ import com.android.systemui.plugins.statusbar.StatusBarStateController import com.android.systemui.power.data.repository.FakePowerRepository import com.android.systemui.power.domain.interactor.PowerInteractor import com.android.systemui.scene.domain.interactor.sceneInteractor -import com.android.systemui.scene.shared.flag.FakeSceneContainerFlags import com.android.systemui.scene.shared.model.Scenes import com.android.systemui.shade.LargeScreenHeaderHelper import com.android.systemui.shade.data.repository.FakeShadeRepository @@ -87,8 +86,8 @@ import org.mockito.ArgumentMatchers.eq import org.mockito.Mockito import org.mockito.Mockito.mock import org.mockito.Mockito.verify -import org.mockito.Mockito.`when` as whenever import org.mockito.MockitoAnnotations +import org.mockito.Mockito.`when` as whenever @SmallTest @RunWith(AndroidTestingRunner::class) @@ -135,7 +134,6 @@ class StatusBarStateControllerImplTest : SysuiTestCase() { val keyguardTransitionRepository = FakeKeyguardTransitionRepository() val featureFlags = FakeFeatureFlagsClassic() val shadeRepository = FakeShadeRepository() - val sceneContainerFlags = FakeSceneContainerFlags() val configurationRepository = FakeConfigurationRepository() val keyguardTransitionInteractor = kosmos.keyguardTransitionInteractor fromLockscreenTransitionInteractor = kosmos.fromLockscreenTransitionInteractor @@ -146,7 +144,6 @@ class StatusBarStateControllerImplTest : SysuiTestCase() { keyguardRepository, FakeCommandQueue(), powerInteractor, - sceneContainerFlags, FakeKeyguardBouncerRepository(), ConfigurationInteractor(configurationRepository), shadeRepository, diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/DynamicChildBindControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/DynamicChildBindControllerTest.java index eb692eb1b4ac..0e24ed49fea0 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/DynamicChildBindControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/DynamicChildBindControllerTest.java @@ -40,6 +40,9 @@ import com.android.systemui.statusbar.notification.collection.NotificationEntryB import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow; import com.android.systemui.statusbar.notification.row.RowContentBindParams; import com.android.systemui.statusbar.notification.row.RowContentBindStage; +import com.android.systemui.statusbar.notification.row.RowInflaterTask; +import com.android.systemui.statusbar.notification.row.RowInflaterTaskLogger; +import com.android.systemui.util.time.FakeSystemClock; import org.junit.Before; import org.junit.Test; @@ -114,20 +117,25 @@ public class DynamicChildBindControllerTest extends SysuiTestCase { private NotificationEntry addGroup(int size) { NotificationEntry summary = new NotificationEntryBuilder().build(); - summary.setRow(createRow()); + summary.setRow(createRow(summary)); ArrayList<NotificationEntry> children = new ArrayList<>(); for (int i = 0; i < size; i++) { NotificationEntry child = new NotificationEntryBuilder().build(); - child.setRow(createRow()); + child.setRow(createRow(child)); children.add(child); } mGroupNotifs.put(summary, children); return summary; } - private ExpandableNotificationRow createRow() { + private ExpandableNotificationRow createRow(NotificationEntry entry) { + LayoutInflater inflater = LayoutInflater.from(mContext); + inflater.setFactory2( + new RowInflaterTask.RowAsyncLayoutInflater(entry, new FakeSystemClock(), mock( + RowInflaterTaskLogger.class))); + ExpandableNotificationRow row = (ExpandableNotificationRow) - LayoutInflater.from(mContext).inflate(R.layout.status_bar_notification_row, null); + inflater.inflate(R.layout.status_bar_notification_row, null); row.getPrivateLayout().setContractedChild(new View(mContext)); row.getPrivateLayout().setExpandedChild(new View(mContext)); return row; diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/logging/NotificationLoggerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/logging/NotificationLoggerTest.java index 54a6523d82a1..bb68ec540bac 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/logging/NotificationLoggerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/logging/NotificationLoggerTest.java @@ -138,7 +138,6 @@ public class NotificationLoggerTest extends SysuiTestCase { mHeadsUpManager, mPowerInteractor, mActiveNotificationsInteractor, - mKosmos.getSceneContainerFlags(), () -> mKosmos.getSceneInteractor()); mWindowRootViewVisibilityInteractor.setIsLockscreenOrShadeVisible(true); diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationContentViewTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationContentViewTest.kt index 91e46668ed44..7332bc34b030 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationContentViewTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationContentViewTest.kt @@ -79,10 +79,11 @@ class NotificationContentViewTest : SysuiTestCase() { initMocks(this) fakeParent = spy(FrameLayout(mContext, /* attrs= */ null).also { it.visibility = View.GONE }) + val mockEntry = createMockNotificationEntry() row = spy( - ExpandableNotificationRow(mContext, /* attrs= */ null).apply { - entry = createMockNotificationEntry() + ExpandableNotificationRow(mContext, /* attrs= */ null, mockEntry).apply { + entry = mockEntry } ) ViewUtils.attachView(fakeParent) diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationGutsManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationGutsManagerTest.java index db053d842bdb..9e2856d65a71 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationGutsManagerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationGutsManagerTest.java @@ -180,7 +180,6 @@ public class NotificationGutsManagerTest extends SysuiTestCase { mHeadsUpManager, PowerInteractorFactory.create().getPowerInteractor(), mActiveNotificationsInteractor, - mKosmos.getSceneContainerFlags(), () -> mKosmos.getSceneInteractor() ); diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationGutsManagerWithScenesTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationGutsManagerWithScenesTest.kt index 65a960b5ff6c..1b85dfa5a087 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationGutsManagerWithScenesTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationGutsManagerWithScenesTest.kt @@ -45,6 +45,7 @@ import com.android.internal.logging.testing.UiEventLoggerFake import com.android.internal.statusbar.statusBarService import com.android.systemui.SysuiTestCase import com.android.systemui.concurrency.fakeExecutor +import com.android.systemui.flags.EnableSceneContainer import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository import com.android.systemui.kosmos.testScope import com.android.systemui.people.widget.PeopleSpaceWidgetManager @@ -55,7 +56,6 @@ import com.android.systemui.power.domain.interactor.PowerInteractorFactory.creat import com.android.systemui.scene.data.repository.WindowRootViewVisibilityRepository import com.android.systemui.scene.domain.interactor.WindowRootViewVisibilityInteractor import com.android.systemui.scene.domain.interactor.sceneInteractor -import com.android.systemui.scene.shared.flag.fakeSceneContainerFlags import com.android.systemui.scene.shared.model.Scenes import com.android.systemui.settings.UserContextProvider import com.android.systemui.shade.shadeControllerSceneImpl @@ -93,6 +93,7 @@ import org.mockito.invocation.InvocationOnMock @SmallTest @RunWith(AndroidTestingRunner::class) @RunWithLooper +@EnableSceneContainer class NotificationGutsManagerWithScenesTest : SysuiTestCase() { private val testNotificationChannel = NotificationChannel( @@ -143,8 +144,6 @@ class NotificationGutsManagerWithScenesTest : SysuiTestCase() { @Before fun setUp() { MockitoAnnotations.initMocks(this) - val sceneContainerFlags = kosmos.fakeSceneContainerFlags - sceneContainerFlags.enabled = true allowTestableLooperAsMainThread() helper = NotificationTestHelper(mContext, mDependency) Mockito.`when`(accessibilityManager.isTouchExplorationEnabled).thenReturn(false) @@ -156,9 +155,9 @@ class NotificationGutsManagerWithScenesTest : SysuiTestCase() { headsUpManager, create().powerInteractor, activeNotificationsInteractor, - sceneContainerFlags, - { sceneInteractor }, - ) + ) { + sceneInteractor + } gutsManager = NotificationGutsManager( mContext, diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationShelfTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationShelfTest.kt index 9a7b8ec2ec07..745d20dd686b 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationShelfTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationShelfTest.kt @@ -1,5 +1,6 @@ package com.android.systemui.statusbar.notification.stack +import android.service.notification.StatusBarNotification import android.testing.AndroidTestingRunner import android.testing.TestableLooper.RunWithLooper import android.view.LayoutInflater @@ -14,6 +15,7 @@ import com.android.systemui.res.R import com.android.systemui.shade.transition.LargeScreenShadeInterpolator import com.android.systemui.statusbar.NotificationShelf import com.android.systemui.statusbar.StatusBarIconView +import com.android.systemui.statusbar.notification.collection.NotificationEntry import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow import com.android.systemui.statusbar.notification.row.ExpandableView import com.android.systemui.statusbar.notification.shared.NotificationIconContainerRefactor @@ -457,8 +459,13 @@ open class NotificationShelfTest : SysuiTestCase() { expansionFraction: Float, expectedAlpha: Float ) { + val sbnMock: StatusBarNotification = mock() + val mockEntry = mock<NotificationEntry>().apply { + whenever(this.sbn).thenReturn(sbnMock) + } + val row = ExpandableNotificationRow(mContext, null, mockEntry) whenever(ambientState.lastVisibleBackgroundChild) - .thenReturn(ExpandableNotificationRow(mContext, null)) + .thenReturn(row) whenever(ambientState.isExpansionChanging).thenReturn(true) whenever(ambientState.expansionFraction).thenReturn(expansionFraction) whenever(hostLayoutController.speedBumpIndex).thenReturn(0) diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/domain/interactor/HideNotificationsInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/domain/interactor/HideNotificationsInteractorTest.kt index 4bfd7e3bef83..df82df842c65 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/domain/interactor/HideNotificationsInteractorTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/domain/interactor/HideNotificationsInteractorTest.kt @@ -31,7 +31,7 @@ import com.android.systemui.power.domain.interactor.PowerInteractor import com.android.systemui.power.shared.model.ScreenPowerState.SCREEN_ON import com.android.systemui.power.shared.model.WakefulnessState.STARTING_TO_SLEEP import com.android.systemui.statusbar.policy.FakeConfigurationController -import com.android.systemui.unfold.TestUnfoldTransitionProvider +import com.android.systemui.unfold.FakeUnfoldTransitionProvider import com.android.systemui.unfold.data.repository.UnfoldTransitionRepositoryImpl import com.android.systemui.unfold.domain.interactor.UnfoldTransitionInteractorImpl import com.android.systemui.util.animation.data.repository.FakeAnimationStatusRepository @@ -59,7 +59,7 @@ open class HideNotificationsInteractorTest : SysuiTestCase() { private val animationStatus = FakeAnimationStatusRepository() private val configurationController = FakeConfigurationController() - private val unfoldTransitionProgressProvider = TestUnfoldTransitionProvider() + private val unfoldTransitionProgressProvider = FakeUnfoldTransitionProvider() private val powerRepository = FakePowerRepository() private val powerInteractor = PowerInteractor( 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 783bf80000d6..5c651035262d 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 @@ -31,6 +31,7 @@ import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyBoolean; import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.atLeastOnce; import static org.mockito.Mockito.clearInvocations; import static org.mockito.Mockito.doAnswer; import static org.mockito.Mockito.mock; @@ -101,6 +102,7 @@ import com.android.systemui.communal.domain.interactor.CommunalInteractor; import com.android.systemui.communal.shared.model.CommunalScenes; import com.android.systemui.demomode.DemoModeController; import com.android.systemui.dump.DumpManager; +import com.android.systemui.flags.EnableSceneContainer; import com.android.systemui.flags.FakeFeatureFlags; import com.android.systemui.flags.Flags; import com.android.systemui.fragments.FragmentService; @@ -120,7 +122,6 @@ import com.android.systemui.plugins.statusbar.StatusBarStateController; import com.android.systemui.power.domain.interactor.PowerInteractor; import com.android.systemui.res.R; import com.android.systemui.scene.domain.interactor.WindowRootViewVisibilityInteractor; -import com.android.systemui.scene.shared.flag.FakeSceneContainerFlags; import com.android.systemui.settings.UserTracker; import com.android.systemui.settings.brightness.BrightnessSliderController; import com.android.systemui.settings.brightness.domain.interactor.BrightnessMirrorShowingInteractor; @@ -336,8 +337,6 @@ public class CentralSurfacesImplTest extends SysuiTestCase { private final DumpManager mDumpManager = new DumpManager(); private final ScreenLifecycle mScreenLifecycle = new ScreenLifecycle(mDumpManager); - private final FakeSceneContainerFlags mSceneContainerFlags = new FakeSceneContainerFlags(); - private final BrightnessMirrorShowingInteractor mBrightnessMirrorShowingInteractor = mKosmos.getBrightnessMirrorShowingInteractor(); @@ -351,7 +350,9 @@ public class CentralSurfacesImplTest extends SysuiTestCase { // Turn AOD on and toggle feature flag for jank fixes mFeatureFlags.set(Flags.ZJ_285570694_LOCKSCREEN_TRANSITION_FROM_AOD, true); when(mDozeParameters.getAlwaysOn()).thenReturn(true); - mSetFlagsRule.disableFlags(com.android.systemui.Flags.FLAG_DEVICE_ENTRY_UDFPS_REFACTOR); + if (!com.android.systemui.Flags.sceneContainer()) { + mSetFlagsRule.disableFlags(com.android.systemui.Flags.FLAG_DEVICE_ENTRY_UDFPS_REFACTOR); + } IThermalService thermalService = mock(IThermalService.class); mPowerManager = new PowerManager(mContext, mPowerManagerService, thermalService, @@ -426,22 +427,25 @@ public class CentralSurfacesImplTest extends SysuiTestCase { ((Runnable) invocation.getArgument(0)).run(); return null; }).when(mNotificationShadeWindowController).batchApplyWindowLayoutParams(any()); - - mShadeController = spy(new ShadeControllerImpl( - mCommandQueue, - mMainExecutor, - mock(WindowRootViewVisibilityInteractor.class), - mKeyguardStateController, - mStatusBarStateController, - mStatusBarKeyguardViewManager, - mStatusBarWindowController, - mDeviceProvisionedController, - mNotificationShadeWindowController, - 0, - () -> mNotificationPanelViewController, - () -> mAssistManager, - () -> mNotificationGutsManager - )); + if (com.android.systemui.Flags.sceneContainer()) { + mShadeController = spy(mKosmos.getShadeController()); + } else { + mShadeController = spy(new ShadeControllerImpl( + mCommandQueue, + mMainExecutor, + mock(WindowRootViewVisibilityInteractor.class), + mKeyguardStateController, + mStatusBarStateController, + mStatusBarKeyguardViewManager, + mStatusBarWindowController, + mDeviceProvisionedController, + mNotificationShadeWindowController, + 0, + () -> mNotificationPanelViewController, + () -> mAssistManager, + () -> mNotificationGutsManager + )); + } mShadeController.setNotificationShadeWindowViewController( mNotificationShadeWindowViewController); mShadeController.setNotificationPresenter(mNotificationPresenter); @@ -562,7 +566,6 @@ public class CentralSurfacesImplTest extends SysuiTestCase { mUserTracker, () -> mFingerprintManager, mActivityStarter, - mSceneContainerFlags, mBrightnessMirrorShowingInteractor ); mScreenLifecycle.addObserver(mCentralSurfaces.mScreenObserver); @@ -1094,25 +1097,24 @@ public class CentralSurfacesImplTest extends SysuiTestCase { } @Test + @EnableSceneContainer public void brightnesShowingChanged_flagEnabled_ScrimControllerNotified() { - mSceneContainerFlags.setEnabled(true); mCentralSurfaces.registerCallbacks(); mBrightnessMirrorShowingInteractor.setMirrorShowing(true); mTestScope.getTestScheduler().runCurrent(); - verify(mScrimController).transitionTo(ScrimState.BRIGHTNESS_MIRROR); + verify(mScrimController, atLeastOnce()).transitionTo(ScrimState.BRIGHTNESS_MIRROR); mBrightnessMirrorShowingInteractor.setMirrorShowing(false); mTestScope.getTestScheduler().runCurrent(); ArgumentCaptor<ScrimState> captor = ArgumentCaptor.forClass(ScrimState.class); // The default is to call the one with the callback argument - verify(mScrimController).transitionTo(captor.capture(), any()); + verify(mScrimController, atLeastOnce()).transitionTo(captor.capture(), any()); assertThat(captor.getValue()).isNotEqualTo(ScrimState.BRIGHTNESS_MIRROR); } @Test public void brightnesShowingChanged_flagDisabled_ScrimControllerNotified() { - mSceneContainerFlags.setEnabled(false); mCentralSurfaces.registerCallbacks(); mBrightnessMirrorShowingInteractor.setMirrorShowing(true); diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewControllerTest.java index dc3db4c7fa19..a6a4f24be4a4 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewControllerTest.java @@ -20,9 +20,9 @@ package com.android.systemui.statusbar.phone; import static android.app.StatusBarManager.DISABLE2_SYSTEM_ICONS; import static android.app.StatusBarManager.DISABLE_SYSTEM_INFO; +import static com.android.systemui.Flags.FLAG_UPDATE_USER_SWITCHER_BACKGROUND; import static com.android.systemui.statusbar.StatusBarState.KEYGUARD; import static com.android.systemui.statusbar.StatusBarState.SHADE; -import static com.android.systemui.Flags.FLAG_UPDATE_USER_SWITCHER_BACKGROUND; import static com.google.common.truth.Truth.assertThat; @@ -172,7 +172,6 @@ public class KeyguardStatusBarViewControllerTest extends SysuiTestCase { mKeyguardRepository, mCommandQueue, PowerInteractorFactory.create().getPowerInteractor(), - mKosmos.getSceneContainerFlags(), new FakeKeyguardBouncerRepository(), new ConfigurationInteractor(new FakeConfigurationRepository()), new FakeShadeRepository(), diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewControllerTest.kt index 1463680de7d8..d365663e3621 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewControllerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewControllerTest.kt @@ -33,7 +33,6 @@ import com.android.systemui.SysuiTestCase import com.android.systemui.flags.FeatureFlags import com.android.systemui.flags.Flags import com.android.systemui.res.R -import com.android.systemui.scene.shared.flag.FakeSceneContainerFlags import com.android.systemui.scene.ui.view.WindowRootView import com.android.systemui.shade.ShadeControllerImpl import com.android.systemui.shade.ShadeLogger @@ -51,6 +50,8 @@ import com.android.systemui.util.mockito.argumentCaptor import com.android.systemui.util.mockito.whenever import com.android.systemui.util.view.ViewUtil import com.google.common.truth.Truth.assertThat +import java.util.Optional +import javax.inject.Provider import org.junit.Before import org.junit.Test import org.mockito.ArgumentCaptor @@ -61,8 +62,6 @@ import org.mockito.Mockito.spy import org.mockito.Mockito.verify import org.mockito.Mockito.`when` import org.mockito.MockitoAnnotations -import java.util.Optional -import javax.inject.Provider @SmallTest class PhoneStatusBarViewControllerTest : SysuiTestCase() { @@ -296,7 +295,6 @@ class PhoneStatusBarViewControllerTest : SysuiTestCase() { Optional.of(sysuiUnfoldComponent), Optional.of(progressProvider), featureFlags, - FakeSceneContainerFlags(), userChipViewModel, centralSurfacesImpl, statusBarWindowStateController, diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarMoveFromCenterAnimationControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarMoveFromCenterAnimationControllerTest.kt index feff046bb708..1ec1765e2e57 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarMoveFromCenterAnimationControllerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarMoveFromCenterAnimationControllerTest.kt @@ -8,7 +8,7 @@ import android.view.View import android.view.WindowManager import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase -import com.android.systemui.unfold.TestUnfoldTransitionProvider +import com.android.systemui.unfold.FakeUnfoldTransitionProvider import com.android.systemui.unfold.util.CurrentActivityTypeProvider import com.android.systemui.unfold.util.ScopedUnfoldTransitionProgressProvider import com.android.systemui.util.mockito.whenever @@ -23,17 +23,14 @@ import org.mockito.MockitoAnnotations @TestableLooper.RunWithLooper class StatusBarMoveFromCenterAnimationControllerTest : SysuiTestCase() { - @Mock - private lateinit var windowManager: WindowManager + @Mock private lateinit var windowManager: WindowManager - @Mock - private lateinit var display: Display + @Mock private lateinit var display: Display - @Mock - private lateinit var currentActivityTypeProvider: CurrentActivityTypeProvider + @Mock private lateinit var currentActivityTypeProvider: CurrentActivityTypeProvider private val view: View = View(context) - private val progressProvider = TestUnfoldTransitionProvider() + private val progressProvider = FakeUnfoldTransitionProvider() private val scopedProvider = ScopedUnfoldTransitionProgressProvider(progressProvider) private lateinit var controller: StatusBarMoveFromCenterAnimationController diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/satellite/domain/interactor/DeviceBasedSatelliteInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/satellite/domain/interactor/DeviceBasedSatelliteInteractorTest.kt index 42bbe3e6fa1d..c4ab943c09df 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/satellite/domain/interactor/DeviceBasedSatelliteInteractorTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/satellite/domain/interactor/DeviceBasedSatelliteInteractorTest.kt @@ -26,6 +26,10 @@ import com.android.systemui.statusbar.pipeline.mobile.domain.interactor.FakeMobi import com.android.systemui.statusbar.pipeline.mobile.util.FakeMobileMappingsProxy import com.android.systemui.statusbar.pipeline.satellite.data.prod.FakeDeviceBasedSatelliteRepository import com.android.systemui.statusbar.pipeline.satellite.shared.model.SatelliteConnectionState +import com.android.systemui.statusbar.pipeline.shared.data.repository.FakeConnectivityRepository +import com.android.systemui.statusbar.pipeline.wifi.data.repository.FakeWifiRepository +import com.android.systemui.statusbar.pipeline.wifi.domain.interactor.WifiInteractorImpl +import com.android.systemui.statusbar.pipeline.wifi.shared.model.WifiNetworkModel import com.android.systemui.statusbar.policy.data.repository.FakeDeviceProvisioningRepository import com.android.systemui.statusbar.policy.domain.interactor.DeviceProvisioningInteractor import com.android.systemui.util.mockito.mock @@ -53,6 +57,10 @@ class DeviceBasedSatelliteInteractorTest : SysuiTestCase() { private val deviceProvisionedRepository = FakeDeviceProvisioningRepository() private val deviceProvisioningInteractor = DeviceProvisioningInteractor(deviceProvisionedRepository) + private val connectivityRepository = FakeConnectivityRepository() + private val wifiRepository = FakeWifiRepository() + private val wifiInteractor = + WifiInteractorImpl(connectivityRepository, wifiRepository, testScope.backgroundScope) @Before fun setUp() { @@ -61,6 +69,7 @@ class DeviceBasedSatelliteInteractorTest : SysuiTestCase() { repo, iconsInteractor, deviceProvisioningInteractor, + wifiInteractor, testScope.backgroundScope, ) } @@ -103,6 +112,7 @@ class DeviceBasedSatelliteInteractorTest : SysuiTestCase() { repo, iconsInteractor, deviceProvisioningInteractor, + wifiInteractor, testScope.backgroundScope, ) @@ -150,6 +160,7 @@ class DeviceBasedSatelliteInteractorTest : SysuiTestCase() { repo, iconsInteractor, deviceProvisioningInteractor, + wifiInteractor, testScope.backgroundScope, ) @@ -205,6 +216,7 @@ class DeviceBasedSatelliteInteractorTest : SysuiTestCase() { repo, iconsInteractor, deviceProvisioningInteractor, + wifiInteractor, testScope.backgroundScope, ) @@ -337,6 +349,7 @@ class DeviceBasedSatelliteInteractorTest : SysuiTestCase() { repo, iconsInteractor, deviceProvisioningInteractor, + wifiInteractor, testScope.backgroundScope, ) @@ -353,4 +366,28 @@ class DeviceBasedSatelliteInteractorTest : SysuiTestCase() { // THEN the value is still false, because the flag is off assertThat(latest).isFalse() } + + @Test + fun isWifiActive_falseWhenWifiNotActive() = + testScope.runTest { + val latest by collectLastValue(underTest.isWifiActive) + + // WHEN wifi is not active + wifiRepository.setWifiNetwork(WifiNetworkModel.Invalid("test")) + + // THEN the interactor returns false due to the wifi network not being active + assertThat(latest).isFalse() + } + + @Test + fun isWifiActive_trueWhenWifiIsActive() = + testScope.runTest { + val latest by collectLastValue(underTest.isWifiActive) + + // WHEN wifi is active + wifiRepository.setWifiNetwork(WifiNetworkModel.Active(networkId = 0, level = 1)) + + // THEN the interactor returns true due to the wifi network being active + assertThat(latest).isTrue() + } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/satellite/ui/viewmodel/DeviceBasedSatelliteViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/satellite/ui/viewmodel/DeviceBasedSatelliteViewModelTest.kt index 1d6cd3774449..64f19b6c60ad 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/satellite/ui/viewmodel/DeviceBasedSatelliteViewModelTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/satellite/ui/viewmodel/DeviceBasedSatelliteViewModelTest.kt @@ -26,6 +26,10 @@ import com.android.systemui.statusbar.pipeline.mobile.domain.interactor.FakeMobi import com.android.systemui.statusbar.pipeline.mobile.util.FakeMobileMappingsProxy import com.android.systemui.statusbar.pipeline.satellite.data.prod.FakeDeviceBasedSatelliteRepository import com.android.systemui.statusbar.pipeline.satellite.domain.interactor.DeviceBasedSatelliteInteractor +import com.android.systemui.statusbar.pipeline.shared.data.repository.FakeConnectivityRepository +import com.android.systemui.statusbar.pipeline.wifi.data.repository.FakeWifiRepository +import com.android.systemui.statusbar.pipeline.wifi.domain.interactor.WifiInteractorImpl +import com.android.systemui.statusbar.pipeline.wifi.shared.model.WifiNetworkModel import com.android.systemui.statusbar.policy.data.repository.FakeDeviceProvisioningRepository import com.android.systemui.statusbar.policy.domain.interactor.DeviceProvisioningInteractor import com.android.systemui.util.mockito.mock @@ -44,14 +48,18 @@ class DeviceBasedSatelliteViewModelTest : SysuiTestCase() { private lateinit var underTest: DeviceBasedSatelliteViewModel private lateinit var interactor: DeviceBasedSatelliteInteractor private lateinit var airplaneModeRepository: FakeAirplaneModeRepository - private val repo = FakeDeviceBasedSatelliteRepository() + private val testScope = TestScope() + private val mobileIconsInteractor = FakeMobileIconsInteractor(FakeMobileMappingsProxy(), mock()) + private val deviceProvisionedRepository = FakeDeviceProvisioningRepository() private val deviceProvisioningInteractor = DeviceProvisioningInteractor(deviceProvisionedRepository) - - private val testScope = TestScope() + private val connectivityRepository = FakeConnectivityRepository() + private val wifiRepository = FakeWifiRepository() + private val wifiInteractor = + WifiInteractorImpl(connectivityRepository, wifiRepository, testScope.backgroundScope) @Before fun setUp() { @@ -63,6 +71,7 @@ class DeviceBasedSatelliteViewModelTest : SysuiTestCase() { repo, mobileIconsInteractor, deviceProvisioningInteractor, + wifiInteractor, testScope.backgroundScope, ) @@ -253,4 +262,40 @@ class DeviceBasedSatelliteViewModelTest : SysuiTestCase() { // THEN icon is null because the device is not provisioned assertThat(latest).isInstanceOf(Icon::class.java) } + + @OptIn(ExperimentalCoroutinesApi::class) + @Test + fun icon_wifiIsActive() = + testScope.runTest { + val latest by collectLastValue(underTest.icon) + + // GIVEN satellite is allowed + repo.isSatelliteAllowedForCurrentLocation.value = true + + // GIVEN all icons are OOS + val i1 = mobileIconsInteractor.getMobileConnectionInteractorForSubId(1) + i1.isInService.value = false + i1.isEmergencyOnly.value = false + + // GIVEN apm is disabled + airplaneModeRepository.setIsAirplaneMode(false) + + // GIVEN device is provisioned + deviceProvisionedRepository.setDeviceProvisioned(true) + + // GIVEN wifi network is active + wifiRepository.setWifiNetwork(WifiNetworkModel.Active(networkId = 0, level = 1)) + + // THEN icon is null because the device is connected to wifi + assertThat(latest).isNull() + + // GIVEN device loses wifi connection + wifiRepository.setWifiNetwork(WifiNetworkModel.Invalid("test")) + + // Wait for delay to be completed + advanceTimeBy(10.seconds) + + // THEN icon is set because the device lost wifi connection + assertThat(latest).isInstanceOf(Icon::class.java) + } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/ui/viewmodel/KeyguardStatusBarViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/ui/viewmodel/KeyguardStatusBarViewModelTest.kt index 69536c5e9c0a..bdd3d188ad91 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/ui/viewmodel/KeyguardStatusBarViewModelTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/ui/viewmodel/KeyguardStatusBarViewModelTest.kt @@ -30,7 +30,6 @@ import com.android.systemui.keyguard.shared.model.StatusBarState import com.android.systemui.kosmos.testScope import com.android.systemui.power.domain.interactor.PowerInteractorFactory import com.android.systemui.scene.domain.interactor.sceneInteractor -import com.android.systemui.scene.shared.flag.sceneContainerFlags import com.android.systemui.shade.data.repository.FakeShadeRepository import com.android.systemui.statusbar.CommandQueue import com.android.systemui.statusbar.data.repository.FakeKeyguardStatusBarRepository @@ -60,7 +59,6 @@ class KeyguardStatusBarViewModelTest : SysuiTestCase() { keyguardRepository, mock<CommandQueue>(), PowerInteractorFactory.create().powerInteractor, - kosmos.sceneContainerFlags, FakeKeyguardBouncerRepository(), ConfigurationInteractor(FakeConfigurationRepository()), FakeShadeRepository(), diff --git a/packages/SystemUI/tests/src/com/android/systemui/unfold/DisplaySwitchLatencyTrackerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/unfold/DisplaySwitchLatencyTrackerTest.kt index 28adbceda847..383f4a33857d 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/unfold/DisplaySwitchLatencyTrackerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/unfold/DisplaySwitchLatencyTrackerTest.kt @@ -86,7 +86,7 @@ class DisplaySwitchLatencyTrackerTest : SysuiTestCase() { private val areAnimationEnabled = MutableStateFlow(true) private val lastWakefulnessEvent = MutableStateFlow(WakefulnessModel()) private val systemClock = FakeSystemClock() - private val unfoldTransitionProgressProvider = TestUnfoldTransitionProvider() + private val unfoldTransitionProgressProvider = FakeUnfoldTransitionProvider() private val unfoldTransitionRepository = UnfoldTransitionRepositoryImpl(Optional.of(unfoldTransitionProgressProvider)) private val unfoldTransitionInteractor = diff --git a/packages/SystemUI/tests/src/com/android/systemui/unfold/UnfoldHapticsPlayerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/unfold/UnfoldHapticsPlayerTest.kt index b9c7e6133669..fd513c9c9235 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/unfold/UnfoldHapticsPlayerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/unfold/UnfoldHapticsPlayerTest.kt @@ -35,7 +35,7 @@ import org.mockito.Mockito.verify @SmallTest class UnfoldHapticsPlayerTest : SysuiTestCase() { - private val progressProvider = TestUnfoldTransitionProvider() + private val progressProvider = FakeUnfoldTransitionProvider() private val vibrator: Vibrator = mock() private val testFoldProvider = TestFoldProvider() diff --git a/packages/SystemUI/tests/src/com/android/systemui/unfold/UnfoldLatencyTrackerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/unfold/UnfoldLatencyTrackerTest.kt index ba72716997e3..2955384c0bbd 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/unfold/UnfoldLatencyTrackerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/unfold/UnfoldLatencyTrackerTest.kt @@ -27,6 +27,7 @@ import com.android.systemui.keyguard.ScreenLifecycle import com.android.systemui.unfold.util.FoldableDeviceStates import com.android.systemui.unfold.util.FoldableTestUtils import com.android.systemui.util.mockito.any +import java.util.Optional import org.junit.Before import org.junit.Test import org.junit.runner.RunWith @@ -37,45 +38,41 @@ import org.mockito.Mockito.never import org.mockito.Mockito.verify import org.mockito.Mockito.verifyNoMoreInteractions import org.mockito.MockitoAnnotations -import java.util.Optional @RunWith(AndroidTestingRunner::class) @SmallTest class UnfoldLatencyTrackerTest : SysuiTestCase() { - @Mock - lateinit var latencyTracker: LatencyTracker + @Mock lateinit var latencyTracker: LatencyTracker - @Mock - lateinit var deviceStateManager: DeviceStateManager + @Mock lateinit var deviceStateManager: DeviceStateManager - @Mock - lateinit var screenLifecycle: ScreenLifecycle + @Mock lateinit var screenLifecycle: ScreenLifecycle - @Captor - private lateinit var foldStateListenerCaptor: ArgumentCaptor<FoldStateListener> + @Captor private lateinit var foldStateListenerCaptor: ArgumentCaptor<FoldStateListener> - @Captor - private lateinit var screenLifecycleCaptor: ArgumentCaptor<ScreenLifecycle.Observer> + @Captor private lateinit var screenLifecycleCaptor: ArgumentCaptor<ScreenLifecycle.Observer> private lateinit var deviceStates: FoldableDeviceStates private lateinit var unfoldLatencyTracker: UnfoldLatencyTracker - private val transitionProgressProvider = TestUnfoldTransitionProvider() + private val transitionProgressProvider = FakeUnfoldTransitionProvider() @Before fun setUp() { MockitoAnnotations.initMocks(this) - unfoldLatencyTracker = UnfoldLatencyTracker( - latencyTracker, - deviceStateManager, - Optional.of(transitionProgressProvider), - context.mainExecutor, - context, - context.contentResolver, - screenLifecycle - ).apply { init() } + unfoldLatencyTracker = + UnfoldLatencyTracker( + latencyTracker, + deviceStateManager, + Optional.of(transitionProgressProvider), + context.mainExecutor, + context, + context.contentResolver, + screenLifecycle + ) + .apply { init() } deviceStates = FoldableTestUtils.findDeviceStates(context) verify(deviceStateManager).registerCallback(any(), foldStateListenerCaptor.capture()) @@ -107,7 +104,7 @@ class UnfoldLatencyTrackerTest : SysuiTestCase() { } @Test - fun unfold_firstFoldEventAnimationsEnabledOnScreenTurnedOnAndTransitionStarted_eventNotPropagated() { + fun firstFoldEventAnimationsEnabledOnScreenTurnedOnAndTransitionStarted_eventNotPropagated() { setAnimationsEnabled(true) sendFoldEvent(folded = false) @@ -118,7 +115,7 @@ class UnfoldLatencyTrackerTest : SysuiTestCase() { } @Test - fun unfold_secondFoldEventAnimationsEnabledOnScreenTurnedOnAndTransitionStarted_eventPropagated() { + fun secondFoldEventAnimationsEnabledOnScreenTurnedOnAndTransitionStarted_eventPropagated() { setAnimationsEnabled(true) sendFoldEvent(folded = true) sendFoldEvent(folded = false) @@ -131,7 +128,7 @@ class UnfoldLatencyTrackerTest : SysuiTestCase() { } @Test - fun unfold_unfoldFoldUnfoldAnimationsEnabledOnScreenTurnedOnAndTransitionStarted_eventPropagated() { + fun unfoldFoldUnfoldAnimationsEnabledOnScreenTurnedOnAndTransitionStarted_eventPropagated() { setAnimationsEnabled(true) sendFoldEvent(folded = false) sendFoldEvent(folded = true) @@ -196,4 +193,4 @@ class UnfoldLatencyTrackerTest : SysuiTestCase() { durationScale.toString() ) } -}
\ No newline at end of file +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/unfold/UnfoldTransitionWallpaperControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/unfold/UnfoldTransitionWallpaperControllerTest.kt index 6ec0251d41a5..0c452eb9d461 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/unfold/UnfoldTransitionWallpaperControllerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/unfold/UnfoldTransitionWallpaperControllerTest.kt @@ -17,21 +17,18 @@ import org.mockito.junit.MockitoJUnit @SmallTest class UnfoldTransitionWallpaperControllerTest : SysuiTestCase() { - @Mock - private lateinit var wallpaperController: WallpaperController + @Mock private lateinit var wallpaperController: WallpaperController - private val progressProvider = TestUnfoldTransitionProvider() + private val progressProvider = FakeUnfoldTransitionProvider() - @JvmField - @Rule - val mockitoRule = MockitoJUnit.rule() + @JvmField @Rule val mockitoRule = MockitoJUnit.rule() private lateinit var unfoldWallpaperController: UnfoldTransitionWallpaperController @Before fun setup() { - unfoldWallpaperController = UnfoldTransitionWallpaperController(progressProvider, - wallpaperController) + unfoldWallpaperController = + UnfoldTransitionWallpaperController(progressProvider, wallpaperController) unfoldWallpaperController.init() } diff --git a/packages/SystemUI/tests/src/com/android/systemui/unfold/progress/MainThreadUnfoldTransitionProgressProviderTest.kt b/packages/SystemUI/tests/src/com/android/systemui/unfold/progress/MainThreadUnfoldTransitionProgressProviderTest.kt index 2bc05fcc8166..e5f619b299a1 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/unfold/progress/MainThreadUnfoldTransitionProgressProviderTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/unfold/progress/MainThreadUnfoldTransitionProgressProviderTest.kt @@ -23,7 +23,7 @@ import android.testing.AndroidTestingRunner import android.testing.TestableLooper.RunWithLooper import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase -import com.android.systemui.unfold.TestUnfoldTransitionProvider +import com.android.systemui.unfold.FakeUnfoldTransitionProvider import com.android.systemui.utils.os.FakeHandler import kotlin.test.Test import kotlinx.coroutines.test.runTest @@ -34,7 +34,7 @@ import org.junit.runner.RunWith @RunWithLooper(setAsMainLooper = true) class MainThreadUnfoldTransitionProgressProviderTest : SysuiTestCase() { - private val wrappedProgressProvider = TestUnfoldTransitionProvider() + private val wrappedProgressProvider = FakeUnfoldTransitionProvider() private val fakeHandler = FakeHandler(Looper.getMainLooper()) private val listener = TestUnfoldProgressListener() diff --git a/packages/SystemUI/tests/src/com/android/systemui/unfold/util/NaturalRotationUnfoldProgressProviderTest.kt b/packages/SystemUI/tests/src/com/android/systemui/unfold/util/NaturalRotationUnfoldProgressProviderTest.kt index d864d53fea32..70ec050afe1d 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/unfold/util/NaturalRotationUnfoldProgressProviderTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/unfold/util/NaturalRotationUnfoldProgressProviderTest.kt @@ -20,7 +20,7 @@ import android.testing.TestableLooper import android.view.Surface import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase -import com.android.systemui.unfold.TestUnfoldTransitionProvider +import com.android.systemui.unfold.FakeUnfoldTransitionProvider import com.android.systemui.unfold.UnfoldTransitionProgressProvider.TransitionProgressListener import com.android.systemui.unfold.updates.RotationChangeProvider import com.android.systemui.unfold.updates.RotationChangeProvider.RotationListener @@ -43,14 +43,14 @@ class NaturalRotationUnfoldProgressProviderTest : SysuiTestCase() { @Mock lateinit var rotationChangeProvider: RotationChangeProvider - private val sourceProvider = TestUnfoldTransitionProvider() + private val sourceProvider = FakeUnfoldTransitionProvider() @Mock lateinit var transitionListener: TransitionProgressListener @Captor private lateinit var rotationListenerCaptor: ArgumentCaptor<RotationListener> lateinit var progressProvider: NaturalRotationUnfoldProgressProvider - private lateinit var testableLooper : TestableLooper + private lateinit var testableLooper: TestableLooper @Before fun setUp() { diff --git a/packages/SystemUI/tests/src/com/android/systemui/unfold/util/ScaleAwareUnfoldProgressProviderTest.kt b/packages/SystemUI/tests/src/com/android/systemui/unfold/util/ScaleAwareUnfoldProgressProviderTest.kt index 2f29b3bdd3b5..451bd24dd83f 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/unfold/util/ScaleAwareUnfoldProgressProviderTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/unfold/util/ScaleAwareUnfoldProgressProviderTest.kt @@ -22,7 +22,7 @@ import android.testing.AndroidTestingRunner import android.testing.TestableLooper import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase -import com.android.systemui.unfold.TestUnfoldTransitionProvider +import com.android.systemui.unfold.FakeUnfoldTransitionProvider import com.android.systemui.unfold.UnfoldTransitionProgressProvider.TransitionProgressListener import com.android.systemui.util.mockito.any import org.junit.Before @@ -42,7 +42,7 @@ class ScaleAwareUnfoldProgressProviderTest : SysuiTestCase() { @Mock lateinit var sinkProvider: TransitionProgressListener - private val sourceProvider = TestUnfoldTransitionProvider() + private val sourceProvider = FakeUnfoldTransitionProvider() private lateinit var contentResolver: ContentResolver private lateinit var progressProvider: ScaleAwareTransitionProgressProvider @@ -132,6 +132,6 @@ class ScaleAwareUnfoldProgressProviderTest : SysuiTestCase() { durationScale.toString() ) - animatorDurationScaleListenerCaptor.value.dispatchChange(/* selfChange= */false) + animatorDurationScaleListenerCaptor.value.dispatchChange(/* selfChange= */ false) } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/unfold/util/ScopedUnfoldTransitionProgressProviderTest.kt b/packages/SystemUI/tests/src/com/android/systemui/unfold/util/ScopedUnfoldTransitionProgressProviderTest.kt index 95c934e988e7..4486402f43ec 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/unfold/util/ScopedUnfoldTransitionProgressProviderTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/unfold/util/ScopedUnfoldTransitionProgressProviderTest.kt @@ -23,7 +23,7 @@ import android.testing.AndroidTestingRunner import android.testing.TestableLooper.RunWithLooper import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase -import com.android.systemui.unfold.TestUnfoldTransitionProvider +import com.android.systemui.unfold.FakeUnfoldTransitionProvider import com.android.systemui.unfold.progress.TestUnfoldProgressListener import com.google.common.truth.Truth.assertThat import kotlin.time.Duration.Companion.seconds @@ -43,7 +43,7 @@ import org.junit.runner.RunWith @RunWithLooper class ScopedUnfoldTransitionProgressProviderTest : SysuiTestCase() { - private val rootProvider = TestUnfoldTransitionProvider() + private val rootProvider = FakeUnfoldTransitionProvider() private val listener = TestUnfoldProgressListener() private val testScope = TestScope(UnconfinedTestDispatcher()) private val bgThread = diff --git a/packages/SystemUI/tests/src/com/android/systemui/unfold/util/UnfoldOnlyProgressProviderTest.kt b/packages/SystemUI/tests/src/com/android/systemui/unfold/util/UnfoldOnlyProgressProviderTest.kt index f484ea04bb4f..cd4d7b54916e 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/unfold/util/UnfoldOnlyProgressProviderTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/unfold/util/UnfoldOnlyProgressProviderTest.kt @@ -19,7 +19,7 @@ import android.testing.AndroidTestingRunner import android.testing.TestableLooper import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase -import com.android.systemui.unfold.TestUnfoldTransitionProvider +import com.android.systemui.unfold.FakeUnfoldTransitionProvider import com.android.systemui.unfold.progress.TestUnfoldProgressListener import com.google.common.util.concurrent.MoreExecutors import org.junit.Before @@ -32,7 +32,7 @@ import org.junit.runner.RunWith class UnfoldOnlyProgressProviderTest : SysuiTestCase() { private val listener = TestUnfoldProgressListener() - private val sourceProvider = TestUnfoldTransitionProvider() + private val sourceProvider = FakeUnfoldTransitionProvider() private val foldProvider = TestFoldProvider() diff --git a/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java b/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java index c24c86c8cb2a..d9a0c4be04ff 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java @@ -121,8 +121,6 @@ import com.android.systemui.power.domain.interactor.PowerInteractor; import com.android.systemui.scene.FakeWindowRootViewComponent; import com.android.systemui.scene.data.repository.SceneContainerRepository; import com.android.systemui.scene.domain.interactor.SceneInteractor; -import com.android.systemui.scene.shared.flag.FakeSceneContainerFlags; -import com.android.systemui.scene.shared.flag.SceneContainerFlags; import com.android.systemui.scene.shared.logger.SceneLogger; import com.android.systemui.settings.FakeDisplayTracker; import com.android.systemui.settings.UserTracker; @@ -360,8 +358,6 @@ public class BubblesTest extends SysuiTestCase { @Mock private Display mDefaultDisplay; @Mock - private SceneContainerFlags mSceneContainerFlags; - @Mock private LargeScreenHeaderHelper mLargeScreenHeaderHelper; private final KosmosJavaAdapter mKosmos = new KosmosJavaAdapter(this); @@ -430,14 +426,12 @@ public class BubblesTest extends SysuiTestCase { mock(SceneLogger.class), mKosmos.getDeviceUnlockedInteractor()); - FakeSceneContainerFlags sceneContainerFlags = new FakeSceneContainerFlags(); KeyguardTransitionInteractor keyguardTransitionInteractor = mKosmos.getKeyguardTransitionInteractor(); KeyguardInteractor keyguardInteractor = new KeyguardInteractor( keyguardRepository, new FakeCommandQueue(), powerInteractor, - sceneContainerFlags, new FakeKeyguardBouncerRepository(), new ConfigurationInteractor(configurationRepository), shadeRepository, @@ -503,7 +497,6 @@ public class BubblesTest extends SysuiTestCase { mShadeWindowLogger, () -> mSelectedUserInteractor, mUserTracker, - mSceneContainerFlags, mKosmos::getCommunalInteractor ); mNotificationShadeWindowController.fetchWindowRootView(); diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/SysUITestModule.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/SysUITestModule.kt index de7b14d1e102..0682361823ee 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/SysUITestModule.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/SysUITestModule.kt @@ -31,7 +31,7 @@ import com.android.systemui.deviceentry.data.repository.FaceWakeUpTriggersConfig import com.android.systemui.deviceentry.domain.interactor.DeviceEntryFaceAuthInteractor import com.android.systemui.deviceentry.domain.interactor.SystemUIDeviceEntryFaceAuthInteractor import com.android.systemui.scene.SceneContainerFrameworkModule -import com.android.systemui.scene.shared.flag.SceneContainerFlags +import com.android.systemui.scene.shared.flag.SceneContainerFlag import com.android.systemui.scene.shared.model.SceneContainerConfig import com.android.systemui.scene.shared.model.SceneDataSource import com.android.systemui.scene.shared.model.SceneDataSourceDelegator @@ -104,11 +104,10 @@ interface SysUITestModule { @Provides fun provideBaseShadeInteractor( - sceneContainerFlags: SceneContainerFlags, sceneContainerOn: Provider<ShadeInteractorSceneContainerImpl>, sceneContainerOff: Provider<ShadeInteractorLegacyImpl> ): BaseShadeInteractor { - return if (sceneContainerFlags.isEnabled()) { + return if (SceneContainerFlag.isEnabled) { sceneContainerOn.get() } else { sceneContainerOff.get() diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/bouncer/shared/flag/ComposeBouncerFlagsKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/bouncer/shared/flag/ComposeBouncerFlagsKosmos.kt index 5c3e1f410e63..60d97d1b1437 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/bouncer/shared/flag/ComposeBouncerFlagsKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/bouncer/shared/flag/ComposeBouncerFlagsKosmos.kt @@ -17,8 +17,6 @@ package com.android.systemui.bouncer.shared.flag import com.android.systemui.kosmos.Kosmos -import com.android.systemui.scene.shared.flag.fakeSceneContainerFlags -var Kosmos.fakeComposeBouncerFlags by - Kosmos.Fixture { FakeComposeBouncerFlags(fakeSceneContainerFlags) } +var Kosmos.fakeComposeBouncerFlags by Kosmos.Fixture { FakeComposeBouncerFlags() } val Kosmos.composeBouncerFlags by Kosmos.Fixture<ComposeBouncerFlags> { fakeComposeBouncerFlags } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/bouncer/shared/flag/FakeComposeBouncerFlags.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/bouncer/shared/flag/FakeComposeBouncerFlags.kt index c116bbd32f9e..7482c0feb56a 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/bouncer/shared/flag/FakeComposeBouncerFlags.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/bouncer/shared/flag/FakeComposeBouncerFlags.kt @@ -16,14 +16,11 @@ package com.android.systemui.bouncer.shared.flag -import com.android.systemui.scene.shared.flag.SceneContainerFlags +import com.android.systemui.scene.shared.flag.SceneContainerFlag -class FakeComposeBouncerFlags( - private val sceneContainerFlags: SceneContainerFlags, - var composeBouncerEnabled: Boolean = false -) : ComposeBouncerFlags { +class FakeComposeBouncerFlags(var composeBouncerEnabled: Boolean = false) : ComposeBouncerFlags { override fun isComposeBouncerOrSceneContainerEnabled(): Boolean { - return sceneContainerFlags.isEnabled() || composeBouncerEnabled + return SceneContainerFlag.isEnabled || composeBouncerEnabled } @Deprecated( diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/communal/domain/interactor/CommunalInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/domain/interactor/CommunalInteractorKosmos.kt index 4b6ef373a7ee..3dd382f6db3e 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/communal/domain/interactor/CommunalInteractorKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/domain/interactor/CommunalInteractorKosmos.kt @@ -34,7 +34,6 @@ import com.android.systemui.kosmos.applicationCoroutineScope import com.android.systemui.log.logcatLogBuffer import com.android.systemui.plugins.activityStarter import com.android.systemui.scene.domain.interactor.sceneInteractor -import com.android.systemui.scene.shared.flag.sceneContainerFlags import com.android.systemui.settings.userTracker import com.android.systemui.smartspace.data.repository.smartspaceRepository import com.android.systemui.user.data.repository.fakeUserRepository @@ -46,21 +45,20 @@ val Kosmos.communalInteractor by Fixture { broadcastDispatcher = broadcastDispatcher, communalRepository = communalRepository, widgetRepository = communalWidgetRepository, - mediaRepository = communalMediaRepository, communalPrefsRepository = communalPrefsRepository, + mediaRepository = communalMediaRepository, smartspaceRepository = smartspaceRepository, - appWidgetHost = mock(), keyguardInteractor = keyguardInteractor, + communalSettingsInteractor = communalSettingsInteractor, + appWidgetHost = mock(), editWidgetsActivityStarter = editWidgetsActivityStarter, userTracker = userTracker, activityStarter = activityStarter, userManager = userManager, dockManager = fakeDockManager, + sceneInteractor = sceneInteractor, logBuffer = logcatLogBuffer("CommunalInteractor"), tableLogBuffer = mock(), - communalSettingsInteractor = communalSettingsInteractor, - sceneInteractor = sceneInteractor, - sceneContainerFlags = sceneContainerFlags, ) } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractorFactory.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractorFactory.kt index e21c76672c1d..2e751cce7bad 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractorFactory.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractorFactory.kt @@ -24,12 +24,9 @@ import com.android.systemui.flags.FakeFeatureFlags import com.android.systemui.keyguard.data.repository.FakeCommandQueue import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository import com.android.systemui.keyguard.shared.model.KeyguardState -import com.android.systemui.kosmos.testScope import com.android.systemui.power.domain.interactor.PowerInteractor import com.android.systemui.power.domain.interactor.PowerInteractorFactory import com.android.systemui.scene.domain.interactor.SceneInteractor -import com.android.systemui.scene.shared.flag.FakeSceneContainerFlags -import com.android.systemui.scene.shared.flag.SceneContainerFlags import com.android.systemui.shade.data.repository.FakeShadeRepository import com.android.systemui.statusbar.notification.stack.domain.interactor.SharedNotificationContainerInteractor import com.android.systemui.statusbar.notification.stack.domain.interactor.SharedNotificationContainerInteractor.ConfigurationBasedDimensions @@ -49,7 +46,6 @@ object KeyguardInteractorFactory { @JvmStatic fun create( featureFlags: FakeFeatureFlags = FakeFeatureFlags(), - sceneContainerFlags: SceneContainerFlags = FakeSceneContainerFlags(), repository: FakeKeyguardRepository = FakeKeyguardRepository(), commandQueue: FakeCommandQueue = FakeCommandQueue(), bouncerRepository: FakeKeyguardBouncerRepository = FakeKeyguardBouncerRepository(), @@ -88,7 +84,6 @@ object KeyguardInteractorFactory { repository = repository, commandQueue = commandQueue, featureFlags = featureFlags, - sceneContainerFlags = sceneContainerFlags, bouncerRepository = bouncerRepository, configurationRepository = configurationRepository, shadeRepository = shadeRepository, @@ -96,13 +91,12 @@ object KeyguardInteractorFactory { KeyguardInteractor( repository = repository, commandQueue = commandQueue, - sceneContainerFlags = sceneContainerFlags, + powerInteractor = powerInteractor, bouncerRepository = bouncerRepository, configurationInteractor = ConfigurationInteractor(configurationRepository), shadeRepository = shadeRepository, - sceneInteractorProvider = { sceneInteractor }, keyguardTransitionInteractor = keyguardTransitionInteractor, - powerInteractor = powerInteractor, + sceneInteractorProvider = { sceneInteractor }, fromGoneTransitionInteractor = { fromGoneTransitionInteractor }, sharedNotificationContainerInteractor = { sncInteractor }, applicationScope = testScope, @@ -114,7 +108,6 @@ object KeyguardInteractorFactory { val repository: FakeKeyguardRepository, val commandQueue: FakeCommandQueue, val featureFlags: FakeFeatureFlags, - val sceneContainerFlags: SceneContainerFlags, val bouncerRepository: FakeKeyguardBouncerRepository, val configurationRepository: FakeConfigurationRepository, val shadeRepository: FakeShadeRepository, diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractorKosmos.kt index 2a0c01c5c0af..94267189e179 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractorKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractorKosmos.kt @@ -23,7 +23,6 @@ import com.android.systemui.kosmos.Kosmos import com.android.systemui.kosmos.testScope import com.android.systemui.power.domain.interactor.powerInteractor import com.android.systemui.scene.domain.interactor.sceneInteractor -import com.android.systemui.scene.shared.flag.sceneContainerFlags import com.android.systemui.shade.data.repository.shadeRepository import com.android.systemui.statusbar.commandQueue import com.android.systemui.statusbar.notification.stack.domain.interactor.sharedNotificationContainerInteractor @@ -34,7 +33,6 @@ val Kosmos.keyguardInteractor: KeyguardInteractor by repository = keyguardRepository, commandQueue = commandQueue, powerInteractor = powerInteractor, - sceneContainerFlags = sceneContainerFlags, bouncerRepository = keyguardBouncerRepository, configurationInteractor = configurationInteractor, shadeRepository = shadeRepository, diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerViewModelKosmos.kt index b4f1218617a2..bdd4afa3da7d 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerViewModelKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerViewModelKosmos.kt @@ -18,7 +18,7 @@ package com.android.systemui.keyguard.ui.viewmodel -import com.android.systemui.keyguard.ui.keyguardTransitionAnimationFlow +import com.android.systemui.keyguard.domain.interactor.keyguardTransitionInteractor import com.android.systemui.kosmos.Kosmos import com.android.systemui.kosmos.Kosmos.Fixture import com.android.systemui.statusbar.phone.statusBarKeyguardViewManager @@ -27,6 +27,6 @@ import kotlinx.coroutines.ExperimentalCoroutinesApi val Kosmos.alternateBouncerViewModel by Fixture { AlternateBouncerViewModel( statusBarKeyguardViewManager = statusBarKeyguardViewManager, - animationFlow = keyguardTransitionAnimationFlow, + keyguardTransitionInteractor = keyguardTransitionInteractor, ) } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryIconViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryIconViewModelKosmos.kt index 709f86426f94..58b0ff8513f5 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryIconViewModelKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryIconViewModelKosmos.kt @@ -26,7 +26,6 @@ import com.android.systemui.keyguard.ui.transitions.DeviceEntryIconTransition import com.android.systemui.kosmos.Kosmos import com.android.systemui.kosmos.Kosmos.Fixture import com.android.systemui.kosmos.testScope -import com.android.systemui.scene.shared.flag.sceneContainerFlags import com.android.systemui.shade.domain.interactor.shadeInteractor import com.android.systemui.statusbar.phone.statusBarKeyguardViewManager import kotlinx.coroutines.ExperimentalCoroutinesApi @@ -47,7 +46,6 @@ val Kosmos.deviceEntryIconViewModel by Fixture { transitionInteractor = keyguardTransitionInteractor, keyguardInteractor = keyguardInteractor, viewModel = aodToLockscreenTransitionViewModel, - sceneContainerFlags = sceneContainerFlags, keyguardViewController = { statusBarKeyguardViewManager }, deviceEntryInteractor = deviceEntryInteractor, deviceEntrySourceInteractor = deviceEntrySourceInteractor, diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/kosmos/KosmosJavaAdapter.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/kosmos/KosmosJavaAdapter.kt index fdc3e0a22627..162f278b7a8f 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/kosmos/KosmosJavaAdapter.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/kosmos/KosmosJavaAdapter.kt @@ -48,10 +48,9 @@ import com.android.systemui.power.data.repository.fakePowerRepository import com.android.systemui.power.domain.interactor.powerInteractor import com.android.systemui.scene.domain.interactor.sceneInteractor import com.android.systemui.scene.sceneContainerConfig -import com.android.systemui.scene.shared.flag.fakeSceneContainerFlags -import com.android.systemui.scene.shared.flag.sceneContainerFlags import com.android.systemui.scene.shared.model.sceneDataSource import com.android.systemui.settings.brightness.domain.interactor.brightnessMirrorShowingInteractor +import com.android.systemui.shade.shadeController import com.android.systemui.statusbar.notification.stack.domain.interactor.sharedNotificationContainerInteractor import com.android.systemui.statusbar.phone.screenOffAnimationController import com.android.systemui.statusbar.pipeline.mobile.data.repository.fakeMobileConnectionsRepository @@ -71,8 +70,6 @@ class KosmosJavaAdapter( val testDispatcher by lazy { kosmos.testDispatcher } val testScope by lazy { kosmos.testScope } val fakeFeatureFlags by lazy { kosmos.fakeFeatureFlagsClassic } - val fakeSceneContainerFlags by lazy { kosmos.fakeSceneContainerFlags } - val sceneContainerFlags by lazy { kosmos.sceneContainerFlags } val fakeExecutor by lazy { kosmos.fakeExecutor } val fakeExecutorHandler by lazy { kosmos.fakeExecutorHandler } val configurationRepository by lazy { kosmos.fakeConfigurationRepository } @@ -112,6 +109,7 @@ class KosmosJavaAdapter( } val brightnessMirrorShowingInteractor by lazy { kosmos.brightnessMirrorShowingInteractor } val qsLongPressEffect by lazy { kosmos.qsLongPressEffect } + val shadeController by lazy { kosmos.shadeController } init { kosmos.applicationContext = testCase.context diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/media/controls/ui/viewmodel/MediaControlViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/media/controls/ui/viewmodel/MediaControlViewModelKosmos.kt index da2170c85fe7..2f3d3c3e0489 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/media/controls/ui/viewmodel/MediaControlViewModelKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/media/controls/ui/viewmodel/MediaControlViewModelKosmos.kt @@ -17,6 +17,7 @@ package com.android.systemui.media.controls.ui.viewmodel import android.content.applicationContext +import com.android.systemui.concurrency.fakeExecutor import com.android.systemui.kosmos.Kosmos import com.android.systemui.kosmos.testDispatcher import com.android.systemui.media.controls.domain.pipeline.interactor.mediaControlInteractor @@ -27,6 +28,7 @@ val Kosmos.mediaControlViewModel by MediaControlViewModel( applicationContext = applicationContext, backgroundDispatcher = testDispatcher, + backgroundExecutor = fakeExecutor, interactor = mediaControlInteractor, logger = mediaUiEventLogger, ) diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/media/controls/util/MediaFlagsKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/media/controls/util/MediaFlagsKosmos.kt index 6f652f224975..e88d22a0e0cd 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/media/controls/util/MediaFlagsKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/media/controls/util/MediaFlagsKosmos.kt @@ -18,9 +18,5 @@ package com.android.systemui.media.controls.util import com.android.systemui.flags.featureFlagsClassic import com.android.systemui.kosmos.Kosmos -import com.android.systemui.scene.shared.flag.sceneContainerFlags -val Kosmos.mediaFlags by - Kosmos.Fixture { - MediaFlags(featureFlags = featureFlagsClassic, sceneContainerFlags = sceneContainerFlags) - } +val Kosmos.mediaFlags by Kosmos.Fixture { MediaFlags(featureFlags = featureFlagsClassic) } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/scene/shared/flag/FakeSceneContainerFlags.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/scene/shared/flag/FakeSceneContainerFlags.kt deleted file mode 100644 index ded725683e59..000000000000 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/scene/shared/flag/FakeSceneContainerFlags.kt +++ /dev/null @@ -1,44 +0,0 @@ -/* - * Copyright 2023 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.systemui.scene.shared.flag - -import dagger.Binds -import dagger.Module -import dagger.Provides - -class FakeSceneContainerFlags( - var enabled: Boolean = SceneContainerFlag.isEnabled, -) : SceneContainerFlags { - - override fun isEnabled(): Boolean { - return enabled - } - - override fun requirementDescription(): String { - return "" - } -} - -@Module(includes = [FakeSceneContainerFlagsModule.Bindings::class]) -class FakeSceneContainerFlagsModule( - @get:Provides val sceneContainerFlags: FakeSceneContainerFlags = FakeSceneContainerFlags(), -) { - @Module - interface Bindings { - @Binds fun bindFake(fake: FakeSceneContainerFlags): SceneContainerFlags - } -} diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/scene/shared/flag/SceneContainerFlagsKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/scene/shared/flag/SceneContainerFlagsKosmos.kt deleted file mode 100644 index 979d8e76f3ee..000000000000 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/scene/shared/flag/SceneContainerFlagsKosmos.kt +++ /dev/null @@ -1,22 +0,0 @@ -/* - * Copyright (C) 2023 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.systemui.scene.shared.flag - -import com.android.systemui.kosmos.Kosmos - -var Kosmos.fakeSceneContainerFlags by Kosmos.Fixture { FakeSceneContainerFlags() } -val Kosmos.sceneContainerFlags by Kosmos.Fixture<SceneContainerFlags> { fakeSceneContainerFlags } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/shade/domain/interactor/ShadeInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/shade/domain/interactor/ShadeInteractorKosmos.kt index 07e2d6b0334c..543d5b6f8857 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/shade/domain/interactor/ShadeInteractorKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/shade/domain/interactor/ShadeInteractorKosmos.kt @@ -23,7 +23,6 @@ import com.android.systemui.kosmos.Kosmos import com.android.systemui.kosmos.applicationCoroutineScope import com.android.systemui.power.domain.interactor.powerInteractor import com.android.systemui.scene.domain.interactor.sceneInteractor -import com.android.systemui.scene.shared.flag.sceneContainerFlags import com.android.systemui.shade.ShadeModule import com.android.systemui.shade.data.repository.shadeRepository import com.android.systemui.statusbar.disableflags.data.repository.disableFlagsRepository @@ -36,7 +35,6 @@ import com.android.systemui.user.domain.interactor.userSwitcherInteractor var Kosmos.baseShadeInteractor: BaseShadeInteractor by Kosmos.Fixture { ShadeModule.provideBaseShadeInteractor( - sceneContainerFlags = sceneContainerFlags, sceneContainerOn = { shadeInteractorSceneContainerImpl }, sceneContainerOff = { shadeInteractorLegacyImpl }, ) diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationsPlaceholderViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationsPlaceholderViewModelKosmos.kt index b2492110f849..f0eea386ecdb 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationsPlaceholderViewModelKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationsPlaceholderViewModelKosmos.kt @@ -21,7 +21,6 @@ import com.android.systemui.flags.featureFlagsClassic import com.android.systemui.keyguard.domain.interactor.keyguardInteractor import com.android.systemui.kosmos.Kosmos import com.android.systemui.kosmos.Kosmos.Fixture -import com.android.systemui.scene.shared.flag.sceneContainerFlags import com.android.systemui.shade.domain.interactor.shadeInteractor import com.android.systemui.statusbar.notification.stack.domain.interactor.notificationStackAppearanceInteractor @@ -30,7 +29,6 @@ val Kosmos.notificationsPlaceholderViewModel by Fixture { dumpManager = dumpManager, interactor = notificationStackAppearanceInteractor, shadeInteractor = shadeInteractor, - flags = sceneContainerFlags, featureFlags = featureFlagsClassic, keyguardInteractor = keyguardInteractor, ) diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/WindowRootViewVisibilityInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/WindowRootViewVisibilityInteractorKosmos.kt index cf800d04f816..9c3f510c8585 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/WindowRootViewVisibilityInteractorKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/WindowRootViewVisibilityInteractorKosmos.kt @@ -24,7 +24,6 @@ import com.android.systemui.power.domain.interactor.powerInteractor import com.android.systemui.scene.data.repository.windowRootViewVisibilityRepository import com.android.systemui.scene.domain.interactor.WindowRootViewVisibilityInteractor import com.android.systemui.scene.domain.interactor.sceneInteractor -import com.android.systemui.scene.shared.flag.sceneContainerFlags import com.android.systemui.statusbar.notification.domain.interactor.activeNotificationsInteractor import com.android.systemui.statusbar.policy.headsUpManager @@ -36,7 +35,7 @@ val Kosmos.windowRootViewVisibilityInteractor by Fixture { headsUpManager = headsUpManager, powerInteractor = powerInteractor, activeNotificationsInteractor = activeNotificationsInteractor, - sceneInteractorProvider = { sceneInteractor }, - sceneContainerFlags = sceneContainerFlags, - ) + ) { + sceneInteractor + } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/unfold/TestUnfoldTransitionProvider.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/unfold/FakeUnfoldTransitionProvider.kt index 56c624565971..94f0c44a51b2 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/unfold/TestUnfoldTransitionProvider.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/unfold/FakeUnfoldTransitionProvider.kt @@ -2,7 +2,7 @@ package com.android.systemui.unfold import com.android.systemui.unfold.UnfoldTransitionProgressProvider.TransitionProgressListener -class TestUnfoldTransitionProvider : UnfoldTransitionProgressProvider, TransitionProgressListener { +class FakeUnfoldTransitionProvider : UnfoldTransitionProgressProvider, TransitionProgressListener { private val listeners = mutableListOf<TransitionProgressListener>() diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/unfold/UnfoldTransitionProgressProviderKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/unfold/UnfoldTransitionProgressProviderKosmos.kt index 7c54a5707bf4..a0f5b58c1cd0 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/unfold/UnfoldTransitionProgressProviderKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/unfold/UnfoldTransitionProgressProviderKosmos.kt @@ -18,6 +18,8 @@ package com.android.systemui.unfold import com.android.systemui.kosmos.Kosmos import com.android.systemui.kosmos.Kosmos.Fixture -import com.android.systemui.util.mockito.mock -var Kosmos.unfoldTransitionProgressProvider by Fixture { mock<UnfoldTransitionProgressProvider>() } +val Kosmos.fakeUnfoldTransitionProgressProvider by Fixture { FakeUnfoldTransitionProvider() } + +val Kosmos.unfoldTransitionProgressProvider by + Fixture<UnfoldTransitionProgressProvider> { fakeUnfoldTransitionProgressProvider } diff --git a/packages/SystemUI/utils/Android.bp b/packages/SystemUI/utils/Android.bp new file mode 100644 index 000000000000..1ef381617c20 --- /dev/null +++ b/packages/SystemUI/utils/Android.bp @@ -0,0 +1,32 @@ +// +// 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 { + default_team: "trendy_team_system_ui_please_use_a_more_specific_subteam_if_possible_", + default_applicable_licenses: ["frameworks_base_packages_SystemUI_license"], +} + +java_library { + name: "SystemUI-shared-utils", + srcs: [ + "src/**/*.java", + "src/**/*.kt", + ], + static_libs: [ + "kotlin-stdlib", + "kotlinx_coroutines", + ], +} diff --git a/packages/SystemUI/utils/src/com/android/systemui/utils/coroutines/flow/FlowConflated.kt b/packages/SystemUI/utils/src/com/android/systemui/utils/coroutines/flow/FlowConflated.kt new file mode 100644 index 000000000000..ed97c600adec --- /dev/null +++ b/packages/SystemUI/utils/src/com/android/systemui/utils/coroutines/flow/FlowConflated.kt @@ -0,0 +1,149 @@ +/* + * 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. + */ + +@file:OptIn(ExperimentalTypeInference::class) + +package com.android.systemui.utils.coroutines.flow + +import kotlin.experimental.ExperimentalTypeInference +import kotlinx.coroutines.channels.ProducerScope +import kotlinx.coroutines.channels.SendChannel +import kotlinx.coroutines.channels.awaitClose +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.buffer +import kotlinx.coroutines.flow.callbackFlow +import kotlinx.coroutines.flow.channelFlow +import kotlinx.coroutines.flow.conflate +import kotlinx.coroutines.flow.flowOn +import kotlinx.coroutines.flow.produceIn +import kotlinx.coroutines.suspendCancellableCoroutine + +/** + * Creates an instance of a _cold_ [Flow] with elements that are sent to a [SendChannel] provided to + * the builder's [block] of code via [ProducerScope]. It allows elements to be produced by code that + * is running in a different context or concurrently. + * + * The resulting flow is _cold_, which means that [block] is called every time a terminal operator + * is applied to the resulting flow. + * + * This builder ensures thread-safety and context preservation, thus the provided [ProducerScope] + * can be used from any context, e.g. from a callback-based API. The resulting flow completes as + * soon as the code in the [block] completes. [awaitClose] should be used to keep the flow running, + * otherwise the channel will be closed immediately when block completes. [awaitClose] argument is + * called either when a flow consumer cancels the flow collection or when a callback-based API + * invokes [SendChannel.close] manually and is typically used to cleanup the resources after the + * completion, e.g. unregister a callback. Using [awaitClose] is mandatory in order to prevent + * memory leaks when the flow collection is cancelled, otherwise the callback may keep running even + * when the flow collector is already completed. To avoid such leaks, this method throws + * [IllegalStateException] if block returns, but the channel is not closed yet. + * + * A [conflated][conflate] channel is used. Use the [buffer] operator on the resulting flow to + * specify a user-defined value and to control what happens when data is produced faster than + * consumed, i.e. to control the back-pressure behavior. + * + * Adjacent applications of [callbackFlow], [flowOn], [buffer], and [produceIn] are always fused so + * that only one properly configured channel is used for execution. + * + * Example of usage that converts a multi-shot callback API to a flow. For single-shot callbacks use + * [suspendCancellableCoroutine]. + * + * ``` + * fun flowFrom(api: CallbackBasedApi): Flow<T> = callbackFlow { + * val callback = object : Callback { // Implementation of some callback interface + * override fun onNextValue(value: T) { + * // To avoid blocking you can configure channel capacity using + * // either buffer(Channel.CONFLATED) or buffer(Channel.UNLIMITED) to avoid overfill + * trySendBlocking(value) + * .onFailure { throwable -> + * // Downstream has been cancelled or failed, can log here + * } + * } + * override fun onApiError(cause: Throwable) { + * cancel(CancellationException("API Error", cause)) + * } + * override fun onCompleted() = channel.close() + * } + * api.register(callback) + * /* + * * Suspends until either 'onCompleted'/'onApiError' from the callback is invoked + * * or flow collector is cancelled (e.g. by 'take(1)' or because a collector's coroutine was cancelled). + * * In both cases, callback will be properly unregistered. + * */ + * awaitClose { api.unregister(callback) } + * } + * ``` + * > The callback `register`/`unregister` methods provided by an external API must be thread-safe, + * > because `awaitClose` block can be called at any time due to asynchronous nature of + * > cancellation, even concurrently with the call of the callback. + * + * This builder is to be preferred over [callbackFlow], due to the latter's default configuration of + * using an internal buffer, negatively impacting system health. + * + * @see callbackFlow + */ +fun <T> conflatedCallbackFlow( + @BuilderInference block: suspend ProducerScope<T>.() -> Unit, +): Flow<T> = callbackFlow(block).conflate() + +/** + * Creates an instance of a _cold_ [Flow] with elements that are sent to a [SendChannel] provided to + * the builder's [block] of code via [ProducerScope]. It allows elements to be produced by code that + * is running in a different context or concurrently. The resulting flow is _cold_, which means that + * [block] is called every time a terminal operator is applied to the resulting flow. + * + * This builder ensures thread-safety and context preservation, thus the provided [ProducerScope] + * can be used concurrently from different contexts. The resulting flow completes as soon as the + * code in the [block] and all its children completes. Use [awaitClose] as the last statement to + * keep it running. A more detailed example is provided in the documentation of [callbackFlow]. + * + * A [conflated][conflate] channel is used. Use the [buffer] operator on the resulting flow to + * specify a user-defined value and to control what happens when data is produced faster than + * consumed, i.e. to control the back-pressure behavior. + * + * Adjacent applications of [channelFlow], [flowOn], [buffer], and [produceIn] are always fused so + * that only one properly configured channel is used for execution. + * + * Examples of usage: + * ``` + * fun <T> Flow<T>.merge(other: Flow<T>): Flow<T> = channelFlow { + * // collect from one coroutine and send it + * launch { + * collect { send(it) } + * } + * // collect and send from this coroutine, too, concurrently + * other.collect { send(it) } + * } + * + * fun <T> contextualFlow(): Flow<T> = channelFlow { + * // send from one coroutine + * launch(Dispatchers.IO) { + * send(computeIoValue()) + * } + * // send from another coroutine, concurrently + * launch(Dispatchers.Default) { + * send(computeCpuValue()) + * } + * } + * ``` + * + * This builder is to be preferred over [channelFlow], due to the latter's default configuration of + * using an internal buffer, negatively impacting system health. + * + * @see channelFlow + */ +fun <T> conflatedChannelFlow( + @BuilderInference block: suspend ProducerScope<T>.() -> Unit, +): Flow<T> = channelFlow(block).conflate() diff --git a/packages/SystemUI/utils/src/com/android/systemui/utils/coroutines/flow/LatestConflated.kt b/packages/SystemUI/utils/src/com/android/systemui/utils/coroutines/flow/LatestConflated.kt new file mode 100644 index 000000000000..5f8c66078483 --- /dev/null +++ b/packages/SystemUI/utils/src/com/android/systemui/utils/coroutines/flow/LatestConflated.kt @@ -0,0 +1,120 @@ +/* + * 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. + */ + +@file:OptIn(ExperimentalCoroutinesApi::class, ExperimentalTypeInference::class) + +package com.android.systemui.utils.coroutines.flow + +import kotlin.experimental.ExperimentalTypeInference +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.FlowCollector +import kotlinx.coroutines.flow.conflate +import kotlinx.coroutines.flow.flatMapLatest +import kotlinx.coroutines.flow.mapLatest +import kotlinx.coroutines.flow.transformLatest + +/** + * Returns a flow that emits elements from the original flow transformed by [transform] function. + * When the original flow emits a new value, computation of the [transform] block for previous value + * is cancelled. + * + * For example, the following flow: + * ``` + * flow { + * emit("a") + * delay(100) + * emit("b") + * }.mapLatest { value -> + * println("Started computing $value") + * delay(200) + * "Computed $value" + * } + * ``` + * + * will print "Started computing a" and "Started computing b", but the resulting flow will contain + * only "Computed b" value. + * + * This operator is [conflated][conflate] by default, and as such should be preferred over usage of + * [mapLatest], due to the latter's default configuration of using an internal buffer, negatively + * impacting system health. + * + * @see mapLatest + */ +fun <T, R> Flow<T>.mapLatestConflated(@BuilderInference transform: suspend (T) -> R): Flow<R> = + mapLatest(transform).conflate() + +/** + * Returns a flow that switches to a new flow produced by [transform] function every time the + * original flow emits a value. When the original flow emits a new value, the previous flow produced + * by `transform` block is cancelled. + * + * For example, the following flow: + * ``` + * flow { + * emit("a") + * delay(100) + * emit("b") + * }.flatMapLatest { value -> + * flow { + * emit(value) + * delay(200) + * emit(value + "_last") + * } + * } + * ``` + * + * produces `a b b_last` + * + * This operator is [conflated][conflate] by default, and as such should be preferred over usage of + * [flatMapLatest], due to the latter's default configuration of using an internal buffer, + * negatively impacting system health. + * + * @see flatMapLatest + */ +fun <T, R> Flow<T>.flatMapLatestConflated( + @BuilderInference transform: suspend (T) -> Flow<R>, +): Flow<R> = flatMapLatest(transform).conflate() + +/** + * Returns a flow that produces element by [transform] function every time the original flow emits a + * value. When the original flow emits a new value, the previous `transform` block is cancelled, + * thus the name `transformLatest`. + * + * For example, the following flow: + * ``` + * flow { + * emit("a") + * delay(100) + * emit("b") + * }.transformLatest { value -> + * emit(value) + * delay(200) + * emit(value + "_last") + * } + * ``` + * + * produces `a b b_last`. + * + * This operator is [conflated][conflate] by default, and as such should be preferred over usage of + * [transformLatest], due to the latter's default configuration of using an internal buffer, + * negatively impacting system health. + * + * @see transformLatest + */ +fun <T, R> Flow<T>.transformLatestConflated( + @BuilderInference transform: suspend FlowCollector<R>.(T) -> Unit, +): Flow<R> = transformLatest(transform).conflate() diff --git a/packages/WallpaperBackup/src/com/android/wallpaperbackup/WallpaperBackupAgent.java b/packages/WallpaperBackup/src/com/android/wallpaperbackup/WallpaperBackupAgent.java index 23e269a67283..cbbce1a74e94 100644 --- a/packages/WallpaperBackup/src/com/android/wallpaperbackup/WallpaperBackupAgent.java +++ b/packages/WallpaperBackup/src/com/android/wallpaperbackup/WallpaperBackupAgent.java @@ -19,6 +19,7 @@ package com.android.wallpaperbackup; import static android.app.WallpaperManager.FLAG_LOCK; import static android.app.WallpaperManager.FLAG_SYSTEM; import static android.app.WallpaperManager.ORIENTATION_UNKNOWN; +import static android.os.ParcelFileDescriptor.MODE_READ_ONLY; import static com.android.wallpaperbackup.WallpaperEventLogger.ERROR_INELIGIBLE; import static com.android.wallpaperbackup.WallpaperEventLogger.ERROR_NO_METADATA; @@ -39,6 +40,7 @@ import android.content.Context; import android.content.SharedPreferences; import android.content.pm.IPackageManager; import android.content.pm.PackageInfo; +import android.graphics.BitmapFactory; import android.graphics.Point; import android.graphics.Rect; import android.hardware.display.DisplayManager; @@ -109,22 +111,16 @@ public class WallpaperBackupAgent extends BackupAgent { static final String LOCK_WALLPAPER_STAGE = "wallpaper-lock-stage"; @VisibleForTesting static final String WALLPAPER_INFO_STAGE = "wallpaper-info-stage"; - @VisibleForTesting static final String WALLPAPER_BACKUP_DEVICE_INFO_STAGE = "wallpaper-backup-device-info-stage"; - static final String EMPTY_SENTINEL = "empty"; static final String QUOTA_SENTINEL = "quota"; - // Shared preferences constants. static final String PREFS_NAME = "wbprefs.xml"; static final String SYSTEM_GENERATION = "system_gen"; static final String LOCK_GENERATION = "lock_gen"; - /** - * An approximate area threshold to compare device dimension similarity - */ - static final int AREA_THRESHOLD = 50; // TODO (b/327637867): determine appropriate threshold + static final float DEFAULT_ACCEPTABLE_PARALLAX = 0.2f; // If this file exists, it means we exceeded our quota last time private File mQuotaFile; @@ -336,7 +332,6 @@ public class WallpaperBackupAgent extends BackupAgent { mEventLogger.onSystemImageWallpaperBackupFailed(error); } - private void backupLockWallpaperFileIfItExists(SharedPreferences sharedPrefs, boolean lockChanged, int lockGeneration, FullBackupDataOutput data) throws IOException { final File lockImageStage = new File(getFilesDir(), LOCK_WALLPAPER_STAGE); @@ -409,6 +404,16 @@ public class WallpaperBackupAgent extends BackupAgent { } } + private static String readText(TypedXmlPullParser parser) + throws IOException, XmlPullParserException { + String result = ""; + if (parser.next() == XmlPullParser.TEXT) { + result = parser.getText(); + parser.nextTag(); + } + return result; + } + @VisibleForTesting // fullBackupFile is final, so we intercept backups here in tests. protected void backupFile(File file, FullBackupDataOutput data) { @@ -438,18 +443,10 @@ public class WallpaperBackupAgent extends BackupAgent { boolean lockImageStageExists = lockImageStage.exists(); try { - // Parse the device dimensions of the source device and compare with target to - // to identify whether we need to skip the remainder of the restore process + // Parse the device dimensions of the source device Pair<Point, Point> sourceDeviceDimensions = parseDeviceDimensions( deviceDimensionsStage); - Point targetDeviceDimensions = getScreenDimensions(); - if (sourceDeviceDimensions != null && targetDeviceDimensions != null - && isSourceDeviceSignificantlySmallerThanTarget(sourceDeviceDimensions.first, - targetDeviceDimensions)) { - Slog.d(TAG, "The source device is significantly smaller than target"); - } - // First parse the live component name so that we know for logging if we care about // logging errors with the image restore. ComponentName wpService = parseWallpaperComponent(infoStage, "wp"); @@ -466,9 +463,10 @@ public class WallpaperBackupAgent extends BackupAgent { // to back up the original image on the source device, or there was no user-supplied // wallpaper image present. if (lockImageStageExists) { - restoreFromStage(lockImageStage, infoStage, "kwp", FLAG_LOCK); + restoreFromStage(lockImageStage, infoStage, "kwp", FLAG_LOCK, + sourceDeviceDimensions); } - restoreFromStage(imageStage, infoStage, "wp", sysWhich); + restoreFromStage(imageStage, infoStage, "wp", sysWhich, sourceDeviceDimensions); // And reset to the wallpaper service we should be using if (mLockHasLiveComponent) { @@ -543,16 +541,6 @@ public class WallpaperBackupAgent extends BackupAgent { } } - private static String readText(TypedXmlPullParser parser) - throws IOException, XmlPullParserException { - String result = ""; - if (parser.next() == XmlPullParser.TEXT) { - result = parser.getText(); - parser.nextTag(); - } - return result; - } - @VisibleForTesting void updateWallpaperComponent(ComponentName wpService, int which) throws IOException { @@ -578,10 +566,13 @@ public class WallpaperBackupAgent extends BackupAgent { } } - private void restoreFromStage(File stage, File info, String hintTag, int which) + private void restoreFromStage(File stage, File info, String hintTag, int which, + Pair<Point, Point> sourceDeviceDimensions) throws IOException { if (stage.exists()) { if (multiCrop()) { + // TODO(b/332937943): implement offset adjustment by manually adjusting crop to + // adhere to device aspect ratio SparseArray<Rect> cropHints = parseCropHints(info, hintTag); if (cropHints != null) { Slog.i(TAG, "Got restored wallpaper; applying which=" + which @@ -601,7 +592,6 @@ public class WallpaperBackupAgent extends BackupAgent { } return; } - // Parse the restored info file to find the crop hint. Note that this currently // relies on a priori knowledge of the wallpaper info file schema. Rect cropHint = parseCropHint(info, hintTag); @@ -609,8 +599,33 @@ public class WallpaperBackupAgent extends BackupAgent { Slog.i(TAG, "Got restored wallpaper; applying which=" + which + "; cropHint = " + cropHint); try (FileInputStream in = new FileInputStream(stage)) { - mWallpaperManager.setStream(in, cropHint.isEmpty() ? null : cropHint, true, - which); + + if (sourceDeviceDimensions != null && sourceDeviceDimensions.first != null) { + BitmapFactory.Options options = new BitmapFactory.Options(); + options.inJustDecodeBounds = true; + ParcelFileDescriptor pdf = ParcelFileDescriptor.open(stage, MODE_READ_ONLY); + BitmapFactory.decodeFileDescriptor(pdf.getFileDescriptor(), + null, options); + Point bitmapSize = new Point(options.outWidth, options.outHeight); + Point sourceDeviceSize = new Point(sourceDeviceDimensions.first.x, + sourceDeviceDimensions.first.y); + Point targetDeviceDimensions = getScreenDimensions(); + + // TODO: for now we handle only the case where the target device has smaller + // aspect ratio than the source device i.e. the target device is more narrow + // than the source device + if (isTargetMoreNarrowThanSource(targetDeviceDimensions, + sourceDeviceSize)) { + Rect adjustedCrop = findNewCropfromOldCrop(cropHint, + sourceDeviceDimensions.first, true, targetDeviceDimensions, + bitmapSize, true); + + cropHint.set(adjustedCrop); + } + } + + mWallpaperManager.setStream(in, cropHint.isEmpty() ? null : cropHint, + true, which); // And log the success if ((which & FLAG_SYSTEM) > 0) { @@ -629,6 +644,209 @@ public class WallpaperBackupAgent extends BackupAgent { } } + /** + * This method computes the crop of the stored wallpaper to preserve its center point as the + * user had set it in the previous device. + * + * The algorithm involves first computing the original crop of the user (without parallax). Then + * manually adjusting the user's original crop to respect the current device's aspect ratio + * (thereby preserving the center point). Then finally, adding any leftover image real-estate + * (i.e. space left over on the horizontal axis) to add parallax effect. Parallax is only added + * if was present in the old device's settings. + * + */ + private Rect findNewCropfromOldCrop(Rect oldCrop, Point oldDisplaySize, boolean oldRtl, + Point newDisplaySize, Point bitmapSize, boolean newRtl) { + Rect cropWithoutParallax = withoutParallax(oldCrop, oldDisplaySize, oldRtl, bitmapSize); + oldCrop = oldCrop.isEmpty() ? new Rect(0, 0, bitmapSize.x, bitmapSize.y) : oldCrop; + float oldParallaxAmount = ((float) oldCrop.width() / cropWithoutParallax.width()) - 1; + + Rect newCropWithSameCenterWithoutParallax = sameCenter(newDisplaySize, bitmapSize, + cropWithoutParallax); + + Rect newCrop = newCropWithSameCenterWithoutParallax; + + // calculate the amount of left-over space there is in the image after adjusting the crop + // from the above operation i.e. in a rtl configuration, this is the remaining space in the + // image after subtracting the new crop's right edge coordinate from the image itself, and + // for ltr, its just the new crop's left edge coordinate (as it's the distance from the + // beginning of the image) + int widthAvailableForParallaxOnTheNewDevice = + (newRtl) ? newCrop.left : bitmapSize.x - newCrop.right; + + // calculate relatively how much this available space is as a fraction of the total cropped + // image + float availableParallaxAmount = + (float) widthAvailableForParallaxOnTheNewDevice / newCrop.width(); + + float minAcceptableParallax = Math.min(DEFAULT_ACCEPTABLE_PARALLAX, oldParallaxAmount); + + if (DEBUG) { + Slog.d(TAG, "- cropWithoutParallax: " + cropWithoutParallax); + Slog.d(TAG, "- oldParallaxAmount: " + oldParallaxAmount); + Slog.d(TAG, "- newCropWithSameCenterWithoutParallax: " + + newCropWithSameCenterWithoutParallax); + Slog.d(TAG, "- widthAvailableForParallaxOnTheNewDevice: " + + widthAvailableForParallaxOnTheNewDevice); + Slog.d(TAG, "- availableParallaxAmount: " + availableParallaxAmount); + Slog.d(TAG, "- minAcceptableParallax: " + minAcceptableParallax); + Slog.d(TAG, "- oldCrop: " + oldCrop); + Slog.d(TAG, "- oldDisplaySize: " + oldDisplaySize); + Slog.d(TAG, "- oldRtl: " + oldRtl); + Slog.d(TAG, "- newDisplaySize: " + newDisplaySize); + Slog.d(TAG, "- bitmapSize: " + bitmapSize); + Slog.d(TAG, "- newRtl: " + newRtl); + } + if (availableParallaxAmount >= minAcceptableParallax) { + // but in any case, don't put more parallax than the amount of the old device + float parallaxToAdd = Math.min(availableParallaxAmount, oldParallaxAmount); + + int widthToAddForParallax = (int) (newCrop.width() * parallaxToAdd); + if (DEBUG) { + Slog.d(TAG, "- parallaxToAdd: " + parallaxToAdd); + Slog.d(TAG, "- widthToAddForParallax: " + widthToAddForParallax); + } + if (newRtl) { + newCrop.left -= widthToAddForParallax; + } else { + newCrop.right += widthToAddForParallax; + } + } + return newCrop; + } + + /** + * This method computes the original crop of the user without parallax. + * + * NOTE: When the user sets the wallpaper with a specific crop, there may additional image added + * to the crop to support parallax. In order to determine the user's actual crop the parallax + * must be removed if it exists. + */ + Rect withoutParallax(Rect crop, Point displaySize, boolean rtl, Point bitmapSize) { + // in the case an image's crop is not set, we assume the image itself is cropped + if (crop.isEmpty()) { + crop = new Rect(0, 0, bitmapSize.x, bitmapSize.y); + } + + if (DEBUG) { + Slog.w(TAG, "- crop: " + crop); + } + + Rect adjustedCrop = new Rect(crop); + float suggestedDisplayRatio = (float) displaySize.x / displaySize.y; + + // here we calculate the width of the wallpaper image such that it has the same aspect ratio + // as the given display i.e. the width of the image on a single page of the device without + // parallax (i.e. displaySize will correspond to the display the crop was originally set on) + int wallpaperWidthWithoutParallax = (int) (0.5f + (float) displaySize.x * crop.height() + / displaySize.y); + // subtracting wallpaperWidthWithoutParallax from the wallpaper crop gives the amount of + // parallax added + int widthToRemove = Math.max(0, crop.width() - wallpaperWidthWithoutParallax); + + if (DEBUG) { + Slog.d(TAG, "- adjustedCrop: " + adjustedCrop); + Slog.d(TAG, "- suggestedDisplayRatio: " + suggestedDisplayRatio); + Slog.d(TAG, "- wallpaperWidthWithoutParallax: " + wallpaperWidthWithoutParallax); + Slog.d(TAG, "- widthToRemove: " + widthToRemove); + } + if (rtl) { + adjustedCrop.left += widthToRemove; + } else { + adjustedCrop.right -= widthToRemove; + } + + if (DEBUG) { + Slog.d(TAG, "- adjustedCrop: " + crop); + } + return adjustedCrop; + } + + /** + * This method computes a new crop based on the given crop in order to preserve the center point + * of the given crop on the provided displaySize. This is only for the case where the device + * displaySize has a smaller aspect ratio than the cropped image. + * + * NOTE: If the width to height ratio is less in the device display than cropped image + * this means the aspect ratios are off and there will be distortions in the image + * if the image is applied to the current display (i.e. the image will be skewed -> + * pixels in the image will not align correctly with the same pixels in the image that are + * above them) + */ + Rect sameCenter(Point displaySize, Point bitmapSize, Rect crop) { + + // in the case an image's crop is not set, we assume the image itself is cropped + if (crop.isEmpty()) { + crop = new Rect(0, 0, bitmapSize.x, bitmapSize.y); + } + + float screenRatio = (float) displaySize.x / displaySize.y; + float cropRatio = (float) crop.width() / crop.height(); + + Rect adjustedCrop = new Rect(crop); + + if (screenRatio < cropRatio) { + // the screen is more narrow than the image, and as such, the image will need to be + // zoomed in till it fits in the vertical axis. Due to this, we need to manually adjust + // the image's crop in order for it to fit into the screen without having the framework + // do it (since the framework left aligns the image after zooming) + + // Calculate the height of the adjusted wallpaper crop so it respects the aspect ratio + // of the device. To calculate the height, we will use the width of the current crop. + // This is so we find the largest height possible which also respects the device aspect + // ratio. + int heightToAdd = (int) (0.5f + crop.width() / screenRatio - crop.height()); + + // Calculate how much extra image space available that can be used to adjust + // the crop. If this amount is less than heightToAdd, from above, then that means we + // can't use heightToAdd. Instead we will need to use the maximum possible height, which + // is the height of the original bitmap. NOTE: the bitmap height may be different than + // the crop. + // since there is no guarantee to have height available on both sides + // (e.g. the available height might be fully at the bottom), grab the minimum + int availableHeight = 2 * Math.min(crop.top, bitmapSize.y - crop.bottom); + int actualHeightToAdd = Math.min(heightToAdd, availableHeight); + + // half of the additional height is added to the top and bottom of the crop + adjustedCrop.top -= actualHeightToAdd / 2 + actualHeightToAdd % 2; + adjustedCrop.bottom += actualHeightToAdd / 2; + + // Calculate the width of the adjusted crop. Initially we used the fixed width of the + // crop to calculate the heightToAdd, but since this height may be invalid (based on + // the calculation above) we calculate the width again instead of using the fixed width, + // using the adjustedCrop's updated height. + int widthToRemove = (int) (0.5f + crop.width() - adjustedCrop.height() * screenRatio); + + // half of the additional width is subtracted from the left and right side of the crop + int widthToRemoveLeft = widthToRemove / 2; + int widthToRemoveRight = widthToRemove / 2 + widthToRemove % 2; + + adjustedCrop.left += widthToRemoveLeft; + adjustedCrop.right -= widthToRemoveRight; + + if (DEBUG) { + Slog.d(TAG, "cropRatio: " + cropRatio); + Slog.d(TAG, "screenRatio: " + screenRatio); + Slog.d(TAG, "heightToAdd: " + heightToAdd); + Slog.d(TAG, "actualHeightToAdd: " + actualHeightToAdd); + Slog.d(TAG, "availableHeight: " + availableHeight); + Slog.d(TAG, "widthToRemove: " + widthToRemove); + Slog.d(TAG, "adjustedCrop: " + adjustedCrop); + } + + return adjustedCrop; + } + + return adjustedCrop; + } + + private boolean isTargetMoreNarrowThanSource(Point targetDisplaySize, Point srcDisplaySize) { + float targetScreenRatio = (float) targetDisplaySize.x / targetDisplaySize.y; + float srcScreenRatio = (float) srcDisplaySize.x / srcDisplaySize.y; + + return (targetScreenRatio < srcScreenRatio); + } + private void logRestoreErrorIfNoLiveComponent(int which, String error) { if (mSystemHasLiveComponent) { return; @@ -644,6 +862,7 @@ public class WallpaperBackupAgent extends BackupAgent { mEventLogger.onLockImageWallpaperRestoreFailed(error); } } + private Rect parseCropHint(File wallpaperInfo, String sectionTag) { Rect cropHint = new Rect(); try (FileInputStream stream = new FileInputStream(wallpaperInfo)) { @@ -681,7 +900,7 @@ public class WallpaperBackupAgent extends BackupAgent { if (type != XmlPullParser.START_TAG) continue; String tag = parser.getName(); if (!sectionTag.equals(tag)) continue; - for (Pair<Integer, String> pair: List.of( + for (Pair<Integer, String> pair : List.of( new Pair<>(WallpaperManager.PORTRAIT, "Portrait"), new Pair<>(WallpaperManager.LANDSCAPE, "Landscape"), new Pair<>(WallpaperManager.SQUARE_PORTRAIT, "SquarePortrait"), @@ -907,22 +1126,6 @@ public class WallpaperBackupAgent extends BackupAgent { return internalDisplays; } - /** - * This method compares the source and target dimensions, and returns true if there is a - * significant difference in area between them and the source dimensions are smaller than the - * target dimensions. - * - * @param sourceDimensions is the dimensions of the source device - * @param targetDimensions is the dimensions of the target device - */ - @VisibleForTesting - boolean isSourceDeviceSignificantlySmallerThanTarget(Point sourceDimensions, - Point targetDimensions) { - int rawAreaDelta = (targetDimensions.x * targetDimensions.y) - - (sourceDimensions.x * sourceDimensions.y); - return rawAreaDelta > AREA_THRESHOLD; - } - @VisibleForTesting boolean isDeviceInRestore() { try { diff --git a/packages/WallpaperBackup/test/src/com/android/wallpaperbackup/WallpaperBackupAgentTest.java b/packages/WallpaperBackup/test/src/com/android/wallpaperbackup/WallpaperBackupAgentTest.java index ec9223c7d667..3ecdf3f101a5 100644 --- a/packages/WallpaperBackup/test/src/com/android/wallpaperbackup/WallpaperBackupAgentTest.java +++ b/packages/WallpaperBackup/test/src/com/android/wallpaperbackup/WallpaperBackupAgentTest.java @@ -59,7 +59,6 @@ import android.content.Context; import android.content.Intent; import android.content.pm.PackageManager; import android.content.pm.ResolveInfo; -import android.graphics.Point; import android.graphics.Rect; import android.os.FileUtils; import android.os.ParcelFileDescriptor; @@ -841,26 +840,6 @@ public class WallpaperBackupAgentTest { testParseCropHints(testMap); } - @Test - public void test_sourceDimensionsAreLargerThanTarget() { - // source device is larger than target, expecting to get false - Point sourceDimensions = new Point(2208, 1840); - Point targetDimensions = new Point(1080, 2092); - boolean isSourceSmaller = mWallpaperBackupAgent - .isSourceDeviceSignificantlySmallerThanTarget(sourceDimensions, targetDimensions); - assertThat(isSourceSmaller).isEqualTo(false); - } - - @Test - public void test_sourceDimensionsMuchSmallerThanTarget() { - // source device is smaller than target, expecting to get true - Point sourceDimensions = new Point(1080, 2092); - Point targetDimensions = new Point(2208, 1840); - boolean isSourceSmaller = mWallpaperBackupAgent - .isSourceDeviceSignificantlySmallerThanTarget(sourceDimensions, targetDimensions); - assertThat(isSourceSmaller).isEqualTo(true); - } - private void testParseCropHints(Map<Integer, Rect> testMap) throws Exception { assumeTrue(multiCrop()); mockRestoredStaticWallpaperFile(testMap); diff --git a/services/accessibility/java/com/android/server/accessibility/gestures/TouchExplorer.java b/services/accessibility/java/com/android/server/accessibility/gestures/TouchExplorer.java index be2ad21ee020..d279bd553970 100644 --- a/services/accessibility/java/com/android/server/accessibility/gestures/TouchExplorer.java +++ b/services/accessibility/java/com/android/server/accessibility/gestures/TouchExplorer.java @@ -852,11 +852,9 @@ public class TouchExplorer extends BaseEventStreamTransformation final int pointerIdBits = (1 << pointerId); if (mSendHoverEnterAndMoveDelayed.isPending()) { // If we have not delivered the enter schedule an exit. - if (Flags.resetHoverEventTimerOnActionUp()) { - // We cancel first to reset the time window so that the user has the full amount of - // time to do a multi tap. - mSendHoverEnterAndMoveDelayed.repost(); - } + // We cancel first to reset the time window so that the user has the full amount of + // time to do a multi tap. + mSendHoverEnterAndMoveDelayed.repost(); mSendHoverExitDelayed.post(event, rawEvent, pointerIdBits, policyFlags); } else { // The user is touch exploring so we send events for end. @@ -1601,7 +1599,7 @@ public class TouchExplorer extends BaseEventStreamTransformation + " pointers down."); return; } - if (Flags.resetHoverEventTimerOnActionUp() && mEvents.size() == 0) { + if (mEvents.size() == 0) { return; } // Send an accessibility event to announce the touch exploration start. diff --git a/services/autofill/bugfixes.aconfig b/services/autofill/bugfixes.aconfig index ced10fbeff0c..70ecc055929c 100644 --- a/services/autofill/bugfixes.aconfig +++ b/services/autofill/bugfixes.aconfig @@ -41,3 +41,10 @@ flag { description: "Use weak reference to address binder leak problem" bug: "307972253" } + +flag { + name: "include_last_focused_id_and_session_id_in_client_state" + namespace: "autofill" + description: "Include the current view id and session id into the FillEventHistory as part of ClientState" + bug: "334141398" +} diff --git a/services/autofill/java/com/android/server/autofill/Session.java b/services/autofill/java/com/android/server/autofill/Session.java index 3f3ff4a46edf..3a384065217e 100644 --- a/services/autofill/java/com/android/server/autofill/Session.java +++ b/services/autofill/java/com/android/server/autofill/Session.java @@ -5188,11 +5188,13 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState String[] exception = resultData.getStringArray( CredentialProviderService.EXTRA_GET_CREDENTIAL_EXCEPTION); if (exception != null && exception.length >= 2) { + String errType = exception[0]; + String errMsg = exception[1]; Slog.w(TAG, "Credman bottom sheet from pinned " - + "entry failed with: + " + exception[0] + " , " - + exception[1]); + + "entry failed with: + " + errType + " , " + + errMsg); sendCredentialManagerResponseToApp(/*response=*/ null, - new GetCredentialException(exception[0], exception[1]), + new GetCredentialException(errType, errMsg), mAutofillId); } } else { diff --git a/services/companion/java/com/android/server/companion/devicepresence/DevicePresenceProcessor.java b/services/companion/java/com/android/server/companion/devicepresence/DevicePresenceProcessor.java index cfb7f337242b..af49df69a979 100644 --- a/services/companion/java/com/android/server/companion/devicepresence/DevicePresenceProcessor.java +++ b/services/companion/java/com/android/server/companion/devicepresence/DevicePresenceProcessor.java @@ -56,6 +56,7 @@ import android.util.SparseArray; import android.util.SparseBooleanArray; import com.android.internal.annotations.GuardedBy; +import com.android.internal.util.CollectionUtils; import com.android.server.companion.association.AssociationStore; import java.io.PrintWriter; @@ -1031,6 +1032,9 @@ public class DevicePresenceProcessor implements AssociationStore.OnChangeListene public void sendDevicePresenceEventOnUnlocked(int userId) { final List<DevicePresenceEvent> deviceEvents = getPendingDevicePresenceEventsByUserId( userId); + if (CollectionUtils.isEmpty(deviceEvents)) { + return; + } final List<ObservableUuid> observableUuids = mObservableUuidStore.getObservableUuidsForUser(userId); // Notify and bind the app after the phone is unlocked. @@ -1068,7 +1072,7 @@ public class DevicePresenceProcessor implements AssociationStore.OnChangeListene } } - clearPendingDevicePresenceEventsByUserId(userId); + removePendingDevicePresenceEventsByUserId(userId); } private List<DevicePresenceEvent> getPendingDevicePresenceEventsByUserId(int userId) { @@ -1077,9 +1081,11 @@ public class DevicePresenceProcessor implements AssociationStore.OnChangeListene } } - private void clearPendingDevicePresenceEventsByUserId(int userId) { + private void removePendingDevicePresenceEventsByUserId(int userId) { synchronized (mPendingDevicePresenceEvents) { - mPendingDevicePresenceEvents.get(userId).clear(); + if (mPendingDevicePresenceEvents.contains(userId)) { + mPendingDevicePresenceEvents.remove(userId); + } } } diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java index 5ec6b7200191..ee5d49b693ac 100644 --- a/services/core/java/com/android/server/am/ActivityManagerService.java +++ b/services/core/java/com/android/server/am/ActivityManagerService.java @@ -9189,6 +9189,11 @@ public class ActivityManagerService extends IActivityManager.Stub private class MyBinderProxyCountEventListener implements BinderProxyCountEventListener { @Override public void onLimitReached(int uid) { + // Spawn a new thread for the dump as it'll take long time. + new Thread(() -> handleLimitReached(uid), "BinderProxy Dump: " + uid).start(); + } + + private void handleLimitReached(int uid) { Slog.wtf(TAG, "Uid " + uid + " sent too many Binders to uid " + Process.myUid()); BinderProxy.dumpProxyDebugInfo(); diff --git a/services/core/java/com/android/server/display/DisplayDeviceConfig.java b/services/core/java/com/android/server/display/DisplayDeviceConfig.java index 1c169a055078..5c9318165358 100644 --- a/services/core/java/com/android/server/display/DisplayDeviceConfig.java +++ b/services/core/java/com/android/server/display/DisplayDeviceConfig.java @@ -1766,7 +1766,8 @@ public class DisplayDeviceConfig { loadDensityMapping(config); loadBrightnessDefaultFromDdcXml(config); loadBrightnessConstraintsFromConfigXml(); - if (mFlags.isEvenDimmerEnabled()) { + if (mFlags.isEvenDimmerEnabled() && mContext.getResources().getBoolean( + com.android.internal.R.bool.config_evenDimmerEnabled)) { mEvenDimmerBrightnessData = EvenDimmerBrightnessData.loadConfig(config); } loadBrightnessMap(config); diff --git a/services/core/java/com/android/server/display/DisplayManagerService.java b/services/core/java/com/android/server/display/DisplayManagerService.java index 8f1277bfe507..4e709a7fd95d 100644 --- a/services/core/java/com/android/server/display/DisplayManagerService.java +++ b/services/core/java/com/android/server/display/DisplayManagerService.java @@ -5271,5 +5271,13 @@ public final class DisplayManagerService extends SystemService { public ExternalDisplayStatsService getExternalDisplayStatsService() { return mExternalDisplayStatsService; } + + /** + * Called on external display is ready to be enabled. + */ + @Override + public void onExternalDisplayReadyToBeEnabled(int displayId) { + mDisplayModeDirector.onExternalDisplayReadyToBeEnabled(displayId); + } } } diff --git a/services/core/java/com/android/server/display/ExternalDisplayPolicy.java b/services/core/java/com/android/server/display/ExternalDisplayPolicy.java index b24caf4ced76..3c2918f833ba 100644 --- a/services/core/java/com/android/server/display/ExternalDisplayPolicy.java +++ b/services/core/java/com/android/server/display/ExternalDisplayPolicy.java @@ -91,6 +91,8 @@ class ExternalDisplayPolicy { @NonNull ExternalDisplayStatsService getExternalDisplayStatsService(); + + void onExternalDisplayReadyToBeEnabled(int displayId); } @NonNull @@ -185,6 +187,10 @@ class ExternalDisplayPolicy { return; } + if (enabled) { + mInjector.onExternalDisplayReadyToBeEnabled(logicalDisplay.getDisplayIdLocked()); + } + mLogicalDisplayMapper.setDisplayEnabledLocked(logicalDisplay, enabled); } @@ -217,6 +223,7 @@ class ExternalDisplayPolicy { if ((Build.IS_ENG || Build.IS_USERDEBUG) && SystemProperties.getBoolean(ENABLE_ON_CONNECT, false)) { Slog.w(TAG, "External display is enabled by default, bypassing user consent."); + mInjector.onExternalDisplayReadyToBeEnabled(logicalDisplay.getDisplayIdLocked()); mInjector.sendExternalDisplayEventLocked(logicalDisplay, EVENT_DISPLAY_CONNECTED); return; } else { diff --git a/services/core/java/com/android/server/display/mode/DisplayModeDirector.java b/services/core/java/com/android/server/display/mode/DisplayModeDirector.java index a862b6e8f8f4..9064763c423a 100644 --- a/services/core/java/com/android/server/display/mode/DisplayModeDirector.java +++ b/services/core/java/com/android/server/display/mode/DisplayModeDirector.java @@ -694,6 +694,13 @@ public class DisplayModeDirector { } /** + * Called when external display is ready to be enabled. + */ + public void onExternalDisplayReadyToBeEnabled(int displayId) { + mDisplayObserver.onExternalDisplayReadyToBeEnabled(displayId); + } + + /** * Listens for changes refresh rate coordination. */ public interface DesiredDisplayModeSpecsListener { @@ -1379,6 +1386,13 @@ public class DisplayModeDirector { } } + + void onExternalDisplayReadyToBeEnabled(int displayId) { + DisplayInfo displayInfo = getDisplayInfo(displayId); + updateDisplaysPeakRefreshRateAndResolution(displayInfo); + addDisplaysSynchronizedPeakRefreshRate(displayInfo); + } + @Override public void onDisplayAdded(int displayId) { updateVrrStatus(displayId); @@ -1386,8 +1400,6 @@ public class DisplayModeDirector { updateDisplayModes(displayId, displayInfo); updateLayoutLimitedFrameRate(displayId, displayInfo); updateUserSettingDisplayPreferredSize(displayInfo); - updateDisplaysPeakRefreshRateAndResolution(displayInfo); - addDisplaysSynchronizedPeakRefreshRate(displayInfo); } @Override diff --git a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java index 6b33199ec230..2583d73046ce 100644 --- a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java +++ b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java @@ -284,12 +284,11 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. final Resources mRes; private final Handler mHandler; - /** - * TODO(b/329163064): Remove this field. - */ - @NonNull @MultiUserUnawareField - private InputMethodSettings mSettings; + @UserIdInt + @GuardedBy("ImfLock.class") + private int mCurrentUserId; + @MultiUserUnawareField final SettingsObserver mSettingsObserver; final WindowManagerInternal mWindowManagerInternal; @@ -494,7 +493,7 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. @GuardedBy("ImfLock.class") @Nullable InputMethodInfo queryInputMethodForCurrentUserLocked(@NonNull String imeId) { - return mSettings.getMethodMap().get(imeId); + return InputMethodSettingsRepository.get(mCurrentUserId).getMethodMap().get(imeId); } /** @@ -815,7 +814,8 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. InputMethodManager.invalidateLocalStylusHandwritingAvailabilityCaches(); } else { boolean enabledChanged = false; - String newEnabled = mSettings.getEnabledInputMethodsStr(); + String newEnabled = InputMethodSettingsRepository.get(mCurrentUserId) + .getEnabledInputMethodsStr(); if (!mLastEnabled.equals(newEnabled)) { mLastEnabled = newEnabled; enabledChanged = true; @@ -847,9 +847,11 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. // sender userId can be a real user ID or USER_ALL. final int senderUserId = pendingResult.getSendingUserId(); if (senderUserId != UserHandle.USER_ALL) { - if (senderUserId != mSettings.getUserId()) { - // A background user is trying to hide the dialog. Ignore. - return; + synchronized (ImfLock.class) { + if (senderUserId != mCurrentUserId) { + // A background user is trying to hide the dialog. Ignore. + return; + } } } mMenuController.hideInputMethodMenu(); @@ -880,9 +882,6 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. AdditionalSubtypeMapRepository.get(userId), DirectBootAwareness.AUTO); InputMethodSettingsRepository.put(userId, settings); - if (userId == mSettings.getUserId()) { - mSettings = settings; - } } postInputMethodSettingUpdatedLocked(true /* resetDefaultEnabledIme */); // If the locale is changed, needs to reset the default ime @@ -944,7 +943,7 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. @GuardedBy("ImfLock.class") private boolean isChangingPackagesOfCurrentUserLocked() { final int userId = getChangingUserId(); - final boolean retval = userId == mSettings.getUserId(); + final boolean retval = userId == mCurrentUserId; if (DEBUG) { if (!retval) { Slog.d(TAG, "--- ignore this call back from a background user: " + userId); @@ -959,8 +958,10 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. if (!isChangingPackagesOfCurrentUserLocked()) { return false; } - String curInputMethodId = mSettings.getSelectedInputMethod(); - final List<InputMethodInfo> methodList = mSettings.getMethodList(); + final InputMethodSettings settings = + InputMethodSettingsRepository.get(mCurrentUserId); + String curInputMethodId = settings.getSelectedInputMethod(); + final List<InputMethodInfo> methodList = settings.getMethodList(); final int numImes = methodList.size(); if (curInputMethodId != null) { for (int i = 0; i < numImes; i++) { @@ -1077,7 +1078,7 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. private void onFinishPackageChangesInternal() { synchronized (ImfLock.class) { final int userId = getChangingUserId(); - final boolean isCurrentUser = (userId == mSettings.getUserId()); + final boolean isCurrentUser = (userId == mCurrentUserId); final AdditionalSubtypeMap additionalSubtypeMap = AdditionalSubtypeMapRepository.get(userId); final InputMethodSettings settings = InputMethodSettingsRepository.get(userId); @@ -1135,8 +1136,6 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. if (!isCurrentUser) { return; } - mSettings = queryInputMethodServicesInternal(mContext, userId, - newAdditionalSubtypeMap, DirectBootAwareness.AUTO); postInputMethodSettingUpdatedLocked(false /* resetDefaultEnabledIme */); boolean changed = false; @@ -1291,8 +1290,7 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. void onUnlockUser(@UserIdInt int userId) { synchronized (ImfLock.class) { if (DEBUG) { - Slog.d(TAG, "onUnlockUser: userId=" + userId + " curUserId=" - + mSettings.getUserId()); + Slog.d(TAG, "onUnlockUser: userId=" + userId + " curUserId=" + mCurrentUserId); } if (!mSystemReady) { return; @@ -1300,8 +1298,7 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. final InputMethodSettings newSettings = queryInputMethodServicesInternal(mContext, userId, AdditionalSubtypeMapRepository.get(userId), DirectBootAwareness.AUTO); InputMethodSettingsRepository.put(userId, newSettings); - if (mSettings.getUserId() == userId) { - mSettings = newSettings; + if (mCurrentUserId == userId) { // We need to rebuild IMEs. postInputMethodSettingUpdatedLocked(false /* resetDefaultEnabledIme */); updateInputMethodsFromSettingsLocked(true /* enabledChanged */); @@ -1375,16 +1372,16 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. InputMethodSettingsRepository.initialize(mHandler, mContext); AdditionalSubtypeMapRepository.initialize(mHandler, mContext); - final int userId = mActivityManagerInternal.getCurrentUserId(); + mCurrentUserId = mActivityManagerInternal.getCurrentUserId(); - mSettings = InputMethodSettingsRepository.get(userId); + final InputMethodSettings settings = InputMethodSettingsRepository.get(mCurrentUserId); mSwitchingController = InputMethodSubtypeSwitchingController.createInstanceLocked(context, - mSettings.getMethodMap(), userId); + settings.getMethodMap(), settings.getUserId()); mHardwareKeyboardShortcutController = - new HardwareKeyboardShortcutController(mSettings.getMethodMap(), - mSettings.getUserId()); + new HardwareKeyboardShortcutController(settings.getMethodMap(), + settings.getUserId()); mMenuController = new InputMethodMenuController(this); mBindingController = bindingControllerForTesting != null @@ -1416,7 +1413,7 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. @GuardedBy("ImfLock.class") @UserIdInt int getCurrentImeUserIdLocked() { - return mSettings.getUserId(); + return mCurrentUserId; } private final class InkWindowInitializer implements Runnable { @@ -1452,12 +1449,13 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. private void resetDefaultImeLocked(Context context) { // Do not reset the default (current) IME when it is a 3rd-party IME String selectedMethodId = getSelectedMethodIdLocked(); + final InputMethodSettings settings = InputMethodSettingsRepository.get(mCurrentUserId); if (selectedMethodId != null - && !mSettings.getMethodMap().get(selectedMethodId).isSystem()) { + && !settings.getMethodMap().get(selectedMethodId).isSystem()) { return; } final List<InputMethodInfo> suitableImes = InputMethodInfoUtils.getDefaultEnabledImes( - context, mSettings.getEnabledInputMethodList()); + context, settings.getEnabledInputMethodList()); if (suitableImes.isEmpty()) { Slog.i(TAG, "No default found"); return; @@ -1513,7 +1511,7 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. IInputMethodClientInvoker clientToBeReset) { if (DEBUG) { Slog.d(TAG, "Switching user stage 1/3. newUserId=" + newUserId - + " currentUserId=" + mSettings.getUserId()); + + " currentUserId=" + mCurrentUserId); } maybeInitImeNavbarConfigLocked(newUserId); @@ -1521,8 +1519,9 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. // ContentObserver should be registered again when the user is changed mSettingsObserver.registerContentObserverLocked(newUserId); - mSettings = InputMethodSettings.createEmptyMap(newUserId); - final String defaultImiId = mSettings.getSelectedInputMethod(); + mCurrentUserId = newUserId; + final String defaultImiId = SecureSettingsWrapper.getString( + Settings.Secure.DEFAULT_INPUT_METHOD, null, newUserId); if (DEBUG) { Slog.d(TAG, "Switching user stage 2/3. newUserId=" + newUserId @@ -1540,12 +1539,9 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. // and user switch would not happen at that time. resetCurrentMethodAndClientLocked(UnbindReason.SWITCH_USER); - final InputMethodSettings newSettings = queryInputMethodServicesInternal(mContext, - newUserId, AdditionalSubtypeMapRepository.get(newUserId), DirectBootAwareness.AUTO); - InputMethodSettingsRepository.put(newUserId, newSettings); - mSettings = newSettings; + final InputMethodSettings newSettings = InputMethodSettingsRepository.get(newUserId); postInputMethodSettingUpdatedLocked(initialUserSwitch /* resetDefaultEnabledIme */); - if (TextUtils.isEmpty(mSettings.getSelectedInputMethod())) { + if (TextUtils.isEmpty(newSettings.getSelectedInputMethod())) { // This is the first time of the user switch and // set the current ime to the proper one. resetDefaultImeLocked(mContext); @@ -1555,12 +1551,12 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. if (initialUserSwitch) { InputMethodUtils.setNonSelectedSystemImesDisabledUntilUsed( getPackageManagerForUser(mContext, newUserId), - mSettings.getEnabledInputMethodList()); + newSettings.getEnabledInputMethodList()); } if (DEBUG) { Slog.d(TAG, "Switching user stage 3/3. newUserId=" + newUserId - + " selectedIme=" + mSettings.getSelectedInputMethod()); + + " selectedIme=" + newSettings.getSelectedInputMethod()); } if (mIsInteractive && clientToBeReset != null) { @@ -1583,7 +1579,7 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. } if (!mSystemReady) { mSystemReady = true; - final int currentUserId = mSettings.getUserId(); + final int currentUserId = mCurrentUserId; mStatusBarManagerInternal = LocalServices.getService(StatusBarManagerInternal.class); hideStatusBarIconLocked(); @@ -1604,7 +1600,7 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. // the "mImeDrawsImeNavBarResLazyInitFuture" field. synchronized (ImfLock.class) { mImeDrawsImeNavBarResLazyInitFuture = null; - if (currentUserId != mSettings.getUserId()) { + if (currentUserId != mCurrentUserId) { // This means that the current user is already switched to other user // before the background task is executed. In this scenario the relevant // field should already be initialized. @@ -1623,19 +1619,19 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. UserHandle.ALL, broadcastFilterForAllUsers, null, null, Context.RECEIVER_EXPORTED); - final String defaultImiId = mSettings.getSelectedInputMethod(); + final String defaultImiId = SecureSettingsWrapper.getString( + Settings.Secure.DEFAULT_INPUT_METHOD, null, currentUserId); final boolean imeSelectedOnBoot = !TextUtils.isEmpty(defaultImiId); final InputMethodSettings newSettings = queryInputMethodServicesInternal(mContext, currentUserId, AdditionalSubtypeMapRepository.get(currentUserId), DirectBootAwareness.AUTO); InputMethodSettingsRepository.put(currentUserId, newSettings); - mSettings = newSettings; postInputMethodSettingUpdatedLocked( !imeSelectedOnBoot /* resetDefaultEnabledIme */); updateFromSettingsLocked(true); InputMethodUtils.setNonSelectedSystemImesDisabledUntilUsed( getPackageManagerForUser(mContext, currentUserId), - mSettings.getEnabledInputMethodList()); + newSettings.getEnabledInputMethodList()); } } } @@ -1682,7 +1678,7 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. } synchronized (ImfLock.class) { final int[] resolvedUserIds = InputMethodUtils.resolveUserId(userId, - mSettings.getUserId(), null); + mCurrentUserId, null); if (resolvedUserIds.length != 1) { return Collections.emptyList(); } @@ -1705,7 +1701,7 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. } synchronized (ImfLock.class) { final int[] resolvedUserIds = InputMethodUtils.resolveUserId(userId, - mSettings.getUserId(), null); + mCurrentUserId, null); if (resolvedUserIds.length != 1) { return Collections.emptyList(); } @@ -1733,7 +1729,7 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. } // Check if selected IME of current user supports handwriting. - if (userId == mSettings.getUserId()) { + if (userId == mCurrentUserId) { return mBindingController.supportsStylusHandwriting() && (!connectionless || mBindingController.supportsConnectionlessStylusHandwriting()); @@ -1781,15 +1777,8 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. @GuardedBy("ImfLock.class") private List<InputMethodInfo> getEnabledInputMethodListLocked(@UserIdInt int userId, int callingUid) { - final ArrayList<InputMethodInfo> methodList; - final InputMethodSettings settings; - if (userId == mSettings.getUserId()) { - methodList = mSettings.getEnabledInputMethodList(); - settings = mSettings; - } else { - settings = InputMethodSettingsRepository.get(userId); - methodList = settings.getEnabledInputMethodList(); - } + final InputMethodSettings settings = InputMethodSettingsRepository.get(userId); + final ArrayList<InputMethodInfo> methodList = settings.getEnabledInputMethodList(); // filter caller's access to input methods methodList.removeIf(imi -> !canCallerAccessInputMethod(imi.getPackageName(), callingUid, userId, settings)); @@ -2028,7 +2017,7 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. final boolean restarting = !initial; final Binder startInputToken = new Binder(); - final StartInputInfo info = new StartInputInfo(mSettings.getUserId(), + final StartInputInfo info = new StartInputInfo(mCurrentUserId, getCurTokenLocked(), mCurTokenDisplayId, getCurIdLocked(), startInputReason, restarting, UserHandle.getUserId(mCurClient.mUid), @@ -2042,9 +2031,9 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. // same-user scenarios. // That said ignoring cross-user scenario will never affect IMEs that do not have // INTERACT_ACROSS_USERS(_FULL) permissions, which is actually almost always the case. - if (mSettings.getUserId() == UserHandle.getUserId( + if (mCurrentUserId == UserHandle.getUserId( mCurClient.mUid)) { - mPackageManagerInternal.grantImplicitAccess(mSettings.getUserId(), + mPackageManagerInternal.grantImplicitAccess(mCurrentUserId, null /* intent */, UserHandle.getAppId(getCurMethodUidLocked()), mCurClient.mUid, true /* direct */); } @@ -2067,7 +2056,8 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. } String curId = getCurIdLocked(); - final InputMethodInfo curInputMethodInfo = mSettings.getMethodMap().get(curId); + final InputMethodInfo curInputMethodInfo = InputMethodSettingsRepository.get(mCurrentUserId) + .getMethodMap().get(curId); final boolean suppressesSpellChecker = curInputMethodInfo != null && curInputMethodInfo.suppressesSpellChecker(); final SparseArray<IAccessibilityInputMethodSession> accessibilityInputMethodSessions = @@ -2255,17 +2245,18 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. return currentMethodId; } + final InputMethodSettings settings = InputMethodSettingsRepository.get(mCurrentUserId); final int oldDeviceId = mDeviceIdToShowIme; mDeviceIdToShowIme = mVdmInternal.getDeviceIdForDisplayId(mDisplayIdToShowIme); if (mDeviceIdToShowIme == DEVICE_ID_DEFAULT) { if (oldDeviceId == DEVICE_ID_DEFAULT) { return currentMethodId; } - final String defaultDeviceMethodId = mSettings.getSelectedDefaultDeviceInputMethod(); + final String defaultDeviceMethodId = settings.getSelectedDefaultDeviceInputMethod(); if (DEBUG) { Slog.v(TAG, "Restoring default device input method: " + defaultDeviceMethodId); } - mSettings.putSelectedDefaultDeviceInputMethod(null); + settings.putSelectedDefaultDeviceInputMethod(null); return defaultDeviceMethodId; } @@ -2273,7 +2264,7 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. mVirtualDeviceMethodMap.get(mDeviceIdToShowIme, currentMethodId); if (Objects.equals(deviceMethodId, currentMethodId)) { return currentMethodId; - } else if (!mSettings.getMethodMap().containsKey(deviceMethodId)) { + } else if (!settings.getMethodMap().containsKey(deviceMethodId)) { if (DEBUG) { Slog.v(TAG, "Disabling IME on virtual device with id " + mDeviceIdToShowIme + " because its custom input method is not available: " + deviceMethodId); @@ -2285,7 +2276,7 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. if (DEBUG) { Slog.v(TAG, "Storing default device input method " + currentMethodId); } - mSettings.putSelectedDefaultDeviceInputMethod(currentMethodId); + settings.putSelectedDefaultDeviceInputMethod(currentMethodId); } if (DEBUG) { Slog.v(TAG, "Switching current input method from " + currentMethodId @@ -2315,7 +2306,8 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. if (isSoftInputModeStateVisibleAllowed(unverifiedTargetSdkVersion, startInputFlags)) { return false; } - final InputMethodInfo imi = mSettings.getMethodMap().get(selectedMethodId); + final InputMethodInfo imi = InputMethodSettingsRepository.get(mCurrentUserId) + .getMethodMap().get(selectedMethodId); if (imi == null) { return false; } @@ -2659,7 +2651,7 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. } else if (packageName != null) { if (DEBUG) Slog.d(TAG, "show a small icon for the input method"); final PackageManager userAwarePackageManager = - getPackageManagerForUser(mContext, mSettings.getUserId()); + getPackageManagerForUser(mContext, mCurrentUserId); ApplicationInfo applicationInfo = null; try { applicationInfo = userAwarePackageManager.getApplicationInfo(packageName, @@ -2721,7 +2713,7 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. return false; } if (mWindowManagerInternal.isKeyguardShowingAndNotOccluded() - && mWindowManagerInternal.isKeyguardSecure(mSettings.getUserId())) { + && mWindowManagerInternal.isKeyguardSecure(mCurrentUserId)) { return false; } if ((visibility & InputMethodService.IME_ACTIVE) == 0 @@ -2738,7 +2730,8 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. return false; } - List<InputMethodInfo> imes = mSettings.getEnabledInputMethodListWithFilter( + final InputMethodSettings settings = InputMethodSettingsRepository.get(mCurrentUserId); + List<InputMethodInfo> imes = settings.getEnabledInputMethodListWithFilter( InputMethodInfo::shouldShowInInputMethodPicker); final int numImes = imes.size(); if (numImes > 2) return true; @@ -2750,7 +2743,7 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. for (int i = 0; i < numImes; ++i) { final InputMethodInfo imi = imes.get(i); final List<InputMethodSubtype> subtypes = - mSettings.getEnabledInputMethodSubtypeList(imi, true); + settings.getEnabledInputMethodSubtypeList(imi, true); final int subtypeCount = subtypes.size(); if (subtypeCount == 0) { ++nonAuxCount; @@ -2902,11 +2895,12 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. @GuardedBy("ImfLock.class") void updateInputMethodsFromSettingsLocked(boolean enabledMayChange) { + final InputMethodSettings settings = InputMethodSettingsRepository.get(mCurrentUserId); if (enabledMayChange) { final PackageManager userAwarePackageManager = getPackageManagerForUser(mContext, - mSettings.getUserId()); + settings.getUserId()); - List<InputMethodInfo> enabled = mSettings.getEnabledInputMethodList(); + List<InputMethodInfo> enabled = settings.getEnabledInputMethodList(); for (int i = 0; i < enabled.size(); i++) { // We allow the user to select "disabled until used" apps, so if they // are enabling one of those here we now need to make it enabled. @@ -2933,20 +2927,20 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. if (mDeviceIdToShowIme == DEVICE_ID_DEFAULT) { String ime = SecureSettingsWrapper.getString( - Settings.Secure.DEFAULT_INPUT_METHOD, null, mSettings.getUserId()); + Settings.Secure.DEFAULT_INPUT_METHOD, null, settings.getUserId()); String defaultDeviceIme = SecureSettingsWrapper.getString( - Settings.Secure.DEFAULT_DEVICE_INPUT_METHOD, null, mSettings.getUserId()); + Settings.Secure.DEFAULT_DEVICE_INPUT_METHOD, null, settings.getUserId()); if (defaultDeviceIme != null && !Objects.equals(ime, defaultDeviceIme)) { if (DEBUG) { Slog.v(TAG, "Current input method " + ime + " differs from the stored default" - + " device input method for user " + mSettings.getUserId() + + " device input method for user " + settings.getUserId() + " - restoring " + defaultDeviceIme); } SecureSettingsWrapper.putString( Settings.Secure.DEFAULT_INPUT_METHOD, defaultDeviceIme, - mSettings.getUserId()); + settings.getUserId()); SecureSettingsWrapper.putString( - Settings.Secure.DEFAULT_DEVICE_INPUT_METHOD, null, mSettings.getUserId()); + Settings.Secure.DEFAULT_DEVICE_INPUT_METHOD, null, settings.getUserId()); } } @@ -2954,14 +2948,14 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. // ENABLED_INPUT_METHODS is taking care of keeping them correctly in // sync, so we will never have a DEFAULT_INPUT_METHOD that is not // enabled. - String id = mSettings.getSelectedInputMethod(); + String id = settings.getSelectedInputMethod(); // There is no input method selected, try to choose new applicable input method. if (TextUtils.isEmpty(id) && chooseNewDefaultIMELocked()) { - id = mSettings.getSelectedInputMethod(); + id = settings.getSelectedInputMethod(); } if (!TextUtils.isEmpty(id)) { try { - setInputMethodLocked(id, mSettings.getSelectedInputMethodSubtypeId(id)); + setInputMethodLocked(id, settings.getSelectedInputMethodSubtypeId(id)); } catch (IllegalArgumentException e) { Slog.w(TAG, "Unknown input method from prefs: " + id, e); resetCurrentMethodAndClientLocked(UnbindReason.SWITCH_IME_FAILED); @@ -2972,18 +2966,18 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. } // TODO: Instantiate mSwitchingController for each user. - if (mSettings.getUserId() == mSwitchingController.getUserId()) { - mSwitchingController.resetCircularListLocked(mSettings.getMethodMap()); + if (settings.getUserId() == mSwitchingController.getUserId()) { + mSwitchingController.resetCircularListLocked(settings.getMethodMap()); } else { mSwitchingController = InputMethodSubtypeSwitchingController.createInstanceLocked( - mContext, mSettings.getMethodMap(), mSettings.getUserId()); + mContext, settings.getMethodMap(), settings.getUserId()); } // TODO: Instantiate mHardwareKeyboardShortcutController for each user. - if (mSettings.getUserId() == mHardwareKeyboardShortcutController.getUserId()) { - mHardwareKeyboardShortcutController.reset(mSettings.getMethodMap()); + if (settings.getUserId() == mHardwareKeyboardShortcutController.getUserId()) { + mHardwareKeyboardShortcutController.reset(settings.getMethodMap()); } else { mHardwareKeyboardShortcutController = new HardwareKeyboardShortcutController( - mSettings.getMethodMap(), mSettings.getUserId()); + settings.getMethodMap(), settings.getUserId()); } sendOnNavButtonFlagsChangedLocked(); } @@ -3007,14 +3001,15 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. @GuardedBy("ImfLock.class") void setInputMethodLocked(String id, int subtypeId, int deviceId) { - InputMethodInfo info = mSettings.getMethodMap().get(id); + final InputMethodSettings settings = InputMethodSettingsRepository.get(mCurrentUserId); + InputMethodInfo info = settings.getMethodMap().get(id); if (info == null) { throw getExceptionForUnknownImeId(id); } // See if we need to notify a subtype change within the same IME. if (id.equals(getSelectedMethodIdLocked())) { - final int userId = mSettings.getUserId(); + final int userId = settings.getUserId(); final int subtypeCount = info.getSubtypeCount(); if (subtypeCount <= 0) { notifyInputMethodSubtypeChangedLocked(userId, info, null); @@ -3055,7 +3050,7 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. // method is a custom one specific to a virtual device. So only update the settings // entry used to restore the default device input method once we want to show the IME // back on the default device. - mSettings.putSelectedDefaultDeviceInputMethod(id); + settings.putSelectedDefaultDeviceInputMethod(id); return; } IInputMethodInvoker curMethod = getCurMethodLocked(); @@ -3583,7 +3578,7 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. return InputBindResult.USER_SWITCHING; } final int[] profileIdsWithDisabled = mUserManagerInternal.getProfileIds( - mSettings.getUserId(), false /* enabledOnly */); + mCurrentUserId, false /* enabledOnly */); for (int profileId : profileIdsWithDisabled) { if (profileId == userId) { scheduleSwitchUserTaskLocked(userId, cs.mClient); @@ -3629,10 +3624,10 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. } // Verify if caller is a background user. - final int currentUserId = mSettings.getUserId(); - if (userId != currentUserId) { + if (userId != mCurrentUserId) { if (ArrayUtils.contains( - mUserManagerInternal.getProfileIds(currentUserId, false), userId)) { + mUserManagerInternal.getProfileIds(mCurrentUserId, false), + userId)) { // cross-profile access is always allowed here to allow // profile-switching. scheduleSwitchUserTaskLocked(userId, cs.mClient); @@ -3821,7 +3816,7 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. && mImeBindingState.mFocusedWindowClient.mClient.asBinder() == client.asBinder()) { return true; } - if (mSettings.getUserId() != UserHandle.getUserId(uid)) { + if (mCurrentUserId != UserHandle.getUserId(uid)) { return false; } if (getCurIntentLocked() != null && InputMethodUtils.checkIfPackageBelongsToUid( @@ -3885,9 +3880,10 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. if (!calledWithValidTokenLocked(token)) { return; } - final InputMethodInfo imi = mSettings.getMethodMap().get(id); + final InputMethodSettings settings = InputMethodSettingsRepository.get(mCurrentUserId); + final InputMethodInfo imi = settings.getMethodMap().get(id); if (imi == null || !canCallerAccessInputMethod( - imi.getPackageName(), callingUid, userId, mSettings)) { + imi.getPackageName(), callingUid, userId, settings)) { throw getExceptionForUnknownImeId(id); } setInputMethodWithSubtypeIdLocked(token, id, NOT_A_SUBTYPE_ID); @@ -3903,9 +3899,10 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. if (!calledWithValidTokenLocked(token)) { return; } - final InputMethodInfo imi = mSettings.getMethodMap().get(id); + final InputMethodSettings settings = InputMethodSettingsRepository.get(mCurrentUserId); + final InputMethodInfo imi = settings.getMethodMap().get(id); if (imi == null || !canCallerAccessInputMethod( - imi.getPackageName(), callingUid, userId, mSettings)) { + imi.getPackageName(), callingUid, userId, settings)) { throw getExceptionForUnknownImeId(id); } if (subtype != null) { @@ -3923,10 +3920,11 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. if (!calledWithValidTokenLocked(token)) { return false; } - final Pair<String, String> lastIme = mSettings.getLastInputMethodAndSubtype(); + final InputMethodSettings settings = InputMethodSettingsRepository.get(mCurrentUserId); + final Pair<String, String> lastIme = settings.getLastInputMethodAndSubtype(); final InputMethodInfo lastImi; if (lastIme != null) { - lastImi = mSettings.getMethodMap().get(lastIme.first); + lastImi = settings.getMethodMap().get(lastIme.first); } else { lastImi = null; } @@ -3950,7 +3948,7 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. // This is a safety net. If the currentSubtype can't be added to the history // and the framework couldn't find the last ime, we will make the last ime be // the most applicable enabled keyboard subtype of the system imes. - final List<InputMethodInfo> enabled = mSettings.getEnabledInputMethodList(); + final List<InputMethodInfo> enabled = settings.getEnabledInputMethodList(); if (enabled != null) { final int enabledCount = enabled.size(); final String locale; @@ -3958,7 +3956,7 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. && !TextUtils.isEmpty(mCurrentSubtype.getLocale())) { locale = mCurrentSubtype.getLocale(); } else { - locale = SystemLocaleWrapper.get(mSettings.getUserId()).get(0).toString(); + locale = SystemLocaleWrapper.get(mCurrentUserId).get(0).toString(); } for (int i = 0; i < enabledCount; ++i) { final InputMethodInfo imi = enabled.get(i); @@ -4005,8 +4003,9 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. @GuardedBy("ImfLock.class") private boolean switchToNextInputMethodLocked(@Nullable IBinder token, boolean onlyCurrentIme) { + final InputMethodSettings settings = InputMethodSettingsRepository.get(mCurrentUserId); final ImeSubtypeListItem nextSubtype = mSwitchingController.getNextInputMethodLocked( - onlyCurrentIme, mSettings.getMethodMap().get(getSelectedMethodIdLocked()), + onlyCurrentIme, settings.getMethodMap().get(getSelectedMethodIdLocked()), mCurrentSubtype); if (nextSubtype == null) { return false; @@ -4022,9 +4021,10 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. if (!calledWithValidTokenLocked(token)) { return false; } + final InputMethodSettings settings = InputMethodSettingsRepository.get(mCurrentUserId); final ImeSubtypeListItem nextSubtype = mSwitchingController.getNextInputMethodLocked( false /* onlyCurrentIme */, - mSettings.getMethodMap().get(getSelectedMethodIdLocked()), mCurrentSubtype); + settings.getMethodMap().get(getSelectedMethodIdLocked()), mCurrentSubtype); return nextSubtype != null; } } @@ -4036,10 +4036,6 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. Manifest.permission.INTERACT_ACROSS_USERS_FULL, null); } synchronized (ImfLock.class) { - if (mSettings.getUserId() == userId) { - return mSettings.getLastInputMethodSubtype(); - } - return InputMethodSettingsRepository.get(userId).getLastInputMethodSubtype(); } } @@ -4071,7 +4067,7 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. } final var additionalSubtypeMap = AdditionalSubtypeMapRepository.get(userId); - final boolean isCurrentUser = (mSettings.getUserId() == userId); + final boolean isCurrentUser = (mCurrentUserId == userId); final InputMethodSettings settings = InputMethodSettingsRepository.get(userId); final var newAdditionalSubtypeMap = settings.getNewAdditionalSubtypeMap( imiId, toBeAdded, additionalSubtypeMap, mPackageManagerInternal, callingUid); @@ -4085,7 +4081,6 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. if (isCurrentUser) { final long ident = Binder.clearCallingIdentity(); try { - mSettings = newSettings; postInputMethodSettingUpdatedLocked(false /* resetDefaultEnabledIme */); } finally { Binder.restoreCallingIdentity(ident); @@ -4115,7 +4110,7 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. final long ident = Binder.clearCallingIdentity(); try { synchronized (ImfLock.class) { - final boolean currentUser = (mSettings.getUserId() == userId); + final boolean currentUser = (mCurrentUserId == userId); final InputMethodSettings settings = InputMethodSettingsRepository.get(userId); if (!settings.setEnabledInputMethodSubtypes(imeId, subtypeHashCodes)) { return; @@ -4458,11 +4453,11 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. } return; } - if (mSettings.getUserId() != mSwitchingController.getUserId()) { + if (mCurrentUserId != mSwitchingController.getUserId()) { return; } - final InputMethodInfo imi = - mSettings.getMethodMap().get(getSelectedMethodIdLocked()); + final InputMethodInfo imi = InputMethodSettingsRepository.get(mCurrentUserId) + .getMethodMap().get(getSelectedMethodIdLocked()); if (imi != null) { mSwitchingController.onUserActionLocked(imi, mCurrentSubtype); } @@ -4522,8 +4517,9 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. return; } else { // Called with current IME's token. - if (mSettings.getMethodMap().get(id) != null - && mSettings.getEnabledInputMethodListWithFilter( + final InputMethodSettings settings = InputMethodSettingsRepository.get(mCurrentUserId); + if (settings.getMethodMap().get(id) != null + && settings.getEnabledInputMethodListWithFilter( (info) -> info.getId().equals(id)).isEmpty()) { throw new IllegalStateException("Requested IME is not enabled: " + id); } @@ -4702,21 +4698,23 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. return false; } synchronized (ImfLock.class) { + final InputMethodSettings settings = + InputMethodSettingsRepository.get(mCurrentUserId); final boolean isScreenLocked = mWindowManagerInternal.isKeyguardLocked() - && mWindowManagerInternal.isKeyguardSecure(mSettings.getUserId()); - final String lastInputMethodId = mSettings.getSelectedInputMethod(); + && mWindowManagerInternal.isKeyguardSecure(settings.getUserId()); + final String lastInputMethodId = settings.getSelectedInputMethod(); int lastInputMethodSubtypeId = - mSettings.getSelectedInputMethodSubtypeId(lastInputMethodId); + settings.getSelectedInputMethodSubtypeId(lastInputMethodId); final List<ImeSubtypeListItem> imList = InputMethodSubtypeSwitchingController .getSortedInputMethodAndSubtypeList( showAuxSubtypes, isScreenLocked, true /* forImeMenu */, - mContext, mSettings.getMethodMap(), mSettings.getUserId()); + mContext, settings.getMethodMap(), settings.getUserId()); if (imList.isEmpty()) { Slog.w(TAG, "Show switching menu failed, imList is empty," + " showAuxSubtypes: " + showAuxSubtypes + " isScreenLocked: " + isScreenLocked - + " userId: " + mSettings.getUserId()); + + " userId: " + settings.getUserId()); return false; } @@ -4902,8 +4900,9 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. @GuardedBy("ImfLock.class") private boolean chooseNewDefaultIMELocked() { + final InputMethodSettings settings = InputMethodSettingsRepository.get(mCurrentUserId); final InputMethodInfo imi = InputMethodInfoUtils.getMostApplicableDefaultIME( - mSettings.getEnabledInputMethodList()); + settings.getEnabledInputMethodList()); if (imi != null) { if (DEBUG) { Slog.d(TAG, "New default IME was selected: " + imi.getId()); @@ -5017,6 +5016,8 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. mMethodMapUpdateCount++; mMyPackageMonitor.clearKnownImePackageNamesLocked(); + final InputMethodSettings settings = InputMethodSettingsRepository.get(mCurrentUserId); + // Construct the set of possible IME packages for onPackageChanged() to avoid false // negatives when the package state remains to be the same but only the component state is // changed. @@ -5027,7 +5028,7 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. final List<ResolveInfo> allInputMethodServices = mContext.getPackageManager().queryIntentServicesAsUser( new Intent(InputMethod.SERVICE_INTERFACE), - PackageManager.MATCH_DISABLED_COMPONENTS, mSettings.getUserId()); + PackageManager.MATCH_DISABLED_COMPONENTS, settings.getUserId()); final int numImes = allInputMethodServices.size(); for (int i = 0; i < numImes; ++i) { final ServiceInfo si = allInputMethodServices.get(i).serviceInfo; @@ -5042,11 +5043,11 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. if (!resetDefaultEnabledIme) { boolean enabledImeFound = false; boolean enabledNonAuxImeFound = false; - final List<InputMethodInfo> enabledImes = mSettings.getEnabledInputMethodList(); + final List<InputMethodInfo> enabledImes = settings.getEnabledInputMethodList(); final int numImes = enabledImes.size(); for (int i = 0; i < numImes; ++i) { final InputMethodInfo imi = enabledImes.get(i); - if (mSettings.getMethodMap().containsKey(imi.getId())) { + if (settings.getMethodMap().containsKey(imi.getId())) { enabledImeFound = true; if (!imi.isAuxiliaryIme()) { enabledNonAuxImeFound = true; @@ -5070,7 +5071,7 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. if (resetDefaultEnabledIme || reenableMinimumNonAuxSystemImes) { final ArrayList<InputMethodInfo> defaultEnabledIme = - InputMethodInfoUtils.getDefaultEnabledImes(mContext, mSettings.getMethodList(), + InputMethodInfoUtils.getDefaultEnabledImes(mContext, settings.getMethodList(), reenableMinimumNonAuxSystemImes); final int numImes = defaultEnabledIme.size(); for (int i = 0; i < numImes; ++i) { @@ -5082,9 +5083,9 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. } } - final String defaultImiId = mSettings.getSelectedInputMethod(); + final String defaultImiId = settings.getSelectedInputMethod(); if (!TextUtils.isEmpty(defaultImiId)) { - if (!mSettings.getMethodMap().containsKey(defaultImiId)) { + if (!settings.getMethodMap().containsKey(defaultImiId)) { Slog.w(TAG, "Default IME is uninstalled. Choose new default IME."); if (chooseNewDefaultIMELocked()) { updateInputMethodsFromSettingsLocked(true); @@ -5098,26 +5099,26 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. updateDefaultVoiceImeIfNeededLocked(); // TODO: Instantiate mSwitchingController for each user. - if (mSettings.getUserId() == mSwitchingController.getUserId()) { - mSwitchingController.resetCircularListLocked(mSettings.getMethodMap()); + if (settings.getUserId() == mSwitchingController.getUserId()) { + mSwitchingController.resetCircularListLocked(settings.getMethodMap()); } else { mSwitchingController = InputMethodSubtypeSwitchingController.createInstanceLocked( - mContext, mSettings.getMethodMap(), mSettings.getUserId()); + mContext, settings.getMethodMap(), mCurrentUserId); } // TODO: Instantiate mHardwareKeyboardShortcutController for each user. - if (mSettings.getUserId() == mHardwareKeyboardShortcutController.getUserId()) { - mHardwareKeyboardShortcutController.reset(mSettings.getMethodMap()); + if (settings.getUserId() == mHardwareKeyboardShortcutController.getUserId()) { + mHardwareKeyboardShortcutController.reset(settings.getMethodMap()); } else { mHardwareKeyboardShortcutController = new HardwareKeyboardShortcutController( - mSettings.getMethodMap(), mSettings.getUserId()); + settings.getMethodMap(), settings.getUserId()); } sendOnNavButtonFlagsChangedLocked(); // Notify InputMethodListListeners of the new installed InputMethods. - final List<InputMethodInfo> inputMethodList = mSettings.getMethodList(); + final List<InputMethodInfo> inputMethodList = settings.getMethodList(); mHandler.obtainMessage(MSG_DISPATCH_ON_INPUT_METHOD_LIST_UPDATED, - mSettings.getUserId(), 0 /* unused */, inputMethodList).sendToTarget(); + settings.getUserId(), 0 /* unused */, inputMethodList).sendToTarget(); } @GuardedBy("ImfLock.class") @@ -5132,11 +5133,12 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. @GuardedBy("ImfLock.class") private void updateDefaultVoiceImeIfNeededLocked() { + final InputMethodSettings settings = InputMethodSettingsRepository.get(mCurrentUserId); final String systemSpeechRecognizer = mContext.getString(com.android.internal.R.string.config_systemSpeechRecognizer); - final String currentDefaultVoiceImeId = mSettings.getDefaultVoiceInputMethod(); + final String currentDefaultVoiceImeId = settings.getDefaultVoiceInputMethod(); final InputMethodInfo newSystemVoiceIme = InputMethodInfoUtils.chooseSystemVoiceIme( - mSettings.getMethodMap(), systemSpeechRecognizer, currentDefaultVoiceImeId); + settings.getMethodMap(), systemSpeechRecognizer, currentDefaultVoiceImeId); if (newSystemVoiceIme == null) { if (DEBUG) { Slog.i(TAG, "Found no valid default Voice IME. If the user is still locked," @@ -5145,7 +5147,7 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. // Clear DEFAULT_VOICE_INPUT_METHOD when necessary. Note that InputMethodSettings // does not update the actual Secure Settings until the user is unlocked. if (!TextUtils.isEmpty(currentDefaultVoiceImeId)) { - mSettings.putDefaultVoiceInputMethod(""); + settings.putDefaultVoiceInputMethod(""); // We don't support disabling the voice ime when a package is removed from the // config. } @@ -5158,7 +5160,7 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. Slog.i(TAG, "Enabling the default Voice IME:" + newSystemVoiceIme); } setInputMethodEnabledLocked(newSystemVoiceIme.getId(), true); - mSettings.putDefaultVoiceInputMethod(newSystemVoiceIme.getId()); + settings.putDefaultVoiceInputMethod(newSystemVoiceIme.getId()); } // ---------------------------------------------------------------------- @@ -5173,8 +5175,9 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. */ @GuardedBy("ImfLock.class") private boolean setInputMethodEnabledLocked(String id, boolean enabled) { + final InputMethodSettings settings = InputMethodSettingsRepository.get(mCurrentUserId); if (enabled) { - final String enabledImeIdsStr = mSettings.getEnabledInputMethodsStr(); + final String enabledImeIdsStr = settings.getEnabledInputMethodsStr(); final String newEnabledImeIdsStr = InputMethodUtils.concatEnabledImeIds( enabledImeIdsStr, id); if (TextUtils.equals(enabledImeIdsStr, newEnabledImeIdsStr)) { @@ -5182,29 +5185,29 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. // Nothing to do. The previous state was enabled. return true; } - mSettings.putEnabledInputMethodsStr(newEnabledImeIdsStr); + settings.putEnabledInputMethodsStr(newEnabledImeIdsStr); // Previous state was disabled. return false; } else { - final List<Pair<String, ArrayList<String>>> enabledInputMethodsList = mSettings + final List<Pair<String, ArrayList<String>>> enabledInputMethodsList = settings .getEnabledInputMethodsAndSubtypeList(); StringBuilder builder = new StringBuilder(); - if (mSettings.buildAndPutEnabledInputMethodsStrRemovingId( + if (settings.buildAndPutEnabledInputMethodsStrRemovingId( builder, enabledInputMethodsList, id)) { if (mDeviceIdToShowIme == DEVICE_ID_DEFAULT) { // Disabled input method is currently selected, switch to another one. - final String selId = mSettings.getSelectedInputMethod(); + final String selId = settings.getSelectedInputMethod(); if (id.equals(selId) && !chooseNewDefaultIMELocked()) { Slog.i(TAG, "Can't find new IME, unsetting the current input method."); resetSelectedInputMethodAndSubtypeLocked(""); } - } else if (id.equals(mSettings.getSelectedDefaultDeviceInputMethod())) { + } else if (id.equals(settings.getSelectedDefaultDeviceInputMethod())) { // Disabled default device IME while using a virtual device one, choose a // new default one but only update the settings. InputMethodInfo newDefaultIme = InputMethodInfoUtils.getMostApplicableDefaultIME( - mSettings.getEnabledInputMethodList()); - mSettings.putSelectedDefaultDeviceInputMethod( + settings.getEnabledInputMethodList()); + settings.putSelectedDefaultDeviceInputMethod( newDefaultIme == null ? null : newDefaultIme.getId()); } // Previous state was enabled. @@ -5220,29 +5223,30 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. @GuardedBy("ImfLock.class") private void setSelectedInputMethodAndSubtypeLocked(InputMethodInfo imi, int subtypeId, boolean setSubtypeOnly) { - mSettings.saveCurrentInputMethodAndSubtypeToHistory(getSelectedMethodIdLocked(), + final InputMethodSettings settings = InputMethodSettingsRepository.get(mCurrentUserId); + settings.saveCurrentInputMethodAndSubtypeToHistory(getSelectedMethodIdLocked(), mCurrentSubtype); // Set Subtype here if (imi == null || subtypeId < 0) { - mSettings.putSelectedSubtype(NOT_A_SUBTYPE_ID); + settings.putSelectedSubtype(NOT_A_SUBTYPE_ID); mCurrentSubtype = null; } else { if (subtypeId < imi.getSubtypeCount()) { InputMethodSubtype subtype = imi.getSubtypeAt(subtypeId); - mSettings.putSelectedSubtype(subtype.hashCode()); + settings.putSelectedSubtype(subtype.hashCode()); mCurrentSubtype = subtype; } else { - mSettings.putSelectedSubtype(NOT_A_SUBTYPE_ID); + settings.putSelectedSubtype(NOT_A_SUBTYPE_ID); // If the subtype is not specified, choose the most applicable one mCurrentSubtype = getCurrentInputMethodSubtypeLocked(); } } - notifyInputMethodSubtypeChangedLocked(mSettings.getUserId(), imi, mCurrentSubtype); + notifyInputMethodSubtypeChangedLocked(settings.getUserId(), imi, mCurrentSubtype); if (!setSubtypeOnly) { // Set InputMethod here - mSettings.putSelectedInputMethod(imi != null ? imi.getId() : ""); + settings.putSelectedInputMethod(imi != null ? imi.getId() : ""); } } @@ -5250,13 +5254,15 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. private void resetSelectedInputMethodAndSubtypeLocked(String newDefaultIme) { mDeviceIdToShowIme = DEVICE_ID_DEFAULT; mDisplayIdToShowIme = INVALID_DISPLAY; - mSettings.putSelectedDefaultDeviceInputMethod(null); - InputMethodInfo imi = mSettings.getMethodMap().get(newDefaultIme); + final InputMethodSettings settings = InputMethodSettingsRepository.get(mCurrentUserId); + settings.putSelectedDefaultDeviceInputMethod(null); + + InputMethodInfo imi = settings.getMethodMap().get(newDefaultIme); int lastSubtypeId = NOT_A_SUBTYPE_ID; // newDefaultIme is empty when there is no candidate for the selected IME. if (imi != null && !TextUtils.isEmpty(newDefaultIme)) { - String subtypeHashCode = mSettings.getLastSubtypeForInputMethod(newDefaultIme); + String subtypeHashCode = settings.getLastSubtypeForInputMethod(newDefaultIme); if (subtypeHashCode != null) { try { lastSubtypeId = SubtypeUtils.getSubtypeIdFromHashCode(imi, @@ -5283,7 +5289,7 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. Manifest.permission.INTERACT_ACROSS_USERS_FULL, null); } synchronized (ImfLock.class) { - if (mSettings.getUserId() == userId) { + if (mCurrentUserId == userId) { return getCurrentInputMethodSubtypeLocked(); } @@ -5308,26 +5314,27 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. if (selectedMethodId == null) { return null; } - final boolean subtypeIsSelected = mSettings.isSubtypeSelected(); - final InputMethodInfo imi = mSettings.getMethodMap().get(selectedMethodId); + final InputMethodSettings settings = InputMethodSettingsRepository.get(mCurrentUserId); + final boolean subtypeIsSelected = settings.isSubtypeSelected(); + final InputMethodInfo imi = settings.getMethodMap().get(selectedMethodId); if (imi == null || imi.getSubtypeCount() == 0) { return null; } if (!subtypeIsSelected || mCurrentSubtype == null || !SubtypeUtils.isValidSubtypeId(imi, mCurrentSubtype.hashCode())) { - int subtypeId = mSettings.getSelectedInputMethodSubtypeId(selectedMethodId); + int subtypeId = settings.getSelectedInputMethodSubtypeId(selectedMethodId); if (subtypeId == NOT_A_SUBTYPE_ID) { // If there are no selected subtypes, the framework will try to find // the most applicable subtype from explicitly or implicitly enabled // subtypes. List<InputMethodSubtype> explicitlyOrImplicitlyEnabledSubtypes = - mSettings.getEnabledInputMethodSubtypeList(imi, true); + settings.getEnabledInputMethodSubtypeList(imi, true); // If there is only one explicitly or implicitly enabled subtype, // just returns it. if (explicitlyOrImplicitlyEnabledSubtypes.size() == 1) { mCurrentSubtype = explicitlyOrImplicitlyEnabledSubtypes.get(0); } else if (explicitlyOrImplicitlyEnabledSubtypes.size() > 1) { - final String locale = SystemLocaleWrapper.get(mSettings.getUserId()) + final String locale = SystemLocaleWrapper.get(settings.getUserId()) .get(0).toString(); mCurrentSubtype = SubtypeUtils.findLastResortApplicableSubtype( explicitlyOrImplicitlyEnabledSubtypes, @@ -5356,16 +5363,16 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. @GuardedBy("ImfLock.class") private boolean switchToInputMethodLocked(String imeId, @UserIdInt int userId) { - if (userId == mSettings.getUserId()) { - if (!mSettings.getMethodMap().containsKey(imeId) - || !mSettings.getEnabledInputMethodList() - .contains(mSettings.getMethodMap().get(imeId))) { + final InputMethodSettings settings = InputMethodSettingsRepository.get(userId); + if (userId == mCurrentUserId) { + if (!settings.getMethodMap().containsKey(imeId) + || !settings.getEnabledInputMethodList() + .contains(settings.getMethodMap().get(imeId))) { return false; // IME is not found or not enabled. } setInputMethodLocked(imeId, NOT_A_SUBTYPE_ID); return true; } - final InputMethodSettings settings = InputMethodSettingsRepository.get(userId); if (!settings.getMethodMap().containsKey(imeId) || !settings.getEnabledInputMethodList().contains( settings.getMethodMap().get(imeId))) { @@ -5406,8 +5413,9 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. @GuardedBy("ImfLock.class") private void switchKeyboardLayoutLocked(int direction) { - final InputMethodInfo currentImi = mSettings.getMethodMap().get( - getSelectedMethodIdLocked()); + final InputMethodSettings settings = InputMethodSettingsRepository.get(mCurrentUserId); + + final InputMethodInfo currentImi = settings.getMethodMap().get(getSelectedMethodIdLocked()); if (currentImi == null) { return; } @@ -5419,7 +5427,7 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. if (nextSubtypeHandle == null) { return; } - final InputMethodInfo nextImi = mSettings.getMethodMap().get(nextSubtypeHandle.getImeId()); + final InputMethodInfo nextImi = settings.getMethodMap().get(nextSubtypeHandle.getImeId()); if (nextImi == null) { return; } @@ -5498,17 +5506,14 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. @Override public boolean setInputMethodEnabled(String imeId, boolean enabled, @UserIdInt int userId) { synchronized (ImfLock.class) { - if (userId == mSettings.getUserId()) { - if (!mSettings.getMethodMap().containsKey(imeId)) { - return false; // IME is not found. - } - setInputMethodEnabledLocked(imeId, enabled); - return true; - } final InputMethodSettings settings = InputMethodSettingsRepository.get(userId); if (!settings.getMethodMap().containsKey(imeId)) { return false; // IME is not found. } + if (userId == mCurrentUserId) { + setInputMethodEnabledLocked(imeId, enabled); + return true; + } if (enabled) { final String enabledImeIdsStr = settings.getEnabledInputMethodsStr(); final String newEnabledImeIdsStr = InputMethodUtils.concatEnabledImeIds( @@ -5835,8 +5840,9 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. final Printer p = new PrintWriterPrinter(pw); synchronized (ImfLock.class) { + final InputMethodSettings settings = InputMethodSettingsRepository.get(mCurrentUserId); p.println("Current Input Method Manager state:"); - final List<InputMethodInfo> methodList = mSettings.getMethodList(); + final List<InputMethodInfo> methodList = settings.getMethodList(); int numImes = methodList.size(); p.println(" Input Methods: mMethodMapUpdateCount=" + mMethodMapUpdateCount); for (int i = 0; i < numImes; i++) { @@ -5860,6 +5866,7 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. p.println(" curSession=" + c.mCurSession); }; mClientController.forAllClients(clientControllerDump); + p.println(" mCurrentUserId=" + mCurrentUserId); p.println(" mCurMethodId=" + getSelectedMethodIdLocked()); client = mCurClient; p.println(" mCurClient=" + client + " mCurSeq=" + getSequenceNumberLocked()); @@ -5885,8 +5892,6 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. ? Arrays.toString(mStylusIds.toArray()) : "")); p.println(" mSwitchingController:"); mSwitchingController.dump(p); - p.println(" mSettings:"); - mSettings.dump(p, " "); p.println(" mStartInputHistory:"); mStartInputHistory.dump(pw, " "); @@ -6141,7 +6146,7 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. } synchronized (ImfLock.class) { final int[] userIds = InputMethodUtils.resolveUserId(userIdToBeResolved, - mSettings.getUserId(), shellCommand.getErrPrintWriter()); + mCurrentUserId, shellCommand.getErrPrintWriter()); try (PrintWriter pr = shellCommand.getOutPrintWriter()) { for (int userId : userIds) { final List<InputMethodInfo> methods = all @@ -6186,7 +6191,7 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. PrintWriter error = shellCommand.getErrPrintWriter()) { synchronized (ImfLock.class) { final int[] userIds = InputMethodUtils.resolveUserId(userIdToBeResolved, - mSettings.getUserId(), shellCommand.getErrPrintWriter()); + mCurrentUserId, shellCommand.getErrPrintWriter()); for (int userId : userIds) { if (!userHasDebugPriv(userId, shellCommand)) { continue; @@ -6245,14 +6250,14 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. PrintWriter error) { boolean failedToEnableUnknownIme = false; boolean previouslyEnabled = false; - if (userId == mSettings.getUserId()) { - if (enabled && !mSettings.getMethodMap().containsKey(imeId)) { + final InputMethodSettings settings = InputMethodSettingsRepository.get(userId); + if (userId == mCurrentUserId) { + if (enabled && !settings.getMethodMap().containsKey(imeId)) { failedToEnableUnknownIme = true; } else { previouslyEnabled = setInputMethodEnabledLocked(imeId, enabled); } } else { - final InputMethodSettings settings = InputMethodSettingsRepository.get(userId); if (enabled) { if (!settings.getMethodMap().containsKey(imeId)) { failedToEnableUnknownIme = true; @@ -6307,7 +6312,7 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. PrintWriter error = shellCommand.getErrPrintWriter()) { synchronized (ImfLock.class) { final int[] userIds = InputMethodUtils.resolveUserId(userIdToBeResolved, - mSettings.getUserId(), shellCommand.getErrPrintWriter()); + mCurrentUserId, shellCommand.getErrPrintWriter()); for (int userId : userIds) { if (!userHasDebugPriv(userId, shellCommand)) { continue; @@ -6347,7 +6352,7 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. synchronized (ImfLock.class) { try (PrintWriter out = shellCommand.getOutPrintWriter()) { final int[] userIds = InputMethodUtils.resolveUserId(userIdToBeResolved, - mSettings.getUserId(), shellCommand.getErrPrintWriter()); + mCurrentUserId, shellCommand.getErrPrintWriter()); for (int userId : userIds) { if (!userHasDebugPriv(userId, shellCommand)) { continue; @@ -6359,15 +6364,16 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. } final String nextIme; final List<InputMethodInfo> nextEnabledImes; - if (userId == mSettings.getUserId()) { + final InputMethodSettings settings = InputMethodSettingsRepository.get(userId); + if (userId == mCurrentUserId) { hideCurrentInputLocked(mImeBindingState.mFocusedWindow, 0 /* flags */, SoftInputShowHideReason.HIDE_RESET_SHELL_COMMAND); mBindingController.unbindCurrentMethod(); // Enable default IMEs, disable others - var toDisable = mSettings.getEnabledInputMethodList(); + var toDisable = settings.getEnabledInputMethodList(); var defaultEnabled = InputMethodInfoUtils.getDefaultEnabledImes( - mContext, mSettings.getMethodList()); + mContext, settings.getMethodList()); toDisable.removeAll(defaultEnabled); for (InputMethodInfo info : toDisable) { setInputMethodEnabledLocked(info.getId(), false); @@ -6381,14 +6387,11 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. } updateInputMethodsFromSettingsLocked(true /* enabledMayChange */); InputMethodUtils.setNonSelectedSystemImesDisabledUntilUsed( - getPackageManagerForUser(mContext, mSettings.getUserId()), - mSettings.getEnabledInputMethodList()); - nextIme = mSettings.getSelectedInputMethod(); - nextEnabledImes = mSettings.getEnabledInputMethodList(); + getPackageManagerForUser(mContext, settings.getUserId()), + settings.getEnabledInputMethodList()); + nextIme = settings.getSelectedInputMethod(); + nextEnabledImes = settings.getEnabledInputMethodList(); } else { - final InputMethodSettings settings = - InputMethodSettingsRepository.get(userId); - nextEnabledImes = InputMethodInfoUtils.getDefaultEnabledImes(mContext, settings.getMethodList()); nextIme = InputMethodInfoUtils.getMostApplicableDefaultIME( diff --git a/services/core/java/com/android/server/notification/NotificationChannelExtractor.java b/services/core/java/com/android/server/notification/NotificationChannelExtractor.java index bd73cb6544f0..1938642ef396 100644 --- a/services/core/java/com/android/server/notification/NotificationChannelExtractor.java +++ b/services/core/java/com/android/server/notification/NotificationChannelExtractor.java @@ -23,9 +23,15 @@ import static android.media.AudioAttributes.USAGE_NOTIFICATION; import android.app.Notification; import android.app.NotificationChannel; +import android.compat.annotation.ChangeId; +import android.compat.annotation.LoggingOnly; import android.content.Context; import android.media.AudioAttributes; +import android.os.Binder; +import android.os.RemoteException; +import android.os.ServiceManager; import android.util.Slog; +import com.android.internal.compat.IPlatformCompat; /** * Stores the latest notification channel information for this notification @@ -34,14 +40,26 @@ public class NotificationChannelExtractor implements NotificationSignalExtractor private static final String TAG = "ChannelExtractor"; private static final boolean DBG = false; + /** + * Corrects audio attributes for notifications based on characteristics of the notifications. + */ + @ChangeId + @LoggingOnly + static final long RESTRICT_AUDIO_ATTRIBUTES = 331793339L; + private RankingConfig mConfig; private Context mContext; + private IPlatformCompat mPlatformCompat; public void initialize(Context ctx, NotificationUsageStats usageStats) { mContext = ctx; if (DBG) Slog.d(TAG, "Initializing " + getClass().getSimpleName() + "."); } + public void setCompatChangeLogger(IPlatformCompat platformCompat) { + mPlatformCompat = platformCompat; + } + public RankingReconsideration process(NotificationRecord record) { if (record == null || record.getNotification() == null) { if (DBG) Slog.d(TAG, "skipping empty notification"); @@ -80,6 +98,7 @@ public class NotificationChannelExtractor implements NotificationSignalExtractor } if (updateAttributes) { + reportAudioAttributesChanged(record.getUid()); NotificationChannel clone = record.getChannel().copy(); clone.setSound(clone.getSound(), new AudioAttributes.Builder(attributes) .setUsage(USAGE_NOTIFICATION) @@ -91,6 +110,17 @@ public class NotificationChannelExtractor implements NotificationSignalExtractor return null; } + private void reportAudioAttributesChanged(int uid) { + final long id = Binder.clearCallingIdentity(); + try { + mPlatformCompat.reportChangeByUid(RESTRICT_AUDIO_ATTRIBUTES, uid); + } catch (RemoteException e) { + Slog.e(TAG, "Unexpected exception while reporting to changecompat", e); + } finally { + Binder.restoreCallingIdentity(id); + } + } + @Override public void setConfig(RankingConfig config) { mConfig = config; diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java index 8075ae0e4d61..b48cad2406e3 100755 --- a/services/core/java/com/android/server/notification/NotificationManagerService.java +++ b/services/core/java/com/android/server/notification/NotificationManagerService.java @@ -220,6 +220,7 @@ import android.content.IntentFilter; import android.content.pm.ApplicationInfo; import android.content.pm.IPackageManager; import android.content.pm.LauncherApps; +import android.content.pm.ModuleInfo; import android.content.pm.PackageManager; import android.content.pm.PackageManager.NameNotFoundException; import android.content.pm.PackageManagerInternal; @@ -675,6 +676,10 @@ public class NotificationManagerService extends SystemService { private static final int DB_VERSION = 1; + + private static final String ADSERVICES_MODULE_PKG_NAME = + "com.android.adservices"; + private static final String TAG_NOTIFICATION_POLICY = "notification-policy"; private static final String ATTR_VERSION = "version"; @@ -736,6 +741,8 @@ public class NotificationManagerService extends SystemService { private AppOpsManager.OnOpChangedListener mAppOpsListener; + private ModuleInfo mAdservicesModuleInfo; + static class Archive { final SparseArray<Boolean> mEnabled; final int mBufferSize; @@ -2508,12 +2515,8 @@ public class NotificationManagerService extends SystemService { mAppOps, mUserProfiles, mShowReviewPermissionsNotification); - mRankingHelper = new RankingHelper(getContext(), - mRankingHandler, - mPreferencesHelper, - mZenModeHelper, - mUsageStats, - extractorNames); + mRankingHelper = new RankingHelper(getContext(), mRankingHandler, mPreferencesHelper, + mZenModeHelper, mUsageStats, extractorNames, mPlatformCompat); mSnoozeHelper = snoozeHelper; mGroupHelper = groupHelper; mHistoryManager = historyManager; @@ -2938,6 +2941,15 @@ public class NotificationManagerService extends SystemService { mZenModeHelper.setDeviceEffectsApplier( new DefaultDeviceEffectsApplier(getContext())); } + List<ModuleInfo> moduleInfoList = + mPackageManagerClient.getInstalledModules( + PackageManager.MATCH_DEBUG_TRIAGED_MISSING); + // Cache adservices module info + for (ModuleInfo mi : moduleInfoList) { + if (Objects.equals(mi.getApexModuleName(), ADSERVICES_MODULE_PKG_NAME)) { + mAdservicesModuleInfo = mi; + } + } } else if (phase == SystemService.PHASE_ACTIVITY_MANAGER_READY) { mSnoozeHelper.scheduleRepostsForPersistedNotifications(System.currentTimeMillis()); } else if (phase == SystemService.PHASE_DEVICE_SPECIFIC_SERVICES_READY) { @@ -7687,13 +7699,27 @@ public class NotificationManagerService extends SystemService { private boolean canBeNonDismissible(ApplicationInfo ai, Notification notification) { return notification.isMediaNotification() || isEnterpriseExempted(ai) || notification.isStyle(Notification.CallStyle.class) - || isDefaultSearchSelectorPackage(ai.packageName); + || isDefaultSearchSelectorPackage(ai.packageName) + || isDefaultAdservicesPackage(ai.packageName); } private boolean isDefaultSearchSelectorPackage(String pkg) { return Objects.equals(mDefaultSearchSelectorPkg, pkg); } + private boolean isDefaultAdservicesPackage(String pkg) { + if (mAdservicesModuleInfo == null) { + return false; + } + // Handles the special package structure for mainline modules + for (String apkName : mAdservicesModuleInfo.getApkInApexPackageNames()) { + if (Objects.equals(apkName, pkg)) { + return true; + } + } + return false; + } + private boolean isEnterpriseExempted(ApplicationInfo ai) { // Check if the app is an organization admin app // TODO(b/234609037): Replace with new DPM APIs to check if organization admin diff --git a/services/core/java/com/android/server/notification/NotificationSignalExtractor.java b/services/core/java/com/android/server/notification/NotificationSignalExtractor.java index 24c1d5966020..f0358d1e1d8c 100644 --- a/services/core/java/com/android/server/notification/NotificationSignalExtractor.java +++ b/services/core/java/com/android/server/notification/NotificationSignalExtractor.java @@ -17,6 +17,7 @@ package com.android.server.notification; import android.content.Context; +import com.android.internal.compat.IPlatformCompat; /** * Extracts signals that will be useful to the {@link NotificationComparator} and caches them @@ -52,4 +53,6 @@ public interface NotificationSignalExtractor { * DND. */ void setZenHelper(ZenModeHelper helper); + + default void setCompatChangeLogger(IPlatformCompat platformCompat){}; } diff --git a/services/core/java/com/android/server/notification/PreferencesHelper.java b/services/core/java/com/android/server/notification/PreferencesHelper.java index 50ca984dcf57..461bd9c0663b 100644 --- a/services/core/java/com/android/server/notification/PreferencesHelper.java +++ b/services/core/java/com/android/server/notification/PreferencesHelper.java @@ -1387,8 +1387,7 @@ public class PreferencesHelper implements RankingConfig { public void updateFixedImportance(List<UserInfo> users) { for (UserInfo user : users) { List<PackageInfo> packages = mPm.getInstalledPackagesAsUser( - PackageManager.PackageInfoFlags.of(PackageManager.MATCH_SYSTEM_ONLY), - user.getUserHandle().getIdentifier()); + 0, user.getUserHandle().getIdentifier()); for (PackageInfo pi : packages) { boolean fixed = mPermissionHelper.isPermissionFixed( pi.packageName, user.getUserHandle().getIdentifier()); diff --git a/services/core/java/com/android/server/notification/RankingHelper.java b/services/core/java/com/android/server/notification/RankingHelper.java index 68e0eaaf31cd..77568015fe79 100644 --- a/services/core/java/com/android/server/notification/RankingHelper.java +++ b/services/core/java/com/android/server/notification/RankingHelper.java @@ -15,6 +15,9 @@ */ package com.android.server.notification; +import static android.app.Flags.restrictAudioAttributesAlarm; +import static android.app.Flags.restrictAudioAttributesCall; +import static android.app.Flags.restrictAudioAttributesMedia; import static android.app.Flags.sortSectionByTime; import static android.app.NotificationManager.IMPORTANCE_MIN; import static android.text.TextUtils.formatSimple; @@ -27,6 +30,7 @@ import android.util.ArrayMap; import android.util.Slog; import android.util.proto.ProtoOutputStream; +import com.android.internal.compat.IPlatformCompat; import com.android.tools.r8.keepanno.annotations.KeepItemKind; import com.android.tools.r8.keepanno.annotations.KeepTarget; import com.android.tools.r8.keepanno.annotations.UsesReflection; @@ -56,7 +60,8 @@ public class RankingHelper { methodName = "<init>") }) public RankingHelper(Context context, RankingHandler rankingHandler, RankingConfig config, - ZenModeHelper zenHelper, NotificationUsageStats usageStats, String[] extractorNames) { + ZenModeHelper zenHelper, NotificationUsageStats usageStats, String[] extractorNames, + IPlatformCompat platformCompat) { mContext = context; mRankingHandler = rankingHandler; if (sortSectionByTime()) { @@ -75,6 +80,10 @@ public class RankingHelper { extractor.initialize(mContext, usageStats); extractor.setConfig(config); extractor.setZenHelper(zenHelper); + if (restrictAudioAttributesAlarm() || restrictAudioAttributesMedia() + || restrictAudioAttributesCall()) { + extractor.setCompatChangeLogger(platformCompat); + } mSignalExtractors[i] = extractor; } catch (ClassNotFoundException e) { Slog.w(TAG, "Couldn't find extractor " + extractorNames[i] + ".", e); diff --git a/services/core/java/com/android/server/pm/ShortcutService.java b/services/core/java/com/android/server/pm/ShortcutService.java index 4c653f6ce95f..fe9c3f217841 100644 --- a/services/core/java/com/android/server/pm/ShortcutService.java +++ b/services/core/java/com/android/server/pm/ShortcutService.java @@ -50,6 +50,7 @@ import android.content.pm.ComponentInfo; import android.content.pm.IPackageManager; import android.content.pm.IShortcutService; import android.content.pm.LauncherApps; +import android.content.pm.LauncherApps.ShortcutChangeCallback; import android.content.pm.LauncherApps.ShortcutQuery; import android.content.pm.PackageInfo; import android.content.pm.PackageManager; @@ -151,6 +152,7 @@ import java.util.Arrays; import java.util.Collections; import java.util.List; import java.util.Objects; +import java.util.concurrent.CopyOnWriteArrayList; import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicLong; import java.util.function.Consumer; @@ -320,12 +322,11 @@ public class ShortcutService extends IShortcutService.Stub { private final Handler mHandler; - @GuardedBy("mLock") - private final ArrayList<ShortcutChangeListener> mListeners = new ArrayList<>(1); + private final CopyOnWriteArrayList<ShortcutChangeListener> mListeners = + new CopyOnWriteArrayList<>(); - @GuardedBy("mLock") - private final ArrayList<LauncherApps.ShortcutChangeCallback> mShortcutChangeCallbacks = - new ArrayList<>(1); + private final CopyOnWriteArrayList<ShortcutChangeCallback> mShortcutChangeCallbacks = + new CopyOnWriteArrayList<>(); private final AtomicLong mRawLastResetTime = new AtomicLong(0); @@ -1841,18 +1842,11 @@ public class ShortcutService extends IShortcutService.Stub { @UserIdInt final int userId) { return () -> { try { - final ArrayList<ShortcutChangeListener> copy; - synchronized (mLock) { - if (!isUserUnlockedL(userId)) { - return; - } - - copy = new ArrayList<>(mListeners); + if (!isUserUnlockedL(userId)) { + return; } // Note onShortcutChanged() needs to be called with the system service permissions. - for (int i = copy.size() - 1; i >= 0; i--) { - copy.get(i).onShortcutChanged(packageName, userId); - } + mListeners.forEach(listener -> listener.onShortcutChanged(packageName, userId)); } catch (Exception ignore) { } }; @@ -1867,22 +1861,17 @@ public class ShortcutService extends IShortcutService.Stub { final UserHandle user = UserHandle.of(userId); injectPostToHandler(() -> { try { - final ArrayList<LauncherApps.ShortcutChangeCallback> copy; - synchronized (mLock) { - if (!isUserUnlockedL(userId)) { - return; - } - - copy = new ArrayList<>(mShortcutChangeCallbacks); + if (!isUserUnlockedL(userId)) { + return; } - for (int i = copy.size() - 1; i >= 0; i--) { + mShortcutChangeCallbacks.forEach(callback -> { if (!CollectionUtils.isEmpty(changedList)) { - copy.get(i).onShortcutsAddedOrUpdated(packageName, changedList, user); + callback.onShortcutsAddedOrUpdated(packageName, changedList, user); } if (!CollectionUtils.isEmpty(removedList)) { - copy.get(i).onShortcutsRemoved(packageName, removedList, user); + callback.onShortcutsRemoved(packageName, removedList, user); } - } + }); } catch (Exception ignore) { } }); @@ -3425,17 +3414,13 @@ public class ShortcutService extends IShortcutService.Stub { @Override public void addListener(@NonNull ShortcutChangeListener listener) { - synchronized (mLock) { - mListeners.add(Objects.requireNonNull(listener)); - } + mListeners.add(Objects.requireNonNull(listener)); } @Override public void addShortcutChangeCallback( @NonNull LauncherApps.ShortcutChangeCallback callback) { - synchronized (mLock) { - mShortcutChangeCallbacks.add(Objects.requireNonNull(callback)); - } + mShortcutChangeCallbacks.add(Objects.requireNonNull(callback)); } @Override diff --git a/services/core/java/com/android/server/power/stats/BatteryStatsImpl.java b/services/core/java/com/android/server/power/stats/BatteryStatsImpl.java index d060c7ca3034..54cb9c9a9a9b 100644 --- a/services/core/java/com/android/server/power/stats/BatteryStatsImpl.java +++ b/services/core/java/com/android/server/power/stats/BatteryStatsImpl.java @@ -4885,7 +4885,6 @@ public class BatteryStatsImpl extends BatteryStats { if (type == WAKE_TYPE_PARTIAL) { // Only care about partial wake locks, since full wake locks // will be canceled when the user puts the screen to sleep. - aggregateLastWakeupUptimeLocked(elapsedRealtimeMs, uptimeMs); if (historyName == null) { historyName = name; } @@ -5205,20 +5204,14 @@ public class BatteryStatsImpl extends BatteryStats { } @GuardedBy("this") - void aggregateLastWakeupUptimeLocked(long elapsedRealtimeMs, long uptimeMs) { + public void noteWakeupReasonLocked(String reason, long elapsedRealtimeMs, long uptimeMs) { if (mLastWakeupReason != null) { long deltaUptimeMs = uptimeMs - mLastWakeupUptimeMs; SamplingTimer timer = getWakeupReasonTimerLocked(mLastWakeupReason); timer.add(deltaUptimeMs * 1000, 1, elapsedRealtimeMs); // time in in microseconds mFrameworkStatsLogger.kernelWakeupReported(deltaUptimeMs * 1000, mLastWakeupReason, mLastWakeupElapsedTimeMs); - mLastWakeupReason = null; } - } - - @GuardedBy("this") - public void noteWakeupReasonLocked(String reason, long elapsedRealtimeMs, long uptimeMs) { - aggregateLastWakeupUptimeLocked(elapsedRealtimeMs, uptimeMs); mHistory.recordWakeupEvent(elapsedRealtimeMs, uptimeMs, reason); mLastWakeupReason = reason; mLastWakeupUptimeMs = uptimeMs; diff --git a/services/core/java/com/android/server/vcn/routeselection/IpSecPacketLossDetector.java b/services/core/java/com/android/server/vcn/routeselection/IpSecPacketLossDetector.java index c5d333317013..36192537493a 100644 --- a/services/core/java/com/android/server/vcn/routeselection/IpSecPacketLossDetector.java +++ b/services/core/java/com/android/server/vcn/routeselection/IpSecPacketLossDetector.java @@ -64,12 +64,19 @@ public class IpSecPacketLossDetector extends NetworkMetricMonitor { private static final int PACKET_LOSS_PERCENT_UNAVAILABLE = -1; + // Ignore the packet loss detection result if the expected packet number is smaller than 10. + // Solarwinds NPM uses 10 ICMP echos to calculate packet loss rate (as per + // https://thwack.solarwinds.com/products/network-performance-monitor-npm/f/forum/63829/how-is-packet-loss-calculated) + @VisibleForTesting(visibility = Visibility.PRIVATE) + static final int MIN_VALID_EXPECTED_RX_PACKET_NUM = 10; + @Retention(RetentionPolicy.SOURCE) @IntDef( prefix = {"PACKET_LOSS_"}, value = { PACKET_LOSS_RATE_VALID, PACKET_LOSS_RATE_INVALID, + PACKET_LOSS_UNUSUAL_SEQ_NUM_LEAP, }) @Target({ElementType.TYPE_USE}) private @interface PacketLossResultType {} @@ -84,11 +91,23 @@ public class IpSecPacketLossDetector extends NetworkMetricMonitor { * <ul> * <li>The replay window did not proceed and thus all packets might have been delivered out of * order + * <li>The expected received packet number is too small and thus the detection result is not + * reliable * <li>There are unexpected errors * </ul> */ private static final int PACKET_LOSS_RATE_INVALID = 1; + /** + * The sequence number increase is unusually large and might be caused an intentional leap on + * the server's downlink + * + * <p>Inbound sequence number will not always increase consecutively. During load balancing the + * server might add a big leap on the sequence number intentionally. In such case a high packet + * loss rate does not always indicate a lossy network + */ + private static final int PACKET_LOSS_UNUSUAL_SEQ_NUM_LEAP = 2; + // For VoIP, losses between 5% and 10% of the total packet stream will affect the quality // significantly (as per "Computer Networking for LANS to WANS: Hardware, Software and // Security"). For audio and video streaming, above 10-12% packet loss is unacceptable (as per @@ -98,8 +117,12 @@ public class IpSecPacketLossDetector extends NetworkMetricMonitor { private static final int POLL_IPSEC_STATE_INTERVAL_SECONDS_DEFAULT = 20; + // By default, there's no maximum limit enforced + private static final int MAX_SEQ_NUM_INCREASE_DEFAULT_DISABLED = -1; + private long mPollIpSecStateIntervalMs; - private final int mPacketLossRatePercentThreshold; + private int mPacketLossRatePercentThreshold; + private int mMaxSeqNumIncreasePerSecond; @NonNull private final Handler mHandler; @NonNull private final PowerManager mPowerManager; @@ -138,6 +161,7 @@ public class IpSecPacketLossDetector extends NetworkMetricMonitor { mPollIpSecStateIntervalMs = getPollIpSecStateIntervalMs(carrierConfig); mPacketLossRatePercentThreshold = getPacketLossRatePercentThreshold(carrierConfig); + mMaxSeqNumIncreasePerSecond = getMaxSeqNumIncreasePerSecond(carrierConfig); // Register for system broadcasts to monitor idle mode change final IntentFilter intentFilter = new IntentFilter(); @@ -202,6 +226,24 @@ public class IpSecPacketLossDetector extends NetworkMetricMonitor { return IPSEC_PACKET_LOSS_PERCENT_THRESHOLD_DEFAULT; } + @VisibleForTesting(visibility = Visibility.PRIVATE) + static int getMaxSeqNumIncreasePerSecond(@Nullable PersistableBundleWrapper carrierConfig) { + int maxSeqNumIncrease = MAX_SEQ_NUM_INCREASE_DEFAULT_DISABLED; + if (Flags.handleSeqNumLeap() && carrierConfig != null) { + maxSeqNumIncrease = + carrierConfig.getInt( + VcnManager.VCN_NETWORK_SELECTION_MAX_SEQ_NUM_INCREASE_PER_SECOND_KEY, + MAX_SEQ_NUM_INCREASE_DEFAULT_DISABLED); + } + + if (maxSeqNumIncrease < MAX_SEQ_NUM_INCREASE_DEFAULT_DISABLED) { + logE(TAG, "Invalid value of MAX_SEQ_NUM_INCREASE_PER_SECOND_KEY " + maxSeqNumIncrease); + return MAX_SEQ_NUM_INCREASE_DEFAULT_DISABLED; + } + + return maxSeqNumIncrease; + } + @Override protected void onSelectedUnderlyingNetworkChanged() { if (!isSelectedUnderlyingNetwork()) { @@ -237,6 +279,11 @@ public class IpSecPacketLossDetector extends NetworkMetricMonitor { // The already scheduled event will not be affected. The followup events will be scheduled // with the new interval mPollIpSecStateIntervalMs = getPollIpSecStateIntervalMs(carrierConfig); + + if (Flags.handleSeqNumLeap()) { + mPacketLossRatePercentThreshold = getPacketLossRatePercentThreshold(carrierConfig); + mMaxSeqNumIncreasePerSecond = getMaxSeqNumIncreasePerSecond(carrierConfig); + } } @Override @@ -339,7 +386,10 @@ public class IpSecPacketLossDetector extends NetworkMetricMonitor { final PacketLossCalculationResult calculateResult = mPacketLossCalculator.getPacketLossRatePercentage( - mLastIpSecTransformState, state, getLogPrefix()); + mLastIpSecTransformState, + state, + mMaxSeqNumIncreasePerSecond, + getLogPrefix()); if (calculateResult.getResultType() == PACKET_LOSS_RATE_INVALID) { return; @@ -356,11 +406,18 @@ public class IpSecPacketLossDetector extends NetworkMetricMonitor { mLastIpSecTransformState = state; if (calculateResult.getPacketLossRatePercent() < mPacketLossRatePercentThreshold) { logV(logMsg); + + // In both "valid" or "unusual_seq_num_leap" cases, notify that the network has passed + // the validation onValidationResultReceivedInternal(false /* isFailed */); } else { logInfo(logMsg); - onValidationResultReceivedInternal(true /* isFailed */); + if (calculateResult.getResultType() == PACKET_LOSS_RATE_VALID) { + onValidationResultReceivedInternal(true /* isFailed */); + } + + // In both "valid" or "unusual_seq_num_leap" cases, trigger network validation if (Flags.validateNetworkOnIpsecLoss()) { // Trigger re-validation of the underlying network; if it fails, the VCN will // attempt to migrate away. @@ -376,6 +433,7 @@ public class IpSecPacketLossDetector extends NetworkMetricMonitor { public PacketLossCalculationResult getPacketLossRatePercentage( @NonNull IpSecTransformState oldState, @NonNull IpSecTransformState newState, + int maxSeqNumIncreasePerSecond, String logPrefix) { logVIpSecTransform("oldState", oldState, logPrefix); logVIpSecTransform("newState", newState, logPrefix); @@ -392,6 +450,22 @@ public class IpSecPacketLossDetector extends NetworkMetricMonitor { return PacketLossCalculationResult.invalid(); } + boolean isUnusualSeqNumLeap = false; + + // Handle sequence number leap + if (Flags.handleSeqNumLeap() + && maxSeqNumIncreasePerSecond != MAX_SEQ_NUM_INCREASE_DEFAULT_DISABLED) { + final long timeDiffMillis = + newState.getTimestampMillis() - oldState.getTimestampMillis(); + final long maxSeqNumIncrease = timeDiffMillis * maxSeqNumIncreasePerSecond / 1000; + + // Sequence numbers are unsigned 32-bit values. If maxSeqNumIncrease overflows, + // isUnusualSeqNumLeap can never be true. + if (maxSeqNumIncrease >= 0 && newSeqHi - oldSeqHi >= maxSeqNumIncrease) { + isUnusualSeqNumLeap = true; + } + } + // Get the expected packet count by assuming there is no packet loss. In this case, SA // should receive all packets whose sequence numbers are smaller than the lower bound of // the replay window AND the packets received within the window. @@ -411,6 +485,11 @@ public class IpSecPacketLossDetector extends NetworkMetricMonitor { + " actualPktCntDiff: " + actualPktCntDiff); + if (Flags.handleSeqNumLeap() && expectedPktCntDiff < MIN_VALID_EXPECTED_RX_PACKET_NUM) { + // The sample size is too small to ensure a reliable detection result + return PacketLossCalculationResult.invalid(); + } + if (expectedPktCntDiff < 0 || expectedPktCntDiff == 0 || actualPktCntDiff < 0 @@ -420,7 +499,9 @@ public class IpSecPacketLossDetector extends NetworkMetricMonitor { } final int percent = 100 - (int) (actualPktCntDiff * 100 / expectedPktCntDiff); - return PacketLossCalculationResult.valid(percent); + return isUnusualSeqNumLeap + ? PacketLossCalculationResult.unusualSeqNumLeap(percent) + : PacketLossCalculationResult.valid(percent); } } @@ -462,6 +543,11 @@ public class IpSecPacketLossDetector extends NetworkMetricMonitor { PACKET_LOSS_RATE_INVALID, PACKET_LOSS_PERCENT_UNAVAILABLE); } + /** Construct an instance indicating that there is an unusual sequence number leap */ + public static PacketLossCalculationResult unusualSeqNumLeap(int percent) { + return new PacketLossCalculationResult(PACKET_LOSS_UNUSUAL_SEQ_NUM_LEAP, percent); + } + @PacketLossResultType public int getResultType() { return mResultType; diff --git a/services/core/java/com/android/server/vcn/routeselection/NetworkMetricMonitor.java b/services/core/java/com/android/server/vcn/routeselection/NetworkMetricMonitor.java index a1b212f8d3d7..b9b10606a188 100644 --- a/services/core/java/com/android/server/vcn/routeselection/NetworkMetricMonitor.java +++ b/services/core/java/com/android/server/vcn/routeselection/NetworkMetricMonitor.java @@ -272,6 +272,11 @@ public abstract class NetworkMetricMonitor implements AutoCloseable { } } + protected static void logE(String className, String msgWithPrefix) { + Slog.w(className, msgWithPrefix); + LOCAL_LOG.log("[ERROR ] " + className + msgWithPrefix); + } + protected static void logWtf(String className, String msgWithPrefix) { Slog.wtf(className, msgWithPrefix); LOCAL_LOG.log("[WTF ] " + className + msgWithPrefix); diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java index 87c5b7b8a120..00d42e0eb109 100644 --- a/services/core/java/com/android/server/wm/DisplayContent.java +++ b/services/core/java/com/android/server/wm/DisplayContent.java @@ -2003,7 +2003,12 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp } // Update directly because the app which will change the orientation of display is ready. if (mDisplayRotation.updateOrientation(getOrientation(), false /* forceUpdate */)) { - sendNewConfiguration(); + // Run rotation change on display thread. See Transition#shouldApplyOnDisplayThread(). + mWmService.mH.post(() -> { + synchronized (mWmService.mGlobalLock) { + sendNewConfiguration(); + } + }); return; } if (mRemoteDisplayChangeController.isWaitingForRemoteDisplayChange()) { diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java index ed88b5a7c449..143605ac7320 100644 --- a/services/core/java/com/android/server/wm/WindowManagerService.java +++ b/services/core/java/com/android/server/wm/WindowManagerService.java @@ -2446,6 +2446,9 @@ public class WindowManagerService extends IWindowManager.Stub ProtoLog.i(WM_DEBUG_SCREEN_ON, "Relayout %s: oldVis=%d newVis=%d. %s", win, oldVisibility, viewVisibility, new RuntimeException().fillInStackTrace()); + if (becameVisible) { + onWindowVisible(win); + } win.setDisplayLayoutNeeded(); win.mGivenInsetsPending = (flags & WindowManagerGlobal.RELAYOUT_INSETS_PENDING) != 0; @@ -10168,7 +10171,7 @@ public class WindowManagerService extends IWindowManager.Stub * Called to notify WMS that the specified window has become visible. This shows a Toast if the * window is deemed to hold sensitive content. */ - void onWindowVisible(@NonNull WindowState w) { + private void onWindowVisible(@NonNull WindowState w) { showToastIfBlockingScreenCapture(w); } diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java index d3baedc6e2a1..2fcee50e6f85 100644 --- a/services/core/java/com/android/server/wm/WindowState.java +++ b/services/core/java/com/android/server/wm/WindowState.java @@ -28,7 +28,6 @@ import static android.graphics.GraphicsProtos.dumpPointProto; import static android.os.InputConstants.DEFAULT_DISPATCHING_TIMEOUT_MILLIS; import static android.os.PowerManager.DRAW_WAKE_LOCK; import static android.os.Trace.TRACE_TAG_WINDOW_MANAGER; -import static android.permission.flags.Flags.sensitiveContentImprovements; import static android.view.SurfaceControl.Transaction; import static android.view.ViewTreeObserver.InternalInsetsInfo.TOUCHABLE_INSETS_CONTENT; import static android.view.ViewTreeObserver.InternalInsetsInfo.TOUCHABLE_INSETS_FRAME; @@ -2139,9 +2138,6 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP } } setDisplayLayoutNeeded(); - if (sensitiveContentImprovements() && visible) { - mWmService.onWindowVisible(this); - } } } diff --git a/services/core/jni/linux/usb/f_accessory.h b/services/core/jni/linux/usb/f_accessory.h new file mode 100644 index 000000000000..abd864cabc5d --- /dev/null +++ b/services/core/jni/linux/usb/f_accessory.h @@ -0,0 +1,34 @@ +/* + * This file is auto-generated. Modifications will be lost. + * + * See https://android.googlesource.com/platform/bionic/+/master/libc/kernel/ + * for more information. + */ +#ifndef _UAPI_LINUX_USB_F_ACCESSORY_H +#define _UAPI_LINUX_USB_F_ACCESSORY_H +#define USB_ACCESSORY_VENDOR_ID 0x18D1 +#define USB_ACCESSORY_PRODUCT_ID 0x2D00 +#define USB_ACCESSORY_ADB_PRODUCT_ID 0x2D01 +#define ACCESSORY_STRING_MANUFACTURER 0 +#define ACCESSORY_STRING_MODEL 1 +#define ACCESSORY_STRING_DESCRIPTION 2 +#define ACCESSORY_STRING_VERSION 3 +#define ACCESSORY_STRING_URI 4 +#define ACCESSORY_STRING_SERIAL 5 +#define ACCESSORY_GET_PROTOCOL 51 +#define ACCESSORY_SEND_STRING 52 +#define ACCESSORY_START 53 +#define ACCESSORY_REGISTER_HID 54 +#define ACCESSORY_UNREGISTER_HID 55 +#define ACCESSORY_SET_HID_REPORT_DESC 56 +#define ACCESSORY_SEND_HID_EVENT 57 +#define ACCESSORY_SET_AUDIO_MODE 58 +#define ACCESSORY_GET_STRING_MANUFACTURER _IOW('M', 1, char[256]) +#define ACCESSORY_GET_STRING_MODEL _IOW('M', 2, char[256]) +#define ACCESSORY_GET_STRING_DESCRIPTION _IOW('M', 3, char[256]) +#define ACCESSORY_GET_STRING_VERSION _IOW('M', 4, char[256]) +#define ACCESSORY_GET_STRING_URI _IOW('M', 5, char[256]) +#define ACCESSORY_GET_STRING_SERIAL _IOW('M', 6, char[256]) +#define ACCESSORY_IS_START_REQUESTED _IO('M', 7) +#define ACCESSORY_GET_AUDIO_MODE _IO('M', 8) +#endif diff --git a/services/credentials/java/com/android/server/credentials/GetCandidateRequestSession.java b/services/credentials/java/com/android/server/credentials/GetCandidateRequestSession.java index dedb687cff22..b1673e2c4c3c 100644 --- a/services/credentials/java/com/android/server/credentials/GetCandidateRequestSession.java +++ b/services/credentials/java/com/android/server/credentials/GetCandidateRequestSession.java @@ -26,6 +26,7 @@ import android.credentials.CredentialManager; import android.credentials.CredentialProviderInfo; import android.credentials.GetCandidateCredentialsException; import android.credentials.GetCandidateCredentialsResponse; +import android.credentials.GetCredentialException; import android.credentials.GetCredentialRequest; import android.credentials.GetCredentialResponse; import android.credentials.IGetCandidateCredentialsCallback; @@ -159,24 +160,26 @@ public class GetCandidateRequestSession extends RequestSession<GetCredentialRequ public void onFinalErrorReceived(ComponentName componentName, String errorType, String message) { Slog.d(TAG, "onFinalErrorReceived"); + if (GetCredentialException.TYPE_USER_CANCELED.equals(errorType)) { + Slog.d(TAG, "User canceled but session is not being terminated"); + return; + } respondToFinalReceiverWithFailureAndFinish(errorType, message); } @Override public void onUiCancellation(boolean isUserCancellation) { - String exception = GetCandidateCredentialsException.TYPE_USER_CANCELED; - String message = "User cancelled the selector"; - if (!isUserCancellation) { - exception = GetCandidateCredentialsException.TYPE_INTERRUPTED; - message = "The UI was interrupted - please try again."; - } - mRequestSessionMetric.collectFrameworkException(exception); - respondToFinalReceiverWithFailureAndFinish(exception, message); + Slog.d(TAG, "User canceled but session is not being terminated"); } private void respondToFinalReceiverWithFailureAndFinish( String exception, String message ) { + if (mRequestSessionStatus == RequestSessionStatus.COMPLETE) { + Slog.w(TAG, "Request has already been completed. This is strange."); + return; + } + if (mAutofillCallback != null) { Bundle resultData = new Bundle(); resultData.putStringArray( @@ -221,6 +224,19 @@ public class GetCandidateRequestSession extends RequestSession<GetCredentialRequ public void onFinalResponseReceived(ComponentName componentName, GetCredentialResponse response) { Slog.d(TAG, "onFinalResponseReceived"); + if (mRequestSessionStatus == RequestSessionStatus.COMPLETE) { + Slog.w(TAG, "Request has already been completed. This is strange."); + return; + } + respondToFinalReceiverWithResponseAndFinish(response); + } + + private void respondToFinalReceiverWithResponseAndFinish(GetCredentialResponse response) { + if (mRequestSessionStatus == RequestSessionStatus.COMPLETE) { + Slog.w(TAG, "Request has already been completed. This is strange."); + return; + } + if (this.mAutofillCallback != null) { Slog.d(TAG, "onFinalResponseReceived sending through final receiver"); Bundle resultData = new Bundle(); diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/OverlayPackagesProvider.java b/services/devicepolicy/java/com/android/server/devicepolicy/OverlayPackagesProvider.java index 94c137444ede..d18301269618 100644 --- a/services/devicepolicy/java/com/android/server/devicepolicy/OverlayPackagesProvider.java +++ b/services/devicepolicy/java/com/android/server/devicepolicy/OverlayPackagesProvider.java @@ -215,6 +215,9 @@ public class OverlayPackagesProvider { } catch (PackageManager.NameNotFoundException e) { return false; } + if (packageInfo.applicationInfo == null || packageInfo.applicationInfo.metaData == null) { + return false; + } final String metadataKey = sActionToMetadataKeyMap.get(provisioningAction); return packageInfo.applicationInfo.metaData.getBoolean(metadataKey); } diff --git a/services/tests/displayservicetests/src/com/android/server/display/DisplayDeviceConfigTest.java b/services/tests/displayservicetests/src/com/android/server/display/DisplayDeviceConfigTest.java index 5897d76663c5..087714681724 100644 --- a/services/tests/displayservicetests/src/com/android/server/display/DisplayDeviceConfigTest.java +++ b/services/tests/displayservicetests/src/com/android/server/display/DisplayDeviceConfigTest.java @@ -920,6 +920,7 @@ public final class DisplayDeviceConfigTest { @Test public void testEvenDimmer() throws IOException { when(mFlags.isEvenDimmerEnabled()).thenReturn(true); + when(mResources.getBoolean(R.bool.config_evenDimmerEnabled)).thenReturn(true); setupDisplayDeviceConfigFromDisplayConfigFile(getContent(getValidLuxThrottling(), getValidProxSensor(), /* includeIdleMode= */ false, /* enableEvenDimmer */ true)); diff --git a/services/tests/displayservicetests/src/com/android/server/display/ExternalDisplayPolicyTest.java b/services/tests/displayservicetests/src/com/android/server/display/ExternalDisplayPolicyTest.java index ea08be4f1be4..fe7bbe0ecf4b 100644 --- a/services/tests/displayservicetests/src/com/android/server/display/ExternalDisplayPolicyTest.java +++ b/services/tests/displayservicetests/src/com/android/server/display/ExternalDisplayPolicyTest.java @@ -157,6 +157,7 @@ public class ExternalDisplayPolicyTest { verify(mMockedLogicalDisplayMapper, never()).setDisplayEnabledLocked(any(), anyBoolean()); verify(mMockedDisplayNotificationManager, times(2)) .onHighTemperatureExternalDisplayNotAllowed(); + verify(mMockedInjector, never()).onExternalDisplayReadyToBeEnabled(anyInt()); } @Test @@ -167,6 +168,7 @@ public class ExternalDisplayPolicyTest { verify(mMockedLogicalDisplayMapper, never()).setDisplayEnabledLocked(any(), anyBoolean()); verify(mMockedDisplayNotificationManager, never()) .onHighTemperatureExternalDisplayNotAllowed(); + verify(mMockedInjector, never()).onExternalDisplayReadyToBeEnabled(anyInt()); } @Test @@ -184,6 +186,7 @@ public class ExternalDisplayPolicyTest { // Expected only 1 invocation, upon critical temperature. verify(mMockedDisplayNotificationManager).onHighTemperatureExternalDisplayNotAllowed(); verify(mMockedExternalDisplayStatsService).onDisplayDisabled(eq(EXTERNAL_DISPLAY_ID)); + verify(mMockedInjector, never()).onExternalDisplayReadyToBeEnabled(anyInt()); } @Test @@ -191,6 +194,7 @@ public class ExternalDisplayPolicyTest { mExternalDisplayPolicy.setExternalDisplayEnabledLocked(mMockedLogicalDisplay, /*enabled=*/ true); assertDisplaySetEnabled(/*enabled=*/ true); + verify(mMockedInjector).onExternalDisplayReadyToBeEnabled(eq(EXTERNAL_DISPLAY_ID)); } @Test diff --git a/services/tests/displayservicetests/src/com/android/server/display/mode/DisplayModeDirectorTest.java b/services/tests/displayservicetests/src/com/android/server/display/mode/DisplayModeDirectorTest.java index 0efd04657033..d670b138e4bc 100644 --- a/services/tests/displayservicetests/src/com/android/server/display/mode/DisplayModeDirectorTest.java +++ b/services/tests/displayservicetests/src/com/android/server/display/mode/DisplayModeDirectorTest.java @@ -1945,7 +1945,7 @@ public class DisplayModeDirectorTest { SparseArray<SparseArray<Vote>> votesByDisplay = new SparseArray<>(); votesByDisplay.put(DISPLAY_ID_2, votes); - director.getDisplayObserver().onDisplayAdded(DISPLAY_ID_2); + director.getDisplayObserver().onExternalDisplayReadyToBeEnabled(DISPLAY_ID_2); director.injectVotesByDisplay(votesByDisplay); var desiredSpecs = director.getDesiredDisplayModeSpecs(DISPLAY_ID_2); diff --git a/services/tests/displayservicetests/src/com/android/server/display/mode/DisplayObserverTest.java b/services/tests/displayservicetests/src/com/android/server/display/mode/DisplayObserverTest.java index d0dd9218eb17..1c192efb3dc1 100644 --- a/services/tests/displayservicetests/src/com/android/server/display/mode/DisplayObserverTest.java +++ b/services/tests/displayservicetests/src/com/android/server/display/mode/DisplayObserverTest.java @@ -38,7 +38,6 @@ import static org.mockito.Mockito.when; import android.content.Context; import android.content.ContextWrapper; import android.content.res.Resources; -import android.hardware.display.DisplayManager; import android.hardware.display.DisplayManagerInternal; import android.os.Handler; import android.os.Looper; @@ -109,7 +108,7 @@ public class DisplayObserverTest { private Context mContext; private DisplayModeDirector.Injector mInjector; private Handler mHandler; - private DisplayManager.DisplayListener mObserver; + private DisplayModeDirector.DisplayObserver mObserver; private Resources mResources; @Mock private DisplayManagerFlags mDisplayManagerFlags; @@ -161,6 +160,7 @@ public class DisplayObserverTest { .isEqualTo(null); // Testing that the vote is not added when display is added because feature is disabled + mObserver.onExternalDisplayReadyToBeEnabled(EXTERNAL_DISPLAY); mObserver.onDisplayAdded(EXTERNAL_DISPLAY); assertThat(getVote(DEFAULT_DISPLAY, PRIORITY_USER_SETTING_DISPLAY_PREFERRED_SIZE)) .isEqualTo(null); @@ -194,6 +194,7 @@ public class DisplayObserverTest { init(); assertThat(getVote(EXTERNAL_DISPLAY, PRIORITY_USER_SETTING_DISPLAY_PREFERRED_SIZE)) .isEqualTo(null); + mObserver.onExternalDisplayReadyToBeEnabled(EXTERNAL_DISPLAY); mObserver.onDisplayAdded(EXTERNAL_DISPLAY); assertThat(getVote(DEFAULT_DISPLAY, PRIORITY_USER_SETTING_DISPLAY_PREFERRED_SIZE)) .isEqualTo(null); @@ -245,6 +246,7 @@ public class DisplayObserverTest { init(); assertThat(getVote(EXTERNAL_DISPLAY, PRIORITY_USER_SETTING_DISPLAY_PREFERRED_SIZE)) .isEqualTo(null); + mObserver.onExternalDisplayReadyToBeEnabled(EXTERNAL_DISPLAY); mObserver.onDisplayAdded(EXTERNAL_DISPLAY); assertThat(getVote(DEFAULT_DISPLAY, PRIORITY_USER_SETTING_DISPLAY_PREFERRED_SIZE)) .isEqualTo(null); @@ -277,6 +279,7 @@ public class DisplayObserverTest { init(); assertThat(getVote(DEFAULT_DISPLAY, PRIORITY_USER_SETTING_DISPLAY_PREFERRED_SIZE)) .isEqualTo(null); + mObserver.onExternalDisplayReadyToBeEnabled(DEFAULT_DISPLAY); mObserver.onDisplayAdded(DEFAULT_DISPLAY); assertThat(getVote(DEFAULT_DISPLAY, PRIORITY_USER_SETTING_DISPLAY_PREFERRED_SIZE)) .isEqualTo(expectedResolutionVote); @@ -298,6 +301,7 @@ public class DisplayObserverTest { .thenReturn(MAX_HEIGHT); init(); assertThat(getVote(DEFAULT_DISPLAY, PRIORITY_LIMIT_MODE)).isEqualTo(null); + mObserver.onExternalDisplayReadyToBeEnabled(DEFAULT_DISPLAY); mObserver.onDisplayAdded(DEFAULT_DISPLAY); assertThat(getVote(DEFAULT_DISPLAY, PRIORITY_LIMIT_MODE)).isEqualTo(null); assertThat(getVote(EXTERNAL_DISPLAY, PRIORITY_LIMIT_MODE)).isEqualTo(null); @@ -317,6 +321,7 @@ public class DisplayObserverTest { .thenReturn(MAX_HEIGHT); init(); assertThat(getVote(EXTERNAL_DISPLAY, PRIORITY_LIMIT_MODE)).isEqualTo(null); + mObserver.onExternalDisplayReadyToBeEnabled(EXTERNAL_DISPLAY); mObserver.onDisplayAdded(EXTERNAL_DISPLAY); assertThat(getVote(DEFAULT_DISPLAY, PRIORITY_LIMIT_MODE)).isEqualTo(null); assertThat(getVote(EXTERNAL_DISPLAY, PRIORITY_LIMIT_MODE)).isEqualTo( @@ -336,6 +341,7 @@ public class DisplayObserverTest { when(mDisplayManagerFlags.isExternalDisplayLimitModeEnabled()).thenReturn(true); init(); assertThat(getVote(EXTERNAL_DISPLAY, PRIORITY_LIMIT_MODE)).isEqualTo(null); + mObserver.onExternalDisplayReadyToBeEnabled(EXTERNAL_DISPLAY); mObserver.onDisplayAdded(EXTERNAL_DISPLAY); assertThat(getVote(DEFAULT_DISPLAY, PRIORITY_LIMIT_MODE)).isEqualTo(null); assertThat(getVote(EXTERNAL_DISPLAY, PRIORITY_LIMIT_MODE)).isEqualTo(null); @@ -358,6 +364,7 @@ public class DisplayObserverTest { .thenReturn(true); init(); assertThat(getVote(GLOBAL_ID, PRIORITY_SYNCHRONIZED_REFRESH_RATE)).isEqualTo(null); + mObserver.onExternalDisplayReadyToBeEnabled(EXTERNAL_DISPLAY); mObserver.onDisplayAdded(EXTERNAL_DISPLAY); assertThat(getVote(GLOBAL_ID, PRIORITY_SYNCHRONIZED_REFRESH_RATE)).isEqualTo( Vote.forPhysicalRefreshRates( @@ -381,6 +388,7 @@ public class DisplayObserverTest { .thenReturn(true); init(); assertThat(getVote(GLOBAL_ID, PRIORITY_SYNCHRONIZED_REFRESH_RATE)).isEqualTo(null); + mObserver.onExternalDisplayReadyToBeEnabled(EXTERNAL_DISPLAY); mObserver.onDisplayAdded(EXTERNAL_DISPLAY); assertThat(getVote(GLOBAL_ID, PRIORITY_SYNCHRONIZED_REFRESH_RATE)).isEqualTo(null); } @@ -395,6 +403,7 @@ public class DisplayObserverTest { when(mDisplayManagerFlags.isDisplaysRefreshRatesSynchronizationEnabled()).thenReturn(true); init(); assertThat(getVote(GLOBAL_ID, PRIORITY_SYNCHRONIZED_REFRESH_RATE)).isEqualTo(null); + mObserver.onExternalDisplayReadyToBeEnabled(EXTERNAL_DISPLAY); mObserver.onDisplayAdded(EXTERNAL_DISPLAY); assertThat(getVote(GLOBAL_ID, PRIORITY_SYNCHRONIZED_REFRESH_RATE)).isEqualTo(null); } diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationChannelExtractorTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationChannelExtractorTest.java index ad25d76e2db7..770712a191fd 100644 --- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationChannelExtractorTest.java +++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationChannelExtractorTest.java @@ -26,14 +26,18 @@ import static android.media.AudioAttributes.USAGE_NOTIFICATION; import static android.media.AudioAttributes.USAGE_NOTIFICATION_RINGTONE; import static android.media.AudioAttributes.USAGE_UNKNOWN; import static android.platform.test.flag.junit.SetFlagsRule.DefaultInitValueType.DEVICE_DEFAULT; +import static com.android.server.notification.NotificationChannelExtractor.RESTRICT_AUDIO_ATTRIBUTES; import static com.google.common.truth.Truth.assertThat; import static junit.framework.Assert.assertEquals; import static junit.framework.Assert.assertNull; +import static org.mockito.ArgumentMatchers.anyLong; import static org.mockito.Matchers.any; import static org.mockito.Matchers.anyInt; import static org.mockito.Matchers.eq; import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import android.app.Flags; @@ -43,12 +47,14 @@ import android.app.PendingIntent; import android.app.Person; import android.media.AudioAttributes; import android.net.Uri; +import android.os.RemoteException; import android.os.UserHandle; import android.platform.test.annotations.EnableFlags; import android.platform.test.flag.junit.SetFlagsRule; import android.provider.Settings; import android.service.notification.StatusBarNotification; +import com.android.internal.compat.IPlatformCompat; import com.android.server.UiServiceTestCase; import org.junit.Before; @@ -60,6 +66,8 @@ import org.mockito.MockitoAnnotations; public class NotificationChannelExtractorTest extends UiServiceTestCase { @Mock RankingConfig mConfig; + @Mock + IPlatformCompat mPlatformCompat; @Rule public final SetFlagsRule mSetFlagsRule = new SetFlagsRule(DEVICE_DEFAULT); @@ -73,6 +81,7 @@ public class NotificationChannelExtractorTest extends UiServiceTestCase { mExtractor = new NotificationChannelExtractor(); mExtractor.setConfig(mConfig); mExtractor.initialize(mContext, null); + mExtractor.setCompatChangeLogger(mPlatformCompat); } private NotificationRecord getRecord(NotificationChannel channel, Notification n) { @@ -82,7 +91,7 @@ public class NotificationChannelExtractorTest extends UiServiceTestCase { } @Test - public void testExtractsUpdatedConversationChannel() { + public void testExtractsUpdatedConversationChannel() throws RemoteException { NotificationChannel channel = new NotificationChannel("a", "a", IMPORTANCE_LOW); final Notification n = new Notification.Builder(getContext()) .setContentTitle("foo") @@ -101,7 +110,7 @@ public class NotificationChannelExtractorTest extends UiServiceTestCase { } @Test - public void testInvalidShortcutFlagEnabled_looksUpCorrectNonChannel() { + public void testInvalidShortcutFlagEnabled_looksUpCorrectNonChannel() throws RemoteException { NotificationChannel channel = new NotificationChannel("a", "a", IMPORTANCE_LOW); final Notification n = new Notification.Builder(getContext()) .setContentTitle("foo") @@ -122,7 +131,7 @@ public class NotificationChannelExtractorTest extends UiServiceTestCase { } @Test - public void testInvalidShortcutFlagDisabled_looksUpCorrectChannel() { + public void testInvalidShortcutFlagDisabled_looksUpCorrectChannel() throws RemoteException { NotificationChannel channel = new NotificationChannel("a", "a", IMPORTANCE_LOW); final Notification n = new Notification.Builder(getContext()) .setContentTitle("foo") @@ -143,7 +152,7 @@ public class NotificationChannelExtractorTest extends UiServiceTestCase { @Test @EnableFlags(Flags.FLAG_RESTRICT_AUDIO_ATTRIBUTES_CALL) - public void testAudioAttributes_callStyleCanUseCallUsage() { + public void testAudioAttributes_callStyleCanUseCallUsage() throws RemoteException { NotificationChannel channel = new NotificationChannel("a", "a", IMPORTANCE_HIGH); channel.setSound(Uri.EMPTY, new AudioAttributes.Builder() .setUsage(USAGE_NOTIFICATION_RINGTONE) @@ -162,11 +171,12 @@ public class NotificationChannelExtractorTest extends UiServiceTestCase { assertThat(mExtractor.process(r)).isNull(); assertThat(r.getAudioAttributes().getUsage()).isEqualTo(USAGE_NOTIFICATION_RINGTONE); assertThat(r.getChannel()).isEqualTo(channel); + verify(mPlatformCompat, never()).reportChangeByUid(anyLong(), anyInt()); } @Test @EnableFlags(Flags.FLAG_RESTRICT_AUDIO_ATTRIBUTES_CALL) - public void testAudioAttributes_nonCallStyleCannotUseCallUsage() { + public void testAudioAttributes_nonCallStyleCannotUseCallUsage() throws RemoteException { NotificationChannel channel = new NotificationChannel("a", "a", IMPORTANCE_HIGH); channel.setSound(Uri.EMPTY, new AudioAttributes.Builder() .setUsage(USAGE_NOTIFICATION_RINGTONE) @@ -180,13 +190,14 @@ public class NotificationChannelExtractorTest extends UiServiceTestCase { assertThat(mExtractor.process(r)).isNull(); // instance updated assertThat(r.getAudioAttributes().getUsage()).isEqualTo(USAGE_NOTIFICATION); + verify(mPlatformCompat).reportChangeByUid(RESTRICT_AUDIO_ATTRIBUTES, r.getUid()); // in-memory channel unchanged assertThat(channel.getAudioAttributes().getUsage()).isEqualTo(USAGE_NOTIFICATION_RINGTONE); } @Test @EnableFlags(Flags.FLAG_RESTRICT_AUDIO_ATTRIBUTES_ALARM) - public void testAudioAttributes_alarmCategoryCanUseAlarmUsage() { + public void testAudioAttributes_alarmCategoryCanUseAlarmUsage() throws RemoteException { NotificationChannel channel = new NotificationChannel("a", "a", IMPORTANCE_HIGH); channel.setSound(Uri.EMPTY, new AudioAttributes.Builder() .setUsage(USAGE_ALARM) @@ -201,11 +212,12 @@ public class NotificationChannelExtractorTest extends UiServiceTestCase { assertThat(mExtractor.process(r)).isNull(); assertThat(r.getAudioAttributes().getUsage()).isEqualTo(USAGE_ALARM); assertThat(r.getChannel()).isEqualTo(channel); + verify(mPlatformCompat, never()).reportChangeByUid(anyLong(), anyInt()); } @Test @EnableFlags(Flags.FLAG_RESTRICT_AUDIO_ATTRIBUTES_ALARM) - public void testAudioAttributes_nonAlarmCategoryCannotUseAlarmUsage() { + public void testAudioAttributes_nonAlarmCategoryCannotUseAlarmUsage() throws RemoteException { NotificationChannel channel = new NotificationChannel("a", "a", IMPORTANCE_HIGH); channel.setSound(Uri.EMPTY, new AudioAttributes.Builder() .setUsage(USAGE_ALARM) @@ -219,13 +231,14 @@ public class NotificationChannelExtractorTest extends UiServiceTestCase { assertThat(mExtractor.process(r)).isNull(); // instance updated assertThat(r.getAudioAttributes().getUsage()).isEqualTo(USAGE_NOTIFICATION); + verify(mPlatformCompat).reportChangeByUid(RESTRICT_AUDIO_ATTRIBUTES, r.getUid()); // in-memory channel unchanged assertThat(channel.getAudioAttributes().getUsage()).isEqualTo(USAGE_ALARM); } @Test @EnableFlags(Flags.FLAG_RESTRICT_AUDIO_ATTRIBUTES_MEDIA) - public void testAudioAttributes_noMediaUsage() { + public void testAudioAttributes_noMediaUsage() throws RemoteException { NotificationChannel channel = new NotificationChannel("a", "a", IMPORTANCE_HIGH); channel.setSound(Uri.EMPTY, new AudioAttributes.Builder() .setUsage(USAGE_MEDIA) @@ -239,13 +252,14 @@ public class NotificationChannelExtractorTest extends UiServiceTestCase { assertThat(mExtractor.process(r)).isNull(); // instance updated assertThat(r.getAudioAttributes().getUsage()).isEqualTo(USAGE_NOTIFICATION); + verify(mPlatformCompat).reportChangeByUid(RESTRICT_AUDIO_ATTRIBUTES, r.getUid()); // in-memory channel unchanged assertThat(channel.getAudioAttributes().getUsage()).isEqualTo(USAGE_MEDIA); } @Test @EnableFlags(Flags.FLAG_RESTRICT_AUDIO_ATTRIBUTES_MEDIA) - public void testAudioAttributes_noUnknownUsage() { + public void testAudioAttributes_noUnknownUsage() throws RemoteException { NotificationChannel channel = new NotificationChannel("a", "a", IMPORTANCE_HIGH); channel.setSound(Uri.EMPTY, new AudioAttributes.Builder() .setUsage(USAGE_UNKNOWN) @@ -259,6 +273,7 @@ public class NotificationChannelExtractorTest extends UiServiceTestCase { assertThat(mExtractor.process(r)).isNull(); // instance updated assertThat(r.getAudioAttributes().getUsage()).isEqualTo(USAGE_NOTIFICATION); + verify(mPlatformCompat).reportChangeByUid(RESTRICT_AUDIO_ATTRIBUTES, r.getUid()); // in-memory channel unchanged assertThat(channel.getAudioAttributes().getUsage()).isEqualTo(USAGE_UNKNOWN); } diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java index 805bc172035e..011f2e39d6f8 100755 --- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java +++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java @@ -200,6 +200,7 @@ import android.content.pm.ActivityInfo; import android.content.pm.ApplicationInfo; import android.content.pm.IPackageManager; import android.content.pm.LauncherApps; +import android.content.pm.ModuleInfo; import android.content.pm.PackageManager; import android.content.pm.PackageManagerInternal; import android.content.pm.ParceledListSlice; @@ -450,6 +451,9 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { private static final String VALID_CONVO_SHORTCUT_ID = "shortcut"; private static final String SEARCH_SELECTOR_PKG = "searchSelector"; + private static final String ADSERVICES_MODULE_PKG = "com.android.adservices"; + private static final String ADSERVICES_APK_PKG = "com.android.adservices.api"; + @Mock private NotificationListeners mListeners; @Mock @@ -740,7 +744,11 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { // Return first true for RoleObserver main-thread check when(mMainLooper.isCurrentThread()).thenReturn(true).thenReturn(false); - + ModuleInfo moduleInfo = new ModuleInfo(); + moduleInfo.setApexModuleName(ADSERVICES_MODULE_PKG); + moduleInfo.setApkInApexPackageNames(List.of(ADSERVICES_APK_PKG)); + when(mPackageManagerClient.getInstalledModules(anyInt())) + .thenReturn(List.of(moduleInfo)); if (upToBootPhase >= SystemService.PHASE_SYSTEM_SERVICES_READY) { mService.onBootPhase(SystemService.PHASE_SYSTEM_SERVICES_READY, mMainLooper); } @@ -13218,6 +13226,33 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { } @Test + public void fixSystemNotification_defaultAdservices_withOnGoingFlag_nondismissible() + throws Exception { + final ApplicationInfo ai = new ApplicationInfo(); + ai.packageName = ADSERVICES_APK_PKG; + ai.uid = mUid; + ai.flags |= ApplicationInfo.FLAG_SYSTEM; + + when(mPackageManagerClient.getApplicationInfoAsUser(anyString(), anyInt(), anyInt())) + .thenReturn(ai); + when(mAppOpsManager.checkOpNoThrow( + AppOpsManager.OP_SYSTEM_EXEMPT_FROM_DISMISSIBLE_NOTIFICATIONS, ai.uid, + ai.packageName)).thenReturn(AppOpsManager.MODE_IGNORED); + // Given: a notification from an app on the system partition has the flag + // FLAG_ONGOING_EVENT set + Notification n = new Notification.Builder(mContext, "test") + .setOngoing(true) + .build(); + + // When: fix the notification with NotificationManagerService + mService.fixNotification(n, ADSERVICES_APK_PKG, "tag", 9, 0, mUid, NOT_FOREGROUND_SERVICE, + true); + + // Then: the notification's flag FLAG_NO_DISMISS should be set + assertNotSame(0, n.flags & Notification.FLAG_NO_DISMISS); + } + + @Test public void fixCallNotification_withOnGoingFlag_shouldNotBeNonDismissible() throws Exception { // Given: a call notification has the flag FLAG_ONGOING_EVENT set diff --git a/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java b/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java index aeeca2ae86f5..5033a380fa4d 100644 --- a/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java +++ b/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java @@ -3981,7 +3981,7 @@ public class PreferencesHelperTest extends UiServiceTestCase { pm.applicationInfo = new ApplicationInfo(); pm.applicationInfo.uid = UID_O; List<PackageInfo> packages = ImmutableList.of(pm); - when(mPm.getInstalledPackagesAsUser(any(), anyInt())).thenReturn(packages); + when(mPm.getInstalledPackagesAsUser(eq(0), anyInt())).thenReturn(packages); mHelper.updateFixedImportance(users); assertTrue(mHelper.isImportanceLocked(PKG_O, UID_O)); @@ -4097,7 +4097,7 @@ public class PreferencesHelperTest extends UiServiceTestCase { pm.applicationInfo = new ApplicationInfo(); pm.applicationInfo.uid = UID_O; List<PackageInfo> packages = ImmutableList.of(pm); - when(mPm.getInstalledPackagesAsUser(any(), eq(0))).thenReturn(packages); + when(mPm.getInstalledPackagesAsUser(0, 0)).thenReturn(packages); mHelper.updateFixedImportance(users); assertTrue(mHelper.getNotificationChannel(PKG_O, UID_O, a.getId(), false) @@ -4120,7 +4120,7 @@ public class PreferencesHelperTest extends UiServiceTestCase { pm.applicationInfo = new ApplicationInfo(); pm.applicationInfo.uid = UID_O; List<PackageInfo> packages = ImmutableList.of(pm); - when(mPm.getInstalledPackagesAsUser(any(), eq(0))).thenReturn(packages); + when(mPm.getInstalledPackagesAsUser(0, 0)).thenReturn(packages); mHelper.updateFixedImportance(users); NotificationChannel a = new NotificationChannel("a", "a", IMPORTANCE_HIGH); @@ -4309,7 +4309,7 @@ public class PreferencesHelperTest extends UiServiceTestCase { pm.applicationInfo = new ApplicationInfo(); pm.applicationInfo.uid = UID_O; List<PackageInfo> packages = ImmutableList.of(pm); - when(mPm.getInstalledPackagesAsUser(any(), eq(0))).thenReturn(packages); + when(mPm.getInstalledPackagesAsUser(0, 0)).thenReturn(packages); mHelper.updateFixedImportance(users); ArraySet<String> toRemove = new ArraySet<>(); @@ -4341,7 +4341,7 @@ public class PreferencesHelperTest extends UiServiceTestCase { pm.applicationInfo = new ApplicationInfo(); pm.applicationInfo.uid = UID_O; List<PackageInfo> packages = ImmutableList.of(pm); - when(mPm.getInstalledPackagesAsUser(any(), eq(0))).thenReturn(packages); + when(mPm.getInstalledPackagesAsUser(0, 0)).thenReturn(packages); mHelper.updateFixedImportance(users); assertTrue(mHelper.isImportanceLocked(PKG_O, UID_O)); diff --git a/services/tests/uiservicestests/src/com/android/server/notification/RankingHelperTest.java b/services/tests/uiservicestests/src/com/android/server/notification/RankingHelperTest.java index ad420f6bf502..527001df995f 100644 --- a/services/tests/uiservicestests/src/com/android/server/notification/RankingHelperTest.java +++ b/services/tests/uiservicestests/src/com/android/server/notification/RankingHelperTest.java @@ -55,6 +55,7 @@ import androidx.test.InstrumentationRegistry; import androidx.test.filters.SmallTest; import androidx.test.runner.AndroidJUnit4; +import com.android.internal.compat.IPlatformCompat; import com.android.server.UiServiceTestCase; import org.junit.Before; @@ -155,7 +156,8 @@ public class RankingHelperTest extends UiServiceTestCase { NotificationManager.Policy.STATE_CHANNELS_BYPASSING_DND, 0); when(mMockZenModeHelper.getNotificationPolicy()).thenReturn(mTestNotificationPolicy); mHelper = new RankingHelper(getContext(), mHandler, mConfig, mMockZenModeHelper, - mUsageStats, new String[] {ImportanceExtractor.class.getName()}); + mUsageStats, new String[] {ImportanceExtractor.class.getName()}, + mock(IPlatformCompat.class)); mNotiGroupGSortA = new Notification.Builder(mContext, TEST_CHANNEL_ID) .setContentTitle("A") diff --git a/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java b/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java index 2c88ed2db2d6..7356b4376e8a 100644 --- a/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java @@ -1717,6 +1717,7 @@ public class DisplayContentTests extends WindowTestsBase { // The display should be rotated after the launch is finished. doReturn(false).when(app).isAnimating(anyInt(), anyInt()); mDisplayContent.mAppTransition.notifyAppTransitionFinishedLocked(app.token); + waitHandlerIdle(mWm.mH); mStatusBarWindow.finishSeamlessRotation(t); mNavBarWindow.finishSeamlessRotation(t); diff --git a/telephony/java/android/telephony/euicc/EuiccManager.java b/telephony/java/android/telephony/euicc/EuiccManager.java index ebabbf911399..ca4a643d7b20 100644 --- a/telephony/java/android/telephony/euicc/EuiccManager.java +++ b/telephony/java/android/telephony/euicc/EuiccManager.java @@ -1039,17 +1039,18 @@ public class EuiccManager { * subscription on the * current eUICC and the subscription to be downloaded according to the subscription metadata. * Without the former, an {@link #EMBEDDED_SUBSCRIPTION_RESULT_RESOLVABLE_ERROR} will be - * eturned in the callback intent to prompt the user to accept the download. + * returned in the callback intent to prompt the user to accept the download. * * <p> Starting from Android {@link android.os.Build.VERSION_CODES#VANILLA_ICE_CREAM}, * if the caller has the * {@code android.Manifest.permission#MANAGE_DEVICE_POLICY_MANAGED_SUBSCRIPTIONS} permission or - * is a profile owner or device owner, and - * {@code switchAfterDownload} is {@code false}, then the downloaded subscription - * will be managed by that caller. If {@code switchAfterDownload} is true, - * an {@link #EMBEDDED_SUBSCRIPTION_RESULT_RESOLVABLE_ERROR} will be - * returned in the callback intent to prompt the user to accept the download and the - * subscription will not be managed. + * is a profile owner or device owner, then the downloaded subscription + * will be managed by that caller. + * In case the caller is device owner or profile owner of an organization-owned device, {@code + * switchAfterDownload} can be set to true to automatically enable the subscription after + * download. If the caller is a profile owner on non organization owned device + * {@code switchAfterDownload} should be false otherwise the operation will fail with + * {@link #EMBEDDED_SUBSCRIPTION_RESULT_ERROR}. * * <p>On a multi-active SIM device, requires the * {@code android.Manifest.permission#WRITE_EMBEDDED_SUBSCRIPTIONS} permission, or a calling app diff --git a/tests/vcn/java/com/android/server/vcn/routeselection/IpSecPacketLossDetectorTest.java b/tests/vcn/java/com/android/server/vcn/routeselection/IpSecPacketLossDetectorTest.java index 0a83a53a7b59..c8b60e5c335f 100644 --- a/tests/vcn/java/com/android/server/vcn/routeselection/IpSecPacketLossDetectorTest.java +++ b/tests/vcn/java/com/android/server/vcn/routeselection/IpSecPacketLossDetectorTest.java @@ -17,8 +17,11 @@ package com.android.server.vcn.routeselection; import static android.net.vcn.VcnManager.VCN_NETWORK_SELECTION_IPSEC_PACKET_LOSS_PERCENT_THRESHOLD_KEY; +import static android.net.vcn.VcnManager.VCN_NETWORK_SELECTION_MAX_SEQ_NUM_INCREASE_PER_SECOND_KEY; import static android.net.vcn.VcnManager.VCN_NETWORK_SELECTION_POLL_IPSEC_STATE_INTERVAL_SECONDS_KEY; +import static com.android.server.vcn.routeselection.IpSecPacketLossDetector.MIN_VALID_EXPECTED_RX_PACKET_NUM; +import static com.android.server.vcn.routeselection.IpSecPacketLossDetector.getMaxSeqNumIncreasePerSecond; import static com.android.server.vcn.util.PersistableBundleUtils.PersistableBundleWrapper; import static org.junit.Assert.assertEquals; @@ -65,6 +68,7 @@ public class IpSecPacketLossDetectorTest extends NetworkEvaluationTestBase { private static final int REPLAY_BITMAP_LEN_BYTE = 512; private static final int REPLAY_BITMAP_LEN_BIT = REPLAY_BITMAP_LEN_BYTE * 8; private static final int IPSEC_PACKET_LOSS_PERCENT_THRESHOLD = 5; + private static final int MAX_SEQ_NUM_INCREASE_DEFAULT_DISABLED = -1; private static final long POLL_IPSEC_STATE_INTERVAL_MS = TimeUnit.SECONDS.toMillis(30L); @Mock private IpSecTransformWrapper mIpSecTransform; @@ -91,6 +95,9 @@ public class IpSecPacketLossDetectorTest extends NetworkEvaluationTestBase { eq(VCN_NETWORK_SELECTION_IPSEC_PACKET_LOSS_PERCENT_THRESHOLD_KEY), anyInt())) .thenReturn(IPSEC_PACKET_LOSS_PERCENT_THRESHOLD); + when(mCarrierConfig.getInt( + eq(VCN_NETWORK_SELECTION_MAX_SEQ_NUM_INCREASE_PER_SECOND_KEY), anyInt())) + .thenReturn(MAX_SEQ_NUM_INCREASE_DEFAULT_DISABLED); when(mDependencies.getPacketLossCalculator()).thenReturn(mPacketLossCalculator); @@ -112,6 +119,20 @@ public class IpSecPacketLossDetectorTest extends NetworkEvaluationTestBase { .build(); } + private static IpSecTransformState newNextTransformState( + IpSecTransformState before, + long timeDiffMillis, + long rxSeqNoDiff, + long packtCountDiff, + int packetInWin) { + return new IpSecTransformState.Builder() + .setTimestampMillis(before.getTimestampMillis() + timeDiffMillis) + .setRxHighestSequenceNumber(before.getRxHighestSequenceNumber() + rxSeqNoDiff) + .setPacketCount(before.getPacketCount() + packtCountDiff) + .setReplayBitmap(newReplayBitmap(packetInWin)) + .build(); + } + private static byte[] newReplayBitmap(int receivedPktCnt) { final BitSet bitSet = new BitSet(REPLAY_BITMAP_LEN_BIT); for (int i = 0; i < receivedPktCnt; i++) { @@ -165,7 +186,7 @@ public class IpSecPacketLossDetectorTest extends NetworkEvaluationTestBase { // Verify the first polled state is stored assertEquals(mTransformStateInitial, mIpSecPacketLossDetector.getLastTransformState()); verify(mPacketLossCalculator, never()) - .getPacketLossRatePercentage(any(), any(), anyString()); + .getPacketLossRatePercentage(any(), any(), anyInt(), anyString()); // Verify next poll is scheduled assertNull(mTestLooper.nextMessage()); @@ -278,7 +299,7 @@ public class IpSecPacketLossDetectorTest extends NetworkEvaluationTestBase { xfrmStateReceiver.onResult(newTransformState(1, 1, newReplayBitmap(1))); verify(mPacketLossCalculator, never()) - .getPacketLossRatePercentage(any(), any(), anyString()); + .getPacketLossRatePercentage(any(), any(), anyInt(), anyString()); } @Test @@ -289,7 +310,7 @@ public class IpSecPacketLossDetectorTest extends NetworkEvaluationTestBase { xfrmStateReceiver.onError(new RuntimeException("Test")); verify(mPacketLossCalculator, never()) - .getPacketLossRatePercentage(any(), any(), anyString()); + .getPacketLossRatePercentage(any(), any(), anyInt(), anyString()); } private void checkHandleLossRate( @@ -301,7 +322,7 @@ public class IpSecPacketLossDetectorTest extends NetworkEvaluationTestBase { startMonitorAndCaptureStateReceiver(); doReturn(mockPacketLossRate) .when(mPacketLossCalculator) - .getPacketLossRatePercentage(any(), any(), anyString()); + .getPacketLossRatePercentage(any(), any(), anyInt(), anyString()); // Mock receiving two states with mTransformStateInitial and an arbitrary transformNew final IpSecTransformState transformNew = newTransformState(1, 1, newReplayBitmap(1)); @@ -311,7 +332,10 @@ public class IpSecPacketLossDetectorTest extends NetworkEvaluationTestBase { // Verifications verify(mPacketLossCalculator) .getPacketLossRatePercentage( - eq(mTransformStateInitial), eq(transformNew), anyString()); + eq(mTransformStateInitial), + eq(transformNew), + eq(MAX_SEQ_NUM_INCREASE_DEFAULT_DISABLED), + anyString()); if (isLastStateExpectedToUpdate) { assertEquals(transformNew, mIpSecPacketLossDetector.getLastTransformState()); @@ -351,6 +375,22 @@ public class IpSecPacketLossDetectorTest extends NetworkEvaluationTestBase { false /* isCallbackExpected */); } + @Test + public void testHandleLossRate_unusualSeqNumLeap_highLossRate() throws Exception { + checkHandleLossRate( + PacketLossCalculationResult.unusualSeqNumLeap(22), + true /* isLastStateExpectedToUpdate */, + false /* isCallbackExpected */); + } + + @Test + public void testHandleLossRate_unusualSeqNumLeap_lowLossRate() throws Exception { + checkHandleLossRate( + PacketLossCalculationResult.unusualSeqNumLeap(2), + true /* isLastStateExpectedToUpdate */, + true /* isCallbackExpected */); + } + private void checkGetPacketLossRate( IpSecTransformState oldState, IpSecTransformState newState, @@ -358,7 +398,8 @@ public class IpSecPacketLossDetectorTest extends NetworkEvaluationTestBase { throws Exception { assertEquals( expectedLossRate, - mPacketLossCalculator.getPacketLossRatePercentage(oldState, newState, TAG)); + mPacketLossCalculator.getPacketLossRatePercentage( + oldState, newState, MAX_SEQ_NUM_INCREASE_DEFAULT_DISABLED, TAG)); } private void checkGetPacketLossRate( @@ -397,6 +438,21 @@ public class IpSecPacketLossDetectorTest extends NetworkEvaluationTestBase { } @Test + public void testGetPacketLossRate_expectedPacketNumTooFew() throws Exception { + final int oldRxNo = 4096; + final int oldPktCnt = 4096; + final int pktCntDiff = MIN_VALID_EXPECTED_RX_PACKET_NUM - 1; + final byte[] bitmapReceiveAll = newReplayBitmap(4096); + + final IpSecTransformState oldState = + newTransformState(oldRxNo, oldPktCnt, bitmapReceiveAll); + final IpSecTransformState newState = + newTransformState(oldRxNo + pktCntDiff, oldPktCnt + pktCntDiff, bitmapReceiveAll); + + checkGetPacketLossRate(oldState, newState, PacketLossCalculationResult.invalid()); + } + + @Test public void testGetPacketLossRate_againstInitialState() throws Exception { checkGetPacketLossRate(mTransformStateInitial, 7000, 7001, 4096, 0); checkGetPacketLossRate(mTransformStateInitial, 7000, 6000, 4096, 15); @@ -443,6 +499,45 @@ public class IpSecPacketLossDetectorTest extends NetworkEvaluationTestBase { checkGetPacketLossRate(oldState, 20000, 14000, 3000, 10); } + private void checkGetPktLossRate_unusualSeqNumLeap( + int maxSeqNumIncreasePerSecond, + int timeDiffMillis, + int rxSeqNoDiff, + PacketLossCalculationResult expected) + throws Exception { + final IpSecTransformState oldState = mTransformStateInitial; + final IpSecTransformState newState = + newNextTransformState( + oldState, + timeDiffMillis, + rxSeqNoDiff, + 1 /* packtCountDiff */, + 1 /* packetInWin */); + + assertEquals( + expected, + mPacketLossCalculator.getPacketLossRatePercentage( + oldState, newState, maxSeqNumIncreasePerSecond, TAG)); + } + + @Test + public void testGetPktLossRate_unusualSeqNumLeap() throws Exception { + checkGetPktLossRate_unusualSeqNumLeap( + 10000 /* maxSeqNumIncreasePerSecond */, + (int) TimeUnit.SECONDS.toMillis(2L), + 30000 /* rxSeqNoDiff */, + PacketLossCalculationResult.unusualSeqNumLeap(100)); + } + + @Test + public void testGetPktLossRate_unusualSeqNumLeap_smallSeqNumDiff() throws Exception { + checkGetPktLossRate_unusualSeqNumLeap( + 10000 /* maxSeqNumIncreasePerSecond */, + (int) TimeUnit.SECONDS.toMillis(2L), + 5000 /* rxSeqNoDiff */, + PacketLossCalculationResult.valid(100)); + } + // Verify the polling event is scheduled with expected delays private void verifyPollEventDelayAndScheduleNext(long expectedDelayMs) { if (expectedDelayMs > 0) { @@ -469,4 +564,24 @@ public class IpSecPacketLossDetectorTest extends NetworkEvaluationTestBase { // Verify the 3rd poll is scheduled with configured delay verifyPollEventDelayAndScheduleNext(POLL_IPSEC_STATE_INTERVAL_MS); } + + @Test + public void testGetMaxSeqNumIncreasePerSecond() throws Exception { + final int seqNumLeapNegative = 500_000; + when(mCarrierConfig.getInt( + eq(VCN_NETWORK_SELECTION_MAX_SEQ_NUM_INCREASE_PER_SECOND_KEY), anyInt())) + .thenReturn(seqNumLeapNegative); + assertEquals(seqNumLeapNegative, getMaxSeqNumIncreasePerSecond(mCarrierConfig)); + } + + @Test + public void testGetMaxSeqNumIncreasePerSecond_negativeValue() throws Exception { + final int seqNumLeapNegative = -10; + when(mCarrierConfig.getInt( + eq(VCN_NETWORK_SELECTION_MAX_SEQ_NUM_INCREASE_PER_SECOND_KEY), anyInt())) + .thenReturn(seqNumLeapNegative); + assertEquals( + MAX_SEQ_NUM_INCREASE_DEFAULT_DISABLED, + getMaxSeqNumIncreasePerSecond(mCarrierConfig)); + } } diff --git a/tests/vcn/java/com/android/server/vcn/routeselection/NetworkEvaluationTestBase.java b/tests/vcn/java/com/android/server/vcn/routeselection/NetworkEvaluationTestBase.java index af6daa17e223..6189fb0834d8 100644 --- a/tests/vcn/java/com/android/server/vcn/routeselection/NetworkEvaluationTestBase.java +++ b/tests/vcn/java/com/android/server/vcn/routeselection/NetworkEvaluationTestBase.java @@ -123,6 +123,7 @@ public abstract class NetworkEvaluationTestBase { mSetFlagsRule.enableFlags(Flags.FLAG_VALIDATE_NETWORK_ON_IPSEC_LOSS); mSetFlagsRule.enableFlags(Flags.FLAG_EVALUATE_IPSEC_LOSS_ON_LP_NC_CHANGE); + mSetFlagsRule.enableFlags(Flags.FLAG_HANDLE_SEQ_NUM_LEAP); when(mNetwork.getNetId()).thenReturn(-1); |