Merge "Rework VolumePanel clicks modifiers" into main
diff --git a/apct-tests/perftests/packagemanager/AndroidTest.xml b/apct-tests/perftests/packagemanager/AndroidTest.xml
index c9d45a6..db938e4 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 c189a24c..948eb68 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -282,7 +282,7 @@
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 b767c52..138a924 100644
--- a/core/api/system-current.txt
+++ b/core/api/system-current.txt
@@ -1144,6 +1144,7 @@
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 301fef8..e6e46dd 100644
--- a/core/java/android/app/StatusBarManager.java
+++ b/core/java/android/app/StatusBarManager.java
@@ -1448,6 +1448,7 @@
*
* @hide
*/
+ @SystemApi
public boolean isBackDisabled() {
return mBack;
}
@@ -1861,38 +1862,38 @@
};
@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 6246dd7..91cdf8d 100644
--- a/core/java/android/net/vcn/VcnManager.java
+++ b/core/java/android/net/vcn/VcnManager.java
@@ -124,6 +124,22 @@
"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 @@
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 e64823a..6fde398 100644
--- a/core/java/android/net/vcn/flags.aconfig
+++ b/core/java/android/net/vcn/flags.aconfig
@@ -34,4 +34,14 @@
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 c95d6ff..a23df79 100644
--- a/core/java/android/view/SurfaceView.java
+++ b/core/java/android/view/SurfaceView.java
@@ -951,7 +951,7 @@
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 @@
updateBackgroundVisibility(surfaceUpdateTransaction);
updateBackgroundColor(surfaceUpdateTransaction);
- if (mLimitedHdrEnabled) {
+ if (mLimitedHdrEnabled && hdrHeadroomChanged) {
surfaceUpdateTransaction.setDesiredHdrHeadroom(
mBlastSurfaceControl, mHdrHeadroom);
}
@@ -1203,7 +1203,7 @@
}
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 cacf0d2..ac1f646 100644
--- a/core/java/android/view/View.java
+++ b/core/java/android/view/View.java
@@ -2428,6 +2428,26 @@
*/
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 @@
*/
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 @@
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 (viewRootImpl.shouldCheckFrameRate(mPreferredFrameRate > 0f)) {
+ float velocityFrameRate = 0f;
+ if (mAttachInfo.mViewVelocityApi) {
+ float velocity = mFrameContentVelocity;
- 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 (velocity > 0f) {
- if (sToolkitFrameRateVelocityMappingReadOnlyFlagValue) {
- frameRate = convertVelocityToFrameRate(velocity);
+ 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);
}
- viewRootImpl.votePreferredFrameRate(frameRate, FRAME_RATE_COMPATIBILITY_GTE);
- return;
+ }
+ 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, 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 @@
| 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 4024e3c..87dfa029 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_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 @@
// 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 @@
? 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 @@
*/
mIsFrameRateBoosting = false;
mIsTouchBoosting = false;
- setPreferredFrameRateCategory(Math.max(mPreferredFrameRateCategory,
- mLastPreferredFrameRateCategory));
break;
case MSG_CHECK_INVALIDATION_IDLE:
if (!mHasInvalidation && !mIsFrameRateBoosting && !mIsTouchBoosting) {
@@ -7677,7 +7685,6 @@
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 @@
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 @@
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 @@
}
if (sToolkitFrameRateFunctionEnablingReadOnlyFlagValue) {
mFrameRateTransaction.setFrameRate(mSurfaceControl, preferredFrameRate,
- mFrameRateCompatibility).applyAsyncUnsafe();
+ mFrameRateCompatibility).applyAsyncUnsafe();
}
mLastPreferredFrameRate = preferredFrameRate;
}
@@ -12602,12 +12597,6 @@
}
}
- 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 @@
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;
+ if (frameRateCategory > mPreferredFrameRateCategory) {
+ mPreferredFrameRateCategory = frameRateCategory;
+ mFrameRateCategoryChangeReason = reason;
+ mFrameRateCategoryView = view == null ? "-" : view.getClass().getSimpleName();
}
mHasInvalidation = true;
- checkIdleness();
- if (mPreferredFrameRateCategory != oldCategory
- && mPreferredFrameRateCategory == frameRateCategory
- ) {
- mFrameRateCategoryChangeReason = reason;
- mFrameRateCategoryView = view == null ? "null" : view.getClass().getSimpleName();
- }
+ }
+
+ /**
+ * 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 @@
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 @@
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/java/android/webkit/WebViewFactory.java b/core/java/android/webkit/WebViewFactory.java
index 8f1b72e..1034479 100644
--- a/core/java/android/webkit/WebViewFactory.java
+++ b/core/java/android/webkit/WebViewFactory.java
@@ -40,6 +40,7 @@
import android.util.AndroidRuntimeException;
import android.util.ArraySet;
import android.util.Log;
+import android.util.Slog;
import java.io.File;
import java.lang.reflect.Method;
@@ -609,7 +610,7 @@
startedRelroProcesses = WebViewLibraryLoader.prepareNativeLibraries(packageInfo);
} catch (Throwable t) {
// Log and discard errors at this stage as we must not crash the system server.
- Log.e(LOGTAG, "error preparing webview native library", t);
+ Slog.wtf(LOGTAG, "error preparing webview native library", t);
}
WebViewZygote.onWebViewProviderChanged(packageInfo);
diff --git a/core/java/android/webkit/WebViewLibraryLoader.java b/core/java/android/webkit/WebViewLibraryLoader.java
index a68a577..1a8745c 100644
--- a/core/java/android/webkit/WebViewLibraryLoader.java
+++ b/core/java/android/webkit/WebViewLibraryLoader.java
@@ -25,6 +25,7 @@
import android.os.Build;
import android.os.Process;
import android.util.Log;
+import android.util.Slog;
import com.android.internal.annotations.VisibleForTesting;
import com.android.server.LocalServices;
@@ -137,7 +138,7 @@
if (!success) throw new Exception("Failed to start the relro file creator process");
} catch (Throwable t) {
// Log and discard errors as we must not crash the system server.
- Log.e(LOGTAG, "error starting relro file creator for abi " + abi, t);
+ Slog.wtf(LOGTAG, "error starting relro file creator for abi " + abi, t);
crashHandler.run();
}
}
diff --git a/core/jni/com_android_internal_content_FileSystemUtils.cpp b/core/jni/com_android_internal_content_FileSystemUtils.cpp
index 4bd2d72..01920de 100644
--- a/core/jni/com_android_internal_content_FileSystemUtils.cpp
+++ b/core/jni/com_android_internal_content_FileSystemUtils.cpp
@@ -42,7 +42,11 @@
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 @@
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 @@
// 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 @@
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 a6b145c..52445e2 100644
--- a/core/jni/com_android_internal_content_FileSystemUtils.h
+++ b/core/jni/com_android_internal_content_FileSystemUtils.h
@@ -28,4 +28,11 @@
*/
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 faa83f8..9b8dab7 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 @@
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 @@
"%" 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 @@
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 ab714ad..0864210 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/tests/coretests/src/android/view/ViewFrameRateTest.java b/core/tests/coretests/src/android/view/ViewFrameRateTest.java
index 28343f1..0bf9a4c 100644
--- a/core/tests/coretests/src/android/view/ViewFrameRateTest.java
+++ b/core/tests/coretests/src/android/view/ViewFrameRateTest.java
@@ -203,7 +203,9 @@
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 @@
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 e0282a4..80fef6c 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.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 @@
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 @@
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 @@
// 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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
// 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 @@
// 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 @@
// 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 @@
// 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 @@
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 @@
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 @@
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 @@
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 @@
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 e1e41ee..f1a475a 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 @@
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 @@
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 58942ec..487bbfb 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
@@ -588,7 +588,7 @@
if (taskBoundsBeforeMaximize != null) {
destinationBounds.set(taskBoundsBeforeMaximize)
} else {
- getDefaultDesktopTaskBounds(displayLayout, destinationBounds)
+ destinationBounds.set(getDefaultDesktopTaskBounds(displayLayout))
}
} else {
// Save current bounds so that task can be restored back to original bounds if necessary
@@ -624,18 +624,14 @@
}
}
- private fun getDefaultDesktopTaskBounds(displayLayout: DisplayLayout, outBounds: Rect) {
+ private fun getDefaultDesktopTaskBounds(displayLayout: DisplayLayout): Rect {
// TODO(b/319819547): Account for app constraints so apps do not become letterboxed
- val screenBounds = Rect(0, 0, displayLayout.width(), displayLayout.height())
- // Update width and height with default desktop mode values
- val desiredWidth = screenBounds.width().times(DESKTOP_MODE_INITIAL_BOUNDS_SCALE).toInt()
- val desiredHeight = screenBounds.height().times(DESKTOP_MODE_INITIAL_BOUNDS_SCALE).toInt()
- outBounds.set(0, 0, desiredWidth, desiredHeight)
- // Center the task in screen bounds
- outBounds.offset(
- screenBounds.centerX() - outBounds.centerX(),
- screenBounds.centerY() - outBounds.centerY()
- )
+ val desiredWidth = (displayLayout.width() * DESKTOP_MODE_INITIAL_BOUNDS_SCALE).toInt()
+ val desiredHeight = (displayLayout.height() * DESKTOP_MODE_INITIAL_BOUNDS_SCALE).toInt()
+ val heightOffset = (displayLayout.height() - desiredHeight) / 2
+ val widthOffset = (displayLayout.width() - desiredWidth) / 2
+ return Rect(widthOffset, heightOffset,
+ desiredWidth + widthOffset, desiredHeight + heightOffset)
}
private fun getSnapBounds(taskInfo: RunningTaskInfo, position: SnapPosition): Rect {
@@ -889,8 +885,9 @@
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 {
@@ -907,8 +904,9 @@
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 {
@@ -1090,17 +1088,14 @@
* @param taskInfo the task being dragged.
* @param y height of drag, to be checked against status bar height.
*/
- fun onDragPositioningEndThroughStatusBar(
- inputCoordinates: PointF,
- taskInfo: RunningTaskInfo,
- freeformBounds: Rect
- ) {
+ fun onDragPositioningEndThroughStatusBar(inputCoordinates: PointF, taskInfo: RunningTaskInfo) {
val indicator = visualIndicator ?: return
val indicatorType = indicator
.updateIndicatorType(inputCoordinates, taskInfo.windowingMode)
when (indicatorType) {
DesktopModeVisualIndicator.IndicatorType.TO_DESKTOP_INDICATOR -> {
- finalizeDragToDesktop(taskInfo, freeformBounds)
+ val displayLayout = displayController.getDisplayLayout(taskInfo.displayId) ?: return
+ finalizeDragToDesktop(taskInfo, getDefaultDesktopTaskBounds(displayLayout))
}
DesktopModeVisualIndicator.IndicatorType.NO_INDICATOR,
DesktopModeVisualIndicator.IndicatorType.TO_FULLSCREEN_INDICATOR -> {
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 32f271b..87dc391 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 @@
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 @@
}
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 @@
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/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java
index a0f9c6b..7649a782 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java
@@ -858,10 +858,7 @@
// as it likely will change.
relevantDecor.updateHoverAndPressStatus(ev);
mDesktopTasksController.onDragPositioningEndThroughStatusBar(
- new PointF(ev.getRawX(), ev.getRawY()),
- relevantDecor.mTaskInfo,
- calculateFreeformBounds(ev.getDisplayId(),
- DesktopTasksController.DESKTOP_MODE_INITIAL_BOUNDS_SCALE));
+ new PointF(ev.getRawX(), ev.getRawY()), relevantDecor.mTaskInfo);
mMoveToDesktopAnimator = null;
return;
} else {
@@ -913,23 +910,6 @@
}
}
- /**
- * Gets bounds of a scaled window centered relative to the screen bounds
- * @param scale the amount to scale to relative to the Screen Bounds
- */
- private Rect calculateFreeformBounds(int displayId, float scale) {
- // TODO(b/319819547): Account for app constraints so apps do not become letterboxed
- final DisplayLayout displayLayout = mDisplayController.getDisplayLayout(displayId);
- final int screenWidth = displayLayout.width();
- final int screenHeight = displayLayout.height();
-
- final float adjustmentPercentage = (1f - scale) / 2;
- return new Rect((int) (screenWidth * adjustmentPercentage),
- (int) (screenHeight * adjustmentPercentage),
- (int) (screenWidth * (adjustmentPercentage + scale)),
- (int) (screenHeight * (adjustmentPercentage + scale)));
- }
-
@Nullable
private DesktopModeWindowDecoration getRelevantWindowDecor(MotionEvent ev) {
final DesktopModeWindowDecoration focusedDecor = getFocusedDecor();
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 93a967e..4d0f11b 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 @@
(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 @@
}
@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 @@
}
@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 @@
}
@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 @@
}
@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 @@
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 @@
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 @@
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 @@
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 @@
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 34a6bc2..839c7b6 100644
--- a/libs/androidfw/ZipFileRO.cpp
+++ b/libs/androidfw/ZipFileRO.cpp
@@ -119,30 +119,41 @@
* 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 031d2e8..f7c5007 100644
--- a/libs/androidfw/include/androidfw/ZipFileRO.h
+++ b/libs/androidfw/include/androidfw/ZipFileRO.h
@@ -151,6 +151,10 @@
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 34932b1..dc669a5 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 @@
// 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 cf14b1f..823b209 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 b62711f..21fe6ff 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 c836df3..c3edb90 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 @@
* 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 @@
* 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 9fd386f..b6b1a45 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 36f6ad2..429bdbf 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/CredentialSelectorViewModel.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/CredentialSelectorViewModel.kt
@@ -270,6 +270,15 @@
)
}
+ 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 e088d3a..95f49e9 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 @@
* 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 @@
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 @@
* 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 @@
)
/**
- * 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 @@
Log.d(TAG, "The BiometricPrompt API call begins.")
runBiometricFlow(context, biometricDisplayInfo, callback, openMoreOptionsPage,
- onBiometricFailureFallback, BiometricFlowType.GET)
+ onBiometricFailureFallback, BiometricFlowType.GET, onCancelFlowAndFinish)
}
/**
@@ -191,14 +190,15 @@
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 @@
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
- }
-
- val executor = getMainExecutor(context)
-
try {
- biometricPrompt.authenticate(cancellationSignal, executor, callback)
- } catch (e: IllegalArgumentException) {
+ if (onlyUsingDeviceCredentials(biometricDisplayInfo, context)) {
+ onBiometricFailureFallback(biometricFlowType)
+ return
+ }
+
+ val biometricPrompt = setupBiometricPrompt(context, biometricDisplayInfo,
+ openMoreOptionsPage, biometricDisplayInfo.biometricRequestInfo, onCancelFlowAndFinish)
+
+ 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 @@
biometricDisplayInfo: BiometricDisplayInfo,
openMoreOptionsPage: () -> Unit,
biometricRequestInfo: BiometricRequestInfo,
- biometricFlowType: BiometricFlowType,
+ onCancelFlowAndFinish: () -> Unit
): BiometricPrompt {
- val finalAuthenticators = removeDeviceCredential(biometricRequestInfo.allowedAuthenticators)
+ val listener =
+ DialogInterface.OnClickListener { _: DialogInterface?, _: Int -> openMoreOptionsPage() }
- val biometricPrompt = BiometricPrompt.Builder(context)
+ val promptContentViewBuilder = PromptContentViewWithMoreOptionsButton.Builder()
+ .setMoreOptionsButtonListener(context.mainExecutor, listener)
+ biometricDisplayInfo.descriptionForCredential?.let {
+ promptContentViewBuilder.setDescription(it) }
+
+ 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()
+ .setContentView(promptContentViewBuilder.build())
- return biometricPrompt
-}
-
-// 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 (!containsBiometricAuthenticatorWithDeviceCredentials(biometricDisplayInfo
+ .biometricRequestInfo.allowedAuthenticators)) {
+ biometricPromptBuilder.setNegativeButton(context.getString(R.string.string_cancel),
+ getMainExecutor(context)
+ ) { _: DialogInterface?, _: Int -> onCancelFlowAndFinish() }
}
- if (requestAllowedAuthenticators == (BiometricManager.Authenticators.DEVICE_CREDENTIAL or
- BiometricManager.Authenticators.BIOMETRIC_STRONG)) {
- finalAuthenticators = BiometricManager.Authenticators.BIOMETRIC_WEAK
- }
+ biometricDisplayInfo.displaySubtitleText?.let { biometricPromptBuilder.setSubtitle(it) }
- if (requestAllowedAuthenticators == (BiometricManager.Authenticators.DEVICE_CREDENTIAL)) {
- finalAuthenticators = BiometricManager.Authenticators.BIOMETRIC_WEAK
- }
-
- return finalAuthenticators
+ return biometricPromptBuilder.build()
}
/**
@@ -417,15 +472,29 @@
}
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 @@
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 d13d86f..149c14a 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 @@
}
}
+@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 c1120bb3..e68baf4 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.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.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 @@
.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 @@
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 @@
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 @@
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 b03407b..8e78861 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 @@
/** 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 @@
/** 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 @@
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 4428dbb..3cc64a6 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 4dcad10..bd96025 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 9fafcab..86c8f0da 100644
--- a/packages/SettingsLib/DataStore/Android.bp
+++ b/packages/SettingsLib/DataStore/Android.bp
@@ -2,12 +2,17 @@
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 9d3fb66..7644bc9 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 @@
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 @@
*/
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 @@
}
}
-private fun BackupRestoreFileStorage.toBackupRestoreEntity() =
+@VisibleForTesting
+internal fun BackupRestoreFileStorage.toBackupRestoreEntity() =
object : BackupRestoreEntity {
override val key: String
get() = storageFilePath
@@ -107,7 +107,7 @@
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 c4c00cb..935f9cc 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.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 @@
*
* 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 @@
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 @@
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 @@
/** 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 @@
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 @@
}
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 cfdcaff..8242347 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.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 @@
}
}
// 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 @@
/** 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 @@
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 0c1b417..9f9c0d8 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.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 @@
* @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 @@
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 @@
// 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 @@
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 @@
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 @@
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 8770dfa..5d000eb 100644
--- a/packages/SettingsLib/DataStore/tests/Android.bp
+++ b/packages/SettingsLib/DataStore/tests/Android.bp
@@ -9,11 +9,16 @@
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 0000000..867831b
--- /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 0000000..911665a
--- /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 0000000..6cce453
--- /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 0000000..422273d
--- /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 0000000..d8f5028
--- /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 0000000..99998ff
--- /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 b52586c..8638b2f 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?>
+ private val key1 = Object()
+ private val key2 = Object()
- @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 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 @@
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 @@
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 @@
@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 @@
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 @@
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 f065829..173c2b1 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 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()
+ private val observer1 = mock<Observer>()
+ private val observer2 = mock<Observer>()
- @Mock private lateinit var observer1: Observer
-
- @Mock private lateinit var observer2: Observer
-
- @Mock private lateinit var executor: Executor
-
+ 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 @@
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 @@
@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 0000000..fec7d75
--- /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 0000000..823d222
--- /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 0000000..ba02d20
--- /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 4d70aec..7aae1a6 100644
--- a/packages/SettingsLib/aconfig/settingslib_media_flag_declarations.aconfig
+++ b/packages/SettingsLib/aconfig/settingslib_media_flag_declarations.aconfig
@@ -21,3 +21,13 @@
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/bluetooth/LocalBluetoothLeBroadcastAssistant.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothLeBroadcastAssistant.java
index 34c60a1..9faebe2 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothLeBroadcastAssistant.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothLeBroadcastAssistant.java
@@ -381,6 +381,14 @@
});
}
+ /** Gets devices with matched connection states. */
+ public List<BluetoothDevice> getDevicesMatchingConnectionStates(@NonNull int[] states) {
+ if (mService == null) {
+ return new ArrayList<BluetoothDevice>(0);
+ }
+ return mService.getDevicesMatchingConnectionStates(states);
+ }
+
public boolean isEnabled(BluetoothDevice device) {
if (mService == null || device == null) {
return false;
diff --git a/packages/SettingsLib/src/com/android/settingslib/media/InfoMediaManager.java b/packages/SettingsLib/src/com/android/settingslib/media/InfoMediaManager.java
index 3e29872..cbeda14 100644
--- a/packages/SettingsLib/src/com/android/settingslib/media/InfoMediaManager.java
+++ b/packages/SettingsLib/src/com/android/settingslib/media/InfoMediaManager.java
@@ -185,11 +185,7 @@
}
public void startScan() {
- mMediaDevices.clear();
- registerRouter();
startScanOnRouter();
- updateRouteListingPreference();
- refreshDevices();
}
private void updateRouteListingPreference() {
@@ -203,7 +199,6 @@
public final void stopScan() {
stopScanOnRouter();
- unregisterRouter();
}
protected abstract void stopScanOnRouter();
@@ -290,14 +285,37 @@
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 0c2414c..a08a3dc 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 @@
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 @@
* 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 @@
*
* 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 @@
* Start scan connected MediaDevice
*/
public void startScan() {
- synchronized (mMediaDevicesLock) {
- mMediaDevices.clear();
- }
- mInfoMediaManager.registerCallback(mMediaDeviceCallback);
mInfoMediaManager.startScan();
}
@@ -281,9 +285,7 @@
* 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 724dd51..869fb7f 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 @@
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 @@
}
}
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 a4b87da..69faddf 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.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 @@
@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);
+ MediaDevice mediaDevice = mInfoMediaManager.getCurrentConnectedDevice();
+ assertThat(mediaDevice).isNotNull();
+ assertThat(mediaDevice.getId()).isEqualTo(TEST_SYSTEM_ROUTE_ID);
- final List<MediaRoute2Info> routes = new ArrayList<>();
- routes.add(info);
- mShadowRouter2Manager.setTransferableRoutes(routes);
+ 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));
- final MediaDevice mediaDevice = mInfoMediaManager.findMediaDevice(TEST_ID);
- assertThat(mediaDevice).isNull();
-
- 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 @@
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 693b7d0..ddb5419 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 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.shadow.api.Shadow;
import java.util.ArrayList;
+import java.util.Collections;
import java.util.List;
@RunWith(RobolectricTestRunner.class)
@@ -117,6 +120,13 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
mLocalMediaManager.mMediaDevices.add(device2);
assertThat(mLocalMediaManager.mMediaDevices).hasSize(2);
- mLocalMediaManager.registerCallback(mCallback);
mLocalMediaManager.mMediaDeviceCallback
.onDeviceListRemoved(mLocalMediaManager.mMediaDevices);
@@ -302,19 +306,21 @@
@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 @@
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 @@
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 @@
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 @@
@Test
public void onConnectedDeviceChanged_nullConnectedDevice_noException() {
- mLocalMediaManager.registerCallback(mCallback);
mLocalMediaManager.mMediaDeviceCallback.onConnectedDeviceChanged(TEST_DEVICE_ID_2);
}
@@ -392,7 +394,6 @@
when(cachedDevice.isConnected()).thenReturn(false);
when(cachedDevice.isBusy()).thenReturn(false);
- mLocalMediaManager.registerCallback(mCallback);
mLocalMediaManager.connectDevice(device);
mLocalMediaManager.mDeviceAttributeChangeCallback.onDeviceAttributesChanged();
@@ -410,7 +411,6 @@
.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 @@
@Test
public void onDeviceAttributesChanged_shouldBeCalled() {
- mLocalMediaManager.registerCallback(mCallback);
-
mLocalMediaManager.mDeviceAttributeChangeCallback.onDeviceAttributesChanged();
verify(mCallback).onDeviceAttributesChanged();
@@ -475,7 +473,6 @@
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 @@
when(cachedDevice.isConnected()).thenReturn(false);
when(cachedDevice.isBusy()).thenReturn(false);
- mLocalMediaManager.registerCallback(mCallback);
mLocalMediaManager.connectDevice(device);
verify(cachedDevice).connect();
@@ -512,8 +508,6 @@
@Test
public void onRequestFailed_shouldDispatchOnRequestFailed() {
- mLocalMediaManager.registerCallback(mCallback);
-
mLocalMediaManager.mMediaDeviceCallback.onRequestFailed(1);
verify(mCallback).onRequestFailed(1);
@@ -532,7 +526,6 @@
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 ad3eb92..e77cf2f 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 @@
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 @@
+ "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 40db52e..c88c373 100644
--- a/packages/SystemUI/Android.bp
+++ b/packages/SystemUI/Android.bp
@@ -117,6 +117,7 @@
"SystemUILogLib",
"SystemUIPluginLib",
"SystemUISharedLib",
+ "SystemUI-shared-utils",
"SystemUI-statsd",
"SettingsLib",
"com_android_systemui_flags_lib",
@@ -263,6 +264,7 @@
"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 0000000..c2a2696
--- /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 9bd6f81..01c27a4d 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.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 @@
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 @@
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 @@
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/multivalentTests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.kt
index 2d4b63e..ae9794a 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.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.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.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 @@
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 @@
)
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 @@
falsingManager,
userSwitcherController,
featureFlags,
- kosmos.sceneContainerFlags,
globalSettings,
sessionTracker,
Optional.of(sideFpsController),
@@ -283,7 +284,7 @@
deviceProvisionedController,
faceAuthAccessibilityDelegate,
keyguardTransitionInteractor,
- primaryBouncerInteractor,
+ { primaryBouncerInteractor },
) {
deviceEntryInteractor
}
@@ -804,17 +805,17 @@
}
@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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 caf9219..1cd9d76 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.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 @@
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 85774c6..60b48f2 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 @@
// 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 @@
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 741cde8..d850f17 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.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 @@
@OptIn(ExperimentalCoroutinesApi::class)
@SmallTest
@RunWith(AndroidJUnit4::class)
+@EnableSceneContainer
class BouncerActionButtonInteractorTest : SysuiTestCase() {
@Mock private lateinit var selectedUserInteractor: SelectedUserInteractor
@@ -75,7 +76,6 @@
@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 cbdb71b..361b078 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.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 @@
@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 0db0e07..b83c0ce 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.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 @@
@OptIn(ExperimentalCoroutinesApi::class)
@SmallTest
@RunWith(AndroidJUnit4::class)
+@EnableSceneContainer
class BouncerViewModelTest : SysuiTestCase() {
private val kosmos = testKosmos()
@@ -70,7 +71,6 @@
@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 f21e969..497180b 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.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.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 @@
}
@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 5caf35b..37a6ac6 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.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.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 @@
@OptIn(ExperimentalCoroutinesApi::class)
@SmallTest
@RunWith(AndroidJUnit4::class)
+@EnableSceneContainer
class DeviceEntryInteractorTest : SysuiTestCase() {
private val kosmos = testKosmos()
@@ -74,7 +75,6 @@
@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 1dd5d07..12f8918 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.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.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 @@
repository = repository,
commandQueue = commandQueue,
powerInteractor = PowerInteractorFactory.create().powerInteractor,
- sceneContainerFlags = kosmos.sceneContainerFlags,
bouncerRepository = bouncerRepository,
configurationInteractor = ConfigurationInteractor(FakeConfigurationRepository()),
shadeRepository = shadeRepository,
@@ -249,9 +247,9 @@
}
@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/media/controls/data/repository/MediaFilterRepositoryTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/media/controls/data/repository/MediaFilterRepositoryTest.kt
index 33eb90a..f685058 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/media/controls/data/repository/MediaFilterRepositoryTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/media/controls/data/repository/MediaFilterRepositoryTest.kt
@@ -25,6 +25,7 @@
import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.kosmos.testScope
import com.android.systemui.media.controls.MediaTestHelper
+import com.android.systemui.media.controls.shared.model.MediaCommonModel
import com.android.systemui.media.controls.shared.model.MediaData
import com.android.systemui.media.controls.shared.model.MediaDataLoadingModel
import com.android.systemui.media.controls.shared.model.SmartspaceMediaData
@@ -172,12 +173,147 @@
val recommendationsLoadingModel =
SmartspaceMediaLoadingModel.Loaded(KEY_MEDIA_SMARTSPACE)
- underTest.setRecommedationsLoadingState(recommendationsLoadingModel)
+ underTest.setRecommendationsLoadingState(recommendationsLoadingModel)
assertThat(recommendationsLoadingState).isEqualTo(recommendationsLoadingModel)
}
+ @Test
+ fun addMediaControlPlayingThenRemote() =
+ testScope.runTest {
+ val sortedMedia by collectLastValue(underTest.sortedMedia)
+ val playingInstanceId = InstanceId.fakeInstanceId(123)
+ val remoteInstanceId = InstanceId.fakeInstanceId(321)
+ val playingData = createMediaData("app1", true, LOCAL, false, playingInstanceId)
+ val remoteData = createMediaData("app2", true, REMOTE, false, remoteInstanceId)
+
+ underTest.addSelectedUserMediaEntry(playingData)
+ underTest.addMediaDataLoadingState(MediaDataLoadingModel.Loaded(playingInstanceId))
+ underTest.addSelectedUserMediaEntry(remoteData)
+ underTest.addMediaDataLoadingState(MediaDataLoadingModel.Loaded(remoteInstanceId))
+
+ assertThat(sortedMedia?.size).isEqualTo(2)
+ assertThat(sortedMedia?.values)
+ .containsExactly(
+ MediaCommonModel.MediaControl(playingInstanceId),
+ MediaCommonModel.MediaControl(remoteInstanceId)
+ )
+ }
+
+ @Test
+ fun switchMediaControlsPlaying() =
+ testScope.runTest {
+ val sortedMedia by collectLastValue(underTest.sortedMedia)
+ val playingInstanceId1 = InstanceId.fakeInstanceId(123)
+ val playingInstanceId2 = InstanceId.fakeInstanceId(321)
+ var playingData1 = createMediaData("app1", true, LOCAL, false, playingInstanceId1)
+ var playingData2 = createMediaData("app2", false, LOCAL, false, playingInstanceId2)
+
+ underTest.addSelectedUserMediaEntry(playingData1)
+ underTest.addMediaDataLoadingState(MediaDataLoadingModel.Loaded(playingInstanceId1))
+ underTest.addSelectedUserMediaEntry(playingData2)
+ underTest.addMediaDataLoadingState(MediaDataLoadingModel.Loaded(playingInstanceId2))
+
+ assertThat(sortedMedia?.size).isEqualTo(2)
+ assertThat(sortedMedia?.values)
+ .containsExactly(
+ MediaCommonModel.MediaControl(playingInstanceId1),
+ MediaCommonModel.MediaControl(playingInstanceId2)
+ )
+ .inOrder()
+
+ playingData1 = createMediaData("app1", false, LOCAL, false, playingInstanceId1)
+ playingData2 = createMediaData("app2", true, LOCAL, false, playingInstanceId2)
+
+ underTest.addSelectedUserMediaEntry(playingData1)
+ underTest.addMediaDataLoadingState(MediaDataLoadingModel.Loaded(playingInstanceId1))
+ underTest.addSelectedUserMediaEntry(playingData2)
+ underTest.addMediaDataLoadingState(MediaDataLoadingModel.Loaded(playingInstanceId2))
+
+ assertThat(sortedMedia?.size).isEqualTo(2)
+ assertThat(sortedMedia?.values)
+ .containsExactly(
+ MediaCommonModel.MediaControl(playingInstanceId2),
+ MediaCommonModel.MediaControl(playingInstanceId1)
+ )
+ .inOrder()
+ }
+
+ @Test
+ fun fullOrderTest() =
+ testScope.runTest {
+ val sortedMedia by collectLastValue(underTest.sortedMedia)
+ val instanceId1 = InstanceId.fakeInstanceId(123)
+ val instanceId2 = InstanceId.fakeInstanceId(456)
+ val instanceId3 = InstanceId.fakeInstanceId(321)
+ val instanceId4 = InstanceId.fakeInstanceId(654)
+ val instanceId5 = InstanceId.fakeInstanceId(124)
+ val playingAndLocalData = createMediaData("app1", true, LOCAL, false, instanceId1)
+ val playingAndRemoteData = createMediaData("app2", true, REMOTE, false, instanceId2)
+ val stoppedAndLocalData = createMediaData("app3", false, LOCAL, false, instanceId3)
+ val stoppedAndRemoteData = createMediaData("app4", false, REMOTE, false, instanceId4)
+ val canResumeData = createMediaData("app5", false, LOCAL, true, instanceId5)
+
+ val icon = Icon.createWithResource(context, R.drawable.ic_media_play)
+ val mediaRecommendations =
+ SmartspaceMediaData(
+ targetId = KEY_MEDIA_SMARTSPACE,
+ isActive = true,
+ recommendations = MediaTestHelper.getValidRecommendationList(icon),
+ )
+
+ underTest.addSelectedUserMediaEntry(stoppedAndLocalData)
+ underTest.addMediaDataLoadingState(MediaDataLoadingModel.Loaded(instanceId3))
+
+ underTest.addSelectedUserMediaEntry(stoppedAndRemoteData)
+ underTest.addMediaDataLoadingState(MediaDataLoadingModel.Loaded(instanceId4))
+
+ underTest.addSelectedUserMediaEntry(canResumeData)
+ underTest.addMediaDataLoadingState(MediaDataLoadingModel.Loaded(instanceId5))
+
+ underTest.addSelectedUserMediaEntry(playingAndLocalData)
+ underTest.addMediaDataLoadingState(MediaDataLoadingModel.Loaded(instanceId1))
+
+ underTest.addSelectedUserMediaEntry(playingAndRemoteData)
+ underTest.addMediaDataLoadingState(MediaDataLoadingModel.Loaded(instanceId2))
+
+ underTest.setRecommendation(mediaRecommendations)
+ underTest.setRecommendationsLoadingState(
+ SmartspaceMediaLoadingModel.Loaded(KEY_MEDIA_SMARTSPACE, true)
+ )
+
+ assertThat(sortedMedia?.size).isEqualTo(6)
+ assertThat(sortedMedia?.values)
+ .containsExactly(
+ MediaCommonModel.MediaControl(instanceId1),
+ MediaCommonModel.MediaControl(instanceId2),
+ MediaCommonModel.MediaRecommendations(KEY_MEDIA_SMARTSPACE),
+ MediaCommonModel.MediaControl(instanceId4),
+ MediaCommonModel.MediaControl(instanceId3),
+ MediaCommonModel.MediaControl(instanceId5),
+ )
+ .inOrder()
+ }
+
+ private fun createMediaData(
+ app: String,
+ playing: Boolean,
+ playbackLocation: Int,
+ isResume: Boolean,
+ instanceId: InstanceId,
+ ): MediaData {
+ return MediaData(
+ playbackLocation = playbackLocation,
+ resumption = isResume,
+ notificationKey = "key: $app",
+ isPlaying = playing,
+ instanceId = instanceId
+ )
+ }
+
companion object {
+ private const val LOCAL = MediaData.PLAYBACK_LOCAL
+ private const val REMOTE = MediaData.PLAYBACK_CAST_LOCAL
private const val KEY = "KEY"
private const val KEY_MEDIA_SMARTSPACE = "MEDIA_SMARTSPACE_ID"
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/media/controls/domain/interactor/MediaCarouselInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/media/controls/domain/interactor/MediaCarouselInteractorTest.kt
index a0a1eb3..c15776e 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/media/controls/domain/interactor/MediaCarouselInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/media/controls/domain/interactor/MediaCarouselInteractorTest.kt
@@ -33,6 +33,7 @@
import com.android.systemui.media.controls.domain.pipeline.interactor.MediaCarouselInteractor
import com.android.systemui.media.controls.domain.pipeline.interactor.mediaCarouselInteractor
import com.android.systemui.media.controls.domain.pipeline.mediaDataFilter
+import com.android.systemui.media.controls.shared.model.MediaCommonModel
import com.android.systemui.media.controls.shared.model.MediaData
import com.android.systemui.media.controls.shared.model.MediaDataLoadingModel
import com.android.systemui.media.controls.shared.model.SmartspaceMediaData
@@ -94,22 +95,29 @@
collectLastValue(underTest.hasActiveMediaOrRecommendation)
val hasActiveMedia by collectLastValue(underTest.hasActiveMedia)
val hasAnyMedia by collectLastValue(underTest.hasAnyMedia)
+ val sortedMedia by collectLastValue(underTest.sortedMedia)
val userMedia = MediaData(active = false)
val instanceId = userMedia.instanceId
mediaFilterRepository.addSelectedUserMediaEntry(userMedia)
+ mediaFilterRepository.addMediaDataLoadingState(MediaDataLoadingModel.Loaded(instanceId))
assertThat(hasActiveMediaOrRecommendation).isFalse()
assertThat(hasActiveMedia).isFalse()
assertThat(hasAnyMedia).isTrue()
+ assertThat(sortedMedia).containsExactly(MediaCommonModel.MediaControl(instanceId))
assertThat(mediaFilterRepository.removeSelectedUserMediaEntry(instanceId, userMedia))
.isTrue()
+ mediaFilterRepository.addMediaDataLoadingState(
+ MediaDataLoadingModel.Removed(instanceId)
+ )
assertThat(hasActiveMediaOrRecommendation).isFalse()
assertThat(hasActiveMedia).isFalse()
assertThat(hasAnyMedia).isFalse()
+ assertThat(sortedMedia).isEmpty()
}
@Test
@@ -119,6 +127,7 @@
collectLastValue(underTest.hasActiveMediaOrRecommendation)
val hasAnyMediaOrRecommendation by
collectLastValue(underTest.hasAnyMediaOrRecommendation)
+ val sortedMedia by collectLastValue(underTest.sortedMedia)
kosmos.fakeFeatureFlagsClassic.set(Flags.MEDIA_RETAIN_RECOMMENDATIONS, false)
val icon = Icon.createWithResource(context, R.drawable.ic_media_play)
@@ -131,14 +140,28 @@
val userMedia = MediaData(active = false)
mediaFilterRepository.setRecommendation(userMediaRecommendation)
+ mediaFilterRepository.setRecommendationsLoadingState(
+ SmartspaceMediaLoadingModel.Loaded(KEY_MEDIA_SMARTSPACE, true)
+ )
assertThat(hasActiveMediaOrRecommendation).isTrue()
assertThat(hasAnyMediaOrRecommendation).isTrue()
+ assertThat(sortedMedia)
+ .containsExactly(MediaCommonModel.MediaRecommendations(KEY_MEDIA_SMARTSPACE))
mediaFilterRepository.addSelectedUserMediaEntry(userMedia)
+ mediaFilterRepository.addMediaDataLoadingState(
+ MediaDataLoadingModel.Loaded(userMedia.instanceId)
+ )
assertThat(hasActiveMediaOrRecommendation).isTrue()
assertThat(hasAnyMediaOrRecommendation).isTrue()
+ assertThat(sortedMedia)
+ .containsExactly(
+ MediaCommonModel.MediaRecommendations(KEY_MEDIA_SMARTSPACE),
+ MediaCommonModel.MediaControl(userMedia.instanceId)
+ )
+ .inOrder()
}
@Test
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 a277fe0..65fd101 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.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.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.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 @@
@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 @@
footerActionsController = kosmos.footerActionsController,
footerActionsViewModelFactory = kosmos.footerActionsViewModelFactory,
sceneInteractor = sceneInteractor,
+ unfoldTransitionInteractor = kosmos.unfoldTransitionInteractor,
)
val displayTracker = FakeDisplayTracker(context)
@@ -236,15 +239,15 @@
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 @@
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 7f7c24e..8e2eea1 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 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 @@
@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 b179c30..63f4816 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 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 @@
@SmallTest
@RunWith(AndroidJUnit4::class)
+@EnableSceneContainer
class SceneInteractorTest : SysuiTestCase() {
private val kosmos = testKosmos()
@@ -55,7 +56,6 @@
@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 d5e43f4..bfe5ef7 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.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 @@
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 61adcd2..1472a4d 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.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 @@
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 @@
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 @@
headsUpInteractor = kosmos.headsUpNotificationInteractor,
occlusionInteractor = kosmos.sceneContainerOcclusionInteractor,
faceUnlockInteractor = kosmos.deviceEntryFaceAuthInteractor,
- deviceUnlockedInteractor = kosmos.deviceUnlockedInteractor,
shadeInteractor = kosmos.shadeInteractor,
)
}
@@ -1245,7 +1242,6 @@
"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 2938acf..ae5bf07 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 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 @@
@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 7b0127e..427b66b 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.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 @@
@SmallTest
@RunWith(AndroidJUnit4::class)
+@EnableSceneContainer
class SceneContainerViewModelTest : SysuiTestCase() {
private val kosmos = testKosmos()
@@ -58,7 +59,6 @@
@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 cbbcce9..420418b 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.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.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 @@
@ExperimentalCoroutinesApi
@SmallTest
@RunWith(AndroidJUnit4::class)
+@EnableSceneContainer
class ShadeControllerSceneImplTest : SysuiTestCase() {
private val kosmos = Kosmos()
private val testScope = kosmos.testScope
@@ -64,7 +65,6 @@
@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 e759b50..26f342a 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 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 @@
@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 ab95e2c..2727af6 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.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 @@
footerActionsViewModelFactory = kosmos.footerActionsViewModelFactory,
footerActionsController = kosmos.footerActionsController,
sceneInteractor = kosmos.sceneInteractor,
+ unfoldTransitionInteractor = kosmos.unfoldTransitionInteractor,
)
}
@@ -254,4 +257,26 @@
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 a3cf929..01e1aa59 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 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 @@
@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 8f7a56d..a023033 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.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 @@
@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
similarity index 60%
rename from packages/SystemUI/tests/src/com/android/systemui/unfold/domain/interactor/UnfoldTransitionInteractorTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/unfold/domain/interactor/UnfoldTransitionInteractorTest.kt
index 6a801e0..3b4cce4 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 @@
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 8e2bd9b..79bf5f1 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 @@
/** 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 @@
* 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/KeyguardSecurityContainerController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java
index c509356..e8e1cab 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.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 @@
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 @@
FalsingManager falsingManager,
UserSwitcherController userSwitcherController,
FeatureFlags featureFlags,
- SceneContainerFlags sceneContainerFlags,
GlobalSettings globalSettings,
SessionTracker sessionTracker,
Optional<SideFpsController> sideFpsController,
@@ -491,7 +489,6 @@
mFalsingManager = falsingManager;
mUserSwitcherController = userSwitcherController;
mFeatureFlags = featureFlags;
- mSceneContainerFlags = sceneContainerFlags;
mGlobalSettings = globalSettings;
mSessionTracker = sessionTracker;
if (SideFpsControllerRefactor.isEnabled()) {
@@ -534,7 +531,7 @@
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/LegacyLockIconViewController.java b/packages/SystemUI/src/com/android/keyguard/LegacyLockIconViewController.java
index 4e5df35..cf2675b 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.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 @@
@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 @@
@NonNull FeatureFlags featureFlags,
PrimaryBouncerInteractor primaryBouncerInteractor,
Context context,
- Lazy<DeviceEntryInteractor> deviceEntryInteractor,
- SceneContainerFlags sceneContainerFlags
+ Lazy<DeviceEntryInteractor> deviceEntryInteractor
) {
mStatusBarStateController = statusBarStateController;
mKeyguardUpdateMonitor = keyguardUpdateMonitor;
@@ -236,7 +234,6 @@
mResources = resources;
mContext = context;
mDeviceEntryInteractor = deviceEntryInteractor;
- mSceneContainerFlags = sceneContainerFlags;
mAccessibilityDelegate = new View.AccessibilityDelegate() {
private final AccessibilityNodeInfo.AccessibilityAction mAccessibilityAuthenticateHint =
@@ -746,7 +743,7 @@
// 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 454ed27..a9f985f 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.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 @@
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 @@
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 61d1c71..4a60d19 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 @@
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 e789475..62ef365 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 @@
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 @@
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 @@
replaceWith = ReplaceWith("isComposeBouncerOrSceneContainerEnabled()")
)
override fun isOnlyComposeBouncerEnabled(): Boolean {
- return !sceneContainerFlags.isEnabled() && Flags.composeBouncer()
+ return !SceneContainerFlag.isEnabled && Flags.composeBouncer()
}
}
@@ -63,7 +62,7 @@
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 af467ef..613280c 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 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 @@
@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 d4a1f74..0c181e9 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 @@
* 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 373e1c9..619e052 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.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 @@
private val userManager: UserManager,
private val dockManager: DockManager,
sceneInteractor: SceneInteractor,
- sceneContainerFlags: SceneContainerFlags,
@CommunalLog logBuffer: LogBuffer,
@CommunalTableLog tableLogBuffer: TableLogBuffer,
) {
@@ -216,7 +215,7 @@
*/
// 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 165de7c..21af0a0 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 @@
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 bf1f074..eef4b97 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.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 @@
@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 @@
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 @@
.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 c476948..7224536 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.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 @@
private val repository: KeyguardRepository,
private val commandQueue: CommandQueue,
powerInteractor: PowerInteractor,
- sceneContainerFlags: SceneContainerFlags,
bouncerRepository: KeyguardBouncerRepository,
configurationInteractor: ConfigurationInteractor,
shadeRepository: ShadeRepository,
@@ -331,7 +330,7 @@
/** 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 80e94a2..20b7b2a 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.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 @@
.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 6255f0d..7178e1b 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.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 @@
}
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 @@
}
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 5ee35e4f..cc54920 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.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.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 @@
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/DeviceEntryIconViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryIconViewModel.kt
index 49fffdd..45dca99 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.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 @@
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 @@
}
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 f6da033..a6d3312 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 @@
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/data/model/MediaSortKeyModel.kt b/packages/SystemUI/src/com/android/systemui/media/controls/data/model/MediaSortKeyModel.kt
new file mode 100644
index 0000000..cfe5cde
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/data/model/MediaSortKeyModel.kt
@@ -0,0 +1,33 @@
+/*
+ * 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.data.model
+
+import com.android.internal.logging.InstanceId
+import com.android.systemui.media.controls.shared.model.MediaData.Companion.PLAYBACK_LOCAL
+
+data class MediaSortKeyModel(
+ /** Whether the item represents a Smartspace media recommendation that should be prioritized. */
+ val isPrioritizedRec: Boolean = false,
+ val isPlaying: Boolean? = null,
+ val playbackLocation: Int = PLAYBACK_LOCAL,
+ val active: Boolean = true,
+ val isResume: Boolean = false,
+ val lastActive: Long = 0L,
+ val notificationKey: String? = null,
+ val updateTime: Long = 0,
+ val instanceId: InstanceId? = null,
+)
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/data/repository/MediaFilterRepository.kt b/packages/SystemUI/src/com/android/systemui/media/controls/data/repository/MediaFilterRepository.kt
index 9dc5900..7e57cf4 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/data/repository/MediaFilterRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/data/repository/MediaFilterRepository.kt
@@ -18,10 +18,14 @@
import com.android.internal.logging.InstanceId
import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.media.controls.data.model.MediaSortKeyModel
+import com.android.systemui.media.controls.shared.model.MediaCommonModel
import com.android.systemui.media.controls.shared.model.MediaData
import com.android.systemui.media.controls.shared.model.MediaDataLoadingModel
import com.android.systemui.media.controls.shared.model.SmartspaceMediaData
import com.android.systemui.media.controls.shared.model.SmartspaceMediaLoadingModel
+import com.android.systemui.util.time.SystemClock
+import java.util.TreeMap
import javax.inject.Inject
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
@@ -29,7 +33,7 @@
/** A repository that holds the state of filtered media data on the device. */
@SysUISingleton
-class MediaFilterRepository @Inject constructor() {
+class MediaFilterRepository @Inject constructor(private val systemClock: SystemClock) {
/** Instance id of media control that recommendations card reactivated. */
private val _reactivatedId: MutableStateFlow<InstanceId?> = MutableStateFlow(null)
@@ -58,6 +62,26 @@
val recommendationsLoadingState: StateFlow<SmartspaceMediaLoadingModel> =
_recommendationsLoadingState.asStateFlow()
+ private val comparator =
+ compareByDescending<MediaSortKeyModel> {
+ it.isPlaying == true && it.playbackLocation == MediaData.PLAYBACK_LOCAL
+ }
+ .thenByDescending {
+ it.isPlaying == true && it.playbackLocation == MediaData.PLAYBACK_CAST_LOCAL
+ }
+ .thenByDescending { it.active }
+ .thenByDescending { it.isPrioritizedRec }
+ .thenByDescending { !it.isResume }
+ .thenByDescending { it.playbackLocation != MediaData.PLAYBACK_CAST_REMOTE }
+ .thenByDescending { it.lastActive }
+ .thenByDescending { it.updateTime }
+ .thenByDescending { it.notificationKey }
+
+ private val _sortedMedia: MutableStateFlow<TreeMap<MediaSortKeyModel, MediaCommonModel>> =
+ MutableStateFlow(TreeMap<MediaSortKeyModel, MediaCommonModel>(comparator))
+ val sortedMedia: StateFlow<Map<MediaSortKeyModel, MediaCommonModel>> =
+ _sortedMedia.asStateFlow()
+
fun addMediaEntry(key: String, data: MediaData) {
val entries = LinkedHashMap<String, MediaData>(_allUserEntries.value)
entries[key] = data
@@ -138,9 +162,91 @@
} else {
emptyList()
}
+
+ addMediaLoadingToSortedMap(mediaDataLoadingModel)
}
- fun setRecommedationsLoadingState(smartspaceMediaLoadingModel: SmartspaceMediaLoadingModel) {
+ fun setRecommendationsLoadingState(smartspaceMediaLoadingModel: SmartspaceMediaLoadingModel) {
_recommendationsLoadingState.value = smartspaceMediaLoadingModel
+
+ addRecsLoadingToSortedMap(smartspaceMediaLoadingModel)
+ }
+
+ private fun addMediaLoadingToSortedMap(mediaDataLoadingModel: MediaDataLoadingModel) {
+ val instanceId =
+ when (mediaDataLoadingModel) {
+ is MediaDataLoadingModel.Loaded -> mediaDataLoadingModel.instanceId
+ is MediaDataLoadingModel.Removed -> mediaDataLoadingModel.instanceId
+ MediaDataLoadingModel.Unknown -> null
+ }
+ val sortedMap = TreeMap<MediaSortKeyModel, MediaCommonModel>(comparator)
+ sortedMap.putAll(
+ _sortedMedia.value.filter { (_, commonModel) ->
+ commonModel !is MediaCommonModel.MediaControl ||
+ commonModel.instanceId != instanceId
+ }
+ )
+
+ _selectedUserEntries.value[instanceId]?.let {
+ val sortKey =
+ MediaSortKeyModel(
+ isPrioritizedRec = false,
+ it.isPlaying,
+ it.playbackLocation,
+ it.active,
+ it.resumption,
+ it.lastActive,
+ it.notificationKey,
+ systemClock.currentTimeMillis(),
+ it.instanceId,
+ )
+
+ if (mediaDataLoadingModel is MediaDataLoadingModel.Loaded) {
+ sortedMap[sortKey] = MediaCommonModel.MediaControl(it.instanceId)
+ }
+ }
+
+ _sortedMedia.value = sortedMap
+ }
+
+ private fun addRecsLoadingToSortedMap(
+ smartspaceMediaLoadingModel: SmartspaceMediaLoadingModel
+ ) {
+ val isPrioritized: Boolean
+ val key: String?
+ when (smartspaceMediaLoadingModel) {
+ is SmartspaceMediaLoadingModel.Loaded -> {
+ isPrioritized = smartspaceMediaLoadingModel.isPrioritized
+ key = smartspaceMediaLoadingModel.key
+ }
+ is SmartspaceMediaLoadingModel.Removed -> {
+ isPrioritized = false
+ key = smartspaceMediaLoadingModel.key
+ }
+ SmartspaceMediaLoadingModel.Unknown -> {
+ isPrioritized = false
+ key = null
+ }
+ }
+ val sortedMap = TreeMap<MediaSortKeyModel, MediaCommonModel>(comparator)
+ sortedMap.putAll(
+ _sortedMedia.value.filter { (_, commonModel) ->
+ commonModel !is MediaCommonModel.MediaRecommendations || commonModel.key != key
+ }
+ )
+
+ key?.let {
+ val sortKey =
+ MediaSortKeyModel(
+ isPrioritizedRec = isPrioritized,
+ isPlaying = false,
+ active = _smartspaceMediaData.value.isActive,
+ )
+ if (smartspaceMediaLoadingModel is SmartspaceMediaLoadingModel.Loaded) {
+ sortedMap[sortKey] = MediaCommonModel.MediaRecommendations(key)
+ }
+ }
+
+ _sortedMedia.value = sortedMap
}
}
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 a30e582..5432a18 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 @@
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 @@
mediaFilterRepository.addMediaDataLoadingState(
MediaDataLoadingModel.Loaded(data.instanceId)
)
+
+ // Notify listeners
+ listeners.forEach { it.onMediaDataLoaded(key, oldKey, data) }
}
override fun onSmartspaceMediaDataLoaded(
@@ -171,6 +178,20 @@
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.
@@ -186,9 +207,10 @@
smartspaceMediaData.packageName,
smartspaceMediaData.instanceId
)
- mediaFilterRepository.setRecommedationsLoadingState(
+ mediaFilterRepository.setRecommendationsLoadingState(
SmartspaceMediaLoadingModel.Loaded(key, shouldPrioritizeMutable)
)
+ listeners.forEach { it.onSmartspaceMediaDataLoaded(key, data, shouldPrioritizeMutable) }
}
override fun onMediaDataRemoved(key: String) {
@@ -198,6 +220,8 @@
mediaFilterRepository.addMediaDataLoadingState(
MediaDataLoadingModel.Removed(instanceId)
)
+ // Only notify listeners if something actually changed
+ listeners.forEach { it.onMediaDataRemoved(key) }
}
}
}
@@ -212,6 +236,11 @@
mediaFilterRepository.addMediaDataLoadingState(
MediaDataLoadingModel.Loaded(lastActiveId, immediately)
)
+ listeners.forEach { listener ->
+ getKey(lastActiveId)?.let { lastActiveKey ->
+ listener.onMediaDataLoaded(lastActiveKey, lastActiveKey, it, immediately)
+ }
+ }
}
}
@@ -224,9 +253,10 @@
)
)
}
- mediaFilterRepository.setRecommedationsLoadingState(
+ mediaFilterRepository.setRecommendationsLoadingState(
SmartspaceMediaLoadingModel.Removed(key, immediately)
)
+ listeners.forEach { it.onSmartspaceMediaDataRemoved(key, immediately) }
}
@VisibleForTesting
@@ -240,6 +270,7 @@
mediaFilterRepository.addMediaDataLoadingState(
MediaDataLoadingModel.Removed(data.instanceId)
)
+ listeners.forEach { listener -> listener.onMediaDataRemoved(key) }
}
}
}
@@ -247,6 +278,7 @@
@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 @@
mediaFilterRepository.addMediaDataLoadingState(
MediaDataLoadingModel.Removed(instanceId)
)
+ getKey(instanceId)?.let {
+ listenersCopy.forEach { listener -> listener.onMediaDataRemoved(it) }
+ }
}
mediaFilterRepository.allUserEntries.value.forEach { (key, data) ->
@@ -268,6 +303,7 @@
mediaFilterRepository.addMediaDataLoadingState(
MediaDataLoadingModel.Loaded(data.instanceId)
)
+ listenersCopy.forEach { listener -> listener.onMediaDataLoaded(key, null, data) }
}
}
}
@@ -279,7 +315,7 @@
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 @@
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 @@
instanceId = smartspaceMediaData.instanceId,
)
)
- mediaDataManager.dismissSmartspaceRecommendation(
+ mediaDataProcessor.dismissSmartspaceRecommendation(
smartspaceMediaData.targetId,
delay = 0L,
)
@@ -317,6 +353,12 @@
}
}
+ /** 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 @@
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 c7cfb0b..0e2814b 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.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 @@
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 @@
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 cdcf363..b04e938 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
@@ -34,8 +34,10 @@
import com.android.systemui.media.controls.domain.pipeline.MediaSessionBasedFilter
import com.android.systemui.media.controls.domain.pipeline.MediaTimeoutListener
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
@@ -46,6 +48,7 @@
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.distinctUntilChanged
+import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.mapLatest
import kotlinx.coroutines.flow.stateIn
@@ -120,6 +123,10 @@
val recommendationsLoadingState: Flow<SmartspaceMediaLoadingModel> =
mediaFilterRepository.recommendationsLoadingState
+ /** The most recent sorted set for user media instances */
+ val sortedMedia: Flow<List<MediaCommonModel>> =
+ mediaFilterRepository.sortedMedia.map { it.values.toList() }
+
override fun start() {
if (!mediaFlags.isMediaControlsRefactorEnabled()) {
return
@@ -150,13 +157,19 @@
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)
}
@@ -201,9 +214,7 @@
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)
@@ -234,4 +245,12 @@
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/shared/model/MediaCommonModel.kt b/packages/SystemUI/src/com/android/systemui/media/controls/shared/model/MediaCommonModel.kt
new file mode 100644
index 0000000..83e2765
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/shared/model/MediaCommonModel.kt
@@ -0,0 +1,26 @@
+/*
+ * 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.shared.model
+
+import com.android.internal.logging.InstanceId
+
+/** Models any type of media. */
+sealed class MediaCommonModel {
+ data class MediaControl(val instanceId: InstanceId) : MediaCommonModel()
+
+ data class MediaRecommendations(val key: String) : MediaCommonModel()
+}
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 0000000..14a9179
--- /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 b315cac..7fced5f8 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 @@
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 @@
* 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 @@
)
}
+ 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 @@
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 1e67a77..82099e6 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 @@
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 7c59995..d74506d 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 @@
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.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 @@
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 @@
}
},
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 @@
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 @@
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 @@
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 @@
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 @@
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 9029a65..d1014e8 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 @@
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 @@
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 d4bd6da..4e77d13 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 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 @@
/** 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 ffbc560..7ccdf0a 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 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 @@
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 a0607e9..1f4838e 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.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 @@
@Nullable
private View mFooterActionsView;
- private final SceneContainerFlags mSceneContainerFlags;
-
@Inject
public QSImpl(RemoteInputQuickSettingsDisabler remoteInputQsDisabler,
SysuiStatusBarStateController statusBarStateController, CommandQueue commandQueue,
@@ -188,8 +186,7 @@
FooterActionsViewModel.Factory footerActionsViewModelFactory,
FooterActionsViewBinder footerActionsViewBinder,
LargeScreenShadeInterpolator largeScreenShadeInterpolator,
- FeatureFlags featureFlags,
- SceneContainerFlags sceneContainerFlags) {
+ FeatureFlags featureFlags) {
mRemoteInputQuickSettingsDisabler = remoteInputQsDisabler;
mQsMediaHost = qsMediaHost;
mQqsMediaHost = qqsMediaHost;
@@ -205,8 +202,7 @@
mFooterActionsViewModelFactory = footerActionsViewModelFactory;
mFooterActionsViewBinder = footerActionsViewBinder;
mListeningAndVisibilityLifecycleOwner = new ListeningAndVisibilityLifecycleOwner();
- mSceneContainerFlags = sceneContainerFlags;
- if (mSceneContainerFlags.isEnabled()) {
+ if (SceneContainerFlag.isEnabled()) {
mStatusBarState = StatusBarState.SHADE;
}
}
@@ -224,7 +220,7 @@
mQSPanelController.init();
mQuickQSPanelController.init();
- if (!mSceneContainerFlags.isEnabled()) {
+ if (!SceneContainerFlag.isEnabled()) {
mQSFooterActionsViewModel = mFooterActionsViewModelFactory
.create(mListeningAndVisibilityLifecycleOwner);
bindFooterActionsView(mRootView);
@@ -249,7 +245,7 @@
mScrollListener.onQsPanelScrollChanged(scrollY);
}
});
- mQSPanelScrollView.setScrollingEnabled(!mSceneContainerFlags.isEnabled());
+ mQSPanelScrollView.setScrollingEnabled(!SceneContainerFlag.isEnabled());
mHeader = mRootView.findViewById(R.id.header);
mFooter = qsComponent.getQSFooter();
@@ -509,7 +505,7 @@
@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 @@
}
private void setKeyguardShowing(boolean keyguardShowing) {
- if (!mSceneContainerFlags.isEnabled()) {
+ if (!SceneContainerFlag.isEnabled()) {
if (DEBUG) Log.d(TAG, "setKeyguardShowing " + keyguardShowing);
mLastQSExpansion = -1;
@@ -651,7 +647,7 @@
@Override
public int getHeightDiff() {
- if (mSceneContainerFlags.isEnabled()) {
+ if (SceneContainerFlag.isEnabled()) {
return mQSPanelController.getViewBottom() - mHeader.getBottom()
+ mHeader.getPaddingBottom();
} else {
@@ -720,7 +716,7 @@
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 @@
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 @@
// 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 @@
@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 b8c3c1a..e24caf1 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.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 @@
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 @@
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 1d92d78..bb40d3e 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 @@
@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 34b1b2d..a222b3c 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.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 @@
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 @@
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 4ece7b6..1ddc094 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.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 @@
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 @@
@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 @@
// 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 @@
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 @@
KeyguardUnlockAnimationController sysuiUnlockAnimationController,
InWindowLauncherUnlockAnimationManager inWindowLauncherUnlockAnimationManager,
AssistUtils assistUtils,
- SceneContainerFlags sceneContainerFlags,
DumpManager dumpManager,
Optional<UnfoldTransitionProgressForwarder> unfoldTransitionProgressForwarder,
BroadcastDispatcher broadcastDispatcher
@@ -624,7 +622,6 @@
}
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 afd0746..8277c73 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 @@
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 62b0914..69f9443 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.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 @@
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 0665c9e..d202c24 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 @@
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 c736707..1cf1c18 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.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 @@
private val headsUpManager: HeadsUpManager,
private val powerInteractor: PowerInteractor,
private val activeNotificationsInteractor: ActiveNotificationsInteractor,
- sceneContainerFlags: SceneContainerFlags,
sceneInteractorProvider: Provider<SceneInteractor>,
) : CoreStartable {
@@ -68,7 +67,7 @@
* 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 0e66c28..4774eb3 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.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 @@
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 @@
) : CoreStartable {
override fun start() {
- if (flags.isEnabled()) {
+ if (SceneContainerFlag.isEnabled) {
sceneLogger.logFrameworkEnabled(isEnabled = true)
hydrateVisibility()
automaticallySwitchScenes()
@@ -124,16 +123,18 @@
} 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 @@
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
similarity index 84%
rename from packages/SystemUI/src/com/android/systemui/scene/shared/flag/SceneContainerFlags.kt
rename to packages/SystemUI/src/com/android/systemui/scene/shared/flag/SceneContainerFlag.kt
index cff11a7..234eda8 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 @@
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.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 @@
*/
@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 @@
}
}
}
-
-@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 67dc0cc..259a8bf 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.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 @@
viewModel: SceneContainerViewModel,
containerConfig: SceneContainerConfig,
sharedNotificationContainer: SharedNotificationContainer,
- flags: SceneContainerFlags,
scenes: Set<Scene>,
layoutInsetController: LayoutInsetsController,
sceneDataSourceDelegator: SceneDataSourceDelegator,
@@ -44,7 +42,6 @@
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 809ac2e..2ef9b73 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.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 @@
windowInsets: StateFlow<WindowInsets?>,
containerConfig: SceneContainerConfig,
sharedNotificationContainer: SharedNotificationContainer,
- flags: SceneContainerFlags,
scenes: Set<Scene>,
onVisibilityChangedInternal: (isVisible: Boolean) -> Unit,
dataSourceDelegator: SceneDataSourceDelegator,
@@ -115,7 +114,7 @@
// 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 706ac9c..b43a1d2 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.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.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 @@
@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 @@
(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 fb32b9f..adcb14a 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.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 @@
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 @@
ShadeWindowLogger logger,
Lazy<SelectedUserInteractor> userInteractor,
UserTracker userTracker,
- SceneContainerFlags sceneContainerFlags,
Lazy<CommunalInteractor> communalInteractor) {
mContext = context;
mWindowRootViewComponentFactory = windowRootViewComponentFactory;
@@ -186,7 +184,6 @@
dumpManager.registerCriticalDumpable("{slow}NotificationShadeWindowControllerImpl", this);
mAuthController = authController;
mUserInteractor = userInteractor;
- mSceneContainerFlags = sceneContainerFlags;
mCommunalInteractor = communalInteractor;
mLastKeyguardRotationAllowed = mKeyguardStateController.isKeyguardScreenRotationAllowed();
mLockScreenDisplayTimeout = context.getResources()
@@ -289,7 +286,7 @@
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/ShadeModule.kt b/packages/SystemUI/src/com/android/systemui/shade/ShadeModule.kt
index 648d4b5..a0c9391 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 @@
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 @@
@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 @@
@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 @@
@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 @@
@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 @@
@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 @@
@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 @@
@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 f5dd5e4..bc23778 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.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 @@
@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 @@
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 @@
@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 980f665a..6800c61 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.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 @@
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 @@
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/dagger/CentralSurfacesDependenciesModule.java b/packages/SystemUI/src/com/android/systemui/statusbar/dagger/CentralSurfacesDependenciesModule.java
index e5b6497..594c191 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.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.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.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 @@
@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 9e0b16c..feb9afb 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 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 5fbcebd..35afda7 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.notification.collection.NotificationEntry;
import com.android.systemui.util.time.SystemClock;
+import java.util.concurrent.Executor;
+
import javax.inject.Inject;
/**
@@ -58,10 +60,22 @@
}
/**
- * 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 @@
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 @@
}
@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 741102b..cf5366b 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.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 @@
class SharedNotificationContainerBinder
@Inject
constructor(
- private val sceneContainerFlags: SceneContainerFlags,
private val controller: NotificationStackScrollLayoutController,
private val notificationStackSizeCalculator: NotificationStackSizeCalculator,
private val notificationScrollViewBinder: NotificationScrollViewBinder,
@@ -130,7 +128,7 @@
.collect { controller.setMaxDisplayedNotifications(it) }
}
- if (!sceneContainerFlags.isEnabled()) {
+ if (!SceneContainerFlag.isEnabled) {
launch {
viewModel.bounds.collect {
val animate =
@@ -166,7 +164,7 @@
}
}
- 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 b284179..ca19da5 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.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 @@
dumpManager: DumpManager,
private val interactor: NotificationStackAppearanceInteractor,
shadeInteractor: ShadeInteractor,
- flags: SceneContainerFlags,
featureFlags: FeatureFlagsClassic,
private val keyguardInteractor: KeyguardInteractor,
) : FlowDumperImpl(dumpManager) {
@@ -51,7 +50,7 @@
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 08b65e3..cb3c03e 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.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 @@
private final ColorExtractor.OnColorsChangedListener mOnColorsChangedListener =
(extractor, which) -> updateTheme();
-
- private final SceneContainerFlags mSceneContainerFlags;
-
private final BrightnessMirrorShowingInteractor mBrightnessMirrorShowingInteractor;
/**
@@ -708,7 +704,6 @@
UserTracker userTracker,
Provider<FingerprintManager> fingerprintManager,
ActivityStarter activityStarter,
- SceneContainerFlags sceneContainerFlags,
BrightnessMirrorShowingInteractor brightnessMirrorShowingInteractor
) {
mContext = context;
@@ -804,7 +799,6 @@
mUserTracker = userTracker;
mFingerprintManager = fingerprintManager;
mActivityStarter = activityStarter;
- mSceneContainerFlags = sceneContainerFlags;
mBrightnessMirrorShowingInteractor = brightnessMirrorShowingInteractor;
mLockscreenShadeTransitionController = lockscreenShadeTransitionController;
@@ -1088,7 +1082,7 @@
mJavaAdapter.alwaysCollectFlow(
mCommunalInteractor.isIdleOnCommunal(),
mIdleOnCommunalConsumer);
- if (mSceneContainerFlags.isEnabled()) {
+ if (SceneContainerFlag.isEnabled()) {
mJavaAdapter.alwaysCollectFlow(
mBrightnessMirrorShowingInteractor.isShowing(),
this::setBrightnessMirrorShowing
@@ -1277,7 +1271,7 @@
// 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/PhoneStatusBarViewController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewController.kt
index 92fd90a..5206e46 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.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 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 @@
// 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 @@
@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 @@
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 8e8de46..d1189e1 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.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 @@
ShadeInteractor shadeInteractor,
Provider<SceneInteractor> sceneInteractor,
JavaAdapter javaAdapter,
- SceneContainerFlags sceneContainerFlags,
UnlockedScreenOffAnimationController unlockedScreenOffAnimationController,
PrimaryBouncerInteractor primaryBouncerInteractor,
AlternateBouncerInteractor alternateBouncerInteractor
@@ -130,7 +129,7 @@
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 a0c5618..5f08afd 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 @@
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 08ed030..054116d 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.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 @@
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 @@
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 40641be..a0291b8 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 @@
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 1b56702..0c2abd9 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 @@
// 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 0d3682c..fbbd2b9 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 @@
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 @@
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 3e2e564..a8e4496 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 @@
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 @@
/** 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 @@
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 a249961..319b615 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 @@
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 9d81b96..99b5a4b 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 @@
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/KeyguardStatusViewControllerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardStatusViewControllerTest.java
index 11fe862..b2828a4 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 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 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 f924ab4..bcea411 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.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 @@
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 @@
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 @@
mFeatureFlags,
mPrimaryBouncerInteractor,
mContext,
- () -> mDeviceEntryInteractor,
- mKosmos.getFakeSceneContainerFlags()
+ () -> mDeviceEntryInteractor
);
}
@@ -225,6 +228,7 @@
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 8689842..255c7d9 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 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 @@
@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 @@
}
@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 @@
}
@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 f490f3c..cbad133 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.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 @@
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 30c5e6e..d3cc232 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.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 @@
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 238a76e..415da02 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.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 @@
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 a46f755..709f779 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.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.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 @@
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 @@
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 b0aace6..b50d248 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.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 @@
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 9266af4..dc7f372 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.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.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 @@
@Before
fun setup() {
mSetFlagsRule.enableFlags(FLAG_SIDEFPS_CONTROLLER_REFACTOR)
- kosmos.fakeSceneContainerFlags.enabled = false
primaryBouncerInteractor =
PrimaryBouncerInteractor(
@@ -127,7 +126,6 @@
testScope.backgroundScope,
mContext,
deviceEntryFingerprintAuthRepository,
- kosmos.fakeSceneContainerFlags,
kosmos.sceneInteractor,
primaryBouncerInteractor,
alternateBouncerInteractor,
@@ -168,15 +166,14 @@
}
@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 @@
}
@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 3f05bfa..9ccf212 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 @@
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 @@
{ 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 66aa572..5e3a142 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 @@
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/media/controls/domain/pipeline/MediaDataFilterImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/controls/domain/pipeline/MediaDataFilterImplTest.kt
index fe8fdc0..8f73811 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 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 @@
@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
@@ -101,7 +103,7 @@
MediaPlayerData.clear()
whenever(mediaFlags.isPersistentSsCardEnabled()).thenReturn(false)
testScope = TestScope()
- repository = MediaFilterRepository()
+ repository = MediaFilterRepository(FakeSystemClock())
mediaDataFilter =
MediaDataFilterImpl(
context,
@@ -114,7 +116,8 @@
mediaFlags,
repository,
)
- mediaDataFilter.mediaDataManager = mediaDataManager
+ mediaDataFilter.mediaDataProcessor = mediaDataProcessor
+ mediaDataFilter.addListener(listener)
// Start all tests as main user
setUser(USER_MAIN)
@@ -167,6 +170,8 @@
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 @@
mediaDataFilter.onMediaDataLoaded(KEY, null, dataGuest)
+ verify(listener, never())
+ .onMediaDataLoaded(any(), any(), any(), anyBoolean(), anyInt(), anyBoolean())
assertThat(mediaDataLoadedStates).isNotEqualTo(mediaLoadedStatesModel)
}
@@ -196,6 +203,7 @@
mediaLoadedStatesModel.remove(MediaDataLoadingModel.Loaded(dataMain.instanceId))
mediaDataFilter.onMediaDataRemoved(KEY)
+ verify(listener).onMediaDataRemoved(eq(KEY))
assertThat(mediaDataLoadedStates).isEqualTo(mediaLoadedStatesModel)
}
@@ -208,6 +216,7 @@
mediaDataFilter.onMediaDataLoaded(KEY, null, dataGuest)
mediaDataFilter.onMediaDataRemoved(KEY)
+ verify(listener, never()).onMediaDataRemoved(eq(KEY))
assertThat(mediaDataLoadedStates).isEmpty()
}
@@ -226,6 +235,7 @@
setUser(USER_GUEST)
// THEN we should remove the main user's media
+ verify(listener).onMediaDataRemoved(eq(KEY))
assertThat(mediaDataLoadedStates).isEmpty()
}
@@ -243,6 +253,20 @@
// 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 @@
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 @@
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 @@
)
.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 @@
)
.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 @@
)
.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 @@
)
.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 @@
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 @@
)
.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 @@
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 @@
)
)
.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 @@
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 @@
.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 @@
mediaDataFilter.onSmartspaceMediaDataLoaded(SMARTSPACE_KEY, smartspaceData)
mediaDataFilter.onSmartspaceMediaDataRemoved(SMARTSPACE_KEY)
+ verify(listener).onSmartspaceMediaDataRemoved(SMARTSPACE_KEY)
assertThat(recommendationsLoadingState).isEqualTo(recommendationsLoadingModel)
assertThat(
hasActiveMediaOrRecommendation(
@@ -746,15 +812,29 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
.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 @@
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 @@
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/domain/pipeline/MediaDataProcessorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/controls/domain/pipeline/MediaDataProcessorTest.kt
index 5c275b4..ffb50c1 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/controls/domain/pipeline/MediaDataProcessorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/controls/domain/pipeline/MediaDataProcessorTest.kt
@@ -210,7 +210,7 @@
)
testDispatcher = UnconfinedTestDispatcher()
testScope = TestScope(testDispatcher)
- mediaFilterRepository = MediaFilterRepository()
+ mediaFilterRepository = MediaFilterRepository(clock)
mediaDataRepository = MediaDataRepository(mediaFlags, dumpManager)
mediaDataProcessor =
MediaDataProcessor(
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 a73bb2c..e5d3082 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 @@
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 @@
@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 @@
@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 @@
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 e4a4836..6956bea 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 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.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 @@
@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 @@
@Before
public void setup() {
MockitoAnnotations.initMocks(this);
- when(mSceneContainerFlags.isEnabled()).thenReturn(false);
mUnderTest = instantiate();
@@ -496,8 +494,8 @@
}
@Test
+ @EnableSceneContainer
public void testSceneContainerFlagsEnabled_FooterActionsRemoved_controllerNotStarted() {
- when(mSceneContainerFlags.isEnabled()).thenReturn(true);
clearInvocations(
mFooterActionsViewBinder, mFooterActionsViewModel, mFooterActionsViewModelFactory);
QSImpl other = instantiate();
@@ -513,9 +511,8 @@
}
@Test
+ @EnableSceneContainer
public void testSceneContainerFlagsEnabled_statusBarStateIsShade() {
- when(mSceneContainerFlags.isEnabled()).thenReturn(true);
-
mUnderTest.onStateChanged(KEYGUARD);
assertThat(mUnderTest.getStatusBarState()).isEqualTo(SHADE);
@@ -524,9 +521,8 @@
}
@Test
+ @EnableSceneContainer
public void testSceneContainerFlagsEnabled_isKeyguardState_alwaysFalse() {
- when(mSceneContainerFlags.isEnabled()).thenReturn(true);
-
mUnderTest.onStateChanged(KEYGUARD);
assertThat(mUnderTest.isKeyguardState()).isFalse();
@@ -559,8 +555,8 @@
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 a60494f..0275643 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.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.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 @@
@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 @@
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 8acde36..4915e55 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.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 @@
@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 @@
`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 1313227..387f27d 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.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 @@
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 cf7c6f4..b89ccef 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.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 @@
@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 @@
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 @@
mShadeWindowLogger,
() -> mSelectedUserInteractor,
mUserTracker,
- mSceneContainerFlags,
() -> communalInteractor) {
@Override
protected boolean isDebuggable() {
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 20d877e..77ad17a 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.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 @@
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 433c95a..6bdd3ef 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.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 @@
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 56fc0c7..a05a23b 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 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 @@
@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 @@
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 d2fc087..be5af88 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.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.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 @@
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 @@
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 eb692eb..0e24ed4 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.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 @@
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 54a6523..bb68ec5 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 @@
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 91e4666..7332bc3 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 @@
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 db053d8..9e2856d 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 @@
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 65a960b..1b85dfa 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.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.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 @@
@SmallTest
@RunWith(AndroidTestingRunner::class)
@RunWithLooper
+@EnableSceneContainer
class NotificationGutsManagerWithScenesTest : SysuiTestCase() {
private val testNotificationChannel =
NotificationChannel(
@@ -143,8 +144,6 @@
@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 @@
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 9a7b8ec..745d20d 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.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 @@
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 4bfd7e3..df82df8 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.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 @@
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 783bf80..5c65103 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.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.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.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 @@
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 @@
// 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 @@
((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 @@
mUserTracker,
() -> mFingerprintManager,
mActivityStarter,
- mSceneContainerFlags,
mBrightnessMirrorShowingInteractor
);
mScreenLifecycle.addObserver(mCentralSurfaces.mScreenObserver);
@@ -1094,25 +1097,24 @@
}
@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 dc3db4c..a6a4f24 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 @@
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 @@
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 1463680..d365663 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.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.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.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 @@
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 feff046..1ec1765 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.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 @@
@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 42bbe3e..c4ab943 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.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 @@
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 @@
repo,
iconsInteractor,
deviceProvisioningInteractor,
+ wifiInteractor,
testScope.backgroundScope,
)
}
@@ -103,6 +112,7 @@
repo,
iconsInteractor,
deviceProvisioningInteractor,
+ wifiInteractor,
testScope.backgroundScope,
)
@@ -150,6 +160,7 @@
repo,
iconsInteractor,
deviceProvisioningInteractor,
+ wifiInteractor,
testScope.backgroundScope,
)
@@ -205,6 +216,7 @@
repo,
iconsInteractor,
deviceProvisioningInteractor,
+ wifiInteractor,
testScope.backgroundScope,
)
@@ -337,6 +349,7 @@
repo,
iconsInteractor,
deviceProvisioningInteractor,
+ wifiInteractor,
testScope.backgroundScope,
)
@@ -353,4 +366,28 @@
// 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 1d6cd37..64f19b6 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.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 @@
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 @@
repo,
mobileIconsInteractor,
deviceProvisioningInteractor,
+ wifiInteractor,
testScope.backgroundScope,
)
@@ -253,4 +262,40 @@
// 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 69536c5..bdd3d18 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.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 @@
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 28adbce..383f4a3 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 @@
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 b9c7e61..fd513c9 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 @@
@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 ba72716..2955384 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.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.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 @@
}
@Test
- fun unfold_firstFoldEventAnimationsEnabledOnScreenTurnedOnAndTransitionStarted_eventNotPropagated() {
+ fun firstFoldEventAnimationsEnabledOnScreenTurnedOnAndTransitionStarted_eventNotPropagated() {
setAnimationsEnabled(true)
sendFoldEvent(folded = false)
@@ -118,7 +115,7 @@
}
@Test
- fun unfold_secondFoldEventAnimationsEnabledOnScreenTurnedOnAndTransitionStarted_eventPropagated() {
+ fun secondFoldEventAnimationsEnabledOnScreenTurnedOnAndTransitionStarted_eventPropagated() {
setAnimationsEnabled(true)
sendFoldEvent(folded = true)
sendFoldEvent(folded = false)
@@ -131,7 +128,7 @@
}
@Test
- fun unfold_unfoldFoldUnfoldAnimationsEnabledOnScreenTurnedOnAndTransitionStarted_eventPropagated() {
+ fun unfoldFoldUnfoldAnimationsEnabledOnScreenTurnedOnAndTransitionStarted_eventPropagated() {
setAnimationsEnabled(true)
sendFoldEvent(folded = false)
sendFoldEvent(folded = true)
@@ -196,4 +193,4 @@
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 6ec0251..0c452eb 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 @@
@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 2bc05fc..e5f619b 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.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 @@
@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 d864d53..70ec050 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.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 @@
@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 2f29b3b..451bd24 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.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 @@
@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 @@
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 95c934e..4486402 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.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 @@
@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 f484ea0..cd4d7b5 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.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 @@
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 c24c86c..d9a0c4b 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.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 @@
@Mock
private Display mDefaultDisplay;
@Mock
- private SceneContainerFlags mSceneContainerFlags;
- @Mock
private LargeScreenHeaderHelper mLargeScreenHeaderHelper;
private final KosmosJavaAdapter mKosmos = new KosmosJavaAdapter(this);
@@ -430,14 +426,12 @@
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 @@
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 de7b14d..0682361 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.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 @@
@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 5c3e1f4..60d97d1 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 c116bbd..7482c0f 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 4b6ef37..3dd382f 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.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 @@
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 e21c766..2e751cc 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.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 @@
@JvmStatic
fun create(
featureFlags: FakeFeatureFlags = FakeFeatureFlags(),
- sceneContainerFlags: SceneContainerFlags = FakeSceneContainerFlags(),
repository: FakeKeyguardRepository = FakeKeyguardRepository(),
commandQueue: FakeCommandQueue = FakeCommandQueue(),
bouncerRepository: FakeKeyguardBouncerRepository = FakeKeyguardBouncerRepository(),
@@ -88,7 +84,6 @@
repository = repository,
commandQueue = commandQueue,
featureFlags = featureFlags,
- sceneContainerFlags = sceneContainerFlags,
bouncerRepository = bouncerRepository,
configurationRepository = configurationRepository,
shadeRepository = shadeRepository,
@@ -96,13 +91,12 @@
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 @@
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 2a0c01c..9426718 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.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 @@
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/DeviceEntryIconViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryIconViewModelKosmos.kt
index 709f864..58b0ff8 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.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 @@
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 fdc3e0a..162f278 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.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 @@
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 @@
}
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/data/repository/MediaFilterRepositoryKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/media/controls/data/repository/MediaFilterRepositoryKosmos.kt
index 7ce810e..7dab5c2 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/media/controls/data/repository/MediaFilterRepositoryKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/media/controls/data/repository/MediaFilterRepositoryKosmos.kt
@@ -17,5 +17,6 @@
package com.android.systemui.media.controls.data.repository
import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.util.time.systemClock
-val Kosmos.mediaFilterRepository by Kosmos.Fixture { MediaFilterRepository() }
+val Kosmos.mediaFilterRepository by Kosmos.Fixture { MediaFilterRepository(systemClock) }
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 da2170c..2f3d3c3 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 @@
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 6f652f2..e88d22a 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 @@
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 ded7256..0000000
--- 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 979d8e7..0000000
--- 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 07e2d6b..543d5b6 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.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 @@
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 b249211..f0eea38 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.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 @@
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 cf800d0..9c3f510 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.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 @@
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
similarity index 93%
rename from packages/SystemUI/tests/src/com/android/systemui/unfold/TestUnfoldTransitionProvider.kt
rename to packages/SystemUI/tests/utils/src/com/android/systemui/unfold/FakeUnfoldTransitionProvider.kt
index 56c6245..94f0c44 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 @@
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 7c54a57..a0f5b58 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 @@
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 0000000..1ef3816
--- /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 0000000..ed97c60
--- /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 0000000..5f8c660
--- /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 23e269a..cbbce1a 100644
--- a/packages/WallpaperBackup/src/com/android/wallpaperbackup/WallpaperBackupAgent.java
+++ b/packages/WallpaperBackup/src/com/android/wallpaperbackup/WallpaperBackupAgent.java
@@ -19,6 +19,7 @@
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.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 @@
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 @@
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 @@
}
}
+ 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 @@
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 @@
// 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 @@
}
}
- 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 @@
}
}
- 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 @@
}
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 @@
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 @@
}
}
+ /**
+ * 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 @@
mEventLogger.onLockImageWallpaperRestoreFailed(error);
}
}
+
private Rect parseCropHint(File wallpaperInfo, String sectionTag) {
Rect cropHint = new Rect();
try (FileInputStream stream = new FileInputStream(wallpaperInfo)) {
@@ -681,7 +900,7 @@
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 @@
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 ec9223c..3ecdf3f 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.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 @@
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 be2ad21..d279bd5 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 @@
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 @@
+ " 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 ced10fb..70ecc05 100644
--- a/services/autofill/bugfixes.aconfig
+++ b/services/autofill/bugfixes.aconfig
@@ -41,3 +41,10 @@
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 3f3ff4a..3a38406 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 @@
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 cfb7f337..af49df6 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.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 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 @@
}
}
- clearPendingDevicePresenceEventsByUserId(userId);
+ removePendingDevicePresenceEventsByUserId(userId);
}
private List<DevicePresenceEvent> getPendingDevicePresenceEventsByUserId(int userId) {
@@ -1077,9 +1081,11 @@
}
}
- 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 5ec6b72..ee5d49b 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 @@
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 1c169a0..5c93181 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 @@
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 8f1277b..4e709a7 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 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 b24caf4..3c2918f 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 @@
@NonNull
ExternalDisplayStatsService getExternalDisplayStatsService();
+
+ void onExternalDisplayReadyToBeEnabled(int displayId);
}
@NonNull
@@ -185,6 +187,10 @@
return;
}
+ if (enabled) {
+ mInjector.onExternalDisplayReadyToBeEnabled(logicalDisplay.getDisplayIdLocked());
+ }
+
mLogicalDisplayMapper.setDisplayEnabledLocked(logicalDisplay, enabled);
}
@@ -217,6 +223,7 @@
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 a862b6e..9064763 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 @@
}
/**
+ * 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 @@
}
}
+
+ void onExternalDisplayReadyToBeEnabled(int displayId) {
+ DisplayInfo displayInfo = getDisplayInfo(displayId);
+ updateDisplaysPeakRefreshRateAndResolution(displayInfo);
+ addDisplaysSynchronizedPeakRefreshRate(displayInfo);
+ }
+
@Override
public void onDisplayAdded(int displayId) {
updateVrrStatus(displayId);
@@ -1386,8 +1400,6 @@
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 6b33199..2583d73 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 @@
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 @@
@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 @@
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 @@
// 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 @@
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 @@
@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 @@
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 @@
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 @@
if (!isCurrentUser) {
return;
}
- mSettings = queryInputMethodServicesInternal(mContext, userId,
- newAdditionalSubtypeMap, DirectBootAwareness.AUTO);
postInputMethodSettingUpdatedLocked(false /* resetDefaultEnabledIme */);
boolean changed = false;
@@ -1291,8 +1290,7 @@
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 @@
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 @@
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 @@
@GuardedBy("ImfLock.class")
@UserIdInt
int getCurrentImeUserIdLocked() {
- return mSettings.getUserId();
+ return mCurrentUserId;
}
private final class InkWindowInitializer implements Runnable {
@@ -1452,12 +1449,13 @@
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 @@
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 @@
// 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 @@
// 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 @@
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 @@
}
if (!mSystemReady) {
mSystemReady = true;
- final int currentUserId = mSettings.getUserId();
+ final int currentUserId = mCurrentUserId;
mStatusBarManagerInternal =
LocalServices.getService(StatusBarManagerInternal.class);
hideStatusBarIconLocked();
@@ -1604,7 +1600,7 @@
// 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 @@
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 @@
}
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 @@
}
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 @@
}
// 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 @@
@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 @@
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 @@
// 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 @@
}
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 @@
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 @@
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 @@
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 @@
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 @@
} 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 @@
return false;
}
if (mWindowManagerInternal.isKeyguardShowingAndNotOccluded()
- && mWindowManagerInternal.isKeyguardSecure(mSettings.getUserId())) {
+ && mWindowManagerInternal.isKeyguardSecure(mCurrentUserId)) {
return false;
}
if ((visibility & InputMethodService.IME_ACTIVE) == 0
@@ -2738,7 +2730,8 @@
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 @@
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 @@
@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 @@
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 @@
// 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 @@
}
// 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 @@
@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 @@
// 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 @@
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 @@
}
// 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 @@
&& 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 @@
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 @@
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 @@
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 @@
// 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 @@
&& !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 @@
@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 @@
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 @@
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 @@
}
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 @@
if (isCurrentUser) {
final long ident = Binder.clearCallingIdentity();
try {
- mSettings = newSettings;
postInputMethodSettingUpdatedLocked(false /* resetDefaultEnabledIme */);
} finally {
Binder.restoreCallingIdentity(ident);
@@ -4115,7 +4110,7 @@
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 @@
}
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 @@
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 @@
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 @@
@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 @@
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 @@
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 @@
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 @@
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 @@
}
}
- 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 @@
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 @@
@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 @@
// 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 @@
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 @@
*/
@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 @@
// 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 @@
@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 @@
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 @@
Manifest.permission.INTERACT_ACROSS_USERS_FULL, null);
}
synchronized (ImfLock.class) {
- if (mSettings.getUserId() == userId) {
+ if (mCurrentUserId == userId) {
return getCurrentInputMethodSubtypeLocked();
}
@@ -5308,26 +5314,27 @@
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 @@
@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 @@
@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 @@
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 @@
@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 @@
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 @@
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 @@
? 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 @@
}
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 @@
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 @@
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 @@
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 @@
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 @@
}
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 @@
}
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 bd73cb6..1938642 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 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 @@
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 @@
}
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 @@
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 8075ae0..b48cad2 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.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 @@
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 @@
private AppOpsManager.OnOpChangedListener mAppOpsListener;
+ private ModuleInfo mAdservicesModuleInfo;
+
static class Archive {
final SparseArray<Boolean> mEnabled;
final int mBufferSize;
@@ -2508,12 +2515,8 @@
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 @@
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 @@
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 24c1d59..f0358d1 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 @@
* 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 50ca984..461bd9c 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 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 68e0eaa..7756801 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.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 @@
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 @@
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 4c653f6..fe9c3f2 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.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.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 @@
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 @@
@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 @@
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 @@
@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 d060c7c..54cb9c9 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 @@
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 @@
}
@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/tracing/TracingServiceProxy.java b/services/core/java/com/android/server/tracing/TracingServiceProxy.java
index 10e868d..c1d92cf 100644
--- a/services/core/java/com/android/server/tracing/TracingServiceProxy.java
+++ b/services/core/java/com/android/server/tracing/TracingServiceProxy.java
@@ -119,8 +119,13 @@
}
@Override
- public void onStart() {
- publishBinderService(TRACING_SERVICE_PROXY_BINDER_NAME, mTracingServiceProxy);
+ public void onStart() {}
+
+ @Override
+ public void onBootPhase(int phase) {
+ if (phase == SystemService.PHASE_THIRD_PARTY_APPS_CAN_START) {
+ publishBinderService(TRACING_SERVICE_PROXY_BINDER_NAME, mTracingServiceProxy);
+ }
}
private void notifyTraceur(boolean sessionStolen) {
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 c5d3333..3619253 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 @@
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 @@
* <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 @@
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 @@
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 @@
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 @@
// 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 @@
final PacketLossCalculationResult calculateResult =
mPacketLossCalculator.getPacketLossRatePercentage(
- mLastIpSecTransformState, state, getLogPrefix());
+ mLastIpSecTransformState,
+ state,
+ mMaxSeqNumIncreasePerSecond,
+ getLogPrefix());
if (calculateResult.getResultType() == PACKET_LOSS_RATE_INVALID) {
return;
@@ -356,11 +406,18 @@
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 PacketLossCalculationResult getPacketLossRatePercentage(
@NonNull IpSecTransformState oldState,
@NonNull IpSecTransformState newState,
+ int maxSeqNumIncreasePerSecond,
String logPrefix) {
logVIpSecTransform("oldState", oldState, logPrefix);
logVIpSecTransform("newState", newState, logPrefix);
@@ -392,6 +450,22 @@
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 @@
+ " 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 @@
}
final int percent = 100 - (int) (actualPktCntDiff * 100 / expectedPktCntDiff);
- return PacketLossCalculationResult.valid(percent);
+ return isUnusualSeqNumLeap
+ ? PacketLossCalculationResult.unusualSeqNumLeap(percent)
+ : PacketLossCalculationResult.valid(percent);
}
}
@@ -462,6 +543,11 @@
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 a1b212f..b9b1060 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 @@
}
}
+ 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/webkit/SystemImpl.java b/services/core/java/com/android/server/webkit/SystemImpl.java
index 5e34596..a821f545 100644
--- a/services/core/java/com/android/server/webkit/SystemImpl.java
+++ b/services/core/java/com/android/server/webkit/SystemImpl.java
@@ -35,6 +35,7 @@
import android.provider.Settings;
import android.util.AndroidRuntimeException;
import android.util.Log;
+import android.util.Slog;
import android.webkit.UserPackage;
import android.webkit.WebViewFactory;
import android.webkit.WebViewProviderInfo;
@@ -201,6 +202,7 @@
ActivityManager.getService().killPackageDependents(packageName,
UserHandle.USER_ALL);
} catch (RemoteException e) {
+ Slog.wtf(TAG, "failed to call killPackageDependents for " + packageName, e);
}
}
diff --git a/services/core/java/com/android/server/webkit/WebViewUpdateServiceImpl.java b/services/core/java/com/android/server/webkit/WebViewUpdateServiceImpl.java
index 532ff98..dcf20f9 100644
--- a/services/core/java/com/android/server/webkit/WebViewUpdateServiceImpl.java
+++ b/services/core/java/com/android/server/webkit/WebViewUpdateServiceImpl.java
@@ -186,9 +186,12 @@
}
onWebViewProviderChanged(mCurrentWebViewPackage);
}
+ } catch (WebViewPackageMissingException e) {
+ Slog.e(TAG, "Could not find valid WebView package to create relro with", e);
} catch (Throwable t) {
- // Log and discard errors at this stage as we must not crash the system server.
- Slog.e(TAG, "error preparing webview provider from system server", t);
+ // We don't know a case when this should happen but we log and discard errors at this
+ // stage as we must not crash the system server.
+ Slog.wtf(TAG, "error preparing webview provider from system server", t);
}
if (getCurrentWebViewPackage() == null) {
diff --git a/services/core/java/com/android/server/webkit/WebViewUpdateServiceImpl2.java b/services/core/java/com/android/server/webkit/WebViewUpdateServiceImpl2.java
index fb338ba..993597e 100644
--- a/services/core/java/com/android/server/webkit/WebViewUpdateServiceImpl2.java
+++ b/services/core/java/com/android/server/webkit/WebViewUpdateServiceImpl2.java
@@ -247,9 +247,12 @@
attemptRepair();
}
+ } catch (WebViewPackageMissingException e) {
+ Slog.e(TAG, "Could not find valid WebView package to create relro with", e);
} catch (Throwable t) {
- // Log and discard errors at this stage as we must not crash the system server.
- Slog.e(TAG, "error preparing webview provider from system server", t);
+ // We don't know a case when this should happen but we log and discard errors at this
+ // stage as we must not crash the system server.
+ Slog.wtf(TAG, "error preparing webview provider from system server", t);
}
}
diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java
index 42373aa..bf094ed 100644
--- a/services/core/java/com/android/server/wm/ActivityRecord.java
+++ b/services/core/java/com/android/server/wm/ActivityRecord.java
@@ -4479,6 +4479,7 @@
getDisplayContent().mOpeningApps.remove(this);
getDisplayContent().mUnknownAppVisibilityController.appRemovedOrHidden(this);
mWmService.mSnapshotController.onAppRemoved(this);
+ mAtmService.mStartingProcessActivities.remove(this);
mTaskSupervisor.getActivityMetricsLogger().notifyActivityRemoved(this);
mTaskSupervisor.mStoppingActivities.remove(this);
@@ -6221,6 +6222,10 @@
}
boolean shouldBeVisible() {
+ return shouldBeVisible(false /* ignoringKeyguard */);
+ }
+
+ boolean shouldBeVisible(boolean ignoringKeyguard) {
final Task task = getTask();
if (task == null) {
return false;
@@ -6228,7 +6233,7 @@
final boolean behindOccludedContainer = !task.shouldBeVisible(null /* starting */)
|| task.getOccludingActivityAbove(this) != null;
- return shouldBeVisible(behindOccludedContainer, false /* ignoringKeyguard */);
+ return shouldBeVisible(behindOccludedContainer, ignoringKeyguard);
}
void makeVisibleIfNeeded(ActivityRecord starting, boolean reportToClient) {
@@ -10510,8 +10515,7 @@
if (!getTurnScreenOnFlag()) {
return false;
}
- final Task rootTask = getRootTask();
- return mCurrentLaunchCanTurnScreenOn && rootTask != null
+ return mCurrentLaunchCanTurnScreenOn
&& mTaskSupervisor.getKeyguardController().checkKeyguardVisibility(this);
}
diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
index a0f615b..d984fb1 100644
--- a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
+++ b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
@@ -390,6 +390,9 @@
final VisibleActivityProcessTracker mVisibleActivityProcessTracker;
+ /** The starting activities which are waiting for their processes to attach. */
+ final ArrayList<ActivityRecord> mStartingProcessActivities = new ArrayList<>();
+
/* Global service lock used by the package the owns this service. */
final WindowManagerGlobalLock mGlobalLock = new WindowManagerGlobalLock();
/**
@@ -4331,6 +4334,9 @@
if (mDemoteTopAppReasons != 0) {
pw.println(" mDemoteTopAppReasons=" + mDemoteTopAppReasons);
}
+ if (!mStartingProcessActivities.isEmpty()) {
+ pw.println(" mStartingProcessActivities=" + mStartingProcessActivities);
+ }
}
if (!printedAnything) {
@@ -5178,6 +5184,13 @@
void startProcessAsync(ActivityRecord activity, boolean knownToBeDead, boolean isTop,
String hostingType) {
+ if (!mStartingProcessActivities.contains(activity)) {
+ mStartingProcessActivities.add(activity);
+ } else if (mProcessNames.get(
+ activity.processName, activity.info.applicationInfo.uid) != null) {
+ // The process is already starting. Wait for it to attach.
+ return;
+ }
try {
if (Trace.isTagEnabled(TRACE_TAG_WINDOW_MANAGER)) {
Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "dispatchingStartProcess:"
@@ -6174,7 +6187,20 @@
@Override
public void onProcessRemoved(String name, int uid) {
synchronized (mGlobalLockWithoutBoost) {
- mProcessNames.remove(name, uid);
+ final WindowProcessController proc = mProcessNames.remove(name, uid);
+ if (proc != null && !mStartingProcessActivities.isEmpty()) {
+ for (int i = mStartingProcessActivities.size() - 1; i >= 0; i--) {
+ final ActivityRecord r = mStartingProcessActivities.get(i);
+ if (uid == r.info.applicationInfo.uid && name.equals(r.processName)) {
+ Slog.w(TAG, proc + " is removed with pending start " + r);
+ mStartingProcessActivities.remove(i);
+ // If visible, finish it to avoid getting stuck on screen.
+ if (r.isVisibleRequested()) {
+ r.finishIfPossible("starting-proc-removed", false /* oomAdj */);
+ }
+ }
+ }
+ }
}
}
diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java
index 87c5b7b..00d42e0 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 @@
}
// 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/RootWindowContainer.java b/services/core/java/com/android/server/wm/RootWindowContainer.java
index e9a877e..1e88fe4 100644
--- a/services/core/java/com/android/server/wm/RootWindowContainer.java
+++ b/services/core/java/com/android/server/wm/RootWindowContainer.java
@@ -72,7 +72,6 @@
import static com.android.server.wm.RootWindowContainerProto.WINDOW_CONTAINER;
import static com.android.server.wm.Task.REPARENT_LEAVE_ROOT_TASK_IN_PLACE;
import static com.android.server.wm.Task.REPARENT_MOVE_ROOT_TASK_TO_FRONT;
-import static com.android.server.wm.TaskFragment.TASK_FRAGMENT_VISIBILITY_INVISIBLE;
import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_LAYOUT_REPEATS;
import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_WINDOW_TRACE;
import static com.android.server.wm.WindowManagerDebugConfig.TAG_WITH_CLASS_NAME;
@@ -270,8 +269,6 @@
private int mTmpTaskLayerRank;
private final RankTaskLayersRunnable mRankTaskLayersRunnable = new RankTaskLayersRunnable();
- private final AttachApplicationHelper mAttachApplicationHelper = new AttachApplicationHelper();
-
private String mDestroyAllActivitiesReason;
private final Runnable mDestroyAllActivitiesRunnable = new Runnable() {
@Override
@@ -1838,11 +1835,39 @@
}
boolean attachApplication(WindowProcessController app) throws RemoteException {
- try {
- return mAttachApplicationHelper.process(app);
- } finally {
- mAttachApplicationHelper.reset();
+ final ArrayList<ActivityRecord> activities = mService.mStartingProcessActivities;
+ RemoteException remoteException = null;
+ boolean hasActivityStarted = false;
+ for (int i = activities.size() - 1; i >= 0; i--) {
+ final ActivityRecord r = activities.get(i);
+ if (app.mUid != r.info.applicationInfo.uid || !app.mName.equals(r.processName)) {
+ // The attaching process does not match the starting activity.
+ continue;
+ }
+ // Consume the pending record.
+ activities.remove(i);
+ final TaskFragment tf = r.getTaskFragment();
+ if (tf == null || r.finishing || r.app != null
+ // Ignore keyguard because the app may use show-when-locked when creating.
+ || !r.shouldBeVisible(true /* ignoringKeyguard */)
+ || !r.showToCurrentUser()) {
+ continue;
+ }
+ try {
+ final boolean canResume = r.isFocusable() && r == tf.topRunningActivity();
+ if (mTaskSupervisor.realStartActivityLocked(r, app, canResume,
+ true /* checkConfig */)) {
+ hasActivityStarted = true;
+ }
+ } catch (RemoteException e) {
+ Slog.w(TAG, "Exception in new process when starting " + r, e);
+ remoteException = e;
+ }
}
+ if (remoteException != null) {
+ throw remoteException;
+ }
+ return hasActivityStarted;
}
/**
@@ -3748,67 +3773,4 @@
}
}
}
-
- private class AttachApplicationHelper implements Consumer<Task>, Predicate<ActivityRecord> {
- private boolean mHasActivityStarted;
- private RemoteException mRemoteException;
- private WindowProcessController mApp;
- private ActivityRecord mTop;
-
- void reset() {
- mHasActivityStarted = false;
- mRemoteException = null;
- mApp = null;
- mTop = null;
- }
-
- boolean process(WindowProcessController app) throws RemoteException {
- mApp = app;
- for (int displayNdx = getChildCount() - 1; displayNdx >= 0; --displayNdx) {
- getChildAt(displayNdx).forAllRootTasks(this);
- if (mRemoteException != null) {
- throw mRemoteException;
- }
- }
- if (!mHasActivityStarted) {
- ensureActivitiesVisible();
- }
- return mHasActivityStarted;
- }
-
- @Override
- public void accept(Task rootTask) {
- if (mRemoteException != null) {
- return;
- }
- if (rootTask.getVisibility(null /* starting */)
- == TASK_FRAGMENT_VISIBILITY_INVISIBLE) {
- return;
- }
- mTop = rootTask.topRunningActivity();
- rootTask.forAllActivities(this);
- }
-
- @Override
- public boolean test(ActivityRecord r) {
- if (r.finishing || !r.showToCurrentUser() || !r.visibleIgnoringKeyguard
- || r.app != null || mApp.mUid != r.info.applicationInfo.uid
- || !mApp.mName.equals(r.processName)) {
- return false;
- }
-
- try {
- if (mTaskSupervisor.realStartActivityLocked(r, mApp,
- mTop == r && r.getTask().canBeResumed(r) /* andResume */,
- true /* checkConfig */)) {
- mHasActivityStarted = true;
- }
- } catch (RemoteException e) {
- Slog.w(TAG, "Exception in new application when starting activity " + mTop, e);
- mRemoteException = e;
- return true;
- }
- return false;
- }
- }
}
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index ed88b5a..143605a 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 @@
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 @@
* 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/WindowManagerShellCommand.java b/services/core/java/com/android/server/wm/WindowManagerShellCommand.java
index d340272..1f06bfa 100644
--- a/services/core/java/com/android/server/wm/WindowManagerShellCommand.java
+++ b/services/core/java/com/android/server/wm/WindowManagerShellCommand.java
@@ -1549,9 +1549,9 @@
pw.println(" both it and R.dimen.config_fixedOrientationLetterboxAspectRatio will");
pw.println(" be ignored and framework implementation will determine aspect ratio.");
pw.println(" --cornerRadius radius");
- pw.println(" Corners radius for activities in the letterbox mode. If radius < 0,");
- pw.println(" both it and R.integer.config_letterboxActivityCornersRadius will be");
- pw.println(" ignored and corners of the activity won't be rounded.");
+ pw.println(" Corners radius (in pixels) for activities in the letterbox mode.");
+ pw.println(" If radius < 0, both R.integer.config_letterboxActivityCornersRadius");
+ pw.println(" and it will be ignored and corners of the activity won't be rounded.");
pw.println(" --backgroundType [reset|solid_color|app_color_background");
pw.println(" |app_color_background_floating|wallpaper]");
pw.println(" Type of background used in the letterbox mode.");
diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java
index d3baedc..2fcee50 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.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 @@
}
}
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 0000000..abd864c
--- /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 dedb687..b1673e2 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.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 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 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 94c1374..d1830126 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 @@
} 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 5897d76..0877146 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 @@
@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 ea08be4..fe7bbe0 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 @@
verify(mMockedLogicalDisplayMapper, never()).setDisplayEnabledLocked(any(), anyBoolean());
verify(mMockedDisplayNotificationManager, times(2))
.onHighTemperatureExternalDisplayNotAllowed();
+ verify(mMockedInjector, never()).onExternalDisplayReadyToBeEnabled(anyInt());
}
@Test
@@ -167,6 +168,7 @@
verify(mMockedLogicalDisplayMapper, never()).setDisplayEnabledLocked(any(), anyBoolean());
verify(mMockedDisplayNotificationManager, never())
.onHighTemperatureExternalDisplayNotAllowed();
+ verify(mMockedInjector, never()).onExternalDisplayReadyToBeEnabled(anyInt());
}
@Test
@@ -184,6 +186,7 @@
// 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 @@
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 0efd046..d670b13 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 @@
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 d0dd921..1c192ef 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 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 @@
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 @@
.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 @@
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 @@
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 @@
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 @@
.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 @@
.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 @@
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 @@
.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 @@
.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 @@
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 ad25d76..770712a 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_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.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 @@
public class NotificationChannelExtractorTest extends UiServiceTestCase {
@Mock RankingConfig mConfig;
+ @Mock
+ IPlatformCompat mPlatformCompat;
@Rule
public final SetFlagsRule mSetFlagsRule = new SetFlagsRule(DEVICE_DEFAULT);
@@ -73,6 +81,7 @@
mExtractor = new NotificationChannelExtractor();
mExtractor.setConfig(mConfig);
mExtractor.initialize(mContext, null);
+ mExtractor.setCompatChangeLogger(mPlatformCompat);
}
private NotificationRecord getRecord(NotificationChannel channel, Notification n) {
@@ -82,7 +91,7 @@
}
@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 @@
}
@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 @@
}
@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 @@
@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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 805bc17..011f2e3 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.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 @@
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 @@
// 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 @@
}
@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 aeeca2ae..5033a380 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 @@
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 @@
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 @@
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 @@
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 @@
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 ad420f6..527001d 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.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 @@
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 2c88ed2..7356b43 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 @@
// 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/services/tests/wmtests/src/com/android/server/wm/RootWindowContainerTests.java b/services/tests/wmtests/src/com/android/server/wm/RootWindowContainerTests.java
index 649f520..dcf3dad 100644
--- a/services/tests/wmtests/src/com/android/server/wm/RootWindowContainerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/RootWindowContainerTests.java
@@ -74,6 +74,7 @@
import android.content.res.Resources;
import android.graphics.Rect;
import android.os.PowerManager;
+import android.os.RemoteException;
import android.os.UserHandle;
import android.platform.test.annotations.Presubmit;
import android.util.Pair;
@@ -237,6 +238,24 @@
assertFalse(wpc.hasActivities());
}
+ @Test
+ public void testAttachApplication() {
+ final ActivityRecord activity = new ActivityBuilder(mAtm).setCreateTask(true).build();
+ activity.detachFromProcess();
+ mAtm.startProcessAsync(activity, false /* knownToBeDead */,
+ true /* isTop */, "test" /* hostingType */);
+ final WindowProcessController proc = mSystemServicesTestRule.addProcess(
+ activity.packageName, activity.processName,
+ 6789 /* pid */, activity.info.applicationInfo.uid);
+ try {
+ mRootWindowContainer.attachApplication(proc);
+ verify(mSupervisor).realStartActivityLocked(eq(activity), eq(proc), anyBoolean(),
+ anyBoolean());
+ } catch (RemoteException e) {
+ e.rethrowAsRuntimeException();
+ }
+ }
+
/**
* This test ensures that we do not try to restore a task based off an invalid task id. We
* should expect {@code null} to be returned in this case.
diff --git a/telephony/java/android/telephony/euicc/EuiccManager.java b/telephony/java/android/telephony/euicc/EuiccManager.java
index ebabbf9..ca4a643 100644
--- a/telephony/java/android/telephony/euicc/EuiccManager.java
+++ b/telephony/java/android/telephony/euicc/EuiccManager.java
@@ -1039,17 +1039,18 @@
* 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 0a83a53..c8b60e5 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 @@
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 @@
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 @@
.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 @@
// 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 @@
xfrmStateReceiver.onResult(newTransformState(1, 1, newReplayBitmap(1)));
verify(mPacketLossCalculator, never())
- .getPacketLossRatePercentage(any(), any(), anyString());
+ .getPacketLossRatePercentage(any(), any(), anyInt(), anyString());
}
@Test
@@ -289,7 +310,7 @@
xfrmStateReceiver.onError(new RuntimeException("Test"));
verify(mPacketLossCalculator, never())
- .getPacketLossRatePercentage(any(), any(), anyString());
+ .getPacketLossRatePercentage(any(), any(), anyInt(), anyString());
}
private void checkHandleLossRate(
@@ -301,7 +322,7 @@
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 @@
// 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 @@
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 @@
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 @@
}
@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 @@
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 @@
// 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 af6daa1..6189fb0 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 @@
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);