Merge "[Bouncer] Refine entry animation scrimmed." into tm-qpr-dev
diff --git a/apex/jobscheduler/service/java/com/android/server/usage/AppStandbyController.java b/apex/jobscheduler/service/java/com/android/server/usage/AppStandbyController.java
index 5d9f335..a6f47d4 100644
--- a/apex/jobscheduler/service/java/com/android/server/usage/AppStandbyController.java
+++ b/apex/jobscheduler/service/java/com/android/server/usage/AppStandbyController.java
@@ -109,6 +109,7 @@
import android.util.SparseArray;
import android.util.SparseBooleanArray;
import android.util.SparseLongArray;
+import android.util.SparseSetArray;
import android.util.TimeUtils;
import android.view.Display;
import android.widget.Toast;
@@ -452,6 +453,12 @@
private final Map<String, String> mAppStandbyProperties = new ArrayMap<>();
/**
+ * Set of apps that were restored via backup & restore, per user, that need their
+ * standby buckets to be adjusted when installed.
+ */
+ private final SparseSetArray<String> mAppsToRestoreToRare = new SparseSetArray<>();
+
+ /**
* List of app-ids of system packages, populated on boot, when system services are ready.
*/
private final ArrayList<Integer> mSystemPackagesAppIds = new ArrayList<>();
@@ -968,13 +975,17 @@
+ standbyBucketToString(newBucket));
}
} else {
- newBucket = getBucketForLocked(packageName, userId,
- elapsedRealtime);
- if (DEBUG) {
- Slog.d(TAG, "Evaluated AOSP newBucket = "
- + standbyBucketToString(newBucket));
+ // Don't update the standby state for apps that were restored
+ if (!(oldMainReason == REASON_MAIN_DEFAULT
+ && (app.bucketingReason & REASON_SUB_MASK)
+ == REASON_SUB_DEFAULT_APP_RESTORED)) {
+ newBucket = getBucketForLocked(packageName, userId, elapsedRealtime);
+ if (DEBUG) {
+ Slog.d(TAG, "Evaluated AOSP newBucket = "
+ + standbyBucketToString(newBucket));
+ }
+ reason = REASON_MAIN_TIMEOUT;
}
- reason = REASON_MAIN_TIMEOUT;
}
}
@@ -1610,18 +1621,29 @@
final int reason = REASON_MAIN_DEFAULT | REASON_SUB_DEFAULT_APP_RESTORED;
final long nowElapsed = mInjector.elapsedRealtime();
for (String packageName : restoredApps) {
- // If the package is not installed, don't allow the bucket to be set.
+ // If the package is not installed, don't allow the bucket to be set. Instead, add it
+ // to a list of all packages whose buckets need to be adjusted when installed.
if (!mInjector.isPackageInstalled(packageName, 0, userId)) {
- Slog.e(TAG, "Tried to restore bucket for uninstalled app: " + packageName);
+ Slog.i(TAG, "Tried to restore bucket for uninstalled app: " + packageName);
+ mAppsToRestoreToRare.add(userId, packageName);
continue;
}
- final int standbyBucket = getAppStandbyBucket(packageName, userId, nowElapsed, false);
- // Only update the standby bucket to RARE if the app is still in the NEVER bucket.
- if (standbyBucket == STANDBY_BUCKET_NEVER) {
- setAppStandbyBucket(packageName, userId, STANDBY_BUCKET_RARE, reason,
- nowElapsed, false);
- }
+ restoreAppToRare(packageName, userId, nowElapsed, reason);
+ }
+ // Clear out the list of restored apps that need to have their standby buckets adjusted
+ // if they still haven't been installed eight hours after restore.
+ // Note: if the device reboots within these first 8 hours, this list will be lost since it's
+ // not persisted - this is the expected behavior for now and may be updated in the future.
+ mHandler.postDelayed(() -> mAppsToRestoreToRare.remove(userId), 8 * ONE_HOUR);
+ }
+
+ /** Adjust the standby bucket of the given package for the user to RARE. */
+ private void restoreAppToRare(String pkgName, int userId, long nowElapsed, int reason) {
+ final int standbyBucket = getAppStandbyBucket(pkgName, userId, nowElapsed, false);
+ // Only update the standby bucket to RARE if the app is still in the NEVER bucket.
+ if (standbyBucket == STANDBY_BUCKET_NEVER) {
+ setAppStandbyBucket(pkgName, userId, STANDBY_BUCKET_RARE, reason, nowElapsed, false);
}
}
@@ -2116,15 +2138,24 @@
}
// component-level enable/disable can affect bucketing, so we always
// reevaluate that for any PACKAGE_CHANGED
- mHandler.obtainMessage(MSG_CHECK_PACKAGE_IDLE_STATE, userId, -1, pkgName)
- .sendToTarget();
+ if (Intent.ACTION_PACKAGE_CHANGED.equals(action)) {
+ mHandler.obtainMessage(MSG_CHECK_PACKAGE_IDLE_STATE, userId, -1, pkgName)
+ .sendToTarget();
+ }
}
if ((Intent.ACTION_PACKAGE_REMOVED.equals(action) ||
Intent.ACTION_PACKAGE_ADDED.equals(action))) {
if (intent.getBooleanExtra(Intent.EXTRA_REPLACING, false)) {
maybeUnrestrictBuggyApp(pkgName, userId);
- } else {
+ } else if (!Intent.ACTION_PACKAGE_ADDED.equals(action)) {
clearAppIdleForPackage(pkgName, userId);
+ } else {
+ // Package was just added and it's not being replaced.
+ if (mAppsToRestoreToRare.contains(userId, pkgName)) {
+ restoreAppToRare(pkgName, userId, mInjector.elapsedRealtime(),
+ REASON_MAIN_DEFAULT | REASON_SUB_DEFAULT_APP_RESTORED);
+ mAppsToRestoreToRare.remove(userId, pkgName);
+ }
}
}
}
diff --git a/core/java/android/animation/AnimationHandler.java b/core/java/android/animation/AnimationHandler.java
index 19606c1..df8a50a 100644
--- a/core/java/android/animation/AnimationHandler.java
+++ b/core/java/android/animation/AnimationHandler.java
@@ -19,10 +19,10 @@
import android.os.SystemClock;
import android.os.SystemProperties;
import android.util.ArrayMap;
-import android.util.ArraySet;
import android.util.Log;
import android.view.Choreographer;
+import java.lang.ref.WeakReference;
import java.util.ArrayList;
/**
@@ -40,7 +40,7 @@
public class AnimationHandler {
private static final String TAG = "AnimationHandler";
- private static final boolean LOCAL_LOGV = true;
+ private static final boolean LOCAL_LOGV = false;
/**
* Internal per-thread collections used to avoid set collisions as animations start and end
@@ -78,7 +78,7 @@
* store visible (foreground) requestors; if the set size reaches zero, there are no
* objects in the foreground and it is time to pause animators.
*/
- private final ArraySet<Object> mAnimatorRequestors = new ArraySet<>();
+ private final ArrayList<WeakReference<Object>> mAnimatorRequestors = new ArrayList<>();
private final Choreographer.FrameCallback mFrameCallback = new Choreographer.FrameCallback() {
@Override
@@ -141,24 +141,9 @@
* tracking obsolete+enabled requestors.
*/
public static void removeRequestor(Object requestor) {
- getInstance().removeRequestorImpl(requestor);
- }
-
- private void removeRequestorImpl(Object requestor) {
- // Also request disablement, in case that requestor was the sole object keeping
- // animators un-paused
- long startTime = System.nanoTime();
- requestAnimatorsEnabled(false, requestor);
- Log.d(TAG, "removeRequestorImpl called requestAnimatorsEnabled after " +
- (System.nanoTime() - startTime));
- mAnimatorRequestors.remove(requestor);
- Log.d(TAG, "removeRequestorImpl removed requestor after " +
- (System.nanoTime() - startTime));
+ getInstance().requestAnimatorsEnabledImpl(false, requestor);
if (LOCAL_LOGV) {
- Log.v(TAG, "removeRequestorImpl for " + requestor);
- for (int i = 0; i < mAnimatorRequestors.size(); ++i) {
- Log.v(TAG, "animatorRequesters " + i + " = " + mAnimatorRequestors.valueAt(i));
- }
+ Log.v(TAG, "removeRequestor for " + requestor);
}
}
@@ -176,25 +161,44 @@
}
private void requestAnimatorsEnabledImpl(boolean enable, Object requestor) {
- long startTime = System.nanoTime();
boolean wasEmpty = mAnimatorRequestors.isEmpty();
setAnimatorPausingEnabled(isPauseBgAnimationsEnabledInSystemProperties());
- Log.d(TAG, "requestAnimatorsEnabledImpl called setAnimatorPausingEnabled after " +
- (System.nanoTime() - startTime));
- if (enable) {
- mAnimatorRequestors.add(requestor);
- } else {
- mAnimatorRequestors.remove(requestor);
+ synchronized (mAnimatorRequestors) {
+ // Only store WeakRef objects to avoid leaks
+ if (enable) {
+ // First, check whether such a reference is already on the list
+ WeakReference<Object> weakRef = null;
+ for (int i = mAnimatorRequestors.size() - 1; i >= 0; --i) {
+ WeakReference<Object> ref = mAnimatorRequestors.get(i);
+ Object referent = ref.get();
+ if (referent == requestor) {
+ weakRef = ref;
+ } else if (referent == null) {
+ // Remove any reference that has been cleared
+ mAnimatorRequestors.remove(i);
+ }
+ }
+ if (weakRef == null) {
+ weakRef = new WeakReference<>(requestor);
+ mAnimatorRequestors.add(weakRef);
+ }
+ } else {
+ for (int i = mAnimatorRequestors.size() - 1; i >= 0; --i) {
+ WeakReference<Object> ref = mAnimatorRequestors.get(i);
+ Object referent = ref.get();
+ if (referent == requestor || referent == null) {
+ // remove requested item or item that has been cleared
+ mAnimatorRequestors.remove(i);
+ }
+ }
+ // If a reference to the requestor wasn't in the list, nothing to remove
+ }
}
- Log.d(TAG, "requestAnimatorsEnabledImpl added/removed after " +
- (System.nanoTime() - startTime));
if (!sAnimatorPausingEnabled) {
// Resume any animators that have been paused in the meantime, otherwise noop
// Leave logic above so that if pausing gets re-enabled, the state of the requestors
// list is valid
resumeAnimators();
- Log.d(TAG, "requestAnimatorsEnabledImpl resumed, returning after " +
- (System.nanoTime() - startTime));
return;
}
boolean isEmpty = mAnimatorRequestors.isEmpty();
@@ -209,12 +213,13 @@
Animator.getBackgroundPauseDelay());
}
}
- Log.d(TAG, "requestAnimatorsEnabledImpl post was/is check after " +
- (System.nanoTime() - startTime));
if (LOCAL_LOGV) {
- Log.v(TAG, enable ? "enable" : "disable" + " animators for " + requestor);
+ Log.v(TAG, (enable ? "enable" : "disable") + " animators for " + requestor
+ + " with pauseDelay of " + Animator.getBackgroundPauseDelay());
for (int i = 0; i < mAnimatorRequestors.size(); ++i) {
- Log.v(TAG, "animatorRequesters " + i + " = " + mAnimatorRequestors.valueAt(i));
+ Log.v(TAG, "animatorRequestors " + i + " = "
+ + mAnimatorRequestors.get(i) + " with referent "
+ + mAnimatorRequestors.get(i).get());
}
}
}
diff --git a/core/java/android/app/StatusBarManager.java b/core/java/android/app/StatusBarManager.java
index 13fc2e6..3c0a724 100644
--- a/core/java/android/app/StatusBarManager.java
+++ b/core/java/android/app/StatusBarManager.java
@@ -511,10 +511,26 @@
@SystemApi
public static final int MEDIA_TRANSFER_RECEIVER_STATE_FAR_FROM_SENDER = 1;
+ /**
+ * State indicating that media transfer to this receiver device is succeeded.
+ *
+ * @hide
+ */
+ public static final int MEDIA_TRANSFER_RECEIVER_STATE_TRANSFER_TO_RECEIVER_SUCCEEDED = 2;
+
+ /**
+ * State indicating that media transfer to this receiver device is failed.
+ *
+ * @hide
+ */
+ public static final int MEDIA_TRANSFER_RECEIVER_STATE_TRANSFER_TO_RECEIVER_FAILED = 3;
+
/** @hide */
@IntDef(prefix = {"MEDIA_TRANSFER_RECEIVER_STATE_"}, value = {
MEDIA_TRANSFER_RECEIVER_STATE_CLOSE_TO_SENDER,
MEDIA_TRANSFER_RECEIVER_STATE_FAR_FROM_SENDER,
+ MEDIA_TRANSFER_RECEIVER_STATE_TRANSFER_TO_RECEIVER_SUCCEEDED,
+ MEDIA_TRANSFER_RECEIVER_STATE_TRANSFER_TO_RECEIVER_FAILED,
})
@Retention(RetentionPolicy.SOURCE)
public @interface MediaTransferReceiverState {}
diff --git a/core/java/android/app/trust/ITrustListener.aidl b/core/java/android/app/trust/ITrustListener.aidl
index 6b9d2c73..e4ac0119 100644
--- a/core/java/android/app/trust/ITrustListener.aidl
+++ b/core/java/android/app/trust/ITrustListener.aidl
@@ -24,8 +24,8 @@
* {@hide}
*/
oneway interface ITrustListener {
- void onTrustChanged(boolean enabled, int userId, int flags,
+ void onTrustChanged(boolean enabled, boolean newlyUnlocked, int userId, int flags,
in List<String> trustGrantedMessages);
void onTrustManagedChanged(boolean managed, int userId);
void onTrustError(in CharSequence message);
-}
\ No newline at end of file
+}
diff --git a/core/java/android/app/trust/TrustManager.java b/core/java/android/app/trust/TrustManager.java
index 9e825b72..62f755d 100644
--- a/core/java/android/app/trust/TrustManager.java
+++ b/core/java/android/app/trust/TrustManager.java
@@ -22,6 +22,7 @@
import android.compat.annotation.UnsupportedAppUsage;
import android.content.Context;
import android.hardware.biometrics.BiometricSourceType;
+import android.os.Bundle;
import android.os.Handler;
import android.os.IBinder;
import android.os.Looper;
@@ -45,6 +46,7 @@
private static final String TAG = "TrustManager";
private static final String DATA_FLAGS = "initiatedByUser";
+ private static final String DATA_NEWLY_UNLOCKED = "newlyUnlocked";
private static final String DATA_MESSAGE = "message";
private static final String DATA_GRANTED_MESSAGES = "grantedMessages";
@@ -171,13 +173,14 @@
try {
ITrustListener.Stub iTrustListener = new ITrustListener.Stub() {
@Override
- public void onTrustChanged(boolean enabled, int userId, int flags,
- List<String> trustGrantedMessages) {
+ public void onTrustChanged(boolean enabled, boolean newlyUnlocked, int userId,
+ int flags, List<String> trustGrantedMessages) {
Message m = mHandler.obtainMessage(MSG_TRUST_CHANGED, (enabled ? 1 : 0), userId,
trustListener);
if (flags != 0) {
m.getData().putInt(DATA_FLAGS, flags);
}
+ m.getData().putInt(DATA_NEWLY_UNLOCKED, newlyUnlocked ? 1 : 0);
m.getData().putCharSequenceArrayList(
DATA_GRANTED_MESSAGES, (ArrayList) trustGrantedMessages);
m.sendToTarget();
@@ -265,9 +268,14 @@
public void handleMessage(Message msg) {
switch(msg.what) {
case MSG_TRUST_CHANGED:
- int flags = msg.peekData() != null ? msg.peekData().getInt(DATA_FLAGS) : 0;
- ((TrustListener) msg.obj).onTrustChanged(msg.arg1 != 0, msg.arg2, flags,
- msg.getData().getStringArrayList(DATA_GRANTED_MESSAGES));
+ Bundle data = msg.peekData();
+ int flags = data != null ? data.getInt(DATA_FLAGS) : 0;
+ boolean enabled = msg.arg1 != 0;
+ int newlyUnlockedInt =
+ data != null ? data.getInt(DATA_NEWLY_UNLOCKED) : 0;
+ boolean newlyUnlocked = newlyUnlockedInt != 0;
+ ((TrustListener) msg.obj).onTrustChanged(enabled, newlyUnlocked, msg.arg2,
+ flags, msg.getData().getStringArrayList(DATA_GRANTED_MESSAGES));
break;
case MSG_TRUST_MANAGED_CHANGED:
((TrustListener)msg.obj).onTrustManagedChanged(msg.arg1 != 0, msg.arg2);
@@ -284,6 +292,8 @@
/**
* Reports that the trust state has changed.
* @param enabled If true, the system believes the environment to be trusted.
+ * @param newlyUnlocked If true, the system believes the device is newly unlocked due
+ * to the trust changing.
* @param userId The user, for which the trust changed.
* @param flags Flags specified by the trust agent when granting trust. See
* {@link android.service.trust.TrustAgentService#grantTrust(CharSequence, long, int)
@@ -291,7 +301,7 @@
* @param trustGrantedMessages Messages to display to the user when trust has been granted
* by one or more trust agents.
*/
- void onTrustChanged(boolean enabled, int userId, int flags,
+ void onTrustChanged(boolean enabled, boolean newlyUnlocked, int userId, int flags,
List<String> trustGrantedMessages);
/**
diff --git a/core/java/android/hardware/display/DisplayManagerInternal.java b/core/java/android/hardware/display/DisplayManagerInternal.java
index b0b3e9e..ff80ffd 100644
--- a/core/java/android/hardware/display/DisplayManagerInternal.java
+++ b/core/java/android/hardware/display/DisplayManagerInternal.java
@@ -427,9 +427,6 @@
// 1 (brighter). Set to Float.NaN if there's no override.
public float screenAutoBrightnessAdjustmentOverride;
- // If true, enables automatic brightness control.
- public boolean useAutoBrightness;
-
// If true, scales the brightness to a fraction of desired (as defined by
// screenLowPowerBrightnessFactor).
public boolean lowPowerMode;
@@ -459,7 +456,6 @@
policy = POLICY_BRIGHT;
useProximitySensor = false;
screenBrightnessOverride = PowerManager.BRIGHTNESS_INVALID_FLOAT;
- useAutoBrightness = false;
screenAutoBrightnessAdjustmentOverride = Float.NaN;
screenLowPowerBrightnessFactor = 0.5f;
blockScreenOn = false;
@@ -483,7 +479,6 @@
policy = other.policy;
useProximitySensor = other.useProximitySensor;
screenBrightnessOverride = other.screenBrightnessOverride;
- useAutoBrightness = other.useAutoBrightness;
screenAutoBrightnessAdjustmentOverride = other.screenAutoBrightnessAdjustmentOverride;
screenLowPowerBrightnessFactor = other.screenLowPowerBrightnessFactor;
blockScreenOn = other.blockScreenOn;
@@ -505,7 +500,6 @@
&& useProximitySensor == other.useProximitySensor
&& floatEquals(screenBrightnessOverride,
other.screenBrightnessOverride)
- && useAutoBrightness == other.useAutoBrightness
&& floatEquals(screenAutoBrightnessAdjustmentOverride,
other.screenAutoBrightnessAdjustmentOverride)
&& screenLowPowerBrightnessFactor
@@ -531,7 +525,6 @@
return "policy=" + policyToString(policy)
+ ", useProximitySensor=" + useProximitySensor
+ ", screenBrightnessOverride=" + screenBrightnessOverride
- + ", useAutoBrightness=" + useAutoBrightness
+ ", screenAutoBrightnessAdjustmentOverride="
+ screenAutoBrightnessAdjustmentOverride
+ ", screenLowPowerBrightnessFactor=" + screenLowPowerBrightnessFactor
diff --git a/core/java/android/os/PowerManager.java b/core/java/android/os/PowerManager.java
index a46868e..68e1acb 100644
--- a/core/java/android/os/PowerManager.java
+++ b/core/java/android/os/PowerManager.java
@@ -523,6 +523,13 @@
public static final int GO_TO_SLEEP_FLAG_NO_DOZE = 1 << 0;
/**
+ * Go to sleep flag: Sleep softly, go to sleep only if there's no wakelock explicitly keeping
+ * the device awake.
+ * @hide
+ */
+ public static final int GO_TO_SLEEP_FLAG_SOFT_SLEEP = 1 << 1;
+
+ /**
* @hide
*/
@IntDef(prefix = { "BRIGHTNESS_CONSTRAINT_TYPE" }, value = {
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index d6fa02c6..94a6382 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -9514,6 +9514,16 @@
"lock_screen_show_silent_notifications";
/**
+ * Indicates whether "seen" notifications should be suppressed from the lockscreen.
+ * <p>
+ * Type: int (0 for false, 1 for true)
+ *
+ * @hide
+ */
+ public static final String LOCK_SCREEN_SHOW_ONLY_UNSEEN_NOTIFICATIONS =
+ "lock_screen_show_only_unseen_notifications";
+
+ /**
* Indicates whether snooze options should be shown on notifications
* <p>
* Type: int (0 for false, 1 for true)
diff --git a/core/java/android/service/autofill/FillRequest.java b/core/java/android/service/autofill/FillRequest.java
index 1507c87..6e6dac5 100644
--- a/core/java/android/service/autofill/FillRequest.java
+++ b/core/java/android/service/autofill/FillRequest.java
@@ -111,6 +111,12 @@
*/
public static final @RequestFlags int FLAG_IME_SHOWING = 0x80;
+ /**
+ * Indicates whether autofill session should reset the fill dialog state.
+ * @hide
+ */
+ public static final @RequestFlags int FLAG_RESET_FILL_DIALOG_STATE = 0x100;
+
/** @hide */
public static final int INVALID_REQUEST_ID = Integer.MIN_VALUE;
@@ -208,7 +214,8 @@
FLAG_PASSWORD_INPUT_TYPE,
FLAG_VIEW_NOT_FOCUSED,
FLAG_SUPPORTS_FILL_DIALOG,
- FLAG_IME_SHOWING
+ FLAG_IME_SHOWING,
+ FLAG_RESET_FILL_DIALOG_STATE
})
@Retention(RetentionPolicy.SOURCE)
@DataClass.Generated.Member
@@ -236,6 +243,8 @@
return "FLAG_SUPPORTS_FILL_DIALOG";
case FLAG_IME_SHOWING:
return "FLAG_IME_SHOWING";
+ case FLAG_RESET_FILL_DIALOG_STATE:
+ return "FLAG_RESET_FILL_DIALOG_STATE";
default: return Integer.toHexString(value);
}
}
@@ -312,7 +321,8 @@
| FLAG_PASSWORD_INPUT_TYPE
| FLAG_VIEW_NOT_FOCUSED
| FLAG_SUPPORTS_FILL_DIALOG
- | FLAG_IME_SHOWING);
+ | FLAG_IME_SHOWING
+ | FLAG_RESET_FILL_DIALOG_STATE);
this.mInlineSuggestionsRequest = inlineSuggestionsRequest;
this.mDelayedFillIntentSender = delayedFillIntentSender;
@@ -473,7 +483,8 @@
| FLAG_PASSWORD_INPUT_TYPE
| FLAG_VIEW_NOT_FOCUSED
| FLAG_SUPPORTS_FILL_DIALOG
- | FLAG_IME_SHOWING);
+ | FLAG_IME_SHOWING
+ | FLAG_RESET_FILL_DIALOG_STATE);
this.mInlineSuggestionsRequest = inlineSuggestionsRequest;
this.mDelayedFillIntentSender = delayedFillIntentSender;
@@ -495,10 +506,10 @@
};
@DataClass.Generated(
- time = 1647856966565L,
+ time = 1663290803064L,
codegenVersion = "1.0.23",
sourceFile = "frameworks/base/core/java/android/service/autofill/FillRequest.java",
- inputSignatures = "public static final @android.service.autofill.FillRequest.RequestFlags int FLAG_MANUAL_REQUEST\npublic static final @android.service.autofill.FillRequest.RequestFlags int FLAG_COMPATIBILITY_MODE_REQUEST\npublic static final @android.service.autofill.FillRequest.RequestFlags int FLAG_PASSWORD_INPUT_TYPE\npublic static final @android.service.autofill.FillRequest.RequestFlags int FLAG_VIEW_NOT_FOCUSED\npublic static final @android.service.autofill.FillRequest.RequestFlags int FLAG_SUPPORTS_FILL_DIALOG\npublic static final @android.service.autofill.FillRequest.RequestFlags int FLAG_IME_SHOWING\npublic static final int INVALID_REQUEST_ID\nprivate final int mId\nprivate final @android.annotation.NonNull java.util.List<android.service.autofill.FillContext> mFillContexts\nprivate final @android.annotation.Nullable android.os.Bundle mClientState\nprivate final @android.service.autofill.FillRequest.RequestFlags int mFlags\nprivate final @android.annotation.Nullable android.view.inputmethod.InlineSuggestionsRequest mInlineSuggestionsRequest\nprivate final @android.annotation.Nullable android.content.IntentSender mDelayedFillIntentSender\nprivate void onConstructed()\nclass FillRequest extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genToString=true, genHiddenConstructor=true, genHiddenConstDefs=true)")
+ inputSignatures = "public static final @android.service.autofill.FillRequest.RequestFlags int FLAG_MANUAL_REQUEST\npublic static final @android.service.autofill.FillRequest.RequestFlags int FLAG_COMPATIBILITY_MODE_REQUEST\npublic static final @android.service.autofill.FillRequest.RequestFlags int FLAG_PASSWORD_INPUT_TYPE\npublic static final @android.service.autofill.FillRequest.RequestFlags int FLAG_VIEW_NOT_FOCUSED\npublic static final @android.service.autofill.FillRequest.RequestFlags int FLAG_SUPPORTS_FILL_DIALOG\npublic static final @android.service.autofill.FillRequest.RequestFlags int FLAG_IME_SHOWING\npublic static final @android.service.autofill.FillRequest.RequestFlags int FLAG_RESET_FILL_DIALOG_STATE\npublic static final int INVALID_REQUEST_ID\nprivate final int mId\nprivate final @android.annotation.NonNull java.util.List<android.service.autofill.FillContext> mFillContexts\nprivate final @android.annotation.Nullable android.os.Bundle mClientState\nprivate final @android.service.autofill.FillRequest.RequestFlags int mFlags\nprivate final @android.annotation.Nullable android.view.inputmethod.InlineSuggestionsRequest mInlineSuggestionsRequest\nprivate final @android.annotation.Nullable android.content.IntentSender mDelayedFillIntentSender\nprivate void onConstructed()\nclass FillRequest extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genToString=true, genHiddenConstructor=true, genHiddenConstDefs=true)")
@Deprecated
private void __metadata() {}
diff --git a/core/java/android/service/controls/ControlsProviderService.java b/core/java/android/service/controls/ControlsProviderService.java
index 9396a88..950c8ac 100644
--- a/core/java/android/service/controls/ControlsProviderService.java
+++ b/core/java/android/service/controls/ControlsProviderService.java
@@ -78,7 +78,7 @@
* @hide
*/
public static final String EXTRA_LOCKSCREEN_ALLOW_TRIVIAL_CONTROLS =
- "android.service.controls.EXTRA_LOCKSCREEN_ALLOW_TRIVIAL_CONTROLS";
+ "android.service.controls.extra.EXTRA_LOCKSCREEN_ALLOW_TRIVIAL_CONTROLS";
/**
* @hide
diff --git a/core/java/android/service/voice/HotwordAudioStream.java b/core/java/android/service/voice/HotwordAudioStream.java
index 5442860..1dceb7a 100644
--- a/core/java/android/service/voice/HotwordAudioStream.java
+++ b/core/java/android/service/voice/HotwordAudioStream.java
@@ -37,6 +37,21 @@
public final class HotwordAudioStream implements Parcelable {
/**
+ * Key for int value to be read from {@link #getMetadata()}. The value is read by the system and
+ * is the length (in bytes) of the byte buffers created to copy bytes in the
+ * {@link #getAudioStreamParcelFileDescriptor()} written by the {@link HotwordDetectionService}.
+ * The buffer length should be chosen such that no additional latency is introduced. Typically,
+ * this should be <em>at least</em> the size of byte chunks written by the
+ * {@link HotwordDetectionService}.
+ *
+ * <p>If no value specified in the metadata for the buffer length, or if the value is less than
+ * 1, or if it is greater than 65,536, or if it is not an int, the default value of 2,560 will
+ * be used.</p>
+ */
+ public static final String KEY_AUDIO_STREAM_COPY_BUFFER_LENGTH_BYTES =
+ "android.service.voice.key.AUDIO_STREAM_COPY_BUFFER_LENGTH_BYTES";
+
+ /**
* The {@link AudioFormat} of the audio stream.
*/
@NonNull
diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java
index c0183ad..af07023 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -1417,7 +1417,11 @@
mFirstPostImeInputStage = earlyPostImeStage;
mPendingInputEventQueueLengthCounterName = "aq:pending:" + counterSuffix;
- AnimationHandler.requestAnimatorsEnabled(mAppVisible, this);
+ if (!mRemoved || !mAppVisible) {
+ AnimationHandler.requestAnimatorsEnabled(mAppVisible, this);
+ } else if (LOCAL_LOGV) {
+ Log.v(mTag, "setView() enabling visibility when removed");
+ }
}
}
}
@@ -1755,7 +1759,12 @@
if (!mAppVisible) {
WindowManagerGlobal.trimForeground();
}
- AnimationHandler.requestAnimatorsEnabled(mAppVisible, this);
+ // Only enable if the window is not already removed (via earlier call to doDie())
+ if (!mRemoved || !mAppVisible) {
+ AnimationHandler.requestAnimatorsEnabled(mAppVisible, this);
+ } else if (LOCAL_LOGV) {
+ Log.v(mTag, "handleAppVisibility() enabling visibility when removed");
+ }
}
}
diff --git a/core/java/android/view/autofill/AutofillManager.java b/core/java/android/view/autofill/AutofillManager.java
index 7d7270d..79c3839 100644
--- a/core/java/android/view/autofill/AutofillManager.java
+++ b/core/java/android/view/autofill/AutofillManager.java
@@ -19,6 +19,7 @@
import static android.service.autofill.FillRequest.FLAG_IME_SHOWING;
import static android.service.autofill.FillRequest.FLAG_MANUAL_REQUEST;
import static android.service.autofill.FillRequest.FLAG_PASSWORD_INPUT_TYPE;
+import static android.service.autofill.FillRequest.FLAG_RESET_FILL_DIALOG_STATE;
import static android.service.autofill.FillRequest.FLAG_SUPPORTS_FILL_DIALOG;
import static android.service.autofill.FillRequest.FLAG_VIEW_NOT_FOCUSED;
import static android.view.ContentInfo.SOURCE_AUTOFILL;
@@ -715,7 +716,7 @@
* Autofill will automatically trigger a fill request after activity
* start if there is any field is autofillable. But if there is a field that
* triggered autofill, it is unnecessary to trigger again through
- * AutofillManager#notifyViewEnteredForActivityStarted.
+ * AutofillManager#notifyViewEnteredForFillDialog.
*/
private AtomicBoolean mIsFillRequested;
@@ -728,6 +729,10 @@
private final String[] mFillDialogEnabledHints;
+ // Tracked all views that have appeared, including views that there are no
+ // dataset in responses. Used to avoid request pre-fill request again and again.
+ private final ArraySet<AutofillId> mAllTrackedViews = new ArraySet<>();
+
/** @hide */
public interface AutofillClient {
/**
@@ -1173,6 +1178,16 @@
* @hide
*/
public void notifyViewEnteredForFillDialog(View v) {
+ synchronized (mLock) {
+ if (mTrackedViews != null) {
+ // To support the fill dialog can show for the autofillable Views in
+ // different pages but in the same Activity. We need to reset the
+ // mIsFillRequested flag to allow asking for a new FillRequest when
+ // user switches to other page
+ mTrackedViews.checkViewState(v.getAutofillId());
+ }
+ }
+
// Skip if the fill request has been performed for a view.
if (mIsFillRequested.get()) {
return;
@@ -1299,6 +1314,10 @@
}
mForAugmentedAutofillOnly = false;
}
+
+ if ((flags & FLAG_SUPPORTS_FILL_DIALOG) != 0) {
+ flags |= FLAG_RESET_FILL_DIALOG_STATE;
+ }
updateSessionLocked(id, null, value, ACTION_VIEW_ENTERED, flags);
}
addEnteredIdLocked(id);
@@ -2165,6 +2184,7 @@
mIsFillRequested.set(false);
mShowAutofillDialogCalled = false;
mFillDialogTriggerIds = null;
+ mAllTrackedViews.clear();
if (resetEnteredIds) {
mEnteredIds = null;
}
@@ -2702,14 +2722,9 @@
+ ", mFillableIds=" + mFillableIds
+ ", mEnabled=" + mEnabled
+ ", mSessionId=" + mSessionId);
-
}
+
if (mEnabled && mSessionId == sessionId) {
- if (saveOnAllViewsInvisible) {
- mTrackedViews = new TrackedViews(trackedIds);
- } else {
- mTrackedViews = null;
- }
mSaveOnFinish = saveOnFinish;
if (fillableIds != null) {
if (mFillableIds == null) {
@@ -2731,6 +2746,27 @@
mSaveTriggerId = saveTriggerId;
setNotifyOnClickLocked(mSaveTriggerId, true);
}
+
+ if (!saveOnAllViewsInvisible) {
+ trackedIds = null;
+ }
+
+ final ArraySet<AutofillId> allFillableIds = new ArraySet<>();
+ if (mFillableIds != null) {
+ allFillableIds.addAll(mFillableIds);
+ }
+ if (trackedIds != null) {
+ for (AutofillId id : trackedIds) {
+ id.resetSessionId();
+ allFillableIds.add(id);
+ }
+ }
+
+ if (!allFillableIds.isEmpty()) {
+ mTrackedViews = new TrackedViews(trackedIds, Helper.toArray(allFillableIds));
+ } else {
+ mTrackedViews = null;
+ }
}
}
}
@@ -3502,10 +3538,19 @@
*/
private class TrackedViews {
/** Visible tracked views */
- @Nullable private ArraySet<AutofillId> mVisibleTrackedIds;
+ @NonNull private final ArraySet<AutofillId> mVisibleTrackedIds;
/** Invisible tracked views */
- @Nullable private ArraySet<AutofillId> mInvisibleTrackedIds;
+ @NonNull private final ArraySet<AutofillId> mInvisibleTrackedIds;
+
+ /** Visible tracked views for fill dialog */
+ @NonNull private final ArraySet<AutofillId> mVisibleDialogTrackedIds;
+
+ /** Invisible tracked views for fill dialog */
+ @NonNull private final ArraySet<AutofillId> mInvisibleDialogTrackedIds;
+
+ boolean mHasNewTrackedView;
+ boolean mIsTrackedSaveView;
/**
* Check if set is null or value is in set.
@@ -3571,43 +3616,65 @@
*
* @param trackedIds The views to be tracked
*/
- TrackedViews(@Nullable AutofillId[] trackedIds) {
- final AutofillClient client = getClient();
- if (!ArrayUtils.isEmpty(trackedIds) && client != null) {
- final boolean[] isVisible;
+ TrackedViews(@Nullable AutofillId[] trackedIds, @Nullable AutofillId[] allTrackedIds) {
+ mVisibleTrackedIds = new ArraySet<>();
+ mInvisibleTrackedIds = new ArraySet<>();
+ if (!ArrayUtils.isEmpty(trackedIds)) {
+ mIsTrackedSaveView = true;
+ initialTrackedViews(trackedIds, mVisibleTrackedIds, mInvisibleTrackedIds);
+ }
- if (client.autofillClientIsVisibleForAutofill()) {
- if (sVerbose) Log.v(TAG, "client is visible, check tracked ids");
- isVisible = client.autofillClientGetViewVisibility(trackedIds);
- } else {
- // All false
- isVisible = new boolean[trackedIds.length];
- }
-
- final int numIds = trackedIds.length;
- for (int i = 0; i < numIds; i++) {
- final AutofillId id = trackedIds[i];
- id.resetSessionId();
-
- if (isVisible[i]) {
- mVisibleTrackedIds = addToSet(mVisibleTrackedIds, id);
- } else {
- mInvisibleTrackedIds = addToSet(mInvisibleTrackedIds, id);
- }
- }
+ mVisibleDialogTrackedIds = new ArraySet<>();
+ mInvisibleDialogTrackedIds = new ArraySet<>();
+ if (!ArrayUtils.isEmpty(allTrackedIds)) {
+ initialTrackedViews(allTrackedIds, mVisibleDialogTrackedIds,
+ mInvisibleDialogTrackedIds);
+ mAllTrackedViews.addAll(Arrays.asList(allTrackedIds));
}
if (sVerbose) {
Log.v(TAG, "TrackedViews(trackedIds=" + Arrays.toString(trackedIds) + "): "
+ " mVisibleTrackedIds=" + mVisibleTrackedIds
- + " mInvisibleTrackedIds=" + mInvisibleTrackedIds);
+ + " mInvisibleTrackedIds=" + mInvisibleTrackedIds
+ + " allTrackedIds=" + Arrays.toString(allTrackedIds)
+ + " mVisibleDialogTrackedIds=" + mVisibleDialogTrackedIds
+ + " mInvisibleDialogTrackedIds=" + mInvisibleDialogTrackedIds);
}
- if (mVisibleTrackedIds == null) {
+ if (mIsTrackedSaveView && mVisibleTrackedIds.isEmpty()) {
finishSessionLocked(/* commitReason= */ COMMIT_REASON_VIEW_CHANGED);
}
}
+ private void initialTrackedViews(AutofillId[] trackedIds,
+ @NonNull ArraySet<AutofillId> visibleSet,
+ @NonNull ArraySet<AutofillId> invisibleSet) {
+ final boolean[] isVisible;
+ final AutofillClient client = getClient();
+ if (ArrayUtils.isEmpty(trackedIds) || client == null) {
+ return;
+ }
+ if (client.autofillClientIsVisibleForAutofill()) {
+ if (sVerbose) Log.v(TAG, "client is visible, check tracked ids");
+ isVisible = client.autofillClientGetViewVisibility(trackedIds);
+ } else {
+ // All false
+ isVisible = new boolean[trackedIds.length];
+ }
+
+ final int numIds = trackedIds.length;
+ for (int i = 0; i < numIds; i++) {
+ final AutofillId id = trackedIds[i];
+ id.resetSessionId();
+
+ if (isVisible[i]) {
+ addToSet(visibleSet, id);
+ } else {
+ addToSet(invisibleSet, id);
+ }
+ }
+ }
+
/**
* Called when a {@link View view's} visibility changes.
*
@@ -3624,22 +3691,37 @@
if (isClientVisibleForAutofillLocked()) {
if (isVisible) {
if (isInSet(mInvisibleTrackedIds, id)) {
- mInvisibleTrackedIds = removeFromSet(mInvisibleTrackedIds, id);
- mVisibleTrackedIds = addToSet(mVisibleTrackedIds, id);
+ removeFromSet(mInvisibleTrackedIds, id);
+ addToSet(mVisibleTrackedIds, id);
+ }
+ if (isInSet(mInvisibleDialogTrackedIds, id)) {
+ removeFromSet(mInvisibleDialogTrackedIds, id);
+ addToSet(mVisibleDialogTrackedIds, id);
}
} else {
if (isInSet(mVisibleTrackedIds, id)) {
- mVisibleTrackedIds = removeFromSet(mVisibleTrackedIds, id);
- mInvisibleTrackedIds = addToSet(mInvisibleTrackedIds, id);
+ removeFromSet(mVisibleTrackedIds, id);
+ addToSet(mInvisibleTrackedIds, id);
+ }
+ if (isInSet(mVisibleDialogTrackedIds, id)) {
+ removeFromSet(mVisibleDialogTrackedIds, id);
+ addToSet(mInvisibleDialogTrackedIds, id);
}
}
}
- if (mVisibleTrackedIds == null) {
+ if (mIsTrackedSaveView && mVisibleTrackedIds.isEmpty()) {
if (sVerbose) {
Log.v(TAG, "No more visible ids. Invisible = " + mInvisibleTrackedIds);
}
finishSessionLocked(/* commitReason= */ COMMIT_REASON_VIEW_CHANGED);
+
+ }
+ if (mVisibleDialogTrackedIds.isEmpty()) {
+ if (sVerbose) {
+ Log.v(TAG, "No more visible ids. Invisible = " + mInvisibleDialogTrackedIds);
+ }
+ processNoVisibleTrackedAllViews();
}
}
@@ -3653,66 +3735,66 @@
// The visibility of the views might have changed while the client was not be visible,
// hence update the visibility state for all views.
AutofillClient client = getClient();
- ArraySet<AutofillId> updatedVisibleTrackedIds = null;
- ArraySet<AutofillId> updatedInvisibleTrackedIds = null;
if (client != null) {
if (sVerbose) {
Log.v(TAG, "onVisibleForAutofillChangedLocked(): inv= " + mInvisibleTrackedIds
+ " vis=" + mVisibleTrackedIds);
}
- if (mInvisibleTrackedIds != null) {
- final ArrayList<AutofillId> orderedInvisibleIds =
- new ArrayList<>(mInvisibleTrackedIds);
- final boolean[] isVisible = client.autofillClientGetViewVisibility(
- Helper.toArray(orderedInvisibleIds));
- final int numInvisibleTrackedIds = orderedInvisibleIds.size();
- for (int i = 0; i < numInvisibleTrackedIds; i++) {
- final AutofillId id = orderedInvisibleIds.get(i);
- if (isVisible[i]) {
- updatedVisibleTrackedIds = addToSet(updatedVisibleTrackedIds, id);
-
- if (sDebug) {
- Log.d(TAG, "onVisibleForAutofill() " + id + " became visible");
- }
- } else {
- updatedInvisibleTrackedIds = addToSet(updatedInvisibleTrackedIds, id);
- }
- }
- }
-
- if (mVisibleTrackedIds != null) {
- final ArrayList<AutofillId> orderedVisibleIds =
- new ArrayList<>(mVisibleTrackedIds);
- final boolean[] isVisible = client.autofillClientGetViewVisibility(
- Helper.toArray(orderedVisibleIds));
-
- final int numVisibleTrackedIds = orderedVisibleIds.size();
- for (int i = 0; i < numVisibleTrackedIds; i++) {
- final AutofillId id = orderedVisibleIds.get(i);
-
- if (isVisible[i]) {
- updatedVisibleTrackedIds = addToSet(updatedVisibleTrackedIds, id);
- } else {
- updatedInvisibleTrackedIds = addToSet(updatedInvisibleTrackedIds, id);
-
- if (sDebug) {
- Log.d(TAG, "onVisibleForAutofill() " + id + " became invisible");
- }
- }
- }
- }
-
- mInvisibleTrackedIds = updatedInvisibleTrackedIds;
- mVisibleTrackedIds = updatedVisibleTrackedIds;
+ onVisibleForAutofillChangedInternalLocked(mVisibleTrackedIds, mInvisibleTrackedIds);
+ onVisibleForAutofillChangedInternalLocked(
+ mVisibleDialogTrackedIds, mInvisibleDialogTrackedIds);
}
- if (mVisibleTrackedIds == null) {
+ if (mIsTrackedSaveView && mVisibleTrackedIds.isEmpty()) {
if (sVerbose) {
- Log.v(TAG, "onVisibleForAutofillChangedLocked(): no more visible ids");
+ Log.v(TAG, "onVisibleForAutofillChangedLocked(): no more visible ids");
}
finishSessionLocked(/* commitReason= */ COMMIT_REASON_VIEW_CHANGED);
}
+ if (mVisibleDialogTrackedIds.isEmpty()) {
+ if (sVerbose) {
+ Log.v(TAG, "onVisibleForAutofillChangedLocked(): no more visible ids");
+ }
+ processNoVisibleTrackedAllViews();
+ }
+ }
+
+ void onVisibleForAutofillChangedInternalLocked(@NonNull ArraySet<AutofillId> visibleSet,
+ @NonNull ArraySet<AutofillId> invisibleSet) {
+ // The visibility of the views might have changed while the client was not be visible,
+ // hence update the visibility state for all views.
+ if (sVerbose) {
+ Log.v(TAG, "onVisibleForAutofillChangedLocked(): inv= " + invisibleSet
+ + " vis=" + visibleSet);
+ }
+
+ ArraySet<AutofillId> allTrackedIds = new ArraySet<>();
+ allTrackedIds.addAll(visibleSet);
+ allTrackedIds.addAll(invisibleSet);
+ if (!allTrackedIds.isEmpty()) {
+ visibleSet.clear();
+ invisibleSet.clear();
+ initialTrackedViews(Helper.toArray(allTrackedIds), visibleSet, invisibleSet);
+ }
+ }
+
+ private void processNoVisibleTrackedAllViews() {
+ mShowAutofillDialogCalled = false;
+ }
+
+ void checkViewState(AutofillId id) {
+ if (mAllTrackedViews.contains(id)) {
+ return;
+ }
+ // Add the id as tracked to avoid triggering fill request again and again.
+ mAllTrackedViews.add(id);
+ if (mHasNewTrackedView) {
+ return;
+ }
+ // First one new tracks view
+ mIsFillRequested.set(false);
+ mHasNewTrackedView = true;
}
}
diff --git a/core/java/com/android/internal/app/IVoiceInteractionSessionListener.aidl b/core/java/com/android/internal/app/IVoiceInteractionSessionListener.aidl
index 6e40988..46f78e2 100644
--- a/core/java/com/android/internal/app/IVoiceInteractionSessionListener.aidl
+++ b/core/java/com/android/internal/app/IVoiceInteractionSessionListener.aidl
@@ -31,6 +31,8 @@
/**
* Called when a voice session window is shown/hidden.
+ * Caution that there could be duplicated visibility change callbacks, it's up to the listener
+ * to dedup those events.
*/
void onVoiceSessionWindowVisibilityChanged(boolean visible);
diff --git a/core/java/com/android/internal/widget/LockPatternUtils.java b/core/java/com/android/internal/widget/LockPatternUtils.java
index 1235b60..2dfe893 100644
--- a/core/java/com/android/internal/widget/LockPatternUtils.java
+++ b/core/java/com/android/internal/widget/LockPatternUtils.java
@@ -1522,7 +1522,8 @@
STRONG_AUTH_REQUIRED_AFTER_LOCKOUT,
STRONG_AUTH_REQUIRED_AFTER_TIMEOUT,
STRONG_AUTH_REQUIRED_AFTER_USER_LOCKDOWN,
- STRONG_AUTH_REQUIRED_AFTER_NON_STRONG_BIOMETRICS_TIMEOUT})
+ STRONG_AUTH_REQUIRED_AFTER_NON_STRONG_BIOMETRICS_TIMEOUT,
+ SOME_AUTH_REQUIRED_AFTER_TRUSTAGENT_EXPIRED})
@Retention(RetentionPolicy.SOURCE)
public @interface StrongAuthFlags {}
@@ -1575,11 +1576,18 @@
public static final int STRONG_AUTH_REQUIRED_AFTER_NON_STRONG_BIOMETRICS_TIMEOUT = 0x80;
/**
+ * Some authentication is required because the trustagent either timed out or was disabled
+ * manually.
+ */
+ public static final int SOME_AUTH_REQUIRED_AFTER_TRUSTAGENT_EXPIRED = 0x100;
+
+ /**
* Strong auth flags that do not prevent biometric methods from being accepted as auth.
* If any other flags are set, biometric authentication is disabled.
*/
private static final int ALLOWING_BIOMETRIC = STRONG_AUTH_NOT_REQUIRED
- | SOME_AUTH_REQUIRED_AFTER_USER_REQUEST;
+ | SOME_AUTH_REQUIRED_AFTER_USER_REQUEST
+ | SOME_AUTH_REQUIRED_AFTER_TRUSTAGENT_EXPIRED;
private final SparseIntArray mStrongAuthRequiredForUser = new SparseIntArray();
private final H mHandler;
diff --git a/core/res/res/values-be/strings.xml b/core/res/res/values-be/strings.xml
index c527e59..2d8f21d 100644
--- a/core/res/res/values-be/strings.xml
+++ b/core/res/res/values-be/strings.xml
@@ -1974,7 +1974,7 @@
<string name="pin_specific_target" msgid="7824671240625957415">"Замацаваць праграму \"<xliff:g id="LABEL">%1$s</xliff:g>\""</string>
<string name="unpin_target" msgid="3963318576590204447">"Адмацаваць"</string>
<string name="unpin_specific_target" msgid="3859828252160908146">"Адмацаваць праграму \"<xliff:g id="LABEL">%1$s</xliff:g>\""</string>
- <string name="app_info" msgid="6113278084877079851">"Інфармацыя пра праграму"</string>
+ <string name="app_info" msgid="6113278084877079851">"Звесткі аб праграме"</string>
<string name="negative_duration" msgid="1938335096972945232">"−<xliff:g id="TIME">%1$s</xliff:g>"</string>
<string name="demo_starting_message" msgid="6577581216125805905">"Ідзе запуск дэманстрацыі…"</string>
<string name="demo_restarting_message" msgid="1160053183701746766">"Ідзе скід налад прылады…"</string>
diff --git a/core/res/res/values-fr-rCA/strings.xml b/core/res/res/values-fr-rCA/strings.xml
index 0ec4f1d..40b265a 100644
--- a/core/res/res/values-fr-rCA/strings.xml
+++ b/core/res/res/values-fr-rCA/strings.xml
@@ -1167,8 +1167,8 @@
<string name="no" msgid="5122037903299899715">"Annuler"</string>
<string name="dialog_alert_title" msgid="651856561974090712">"Attention"</string>
<string name="loading" msgid="3138021523725055037">"Chargement en cours..."</string>
- <string name="capital_on" msgid="2770685323900821829">"OUI"</string>
- <string name="capital_off" msgid="7443704171014626777">"NON"</string>
+ <string name="capital_on" msgid="2770685323900821829">"ACTIVÉ"</string>
+ <string name="capital_off" msgid="7443704171014626777">"DÉSACTIVÉ"</string>
<string name="checked" msgid="9179896827054513119">"coché"</string>
<string name="not_checked" msgid="7972320087569023342">"non coché"</string>
<string name="selected" msgid="6614607926197755875">"sélectionné"</string>
diff --git a/core/res/res/values-in/strings.xml b/core/res/res/values-in/strings.xml
index 38a7c7b..b02345a 100644
--- a/core/res/res/values-in/strings.xml
+++ b/core/res/res/values-in/strings.xml
@@ -626,15 +626,15 @@
</string-array>
<string name="fingerprint_error_vendor_unknown" msgid="4170002184907291065">"Terjadi error. Coba lagi."</string>
<string name="fingerprint_icon_content_description" msgid="4741068463175388817">"Ikon sidik jari"</string>
- <string name="face_recalibrate_notification_name" msgid="7311163114750748686">"Face Unlock"</string>
- <string name="face_recalibrate_notification_title" msgid="2524791952735579082">"Masalah pada Face Unlock"</string>
+ <string name="face_recalibrate_notification_name" msgid="7311163114750748686">"Buka dengan Wajah"</string>
+ <string name="face_recalibrate_notification_title" msgid="2524791952735579082">"Masalah pada Buka dengan Wajah"</string>
<string name="face_recalibrate_notification_content" msgid="3064513770251355594">"Ketuk untuk menghapus model wajah, lalu tambahkan wajah Anda lagi"</string>
- <string name="face_setup_notification_title" msgid="8843461561970741790">"Siapkan Face Unlock"</string>
+ <string name="face_setup_notification_title" msgid="8843461561970741790">"Siapkan Buka dengan Wajah"</string>
<string name="face_setup_notification_content" msgid="5463999831057751676">"Buka kunci ponsel dengan melihat ke ponsel"</string>
- <string name="face_sensor_privacy_enabled" msgid="7407126963510598508">"Untuk menggunakan Face Unlock, aktifkan "<b>"Akses kamera"</b>" di Setelan > Privasi"</string>
+ <string name="face_sensor_privacy_enabled" msgid="7407126963510598508">"Untuk menggunakan Buka dengan Wajah, aktifkan "<b>"Akses kamera"</b>" di Setelan > Privasi"</string>
<string name="fingerprint_setup_notification_title" msgid="2002630611398849495">"Siapkan lebih banyak cara untuk membuka kunci"</string>
<string name="fingerprint_setup_notification_content" msgid="205578121848324852">"Ketuk untuk menambahkan sidik jari"</string>
- <string name="fingerprint_recalibrate_notification_name" msgid="1414578431898579354">"Fingerprint Unlock"</string>
+ <string name="fingerprint_recalibrate_notification_name" msgid="1414578431898579354">"Buka dengan Sidik Jari"</string>
<string name="fingerprint_recalibrate_notification_title" msgid="2406561052064558497">"Tidak dapat menggunakan sensor sidik jari"</string>
<string name="fingerprint_recalibrate_notification_content" msgid="8519935717822194943">"Kunjungi penyedia reparasi."</string>
<string name="face_acquired_insufficient" msgid="6889245852748492218">"Tidak dapat membuat model wajah Anda. Coba lagi."</string>
@@ -667,19 +667,19 @@
<string-array name="face_acquired_vendor">
</string-array>
<string name="face_error_hw_not_available" msgid="5085202213036026288">"Tidak dapat memverifikasi wajah. Hardware tidak tersedia."</string>
- <string name="face_error_timeout" msgid="2598544068593889762">"Coba Face Unlock lagi"</string>
+ <string name="face_error_timeout" msgid="2598544068593889762">"Coba Buka dengan Wajah lagi"</string>
<string name="face_error_no_space" msgid="5649264057026021723">"Tidak dapat menyimpan data wajah. Hapus dahulu data lama."</string>
<string name="face_error_canceled" msgid="2164434737103802131">"Pemrosesan wajah dibatalkan."</string>
- <string name="face_error_user_canceled" msgid="5766472033202928373">"Face Unlock dibatalkan oleh pengguna"</string>
+ <string name="face_error_user_canceled" msgid="5766472033202928373">"Buka dengan Wajah dibatalkan oleh pengguna"</string>
<string name="face_error_lockout" msgid="7864408714994529437">"Terlalu banyak percobaan. Coba lagi nanti."</string>
- <string name="face_error_lockout_permanent" msgid="3277134834042995260">"Terlalu banyak upaya gagal. Face Unlock dinonaktifkan."</string>
+ <string name="face_error_lockout_permanent" msgid="3277134834042995260">"Terlalu banyak upaya gagal. Buka dengan Wajah dinonaktifkan."</string>
<string name="face_error_lockout_screen_lock" msgid="5062609811636860928">"Terlalu banyak upaya gagal. Masukkan kunci layar."</string>
<string name="face_error_unable_to_process" msgid="5723292697366130070">"Tidak dapat memverifikasi wajah. Coba lagi."</string>
- <string name="face_error_not_enrolled" msgid="1134739108536328412">"Anda belum menyiapkan Face Unlock"</string>
- <string name="face_error_hw_not_present" msgid="7940978724978763011">"Face Unlock tidak didukung di perangkat ini"</string>
+ <string name="face_error_not_enrolled" msgid="1134739108536328412">"Anda belum menyiapkan Buka dengan Wajah"</string>
+ <string name="face_error_hw_not_present" msgid="7940978724978763011">"Buka dengan Wajah tidak didukung di perangkat ini"</string>
<string name="face_error_security_update_required" msgid="5076017208528750161">"Sensor dinonaktifkan untuk sementara."</string>
<string name="face_name_template" msgid="3877037340223318119">"<xliff:g id="FACEID">%d</xliff:g> wajah"</string>
- <string name="face_app_setting_name" msgid="5854024256907828015">"Gunakan Face Unlock"</string>
+ <string name="face_app_setting_name" msgid="5854024256907828015">"Gunakan Buka dengan Wajah"</string>
<string name="face_or_screen_lock_app_setting_name" msgid="1603149075605709106">"Gunakan face lock atau kunci layar"</string>
<string name="face_dialog_default_subtitle" msgid="6620492813371195429">"Gunakan wajah untuk melanjutkan"</string>
<string name="face_or_screen_lock_dialog_default_subtitle" msgid="5006381531158341844">"Gunakan face lock atau kunci layar untuk melanjutkan"</string>
@@ -928,7 +928,7 @@
<string name="lockscreen_pattern_wrong" msgid="2940138714468358458">"Coba lagi"</string>
<string name="lockscreen_password_wrong" msgid="8605355913868947490">"Coba lagi"</string>
<string name="lockscreen_storage_locked" msgid="634993789186443380">"Membuka kunci untuk semua fitur dan data"</string>
- <string name="faceunlock_multiple_failures" msgid="681991538434031708">"Percobaan Face Unlock melebihi batas maksimum"</string>
+ <string name="faceunlock_multiple_failures" msgid="681991538434031708">"Percobaan Buka dengan Wajah melebihi batas maksimum"</string>
<string name="lockscreen_missing_sim_message_short" msgid="1248431165144893792">"Tidak ada kartu SIM"</string>
<string name="lockscreen_missing_sim_message" product="tablet" msgid="8596805728510570760">"Tidak ada kartu SIM dalam tablet."</string>
<string name="lockscreen_missing_sim_message" product="tv" msgid="2582768023352171073">"Tidak ada kartu SIM di perangkat Android TV."</string>
@@ -998,7 +998,7 @@
<string name="keyguard_accessibility_expand_lock_area" msgid="4215280881346033434">"Luaskan area buka kunci."</string>
<string name="keyguard_accessibility_slide_unlock" msgid="2968195219692413046">"Buka kunci dengan menggeser."</string>
<string name="keyguard_accessibility_pattern_unlock" msgid="8669128146589233293">"Buka kunci dengan pola."</string>
- <string name="keyguard_accessibility_face_unlock" msgid="4533832120787386728">"Face Unlock."</string>
+ <string name="keyguard_accessibility_face_unlock" msgid="4533832120787386728">"Buka dengan Wajah."</string>
<string name="keyguard_accessibility_pin_unlock" msgid="4020864007967340068">"Buka kunci dengan PIN."</string>
<string name="keyguard_accessibility_sim_pin_unlock" msgid="4895939120871890557">"PIN SIM terbuka."</string>
<string name="keyguard_accessibility_sim_puk_unlock" msgid="3459003464041899101">"PUK SIM terbuka."</string>
diff --git a/core/res/res/values-pt-rBR/strings.xml b/core/res/res/values-pt-rBR/strings.xml
index 6b88f8e..42a477b 100644
--- a/core/res/res/values-pt-rBR/strings.xml
+++ b/core/res/res/values-pt-rBR/strings.xml
@@ -897,7 +897,7 @@
<string name="relationTypeFriend" msgid="3192092625893980574">"Amigo(a)"</string>
<string name="relationTypeManager" msgid="2272860813153171857">"Gerente"</string>
<string name="relationTypeMother" msgid="2331762740982699460">"Mãe"</string>
- <string name="relationTypeParent" msgid="4177920938333039882">"Pai/Mãe"</string>
+ <string name="relationTypeParent" msgid="4177920938333039882">"familiar responsável"</string>
<string name="relationTypePartner" msgid="4018017075116766194">"Parceiro"</string>
<string name="relationTypeReferredBy" msgid="5285082289602849400">"Indicado por"</string>
<string name="relationTypeRelative" msgid="3396498519818009134">"Parente"</string>
diff --git a/core/res/res/values-pt/strings.xml b/core/res/res/values-pt/strings.xml
index 6b88f8e..42a477b 100644
--- a/core/res/res/values-pt/strings.xml
+++ b/core/res/res/values-pt/strings.xml
@@ -897,7 +897,7 @@
<string name="relationTypeFriend" msgid="3192092625893980574">"Amigo(a)"</string>
<string name="relationTypeManager" msgid="2272860813153171857">"Gerente"</string>
<string name="relationTypeMother" msgid="2331762740982699460">"Mãe"</string>
- <string name="relationTypeParent" msgid="4177920938333039882">"Pai/Mãe"</string>
+ <string name="relationTypeParent" msgid="4177920938333039882">"familiar responsável"</string>
<string name="relationTypePartner" msgid="4018017075116766194">"Parceiro"</string>
<string name="relationTypeReferredBy" msgid="5285082289602849400">"Indicado por"</string>
<string name="relationTypeRelative" msgid="3396498519818009134">"Parente"</string>
diff --git a/core/res/res/values-sk/strings.xml b/core/res/res/values-sk/strings.xml
index bfb7a38..01e57e1 100644
--- a/core/res/res/values-sk/strings.xml
+++ b/core/res/res/values-sk/strings.xml
@@ -993,7 +993,7 @@
<string name="keyguard_accessibility_user_selector" msgid="1466067610235696600">"Výber používateľa"</string>
<string name="keyguard_accessibility_status" msgid="6792745049712397237">"Stav"</string>
<string name="keyguard_accessibility_camera" msgid="7862557559464986528">"Fotoaparát"</string>
- <string name="keygaurd_accessibility_media_controls" msgid="2267379779900620614">"Ovládacie prvky médií"</string>
+ <string name="keygaurd_accessibility_media_controls" msgid="2267379779900620614">"Ovládanie médií"</string>
<string name="keyguard_accessibility_widget_reorder_start" msgid="7066213328912939191">"Zmena usporiadania miniaplikácií sa začala."</string>
<string name="keyguard_accessibility_widget_reorder_end" msgid="1083806817600593490">"Zmena usporiadania miniaplikácií sa skončila."</string>
<string name="keyguard_accessibility_widget_deleted" msgid="1509738950119878705">"Miniaplikácia <xliff:g id="WIDGET_INDEX">%1$s</xliff:g> bola odstránená."</string>
diff --git a/core/res/res/values-te/strings.xml b/core/res/res/values-te/strings.xml
index 8647c5c..f047022 100644
--- a/core/res/res/values-te/strings.xml
+++ b/core/res/res/values-te/strings.xml
@@ -364,7 +364,7 @@
<string name="permdesc_receiveMms" msgid="958102423732219710">"MMS మెసేజ్లను స్వీకరించడానికి, ప్రాసెస్ చేయడానికి యాప్ను అనుమతిస్తుంది. మీ డివైజ్కు వచ్చిన మెసేజ్లను మీకు చూపకుండానే యాప్ పర్యవేక్షించగలదని లేదా తొలగించగలదని దీని అర్థం."</string>
<string name="permlab_bindCellBroadcastService" msgid="586746677002040651">"సెల్ ప్రసార మెసేజ్లను ఫార్వర్డ్ చేయడం"</string>
<string name="permdesc_bindCellBroadcastService" msgid="6540910200973641606">"సెల్ ప్రసార మెసేజ్లను స్వీకరించినప్పుడు, వాటిని ఫార్వర్డ్ చేయడానికి సెల్ ప్రసార మాడ్యూల్కు కట్టుబడి ఉండేందుకు యాప్ను అనుమతిస్తుంది. ఎమర్జెన్సీ పరిస్థితుల గురించి మిమ్మల్ని హెచ్చరించడానికి కొన్ని లొకేషన్లలో సెల్ ప్రసార అలర్ట్లు డెలివరీ చేయబడతాయి. ఎమర్జెన్సీ సెల్ ప్రసార అలర్ట్ను స్వీకరించినప్పుడు హానికరమైన యాప్లు మీ పరికరం పనితీరుకు లేదా నిర్వహణకు ఆటంకం కలిగించే అవకాశం ఉంది."</string>
- <string name="permlab_manageOngoingCalls" msgid="281244770664231782">"కొనసాగుతున్న కాల్స్ను మేనేజ్ చేయి"</string>
+ <string name="permlab_manageOngoingCalls" msgid="281244770664231782">"కొనసాగుతున్న కాల్స్ను మేనేజ్ చేయండి"</string>
<string name="permdesc_manageOngoingCalls" msgid="7003138133829915265">"మీ పరికరంలో కొనసాగుతున్న కాల్స్ను చూడటానికి అలాగే వాటిని కంట్రోల్ చేయడానికి ఒక యాప్కు అనుమతిస్తోంది."</string>
<string name="permlab_readCellBroadcasts" msgid="5869884450872137693">"సెల్ ప్రసార మెసేజ్లను చదవడం"</string>
<string name="permdesc_readCellBroadcasts" msgid="672513437331980168">"మీ పరికరం స్వీకరించిన సెల్ ప్రసార మెసేజ్లను చదవడానికి యాప్ను అనుమతిస్తుంది. ఎమర్జెన్సీ పరిస్థితుల గురించి మిమ్మల్ని హెచ్చరించడానికి కొన్ని లొకేషన్లలో సెల్ ప్రసార అలర్ట్లు డెలివరీ చేయబడతాయి. ఎమర్జెన్సీ సెల్ ప్రసార అలర్ట్ను స్వీకరించినప్పుడు హానికరమైన యాప్లు మీ పరికరం పనితీరుకు లేదా నిర్వహణకు ఆటంకం కలిగించే అవకాశం ఉంది."</string>
@@ -1149,7 +1149,7 @@
<string name="redo" msgid="7231448494008532233">"చర్యను రిపీట్ చేయి"</string>
<string name="autofill" msgid="511224882647795296">"ఆటోఫిల్"</string>
<string name="textSelectionCABTitle" msgid="5151441579532476940">"వచన ఎంపిక"</string>
- <string name="addToDictionary" msgid="8041821113480950096">"నిఘంటువుకు జోడించు"</string>
+ <string name="addToDictionary" msgid="8041821113480950096">"నిఘంటువుకు జోడించండి"</string>
<string name="deleteText" msgid="4200807474529938112">"తొలగించండి"</string>
<string name="inputMethod" msgid="1784759500516314751">"ఇన్పుట్ పద్ధతి"</string>
<string name="editTextMenuTitle" msgid="857666911134482176">"వచనానికి సంబంధించిన చర్యలు"</string>
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index bbbb79c..22f4298 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -4789,11 +4789,11 @@
<integer name="config_defaultPeakRefreshRate">0</integer>
<!-- The display uses different gamma curves for different refresh rates. It's hard for panel
- vendor to tune the curves to have exact same brightness for different refresh rate. So
+ vendors to tune the curves to have exact same brightness for different refresh rate. So
flicker could be observed at switch time. The issue is worse at the gamma lower end.
In addition, human eyes are more sensitive to the flicker at darker environment.
To prevent flicker, we only support higher refresh rates if the display brightness is above
- a threshold. And the darker environment could have higher threshold.
+ a threshold.
For example, no higher refresh rate if
display brightness <= disp0 && ambient brightness <= amb0
|| display brightness <= disp1 && ambient brightness <= amb1 -->
@@ -4815,13 +4815,12 @@
<integer name="config_defaultRefreshRateInZone">0</integer>
<!-- The display uses different gamma curves for different refresh rates. It's hard for panel
- vendor to tune the curves to have exact same brightness for different refresh rate. So
+ vendors to tune the curves to have exact same brightness for different refresh rate. So
flicker could be observed at switch time. The issue can be observed on the screen with
even full white content at the high brightness. To prevent flickering, we support fixed
refresh rates if the display and ambient brightness are equal to or above the provided
thresholds. You can define multiple threshold levels as higher brightness environments
- may have lower display brightness requirements for the flickering is visible. And the
- high brightness environment could have higher threshold.
+ may have lower display brightness requirements for the flickering is visible.
For example, fixed refresh rate if
display brightness >= disp0 && ambient brightness >= amb0
|| display brightness >= disp1 && ambient brightness >= amb1 -->
diff --git a/core/tests/utiltests/src/com/android/internal/util/LockPatternUtilsTest.java b/core/tests/utiltests/src/com/android/internal/util/LockPatternUtilsTest.java
index 4679a9e..0b70199 100644
--- a/core/tests/utiltests/src/com/android/internal/util/LockPatternUtilsTest.java
+++ b/core/tests/utiltests/src/com/android/internal/util/LockPatternUtilsTest.java
@@ -19,6 +19,9 @@
import static android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_MANAGED;
import static android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED;
+import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.SOME_AUTH_REQUIRED_AFTER_TRUSTAGENT_EXPIRED;
+import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_LOCKOUT;
+
import static com.google.common.truth.Truth.assertThat;
import static org.junit.Assert.assertFalse;
@@ -37,6 +40,7 @@
import android.content.Context;
import android.content.ContextWrapper;
import android.content.pm.UserInfo;
+import android.os.Looper;
import android.os.RemoteException;
import android.os.UserManager;
import android.provider.Settings;
@@ -233,6 +237,45 @@
ComponentName.unflattenFromString("com.test/.TestAgent"));
}
+ @Test
+ public void isBiometricAllowedForUser_afterTrustagentExpired_returnsTrue()
+ throws RemoteException {
+ TestStrongAuthTracker tracker = createStrongAuthTracker();
+ tracker.changeStrongAuth(SOME_AUTH_REQUIRED_AFTER_TRUSTAGENT_EXPIRED);
+
+ assertTrue(tracker.isBiometricAllowedForUser(
+ /* isStrongBiometric = */ true,
+ DEMO_USER_ID));
+ }
+
+ @Test
+ public void isBiometricAllowedForUser_afterLockout_returnsFalse()
+ throws RemoteException {
+ TestStrongAuthTracker tracker = createStrongAuthTracker();
+ tracker.changeStrongAuth(STRONG_AUTH_REQUIRED_AFTER_LOCKOUT);
+
+ assertFalse(tracker.isBiometricAllowedForUser(
+ /* isStrongBiometric = */ true,
+ DEMO_USER_ID));
+ }
+
+
+ private TestStrongAuthTracker createStrongAuthTracker() {
+ final Context context = new ContextWrapper(InstrumentationRegistry.getTargetContext());
+ return new TestStrongAuthTracker(context, Looper.getMainLooper());
+ }
+
+ private static class TestStrongAuthTracker extends LockPatternUtils.StrongAuthTracker {
+
+ TestStrongAuthTracker(Context context, Looper looper) {
+ super(context, looper);
+ }
+
+ public void changeStrongAuth(@StrongAuthFlags int strongAuthFlags) {
+ handleStrongAuthRequiredChanged(strongAuthFlags, DEMO_USER_ID);
+ }
+ }
+
private ILockSettings createTestLockSettings() {
final Context context = spy(new ContextWrapper(InstrumentationRegistry.getTargetContext()));
mLockPatternUtils = spy(new LockPatternUtils(context));
diff --git a/data/keyboards/Vendor_18d1_Product_4f80.kl b/data/keyboards/Vendor_18d1_Product_4f80.kl
new file mode 100644
index 0000000..42a6925
--- /dev/null
+++ b/data/keyboards/Vendor_18d1_Product_4f80.kl
@@ -0,0 +1,21 @@
+# Copyright (C) 2022 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+#
+# Android Stylus
+#
+
+# Temporarily map the key reported by the stylus to an
+# arbitrary keycode for testing and development.
+key 0x242 VIDEO_APP_1
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java
index d3dc05f..405ee14 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java
@@ -425,12 +425,8 @@
if (container != null) {
// Cleanup if the TaskFragment vanished is not requested by the organizer.
removeContainer(container);
- // Make sure the top container is updated.
- final TaskFragmentContainer newTopContainer = getTopActiveContainer(
- container.getTaskId());
- if (newTopContainer != null) {
- updateContainer(wct, newTopContainer);
- }
+ // Make sure the containers in the Task are up-to-date.
+ updateContainersInTaskIfVisible(wct, container.getTaskId());
}
cleanupTaskFragment(taskFragmentInfo.getFragmentToken());
}
@@ -470,6 +466,13 @@
updateContainersInTask(wct, taskContainer);
}
+ void updateContainersInTaskIfVisible(@NonNull WindowContainerTransaction wct, int taskId) {
+ final TaskContainer taskContainer = getTaskContainer(taskId);
+ if (taskContainer != null && taskContainer.isVisible()) {
+ updateContainersInTask(wct, taskContainer);
+ }
+ }
+
private void updateContainersInTask(@NonNull WindowContainerTransaction wct,
@NonNull TaskContainer taskContainer) {
// Update all TaskFragments in the Task. Make a copy of the list since some may be
@@ -1320,9 +1323,6 @@
void removeContainer(@NonNull TaskFragmentContainer container) {
// Remove all split containers that included this one
final TaskContainer taskContainer = container.getTaskContainer();
- if (taskContainer == null) {
- return;
- }
taskContainer.mContainers.remove(container);
// Marked as a pending removal which will be removed after it is actually removed on the
// server side (#onTaskFragmentVanished).
@@ -1515,14 +1515,8 @@
}
final TaskFragmentContainer container = getContainerWithActivity(activity);
- // Don't launch placeholder if the container is occluded.
- if (container != null && container != getTopActiveContainer(container.getTaskId())) {
- return false;
- }
-
- final SplitContainer splitContainer = getActiveSplitForContainer(container);
- if (splitContainer != null && container.equals(splitContainer.getPrimaryContainer())) {
- // Don't launch placeholder in primary split container
+ if (container != null && !allowLaunchPlaceholder(container)) {
+ // We don't allow activity in this TaskFragment to launch placeholder.
return false;
}
@@ -1550,6 +1544,32 @@
return true;
}
+ /** Whether or not to allow activity in this container to launch placeholder. */
+ @GuardedBy("mLock")
+ private boolean allowLaunchPlaceholder(@NonNull TaskFragmentContainer container) {
+ final TaskFragmentContainer topContainer = getTopActiveContainer(container.getTaskId());
+ if (container != topContainer) {
+ // The container is not the top most.
+ if (!container.isVisible()) {
+ // In case the container is visible (the one on top may be transparent), we may
+ // still want to launch placeholder even if it is not the top most.
+ return false;
+ }
+ if (topContainer.isWaitingActivityAppear()) {
+ // When the top container appeared info is not sent by the server yet, the visible
+ // check above may not be reliable.
+ return false;
+ }
+ }
+
+ final SplitContainer splitContainer = getActiveSplitForContainer(container);
+ if (splitContainer != null && container.equals(splitContainer.getPrimaryContainer())) {
+ // Don't launch placeholder for primary split container.
+ return false;
+ }
+ return true;
+ }
+
/**
* Gets the activity options for starting the placeholder activity. In case the placeholder is
* launched when the Task is in the background, we don't want to bring the Task to the front.
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitPresenter.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitPresenter.java
index 47253d3..a432e2b 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitPresenter.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitPresenter.java
@@ -154,12 +154,8 @@
void cleanupContainer(@NonNull WindowContainerTransaction wct,
@NonNull TaskFragmentContainer container, boolean shouldFinishDependent) {
container.finish(shouldFinishDependent, this, wct, mController);
-
- final TaskFragmentContainer newTopContainer = mController.getTopActiveContainer(
- container.getTaskId());
- if (newTopContainer != null) {
- mController.updateContainer(wct, newTopContainer);
- }
+ // Make sure the containers in the Task is up-to-date.
+ mController.updateContainersInTaskIfVisible(wct, container.getTaskId());
}
/**
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentContainer.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentContainer.java
index fcf0ac7..8240874 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentContainer.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentContainer.java
@@ -184,6 +184,11 @@
return allActivities;
}
+ /** Whether this TaskFragment is visible. */
+ boolean isVisible() {
+ return mInfo != null && mInfo.isVisible();
+ }
+
/** Whether the TaskFragment is in an intermediate state waiting for the server update.*/
boolean isInIntermediateState() {
if (mInfo == null) {
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/layout/WindowLayoutComponentImpl.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/layout/WindowLayoutComponentImpl.java
index b70b320..9b16877 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/layout/WindowLayoutComponentImpl.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/layout/WindowLayoutComponentImpl.java
@@ -35,7 +35,6 @@
import android.os.Bundle;
import android.os.IBinder;
import android.util.ArrayMap;
-import android.window.WindowProvider;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
@@ -129,9 +128,10 @@
});
mWindowLayoutChangeListeners.put(context, consumer);
- // TODO(b/258065175) Further extend this to ContextWrappers.
- if (context instanceof WindowProvider) {
- final IBinder windowContextToken = context.getWindowContextToken();
+ final IBinder windowContextToken = context.getWindowContextToken();
+ if (windowContextToken != null) {
+ // We register component callbacks for window contexts. For activity contexts, they will
+ // receive callbacks from NotifyOnConfigurationChanged instead.
final ConfigurationChangeListener listener =
new ConfigurationChangeListener(windowContextToken);
context.registerComponentCallbacks(listener);
@@ -150,8 +150,8 @@
if (!mWindowLayoutChangeListeners.get(context).equals(consumer)) {
continue;
}
- if (context instanceof WindowProvider) {
- final IBinder token = context.getWindowContextToken();
+ final IBinder token = context.getWindowContextToken();
+ if (token != null) {
context.unregisterComponentCallbacks(mConfigurationChangeListeners.get(token));
mConfigurationChangeListeners.remove(token);
}
@@ -308,9 +308,10 @@
return false;
}
final int windowingMode;
- if (context instanceof Activity) {
+ IBinder activityToken = context.getActivityToken();
+ if (activityToken != null) {
final Configuration taskConfig = ActivityClient.getInstance().getTaskConfiguration(
- context.getActivityToken());
+ activityToken);
if (taskConfig == null) {
// If we cannot determine the task configuration for any reason, it is likely that
// we won't be able to determine its position correctly as well. DisplayFeatures'
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/util/AcceptOnceConsumer.java b/libs/WindowManager/Jetpack/src/androidx/window/util/AcceptOnceConsumer.java
index 7624b69..fe60037 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/util/AcceptOnceConsumer.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/util/AcceptOnceConsumer.java
@@ -27,9 +27,10 @@
*/
public class AcceptOnceConsumer<T> implements Consumer<T> {
private final Consumer<T> mCallback;
- private final DataProducer<T> mProducer;
+ private final AcceptOnceProducerCallback<T> mProducer;
- public AcceptOnceConsumer(@NonNull DataProducer<T> producer, @NonNull Consumer<T> callback) {
+ public AcceptOnceConsumer(@NonNull AcceptOnceProducerCallback<T> producer,
+ @NonNull Consumer<T> callback) {
mProducer = producer;
mCallback = callback;
}
@@ -37,6 +38,20 @@
@Override
public void accept(@NonNull T t) {
mCallback.accept(t);
- mProducer.removeDataChangedCallback(this);
+ mProducer.onConsumerReadyToBeRemoved(this);
+ }
+
+ /**
+ * Interface to allow the {@link AcceptOnceConsumer} to notify the client that created it,
+ * when it is ready to be removed. This allows the client to remove the consumer object
+ * when it deems it is safe to do so.
+ * @param <T> The type of data this callback accepts through {@link #onConsumerReadyToBeRemoved}
+ */
+ public interface AcceptOnceProducerCallback<T> {
+
+ /**
+ * Notifies that the given {@code callback} is ready to be removed
+ */
+ void onConsumerReadyToBeRemoved(Consumer<T> callback);
}
}
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/util/BaseDataProducer.java b/libs/WindowManager/Jetpack/src/androidx/window/util/BaseDataProducer.java
index cbaa277..46c925a 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/util/BaseDataProducer.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/util/BaseDataProducer.java
@@ -19,6 +19,7 @@
import androidx.annotation.GuardedBy;
import androidx.annotation.NonNull;
+import java.util.HashSet;
import java.util.LinkedHashSet;
import java.util.Optional;
import java.util.Set;
@@ -31,11 +32,14 @@
*
* @param <T> The type of data this producer returns through {@link DataProducer#getData}.
*/
-public abstract class BaseDataProducer<T> implements DataProducer<T> {
+public abstract class BaseDataProducer<T> implements DataProducer<T>,
+ AcceptOnceConsumer.AcceptOnceProducerCallback<T> {
private final Object mLock = new Object();
@GuardedBy("mLock")
private final Set<Consumer<T>> mCallbacks = new LinkedHashSet<>();
+ @GuardedBy("mLock")
+ private final Set<Consumer<T>> mCallbacksToRemove = new HashSet<>();
/**
* Adds a callback to the set of callbacks listening for data. Data is delivered through
@@ -85,6 +89,26 @@
for (Consumer<T> callback : mCallbacks) {
callback.accept(value);
}
+ removeFinishedCallbacksLocked();
+ }
+ }
+
+ /**
+ * Removes any callbacks that notified us through {@link #onConsumerReadyToBeRemoved(Consumer)}
+ * that they are ready to be removed.
+ */
+ @GuardedBy("mLock")
+ private void removeFinishedCallbacksLocked() {
+ for (Consumer<T> callback: mCallbacksToRemove) {
+ mCallbacks.remove(callback);
+ }
+ mCallbacksToRemove.clear();
+ }
+
+ @Override
+ public void onConsumerReadyToBeRemoved(Consumer<T> callback) {
+ synchronized (mLock) {
+ mCallbacksToRemove.add(callback);
}
}
}
\ No newline at end of file
diff --git a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/EmbeddingTestUtils.java b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/EmbeddingTestUtils.java
index bc03e4e..2f92a57 100644
--- a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/EmbeddingTestUtils.java
+++ b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/EmbeddingTestUtils.java
@@ -163,11 +163,17 @@
/** Creates a mock TaskFragmentInfo for the given TaskFragment. */
static TaskFragmentInfo createMockTaskFragmentInfo(@NonNull TaskFragmentContainer container,
@NonNull Activity activity) {
+ return createMockTaskFragmentInfo(container, activity, true /* isVisible */);
+ }
+
+ /** Creates a mock TaskFragmentInfo for the given TaskFragment. */
+ static TaskFragmentInfo createMockTaskFragmentInfo(@NonNull TaskFragmentContainer container,
+ @NonNull Activity activity, boolean isVisible) {
return new TaskFragmentInfo(container.getTaskFragmentToken(),
mock(WindowContainerToken.class),
new Configuration(),
1,
- true /* isVisible */,
+ isVisible,
Collections.singletonList(activity.getActivityToken()),
new Point(),
false /* isTaskClearedForReuse */,
diff --git a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/SplitControllerTest.java b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/SplitControllerTest.java
index 6725dfd..3cc31f9 100644
--- a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/SplitControllerTest.java
+++ b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/SplitControllerTest.java
@@ -1254,6 +1254,68 @@
verify(mEmbeddingCallback).accept(any());
}
+ @Test
+ public void testLaunchPlaceholderIfNecessary_nonEmbeddedActivity() {
+ // Launch placeholder for non embedded activity.
+ setupPlaceholderRule(mActivity);
+ mTransactionManager.startNewTransaction();
+ mSplitController.launchPlaceholderIfNecessary(mTransaction, mActivity,
+ true /* isOnCreated */);
+
+ verify(mTransaction).startActivityInTaskFragment(any(), any(), eq(PLACEHOLDER_INTENT),
+ any());
+ }
+
+ @Test
+ public void testLaunchPlaceholderIfNecessary_embeddedInTopTaskFragment() {
+ // Launch placeholder for activity in top TaskFragment.
+ setupPlaceholderRule(mActivity);
+ mTransactionManager.startNewTransaction();
+ final TaskFragmentContainer container = mSplitController.newContainer(mActivity, TASK_ID);
+ mSplitController.launchPlaceholderIfNecessary(mTransaction, mActivity,
+ true /* isOnCreated */);
+
+ assertTrue(container.hasActivity(mActivity.getActivityToken()));
+ verify(mTransaction).startActivityInTaskFragment(any(), any(), eq(PLACEHOLDER_INTENT),
+ any());
+ }
+
+ @Test
+ public void testLaunchPlaceholderIfNecessary_embeddedBelowTaskFragment() {
+ // Do not launch placeholder for invisible activity below the top TaskFragment.
+ setupPlaceholderRule(mActivity);
+ mTransactionManager.startNewTransaction();
+ final TaskFragmentContainer bottomTf = mSplitController.newContainer(mActivity, TASK_ID);
+ final TaskFragmentContainer topTf = mSplitController.newContainer(new Intent(), mActivity,
+ TASK_ID);
+ bottomTf.setInfo(mTransaction, createMockTaskFragmentInfo(bottomTf, mActivity,
+ false /* isVisible */));
+ topTf.setInfo(mTransaction, createMockTaskFragmentInfo(topTf, createMockActivity()));
+ assertFalse(bottomTf.isVisible());
+ mSplitController.launchPlaceholderIfNecessary(mTransaction, mActivity,
+ true /* isOnCreated */);
+
+ verify(mTransaction, never()).startActivityInTaskFragment(any(), any(), any(), any());
+ }
+
+ @Test
+ public void testLaunchPlaceholderIfNecessary_embeddedBelowTransparentTaskFragment() {
+ // Launch placeholder for visible activity below the top TaskFragment.
+ setupPlaceholderRule(mActivity);
+ mTransactionManager.startNewTransaction();
+ final TaskFragmentContainer bottomTf = mSplitController.newContainer(mActivity, TASK_ID);
+ final TaskFragmentContainer topTf = mSplitController.newContainer(new Intent(), mActivity,
+ TASK_ID);
+ bottomTf.setInfo(mTransaction, createMockTaskFragmentInfo(bottomTf, mActivity,
+ true /* isVisible */));
+ topTf.setInfo(mTransaction, createMockTaskFragmentInfo(topTf, createMockActivity()));
+ assertTrue(bottomTf.isVisible());
+ mSplitController.launchPlaceholderIfNecessary(mTransaction, mActivity,
+ true /* isOnCreated */);
+
+ verify(mTransaction).startActivityInTaskFragment(any(), any(), any(), any());
+ }
+
/** Creates a mock activity in the organizer process. */
private Activity createMockActivity() {
return createMockActivity(TASK_ID);
diff --git a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/TaskFragmentContainerTest.java b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/TaskFragmentContainerTest.java
index 5c3ba72..9877236 100644
--- a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/TaskFragmentContainerTest.java
+++ b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/TaskFragmentContainerTest.java
@@ -500,6 +500,29 @@
assertEquals(2, taskContainer.indexOf(tf1));
}
+ @Test
+ public void testIsVisible() {
+ final TaskContainer taskContainer = createTestTaskContainer();
+ final TaskFragmentContainer container = new TaskFragmentContainer(
+ null /* pendingAppearedActivity */, new Intent(), taskContainer, mController,
+ null /* pairedPrimaryTaskFragment */);
+
+ // Not visible when there is not appeared.
+ assertFalse(container.isVisible());
+
+ // Respect info.isVisible.
+ TaskFragmentInfo info = createMockTaskFragmentInfo(container, mActivity,
+ true /* isVisible */);
+ container.setInfo(mTransaction, info);
+
+ assertTrue(container.isVisible());
+
+ info = createMockTaskFragmentInfo(container, mActivity, false /* isVisible */);
+ container.setInfo(mTransaction, info);
+
+ assertFalse(container.isVisible());
+ }
+
/** Creates a mock activity in the organizer process. */
private Activity createMockActivity() {
final Activity activity = mock(Activity.class);
diff --git a/libs/WindowManager/Shell/res/values-ne/strings.xml b/libs/WindowManager/Shell/res/values-ne/strings.xml
index 4a937cf..4b10f5a 100644
--- a/libs/WindowManager/Shell/res/values-ne/strings.xml
+++ b/libs/WindowManager/Shell/res/values-ne/strings.xml
@@ -33,8 +33,7 @@
<string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"अनस्ट्यास गर्नुहोस्"</string>
<string name="dock_forced_resizable" msgid="1749750436092293116">"एप विभाजित स्क्रिनमा काम नगर्न सक्छ।"</string>
<string name="dock_non_resizeble_failed_to_dock_text" msgid="7408396418008948957">"अनुप्रयोगले विभाजित-स्क्रिनलाई समर्थन गर्दैन।"</string>
- <!-- no translation found for dock_multi_instances_not_supported_text (5242868470666346929) -->
- <skip />
+ <string name="dock_multi_instances_not_supported_text" msgid="5242868470666346929">"यो एप एउटा विन्डोमा मात्र खोल्न मिल्छ।"</string>
<string name="forced_resizable_secondary_display" msgid="1768046938673582671">"यो एपले सहायक प्रदर्शनमा काम नगर्नसक्छ।"</string>
<string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"अनुप्रयोगले सहायक प्रदर्शनहरूमा लञ्च सुविधालाई समर्थन गर्दैन।"</string>
<string name="accessibility_divider" msgid="703810061635792791">"विभाजित-स्क्रिन छुट्याउने"</string>
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java
index c743582..09f5cf1 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java
@@ -61,6 +61,7 @@
import com.android.wm.shell.desktopmode.DesktopModeController;
import com.android.wm.shell.desktopmode.DesktopModeStatus;
import com.android.wm.shell.desktopmode.DesktopModeTaskRepository;
+import com.android.wm.shell.desktopmode.DesktopTasksController;
import com.android.wm.shell.displayareahelper.DisplayAreaHelper;
import com.android.wm.shell.displayareahelper.DisplayAreaHelperController;
import com.android.wm.shell.draganddrop.DragAndDropController;
@@ -677,7 +678,11 @@
@WMSingleton
@Provides
static Optional<DesktopMode> provideDesktopMode(
- Optional<DesktopModeController> desktopModeController) {
+ Optional<DesktopModeController> desktopModeController,
+ Optional<DesktopTasksController> desktopTasksController) {
+ if (DesktopModeStatus.isProto2Enabled()) {
+ return desktopTasksController.map(DesktopTasksController::asDesktopMode);
+ }
return desktopModeController.map(DesktopModeController::asDesktopMode);
}
@@ -700,6 +705,23 @@
@BindsOptionalOf
@DynamicOverride
+ abstract DesktopTasksController optionalDesktopTasksController();
+
+ @WMSingleton
+ @Provides
+ static Optional<DesktopTasksController> providesDesktopTasksController(
+ @DynamicOverride Optional<Lazy<DesktopTasksController>> desktopTasksController) {
+ // Use optional-of-lazy for the dependency that this provider relies on.
+ // Lazy ensures that this provider will not be the cause the dependency is created
+ // when it will not be returned due to the condition below.
+ if (DesktopModeStatus.isProto2Enabled()) {
+ return desktopTasksController.map(Lazy::get);
+ }
+ return Optional.empty();
+ }
+
+ @BindsOptionalOf
+ @DynamicOverride
abstract DesktopModeTaskRepository optionalDesktopModeTaskRepository();
@WMSingleton
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java
index 6be8305..701a3a4 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java
@@ -50,6 +50,7 @@
import com.android.wm.shell.common.annotations.ShellMainThread;
import com.android.wm.shell.desktopmode.DesktopModeController;
import com.android.wm.shell.desktopmode.DesktopModeTaskRepository;
+import com.android.wm.shell.desktopmode.DesktopTasksController;
import com.android.wm.shell.draganddrop.DragAndDropController;
import com.android.wm.shell.freeform.FreeformComponents;
import com.android.wm.shell.freeform.FreeformTaskListener;
@@ -189,7 +190,8 @@
ShellTaskOrganizer taskOrganizer,
DisplayController displayController,
SyncTransactionQueue syncQueue,
- Optional<DesktopModeController> desktopModeController) {
+ Optional<DesktopModeController> desktopModeController,
+ Optional<DesktopTasksController> desktopTasksController) {
return new CaptionWindowDecorViewModel(
context,
mainHandler,
@@ -197,7 +199,8 @@
taskOrganizer,
displayController,
syncQueue,
- desktopModeController);
+ desktopModeController,
+ desktopTasksController);
}
//
@@ -616,6 +619,22 @@
@WMSingleton
@Provides
@DynamicOverride
+ static DesktopTasksController provideDesktopTasksController(
+ Context context,
+ ShellInit shellInit,
+ ShellController shellController,
+ ShellTaskOrganizer shellTaskOrganizer,
+ Transitions transitions,
+ @DynamicOverride DesktopModeTaskRepository desktopModeTaskRepository,
+ @ShellMainThread ShellExecutor mainExecutor
+ ) {
+ return new DesktopTasksController(context, shellInit, shellController, shellTaskOrganizer,
+ transitions, desktopModeTaskRepository, mainExecutor);
+ }
+
+ @WMSingleton
+ @Provides
+ @DynamicOverride
static DesktopModeTaskRepository provideDesktopModeTaskRepository() {
return new DesktopModeTaskRepository();
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeStatus.java b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeStatus.java
index 67f4a19..055949f 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeStatus.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeStatus.java
@@ -70,9 +70,13 @@
* @return {@code true} if active
*/
public static boolean isActive(Context context) {
- if (!IS_SUPPORTED) {
+ if (!isAnyEnabled()) {
return false;
}
+ if (isProto2Enabled()) {
+ // Desktop mode is always active in prototype 2
+ return true;
+ }
try {
int result = Settings.System.getIntForUser(context.getContentResolver(),
Settings.System.DESKTOP_MODE, UserHandle.USER_CURRENT);
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
new file mode 100644
index 0000000..b075b14
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt
@@ -0,0 +1,231 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.desktopmode
+
+import android.app.ActivityManager
+import android.app.WindowConfiguration
+import android.app.WindowConfiguration.ACTIVITY_TYPE_HOME
+import android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED
+import android.app.WindowConfiguration.WindowingMode
+import android.content.Context
+import android.view.WindowManager
+import android.window.WindowContainerTransaction
+import androidx.annotation.BinderThread
+import com.android.internal.protolog.common.ProtoLog
+import com.android.wm.shell.ShellTaskOrganizer
+import com.android.wm.shell.common.ExecutorUtils
+import com.android.wm.shell.common.ExternalInterfaceBinder
+import com.android.wm.shell.common.RemoteCallable
+import com.android.wm.shell.common.ShellExecutor
+import com.android.wm.shell.common.annotations.ExternalThread
+import com.android.wm.shell.common.annotations.ShellMainThread
+import com.android.wm.shell.desktopmode.DesktopModeTaskRepository.VisibleTasksListener
+import com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_DESKTOP_MODE
+import com.android.wm.shell.sysui.ShellController
+import com.android.wm.shell.sysui.ShellInit
+import com.android.wm.shell.sysui.ShellSharedConstants
+import com.android.wm.shell.transition.Transitions
+import java.util.concurrent.Executor
+import java.util.function.Consumer
+
+/** Handles moving tasks in and out of desktop */
+class DesktopTasksController(
+ private val context: Context,
+ shellInit: ShellInit,
+ private val shellController: ShellController,
+ private val shellTaskOrganizer: ShellTaskOrganizer,
+ private val transitions: Transitions,
+ private val desktopModeTaskRepository: DesktopModeTaskRepository,
+ @ShellMainThread private val mainExecutor: ShellExecutor
+) : RemoteCallable<DesktopTasksController> {
+
+ private val desktopMode: DesktopModeImpl
+
+ init {
+ desktopMode = DesktopModeImpl()
+ if (DesktopModeStatus.isProto2Enabled()) {
+ shellInit.addInitCallback({ onInit() }, this)
+ }
+ }
+
+ private fun onInit() {
+ ProtoLog.d(WM_SHELL_DESKTOP_MODE, "Initialize DesktopTasksController")
+ shellController.addExternalInterface(
+ ShellSharedConstants.KEY_EXTRA_SHELL_DESKTOP_MODE,
+ { createExternalInterface() },
+ this
+ )
+ }
+
+ /** Show all tasks, that are part of the desktop, on top of launcher */
+ fun showDesktopApps() {
+ ProtoLog.v(WM_SHELL_DESKTOP_MODE, "showDesktopApps")
+ val wct = WindowContainerTransaction()
+
+ bringDesktopAppsToFront(wct)
+
+ // Execute transaction if there are pending operations
+ if (!wct.isEmpty) {
+ if (Transitions.ENABLE_SHELL_TRANSITIONS) {
+ transitions.startTransition(WindowManager.TRANSIT_TO_FRONT, wct, null /* handler */)
+ } else {
+ shellTaskOrganizer.applyTransaction(wct)
+ }
+ }
+ }
+
+ /** Move a task with given `taskId` to desktop */
+ fun moveToDesktop(taskId: Int) {
+ shellTaskOrganizer.getRunningTaskInfo(taskId)?.let { task -> moveToDesktop(task) }
+ }
+
+ /** Move a task to desktop */
+ fun moveToDesktop(task: ActivityManager.RunningTaskInfo) {
+ ProtoLog.v(WM_SHELL_DESKTOP_MODE, "moveToDesktop: %d", task.taskId)
+
+ val wct = WindowContainerTransaction()
+ // Bring other apps to front first
+ bringDesktopAppsToFront(wct)
+
+ wct.setWindowingMode(task.getToken(), WindowConfiguration.WINDOWING_MODE_FREEFORM)
+ wct.reorder(task.getToken(), true /* onTop */)
+
+ if (Transitions.ENABLE_SHELL_TRANSITIONS) {
+ transitions.startTransition(WindowManager.TRANSIT_CHANGE, wct, null /* handler */)
+ } else {
+ shellTaskOrganizer.applyTransaction(wct)
+ }
+ }
+
+ /** Move a task with given `taskId` to fullscreen */
+ fun moveToFullscreen(taskId: Int) {
+ shellTaskOrganizer.getRunningTaskInfo(taskId)?.let { task -> moveToFullscreen(task) }
+ }
+
+ /** Move a task to fullscreen */
+ fun moveToFullscreen(task: ActivityManager.RunningTaskInfo) {
+ ProtoLog.v(WM_SHELL_DESKTOP_MODE, "moveToFullscreen: %d", task.taskId)
+
+ val wct = WindowContainerTransaction()
+ wct.setWindowingMode(task.getToken(), WindowConfiguration.WINDOWING_MODE_FULLSCREEN)
+ wct.setBounds(task.getToken(), null)
+ if (Transitions.ENABLE_SHELL_TRANSITIONS) {
+ transitions.startTransition(WindowManager.TRANSIT_CHANGE, wct, null /* handler */)
+ } else {
+ shellTaskOrganizer.applyTransaction(wct)
+ }
+ }
+
+ /**
+ * Get windowing move for a given `taskId`
+ *
+ * @return [WindowingMode] for the task or [WINDOWING_MODE_UNDEFINED] if task is not found
+ */
+ @WindowingMode
+ fun getTaskWindowingMode(taskId: Int): Int {
+ return shellTaskOrganizer.getRunningTaskInfo(taskId)?.windowingMode
+ ?: WINDOWING_MODE_UNDEFINED
+ }
+
+ private fun bringDesktopAppsToFront(wct: WindowContainerTransaction) {
+ val activeTasks = desktopModeTaskRepository.getActiveTasks()
+
+ // Skip if all tasks are already visible
+ if (activeTasks.isNotEmpty() && activeTasks.all(desktopModeTaskRepository::isVisibleTask)) {
+ ProtoLog.d(
+ WM_SHELL_DESKTOP_MODE,
+ "bringDesktopAppsToFront: active tasks are already in front, skipping."
+ )
+ return
+ }
+
+ ProtoLog.v(WM_SHELL_DESKTOP_MODE, "bringDesktopAppsToFront")
+
+ // First move home to front and then other tasks on top of it
+ moveHomeTaskToFront(wct)
+
+ val allTasksInZOrder = desktopModeTaskRepository.getFreeformTasksInZOrder()
+ activeTasks
+ // Sort descending as the top task is at index 0. It should be ordered to top last
+ .sortedByDescending { taskId -> allTasksInZOrder.indexOf(taskId) }
+ .mapNotNull { taskId -> shellTaskOrganizer.getRunningTaskInfo(taskId) }
+ .forEach { task -> wct.reorder(task.token, true /* onTop */) }
+ }
+
+ private fun moveHomeTaskToFront(wct: WindowContainerTransaction) {
+ shellTaskOrganizer
+ .getRunningTasks(context.displayId)
+ .firstOrNull { task -> task.activityType == ACTIVITY_TYPE_HOME }
+ ?.let { homeTask -> wct.reorder(homeTask.getToken(), true /* onTop */) }
+ }
+
+ override fun getContext(): Context {
+ return context
+ }
+
+ override fun getRemoteCallExecutor(): ShellExecutor {
+ return mainExecutor
+ }
+
+ /** Creates a new instance of the external interface to pass to another process. */
+ private fun createExternalInterface(): ExternalInterfaceBinder {
+ return IDesktopModeImpl(this)
+ }
+
+ /** Get connection interface between sysui and shell */
+ fun asDesktopMode(): DesktopMode {
+ return desktopMode
+ }
+
+ /**
+ * Adds a listener to find out about changes in the visibility of freeform tasks.
+ *
+ * @param listener the listener to add.
+ * @param callbackExecutor the executor to call the listener on.
+ */
+ fun addListener(listener: VisibleTasksListener, callbackExecutor: Executor) {
+ desktopModeTaskRepository.addVisibleTasksListener(listener, callbackExecutor)
+ }
+
+ /** The interface for calls from outside the shell, within the host process. */
+ @ExternalThread
+ private inner class DesktopModeImpl : DesktopMode {
+ override fun addListener(listener: VisibleTasksListener, callbackExecutor: Executor) {
+ mainExecutor.execute {
+ this@DesktopTasksController.addListener(listener, callbackExecutor)
+ }
+ }
+ }
+
+ /** The interface for calls from outside the host process. */
+ @BinderThread
+ private class IDesktopModeImpl(private var controller: DesktopTasksController?) :
+ IDesktopMode.Stub(), ExternalInterfaceBinder {
+ /** Invalidates this instance, preventing future calls from updating the controller. */
+ override fun invalidate() {
+ controller = null
+ }
+
+ override fun showDesktopApps() {
+ ExecutorUtils.executeRemoteCallWithTaskPermission(
+ controller,
+ "showDesktopApps",
+ Consumer(DesktopTasksController::showDesktopApps)
+ )
+ }
+ }
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java
index 5be29db..717ae91 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java
@@ -1613,7 +1613,7 @@
mSplitLayout.flingDividerToDismiss(
mSideStagePosition != SPLIT_POSITION_BOTTOM_OR_RIGHT,
EXIT_REASON_APP_FINISHED);
- } else if (!isSplitScreenVisible()) {
+ } else if (!isSplitScreenVisible() && !mIsSplitEntering) {
exitSplitScreen(null /* childrenToTop */, EXIT_REASON_APP_FINISHED);
}
} else if (isSideStage && hasChildren && !mMainStage.isActive()) {
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 afefd5d..299284f 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
@@ -46,17 +46,18 @@
import androidx.annotation.Nullable;
+import com.android.internal.annotations.VisibleForTesting;
import com.android.wm.shell.R;
import com.android.wm.shell.ShellTaskOrganizer;
import com.android.wm.shell.common.DisplayController;
import com.android.wm.shell.common.SyncTransactionQueue;
import com.android.wm.shell.desktopmode.DesktopModeController;
import com.android.wm.shell.desktopmode.DesktopModeStatus;
+import com.android.wm.shell.desktopmode.DesktopTasksController;
import com.android.wm.shell.freeform.FreeformTaskTransitionStarter;
import com.android.wm.shell.transition.Transitions;
import java.util.Optional;
-import java.util.function.Supplier;
/**
* View model for the window decoration with a caption and shadows. Works with
@@ -66,7 +67,6 @@
public class CaptionWindowDecorViewModel implements WindowDecorViewModel {
private static final String TAG = "CaptionViewModel";
private final CaptionWindowDecoration.Factory mCaptionWindowDecorFactory;
- private final Supplier<InputManager> mInputManagerSupplier;
private final ActivityTaskManager mActivityTaskManager;
private final ShellTaskOrganizer mTaskOrganizer;
private final Context mContext;
@@ -76,33 +76,14 @@
private final SyncTransactionQueue mSyncQueue;
private FreeformTaskTransitionStarter mTransitionStarter;
private Optional<DesktopModeController> mDesktopModeController;
+ private Optional<DesktopTasksController> mDesktopTasksController;
private boolean mTransitionDragActive;
private SparseArray<EventReceiver> mEventReceiversByDisplay = new SparseArray<>();
private final SparseArray<CaptionWindowDecoration> mWindowDecorByTaskId = new SparseArray<>();
private final DragStartListenerImpl mDragStartListener = new DragStartListenerImpl();
- private EventReceiverFactory mEventReceiverFactory = new EventReceiverFactory();
-
- public CaptionWindowDecorViewModel(
- Context context,
- Handler mainHandler,
- Choreographer mainChoreographer,
- ShellTaskOrganizer taskOrganizer,
- DisplayController displayController,
- SyncTransactionQueue syncQueue,
- Optional<DesktopModeController> desktopModeController) {
- this(
- context,
- mainHandler,
- mainChoreographer,
- taskOrganizer,
- displayController,
- syncQueue,
- desktopModeController,
- new CaptionWindowDecoration.Factory(),
- InputManager::getInstance);
- }
+ private InputMonitorFactory mInputMonitorFactory;
public CaptionWindowDecorViewModel(
Context context,
@@ -112,9 +93,32 @@
DisplayController displayController,
SyncTransactionQueue syncQueue,
Optional<DesktopModeController> desktopModeController,
- CaptionWindowDecoration.Factory captionWindowDecorFactory,
- Supplier<InputManager> inputManagerSupplier) {
+ Optional<DesktopTasksController> desktopTasksController) {
+ this(
+ context,
+ mainHandler,
+ mainChoreographer,
+ taskOrganizer,
+ displayController,
+ syncQueue,
+ desktopModeController,
+ desktopTasksController,
+ new CaptionWindowDecoration.Factory(),
+ new InputMonitorFactory());
+ }
+ @VisibleForTesting
+ CaptionWindowDecorViewModel(
+ Context context,
+ Handler mainHandler,
+ Choreographer mainChoreographer,
+ ShellTaskOrganizer taskOrganizer,
+ DisplayController displayController,
+ SyncTransactionQueue syncQueue,
+ Optional<DesktopModeController> desktopModeController,
+ Optional<DesktopTasksController> desktopTasksController,
+ CaptionWindowDecoration.Factory captionWindowDecorFactory,
+ InputMonitorFactory inputMonitorFactory) {
mContext = context;
mMainHandler = mainHandler;
mMainChoreographer = mainChoreographer;
@@ -123,13 +127,10 @@
mDisplayController = displayController;
mSyncQueue = syncQueue;
mDesktopModeController = desktopModeController;
+ mDesktopTasksController = desktopTasksController;
mCaptionWindowDecorFactory = captionWindowDecorFactory;
- mInputManagerSupplier = inputManagerSupplier;
- }
-
- void setEventReceiverFactory(EventReceiverFactory eventReceiverFactory) {
- mEventReceiverFactory = eventReceiverFactory;
+ mInputMonitorFactory = inputMonitorFactory;
}
@Override
@@ -205,7 +206,6 @@
decoration.close();
int displayId = taskInfo.displayId;
if (mEventReceiversByDisplay.contains(displayId)) {
- EventReceiver eventReceiver = mEventReceiversByDisplay.get(displayId);
removeTaskFromEventReceiver(displayId);
}
}
@@ -248,11 +248,13 @@
decoration.createHandleMenu();
} else if (id == R.id.desktop_button) {
mDesktopModeController.ifPresent(c -> c.setDesktopModeActive(true));
+ mDesktopTasksController.ifPresent(c -> c.moveToDesktop(mTaskId));
decoration.closeHandleMenu();
} else if (id == R.id.fullscreen_button) {
mDesktopModeController.ifPresent(c -> c.setDesktopModeActive(false));
+ mDesktopTasksController.ifPresent(c -> c.moveToFullscreen(mTaskId));
decoration.closeHandleMenu();
- decoration.setButtonVisibility();
+ decoration.setButtonVisibility(false);
}
}
@@ -305,8 +307,13 @@
*/
private void handleEventForMove(MotionEvent e) {
RunningTaskInfo taskInfo = mTaskOrganizer.getRunningTaskInfo(mTaskId);
- if (mDesktopModeController.isPresent()
- && mDesktopModeController.get().getDisplayAreaWindowingMode(taskInfo.displayId)
+ if (DesktopModeStatus.isProto2Enabled()
+ && taskInfo.getWindowingMode() == WINDOWING_MODE_FULLSCREEN) {
+ return;
+ }
+ if (DesktopModeStatus.isProto1Enabled() && mDesktopModeController.isPresent()
+ && mDesktopModeController.get().getDisplayAreaWindowingMode(
+ taskInfo.displayId)
== WINDOWING_MODE_FULLSCREEN) {
return;
}
@@ -330,9 +337,20 @@
.stableInsets().top;
mDragResizeCallback.onDragResizeEnd(
e.getRawX(dragPointerIdx), e.getRawY(dragPointerIdx));
- if (e.getRawY(dragPointerIdx) <= statusBarHeight
- && DesktopModeStatus.isActive(mContext)) {
- mDesktopModeController.ifPresent(c -> c.setDesktopModeActive(false));
+ if (e.getRawY(dragPointerIdx) <= statusBarHeight) {
+ if (DesktopModeStatus.isProto2Enabled()) {
+ if (taskInfo.getWindowingMode() == WINDOWING_MODE_FREEFORM) {
+ // Switch a single task to fullscreen
+ mDesktopTasksController.ifPresent(
+ c -> c.moveToFullscreen(taskInfo));
+ }
+ } else if (DesktopModeStatus.isProto1Enabled()) {
+ if (DesktopModeStatus.isActive(mContext)) {
+ // Turn off desktop mode
+ mDesktopModeController.ifPresent(
+ c -> c.setDesktopModeActive(false));
+ }
+ }
}
break;
}
@@ -408,25 +426,33 @@
}
}
- class EventReceiverFactory {
- EventReceiver create(InputMonitor inputMonitor, InputChannel channel, Looper looper) {
- return new EventReceiver(inputMonitor, channel, looper);
- }
- }
-
/**
* Handle MotionEvents relevant to focused task's caption that don't directly touch it
*
* @param ev the {@link MotionEvent} received by {@link EventReceiver}
*/
private void handleReceivedMotionEvent(MotionEvent ev, InputMonitor inputMonitor) {
- if (!DesktopModeStatus.isActive(mContext)) {
- handleCaptionThroughStatusBar(ev);
+ if (DesktopModeStatus.isProto2Enabled()) {
+ CaptionWindowDecoration focusedDecor = getFocusedDecor();
+ if (focusedDecor == null
+ || focusedDecor.mTaskInfo.getWindowingMode() != WINDOWING_MODE_FREEFORM) {
+ handleCaptionThroughStatusBar(ev);
+ }
+ } else if (DesktopModeStatus.isProto1Enabled()) {
+ if (!DesktopModeStatus.isActive(mContext)) {
+ handleCaptionThroughStatusBar(ev);
+ }
}
handleEventOutsideFocusedCaption(ev);
// Prevent status bar from reacting to a caption drag.
- if (mTransitionDragActive && !DesktopModeStatus.isActive(mContext)) {
- inputMonitor.pilferPointers();
+ if (DesktopModeStatus.isProto2Enabled()) {
+ if (mTransitionDragActive) {
+ inputMonitor.pilferPointers();
+ }
+ } else if (DesktopModeStatus.isProto1Enabled()) {
+ if (mTransitionDragActive && !DesktopModeStatus.isActive(mContext)) {
+ inputMonitor.pilferPointers();
+ }
}
}
@@ -455,9 +481,20 @@
case MotionEvent.ACTION_DOWN: {
// Begin drag through status bar if applicable.
CaptionWindowDecoration focusedDecor = getFocusedDecor();
- if (focusedDecor != null && !DesktopModeStatus.isActive(mContext)
- && focusedDecor.checkTouchEventInHandle(ev)) {
- mTransitionDragActive = true;
+ if (focusedDecor != null) {
+ boolean dragFromStatusBarAllowed = false;
+ if (DesktopModeStatus.isProto2Enabled()) {
+ // In proto2 any full screen task can be dragged to freeform
+ dragFromStatusBarAllowed = focusedDecor.mTaskInfo.getWindowingMode()
+ == WINDOWING_MODE_FULLSCREEN;
+ } else if (DesktopModeStatus.isProto1Enabled()) {
+ // In proto1 task can be dragged to freeform when not in desktop mode
+ dragFromStatusBarAllowed = !DesktopModeStatus.isActive(mContext);
+ }
+
+ if (dragFromStatusBarAllowed && focusedDecor.checkTouchEventInHandle(ev)) {
+ mTransitionDragActive = true;
+ }
}
break;
}
@@ -472,7 +509,13 @@
int statusBarHeight = mDisplayController
.getDisplayLayout(focusedDecor.mTaskInfo.displayId).stableInsets().top;
if (ev.getY() > statusBarHeight) {
- mDesktopModeController.ifPresent(c -> c.setDesktopModeActive(true));
+ if (DesktopModeStatus.isProto2Enabled()) {
+ mDesktopTasksController.ifPresent(
+ c -> c.moveToDesktop(focusedDecor.mTaskInfo));
+ } else if (DesktopModeStatus.isProto1Enabled()) {
+ mDesktopModeController.ifPresent(c -> c.setDesktopModeActive(true));
+ }
+
return;
}
}
@@ -500,11 +543,11 @@
}
private void createInputChannel(int displayId) {
- InputManager inputManager = mInputManagerSupplier.get();
+ InputManager inputManager = InputManager.getInstance();
InputMonitor inputMonitor =
- inputManager.monitorGestureInput("caption-touch", mContext.getDisplayId());
- EventReceiver eventReceiver = mEventReceiverFactory.create(
- inputMonitor, inputMonitor.getInputChannel(), Looper.myLooper());
+ mInputMonitorFactory.create(inputManager, mContext);
+ EventReceiver eventReceiver = new EventReceiver(inputMonitor,
+ inputMonitor.getInputChannel(), Looper.myLooper());
mEventReceiversByDisplay.put(displayId, eventReceiver);
}
@@ -562,4 +605,12 @@
mWindowDecorByTaskId.get(taskId).closeHandleMenu();
}
}
+
+ static class InputMonitorFactory {
+ InputMonitor create(InputManager inputManager, Context context) {
+ return inputManager.monitorGestureInput("caption-touch", context.getDisplayId());
+ }
+ }
}
+
+
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecoration.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecoration.java
index 037ca20..f7c7a87 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecoration.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecoration.java
@@ -16,8 +16,9 @@
package com.android.wm.shell.windowdecor;
+import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
+
import android.app.ActivityManager;
-import android.app.WindowConfiguration;
import android.content.Context;
import android.content.res.ColorStateList;
import android.content.res.Resources;
@@ -117,7 +118,7 @@
? R.dimen.freeform_decor_shadow_focused_thickness
: R.dimen.freeform_decor_shadow_unfocused_thickness;
final boolean isFreeform =
- taskInfo.getWindowingMode() == WindowConfiguration.WINDOWING_MODE_FREEFORM;
+ taskInfo.getWindowingMode() == WINDOWING_MODE_FREEFORM;
final boolean isDragResizeable = isFreeform && taskInfo.isResizeable;
WindowDecorLinearLayout oldRootView = mResult.mRootView;
@@ -167,11 +168,17 @@
// If this task is not focused, do not show caption.
setCaptionVisibility(mTaskInfo.isFocused);
- // Only handle should show if Desktop Mode is inactive.
- boolean desktopCurrentStatus = DesktopModeStatus.isActive(mContext);
- if (mDesktopActive != desktopCurrentStatus && mTaskInfo.isFocused) {
- mDesktopActive = desktopCurrentStatus;
- setButtonVisibility();
+ if (mTaskInfo.isFocused) {
+ if (DesktopModeStatus.isProto2Enabled()) {
+ updateButtonVisibility();
+ } else if (DesktopModeStatus.isProto1Enabled()) {
+ // Only handle should show if Desktop Mode is inactive.
+ boolean desktopCurrentStatus = DesktopModeStatus.isActive(mContext);
+ if (mDesktopActive != desktopCurrentStatus) {
+ mDesktopActive = desktopCurrentStatus;
+ setButtonVisibility(mDesktopActive);
+ }
+ }
}
if (!isDragResizeable) {
@@ -214,7 +221,7 @@
View handle = caption.findViewById(R.id.caption_handle);
handle.setOnTouchListener(mOnCaptionTouchListener);
handle.setOnClickListener(mOnCaptionButtonClickListener);
- setButtonVisibility();
+ updateButtonVisibility();
}
private void setupHandleMenu() {
@@ -244,14 +251,25 @@
/**
* Sets the visibility of buttons and color of caption based on desktop mode status
*/
- void setButtonVisibility() {
- mDesktopActive = DesktopModeStatus.isActive(mContext);
- int v = mDesktopActive ? View.VISIBLE : View.GONE;
+ void updateButtonVisibility() {
+ if (DesktopModeStatus.isProto2Enabled()) {
+ setButtonVisibility(mTaskInfo.getWindowingMode() == WINDOWING_MODE_FREEFORM);
+ } else if (DesktopModeStatus.isProto1Enabled()) {
+ mDesktopActive = DesktopModeStatus.isActive(mContext);
+ setButtonVisibility(mDesktopActive);
+ }
+ }
+
+ /**
+ * Show or hide buttons
+ */
+ void setButtonVisibility(boolean visible) {
+ int visibility = visible ? View.VISIBLE : View.GONE;
View caption = mResult.mRootView.findViewById(R.id.caption);
View back = caption.findViewById(R.id.back_button);
View close = caption.findViewById(R.id.close_window);
- back.setVisibility(v);
- close.setVisibility(v);
+ back.setVisibility(visibility);
+ close.setVisibility(visibility);
int buttonTintColorRes =
mDesktopActive ? R.color.decor_button_dark_color
: R.color.decor_button_light_color;
@@ -260,7 +278,7 @@
View handle = caption.findViewById(R.id.caption_handle);
VectorDrawable handleBackground = (VectorDrawable) handle.getBackground();
handleBackground.setTintList(buttonTintColor);
- caption.getBackground().setTint(v == View.VISIBLE ? Color.WHITE : Color.TRANSPARENT);
+ caption.getBackground().setTint(visible ? Color.WHITE : Color.TRANSPARENT);
}
boolean isHandleMenuActive() {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecoration.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecoration.java
index 9215496..7f85988 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecoration.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecoration.java
@@ -33,6 +33,7 @@
import android.view.ViewRootImpl;
import android.view.WindowManager;
import android.view.WindowlessWindowManager;
+import android.window.TaskConstants;
import android.window.WindowContainerTransaction;
import com.android.wm.shell.ShellTaskOrganizer;
@@ -195,7 +196,9 @@
.setParent(mTaskSurface)
.build();
- startT.setTrustedOverlay(mDecorationContainerSurface, true);
+ startT.setTrustedOverlay(mDecorationContainerSurface, true)
+ .setLayer(mDecorationContainerSurface,
+ TaskConstants.TASK_CHILD_LAYER_WINDOW_DECORATIONS);
}
final Rect taskBounds = taskConfig.windowConfiguration.getBounds();
@@ -213,8 +216,6 @@
outResult.mDecorContainerOffsetX, outResult.mDecorContainerOffsetY)
.setWindowCrop(mDecorationContainerSurface,
outResult.mWidth, outResult.mHeight)
- // TODO(b/244455401): Change the z-order when it's better organized
- .setLayer(mDecorationContainerSurface, mTaskInfo.numActivities + 1)
.show(mDecorationContainerSurface);
// TaskBackgroundSurface
@@ -225,6 +226,8 @@
.setEffectLayer()
.setParent(mTaskSurface)
.build();
+
+ startT.setLayer(mTaskBackgroundSurface, TaskConstants.TASK_CHILD_LAYER_TASK_BACKGROUND);
}
float shadowRadius = loadDimension(resources, params.mShadowRadiusId);
@@ -236,8 +239,6 @@
taskBounds.height())
.setShadowRadius(mTaskBackgroundSurface, shadowRadius)
.setColor(mTaskBackgroundSurface, mTmpColor)
- // TODO(b/244455401): Change the z-order when it's better organized
- .setLayer(mTaskBackgroundSurface, -1)
.show(mTaskBackgroundSurface);
// CaptionContainerSurface, CaptionWindowManager
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeControllerTest.java
index 707c049..08af3d3 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeControllerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeControllerTest.java
@@ -16,8 +16,6 @@
package com.android.wm.shell.desktopmode;
-import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME;
-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.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
@@ -29,6 +27,9 @@
import static android.window.WindowContainerTransaction.HierarchyOp.HIERARCHY_OP_TYPE_REORDER;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession;
+import static com.android.wm.shell.desktopmode.DesktopTestHelpers.createFreeformTask;
+import static com.android.wm.shell.desktopmode.DesktopTestHelpers.createFullscreenTask;
+import static com.android.wm.shell.desktopmode.DesktopTestHelpers.createHomeTask;
import static com.google.common.truth.Truth.assertThat;
@@ -48,7 +49,6 @@
import android.testing.AndroidTestingRunner;
import android.window.DisplayAreaInfo;
import android.window.TransitionRequestInfo;
-import android.window.WindowContainerToken;
import android.window.WindowContainerTransaction;
import android.window.WindowContainerTransaction.Change;
import android.window.WindowContainerTransaction.HierarchyOp;
@@ -59,7 +59,6 @@
import com.android.wm.shell.RootTaskDisplayAreaOrganizer;
import com.android.wm.shell.ShellTaskOrganizer;
import com.android.wm.shell.ShellTestCase;
-import com.android.wm.shell.TestRunningTaskInfoBuilder;
import com.android.wm.shell.TestShellExecutor;
import com.android.wm.shell.common.ShellExecutor;
import com.android.wm.shell.sysui.ShellController;
@@ -355,7 +354,7 @@
@Test
public void testHandleTransitionRequest_taskOpen_returnsWct() {
RunningTaskInfo trigger = new RunningTaskInfo();
- trigger.token = new MockToken().mToken;
+ trigger.token = new MockToken().token();
trigger.configuration.windowConfiguration.setWindowingMode(WINDOWING_MODE_FREEFORM);
WindowContainerTransaction wct = mController.handleRequest(
mock(IBinder.class),
@@ -366,7 +365,7 @@
@Test
public void testHandleTransitionRequest_taskToFront_returnsWct() {
RunningTaskInfo trigger = new RunningTaskInfo();
- trigger.token = new MockToken().mToken;
+ trigger.token = new MockToken().token();
trigger.configuration.windowConfiguration.setWindowingMode(WINDOWING_MODE_FREEFORM);
WindowContainerTransaction wct = mController.handleRequest(
mock(IBinder.class),
@@ -381,40 +380,13 @@
}
private DisplayAreaInfo createMockDisplayArea() {
- DisplayAreaInfo displayAreaInfo = new DisplayAreaInfo(new MockToken().mToken,
+ DisplayAreaInfo displayAreaInfo = new DisplayAreaInfo(new MockToken().token(),
mContext.getDisplayId(), 0);
when(mRootTaskDisplayAreaOrganizer.getDisplayAreaInfo(mContext.getDisplayId()))
.thenReturn(displayAreaInfo);
return displayAreaInfo;
}
- private RunningTaskInfo createFreeformTask() {
- return new TestRunningTaskInfoBuilder()
- .setToken(new MockToken().token())
- .setActivityType(ACTIVITY_TYPE_STANDARD)
- .setWindowingMode(WINDOWING_MODE_FREEFORM)
- .setLastActiveTime(100)
- .build();
- }
-
- private RunningTaskInfo createFullscreenTask() {
- return new TestRunningTaskInfoBuilder()
- .setToken(new MockToken().token())
- .setActivityType(ACTIVITY_TYPE_STANDARD)
- .setWindowingMode(WINDOWING_MODE_FULLSCREEN)
- .setLastActiveTime(100)
- .build();
- }
-
- private RunningTaskInfo createHomeTask() {
- return new TestRunningTaskInfoBuilder()
- .setToken(new MockToken().token())
- .setActivityType(ACTIVITY_TYPE_HOME)
- .setWindowingMode(WINDOWING_MODE_FULLSCREEN)
- .setLastActiveTime(100)
- .build();
- }
-
private WindowContainerTransaction getDesktopModeSwitchTransaction() {
ArgumentCaptor<WindowContainerTransaction> arg = ArgumentCaptor.forClass(
WindowContainerTransaction.class);
@@ -442,18 +414,4 @@
assertThat(change.getConfiguration().windowConfiguration.getBounds().isEmpty()).isTrue();
}
- private static class MockToken {
- private final WindowContainerToken mToken;
- private final IBinder mBinder;
-
- MockToken() {
- mToken = mock(WindowContainerToken.class);
- mBinder = mock(IBinder.class);
- when(mToken.asBinder()).thenReturn(mBinder);
- }
-
- WindowContainerToken token() {
- return mToken;
- }
- }
}
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
new file mode 100644
index 0000000..de2473b
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt
@@ -0,0 +1,281 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.desktopmode
+
+import android.app.ActivityManager.RunningTaskInfo
+import android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM
+import android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN
+import android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED
+import android.testing.AndroidTestingRunner
+import android.window.WindowContainerTransaction
+import android.window.WindowContainerTransaction.HierarchyOp.HIERARCHY_OP_TYPE_REORDER
+import androidx.test.filters.SmallTest
+import com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession
+import com.android.dx.mockito.inline.extended.ExtendedMockito.never
+import com.android.dx.mockito.inline.extended.StaticMockitoSession
+import com.android.wm.shell.ShellTaskOrganizer
+import com.android.wm.shell.ShellTestCase
+import com.android.wm.shell.TestShellExecutor
+import com.android.wm.shell.common.ShellExecutor
+import com.android.wm.shell.desktopmode.DesktopTestHelpers.Companion.createFreeformTask
+import com.android.wm.shell.desktopmode.DesktopTestHelpers.Companion.createFullscreenTask
+import com.android.wm.shell.desktopmode.DesktopTestHelpers.Companion.createHomeTask
+import com.android.wm.shell.sysui.ShellController
+import com.android.wm.shell.sysui.ShellInit
+import com.android.wm.shell.transition.Transitions
+import com.google.common.truth.Truth.assertThat
+import com.google.common.truth.Truth.assertWithMessage
+import org.junit.After
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.ArgumentCaptor
+import org.mockito.ArgumentMatchers.isNull
+import org.mockito.Mock
+import org.mockito.Mockito
+import org.mockito.Mockito.any
+import org.mockito.Mockito.anyInt
+import org.mockito.Mockito.clearInvocations
+import org.mockito.Mockito.verify
+import org.mockito.Mockito.`when` as whenever
+
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+class DesktopTasksControllerTest : ShellTestCase() {
+
+ @Mock lateinit var testExecutor: ShellExecutor
+ @Mock lateinit var shellController: ShellController
+ @Mock lateinit var shellTaskOrganizer: ShellTaskOrganizer
+ @Mock lateinit var transitions: Transitions
+
+ lateinit var mockitoSession: StaticMockitoSession
+ lateinit var controller: DesktopTasksController
+ lateinit var shellInit: ShellInit
+ lateinit var desktopModeTaskRepository: DesktopModeTaskRepository
+
+ // Mock running tasks are registered here so we can get the list from mock shell task organizer
+ private val runningTasks = mutableListOf<RunningTaskInfo>()
+
+ @Before
+ fun setUp() {
+ mockitoSession = mockitoSession().mockStatic(DesktopModeStatus::class.java).startMocking()
+ whenever(DesktopModeStatus.isProto2Enabled()).thenReturn(true)
+
+ shellInit = Mockito.spy(ShellInit(testExecutor))
+ desktopModeTaskRepository = DesktopModeTaskRepository()
+
+ whenever(shellTaskOrganizer.getRunningTasks(anyInt())).thenAnswer { runningTasks }
+
+ controller = createController()
+
+ shellInit.init()
+ }
+
+ private fun createController(): DesktopTasksController {
+ return DesktopTasksController(
+ context,
+ shellInit,
+ shellController,
+ shellTaskOrganizer,
+ transitions,
+ desktopModeTaskRepository,
+ TestShellExecutor()
+ )
+ }
+
+ @After
+ fun tearDown() {
+ mockitoSession.finishMocking()
+
+ runningTasks.clear()
+ }
+
+ @Test
+ fun instantiate_addInitCallback() {
+ verify(shellInit).addInitCallback(any(), any<DesktopTasksController>())
+ }
+
+ @Test
+ fun instantiate_flagOff_doNotAddInitCallback() {
+ whenever(DesktopModeStatus.isProto2Enabled()).thenReturn(false)
+ clearInvocations(shellInit)
+
+ createController()
+
+ verify(shellInit, never()).addInitCallback(any(), any<DesktopTasksController>())
+ }
+
+ @Test
+ fun showDesktopApps_allAppsInvisible_bringsToFront() {
+ val homeTask = setUpHomeTask()
+ val task1 = setUpFreeformTask()
+ val task2 = setUpFreeformTask()
+ markTaskHidden(task1)
+ markTaskHidden(task2)
+
+ controller.showDesktopApps()
+
+ val wct = getLatestWct()
+ assertThat(wct.hierarchyOps).hasSize(3)
+ // Expect order to be from bottom: home, task1, task2
+ wct.assertReorderAt(index = 0, homeTask)
+ wct.assertReorderAt(index = 1, task1)
+ wct.assertReorderAt(index = 2, task2)
+ }
+
+ @Test
+ fun showDesktopApps_appsAlreadyVisible_doesNothing() {
+ setUpHomeTask()
+ val task1 = setUpFreeformTask()
+ val task2 = setUpFreeformTask()
+ markTaskVisible(task1)
+ markTaskVisible(task2)
+
+ controller.showDesktopApps()
+
+ verifyWCTNotExecuted()
+ }
+
+ @Test
+ fun showDesktopApps_someAppsInvisible_reordersAll() {
+ val homeTask = setUpHomeTask()
+ val task1 = setUpFreeformTask()
+ val task2 = setUpFreeformTask()
+ markTaskHidden(task1)
+ markTaskVisible(task2)
+
+ controller.showDesktopApps()
+
+ val wct = getLatestWct()
+ assertThat(wct.hierarchyOps).hasSize(3)
+ // Expect order to be from bottom: home, task1, task2
+ wct.assertReorderAt(index = 0, homeTask)
+ wct.assertReorderAt(index = 1, task1)
+ wct.assertReorderAt(index = 2, task2)
+ }
+
+ @Test
+ fun showDesktopApps_noActiveTasks_reorderHomeToTop() {
+ val homeTask = setUpHomeTask()
+
+ controller.showDesktopApps()
+
+ val wct = getLatestWct()
+ assertThat(wct.hierarchyOps).hasSize(1)
+ wct.assertReorderAt(index = 0, homeTask)
+ }
+
+ @Test
+ fun moveToDesktop() {
+ val task = setUpFullscreenTask()
+ controller.moveToDesktop(task)
+ val wct = getLatestWct()
+ assertThat(wct.changes[task.token.asBinder()]?.windowingMode)
+ .isEqualTo(WINDOWING_MODE_FREEFORM)
+ }
+
+ @Test
+ fun moveToDesktop_nonExistentTask_doesNothing() {
+ controller.moveToDesktop(999)
+ verifyWCTNotExecuted()
+ }
+
+ @Test
+ fun moveToFullscreen() {
+ val task = setUpFreeformTask()
+ controller.moveToFullscreen(task)
+ val wct = getLatestWct()
+ assertThat(wct.changes[task.token.asBinder()]?.windowingMode)
+ .isEqualTo(WINDOWING_MODE_FULLSCREEN)
+ }
+
+ @Test
+ fun moveToFullscreen_nonExistentTask_doesNothing() {
+ controller.moveToFullscreen(999)
+ verifyWCTNotExecuted()
+ }
+
+ @Test
+ fun getTaskWindowingMode() {
+ val fullscreenTask = setUpFullscreenTask()
+ val freeformTask = setUpFreeformTask()
+
+ assertThat(controller.getTaskWindowingMode(fullscreenTask.taskId))
+ .isEqualTo(WINDOWING_MODE_FULLSCREEN)
+ assertThat(controller.getTaskWindowingMode(freeformTask.taskId))
+ .isEqualTo(WINDOWING_MODE_FREEFORM)
+ assertThat(controller.getTaskWindowingMode(999)).isEqualTo(WINDOWING_MODE_UNDEFINED)
+ }
+
+ private fun setUpFreeformTask(): RunningTaskInfo {
+ val task = createFreeformTask()
+ whenever(shellTaskOrganizer.getRunningTaskInfo(task.taskId)).thenReturn(task)
+ desktopModeTaskRepository.addActiveTask(task.taskId)
+ desktopModeTaskRepository.addOrMoveFreeformTaskToTop(task.taskId)
+ runningTasks.add(task)
+ return task
+ }
+
+ private fun setUpHomeTask(): RunningTaskInfo {
+ val task = createHomeTask()
+ whenever(shellTaskOrganizer.getRunningTaskInfo(task.taskId)).thenReturn(task)
+ runningTasks.add(task)
+ return task
+ }
+
+ private fun setUpFullscreenTask(): RunningTaskInfo {
+ val task = createFullscreenTask()
+ whenever(shellTaskOrganizer.getRunningTaskInfo(task.taskId)).thenReturn(task)
+ runningTasks.add(task)
+ return task
+ }
+
+ private fun markTaskVisible(task: RunningTaskInfo) {
+ desktopModeTaskRepository.updateVisibleFreeformTasks(task.taskId, visible = true)
+ }
+
+ private fun markTaskHidden(task: RunningTaskInfo) {
+ desktopModeTaskRepository.updateVisibleFreeformTasks(task.taskId, visible = false)
+ }
+
+ private fun getLatestWct(): WindowContainerTransaction {
+ val arg = ArgumentCaptor.forClass(WindowContainerTransaction::class.java)
+ if (Transitions.ENABLE_SHELL_TRANSITIONS) {
+ verify(transitions).startTransition(anyInt(), arg.capture(), isNull())
+ } else {
+ verify(shellTaskOrganizer).applyTransaction(arg.capture())
+ }
+ return arg.value
+ }
+
+ private fun verifyWCTNotExecuted() {
+ if (Transitions.ENABLE_SHELL_TRANSITIONS) {
+ verify(transitions, never()).startTransition(anyInt(), any(), isNull())
+ } else {
+ verify(shellTaskOrganizer, never()).applyTransaction(any())
+ }
+ }
+}
+
+private fun WindowContainerTransaction.assertReorderAt(index: Int, task: RunningTaskInfo) {
+ assertWithMessage("WCT does not have a hierarchy operation at index $index")
+ .that(hierarchyOps.size)
+ .isGreaterThan(index)
+ val op = hierarchyOps[index]
+ assertThat(op.type).isEqualTo(HIERARCHY_OP_TYPE_REORDER)
+ assertThat(op.container).isEqualTo(task.token.asBinder())
+}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTestHelpers.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTestHelpers.kt
new file mode 100644
index 0000000..dc91d75
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTestHelpers.kt
@@ -0,0 +1,61 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.desktopmode
+
+import android.app.ActivityManager.RunningTaskInfo
+import android.app.WindowConfiguration.ACTIVITY_TYPE_HOME
+import android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD
+import android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM
+import android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN
+import com.android.wm.shell.TestRunningTaskInfoBuilder
+
+class DesktopTestHelpers {
+ companion object {
+ /** Create a task that has windowing mode set to [WINDOWING_MODE_FREEFORM] */
+ @JvmStatic
+ fun createFreeformTask(): RunningTaskInfo {
+ return TestRunningTaskInfoBuilder()
+ .setToken(MockToken().token())
+ .setActivityType(ACTIVITY_TYPE_STANDARD)
+ .setWindowingMode(WINDOWING_MODE_FREEFORM)
+ .setLastActiveTime(100)
+ .build()
+ }
+
+ /** Create a task that has windowing mode set to [WINDOWING_MODE_FULLSCREEN] */
+ @JvmStatic
+ fun createFullscreenTask(): RunningTaskInfo {
+ return TestRunningTaskInfoBuilder()
+ .setToken(MockToken().token())
+ .setActivityType(ACTIVITY_TYPE_STANDARD)
+ .setWindowingMode(WINDOWING_MODE_FULLSCREEN)
+ .setLastActiveTime(100)
+ .build()
+ }
+
+ /** Create a new home task */
+ @JvmStatic
+ fun createHomeTask(): RunningTaskInfo {
+ return TestRunningTaskInfoBuilder()
+ .setToken(MockToken().token())
+ .setActivityType(ACTIVITY_TYPE_HOME)
+ .setWindowingMode(WINDOWING_MODE_FULLSCREEN)
+ .setLastActiveTime(100)
+ .build()
+ }
+ }
+}
\ No newline at end of file
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/MockToken.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/MockToken.java
new file mode 100644
index 0000000..09d474d
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/MockToken.java
@@ -0,0 +1,40 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.desktopmode;
+
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+import android.os.IBinder;
+import android.window.WindowContainerToken;
+
+/**
+ * {@link WindowContainerToken} wrapper that supports a mock binder
+ */
+class MockToken {
+ private final WindowContainerToken mToken;
+
+ MockToken() {
+ mToken = mock(WindowContainerToken.class);
+ IBinder binder = mock(IBinder.class);
+ when(mToken.asBinder()).thenReturn(binder);
+ }
+
+ WindowContainerToken token() {
+ return mToken;
+ }
+}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/CaptionWindowDecorViewModelTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/CaptionWindowDecorViewModelTests.java
index ad6fced..4875832 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/CaptionWindowDecorViewModelTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/CaptionWindowDecorViewModelTests.java
@@ -21,14 +21,15 @@
import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
import static org.mockito.Mockito.any;
-import static org.mockito.Mockito.anyInt;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import android.app.ActivityManager;
+import android.hardware.display.DisplayManager;
+import android.hardware.display.VirtualDisplay;
import android.hardware.input.InputManager;
import android.os.Handler;
import android.os.Looper;
@@ -37,9 +38,9 @@
import android.view.InputChannel;
import android.view.InputMonitor;
import android.view.SurfaceControl;
+import android.view.SurfaceView;
import androidx.test.filters.SmallTest;
-import androidx.test.rule.GrantPermissionRule;
import com.android.wm.shell.ShellTaskOrganizer;
import com.android.wm.shell.ShellTestCase;
@@ -47,6 +48,7 @@
import com.android.wm.shell.common.DisplayController;
import com.android.wm.shell.common.SyncTransactionQueue;
import com.android.wm.shell.desktopmode.DesktopModeController;
+import com.android.wm.shell.desktopmode.DesktopTasksController;
import org.junit.Before;
import org.junit.Test;
@@ -55,37 +57,29 @@
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
-import java.util.function.Supplier;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
/** Tests of {@link CaptionWindowDecorViewModel} */
@SmallTest
public class CaptionWindowDecorViewModelTests extends ShellTestCase {
- @Mock private CaptionWindowDecoration mCaptionWindowDecoration;
+ private static final String TAG = "CaptionWindowDecorViewModelTests";
+
+ @Mock private CaptionWindowDecoration mCaptionWindowDecoration;
@Mock private CaptionWindowDecoration.Factory mCaptionWindowDecorFactory;
@Mock private Handler mMainHandler;
-
@Mock private Choreographer mMainChoreographer;
-
@Mock private ShellTaskOrganizer mTaskOrganizer;
-
@Mock private DisplayController mDisplayController;
-
@Mock private SyncTransactionQueue mSyncQueue;
-
@Mock private DesktopModeController mDesktopModeController;
-
+ @Mock private DesktopTasksController mDesktopTasksController;
@Mock private InputMonitor mInputMonitor;
-
- @Mock private InputChannel mInputChannel;
-
- @Mock private CaptionWindowDecorViewModel.EventReceiverFactory mEventReceiverFactory;
-
- @Mock private CaptionWindowDecorViewModel.EventReceiver mEventReceiver;
-
@Mock private InputManager mInputManager;
+ @Mock private CaptionWindowDecorViewModel.InputMonitorFactory mMockInputMonitorFactory;
private final List<InputManager> mMockInputManagers = new ArrayList<>();
private CaptionWindowDecorViewModel mCaptionWindowDecorViewModel;
@@ -103,45 +97,48 @@
mDisplayController,
mSyncQueue,
Optional.of(mDesktopModeController),
+ Optional.of(mDesktopTasksController),
mCaptionWindowDecorFactory,
- new MockObjectSupplier<>(mMockInputManagers, () -> mock(InputManager.class)));
- mCaptionWindowDecorViewModel.setEventReceiverFactory(mEventReceiverFactory);
+ mMockInputMonitorFactory
+ );
doReturn(mCaptionWindowDecoration)
.when(mCaptionWindowDecorFactory)
.create(any(), any(), any(), any(), any(), any(), any(), any());
- when(mInputManager.monitorGestureInput(any(), anyInt())).thenReturn(mInputMonitor);
- when(mEventReceiverFactory.create(any(), any(), any())).thenReturn(mEventReceiver);
- when(mInputMonitor.getInputChannel()).thenReturn(mInputChannel);
+ when(mMockInputMonitorFactory.create(any(), any())).thenReturn(mInputMonitor);
+ // InputChannel cannot be mocked because it passes to InputEventReceiver.
+ final InputChannel[] inputChannels = InputChannel.openInputChannelPair(TAG);
+ inputChannels[0].dispose();
+ when(mInputMonitor.getInputChannel()).thenReturn(inputChannels[1]);
}
@Test
public void testDeleteCaptionOnChangeTransitionWhenNecessary() throws Exception {
- Looper.prepare();
final int taskId = 1;
final ActivityManager.RunningTaskInfo taskInfo =
- createTaskInfo(taskId, WINDOWING_MODE_FREEFORM);
+ createTaskInfo(taskId, Display.DEFAULT_DISPLAY, WINDOWING_MODE_FREEFORM);
SurfaceControl surfaceControl = mock(SurfaceControl.class);
- final SurfaceControl.Transaction startT = mock(SurfaceControl.Transaction.class);
- final SurfaceControl.Transaction finishT = mock(SurfaceControl.Transaction.class);
- GrantPermissionRule.grant(android.Manifest.permission.MONITOR_INPUT);
+ runOnMainThread(() -> {
+ final SurfaceControl.Transaction startT = mock(SurfaceControl.Transaction.class);
+ final SurfaceControl.Transaction finishT = mock(SurfaceControl.Transaction.class);
- mCaptionWindowDecorViewModel.onTaskOpening(taskInfo, surfaceControl, startT, finishT);
+ mCaptionWindowDecorViewModel.onTaskOpening(taskInfo, surfaceControl, startT, finishT);
+
+ taskInfo.configuration.windowConfiguration.setWindowingMode(WINDOWING_MODE_UNDEFINED);
+ taskInfo.configuration.windowConfiguration.setActivityType(ACTIVITY_TYPE_UNDEFINED);
+ mCaptionWindowDecorViewModel.onTaskChanging(taskInfo, surfaceControl, startT, finishT);
+ });
verify(mCaptionWindowDecorFactory)
.create(
- mContext,
- mDisplayController,
- mTaskOrganizer,
- taskInfo,
- surfaceControl,
- mMainHandler,
- mMainChoreographer,
- mSyncQueue);
-
- taskInfo.configuration.windowConfiguration.setWindowingMode(WINDOWING_MODE_UNDEFINED);
- taskInfo.configuration.windowConfiguration.setActivityType(ACTIVITY_TYPE_UNDEFINED);
- mCaptionWindowDecorViewModel.onTaskChanging(taskInfo, surfaceControl, startT, finishT);
+ mContext,
+ mDisplayController,
+ mTaskOrganizer,
+ taskInfo,
+ surfaceControl,
+ mMainHandler,
+ mMainChoreographer,
+ mSyncQueue);
verify(mCaptionWindowDecoration).close();
}
@@ -149,70 +146,105 @@
public void testCreateCaptionOnChangeTransitionWhenNecessary() throws Exception {
final int taskId = 1;
final ActivityManager.RunningTaskInfo taskInfo =
- createTaskInfo(taskId, WINDOWING_MODE_UNDEFINED);
+ createTaskInfo(taskId, Display.DEFAULT_DISPLAY, WINDOWING_MODE_UNDEFINED);
SurfaceControl surfaceControl = mock(SurfaceControl.class);
- final SurfaceControl.Transaction startT = mock(SurfaceControl.Transaction.class);
- final SurfaceControl.Transaction finishT = mock(SurfaceControl.Transaction.class);
- taskInfo.configuration.windowConfiguration.setActivityType(ACTIVITY_TYPE_UNDEFINED);
+ runOnMainThread(() -> {
+ final SurfaceControl.Transaction startT = mock(SurfaceControl.Transaction.class);
+ final SurfaceControl.Transaction finishT = mock(SurfaceControl.Transaction.class);
+ taskInfo.configuration.windowConfiguration.setActivityType(ACTIVITY_TYPE_UNDEFINED);
- mCaptionWindowDecorViewModel.onTaskChanging(taskInfo, surfaceControl, startT, finishT);
+ mCaptionWindowDecorViewModel.onTaskChanging(taskInfo, surfaceControl, startT, finishT);
- verify(mCaptionWindowDecorFactory, never())
+ taskInfo.configuration.windowConfiguration.setWindowingMode(WINDOWING_MODE_FREEFORM);
+ taskInfo.configuration.windowConfiguration.setActivityType(ACTIVITY_TYPE_STANDARD);
+
+ mCaptionWindowDecorViewModel.onTaskChanging(taskInfo, surfaceControl, startT, finishT);
+ });
+ verify(mCaptionWindowDecorFactory, times(1))
.create(
- mContext,
- mDisplayController,
- mTaskOrganizer,
- taskInfo,
- surfaceControl,
- mMainHandler,
- mMainChoreographer,
- mSyncQueue);
-
- taskInfo.configuration.windowConfiguration.setWindowingMode(WINDOWING_MODE_FREEFORM);
- taskInfo.configuration.windowConfiguration.setActivityType(ACTIVITY_TYPE_STANDARD);
-
- mCaptionWindowDecorViewModel.onTaskChanging(taskInfo, surfaceControl, startT, finishT);
-
- verify(mCaptionWindowDecorFactory)
- .create(
- mContext,
- mDisplayController,
- mTaskOrganizer,
- taskInfo,
- surfaceControl,
- mMainHandler,
- mMainChoreographer,
- mSyncQueue);
+ mContext,
+ mDisplayController,
+ mTaskOrganizer,
+ taskInfo,
+ surfaceControl,
+ mMainHandler,
+ mMainChoreographer,
+ mSyncQueue);
}
- private static ActivityManager.RunningTaskInfo createTaskInfo(int taskId, int windowingMode) {
+ @Test
+ public void testCreateAndDisposeEventReceiver() throws Exception {
+ final int taskId = 1;
+ final ActivityManager.RunningTaskInfo taskInfo =
+ createTaskInfo(taskId, Display.DEFAULT_DISPLAY, WINDOWING_MODE_FREEFORM);
+ taskInfo.configuration.windowConfiguration.setActivityType(ACTIVITY_TYPE_STANDARD);
+ runOnMainThread(() -> {
+ SurfaceControl surfaceControl = mock(SurfaceControl.class);
+ final SurfaceControl.Transaction startT = mock(SurfaceControl.Transaction.class);
+ final SurfaceControl.Transaction finishT = mock(SurfaceControl.Transaction.class);
+
+ mCaptionWindowDecorViewModel.onTaskOpening(taskInfo, surfaceControl, startT, finishT);
+
+ mCaptionWindowDecorViewModel.destroyWindowDecoration(taskInfo);
+ });
+ verify(mMockInputMonitorFactory).create(any(), any());
+ verify(mInputMonitor).dispose();
+ }
+
+ @Test
+ public void testEventReceiversOnMultipleDisplays() throws Exception {
+ runOnMainThread(() -> {
+ SurfaceView surfaceView = new SurfaceView(mContext);
+ final DisplayManager mDm = mContext.getSystemService(DisplayManager.class);
+ final VirtualDisplay secondaryDisplay = mDm.createVirtualDisplay(
+ "testEventReceiversOnMultipleDisplays", /*width=*/ 400, /*height=*/ 400,
+ /*densityDpi=*/ 320, surfaceView.getHolder().getSurface(),
+ DisplayManager.VIRTUAL_DISPLAY_FLAG_OWN_CONTENT_ONLY);
+ int secondaryDisplayId = secondaryDisplay.getDisplay().getDisplayId();
+
+ final int taskId = 1;
+ final ActivityManager.RunningTaskInfo taskInfo =
+ createTaskInfo(taskId, Display.DEFAULT_DISPLAY, WINDOWING_MODE_FREEFORM);
+ final ActivityManager.RunningTaskInfo secondTaskInfo =
+ createTaskInfo(taskId + 1, secondaryDisplayId, WINDOWING_MODE_FREEFORM);
+ final ActivityManager.RunningTaskInfo thirdTaskInfo =
+ createTaskInfo(taskId + 2, secondaryDisplayId, WINDOWING_MODE_FREEFORM);
+
+ SurfaceControl surfaceControl = mock(SurfaceControl.class);
+ final SurfaceControl.Transaction startT = mock(SurfaceControl.Transaction.class);
+ final SurfaceControl.Transaction finishT = mock(SurfaceControl.Transaction.class);
+
+ mCaptionWindowDecorViewModel.onTaskOpening(taskInfo, surfaceControl, startT, finishT);
+ mCaptionWindowDecorViewModel.onTaskOpening(secondTaskInfo, surfaceControl,
+ startT, finishT);
+ mCaptionWindowDecorViewModel.onTaskOpening(thirdTaskInfo, surfaceControl,
+ startT, finishT);
+ mCaptionWindowDecorViewModel.destroyWindowDecoration(thirdTaskInfo);
+ mCaptionWindowDecorViewModel.destroyWindowDecoration(taskInfo);
+ });
+ verify(mMockInputMonitorFactory, times(2)).create(any(), any());
+ verify(mInputMonitor, times(1)).dispose();
+ }
+
+ private void runOnMainThread(Runnable r) throws Exception {
+ final Handler mainHandler = new Handler(Looper.getMainLooper());
+ final CountDownLatch latch = new CountDownLatch(1);
+ mainHandler.post(() -> {
+ r.run();
+ latch.countDown();
+ });
+ latch.await(20, TimeUnit.MILLISECONDS);
+ }
+
+ private static ActivityManager.RunningTaskInfo createTaskInfo(int taskId,
+ int displayId, int windowingMode) {
ActivityManager.RunningTaskInfo taskInfo =
new TestRunningTaskInfoBuilder()
- .setDisplayId(Display.DEFAULT_DISPLAY)
+ .setDisplayId(displayId)
.setVisible(true)
.build();
taskInfo.taskId = taskId;
taskInfo.configuration.windowConfiguration.setWindowingMode(windowingMode);
return taskInfo;
}
-
- private static class MockObjectSupplier<T> implements Supplier<T> {
- private final List<T> mObjects;
- private final Supplier<T> mDefaultSupplier;
- private int mNumOfCalls = 0;
-
- private MockObjectSupplier(List<T> objects, Supplier<T> defaultSupplier) {
- mObjects = objects;
- mDefaultSupplier = defaultSupplier;
- }
-
- @Override
- public T get() {
- final T mock =
- mNumOfCalls < mObjects.size() ? mObjects.get(mNumOfCalls)
- : mDefaultSupplier.get();
- ++mNumOfCalls;
- return mock;
- }
- }
}
diff --git a/media/java/android/media/ImageWriter.java b/media/java/android/media/ImageWriter.java
index 9f52bf1..308bb3e 100644
--- a/media/java/android/media/ImageWriter.java
+++ b/media/java/android/media/ImageWriter.java
@@ -100,6 +100,8 @@
private final Object mListenerLock = new Object();
private OnImageReleasedListener mListener;
private ListenerHandler mListenerHandler;
+ private final Object mCloseLock = new Object();
+ private boolean mIsWriterValid = false;
private long mNativeContext;
private int mWidth;
@@ -305,6 +307,8 @@
ImageUtils.getEstimatedNativeAllocBytes(mWidth, mHeight,
useLegacyImageFormat ? imageFormat : hardwareBufferFormat, /*buffer count*/ 1);
VMRuntime.getRuntime().registerNativeAllocation(mEstimatedNativeAllocBytes);
+
+ mIsWriterValid = true;
}
private ImageWriter(Surface surface, int maxImages, boolean useSurfaceImageFormatInfo,
@@ -448,14 +452,17 @@
* @see Image#close
*/
public Image dequeueInputImage() {
- if (mDequeuedImages.size() >= mMaxImages) {
- throw new IllegalStateException("Already dequeued max number of Images " + mMaxImages);
+ synchronized (mCloseLock) {
+ if (mDequeuedImages.size() >= mMaxImages) {
+ throw new IllegalStateException(
+ "Already dequeued max number of Images " + mMaxImages);
+ }
+ WriterSurfaceImage newImage = new WriterSurfaceImage(this);
+ nativeDequeueInputImage(mNativeContext, newImage);
+ mDequeuedImages.add(newImage);
+ newImage.mIsImageValid = true;
+ return newImage;
}
- WriterSurfaceImage newImage = new WriterSurfaceImage(this);
- nativeDequeueInputImage(mNativeContext, newImage);
- mDequeuedImages.add(newImage);
- newImage.mIsImageValid = true;
- return newImage;
}
/**
@@ -513,49 +520,53 @@
if (image == null) {
throw new IllegalArgumentException("image shouldn't be null");
}
- boolean ownedByMe = isImageOwnedByMe(image);
- if (ownedByMe && !(((WriterSurfaceImage) image).mIsImageValid)) {
- throw new IllegalStateException("Image from ImageWriter is invalid");
- }
- // For images from other components that have non-null owner, need to detach first,
- // then attach. Images without owners must already be attachable.
- if (!ownedByMe) {
- if ((image.getOwner() instanceof ImageReader)) {
- ImageReader prevOwner = (ImageReader) image.getOwner();
-
- prevOwner.detachImage(image);
- } else if (image.getOwner() != null) {
- throw new IllegalArgumentException("Only images from ImageReader can be queued to"
- + " ImageWriter, other image source is not supported yet!");
+ synchronized (mCloseLock) {
+ boolean ownedByMe = isImageOwnedByMe(image);
+ if (ownedByMe && !(((WriterSurfaceImage) image).mIsImageValid)) {
+ throw new IllegalStateException("Image from ImageWriter is invalid");
}
- attachAndQueueInputImage(image);
- // This clears the native reference held by the original owner.
- // When this Image is detached later by this ImageWriter, the
- // native memory won't be leaked.
- image.close();
- return;
- }
+ // For images from other components that have non-null owner, need to detach first,
+ // then attach. Images without owners must already be attachable.
+ if (!ownedByMe) {
+ if ((image.getOwner() instanceof ImageReader)) {
+ ImageReader prevOwner = (ImageReader) image.getOwner();
- Rect crop = image.getCropRect();
- nativeQueueInputImage(mNativeContext, image, image.getTimestamp(), image.getDataSpace(),
- crop.left, crop.top, crop.right, crop.bottom, image.getTransform(),
- image.getScalingMode());
+ prevOwner.detachImage(image);
+ } else if (image.getOwner() != null) {
+ throw new IllegalArgumentException(
+ "Only images from ImageReader can be queued to"
+ + " ImageWriter, other image source is not supported yet!");
+ }
- /**
- * Only remove and cleanup the Images that are owned by this
- * ImageWriter. Images detached from other owners are only temporarily
- * owned by this ImageWriter and will be detached immediately after they
- * are released by downstream consumers, so there is no need to keep
- * track of them in mDequeuedImages.
- */
- if (ownedByMe) {
- mDequeuedImages.remove(image);
- // Do not call close here, as close is essentially cancel image.
- WriterSurfaceImage wi = (WriterSurfaceImage) image;
- wi.clearSurfacePlanes();
- wi.mIsImageValid = false;
+ attachAndQueueInputImage(image);
+ // This clears the native reference held by the original owner.
+ // When this Image is detached later by this ImageWriter, the
+ // native memory won't be leaked.
+ image.close();
+ return;
+ }
+
+ Rect crop = image.getCropRect();
+ nativeQueueInputImage(mNativeContext, image, image.getTimestamp(), image.getDataSpace(),
+ crop.left, crop.top, crop.right, crop.bottom, image.getTransform(),
+ image.getScalingMode());
+
+ /**
+ * Only remove and cleanup the Images that are owned by this
+ * ImageWriter. Images detached from other owners are only temporarily
+ * owned by this ImageWriter and will be detached immediately after they
+ * are released by downstream consumers, so there is no need to keep
+ * track of them in mDequeuedImages.
+ */
+ if (ownedByMe) {
+ mDequeuedImages.remove(image);
+ // Do not call close here, as close is essentially cancel image.
+ WriterSurfaceImage wi = (WriterSurfaceImage) image;
+ wi.clearSurfacePlanes();
+ wi.mIsImageValid = false;
+ }
}
}
@@ -691,17 +702,23 @@
*/
@Override
public void close() {
- setOnImageReleasedListener(null, null);
- for (Image image : mDequeuedImages) {
- image.close();
- }
- mDequeuedImages.clear();
- nativeClose(mNativeContext);
- mNativeContext = 0;
+ synchronized (mCloseLock) {
+ if (!mIsWriterValid) {
+ return;
+ }
+ setOnImageReleasedListener(null, null);
+ for (Image image : mDequeuedImages) {
+ image.close();
+ }
+ mDequeuedImages.clear();
+ nativeClose(mNativeContext);
+ mNativeContext = 0;
- if (mEstimatedNativeAllocBytes > 0) {
- VMRuntime.getRuntime().registerNativeFree(mEstimatedNativeAllocBytes);
- mEstimatedNativeAllocBytes = 0;
+ if (mEstimatedNativeAllocBytes > 0) {
+ VMRuntime.getRuntime().registerNativeFree(mEstimatedNativeAllocBytes);
+ mEstimatedNativeAllocBytes = 0;
+ }
+ mIsWriterValid = false;
}
}
@@ -790,10 +807,16 @@
@Override
public void handleMessage(Message msg) {
OnImageReleasedListener listener;
- synchronized (mListenerLock) {
+ boolean isWriterValid;
+ synchronized (ImageWriter.this.mListenerLock) {
listener = mListener;
}
- if (listener != null) {
+ // Check to make sure we don't accidentally queue images after the writer is
+ // closed or closing
+ synchronized (ImageWriter.this.mCloseLock) {
+ isWriterValid = ImageWriter.this.mIsWriterValid;
+ }
+ if (listener != null && isWriterValid) {
listener.onImageReleased(ImageWriter.this);
}
}
@@ -813,10 +836,14 @@
}
final Handler handler;
+ final boolean isWriterValid;
synchronized (iw.mListenerLock) {
handler = iw.mListenerHandler;
}
- if (handler != null) {
+ synchronized (iw.mCloseLock) {
+ isWriterValid = iw.mIsWriterValid;
+ }
+ if (handler != null && isWriterValid) {
handler.sendEmptyMessage(0);
}
}
@@ -1050,6 +1077,9 @@
private int mTransform = 0; //Default no transform
private int mScalingMode = 0; //Default frozen scaling mode
+ private final Object mCloseLock = new Object(); // lock to protect against multiple
+ // simultaneous calls to close()
+
public WriterSurfaceImage(ImageWriter writer) {
mOwner = writer;
mWidth = writer.mWidth;
@@ -1192,8 +1222,10 @@
@Override
public void close() {
- if (mIsImageValid) {
- getOwner().abortImage(this);
+ synchronized (mCloseLock) {
+ if (mIsImageValid) {
+ getOwner().abortImage(this);
+ }
}
}
diff --git a/packages/PrintSpooler/res/values-te/strings.xml b/packages/PrintSpooler/res/values-te/strings.xml
index 3ab43f1..c11e2b7 100644
--- a/packages/PrintSpooler/res/values-te/strings.xml
+++ b/packages/PrintSpooler/res/values-te/strings.xml
@@ -49,10 +49,10 @@
<string name="print_options_collapsed" msgid="7455930445670414332">"ముద్రణ ఎంపికలు కుదించబడ్డాయి"</string>
<string name="search" msgid="5421724265322228497">"సెర్చ్"</string>
<string name="all_printers_label" msgid="3178848870161526399">"అన్ని ప్రింటర్లు"</string>
- <string name="add_print_service_label" msgid="5356702546188981940">"సేవను జోడించు"</string>
+ <string name="add_print_service_label" msgid="5356702546188981940">"సేవను జోడించండి"</string>
<string name="print_search_box_shown_utterance" msgid="7967404953901376090">"సెర్చ్ బాక్స్ చూపబడింది"</string>
<string name="print_search_box_hidden_utterance" msgid="5727755169343113351">"సెర్చ్ బాక్స్ దాచబడింది"</string>
- <string name="print_add_printer" msgid="1088656468360653455">"ప్రింటర్ను జోడించు"</string>
+ <string name="print_add_printer" msgid="1088656468360653455">"ప్రింటర్ను జోడించండి"</string>
<string name="print_select_printer" msgid="7388760939873368698">"ప్రింటర్ను ఎంచుకోండి"</string>
<string name="print_forget_printer" msgid="5035287497291910766">"ప్రింటర్ను విస్మరించు"</string>
<plurals name="print_search_result_count_utterance" formatted="false" msgid="6997663738361080868">
diff --git a/packages/SettingsLib/IllustrationPreference/res/values/colors.xml b/packages/SettingsLib/IllustrationPreference/res/values/colors.xml
index 0de7be0..5d6c343 100644
--- a/packages/SettingsLib/IllustrationPreference/res/values/colors.xml
+++ b/packages/SettingsLib/IllustrationPreference/res/values/colors.xml
@@ -40,6 +40,7 @@
<color name="settingslib_color_grey800">#3c4043</color>
<color name="settingslib_color_grey700">#5f6368</color>
<color name="settingslib_color_grey600">#80868b</color>
+ <color name="settingslib_color_grey500">#9AA0A6</color>
<color name="settingslib_color_grey400">#bdc1c6</color>
<color name="settingslib_color_grey300">#dadce0</color>
<color name="settingslib_color_grey200">#e8eaed</color>
diff --git a/packages/SettingsLib/IllustrationPreference/src/com/android/settingslib/widget/LottieColorUtils.java b/packages/SettingsLib/IllustrationPreference/src/com/android/settingslib/widget/LottieColorUtils.java
index 93b6acc..5e2c437 100644
--- a/packages/SettingsLib/IllustrationPreference/src/com/android/settingslib/widget/LottieColorUtils.java
+++ b/packages/SettingsLib/IllustrationPreference/src/com/android/settingslib/widget/LottieColorUtils.java
@@ -40,10 +40,13 @@
HashMap<String, Integer> map = new HashMap<>();
map.put(
".grey600",
- R.color.settingslib_color_grey300);
+ R.color.settingslib_color_grey400);
+ map.put(
+ ".grey700",
+ R.color.settingslib_color_grey500);
map.put(
".grey800",
- R.color.settingslib_color_grey200);
+ R.color.settingslib_color_grey300);
map.put(
".grey900",
R.color.settingslib_color_grey50);
diff --git a/packages/SettingsLib/res/values-am/strings.xml b/packages/SettingsLib/res/values-am/strings.xml
index adbdf22..ad4c32e 100644
--- a/packages/SettingsLib/res/values-am/strings.xml
+++ b/packages/SettingsLib/res/values-am/strings.xml
@@ -479,8 +479,7 @@
<string name="power_charging_duration" msgid="6127154952524919719">"<xliff:g id="LEVEL">%1$s</xliff:g> - እስኪሞላ ድረስ <xliff:g id="TIME">%2$s</xliff:g> ይቀራል"</string>
<!-- no translation found for power_charging_limited (6732738149313642521) -->
<skip />
- <!-- no translation found for power_charging_future_paused (6829683663982987290) -->
- <skip />
+ <string name="power_charging_future_paused" msgid="6829683663982987290">"<xliff:g id="LEVEL">%1$s</xliff:g> - እስከ <xliff:g id="DOCK_DEFENDER_THRESHOLD">%2$s</xliff:g> ድረስ ኃይል መሙላት"</string>
<string name="battery_info_status_unknown" msgid="268625384868401114">"ያልታወቀ"</string>
<string name="battery_info_status_charging" msgid="4279958015430387405">"ኃይል በመሙላት ላይ"</string>
<string name="battery_info_status_charging_fast" msgid="8027559755902954885">"ኃይል በፍጥነት በመሙላት ላይ"</string>
diff --git a/packages/SettingsLib/res/values-te/strings.xml b/packages/SettingsLib/res/values-te/strings.xml
index ca2f143..730247d 100644
--- a/packages/SettingsLib/res/values-te/strings.xml
+++ b/packages/SettingsLib/res/values-te/strings.xml
@@ -342,7 +342,7 @@
<string name="bluetooth_enable_gabeldorsche_summary" msgid="2054730331770712629">"బ్లూటూత్ Gabeldorsche ఫీచర్ స్ట్యాక్ను ఎనేబుల్ చేస్తుంది."</string>
<string name="enhanced_connectivity_summary" msgid="1576414159820676330">"మెరుగైన కనెక్టివిటీ ఫీచర్ను ఎనేబుల్ చేస్తుంది."</string>
<string name="enable_terminal_title" msgid="3834790541986303654">"స్థానిక టెర్మినల్"</string>
- <string name="enable_terminal_summary" msgid="2481074834856064500">"స్థానిక షెల్ యాక్సెస్ను అందించే టెర్మినల్ యాప్ను ప్రారంభించు"</string>
+ <string name="enable_terminal_summary" msgid="2481074834856064500">"స్థానిక షెల్ యాక్సెస్ను అందించే టెర్మినల్ యాప్ను ప్రారంభించండి"</string>
<string name="hdcp_checking_title" msgid="3155692785074095986">"HDCP చెకింగ్"</string>
<string name="hdcp_checking_dialog_title" msgid="7691060297616217781">"HDCP తనిఖీ ప్రవర్తనను సెట్ చేయండి"</string>
<string name="debug_debugging_category" msgid="535341063709248842">"డీబగ్గింగ్"</string>
diff --git a/packages/SettingsLib/res/values/strings.xml b/packages/SettingsLib/res/values/strings.xml
index d1ac7d0..cf37205 100644
--- a/packages/SettingsLib/res/values/strings.xml
+++ b/packages/SettingsLib/res/values/strings.xml
@@ -1125,9 +1125,9 @@
<!-- [CHAR_LIMIT=40] Label for battery level chart when charging with duration -->
<string name="power_charging_duration"><xliff:g id="level">%1$s</xliff:g> - <xliff:g id="time">%2$s</xliff:g> left until full</string>
<!-- [CHAR_LIMIT=80] Label for battery level chart when charge been limited -->
- <string name="power_charging_limited"><xliff:g id="level">%1$s</xliff:g> - Charging paused</string>
+ <string name="power_charging_limited"><xliff:g id="level">%1$s</xliff:g> - Charging optimized</string>
<!-- [CHAR_LIMIT=80] Label for battery charging future pause -->
- <string name="power_charging_future_paused"><xliff:g id="level">%1$s</xliff:g> - Charging to <xliff:g id="dock_defender_threshold">%2$s</xliff:g></string>
+ <string name="power_charging_future_paused"><xliff:g id="level">%1$s</xliff:g> - Charging optimized</string>
<!-- Battery Info screen. Value for a status item. Used for diagnostic info screens, precise translation isn't needed -->
<string name="battery_info_status_unknown">Unknown</string>
diff --git a/packages/SettingsProvider/res/values/defaults.xml b/packages/SettingsProvider/res/values/defaults.xml
index 46a94fd..99b15db 100644
--- a/packages/SettingsProvider/res/values/defaults.xml
+++ b/packages/SettingsProvider/res/values/defaults.xml
@@ -165,6 +165,9 @@
<!-- Default for Settings.Secure.LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS -->
<bool name="def_lock_screen_allow_private_notifications">true</bool>
+ <!-- Default for Settings.Secure.LOCK_SCREEN_SHOW_ONLY_UNSEEN_NOTIFICATIONS -->
+ <bool name="def_lock_screen_show_only_unseen_notifications">false</bool>
+
<!-- Default for Settings.Global.HEADS_UP_NOTIFICATIONS_ENABLED, 1==on -->
<integer name="def_heads_up_enabled">1</integer>
diff --git a/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java b/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java
index 92a938c..1b0b6b4 100644
--- a/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java
+++ b/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java
@@ -146,6 +146,7 @@
Settings.Secure.LOCK_SCREEN_CUSTOM_CLOCK_FACE,
Settings.Secure.LOCK_SCREEN_SHOW_NOTIFICATIONS,
Settings.Secure.LOCK_SCREEN_SHOW_SILENT_NOTIFICATIONS,
+ Settings.Secure.LOCK_SCREEN_SHOW_ONLY_UNSEEN_NOTIFICATIONS,
Settings.Secure.SHOW_NOTIFICATION_SNOOZE,
Settings.Secure.NOTIFICATION_HISTORY_ENABLED,
Settings.Secure.ZEN_DURATION,
diff --git a/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java b/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java
index eabf4cc..4fa490f 100644
--- a/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java
+++ b/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java
@@ -219,6 +219,7 @@
VALIDATORS.put(Secure.LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, BOOLEAN_VALIDATOR);
VALIDATORS.put(Secure.LOCK_SCREEN_SHOW_NOTIFICATIONS, BOOLEAN_VALIDATOR);
VALIDATORS.put(Secure.LOCK_SCREEN_SHOW_SILENT_NOTIFICATIONS, BOOLEAN_VALIDATOR);
+ VALIDATORS.put(Secure.LOCK_SCREEN_SHOW_ONLY_UNSEEN_NOTIFICATIONS, BOOLEAN_VALIDATOR);
VALIDATORS.put(Secure.SHOW_NOTIFICATION_SNOOZE, BOOLEAN_VALIDATOR);
VALIDATORS.put(Secure.NOTIFICATION_HISTORY_ENABLED, BOOLEAN_VALIDATOR);
VALIDATORS.put(Secure.ZEN_DURATION, ANY_INTEGER_VALIDATOR);
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
index ded7e785..84a5593 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
@@ -3631,7 +3631,7 @@
}
private final class UpgradeController {
- private static final int SETTINGS_VERSION = 211;
+ private static final int SETTINGS_VERSION = 212;
private final int mUserId;
@@ -5527,6 +5527,26 @@
}
currentVersion = 211;
}
+ if (currentVersion == 211) {
+ // Version 211: Set default value for
+ // Secure#LOCK_SCREEN_SHOW_ONLY_UNSEEN_NOTIFICATIONS
+ final SettingsState secureSettings = getSecureSettingsLocked(userId);
+ final Setting lockScreenUnseenSetting = secureSettings
+ .getSettingLocked(Secure.LOCK_SCREEN_SHOW_ONLY_UNSEEN_NOTIFICATIONS);
+ if (lockScreenUnseenSetting.isNull()) {
+ final boolean defSetting = getContext().getResources()
+ .getBoolean(R.bool.def_lock_screen_show_only_unseen_notifications);
+ secureSettings.insertSettingOverrideableByRestoreLocked(
+ Secure.LOCK_SCREEN_SHOW_ONLY_UNSEEN_NOTIFICATIONS,
+ defSetting ? "1" : "0",
+ null /* tag */,
+ true /* makeDefault */,
+ SettingsState.SYSTEM_PACKAGE_NAME);
+ }
+
+ currentVersion = 212;
+ }
+
// vXXX: Add new settings above this point.
if (currentVersion != newVersion) {
diff --git a/packages/SoundPicker/res/values-te/strings.xml b/packages/SoundPicker/res/values-te/strings.xml
index feaf4c8..2d03ac0 100644
--- a/packages/SoundPicker/res/values-te/strings.xml
+++ b/packages/SoundPicker/res/values-te/strings.xml
@@ -19,9 +19,9 @@
<string name="ringtone_default" msgid="798836092118824500">"ఆటోమేటిక్ రింగ్టోన్"</string>
<string name="notification_sound_default" msgid="8133121186242636840">"నోటిఫికేషన్ ఆటోమేటిక్ సౌండ్"</string>
<string name="alarm_sound_default" msgid="4787646764557462649">"అలారం ఆటోమేటిక్ సౌండ్"</string>
- <string name="add_ringtone_text" msgid="6642389991738337529">"రింగ్టోన్ను జోడించు"</string>
- <string name="add_alarm_text" msgid="3545497316166999225">"అలారాన్ని జోడించు"</string>
- <string name="add_notification_text" msgid="4431129543300614788">"నోటిఫికేషన్ని జోడించు"</string>
+ <string name="add_ringtone_text" msgid="6642389991738337529">"రింగ్టోన్ను జోడించండి"</string>
+ <string name="add_alarm_text" msgid="3545497316166999225">"అలారాన్ని జోడించండి"</string>
+ <string name="add_notification_text" msgid="4431129543300614788">"నోటిఫికేషన్ని జోడించండి"</string>
<string name="delete_ringtone_text" msgid="201443984070732499">"తొలగించండి"</string>
<string name="unable_to_add_ringtone" msgid="4583511263449467326">"అనుకూల రింగ్టోన్ను జోడించలేకపోయింది"</string>
<string name="unable_to_delete_ringtone" msgid="6792301380142859496">"అనుకూల రింగ్టోన్ను తొలగించలేకపోయింది"</string>
diff --git a/packages/SystemUI/AndroidManifest.xml b/packages/SystemUI/AndroidManifest.xml
index 4f08a30..2f5b42f 100644
--- a/packages/SystemUI/AndroidManifest.xml
+++ b/packages/SystemUI/AndroidManifest.xml
@@ -886,7 +886,7 @@
android:showForAllUsers="true"
android:finishOnTaskLaunch="true"
android:launchMode="singleInstance"
- android:configChanges="screenSize|smallestScreenSize|screenLayout|keyboard|keyboardHidden"
+ android:configChanges="screenSize|smallestScreenSize|screenLayout|orientation|keyboard|keyboardHidden"
android:visibleToInstantApps="true">
</activity>
diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/ActivityLaunchAnimator.kt b/packages/SystemUI/animation/src/com/android/systemui/animation/ActivityLaunchAnimator.kt
index ebabdf57..fe349f2 100644
--- a/packages/SystemUI/animation/src/com/android/systemui/animation/ActivityLaunchAnimator.kt
+++ b/packages/SystemUI/animation/src/com/android/systemui/animation/ActivityLaunchAnimator.kt
@@ -49,12 +49,12 @@
*/
class ActivityLaunchAnimator(
/** The animator used when animating a View into an app. */
- private val launchAnimator: LaunchAnimator = LaunchAnimator(TIMINGS, INTERPOLATORS),
+ private val launchAnimator: LaunchAnimator = DEFAULT_LAUNCH_ANIMATOR,
/** The animator used when animating a Dialog into an app. */
// TODO(b/218989950): Remove this animator and instead set the duration of the dim fade out to
// TIMINGS.contentBeforeFadeOutDuration.
- private val dialogToAppAnimator: LaunchAnimator = LaunchAnimator(DIALOG_TIMINGS, INTERPOLATORS)
+ private val dialogToAppAnimator: LaunchAnimator = DEFAULT_DIALOG_TO_APP_ANIMATOR
) {
companion object {
/** The timings when animating a View into an app. */
@@ -85,6 +85,9 @@
contentAfterFadeInInterpolator = PathInterpolator(0f, 0f, 0.6f, 1f)
)
+ private val DEFAULT_LAUNCH_ANIMATOR = LaunchAnimator(TIMINGS, INTERPOLATORS)
+ private val DEFAULT_DIALOG_TO_APP_ANIMATOR = LaunchAnimator(DIALOG_TIMINGS, INTERPOLATORS)
+
/** Durations & interpolators for the navigation bar fading in & out. */
private const val ANIMATION_DURATION_NAV_FADE_IN = 266L
private const val ANIMATION_DURATION_NAV_FADE_OUT = 133L
@@ -117,6 +120,22 @@
/** The set of [Listener] that should be notified of any animation started by this animator. */
private val listeners = LinkedHashSet<Listener>()
+ /** Top-level listener that can be used to notify all registered [listeners]. */
+ private val lifecycleListener =
+ object : Listener {
+ override fun onLaunchAnimationStart() {
+ listeners.forEach { it.onLaunchAnimationStart() }
+ }
+
+ override fun onLaunchAnimationEnd() {
+ listeners.forEach { it.onLaunchAnimationEnd() }
+ }
+
+ override fun onLaunchAnimationProgress(linearProgress: Float) {
+ listeners.forEach { it.onLaunchAnimationProgress(linearProgress) }
+ }
+ }
+
/**
* Start an intent and animate the opening window. The intent will be started by running
* [intentStarter], which should use the provided [RemoteAnimationAdapter] and return the launch
@@ -156,7 +175,7 @@
?: throw IllegalStateException(
"ActivityLaunchAnimator.callback must be set before using this animator"
)
- val runner = Runner(controller)
+ val runner = createRunner(controller)
val hideKeyguardWithAnimation = callback.isOnKeyguard() && !showOverLockscreen
// Pass the RemoteAnimationAdapter to the intent starter only if we are not hiding the
@@ -256,7 +275,18 @@
}
/** Create a new animation [Runner] controlled by [controller]. */
- @VisibleForTesting fun createRunner(controller: Controller): Runner = Runner(controller)
+ @VisibleForTesting
+ fun createRunner(controller: Controller): Runner {
+ // Make sure we use the modified timings when animating a dialog into an app.
+ val launchAnimator =
+ if (controller.isDialogLaunch) {
+ dialogToAppAnimator
+ } else {
+ launchAnimator
+ }
+
+ return Runner(controller, callback!!, launchAnimator, lifecycleListener)
+ }
interface PendingIntentStarter {
/**
@@ -353,14 +383,20 @@
* this if the animation was already started, i.e. if [onLaunchAnimationStart] was called
* before the cancellation.
*
- * If this launch animation affected the occlusion state of the keyguard, WM will provide
- * us with [newKeyguardOccludedState] so that we can set the occluded state appropriately.
+ * If this launch animation affected the occlusion state of the keyguard, WM will provide us
+ * with [newKeyguardOccludedState] so that we can set the occluded state appropriately.
*/
fun onLaunchAnimationCancelled(newKeyguardOccludedState: Boolean? = null) {}
}
- @VisibleForTesting
- inner class Runner(private val controller: Controller) : IRemoteAnimationRunner.Stub() {
+ class Runner(
+ private val controller: Controller,
+ private val callback: Callback,
+ /** The animator to use to animate the window launch. */
+ private val launchAnimator: LaunchAnimator = DEFAULT_LAUNCH_ANIMATOR,
+ /** Listener for animation lifecycle events. */
+ private val listener: Listener? = null
+ ) : IRemoteAnimationRunner.Stub() {
private val launchContainer = controller.launchContainer
private val context = launchContainer.context
private val transactionApplierView =
@@ -448,18 +484,9 @@
left = windowBounds.left,
right = windowBounds.right
)
- val callback = this@ActivityLaunchAnimator.callback!!
val windowBackgroundColor =
window.taskInfo?.let { callback.getBackgroundColor(it) } ?: window.backgroundColor
- // Make sure we use the modified timings when animating a dialog into an app.
- val launchAnimator =
- if (controller.isDialogLaunch) {
- dialogToAppAnimator
- } else {
- launchAnimator
- }
-
// TODO(b/184121838): We should somehow get the top and bottom radius of the window
// instead of recomputing isExpandingFullyAbove here.
val isExpandingFullyAbove =
@@ -483,12 +510,12 @@
val controller =
object : Controller by delegate {
override fun onLaunchAnimationStart(isExpandingFullyAbove: Boolean) {
- listeners.forEach { it.onLaunchAnimationStart() }
+ listener?.onLaunchAnimationStart()
delegate.onLaunchAnimationStart(isExpandingFullyAbove)
}
override fun onLaunchAnimationEnd(isExpandingFullyAbove: Boolean) {
- listeners.forEach { it.onLaunchAnimationEnd() }
+ listener?.onLaunchAnimationEnd()
iCallback?.invoke()
delegate.onLaunchAnimationEnd(isExpandingFullyAbove)
}
@@ -505,7 +532,7 @@
}
navigationBar?.let { applyStateToNavigationBar(it, state, linearProgress) }
- listeners.forEach { it.onLaunchAnimationProgress(linearProgress) }
+ listener?.onLaunchAnimationProgress(linearProgress)
delegate.onLaunchAnimationProgress(state, progress, linearProgress)
}
}
diff --git a/packages/SystemUI/compose/features/AndroidManifest.xml b/packages/SystemUI/compose/features/AndroidManifest.xml
index 278a89f..c1a9ec5 100644
--- a/packages/SystemUI/compose/features/AndroidManifest.xml
+++ b/packages/SystemUI/compose/features/AndroidManifest.xml
@@ -16,43 +16,6 @@
-->
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
- xmlns:tools="http://schemas.android.com/tools"
-
package="com.android.systemui.compose.features">
- <application
- android:name="android.app.Application"
- android:appComponentFactory="androidx.core.app.AppComponentFactory"
- tools:replace="android:name,android:appComponentFactory">
- <!-- Disable providers from SystemUI -->
- <provider android:name="com.android.systemui.keyguard.KeyguardSliceProvider"
- android:authorities="com.android.systemui.test.keyguard.disabled"
- android:enabled="false"
- tools:replace="android:authorities"
- tools:node="remove" />
- <provider android:name="com.google.android.systemui.keyguard.KeyguardSliceProviderGoogle"
- android:authorities="com.android.systemui.test.keyguard.disabled"
- android:enabled="false"
- tools:replace="android:authorities"
- tools:node="remove" />
- <provider android:name="com.android.systemui.keyguard.KeyguardQuickAffordanceProvider"
- android:authorities="com.android.systemui.test.keyguard.quickaffordance.disabled"
- android:enabled="false"
- tools:replace="android:authorities"
- tools:node="remove" />
- <provider android:name="com.android.keyguard.clock.ClockOptionsProvider"
- android:authorities="com.android.systemui.test.keyguard.clock.disabled"
- android:enabled="false"
- tools:replace="android:authorities"
- tools:node="remove" />
- <provider android:name="com.android.systemui.people.PeopleProvider"
- android:authorities="com.android.systemui.test.people.disabled"
- android:enabled="false"
- tools:replace="android:authorities"
- tools:node="remove" />
- <provider android:name="androidx.core.content.FileProvider"
- android:authorities="com.android.systemui.test.fileprovider.disabled"
- android:enabled="false"
- tools:replace="android:authorities"
- tools:node="remove"/>
- </application>
+
</manifest>
diff --git a/packages/SystemUI/compose/features/tests/AndroidManifest.xml b/packages/SystemUI/compose/features/tests/AndroidManifest.xml
index 5e54c1f..2fa475d 100644
--- a/packages/SystemUI/compose/features/tests/AndroidManifest.xml
+++ b/packages/SystemUI/compose/features/tests/AndroidManifest.xml
@@ -15,10 +15,46 @@
-->
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:tools="http://schemas.android.com/tools"
package="com.android.systemui.compose.features.tests" >
- <application>
+ <application
+ android:name="android.app.Application"
+ android:appComponentFactory="androidx.core.app.AppComponentFactory"
+ tools:replace="android:name,android:appComponentFactory">
<uses-library android:name="android.test.runner" />
+
+ <!-- Disable providers from SystemUI -->
+ <provider android:name="com.android.systemui.keyguard.KeyguardSliceProvider"
+ android:authorities="com.android.systemui.test.keyguard.disabled"
+ android:enabled="false"
+ tools:replace="android:authorities"
+ tools:node="remove" />
+ <provider android:name="com.google.android.systemui.keyguard.KeyguardSliceProviderGoogle"
+ android:authorities="com.android.systemui.test.keyguard.disabled"
+ android:enabled="false"
+ tools:replace="android:authorities"
+ tools:node="remove" />
+ <provider android:name="com.android.systemui.keyguard.KeyguardQuickAffordanceProvider"
+ android:authorities="com.android.systemui.test.keyguard.quickaffordance.disabled"
+ android:enabled="false"
+ tools:replace="android:authorities"
+ tools:node="remove" />
+ <provider android:name="com.android.keyguard.clock.ClockOptionsProvider"
+ android:authorities="com.android.systemui.test.keyguard.clock.disabled"
+ android:enabled="false"
+ tools:replace="android:authorities"
+ tools:node="remove" />
+ <provider android:name="com.android.systemui.people.PeopleProvider"
+ android:authorities="com.android.systemui.test.people.disabled"
+ android:enabled="false"
+ tools:replace="android:authorities"
+ tools:node="remove" />
+ <provider android:name="androidx.core.content.FileProvider"
+ android:authorities="com.android.systemui.test.fileprovider.disabled"
+ android:enabled="false"
+ tools:replace="android:authorities"
+ tools:node="remove"/>
</application>
<instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
diff --git a/packages/SystemUI/customization/src/com/android/systemui/shared/quickaffordance/shared/model/KeyguardQuickAffordancePreviewConstants.kt b/packages/SystemUI/customization/src/com/android/systemui/shared/quickaffordance/shared/model/KeyguardQuickAffordancePreviewConstants.kt
new file mode 100644
index 0000000..18e8a96
--- /dev/null
+++ b/packages/SystemUI/customization/src/com/android/systemui/shared/quickaffordance/shared/model/KeyguardQuickAffordancePreviewConstants.kt
@@ -0,0 +1,24 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+package com.android.systemui.shared.quickaffordance.shared.model
+
+object KeyguardQuickAffordancePreviewConstants {
+ const val MESSAGE_ID_SLOT_SELECTED = 1337
+ const val KEY_SLOT_ID = "slot_id"
+ const val KEY_INITIALLY_SELECTED_SLOT_ID = "initially_selected_slot_id"
+}
diff --git a/packages/SystemUI/res-keyguard/values-in/strings.xml b/packages/SystemUI/res-keyguard/values-in/strings.xml
index 20d32f1..12f9673 100644
--- a/packages/SystemUI/res-keyguard/values-in/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-in/strings.xml
@@ -84,7 +84,7 @@
<string name="kg_prompt_reason_device_admin" msgid="6961159596224055685">"Perangkat dikunci oleh admin"</string>
<string name="kg_prompt_reason_user_request" msgid="6015774877733717904">"Perangkat dikunci secara manual"</string>
<string name="kg_face_not_recognized" msgid="7903950626744419160">"Tidak dikenali"</string>
- <string name="kg_face_sensor_privacy_enabled" msgid="939511161763558512">"Untuk pakai Face Unlock, beri akses kamera di Setelan"</string>
+ <string name="kg_face_sensor_privacy_enabled" msgid="939511161763558512">"Untuk pakai Buka dengan Wajah, beri akses kamera di Setelan"</string>
<string name="kg_password_default_pin_message" msgid="1434544655827987873">"{count,plural, =1{Masukkan PIN SIM. Tersisa # percobaan lagi sebelum Anda harus menghubungi operator untuk membuka kunci perangkat.}other{Masukkan PIN SIM. Tersisa # percobaan lagi.}}"</string>
<string name="kg_password_default_puk_message" msgid="1025139786449741950">"{count,plural, =1{SIM kini dinonaktifkan. Masukkan kode PUK untuk melanjutkan. Tersisa # percobaan lagi sebelum SIM tidak dapat digunakan secara permanen. Hubungi operator untuk mengetahui detailnya.}other{SIM kini dinonaktifkan. Masukkan kode PUK untuk melanjutkan. Tersisa # percobaan lagi sebelum SIM tidak dapat digunakan secara permanen. Hubungi operator untuk mengetahui detailnya.}}"</string>
<string name="clock_title_default" msgid="6342735240617459864">"Default"</string>
diff --git a/packages/SystemUI/res-keyguard/values/strings.xml b/packages/SystemUI/res-keyguard/values/strings.xml
index da485a9..f522167 100644
--- a/packages/SystemUI/res-keyguard/values/strings.xml
+++ b/packages/SystemUI/res-keyguard/values/strings.xml
@@ -53,7 +53,7 @@
<string name="keyguard_plugged_in_charging_slowly"><xliff:g id="percentage">%s</xliff:g> • Charging slowly</string>
<!-- When the lock screen is showing and the phone plugged in, and the defend mode is triggered, say that charging is temporarily limited. -->
- <string name="keyguard_plugged_in_charging_limited"><xliff:g id="percentage">%s</xliff:g> • Charging paused to protect battery</string>
+ <string name="keyguard_plugged_in_charging_limited"><xliff:g id="percentage">%s</xliff:g> • Charging optimized to protect battery</string>
<!-- On the keyguard screen, when pattern lock is disabled, only tell them to press menu to unlock. This is shown in small font at the bottom. -->
<string name="keyguard_instructions_when_pattern_disabled">Press Menu to unlock.</string>
diff --git a/packages/SystemUI/res/drawable/keyguard_bottom_affordance_bg.xml b/packages/SystemUI/res/drawable/keyguard_bottom_affordance_bg.xml
index 41123c8..18fcebb 100644
--- a/packages/SystemUI/res/drawable/keyguard_bottom_affordance_bg.xml
+++ b/packages/SystemUI/res/drawable/keyguard_bottom_affordance_bg.xml
@@ -16,13 +16,53 @@
* limitations under the License.
*/
-->
-<shape
+<selector
xmlns:android="http://schemas.android.com/apk/res/android"
- xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"
- android:shape="rectangle">
- <solid android:color="?androidprv:attr/colorSurface"/>
- <size
- android:width="@dimen/keyguard_affordance_width"
- android:height="@dimen/keyguard_affordance_height"/>
- <corners android:radius="@dimen/keyguard_affordance_fixed_radius"/>
-</shape>
+ xmlns:androidprv="http://schemas.android.com/apk/prv/res/android">
+
+ <item android:state_selected="true">
+ <layer-list>
+ <item
+ android:left="3dp"
+ android:top="3dp"
+ android:right="3dp"
+ android:bottom="3dp">
+ <shape android:shape="oval">
+ <solid android:color="?androidprv:attr/colorSurface"/>
+ <size
+ android:width="@dimen/keyguard_affordance_width"
+ android:height="@dimen/keyguard_affordance_height"/>
+ </shape>
+ </item>
+
+ <item>
+ <shape android:shape="oval">
+ <stroke
+ android:color="@color/control_primary_text"
+ android:width="2dp"/>
+ <size
+ android:width="@dimen/keyguard_affordance_width"
+ android:height="@dimen/keyguard_affordance_height"/>
+ </shape>
+ </item>
+ </layer-list>
+ </item>
+
+ <item>
+ <layer-list>
+ <item
+ android:left="3dp"
+ android:top="3dp"
+ android:right="3dp"
+ android:bottom="3dp">
+ <shape android:shape="oval">
+ <solid android:color="?androidprv:attr/colorSurface"/>
+ <size
+ android:width="@dimen/keyguard_affordance_width"
+ android:height="@dimen/keyguard_affordance_height"/>
+ </shape>
+ </item>
+ </layer-list>
+ </item>
+
+</selector>
diff --git a/packages/SystemUI/res/values-af/strings.xml b/packages/SystemUI/res/values-af/strings.xml
index 16c615b..fe6aebc 100644
--- a/packages/SystemUI/res/values-af/strings.xml
+++ b/packages/SystemUI/res/values-af/strings.xml
@@ -872,8 +872,7 @@
<string name="media_output_dialog_button_stop_casting" msgid="6581379537930199189">"Hou op uitsaai"</string>
<string name="media_output_dialog_accessibility_title" msgid="4681741064190167888">"Beskikbare toestelle vir oudio-uitsette."</string>
<string name="media_output_dialog_accessibility_seekbar" msgid="5332843993805568978">"Volume"</string>
- <!-- no translation found for media_output_dialog_volume_percentage (1613984910585111798) -->
- <skip />
+ <string name="media_output_dialog_volume_percentage" msgid="1613984910585111798">"<xliff:g id="PERCENTAGE">%1$d</xliff:g>%%"</string>
<string name="media_output_first_broadcast_title" msgid="6292237789860753022">"Hoe uitsaai werk"</string>
<string name="media_output_broadcast" msgid="3555580945878071543">"Saai uit"</string>
<string name="media_output_first_notify_broadcast_message" msgid="6353857724136398494">"Mense in jou omtrek met versoenbare Bluetooth-toestelle kan na die media luister wat jy uitsaai"</string>
@@ -987,15 +986,13 @@
<string name="dream_overlay_status_bar_camera_mic_off" msgid="3199425257833773569">"Kamera en mikrofoon is af"</string>
<string name="dream_overlay_status_bar_notification_indicator" msgid="8091389255691081711">"{count,plural, =1{# kennisgewing}other{# kennisgewings}}"</string>
<string name="dream_overlay_weather_complication_desc" msgid="824503662089783824">"<xliff:g id="WEATHER_CONDITION">%1$s</xliff:g>, <xliff:g id="TEMPERATURE">%2$s</xliff:g>"</string>
- <!-- no translation found for note_task_button_label (8718616095800343136) -->
- <skip />
+ <string name="note_task_button_label" msgid="8718616095800343136">"Neem notas"</string>
<string name="broadcasting_description_is_broadcasting" msgid="765627502786404290">"Uitsaai"</string>
<string name="bt_le_audio_broadcast_dialog_title" msgid="3605428497924077811">"Hou op om <xliff:g id="APP_NAME">%1$s</xliff:g> uit te saai?"</string>
<string name="bt_le_audio_broadcast_dialog_sub_title" msgid="7889684551194225793">"As jy <xliff:g id="SWITCHAPP">%1$s</xliff:g> uitsaai of die uitvoer verander, sal jou huidige uitsending stop"</string>
<string name="bt_le_audio_broadcast_dialog_switch_app" msgid="6098768269397105733">"Saai <xliff:g id="SWITCHAPP">%1$s</xliff:g> uit"</string>
<string name="bt_le_audio_broadcast_dialog_different_output" msgid="7885102097302562674">"Verander uitvoer"</string>
<string name="bt_le_audio_broadcast_dialog_unknown_name" msgid="3791472237793443044">"Onbekend"</string>
- <string name="dream_date_complication_date_format" msgid="8191225366513860104">"EE. d MMM."</string>
<string name="dream_time_complication_12_hr_time_format" msgid="4691197486690291529">"h:mm"</string>
<string name="dream_time_complication_24_hr_time_format" msgid="6248280719733640813">"kk:mm"</string>
<string name="keyguard_affordance_enablement_dialog_action_template" msgid="8164857863036314664">"Maak <xliff:g id="APPNAME">%1$s</xliff:g> oop"</string>
diff --git a/packages/SystemUI/res/values-am/strings.xml b/packages/SystemUI/res/values-am/strings.xml
index 966701a..93d6379 100644
--- a/packages/SystemUI/res/values-am/strings.xml
+++ b/packages/SystemUI/res/values-am/strings.xml
@@ -998,7 +998,6 @@
<string name="bt_le_audio_broadcast_dialog_switch_app" msgid="6098768269397105733">"<xliff:g id="SWITCHAPP">%1$s</xliff:g> ያሰራጩ"</string>
<string name="bt_le_audio_broadcast_dialog_different_output" msgid="7885102097302562674">"ውፅዓትን ይቀይሩ"</string>
<string name="bt_le_audio_broadcast_dialog_unknown_name" msgid="3791472237793443044">"ያልታወቀ"</string>
- <string name="dream_date_complication_date_format" msgid="8191225366513860104">"EEE፣ MMM d"</string>
<string name="dream_time_complication_12_hr_time_format" msgid="4691197486690291529">"h:mm"</string>
<string name="dream_time_complication_24_hr_time_format" msgid="6248280719733640813">"kk:mm"</string>
<string name="keyguard_affordance_enablement_dialog_action_template" msgid="8164857863036314664">"<xliff:g id="APPNAME">%1$s</xliff:g> ይክፈቱ"</string>
diff --git a/packages/SystemUI/res/values-ar/strings.xml b/packages/SystemUI/res/values-ar/strings.xml
index eae26f0..dad19ab 100644
--- a/packages/SystemUI/res/values-ar/strings.xml
+++ b/packages/SystemUI/res/values-ar/strings.xml
@@ -872,8 +872,7 @@
<string name="media_output_dialog_button_stop_casting" msgid="6581379537930199189">"إيقاف البث"</string>
<string name="media_output_dialog_accessibility_title" msgid="4681741064190167888">"الأجهزة المتاحة لإخراج الصوت"</string>
<string name="media_output_dialog_accessibility_seekbar" msgid="5332843993805568978">"مستوى الصوت"</string>
- <!-- no translation found for media_output_dialog_volume_percentage (1613984910585111798) -->
- <skip />
+ <string name="media_output_dialog_volume_percentage" msgid="1613984910585111798">"%%<xliff:g id="PERCENTAGE">%1$d</xliff:g>"</string>
<string name="media_output_first_broadcast_title" msgid="6292237789860753022">"كيفية عمل البث"</string>
<string name="media_output_broadcast" msgid="3555580945878071543">"البث"</string>
<string name="media_output_first_notify_broadcast_message" msgid="6353857724136398494">"يمكن للأشخاص القريبين منك الذين لديهم أجهزة متوافقة تتضمّن بلوتوث الاستماع إلى الوسائط التي تبثها."</string>
@@ -987,15 +986,13 @@
<string name="dream_overlay_status_bar_camera_mic_off" msgid="3199425257833773569">"الكاميرا والميكروفون غير مفعّلين."</string>
<string name="dream_overlay_status_bar_notification_indicator" msgid="8091389255691081711">"{count,plural, =1{إشعار واحد}zero{# إشعار}two{إشعاران}few{# إشعارات}many{# إشعارًا}other{# إشعار}}"</string>
<string name="dream_overlay_weather_complication_desc" msgid="824503662089783824">"<xliff:g id="WEATHER_CONDITION">%1$s</xliff:g>، <xliff:g id="TEMPERATURE">%2$s</xliff:g>"</string>
- <!-- no translation found for note_task_button_label (8718616095800343136) -->
- <skip />
+ <string name="note_task_button_label" msgid="8718616095800343136">"تدوين الملاحظات"</string>
<string name="broadcasting_description_is_broadcasting" msgid="765627502786404290">"البث"</string>
<string name="bt_le_audio_broadcast_dialog_title" msgid="3605428497924077811">"هل تريد إيقاف بث تطبيق <xliff:g id="APP_NAME">%1$s</xliff:g>؟"</string>
<string name="bt_le_audio_broadcast_dialog_sub_title" msgid="7889684551194225793">"إذا أجريت بث تطبيق <xliff:g id="SWITCHAPP">%1$s</xliff:g> أو غيَّرت جهاز الإخراج، سيتوقَف البث الحالي."</string>
<string name="bt_le_audio_broadcast_dialog_switch_app" msgid="6098768269397105733">"بث تطبيق <xliff:g id="SWITCHAPP">%1$s</xliff:g>"</string>
<string name="bt_le_audio_broadcast_dialog_different_output" msgid="7885102097302562674">"تغيير جهاز الإخراج"</string>
<string name="bt_le_audio_broadcast_dialog_unknown_name" msgid="3791472237793443044">"غير معروف"</string>
- <string name="dream_date_complication_date_format" msgid="8191225366513860104">"EEE، d MMM"</string>
<string name="dream_time_complication_12_hr_time_format" msgid="4691197486690291529">"h:mm"</string>
<string name="dream_time_complication_24_hr_time_format" msgid="6248280719733640813">"kk:mm"</string>
<string name="keyguard_affordance_enablement_dialog_action_template" msgid="8164857863036314664">"فتح \"<xliff:g id="APPNAME">%1$s</xliff:g>\""</string>
diff --git a/packages/SystemUI/res/values-as/strings.xml b/packages/SystemUI/res/values-as/strings.xml
index 6fc82be..9c8aed6 100644
--- a/packages/SystemUI/res/values-as/strings.xml
+++ b/packages/SystemUI/res/values-as/strings.xml
@@ -872,8 +872,7 @@
<string name="media_output_dialog_button_stop_casting" msgid="6581379537930199189">"কাষ্ট বন্ধ কৰক"</string>
<string name="media_output_dialog_accessibility_title" msgid="4681741064190167888">"অডিঅ\' আউটপুটৰ বাবে উপলব্ধ ডিভাইচ।"</string>
<string name="media_output_dialog_accessibility_seekbar" msgid="5332843993805568978">"ভলিউম"</string>
- <!-- no translation found for media_output_dialog_volume_percentage (1613984910585111798) -->
- <skip />
+ <string name="media_output_dialog_volume_percentage" msgid="1613984910585111798">"<xliff:g id="PERCENTAGE">%1$d</xliff:g>%%"</string>
<string name="media_output_first_broadcast_title" msgid="6292237789860753022">"সম্প্ৰচাৰ কৰাটোৱে কেনেকৈ কাম কৰে"</string>
<string name="media_output_broadcast" msgid="3555580945878071543">"সম্প্ৰচাৰ"</string>
<string name="media_output_first_notify_broadcast_message" msgid="6353857724136398494">"সমিল ব্লুটুথ ডিভাইচৰ সৈতে আপোনাৰ নিকটৱৰ্তী স্থানত থকা লোকসকলে আপুনি সম্প্ৰচাৰ কৰা মিডিয়াটো শুনিব পাৰে"</string>
@@ -987,15 +986,13 @@
<string name="dream_overlay_status_bar_camera_mic_off" msgid="3199425257833773569">"কেমেৰা আৰু মাইক অফ হৈ আছে"</string>
<string name="dream_overlay_status_bar_notification_indicator" msgid="8091389255691081711">"{count,plural, =1{# টা জাননী}one{# টা জাননী}other{# টা জাননী}}"</string>
<string name="dream_overlay_weather_complication_desc" msgid="824503662089783824">"<xliff:g id="WEATHER_CONDITION">%1$s</xliff:g>, <xliff:g id="TEMPERATURE">%2$s</xliff:g>"</string>
- <!-- no translation found for note_task_button_label (8718616095800343136) -->
- <skip />
+ <string name="note_task_button_label" msgid="8718616095800343136">"টোকাগ্ৰহণ"</string>
<string name="broadcasting_description_is_broadcasting" msgid="765627502786404290">"সম্প্ৰচাৰণ"</string>
<string name="bt_le_audio_broadcast_dialog_title" msgid="3605428497924077811">"<xliff:g id="APP_NAME">%1$s</xliff:g>ৰ সম্প্ৰচাৰ কৰা বন্ধ কৰিবনে?"</string>
<string name="bt_le_audio_broadcast_dialog_sub_title" msgid="7889684551194225793">"যদি আপুনি <xliff:g id="SWITCHAPP">%1$s</xliff:g>ৰ সম্প্ৰচাৰ কৰে অথবা আউটপুট সলনি কৰে, তেন্তে, আপোনাৰ বৰ্তমানৰ সম্প্ৰচাৰ বন্ধ হৈ যাব"</string>
<string name="bt_le_audio_broadcast_dialog_switch_app" msgid="6098768269397105733">"<xliff:g id="SWITCHAPP">%1$s</xliff:g> সম্প্ৰচাৰ কৰক"</string>
<string name="bt_le_audio_broadcast_dialog_different_output" msgid="7885102097302562674">"আউটপুট সলনি কৰক"</string>
<string name="bt_le_audio_broadcast_dialog_unknown_name" msgid="3791472237793443044">"অজ্ঞাত"</string>
- <string name="dream_date_complication_date_format" msgid="8191225366513860104">"EEE, MMM d"</string>
<string name="dream_time_complication_12_hr_time_format" msgid="4691197486690291529">"h:mm"</string>
<string name="dream_time_complication_24_hr_time_format" msgid="6248280719733640813">"kk:mm"</string>
<string name="keyguard_affordance_enablement_dialog_action_template" msgid="8164857863036314664">"<xliff:g id="APPNAME">%1$s</xliff:g> খোলক"</string>
diff --git a/packages/SystemUI/res/values-az/strings.xml b/packages/SystemUI/res/values-az/strings.xml
index e4e031a..8870b61 100644
--- a/packages/SystemUI/res/values-az/strings.xml
+++ b/packages/SystemUI/res/values-az/strings.xml
@@ -872,8 +872,7 @@
<string name="media_output_dialog_button_stop_casting" msgid="6581379537930199189">"Yayımı dayandırın"</string>
<string name="media_output_dialog_accessibility_title" msgid="4681741064190167888">"Audio çıxış üçün əlçatan cihazlar."</string>
<string name="media_output_dialog_accessibility_seekbar" msgid="5332843993805568978">"Səs"</string>
- <!-- no translation found for media_output_dialog_volume_percentage (1613984910585111798) -->
- <skip />
+ <string name="media_output_dialog_volume_percentage" msgid="1613984910585111798">"<xliff:g id="PERCENTAGE">%1$d</xliff:g>%%"</string>
<string name="media_output_first_broadcast_title" msgid="6292237789860753022">"Yayım necə işləyir"</string>
<string name="media_output_broadcast" msgid="3555580945878071543">"Yayım"</string>
<string name="media_output_first_notify_broadcast_message" msgid="6353857724136398494">"Uyğun Bluetooth cihazları olan yaxınlığınızdakı insanlar yayımladığınız medianı dinləyə bilər"</string>
@@ -987,15 +986,13 @@
<string name="dream_overlay_status_bar_camera_mic_off" msgid="3199425257833773569">"Kamera və mikrofon deaktivdir"</string>
<string name="dream_overlay_status_bar_notification_indicator" msgid="8091389255691081711">"{count,plural, =1{# bildiriş}other{# bildiriş}}"</string>
<string name="dream_overlay_weather_complication_desc" msgid="824503662089783824">"<xliff:g id="WEATHER_CONDITION">%1$s</xliff:g>, <xliff:g id="TEMPERATURE">%2$s</xliff:g>"</string>
- <!-- no translation found for note_task_button_label (8718616095800343136) -->
- <skip />
+ <string name="note_task_button_label" msgid="8718616095800343136">"Qeyd tutma"</string>
<string name="broadcasting_description_is_broadcasting" msgid="765627502786404290">"Yayım"</string>
<string name="bt_le_audio_broadcast_dialog_title" msgid="3605428497924077811">"<xliff:g id="APP_NAME">%1$s</xliff:g> tətbiqinin yayımlanması dayandırılsın?"</string>
<string name="bt_le_audio_broadcast_dialog_sub_title" msgid="7889684551194225793">"<xliff:g id="SWITCHAPP">%1$s</xliff:g> tətbiqini yayımlasanız və ya nəticəni dəyişsəniz, cari yayımınız dayandırılacaq"</string>
<string name="bt_le_audio_broadcast_dialog_switch_app" msgid="6098768269397105733">"<xliff:g id="SWITCHAPP">%1$s</xliff:g> tətbiqini yayımlayın"</string>
<string name="bt_le_audio_broadcast_dialog_different_output" msgid="7885102097302562674">"Nəticəni dəyişdirin"</string>
<string name="bt_le_audio_broadcast_dialog_unknown_name" msgid="3791472237793443044">"Naməlum"</string>
- <string name="dream_date_complication_date_format" msgid="8191225366513860104">"HHH, AAA g"</string>
<string name="dream_time_complication_12_hr_time_format" msgid="4691197486690291529">"s:dd"</string>
<string name="dream_time_complication_24_hr_time_format" msgid="6248280719733640813">"ss:dd"</string>
<string name="keyguard_affordance_enablement_dialog_action_template" msgid="8164857863036314664">"<xliff:g id="APPNAME">%1$s</xliff:g> tətbiqini açın"</string>
@@ -1011,8 +1008,6 @@
<string name="rear_display_unfold_bottom_sheet_title" msgid="2137403802960396357">"Daha yaxşı selfi üçün ön displeyə çevrilsin?"</string>
<string name="rear_display_bottom_sheet_description" msgid="1852662982816810352">"Daha yüksək ayırdetmə dəqiqliyi ilə daha geniş şəkil üçün arxaya baxan kameradan istifadə edin."</string>
<string name="rear_display_bottom_sheet_warning" msgid="800995919558238930"><b>"✱ Bu ekran deaktiv ediləcək"</b></string>
- <!-- no translation found for rear_display_accessibility_folded_animation (1538121649587978179) -->
- <skip />
- <!-- no translation found for rear_display_accessibility_unfolded_animation (1946153682258289040) -->
- <skip />
+ <string name="rear_display_accessibility_folded_animation" msgid="1538121649587978179">"Qatlana bilən cihaz açılır"</string>
+ <string name="rear_display_accessibility_unfolded_animation" msgid="1946153682258289040">"Qatlana bilən cihaz fırladılır"</string>
</resources>
diff --git a/packages/SystemUI/res/values-b+sr+Latn/strings.xml b/packages/SystemUI/res/values-b+sr+Latn/strings.xml
index bd964f95..35cbed9 100644
--- a/packages/SystemUI/res/values-b+sr+Latn/strings.xml
+++ b/packages/SystemUI/res/values-b+sr+Latn/strings.xml
@@ -993,7 +993,6 @@
<string name="bt_le_audio_broadcast_dialog_switch_app" msgid="6098768269397105733">"Emitujte aplikaciju <xliff:g id="SWITCHAPP">%1$s</xliff:g>"</string>
<string name="bt_le_audio_broadcast_dialog_different_output" msgid="7885102097302562674">"Promenite izlaz"</string>
<string name="bt_le_audio_broadcast_dialog_unknown_name" msgid="3791472237793443044">"Nepoznato"</string>
- <string name="dream_date_complication_date_format" msgid="8191225366513860104">"DDD, d. MMM"</string>
<string name="dream_time_complication_12_hr_time_format" msgid="4691197486690291529">"s:min"</string>
<string name="dream_time_complication_24_hr_time_format" msgid="6248280719733640813">"č:min"</string>
<string name="keyguard_affordance_enablement_dialog_action_template" msgid="8164857863036314664">"Otvorite: <xliff:g id="APPNAME">%1$s</xliff:g>"</string>
diff --git a/packages/SystemUI/res/values-be/strings.xml b/packages/SystemUI/res/values-be/strings.xml
index 31c9bcc..c653c7a 100644
--- a/packages/SystemUI/res/values-be/strings.xml
+++ b/packages/SystemUI/res/values-be/strings.xml
@@ -723,7 +723,7 @@
<string name="instant_apps_title" msgid="8942706782103036910">"Праграма \"<xliff:g id="APP">%1$s</xliff:g>\" запушчана"</string>
<string name="instant_apps_message" msgid="6112428971833011754">"Праграма адкрыта без усталёўкі."</string>
<string name="instant_apps_message_with_help" msgid="1816952263531203932">"Праграма адкрыта без усталёўкі. Націсніце, каб даведацца больш."</string>
- <string name="app_info" msgid="5153758994129963243">"Звесткі пра праграму"</string>
+ <string name="app_info" msgid="5153758994129963243">"Звесткі аб праграме"</string>
<string name="go_to_web" msgid="636673528981366511">"Перайсці ў браўзер"</string>
<string name="mobile_data" msgid="4564407557775397216">"Маб. перадача даных"</string>
<string name="mobile_data_text_format" msgid="6806501540022589786">"<xliff:g id="ID_1">%1$s</xliff:g> — <xliff:g id="ID_2">%2$s</xliff:g>"</string>
@@ -872,8 +872,7 @@
<string name="media_output_dialog_button_stop_casting" msgid="6581379537930199189">"Спыніць трансляцыю"</string>
<string name="media_output_dialog_accessibility_title" msgid="4681741064190167888">"Даступныя прылады для вываду аўдыя."</string>
<string name="media_output_dialog_accessibility_seekbar" msgid="5332843993805568978">"Гучнасць"</string>
- <!-- no translation found for media_output_dialog_volume_percentage (1613984910585111798) -->
- <skip />
+ <string name="media_output_dialog_volume_percentage" msgid="1613984910585111798">"<xliff:g id="PERCENTAGE">%1$d</xliff:g>%%"</string>
<string name="media_output_first_broadcast_title" msgid="6292237789860753022">"Як адбываецца трансляцыя"</string>
<string name="media_output_broadcast" msgid="3555580945878071543">"Трансляцыя"</string>
<string name="media_output_first_notify_broadcast_message" msgid="6353857724136398494">"Людзі паблізу, у якіх ёсць прылады з Bluetooth, змогуць праслухваць мультымедыйнае змесціва, якое вы трансліруеце"</string>
@@ -987,15 +986,13 @@
<string name="dream_overlay_status_bar_camera_mic_off" msgid="3199425257833773569">"Камера і мікрафон выключаны"</string>
<string name="dream_overlay_status_bar_notification_indicator" msgid="8091389255691081711">"{count,plural, =1{# апавяшчэнне}one{# апавяшчэнне}few{# апавяшчэнні}many{# апавяшчэнняў}other{# апавяшчэння}}"</string>
<string name="dream_overlay_weather_complication_desc" msgid="824503662089783824">"<xliff:g id="WEATHER_CONDITION">%1$s</xliff:g>, <xliff:g id="TEMPERATURE">%2$s</xliff:g>"</string>
- <!-- no translation found for note_task_button_label (8718616095800343136) -->
- <skip />
+ <string name="note_task_button_label" msgid="8718616095800343136">"Стварэнне нататак"</string>
<string name="broadcasting_description_is_broadcasting" msgid="765627502786404290">"Перадача даных"</string>
<string name="bt_le_audio_broadcast_dialog_title" msgid="3605428497924077811">"Спыніць трансляцыю праграмы \"<xliff:g id="APP_NAME">%1$s</xliff:g>\"?"</string>
<string name="bt_le_audio_broadcast_dialog_sub_title" msgid="7889684551194225793">"Пры пераключэнні на праграму \"<xliff:g id="SWITCHAPP">%1$s</xliff:g>\" ці змяненні вываду бягучая трансляцыя спыняецца"</string>
<string name="bt_le_audio_broadcast_dialog_switch_app" msgid="6098768269397105733">"Трансляцыя праграмы \"<xliff:g id="SWITCHAPP">%1$s</xliff:g>\""</string>
<string name="bt_le_audio_broadcast_dialog_different_output" msgid="7885102097302562674">"Змяненне вываду"</string>
<string name="bt_le_audio_broadcast_dialog_unknown_name" msgid="3791472237793443044">"Невядома"</string>
- <string name="dream_date_complication_date_format" msgid="8191225366513860104">"EEE, d MMM"</string>
<string name="dream_time_complication_12_hr_time_format" msgid="4691197486690291529">"h:mm"</string>
<string name="dream_time_complication_24_hr_time_format" msgid="6248280719733640813">"kk:mm"</string>
<string name="keyguard_affordance_enablement_dialog_action_template" msgid="8164857863036314664">"Адкрыць праграму \"<xliff:g id="APPNAME">%1$s</xliff:g>\""</string>
diff --git a/packages/SystemUI/res/values-bg/strings.xml b/packages/SystemUI/res/values-bg/strings.xml
index c2860e9..cd2ad4d 100644
--- a/packages/SystemUI/res/values-bg/strings.xml
+++ b/packages/SystemUI/res/values-bg/strings.xml
@@ -872,8 +872,7 @@
<string name="media_output_dialog_button_stop_casting" msgid="6581379537930199189">"Спиране на предаването"</string>
<string name="media_output_dialog_accessibility_title" msgid="4681741064190167888">"Налични устройства за аудиоизход."</string>
<string name="media_output_dialog_accessibility_seekbar" msgid="5332843993805568978">"Сила на звука"</string>
- <!-- no translation found for media_output_dialog_volume_percentage (1613984910585111798) -->
- <skip />
+ <string name="media_output_dialog_volume_percentage" msgid="1613984910585111798">"<xliff:g id="PERCENTAGE">%1$d</xliff:g>%%"</string>
<string name="media_output_first_broadcast_title" msgid="6292237789860753022">"Как работи предаването"</string>
<string name="media_output_broadcast" msgid="3555580945878071543">"Предаване"</string>
<string name="media_output_first_notify_broadcast_message" msgid="6353857724136398494">"Хората в близост със съвместими устройства с Bluetooth могат да слушат мултимедията, която предавате"</string>
@@ -987,15 +986,13 @@
<string name="dream_overlay_status_bar_camera_mic_off" msgid="3199425257833773569">"Камерата и микрофонът са изключени"</string>
<string name="dream_overlay_status_bar_notification_indicator" msgid="8091389255691081711">"{count,plural, =1{# известие}other{# известия}}"</string>
<string name="dream_overlay_weather_complication_desc" msgid="824503662089783824">"<xliff:g id="WEATHER_CONDITION">%1$s</xliff:g>, <xliff:g id="TEMPERATURE">%2$s</xliff:g>"</string>
- <!-- no translation found for note_task_button_label (8718616095800343136) -->
- <skip />
+ <string name="note_task_button_label" msgid="8718616095800343136">"Водене на бележки"</string>
<string name="broadcasting_description_is_broadcasting" msgid="765627502786404290">"Излъчване"</string>
<string name="bt_le_audio_broadcast_dialog_title" msgid="3605428497924077811">"Да се спре ли предаването на <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string>
<string name="bt_le_audio_broadcast_dialog_sub_title" msgid="7889684551194225793">"Ако предавате <xliff:g id="SWITCHAPP">%1$s</xliff:g> или промените изхода, текущото ви предаване ще бъде прекратено"</string>
<string name="bt_le_audio_broadcast_dialog_switch_app" msgid="6098768269397105733">"Предаване на <xliff:g id="SWITCHAPP">%1$s</xliff:g>"</string>
<string name="bt_le_audio_broadcast_dialog_different_output" msgid="7885102097302562674">"Промяна на изхода"</string>
<string name="bt_le_audio_broadcast_dialog_unknown_name" msgid="3791472237793443044">"Неизвестно"</string>
- <string name="dream_date_complication_date_format" msgid="8191225366513860104">"EEE, d MMM"</string>
<string name="dream_time_complication_12_hr_time_format" msgid="4691197486690291529">"h:mm"</string>
<string name="dream_time_complication_24_hr_time_format" msgid="6248280719733640813">"kk:mm"</string>
<string name="keyguard_affordance_enablement_dialog_action_template" msgid="8164857863036314664">"Отваряне на <xliff:g id="APPNAME">%1$s</xliff:g>"</string>
diff --git a/packages/SystemUI/res/values-bn/strings.xml b/packages/SystemUI/res/values-bn/strings.xml
index 42fc489..4d5e2a3 100644
--- a/packages/SystemUI/res/values-bn/strings.xml
+++ b/packages/SystemUI/res/values-bn/strings.xml
@@ -993,7 +993,6 @@
<string name="bt_le_audio_broadcast_dialog_switch_app" msgid="6098768269397105733">"<xliff:g id="SWITCHAPP">%1$s</xliff:g> সম্প্রচার করুন"</string>
<string name="bt_le_audio_broadcast_dialog_different_output" msgid="7885102097302562674">"আউটপুট পরিবর্তন করুন"</string>
<string name="bt_le_audio_broadcast_dialog_unknown_name" msgid="3791472237793443044">"অজানা"</string>
- <string name="dream_date_complication_date_format" msgid="8191225366513860104">"EEE, MMM d"</string>
<string name="dream_time_complication_12_hr_time_format" msgid="4691197486690291529">"h:mm"</string>
<string name="dream_time_complication_24_hr_time_format" msgid="6248280719733640813">"kk:mm"</string>
<string name="keyguard_affordance_enablement_dialog_action_template" msgid="8164857863036314664">"<xliff:g id="APPNAME">%1$s</xliff:g> খুলুন"</string>
diff --git a/packages/SystemUI/res/values-bs/strings.xml b/packages/SystemUI/res/values-bs/strings.xml
index 62f0c5f..cb4b2a5 100644
--- a/packages/SystemUI/res/values-bs/strings.xml
+++ b/packages/SystemUI/res/values-bs/strings.xml
@@ -986,14 +986,13 @@
<string name="dream_overlay_status_bar_camera_mic_off" msgid="3199425257833773569">"Kamera i mikrofon su isključeni"</string>
<string name="dream_overlay_status_bar_notification_indicator" msgid="8091389255691081711">"{count,plural, =1{# obavještenje}one{# obavještenje}few{# obavještenja}other{# obavještenja}}"</string>
<string name="dream_overlay_weather_complication_desc" msgid="824503662089783824">"<xliff:g id="WEATHER_CONDITION">%1$s</xliff:g>, <xliff:g id="TEMPERATURE">%2$s</xliff:g>"</string>
- <string name="note_task_button_label" msgid="8718616095800343136">"Pisanje bilježaka"</string>
+ <string name="note_task_button_label" msgid="8718616095800343136">"Pisanje bilješki"</string>
<string name="broadcasting_description_is_broadcasting" msgid="765627502786404290">"Emitiranje"</string>
<string name="bt_le_audio_broadcast_dialog_title" msgid="3605428497924077811">"Zaustaviti emitiranje aplikacije <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string>
<string name="bt_le_audio_broadcast_dialog_sub_title" msgid="7889684551194225793">"Ako emitirate aplikaciju <xliff:g id="SWITCHAPP">%1$s</xliff:g> ili promijenite izlaz, trenutno emitiranje će se zaustaviti"</string>
<string name="bt_le_audio_broadcast_dialog_switch_app" msgid="6098768269397105733">"Emitiraj aplikaciju <xliff:g id="SWITCHAPP">%1$s</xliff:g>"</string>
<string name="bt_le_audio_broadcast_dialog_different_output" msgid="7885102097302562674">"Promijeni izlaz"</string>
<string name="bt_le_audio_broadcast_dialog_unknown_name" msgid="3791472237793443044">"Nepoznato"</string>
- <string name="dream_date_complication_date_format" msgid="8191225366513860104">"DDD, MMM d"</string>
<string name="dream_time_complication_12_hr_time_format" msgid="4691197486690291529">"h:mm"</string>
<string name="dream_time_complication_24_hr_time_format" msgid="6248280719733640813">"kk:mm"</string>
<string name="keyguard_affordance_enablement_dialog_action_template" msgid="8164857863036314664">"Otvori aplikaciju <xliff:g id="APPNAME">%1$s</xliff:g>"</string>
@@ -1009,8 +1008,6 @@
<string name="rear_display_unfold_bottom_sheet_title" msgid="2137403802960396357">"Obrnuti na prednji ekran radi boljeg selfija?"</string>
<string name="rear_display_bottom_sheet_description" msgid="1852662982816810352">"Koristite stražnju kameru za širu fotografiju veće rezolucije."</string>
<string name="rear_display_bottom_sheet_warning" msgid="800995919558238930"><b>"✱ Ekran će se isključiti"</b></string>
- <!-- no translation found for rear_display_accessibility_folded_animation (1538121649587978179) -->
- <skip />
- <!-- no translation found for rear_display_accessibility_unfolded_animation (1946153682258289040) -->
- <skip />
+ <string name="rear_display_accessibility_folded_animation" msgid="1538121649587978179">"Rasklopljen sklopivi uređaj"</string>
+ <string name="rear_display_accessibility_unfolded_animation" msgid="1946153682258289040">"Okretanje sklopivog uređaja sa svih strana"</string>
</resources>
diff --git a/packages/SystemUI/res/values-ca/strings.xml b/packages/SystemUI/res/values-ca/strings.xml
index 8f591bc..68fa2dc 100644
--- a/packages/SystemUI/res/values-ca/strings.xml
+++ b/packages/SystemUI/res/values-ca/strings.xml
@@ -872,8 +872,7 @@
<string name="media_output_dialog_button_stop_casting" msgid="6581379537930199189">"Atura l\'emissió"</string>
<string name="media_output_dialog_accessibility_title" msgid="4681741064190167888">"Dispositius disponibles per a la sortida d\'àudio."</string>
<string name="media_output_dialog_accessibility_seekbar" msgid="5332843993805568978">"Volum"</string>
- <!-- no translation found for media_output_dialog_volume_percentage (1613984910585111798) -->
- <skip />
+ <string name="media_output_dialog_volume_percentage" msgid="1613984910585111798">"<xliff:g id="PERCENTAGE">%1$d</xliff:g>%%"</string>
<string name="media_output_first_broadcast_title" msgid="6292237789860753022">"Com funciona l\'emissió"</string>
<string name="media_output_broadcast" msgid="3555580945878071543">"Emet"</string>
<string name="media_output_first_notify_broadcast_message" msgid="6353857724136398494">"Les persones properes amb dispositius Bluetooth compatibles poden escoltar el contingut multimèdia que emets"</string>
@@ -987,15 +986,13 @@
<string name="dream_overlay_status_bar_camera_mic_off" msgid="3199425257833773569">"Càmera i micròfon desactivats"</string>
<string name="dream_overlay_status_bar_notification_indicator" msgid="8091389255691081711">"{count,plural, =1{# notificació}many{# notifications}other{# notificacions}}"</string>
<string name="dream_overlay_weather_complication_desc" msgid="824503662089783824">"<xliff:g id="WEATHER_CONDITION">%1$s</xliff:g>, <xliff:g id="TEMPERATURE">%2$s</xliff:g>"</string>
- <!-- no translation found for note_task_button_label (8718616095800343136) -->
- <skip />
+ <string name="note_task_button_label" msgid="8718616095800343136">"Presa de notes"</string>
<string name="broadcasting_description_is_broadcasting" msgid="765627502786404290">"S\'està emetent"</string>
<string name="bt_le_audio_broadcast_dialog_title" msgid="3605428497924077811">"Vols deixar d\'emetre <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string>
<string name="bt_le_audio_broadcast_dialog_sub_title" msgid="7889684551194225793">"Si emets <xliff:g id="SWITCHAPP">%1$s</xliff:g> o canvies la sortida, l\'emissió actual s\'aturarà"</string>
<string name="bt_le_audio_broadcast_dialog_switch_app" msgid="6098768269397105733">"Emet <xliff:g id="SWITCHAPP">%1$s</xliff:g>"</string>
<string name="bt_le_audio_broadcast_dialog_different_output" msgid="7885102097302562674">"Canvia la sortida"</string>
<string name="bt_le_audio_broadcast_dialog_unknown_name" msgid="3791472237793443044">"Desconeguda"</string>
- <string name="dream_date_complication_date_format" msgid="8191225366513860104">"EEE, d MMM"</string>
<string name="dream_time_complication_12_hr_time_format" msgid="4691197486690291529">"hh:mm"</string>
<string name="dream_time_complication_24_hr_time_format" msgid="6248280719733640813">"kk:mm"</string>
<string name="keyguard_affordance_enablement_dialog_action_template" msgid="8164857863036314664">"Obre <xliff:g id="APPNAME">%1$s</xliff:g>"</string>
diff --git a/packages/SystemUI/res/values-cs/strings.xml b/packages/SystemUI/res/values-cs/strings.xml
index c417d44..6eb360b 100644
--- a/packages/SystemUI/res/values-cs/strings.xml
+++ b/packages/SystemUI/res/values-cs/strings.xml
@@ -872,8 +872,7 @@
<string name="media_output_dialog_button_stop_casting" msgid="6581379537930199189">"Zastavit odesílání"</string>
<string name="media_output_dialog_accessibility_title" msgid="4681741064190167888">"Dostupná zařízení pro zvukový výstup."</string>
<string name="media_output_dialog_accessibility_seekbar" msgid="5332843993805568978">"Hlasitost"</string>
- <!-- no translation found for media_output_dialog_volume_percentage (1613984910585111798) -->
- <skip />
+ <string name="media_output_dialog_volume_percentage" msgid="1613984910585111798">"<xliff:g id="PERCENTAGE">%1$d</xliff:g> %%"</string>
<string name="media_output_first_broadcast_title" msgid="6292237789860753022">"Jak vysílání funguje"</string>
<string name="media_output_broadcast" msgid="3555580945878071543">"Vysílání"</string>
<string name="media_output_first_notify_broadcast_message" msgid="6353857724136398494">"Lidé ve vašem okolí s kompatibilními zařízeními Bluetooth mohou poslouchat média, která vysíláte"</string>
@@ -987,15 +986,13 @@
<string name="dream_overlay_status_bar_camera_mic_off" msgid="3199425257833773569">"Fotoaparát a mikrofon jsou vypnuté"</string>
<string name="dream_overlay_status_bar_notification_indicator" msgid="8091389255691081711">"{count,plural, =1{# oznámení}few{# oznámení}many{# oznámení}other{# oznámení}}"</string>
<string name="dream_overlay_weather_complication_desc" msgid="824503662089783824">"<xliff:g id="WEATHER_CONDITION">%1$s</xliff:g> <xliff:g id="TEMPERATURE">%2$s</xliff:g>"</string>
- <!-- no translation found for note_task_button_label (8718616095800343136) -->
- <skip />
+ <string name="note_task_button_label" msgid="8718616095800343136">"Poznámky"</string>
<string name="broadcasting_description_is_broadcasting" msgid="765627502786404290">"Vysílání"</string>
<string name="bt_le_audio_broadcast_dialog_title" msgid="3605428497924077811">"Zastavit vysílání v aplikaci <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string>
<string name="bt_le_audio_broadcast_dialog_sub_title" msgid="7889684551194225793">"Pokud budete vysílat v aplikaci <xliff:g id="SWITCHAPP">%1$s</xliff:g> nebo změníte výstup, aktuální vysílání se zastaví"</string>
<string name="bt_le_audio_broadcast_dialog_switch_app" msgid="6098768269397105733">"Vysílat v aplikaci <xliff:g id="SWITCHAPP">%1$s</xliff:g>"</string>
<string name="bt_le_audio_broadcast_dialog_different_output" msgid="7885102097302562674">"Změna výstupu"</string>
<string name="bt_le_audio_broadcast_dialog_unknown_name" msgid="3791472237793443044">"Neznámé"</string>
- <string name="dream_date_complication_date_format" msgid="8191225366513860104">"EEE d. MMMM"</string>
<string name="dream_time_complication_12_hr_time_format" msgid="4691197486690291529">"h:mm"</string>
<string name="dream_time_complication_24_hr_time_format" msgid="6248280719733640813">"H:mm"</string>
<string name="keyguard_affordance_enablement_dialog_action_template" msgid="8164857863036314664">"Otevřít <xliff:g id="APPNAME">%1$s</xliff:g>"</string>
diff --git a/packages/SystemUI/res/values-da/strings.xml b/packages/SystemUI/res/values-da/strings.xml
index d4277a7..3ba9087 100644
--- a/packages/SystemUI/res/values-da/strings.xml
+++ b/packages/SystemUI/res/values-da/strings.xml
@@ -993,7 +993,6 @@
<string name="bt_le_audio_broadcast_dialog_switch_app" msgid="6098768269397105733">"Udsend <xliff:g id="SWITCHAPP">%1$s</xliff:g>"</string>
<string name="bt_le_audio_broadcast_dialog_different_output" msgid="7885102097302562674">"Skift output"</string>
<string name="bt_le_audio_broadcast_dialog_unknown_name" msgid="3791472237793443044">"Ukendt"</string>
- <string name="dream_date_complication_date_format" msgid="8191225366513860104">"EEE d. MMM"</string>
<string name="dream_time_complication_12_hr_time_format" msgid="4691197486690291529">"tt.mm"</string>
<string name="dream_time_complication_24_hr_time_format" msgid="6248280719733640813">"kk.mm"</string>
<string name="keyguard_affordance_enablement_dialog_action_template" msgid="8164857863036314664">"Åbn <xliff:g id="APPNAME">%1$s</xliff:g>"</string>
diff --git a/packages/SystemUI/res/values-de/strings.xml b/packages/SystemUI/res/values-de/strings.xml
index d1c88fb..c2d868c 100644
--- a/packages/SystemUI/res/values-de/strings.xml
+++ b/packages/SystemUI/res/values-de/strings.xml
@@ -872,8 +872,7 @@
<string name="media_output_dialog_button_stop_casting" msgid="6581379537930199189">"Streaming beenden"</string>
<string name="media_output_dialog_accessibility_title" msgid="4681741064190167888">"Für die Audioausgabe verfügbare Geräte."</string>
<string name="media_output_dialog_accessibility_seekbar" msgid="5332843993805568978">"Lautstärke"</string>
- <!-- no translation found for media_output_dialog_volume_percentage (1613984910585111798) -->
- <skip />
+ <string name="media_output_dialog_volume_percentage" msgid="1613984910585111798">"<xliff:g id="PERCENTAGE">%1$d</xliff:g> %%"</string>
<string name="media_output_first_broadcast_title" msgid="6292237789860753022">"Funktionsweise von Nachrichten an alle"</string>
<string name="media_output_broadcast" msgid="3555580945878071543">"Nachricht an alle"</string>
<string name="media_output_first_notify_broadcast_message" msgid="6353857724136398494">"Personen, die in der Nähe sind und kompatible Bluetooth-Geräten haben, können sich die Medien anhören, die du per Nachricht an alle sendest"</string>
@@ -987,15 +986,13 @@
<string name="dream_overlay_status_bar_camera_mic_off" msgid="3199425257833773569">"Kamera und Mikrofon ausgeschaltet"</string>
<string name="dream_overlay_status_bar_notification_indicator" msgid="8091389255691081711">"{count,plural, =1{# Benachrichtigung}other{# Benachrichtigungen}}"</string>
<string name="dream_overlay_weather_complication_desc" msgid="824503662089783824">"<xliff:g id="WEATHER_CONDITION">%1$s</xliff:g>, <xliff:g id="TEMPERATURE">%2$s</xliff:g>"</string>
- <!-- no translation found for note_task_button_label (8718616095800343136) -->
- <skip />
+ <string name="note_task_button_label" msgid="8718616095800343136">"Notizen machen"</string>
<string name="broadcasting_description_is_broadcasting" msgid="765627502786404290">"Übertragung läuft"</string>
<string name="bt_le_audio_broadcast_dialog_title" msgid="3605428497924077811">"<xliff:g id="APP_NAME">%1$s</xliff:g> nicht mehr streamen?"</string>
<string name="bt_le_audio_broadcast_dialog_sub_title" msgid="7889684551194225793">"Wenn du <xliff:g id="SWITCHAPP">%1$s</xliff:g> streamst oder die Ausgabe änderst, wird dein aktueller Stream beendet"</string>
<string name="bt_le_audio_broadcast_dialog_switch_app" msgid="6098768269397105733">"<xliff:g id="SWITCHAPP">%1$s</xliff:g> streamen"</string>
<string name="bt_le_audio_broadcast_dialog_different_output" msgid="7885102097302562674">"Ausgabe ändern"</string>
<string name="bt_le_audio_broadcast_dialog_unknown_name" msgid="3791472237793443044">"Unbekannt"</string>
- <string name="dream_date_complication_date_format" msgid="8191225366513860104">"EEE, d. MMM"</string>
<string name="dream_time_complication_12_hr_time_format" msgid="4691197486690291529">"h:mm"</string>
<string name="dream_time_complication_24_hr_time_format" msgid="6248280719733640813">"kk:mm"</string>
<string name="keyguard_affordance_enablement_dialog_action_template" msgid="8164857863036314664">"<xliff:g id="APPNAME">%1$s</xliff:g> öffnen"</string>
diff --git a/packages/SystemUI/res/values-el/strings.xml b/packages/SystemUI/res/values-el/strings.xml
index 84e18f0..fd69488 100644
--- a/packages/SystemUI/res/values-el/strings.xml
+++ b/packages/SystemUI/res/values-el/strings.xml
@@ -993,7 +993,6 @@
<string name="bt_le_audio_broadcast_dialog_switch_app" msgid="6098768269397105733">"Μετάδοση με την εφαρμογή <xliff:g id="SWITCHAPP">%1$s</xliff:g>"</string>
<string name="bt_le_audio_broadcast_dialog_different_output" msgid="7885102097302562674">"Αλλαγή εξόδου"</string>
<string name="bt_le_audio_broadcast_dialog_unknown_name" msgid="3791472237793443044">"Άγνωστο"</string>
- <string name="dream_date_complication_date_format" msgid="8191225366513860104">"ΗΗΗ, ΜΜΜ η"</string>
<string name="dream_time_complication_12_hr_time_format" msgid="4691197486690291529">"ώ:λλ"</string>
<string name="dream_time_complication_24_hr_time_format" msgid="6248280719733640813">"kk:λλ"</string>
<string name="keyguard_affordance_enablement_dialog_action_template" msgid="8164857863036314664">"Άνοιγμα <xliff:g id="APPNAME">%1$s</xliff:g>"</string>
@@ -1009,8 +1008,6 @@
<string name="rear_display_unfold_bottom_sheet_title" msgid="2137403802960396357">"Αναστροφή στην μπροστ. οθόνη για καλύτερη selfie;"</string>
<string name="rear_display_bottom_sheet_description" msgid="1852662982816810352">"Χρησιμοποιήστε την πίσω κάμερα για να βγάλετε μια φωτογραφία με μεγαλύτερο εύρος και υψηλότερη ανάλυση."</string>
<string name="rear_display_bottom_sheet_warning" msgid="800995919558238930"><b>"* Αυτή η οθόνη θα απενεργοποιηθεί"</b></string>
- <!-- no translation found for rear_display_accessibility_folded_animation (1538121649587978179) -->
- <skip />
- <!-- no translation found for rear_display_accessibility_unfolded_animation (1946153682258289040) -->
- <skip />
+ <string name="rear_display_accessibility_folded_animation" msgid="1538121649587978179">"Αναδιπλούμενη συσκευή που ξεδιπλώνει"</string>
+ <string name="rear_display_accessibility_unfolded_animation" msgid="1946153682258289040">"Αναδιπλούμενη συσκευή που διπλώνει"</string>
</resources>
diff --git a/packages/SystemUI/res/values-en-rAU/strings.xml b/packages/SystemUI/res/values-en-rAU/strings.xml
index 133bb64..244865b 100644
--- a/packages/SystemUI/res/values-en-rAU/strings.xml
+++ b/packages/SystemUI/res/values-en-rAU/strings.xml
@@ -993,7 +993,6 @@
<string name="bt_le_audio_broadcast_dialog_switch_app" msgid="6098768269397105733">"Broadcast <xliff:g id="SWITCHAPP">%1$s</xliff:g>"</string>
<string name="bt_le_audio_broadcast_dialog_different_output" msgid="7885102097302562674">"Change output"</string>
<string name="bt_le_audio_broadcast_dialog_unknown_name" msgid="3791472237793443044">"Unknown"</string>
- <string name="dream_date_complication_date_format" msgid="8191225366513860104">"EEE, MMM d"</string>
<string name="dream_time_complication_12_hr_time_format" msgid="4691197486690291529">"h:mm"</string>
<string name="dream_time_complication_24_hr_time_format" msgid="6248280719733640813">"kk:mm"</string>
<string name="keyguard_affordance_enablement_dialog_action_template" msgid="8164857863036314664">"Open <xliff:g id="APPNAME">%1$s</xliff:g>"</string>
@@ -1009,8 +1008,6 @@
<string name="rear_display_unfold_bottom_sheet_title" msgid="2137403802960396357">"Flip to front display for a better selfie?"</string>
<string name="rear_display_bottom_sheet_description" msgid="1852662982816810352">"Use the rear-facing camera for a wider photo with higher resolution."</string>
<string name="rear_display_bottom_sheet_warning" msgid="800995919558238930"><b>"✱ This screen will turn off"</b></string>
- <!-- no translation found for rear_display_accessibility_folded_animation (1538121649587978179) -->
- <skip />
- <!-- no translation found for rear_display_accessibility_unfolded_animation (1946153682258289040) -->
- <skip />
+ <string name="rear_display_accessibility_folded_animation" msgid="1538121649587978179">"Foldable device being unfolded"</string>
+ <string name="rear_display_accessibility_unfolded_animation" msgid="1946153682258289040">"Foldable device being flipped around"</string>
</resources>
diff --git a/packages/SystemUI/res/values-en-rCA/strings.xml b/packages/SystemUI/res/values-en-rCA/strings.xml
index 4caf4e0..5cc99f9 100644
--- a/packages/SystemUI/res/values-en-rCA/strings.xml
+++ b/packages/SystemUI/res/values-en-rCA/strings.xml
@@ -993,7 +993,6 @@
<string name="bt_le_audio_broadcast_dialog_switch_app" msgid="6098768269397105733">"Broadcast <xliff:g id="SWITCHAPP">%1$s</xliff:g>"</string>
<string name="bt_le_audio_broadcast_dialog_different_output" msgid="7885102097302562674">"Change output"</string>
<string name="bt_le_audio_broadcast_dialog_unknown_name" msgid="3791472237793443044">"Unknown"</string>
- <string name="dream_date_complication_date_format" msgid="8191225366513860104">"EEE, MMM d"</string>
<string name="dream_time_complication_12_hr_time_format" msgid="4691197486690291529">"h:mm"</string>
<string name="dream_time_complication_24_hr_time_format" msgid="6248280719733640813">"kk:mm"</string>
<string name="keyguard_affordance_enablement_dialog_action_template" msgid="8164857863036314664">"Open <xliff:g id="APPNAME">%1$s</xliff:g>"</string>
@@ -1009,8 +1008,6 @@
<string name="rear_display_unfold_bottom_sheet_title" msgid="2137403802960396357">"Flip to front display for a better selfie?"</string>
<string name="rear_display_bottom_sheet_description" msgid="1852662982816810352">"Use the rear-facing camera for a wider photo with higher resolution."</string>
<string name="rear_display_bottom_sheet_warning" msgid="800995919558238930"><b>"✱ This screen will turn off"</b></string>
- <!-- no translation found for rear_display_accessibility_folded_animation (1538121649587978179) -->
- <skip />
- <!-- no translation found for rear_display_accessibility_unfolded_animation (1946153682258289040) -->
- <skip />
+ <string name="rear_display_accessibility_folded_animation" msgid="1538121649587978179">"Foldable device being unfolded"</string>
+ <string name="rear_display_accessibility_unfolded_animation" msgid="1946153682258289040">"Foldable device being flipped around"</string>
</resources>
diff --git a/packages/SystemUI/res/values-en-rGB/strings.xml b/packages/SystemUI/res/values-en-rGB/strings.xml
index 133bb64..244865b 100644
--- a/packages/SystemUI/res/values-en-rGB/strings.xml
+++ b/packages/SystemUI/res/values-en-rGB/strings.xml
@@ -993,7 +993,6 @@
<string name="bt_le_audio_broadcast_dialog_switch_app" msgid="6098768269397105733">"Broadcast <xliff:g id="SWITCHAPP">%1$s</xliff:g>"</string>
<string name="bt_le_audio_broadcast_dialog_different_output" msgid="7885102097302562674">"Change output"</string>
<string name="bt_le_audio_broadcast_dialog_unknown_name" msgid="3791472237793443044">"Unknown"</string>
- <string name="dream_date_complication_date_format" msgid="8191225366513860104">"EEE, MMM d"</string>
<string name="dream_time_complication_12_hr_time_format" msgid="4691197486690291529">"h:mm"</string>
<string name="dream_time_complication_24_hr_time_format" msgid="6248280719733640813">"kk:mm"</string>
<string name="keyguard_affordance_enablement_dialog_action_template" msgid="8164857863036314664">"Open <xliff:g id="APPNAME">%1$s</xliff:g>"</string>
@@ -1009,8 +1008,6 @@
<string name="rear_display_unfold_bottom_sheet_title" msgid="2137403802960396357">"Flip to front display for a better selfie?"</string>
<string name="rear_display_bottom_sheet_description" msgid="1852662982816810352">"Use the rear-facing camera for a wider photo with higher resolution."</string>
<string name="rear_display_bottom_sheet_warning" msgid="800995919558238930"><b>"✱ This screen will turn off"</b></string>
- <!-- no translation found for rear_display_accessibility_folded_animation (1538121649587978179) -->
- <skip />
- <!-- no translation found for rear_display_accessibility_unfolded_animation (1946153682258289040) -->
- <skip />
+ <string name="rear_display_accessibility_folded_animation" msgid="1538121649587978179">"Foldable device being unfolded"</string>
+ <string name="rear_display_accessibility_unfolded_animation" msgid="1946153682258289040">"Foldable device being flipped around"</string>
</resources>
diff --git a/packages/SystemUI/res/values-en-rIN/strings.xml b/packages/SystemUI/res/values-en-rIN/strings.xml
index 133bb64..244865b 100644
--- a/packages/SystemUI/res/values-en-rIN/strings.xml
+++ b/packages/SystemUI/res/values-en-rIN/strings.xml
@@ -993,7 +993,6 @@
<string name="bt_le_audio_broadcast_dialog_switch_app" msgid="6098768269397105733">"Broadcast <xliff:g id="SWITCHAPP">%1$s</xliff:g>"</string>
<string name="bt_le_audio_broadcast_dialog_different_output" msgid="7885102097302562674">"Change output"</string>
<string name="bt_le_audio_broadcast_dialog_unknown_name" msgid="3791472237793443044">"Unknown"</string>
- <string name="dream_date_complication_date_format" msgid="8191225366513860104">"EEE, MMM d"</string>
<string name="dream_time_complication_12_hr_time_format" msgid="4691197486690291529">"h:mm"</string>
<string name="dream_time_complication_24_hr_time_format" msgid="6248280719733640813">"kk:mm"</string>
<string name="keyguard_affordance_enablement_dialog_action_template" msgid="8164857863036314664">"Open <xliff:g id="APPNAME">%1$s</xliff:g>"</string>
@@ -1009,8 +1008,6 @@
<string name="rear_display_unfold_bottom_sheet_title" msgid="2137403802960396357">"Flip to front display for a better selfie?"</string>
<string name="rear_display_bottom_sheet_description" msgid="1852662982816810352">"Use the rear-facing camera for a wider photo with higher resolution."</string>
<string name="rear_display_bottom_sheet_warning" msgid="800995919558238930"><b>"✱ This screen will turn off"</b></string>
- <!-- no translation found for rear_display_accessibility_folded_animation (1538121649587978179) -->
- <skip />
- <!-- no translation found for rear_display_accessibility_unfolded_animation (1946153682258289040) -->
- <skip />
+ <string name="rear_display_accessibility_folded_animation" msgid="1538121649587978179">"Foldable device being unfolded"</string>
+ <string name="rear_display_accessibility_unfolded_animation" msgid="1946153682258289040">"Foldable device being flipped around"</string>
</resources>
diff --git a/packages/SystemUI/res/values-en-rXC/strings.xml b/packages/SystemUI/res/values-en-rXC/strings.xml
index abb548b..cb79d11 100644
--- a/packages/SystemUI/res/values-en-rXC/strings.xml
+++ b/packages/SystemUI/res/values-en-rXC/strings.xml
@@ -993,7 +993,6 @@
<string name="bt_le_audio_broadcast_dialog_switch_app" msgid="6098768269397105733">"Broadcast <xliff:g id="SWITCHAPP">%1$s</xliff:g>"</string>
<string name="bt_le_audio_broadcast_dialog_different_output" msgid="7885102097302562674">"Change output"</string>
<string name="bt_le_audio_broadcast_dialog_unknown_name" msgid="3791472237793443044">"Unknown"</string>
- <string name="dream_date_complication_date_format" msgid="8191225366513860104">"EEE, MMM d"</string>
<string name="dream_time_complication_12_hr_time_format" msgid="4691197486690291529">"h:mm"</string>
<string name="dream_time_complication_24_hr_time_format" msgid="6248280719733640813">"kk:mm"</string>
<string name="keyguard_affordance_enablement_dialog_action_template" msgid="8164857863036314664">"Open <xliff:g id="APPNAME">%1$s</xliff:g>"</string>
@@ -1009,8 +1008,6 @@
<string name="rear_display_unfold_bottom_sheet_title" msgid="2137403802960396357">"Flip to front display for a better selfie?"</string>
<string name="rear_display_bottom_sheet_description" msgid="1852662982816810352">"Use the rear-facing camera for a wider photo with higher resolution."</string>
<string name="rear_display_bottom_sheet_warning" msgid="800995919558238930">""<b>"✱ This screen will turn off"</b>""</string>
- <!-- no translation found for rear_display_accessibility_folded_animation (1538121649587978179) -->
- <skip />
- <!-- no translation found for rear_display_accessibility_unfolded_animation (1946153682258289040) -->
- <skip />
+ <string name="rear_display_accessibility_folded_animation" msgid="1538121649587978179">"Foldable device being unfolded"</string>
+ <string name="rear_display_accessibility_unfolded_animation" msgid="1946153682258289040">"Foldable device being flipped around"</string>
</resources>
diff --git a/packages/SystemUI/res/values-es-rUS/strings.xml b/packages/SystemUI/res/values-es-rUS/strings.xml
index 3527aaf..b91d1d6 100644
--- a/packages/SystemUI/res/values-es-rUS/strings.xml
+++ b/packages/SystemUI/res/values-es-rUS/strings.xml
@@ -872,8 +872,7 @@
<string name="media_output_dialog_button_stop_casting" msgid="6581379537930199189">"Detener transmisión"</string>
<string name="media_output_dialog_accessibility_title" msgid="4681741064190167888">"Dispositivos disponibles para salida de audio."</string>
<string name="media_output_dialog_accessibility_seekbar" msgid="5332843993805568978">"Volumen"</string>
- <!-- no translation found for media_output_dialog_volume_percentage (1613984910585111798) -->
- <skip />
+ <string name="media_output_dialog_volume_percentage" msgid="1613984910585111798">"<xliff:g id="PERCENTAGE">%1$d</xliff:g>%%"</string>
<string name="media_output_first_broadcast_title" msgid="6292237789860753022">"Cómo funciona la transmisión"</string>
<string name="media_output_broadcast" msgid="3555580945878071543">"Transmisión"</string>
<string name="media_output_first_notify_broadcast_message" msgid="6353857724136398494">"Las personas cercanas con dispositivos Bluetooth compatibles pueden escuchar el contenido multimedia que transmites"</string>
@@ -987,15 +986,13 @@
<string name="dream_overlay_status_bar_camera_mic_off" msgid="3199425257833773569">"La cámara y el micrófono están apagados"</string>
<string name="dream_overlay_status_bar_notification_indicator" msgid="8091389255691081711">"{count,plural, =1{# notificación}many{# notificaciones}other{# notificaciones}}"</string>
<string name="dream_overlay_weather_complication_desc" msgid="824503662089783824">"<xliff:g id="WEATHER_CONDITION">%1$s</xliff:g>, <xliff:g id="TEMPERATURE">%2$s</xliff:g>"</string>
- <!-- no translation found for note_task_button_label (8718616095800343136) -->
- <skip />
+ <string name="note_task_button_label" msgid="8718616095800343136">"Tomar notas"</string>
<string name="broadcasting_description_is_broadcasting" msgid="765627502786404290">"Transmitiendo"</string>
<string name="bt_le_audio_broadcast_dialog_title" msgid="3605428497924077811">"¿Quieres dejar de transmitir <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string>
<string name="bt_le_audio_broadcast_dialog_sub_title" msgid="7889684551194225793">"Si transmites <xliff:g id="SWITCHAPP">%1$s</xliff:g> o cambias la salida, tu transmisión actual se detendrá"</string>
<string name="bt_le_audio_broadcast_dialog_switch_app" msgid="6098768269397105733">"Transmitir <xliff:g id="SWITCHAPP">%1$s</xliff:g>"</string>
<string name="bt_le_audio_broadcast_dialog_different_output" msgid="7885102097302562674">"Cambia la salida"</string>
<string name="bt_le_audio_broadcast_dialog_unknown_name" msgid="3791472237793443044">"Desconocido"</string>
- <string name="dream_date_complication_date_format" msgid="8191225366513860104">"EEE, d de MMM"</string>
<string name="dream_time_complication_12_hr_time_format" msgid="4691197486690291529">"h:mm"</string>
<string name="dream_time_complication_24_hr_time_format" msgid="6248280719733640813">"kk:mm"</string>
<string name="keyguard_affordance_enablement_dialog_action_template" msgid="8164857863036314664">"Abrir <xliff:g id="APPNAME">%1$s</xliff:g>"</string>
diff --git a/packages/SystemUI/res/values-es/strings.xml b/packages/SystemUI/res/values-es/strings.xml
index dcc52b5..e30b0e9 100644
--- a/packages/SystemUI/res/values-es/strings.xml
+++ b/packages/SystemUI/res/values-es/strings.xml
@@ -872,8 +872,7 @@
<string name="media_output_dialog_button_stop_casting" msgid="6581379537930199189">"Dejar de enviar contenido"</string>
<string name="media_output_dialog_accessibility_title" msgid="4681741064190167888">"Dispositivos disponibles para la salida de audio."</string>
<string name="media_output_dialog_accessibility_seekbar" msgid="5332843993805568978">"Volumen"</string>
- <!-- no translation found for media_output_dialog_volume_percentage (1613984910585111798) -->
- <skip />
+ <string name="media_output_dialog_volume_percentage" msgid="1613984910585111798">"<xliff:g id="PERCENTAGE">%1$d</xliff:g> %%"</string>
<string name="media_output_first_broadcast_title" msgid="6292237789860753022">"Cómo funciona la emisión"</string>
<string name="media_output_broadcast" msgid="3555580945878071543">"Emisión"</string>
<string name="media_output_first_notify_broadcast_message" msgid="6353857724136398494">"Las personas cercanas con dispositivos Bluetooth compatibles pueden escuchar el contenido multimedia que emites"</string>
@@ -987,15 +986,13 @@
<string name="dream_overlay_status_bar_camera_mic_off" msgid="3199425257833773569">"La cámara y el micrófono están desactivados"</string>
<string name="dream_overlay_status_bar_notification_indicator" msgid="8091389255691081711">"{count,plural, =1{# notificación}many{# notificaciones}other{# notificaciones}}"</string>
<string name="dream_overlay_weather_complication_desc" msgid="824503662089783824">"<xliff:g id="WEATHER_CONDITION">%1$s</xliff:g>, <xliff:g id="TEMPERATURE">%2$s</xliff:g>"</string>
- <!-- no translation found for note_task_button_label (8718616095800343136) -->
- <skip />
+ <string name="note_task_button_label" msgid="8718616095800343136">"Tomar notas"</string>
<string name="broadcasting_description_is_broadcasting" msgid="765627502786404290">"Emitiendo"</string>
<string name="bt_le_audio_broadcast_dialog_title" msgid="3605428497924077811">"¿Dejar de emitir <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string>
<string name="bt_le_audio_broadcast_dialog_sub_title" msgid="7889684551194225793">"Si emites <xliff:g id="SWITCHAPP">%1$s</xliff:g> o cambias la salida, tu emisión actual se detendrá"</string>
<string name="bt_le_audio_broadcast_dialog_switch_app" msgid="6098768269397105733">"Emitir <xliff:g id="SWITCHAPP">%1$s</xliff:g>"</string>
<string name="bt_le_audio_broadcast_dialog_different_output" msgid="7885102097302562674">"Cambiar salida"</string>
<string name="bt_le_audio_broadcast_dialog_unknown_name" msgid="3791472237793443044">"Desconocido"</string>
- <string name="dream_date_complication_date_format" msgid="8191225366513860104">"EEE, d MMM"</string>
<string name="dream_time_complication_12_hr_time_format" msgid="4691197486690291529">"h:mm"</string>
<string name="dream_time_complication_24_hr_time_format" msgid="6248280719733640813">"kk:mm"</string>
<string name="keyguard_affordance_enablement_dialog_action_template" msgid="8164857863036314664">"Abrir <xliff:g id="APPNAME">%1$s</xliff:g>"</string>
diff --git a/packages/SystemUI/res/values-et/strings.xml b/packages/SystemUI/res/values-et/strings.xml
index afba616..cd1445b 100644
--- a/packages/SystemUI/res/values-et/strings.xml
+++ b/packages/SystemUI/res/values-et/strings.xml
@@ -872,8 +872,7 @@
<string name="media_output_dialog_button_stop_casting" msgid="6581379537930199189">"Lõpeta ülekanne"</string>
<string name="media_output_dialog_accessibility_title" msgid="4681741064190167888">"Saadaolevad seadmed heli esitamiseks."</string>
<string name="media_output_dialog_accessibility_seekbar" msgid="5332843993805568978">"Helitugevus"</string>
- <!-- no translation found for media_output_dialog_volume_percentage (1613984910585111798) -->
- <skip />
+ <string name="media_output_dialog_volume_percentage" msgid="1613984910585111798">"<xliff:g id="PERCENTAGE">%1$d</xliff:g>%%"</string>
<string name="media_output_first_broadcast_title" msgid="6292237789860753022">"Kuidas ülekandmine toimib?"</string>
<string name="media_output_broadcast" msgid="3555580945878071543">"Ülekanne"</string>
<string name="media_output_first_notify_broadcast_message" msgid="6353857724136398494">"Teie läheduses olevad inimesed, kellel on ühilduvad Bluetooth-seadmed, saavad kuulata teie ülekantavat meediat"</string>
@@ -987,15 +986,13 @@
<string name="dream_overlay_status_bar_camera_mic_off" msgid="3199425257833773569">"Kaamera ja mikrofon on välja lülitatud"</string>
<string name="dream_overlay_status_bar_notification_indicator" msgid="8091389255691081711">"{count,plural, =1{# märguanne}other{# märguannet}}"</string>
<string name="dream_overlay_weather_complication_desc" msgid="824503662089783824">"<xliff:g id="WEATHER_CONDITION">%1$s</xliff:g>, <xliff:g id="TEMPERATURE">%2$s</xliff:g>"</string>
- <!-- no translation found for note_task_button_label (8718616095800343136) -->
- <skip />
+ <string name="note_task_button_label" msgid="8718616095800343136">"Märkmete tegemine"</string>
<string name="broadcasting_description_is_broadcasting" msgid="765627502786404290">"Edastamine"</string>
<string name="bt_le_audio_broadcast_dialog_title" msgid="3605428497924077811">"Kas peatada rakenduse <xliff:g id="APP_NAME">%1$s</xliff:g> ülekandmine?"</string>
<string name="bt_le_audio_broadcast_dialog_sub_title" msgid="7889684551194225793">"Kui kannate rakendust <xliff:g id="SWITCHAPP">%1$s</xliff:g> üle või muudate väljundit, peatatakse teie praegune ülekanne"</string>
<string name="bt_le_audio_broadcast_dialog_switch_app" msgid="6098768269397105733">"Rakenduse <xliff:g id="SWITCHAPP">%1$s</xliff:g> ülekandmine"</string>
<string name="bt_le_audio_broadcast_dialog_different_output" msgid="7885102097302562674">"Väljundi muutmine"</string>
<string name="bt_le_audio_broadcast_dialog_unknown_name" msgid="3791472237793443044">"Tundmatu"</string>
- <string name="dream_date_complication_date_format" msgid="8191225366513860104">"EEE, d. MMM"</string>
<string name="dream_time_complication_12_hr_time_format" msgid="4691197486690291529">"h:mm"</string>
<string name="dream_time_complication_24_hr_time_format" msgid="6248280719733640813">"kk:mm"</string>
<string name="keyguard_affordance_enablement_dialog_action_template" msgid="8164857863036314664">"Ava <xliff:g id="APPNAME">%1$s</xliff:g>"</string>
diff --git a/packages/SystemUI/res/values-eu/strings.xml b/packages/SystemUI/res/values-eu/strings.xml
index 9ded73a..f9508d1 100644
--- a/packages/SystemUI/res/values-eu/strings.xml
+++ b/packages/SystemUI/res/values-eu/strings.xml
@@ -872,8 +872,7 @@
<string name="media_output_dialog_button_stop_casting" msgid="6581379537930199189">"Gelditu igorpena"</string>
<string name="media_output_dialog_accessibility_title" msgid="4681741064190167888">"Audio-irteerarako gailu erabilgarriak."</string>
<string name="media_output_dialog_accessibility_seekbar" msgid="5332843993805568978">"Bolumena"</string>
- <!-- no translation found for media_output_dialog_volume_percentage (1613984910585111798) -->
- <skip />
+ <string name="media_output_dialog_volume_percentage" msgid="1613984910585111798">"%% <xliff:g id="PERCENTAGE">%1$d</xliff:g>"</string>
<string name="media_output_first_broadcast_title" msgid="6292237789860753022">"Nola funtzionatzen dute iragarpenek?"</string>
<string name="media_output_broadcast" msgid="3555580945878071543">"Iragarri"</string>
<string name="media_output_first_notify_broadcast_message" msgid="6353857724136398494">"Bluetooth bidezko gailu bateragarriak dituzten inguruko pertsonek iragartzen ari zaren multimedia-edukia entzun dezakete"</string>
@@ -987,15 +986,13 @@
<string name="dream_overlay_status_bar_camera_mic_off" msgid="3199425257833773569">"Kamera eta mikrofonoa desaktibatuta daude"</string>
<string name="dream_overlay_status_bar_notification_indicator" msgid="8091389255691081711">"{count,plural, =1{# jakinarazpen}other{# jakinarazpen}}"</string>
<string name="dream_overlay_weather_complication_desc" msgid="824503662089783824">"<xliff:g id="WEATHER_CONDITION">%1$s</xliff:g>, <xliff:g id="TEMPERATURE">%2$s</xliff:g>"</string>
- <!-- no translation found for note_task_button_label (8718616095800343136) -->
- <skip />
+ <string name="note_task_button_label" msgid="8718616095800343136">"Oharrak idaztea"</string>
<string name="broadcasting_description_is_broadcasting" msgid="765627502786404290">"Igortzen"</string>
<string name="bt_le_audio_broadcast_dialog_title" msgid="3605428497924077811">"<xliff:g id="APP_NAME">%1$s</xliff:g> aplikazioaren audioa igortzeari utzi nahi diozu?"</string>
<string name="bt_le_audio_broadcast_dialog_sub_title" msgid="7889684551194225793">"<xliff:g id="SWITCHAPP">%1$s</xliff:g> aplikazioaren audioa igortzen baduzu, edo audio-irteera aldatzen baduzu, une hartako igorpena eten egingo da"</string>
<string name="bt_le_audio_broadcast_dialog_switch_app" msgid="6098768269397105733">"Igorri <xliff:g id="SWITCHAPP">%1$s</xliff:g> aplikazioaren audioa"</string>
<string name="bt_le_audio_broadcast_dialog_different_output" msgid="7885102097302562674">"Aldatu audio-irteera"</string>
<string name="bt_le_audio_broadcast_dialog_unknown_name" msgid="3791472237793443044">"Ezezaguna"</string>
- <string name="dream_date_complication_date_format" msgid="8191225366513860104">"EEE, MMM d"</string>
<string name="dream_time_complication_12_hr_time_format" msgid="4691197486690291529">"h:mm"</string>
<string name="dream_time_complication_24_hr_time_format" msgid="6248280719733640813">"kk:mm"</string>
<string name="keyguard_affordance_enablement_dialog_action_template" msgid="8164857863036314664">"Ireki <xliff:g id="APPNAME">%1$s</xliff:g>"</string>
diff --git a/packages/SystemUI/res/values-fa/strings.xml b/packages/SystemUI/res/values-fa/strings.xml
index c93803e9..bb195ce 100644
--- a/packages/SystemUI/res/values-fa/strings.xml
+++ b/packages/SystemUI/res/values-fa/strings.xml
@@ -872,8 +872,7 @@
<string name="media_output_dialog_button_stop_casting" msgid="6581379537930199189">"توقف پخش محتوا"</string>
<string name="media_output_dialog_accessibility_title" msgid="4681741064190167888">"دستگاههای دردسترس برای خروجی صدا."</string>
<string name="media_output_dialog_accessibility_seekbar" msgid="5332843993805568978">"میزان صدا"</string>
- <!-- no translation found for media_output_dialog_volume_percentage (1613984910585111798) -->
- <skip />
+ <string name="media_output_dialog_volume_percentage" msgid="1613984910585111798">"<xliff:g id="PERCENTAGE">%1$d</xliff:g>%%"</string>
<string name="media_output_first_broadcast_title" msgid="6292237789860753022">"همهفرتستی چطور کار میکند"</string>
<string name="media_output_broadcast" msgid="3555580945878071543">"همهفرستی"</string>
<string name="media_output_first_notify_broadcast_message" msgid="6353857724136398494">"افرادی که در اطرافتان دستگاههای Bluetooth سازگار دارند میتوانند به رسانهای که همهفرستی میکنید گوش کنند"</string>
@@ -987,15 +986,13 @@
<string name="dream_overlay_status_bar_camera_mic_off" msgid="3199425257833773569">"دوربین و میکروفون خاموش هستند"</string>
<string name="dream_overlay_status_bar_notification_indicator" msgid="8091389255691081711">"{count,plural, =1{# اعلان}one{# اعلان}other{# اعلان}}"</string>
<string name="dream_overlay_weather_complication_desc" msgid="824503662089783824">"<xliff:g id="WEATHER_CONDITION">%1$s</xliff:g>، <xliff:g id="TEMPERATURE">%2$s</xliff:g>"</string>
- <!-- no translation found for note_task_button_label (8718616095800343136) -->
- <skip />
+ <string name="note_task_button_label" msgid="8718616095800343136">"یادداشتبرداری"</string>
<string name="broadcasting_description_is_broadcasting" msgid="765627502786404290">"همهفرستی"</string>
<string name="bt_le_audio_broadcast_dialog_title" msgid="3605428497924077811">"همهفرستی <xliff:g id="APP_NAME">%1$s</xliff:g> متوقف شود؟"</string>
<string name="bt_le_audio_broadcast_dialog_sub_title" msgid="7889684551194225793">"اگر <xliff:g id="SWITCHAPP">%1$s</xliff:g> را همهفرستی کنید یا خروجی را تغییر دهید، همهفرستی کنونی متوقف خواهد شد"</string>
<string name="bt_le_audio_broadcast_dialog_switch_app" msgid="6098768269397105733">"همهفرستی <xliff:g id="SWITCHAPP">%1$s</xliff:g>"</string>
<string name="bt_le_audio_broadcast_dialog_different_output" msgid="7885102097302562674">"تغییر خروجی"</string>
<string name="bt_le_audio_broadcast_dialog_unknown_name" msgid="3791472237793443044">"نامشخص"</string>
- <string name="dream_date_complication_date_format" msgid="8191225366513860104">"EEE d MMM"</string>
<string name="dream_time_complication_12_hr_time_format" msgid="4691197486690291529">"h:mm"</string>
<string name="dream_time_complication_24_hr_time_format" msgid="6248280719733640813">"kk:mm"</string>
<string name="keyguard_affordance_enablement_dialog_action_template" msgid="8164857863036314664">"باز کردن <xliff:g id="APPNAME">%1$s</xliff:g>"</string>
diff --git a/packages/SystemUI/res/values-fi/strings.xml b/packages/SystemUI/res/values-fi/strings.xml
index d3b2da3..0783c15 100644
--- a/packages/SystemUI/res/values-fi/strings.xml
+++ b/packages/SystemUI/res/values-fi/strings.xml
@@ -872,8 +872,7 @@
<string name="media_output_dialog_button_stop_casting" msgid="6581379537930199189">"Lopeta striimaus"</string>
<string name="media_output_dialog_accessibility_title" msgid="4681741064190167888">"Käytettävissä olevat audiolaitteet"</string>
<string name="media_output_dialog_accessibility_seekbar" msgid="5332843993805568978">"Äänenvoimakkuus"</string>
- <!-- no translation found for media_output_dialog_volume_percentage (1613984910585111798) -->
- <skip />
+ <string name="media_output_dialog_volume_percentage" msgid="1613984910585111798">"<xliff:g id="PERCENTAGE">%1$d</xliff:g> %%"</string>
<string name="media_output_first_broadcast_title" msgid="6292237789860753022">"Miten lähetys toimii"</string>
<string name="media_output_broadcast" msgid="3555580945878071543">"Lähetys"</string>
<string name="media_output_first_notify_broadcast_message" msgid="6353857724136398494">"Lähistöllä olevat ihmiset, joilla on yhteensopiva Bluetooth-laite, voivat kuunnella lähettämääsi mediaa"</string>
@@ -987,15 +986,13 @@
<string name="dream_overlay_status_bar_camera_mic_off" msgid="3199425257833773569">"Kamera ja mikrofoni ovat pois päältä"</string>
<string name="dream_overlay_status_bar_notification_indicator" msgid="8091389255691081711">"{count,plural, =1{# ilmoitus}other{# ilmoitusta}}"</string>
<string name="dream_overlay_weather_complication_desc" msgid="824503662089783824">"<xliff:g id="WEATHER_CONDITION">%1$s</xliff:g>, <xliff:g id="TEMPERATURE">%2$s</xliff:g>"</string>
- <!-- no translation found for note_task_button_label (8718616095800343136) -->
- <skip />
+ <string name="note_task_button_label" msgid="8718616095800343136">"Muistiinpanojen tekeminen"</string>
<string name="broadcasting_description_is_broadcasting" msgid="765627502786404290">"Lähettää"</string>
<string name="bt_le_audio_broadcast_dialog_title" msgid="3605428497924077811">"Lopetetaanko <xliff:g id="APP_NAME">%1$s</xliff:g>-sovelluksen lähettäminen?"</string>
<string name="bt_le_audio_broadcast_dialog_sub_title" msgid="7889684551194225793">"Jos lähetät <xliff:g id="SWITCHAPP">%1$s</xliff:g>-sovellusta tai muutat ulostuloa, nykyinen lähetyksesi loppuu"</string>
<string name="bt_le_audio_broadcast_dialog_switch_app" msgid="6098768269397105733">"Lähetä <xliff:g id="SWITCHAPP">%1$s</xliff:g>-sovellusta"</string>
<string name="bt_le_audio_broadcast_dialog_different_output" msgid="7885102097302562674">"Muuta ulostuloa"</string>
<string name="bt_le_audio_broadcast_dialog_unknown_name" msgid="3791472237793443044">"Tuntematon"</string>
- <string name="dream_date_complication_date_format" msgid="8191225366513860104">"VKP, KKK p"</string>
<string name="dream_time_complication_12_hr_time_format" msgid="4691197486690291529">"h:mm"</string>
<string name="dream_time_complication_24_hr_time_format" msgid="6248280719733640813">"kk:mm"</string>
<string name="keyguard_affordance_enablement_dialog_action_template" msgid="8164857863036314664">"Avaa <xliff:g id="APPNAME">%1$s</xliff:g>"</string>
diff --git a/packages/SystemUI/res/values-fr-rCA/strings.xml b/packages/SystemUI/res/values-fr-rCA/strings.xml
index 0300e9c..15a57b0 100644
--- a/packages/SystemUI/res/values-fr-rCA/strings.xml
+++ b/packages/SystemUI/res/values-fr-rCA/strings.xml
@@ -872,8 +872,7 @@
<string name="media_output_dialog_button_stop_casting" msgid="6581379537930199189">"Arrêter la diffusion"</string>
<string name="media_output_dialog_accessibility_title" msgid="4681741064190167888">"Appareils disponibles pour la sortie audio."</string>
<string name="media_output_dialog_accessibility_seekbar" msgid="5332843993805568978">"Volume"</string>
- <!-- no translation found for media_output_dialog_volume_percentage (1613984910585111798) -->
- <skip />
+ <string name="media_output_dialog_volume_percentage" msgid="1613984910585111798">"<xliff:g id="PERCENTAGE">%1$d</xliff:g> %%"</string>
<string name="media_output_first_broadcast_title" msgid="6292237789860753022">"Fonctionnement de la diffusion"</string>
<string name="media_output_broadcast" msgid="3555580945878071543">"Diffusion"</string>
<string name="media_output_first_notify_broadcast_message" msgid="6353857724136398494">"Les personnes à proximité disposant d\'appareils Bluetooth compatibles peuvent écouter le contenu multimédia que vous diffusez"</string>
@@ -987,15 +986,13 @@
<string name="dream_overlay_status_bar_camera_mic_off" msgid="3199425257833773569">"L\'appareil photo et le micro sont désactivés"</string>
<string name="dream_overlay_status_bar_notification_indicator" msgid="8091389255691081711">"{count,plural, =1{# notification}one{# notification}many{# de notifications}other{# notifications}}"</string>
<string name="dream_overlay_weather_complication_desc" msgid="824503662089783824">"<xliff:g id="WEATHER_CONDITION">%1$s</xliff:g>, <xliff:g id="TEMPERATURE">%2$s</xliff:g>"</string>
- <!-- no translation found for note_task_button_label (8718616095800343136) -->
- <skip />
+ <string name="note_task_button_label" msgid="8718616095800343136">"Prise de note"</string>
<string name="broadcasting_description_is_broadcasting" msgid="765627502786404290">"Diffusion en cours…"</string>
<string name="bt_le_audio_broadcast_dialog_title" msgid="3605428497924077811">"Arrêter la diffusion de <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string>
<string name="bt_le_audio_broadcast_dialog_sub_title" msgid="7889684551194225793">"Si vous diffusez <xliff:g id="SWITCHAPP">%1$s</xliff:g> ou changez la sortie, votre diffusion actuelle s\'arrêtera"</string>
<string name="bt_le_audio_broadcast_dialog_switch_app" msgid="6098768269397105733">"Diffuser <xliff:g id="SWITCHAPP">%1$s</xliff:g>"</string>
<string name="bt_le_audio_broadcast_dialog_different_output" msgid="7885102097302562674">"Changer la sortie"</string>
<string name="bt_le_audio_broadcast_dialog_unknown_name" msgid="3791472237793443044">"Inconnue"</string>
- <string name="dream_date_complication_date_format" msgid="8191225366513860104">"EEE, d MMM"</string>
<string name="dream_time_complication_12_hr_time_format" msgid="4691197486690291529">"h:mm"</string>
<string name="dream_time_complication_24_hr_time_format" msgid="6248280719733640813">"kk:mm"</string>
<string name="keyguard_affordance_enablement_dialog_action_template" msgid="8164857863036314664">"Ouvrir <xliff:g id="APPNAME">%1$s</xliff:g>"</string>
diff --git a/packages/SystemUI/res/values-fr/strings.xml b/packages/SystemUI/res/values-fr/strings.xml
index c1d58eb..a1c6e36 100644
--- a/packages/SystemUI/res/values-fr/strings.xml
+++ b/packages/SystemUI/res/values-fr/strings.xml
@@ -872,8 +872,7 @@
<string name="media_output_dialog_button_stop_casting" msgid="6581379537930199189">"Arrêter la diffusion"</string>
<string name="media_output_dialog_accessibility_title" msgid="4681741064190167888">"Appareils disponibles pour la sortie audio."</string>
<string name="media_output_dialog_accessibility_seekbar" msgid="5332843993805568978">"Volume"</string>
- <!-- no translation found for media_output_dialog_volume_percentage (1613984910585111798) -->
- <skip />
+ <string name="media_output_dialog_volume_percentage" msgid="1613984910585111798">"<xliff:g id="PERCENTAGE">%1$d</xliff:g> %%"</string>
<string name="media_output_first_broadcast_title" msgid="6292237789860753022">"Fonctionnement des annonces"</string>
<string name="media_output_broadcast" msgid="3555580945878071543">"Annonce"</string>
<string name="media_output_first_notify_broadcast_message" msgid="6353857724136398494">"Les personnes à proximité équipées d\'appareils Bluetooth compatibles peuvent écouter le contenu multimédia que vous diffusez"</string>
@@ -987,15 +986,13 @@
<string name="dream_overlay_status_bar_camera_mic_off" msgid="3199425257833773569">"Appareil photo et micro désactivés"</string>
<string name="dream_overlay_status_bar_notification_indicator" msgid="8091389255691081711">"{count,plural, =1{# notification}one{# notification}many{# notifications}other{# notifications}}"</string>
<string name="dream_overlay_weather_complication_desc" msgid="824503662089783824">"<xliff:g id="WEATHER_CONDITION">%1$s</xliff:g>, <xliff:g id="TEMPERATURE">%2$s</xliff:g>"</string>
- <!-- no translation found for note_task_button_label (8718616095800343136) -->
- <skip />
+ <string name="note_task_button_label" msgid="8718616095800343136">"Prendre des notes"</string>
<string name="broadcasting_description_is_broadcasting" msgid="765627502786404290">"Diffusion…"</string>
<string name="bt_le_audio_broadcast_dialog_title" msgid="3605428497924077811">"Arrêter la diffusion de <xliff:g id="APP_NAME">%1$s</xliff:g> ?"</string>
<string name="bt_le_audio_broadcast_dialog_sub_title" msgid="7889684551194225793">"Si vous diffusez <xliff:g id="SWITCHAPP">%1$s</xliff:g> ou que vous modifiez le résultat, votre annonce actuelle s\'arrêtera"</string>
<string name="bt_le_audio_broadcast_dialog_switch_app" msgid="6098768269397105733">"Diffuser <xliff:g id="SWITCHAPP">%1$s</xliff:g>"</string>
<string name="bt_le_audio_broadcast_dialog_different_output" msgid="7885102097302562674">"Modifier le résultat"</string>
<string name="bt_le_audio_broadcast_dialog_unknown_name" msgid="3791472237793443044">"Inconnue"</string>
- <string name="dream_date_complication_date_format" msgid="8191225366513860104">"EEE, MMM j"</string>
<string name="dream_time_complication_12_hr_time_format" msgid="4691197486690291529">"hh:mm"</string>
<string name="dream_time_complication_24_hr_time_format" msgid="6248280719733640813">"kk:mm"</string>
<string name="keyguard_affordance_enablement_dialog_action_template" msgid="8164857863036314664">"Ouvrir <xliff:g id="APPNAME">%1$s</xliff:g>"</string>
diff --git a/packages/SystemUI/res/values-gl/strings.xml b/packages/SystemUI/res/values-gl/strings.xml
index c1daa98..60d2e0a 100644
--- a/packages/SystemUI/res/values-gl/strings.xml
+++ b/packages/SystemUI/res/values-gl/strings.xml
@@ -872,8 +872,7 @@
<string name="media_output_dialog_button_stop_casting" msgid="6581379537930199189">"Deter emisión"</string>
<string name="media_output_dialog_accessibility_title" msgid="4681741064190167888">"Dispositivos dispoñibles para a saída de audio."</string>
<string name="media_output_dialog_accessibility_seekbar" msgid="5332843993805568978">"Volume"</string>
- <!-- no translation found for media_output_dialog_volume_percentage (1613984910585111798) -->
- <skip />
+ <string name="media_output_dialog_volume_percentage" msgid="1613984910585111798">"<xliff:g id="PERCENTAGE">%1$d</xliff:g> %%"</string>
<string name="media_output_first_broadcast_title" msgid="6292237789860753022">"Como funcionan as difusións?"</string>
<string name="media_output_broadcast" msgid="3555580945878071543">"Difusión"</string>
<string name="media_output_first_notify_broadcast_message" msgid="6353857724136398494">"As persoas que estean preto de ti e que dispoñan de dispositivos Bluetooth compatibles poden escoitar o contido multimedia que difundas"</string>
@@ -987,15 +986,13 @@
<string name="dream_overlay_status_bar_camera_mic_off" msgid="3199425257833773569">"A cámara e o micrófono están desactivados"</string>
<string name="dream_overlay_status_bar_notification_indicator" msgid="8091389255691081711">"{count,plural, =1{# notificación}other{# notificacións}}"</string>
<string name="dream_overlay_weather_complication_desc" msgid="824503662089783824">"<xliff:g id="WEATHER_CONDITION">%1$s</xliff:g>, <xliff:g id="TEMPERATURE">%2$s</xliff:g>"</string>
- <!-- no translation found for note_task_button_label (8718616095800343136) -->
- <skip />
+ <string name="note_task_button_label" msgid="8718616095800343136">"Toma de notas"</string>
<string name="broadcasting_description_is_broadcasting" msgid="765627502786404290">"Difusión"</string>
<string name="bt_le_audio_broadcast_dialog_title" msgid="3605428497924077811">"Queres deixar de emitir contido a través de <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string>
<string name="bt_le_audio_broadcast_dialog_sub_title" msgid="7889684551194225793">"Se emites contido a través de <xliff:g id="SWITCHAPP">%1$s</xliff:g> ou cambias de saída, a emisión en curso deterase"</string>
<string name="bt_le_audio_broadcast_dialog_switch_app" msgid="6098768269397105733">"Emitir contido a través de <xliff:g id="SWITCHAPP">%1$s</xliff:g>"</string>
<string name="bt_le_audio_broadcast_dialog_different_output" msgid="7885102097302562674">"Cambiar de saída"</string>
<string name="bt_le_audio_broadcast_dialog_unknown_name" msgid="3791472237793443044">"Descoñecida"</string>
- <string name="dream_date_complication_date_format" msgid="8191225366513860104">"EEE, d MMM"</string>
<string name="dream_time_complication_12_hr_time_format" msgid="4691197486690291529">"h:mm"</string>
<string name="dream_time_complication_24_hr_time_format" msgid="6248280719733640813">"kk:mm"</string>
<string name="keyguard_affordance_enablement_dialog_action_template" msgid="8164857863036314664">"Abrir <xliff:g id="APPNAME">%1$s</xliff:g>"</string>
diff --git a/packages/SystemUI/res/values-gu/strings.xml b/packages/SystemUI/res/values-gu/strings.xml
index 6e306c8..d9572ef 100644
--- a/packages/SystemUI/res/values-gu/strings.xml
+++ b/packages/SystemUI/res/values-gu/strings.xml
@@ -993,7 +993,6 @@
<string name="bt_le_audio_broadcast_dialog_switch_app" msgid="6098768269397105733">"<xliff:g id="SWITCHAPP">%1$s</xliff:g> બ્રોડકાસ્ટ કરો"</string>
<string name="bt_le_audio_broadcast_dialog_different_output" msgid="7885102097302562674">"આઉટપુટ બદલો"</string>
<string name="bt_le_audio_broadcast_dialog_unknown_name" msgid="3791472237793443044">"અજાણ"</string>
- <string name="dream_date_complication_date_format" msgid="8191225366513860104">"EEE, d MMM"</string>
<string name="dream_time_complication_12_hr_time_format" msgid="4691197486690291529">"h:mm"</string>
<string name="dream_time_complication_24_hr_time_format" msgid="6248280719733640813">"kk:mm"</string>
<string name="keyguard_affordance_enablement_dialog_action_template" msgid="8164857863036314664">"<xliff:g id="APPNAME">%1$s</xliff:g> ખોલો"</string>
diff --git a/packages/SystemUI/res/values-hi/strings.xml b/packages/SystemUI/res/values-hi/strings.xml
index 5dba466..6f34cff 100644
--- a/packages/SystemUI/res/values-hi/strings.xml
+++ b/packages/SystemUI/res/values-hi/strings.xml
@@ -872,8 +872,7 @@
<string name="media_output_dialog_button_stop_casting" msgid="6581379537930199189">"कास्टिंग करना रोकें"</string>
<string name="media_output_dialog_accessibility_title" msgid="4681741064190167888">"ऑडियो आउटपुट के लिए उपलब्ध डिवाइस."</string>
<string name="media_output_dialog_accessibility_seekbar" msgid="5332843993805568978">"वॉल्यूम"</string>
- <!-- no translation found for media_output_dialog_volume_percentage (1613984910585111798) -->
- <skip />
+ <string name="media_output_dialog_volume_percentage" msgid="1613984910585111798">"<xliff:g id="PERCENTAGE">%1$d</xliff:g>%%"</string>
<string name="media_output_first_broadcast_title" msgid="6292237789860753022">"ब्रॉडकास्ट करने की सुविधा कैसे काम करती है"</string>
<string name="media_output_broadcast" msgid="3555580945878071543">"ब्रॉडकास्ट करें"</string>
<string name="media_output_first_notify_broadcast_message" msgid="6353857724136398494">"आपके आस-पास मौजूद लोग, ब्रॉडकास्ट किए जा रहे मीडिया को सुन सकते हैं. हालांकि, इसके लिए उनके पास ऐसे ब्लूटूथ डिवाइस होने चाहिए जिन पर मीडिया चलाया जा सके"</string>
@@ -987,15 +986,13 @@
<string name="dream_overlay_status_bar_camera_mic_off" msgid="3199425257833773569">"कैमरा और माइक बंद हैं"</string>
<string name="dream_overlay_status_bar_notification_indicator" msgid="8091389255691081711">"{count,plural, =1{# सूचना}one{# सूचना}other{# सूचनाएं}}"</string>
<string name="dream_overlay_weather_complication_desc" msgid="824503662089783824">"<xliff:g id="WEATHER_CONDITION">%1$s</xliff:g>, <xliff:g id="TEMPERATURE">%2$s</xliff:g>"</string>
- <!-- no translation found for note_task_button_label (8718616095800343136) -->
- <skip />
+ <string name="note_task_button_label" msgid="8718616095800343136">"नोट बनाएं"</string>
<string name="broadcasting_description_is_broadcasting" msgid="765627502786404290">"ब्रॉडकास्ट ऐप्लिकेशन"</string>
<string name="bt_le_audio_broadcast_dialog_title" msgid="3605428497924077811">"<xliff:g id="APP_NAME">%1$s</xliff:g> पर ब्रॉडकास्ट करना रोकें?"</string>
<string name="bt_le_audio_broadcast_dialog_sub_title" msgid="7889684551194225793">"<xliff:g id="SWITCHAPP">%1$s</xliff:g> पर ब्रॉडकास्ट शुरू करने पर या आउटपुट बदलने पर, आपका मौजूदा ब्रॉडकास्ट बंद हो जाएगा"</string>
<string name="bt_le_audio_broadcast_dialog_switch_app" msgid="6098768269397105733">"<xliff:g id="SWITCHAPP">%1$s</xliff:g> पर ब्रॉडकास्ट करें"</string>
<string name="bt_le_audio_broadcast_dialog_different_output" msgid="7885102097302562674">"आउटपुट बदलें"</string>
<string name="bt_le_audio_broadcast_dialog_unknown_name" msgid="3791472237793443044">"कोई जानकारी नहीं"</string>
- <string name="dream_date_complication_date_format" msgid="8191225366513860104">"EEE, MMM d"</string>
<string name="dream_time_complication_12_hr_time_format" msgid="4691197486690291529">"h:mm"</string>
<string name="dream_time_complication_24_hr_time_format" msgid="6248280719733640813">"kk:mm"</string>
<string name="keyguard_affordance_enablement_dialog_action_template" msgid="8164857863036314664">"<xliff:g id="APPNAME">%1$s</xliff:g> खोलें"</string>
diff --git a/packages/SystemUI/res/values-hr/strings.xml b/packages/SystemUI/res/values-hr/strings.xml
index 13f43be..7e7a0d9 100644
--- a/packages/SystemUI/res/values-hr/strings.xml
+++ b/packages/SystemUI/res/values-hr/strings.xml
@@ -993,7 +993,6 @@
<string name="bt_le_audio_broadcast_dialog_switch_app" msgid="6098768269397105733">"Emitiranje aplikacije <xliff:g id="SWITCHAPP">%1$s</xliff:g>"</string>
<string name="bt_le_audio_broadcast_dialog_different_output" msgid="7885102097302562674">"Promjena izlaza"</string>
<string name="bt_le_audio_broadcast_dialog_unknown_name" msgid="3791472237793443044">"Nepoznato"</string>
- <string name="dream_date_complication_date_format" msgid="8191225366513860104">"EEE., d. MMM."</string>
<string name="dream_time_complication_12_hr_time_format" msgid="4691197486690291529">"h:mm"</string>
<string name="dream_time_complication_24_hr_time_format" msgid="6248280719733640813">"kk:mm"</string>
<string name="keyguard_affordance_enablement_dialog_action_template" msgid="8164857863036314664">"Otvorite <xliff:g id="APPNAME">%1$s</xliff:g>"</string>
@@ -1009,8 +1008,6 @@
<string name="rear_display_unfold_bottom_sheet_title" msgid="2137403802960396357">"Prebaciti na prednji zaslon za bolji selfie?"</string>
<string name="rear_display_bottom_sheet_description" msgid="1852662982816810352">"Upotrijebite stražnji fotoaparat za širu fotografiju s višom razlučivošću."</string>
<string name="rear_display_bottom_sheet_warning" msgid="800995919558238930"><b>"✱ Ovaj će se zaslon isključiti"</b></string>
- <!-- no translation found for rear_display_accessibility_folded_animation (1538121649587978179) -->
- <skip />
- <!-- no translation found for rear_display_accessibility_unfolded_animation (1946153682258289040) -->
- <skip />
+ <string name="rear_display_accessibility_folded_animation" msgid="1538121649587978179">"Rasklopljen sklopivi uređaj"</string>
+ <string name="rear_display_accessibility_unfolded_animation" msgid="1946153682258289040">"Okretanje sklopivog uređaja sa svih strana"</string>
</resources>
diff --git a/packages/SystemUI/res/values-hu/strings.xml b/packages/SystemUI/res/values-hu/strings.xml
index 7c816c7..9624acd 100644
--- a/packages/SystemUI/res/values-hu/strings.xml
+++ b/packages/SystemUI/res/values-hu/strings.xml
@@ -872,8 +872,7 @@
<string name="media_output_dialog_button_stop_casting" msgid="6581379537930199189">"Átküldés leállítása"</string>
<string name="media_output_dialog_accessibility_title" msgid="4681741064190167888">"Rendelkezésre álló eszközök a hangkimenethez."</string>
<string name="media_output_dialog_accessibility_seekbar" msgid="5332843993805568978">"Hangerő"</string>
- <!-- no translation found for media_output_dialog_volume_percentage (1613984910585111798) -->
- <skip />
+ <string name="media_output_dialog_volume_percentage" msgid="1613984910585111798">"<xliff:g id="PERCENTAGE">%1$d</xliff:g>%%"</string>
<string name="media_output_first_broadcast_title" msgid="6292237789860753022">"A közvetítés működése"</string>
<string name="media_output_broadcast" msgid="3555580945878071543">"Közvetítés"</string>
<string name="media_output_first_notify_broadcast_message" msgid="6353857724136398494">"A közelben tartózkodó, kompatibilis Bluetooth-eszközzel rendelkező személyek meghallgathatják az Ön közvetített médiatartalmait"</string>
@@ -987,15 +986,13 @@
<string name="dream_overlay_status_bar_camera_mic_off" msgid="3199425257833773569">"A kamera és a mikrofon ki vannak kapcsolva"</string>
<string name="dream_overlay_status_bar_notification_indicator" msgid="8091389255691081711">"{count,plural, =1{# értesítés}other{# értesítés}}"</string>
<string name="dream_overlay_weather_complication_desc" msgid="824503662089783824">"<xliff:g id="WEATHER_CONDITION">%1$s</xliff:g>, <xliff:g id="TEMPERATURE">%2$s</xliff:g>"</string>
- <!-- no translation found for note_task_button_label (8718616095800343136) -->
- <skip />
+ <string name="note_task_button_label" msgid="8718616095800343136">"Jegyzetelés"</string>
<string name="broadcasting_description_is_broadcasting" msgid="765627502786404290">"Sugárzás"</string>
<string name="bt_le_audio_broadcast_dialog_title" msgid="3605428497924077811">"Leállítja a(z) <xliff:g id="APP_NAME">%1$s</xliff:g> közvetítését?"</string>
<string name="bt_le_audio_broadcast_dialog_sub_title" msgid="7889684551194225793">"A(z) <xliff:g id="SWITCHAPP">%1$s</xliff:g> közvetítése vagy a kimenet módosítása esetén a jelenlegi közvetítés leáll"</string>
<string name="bt_le_audio_broadcast_dialog_switch_app" msgid="6098768269397105733">"<xliff:g id="SWITCHAPP">%1$s</xliff:g> közvetítése"</string>
<string name="bt_le_audio_broadcast_dialog_different_output" msgid="7885102097302562674">"Kimenet módosítása"</string>
<string name="bt_le_audio_broadcast_dialog_unknown_name" msgid="3791472237793443044">"Ismeretlen"</string>
- <string name="dream_date_complication_date_format" msgid="8191225366513860104">"EEE, HHH n"</string>
<string name="dream_time_complication_12_hr_time_format" msgid="4691197486690291529">"ó:pp"</string>
<string name="dream_time_complication_24_hr_time_format" msgid="6248280719733640813">"óó:pp"</string>
<string name="keyguard_affordance_enablement_dialog_action_template" msgid="8164857863036314664">"A(z) <xliff:g id="APPNAME">%1$s</xliff:g> megnyitása"</string>
diff --git a/packages/SystemUI/res/values-hy/strings.xml b/packages/SystemUI/res/values-hy/strings.xml
index ea60237..61357e5 100644
--- a/packages/SystemUI/res/values-hy/strings.xml
+++ b/packages/SystemUI/res/values-hy/strings.xml
@@ -872,8 +872,7 @@
<string name="media_output_dialog_button_stop_casting" msgid="6581379537930199189">"Կանգնեցնել հեռարձակումը"</string>
<string name="media_output_dialog_accessibility_title" msgid="4681741064190167888">"Հասանելի սարքեր ձայնի արտածման համար։"</string>
<string name="media_output_dialog_accessibility_seekbar" msgid="5332843993805568978">"Ձայնի ուժգնություն"</string>
- <!-- no translation found for media_output_dialog_volume_percentage (1613984910585111798) -->
- <skip />
+ <string name="media_output_dialog_volume_percentage" msgid="1613984910585111798">"<xliff:g id="PERCENTAGE">%1$d</xliff:g>%%"</string>
<string name="media_output_first_broadcast_title" msgid="6292237789860753022">"Ինչպես է աշխատում հեռարձակումը"</string>
<string name="media_output_broadcast" msgid="3555580945878071543">"Հեռարձակում"</string>
<string name="media_output_first_notify_broadcast_message" msgid="6353857724136398494">"Ձեր մոտակայքում գտնվող՝ համատեղելի Bluetooth սարքերով մարդիկ կարող են լսել մեդիա ֆայլերը, որոնք դուք հեռարձակում եք։"</string>
@@ -987,15 +986,13 @@
<string name="dream_overlay_status_bar_camera_mic_off" msgid="3199425257833773569">"Տեսախցիկը և խոսափողն անջատված են"</string>
<string name="dream_overlay_status_bar_notification_indicator" msgid="8091389255691081711">"{count,plural, =1{# ծանուցում}one{# ծանուցում}other{# ծանուցում}}"</string>
<string name="dream_overlay_weather_complication_desc" msgid="824503662089783824">"<xliff:g id="WEATHER_CONDITION">%1$s</xliff:g>, <xliff:g id="TEMPERATURE">%2$s</xliff:g>"</string>
- <!-- no translation found for note_task_button_label (8718616095800343136) -->
- <skip />
+ <string name="note_task_button_label" msgid="8718616095800343136">"Նշումների ստեղծում"</string>
<string name="broadcasting_description_is_broadcasting" msgid="765627502786404290">"Հեռարձակում"</string>
<string name="bt_le_audio_broadcast_dialog_title" msgid="3605428497924077811">"Կանգնեցնել <xliff:g id="APP_NAME">%1$s</xliff:g> հավելվածի հեռարձակումը"</string>
<string name="bt_le_audio_broadcast_dialog_sub_title" msgid="7889684551194225793">"Եթե հեռարձակեք <xliff:g id="SWITCHAPP">%1$s</xliff:g> հավելվածը կամ փոխեք աուդիո ելքը, ձեր ընթացիկ հեռարձակումը կկանգնեցվի։"</string>
<string name="bt_le_audio_broadcast_dialog_switch_app" msgid="6098768269397105733">"Հեռարձակել <xliff:g id="SWITCHAPP">%1$s</xliff:g> հավելվածը"</string>
<string name="bt_le_audio_broadcast_dialog_different_output" msgid="7885102097302562674">"Փոխել աուդիո ելքը"</string>
<string name="bt_le_audio_broadcast_dialog_unknown_name" msgid="3791472237793443044">"Անհայտ"</string>
- <string name="dream_date_complication_date_format" msgid="8191225366513860104">"EEE, MMM d"</string>
<string name="dream_time_complication_12_hr_time_format" msgid="4691197486690291529">"h:mm"</string>
<string name="dream_time_complication_24_hr_time_format" msgid="6248280719733640813">"kk:mm"</string>
<string name="keyguard_affordance_enablement_dialog_action_template" msgid="8164857863036314664">"Բացել <xliff:g id="APPNAME">%1$s</xliff:g> հավելվածը"</string>
diff --git a/packages/SystemUI/res/values-in/strings.xml b/packages/SystemUI/res/values-in/strings.xml
index 3cdb26e..84ae4d4 100644
--- a/packages/SystemUI/res/values-in/strings.xml
+++ b/packages/SystemUI/res/values-in/strings.xml
@@ -872,8 +872,7 @@
<string name="media_output_dialog_button_stop_casting" msgid="6581379537930199189">"Hentikan transmisi"</string>
<string name="media_output_dialog_accessibility_title" msgid="4681741064190167888">"Perangkat yang tersedia untuk output audio."</string>
<string name="media_output_dialog_accessibility_seekbar" msgid="5332843993805568978">"Volume"</string>
- <!-- no translation found for media_output_dialog_volume_percentage (1613984910585111798) -->
- <skip />
+ <string name="media_output_dialog_volume_percentage" msgid="1613984910585111798">"<xliff:g id="PERCENTAGE">%1$d</xliff:g>%%"</string>
<string name="media_output_first_broadcast_title" msgid="6292237789860753022">"Cara kerja siaran"</string>
<string name="media_output_broadcast" msgid="3555580945878071543">"Siaran"</string>
<string name="media_output_first_notify_broadcast_message" msgid="6353857724136398494">"Orang di dekat Anda dengan perangkat Bluetooth yang kompatibel dapat mendengarkan media yang sedang Anda siarkan"</string>
@@ -987,15 +986,13 @@
<string name="dream_overlay_status_bar_camera_mic_off" msgid="3199425257833773569">"Kamera dan mikrofon nonaktif"</string>
<string name="dream_overlay_status_bar_notification_indicator" msgid="8091389255691081711">"{count,plural, =1{# notifikasi}other{# notifikasi}}"</string>
<string name="dream_overlay_weather_complication_desc" msgid="824503662089783824">"<xliff:g id="WEATHER_CONDITION">%1$s</xliff:g>, <xliff:g id="TEMPERATURE">%2$s</xliff:g>"</string>
- <!-- no translation found for note_task_button_label (8718616095800343136) -->
- <skip />
+ <string name="note_task_button_label" msgid="8718616095800343136">"Pembuatan catatan"</string>
<string name="broadcasting_description_is_broadcasting" msgid="765627502786404290">"Menyiarkan"</string>
<string name="bt_le_audio_broadcast_dialog_title" msgid="3605428497924077811">"Hentikan siaran <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string>
<string name="bt_le_audio_broadcast_dialog_sub_title" msgid="7889684551194225793">"Jika Anda menyiarkan <xliff:g id="SWITCHAPP">%1$s</xliff:g> atau mengubah output, siaran saat ini akan dihentikan"</string>
<string name="bt_le_audio_broadcast_dialog_switch_app" msgid="6098768269397105733">"Siarkan <xliff:g id="SWITCHAPP">%1$s</xliff:g>"</string>
<string name="bt_le_audio_broadcast_dialog_different_output" msgid="7885102097302562674">"Ubah output"</string>
<string name="bt_le_audio_broadcast_dialog_unknown_name" msgid="3791472237793443044">"Tidak diketahui"</string>
- <string name="dream_date_complication_date_format" msgid="8191225366513860104">"EEE, d MMM"</string>
<string name="dream_time_complication_12_hr_time_format" msgid="4691197486690291529">"h:mm"</string>
<string name="dream_time_complication_24_hr_time_format" msgid="6248280719733640813">"kk:mm"</string>
<string name="keyguard_affordance_enablement_dialog_action_template" msgid="8164857863036314664">"Buka <xliff:g id="APPNAME">%1$s</xliff:g>"</string>
diff --git a/packages/SystemUI/res/values-is/strings.xml b/packages/SystemUI/res/values-is/strings.xml
index 7c715382..85f7b72 100644
--- a/packages/SystemUI/res/values-is/strings.xml
+++ b/packages/SystemUI/res/values-is/strings.xml
@@ -872,8 +872,7 @@
<string name="media_output_dialog_button_stop_casting" msgid="6581379537930199189">"Stöðva útsendingu"</string>
<string name="media_output_dialog_accessibility_title" msgid="4681741064190167888">"Tæki í boði fyrir hljóðúttak."</string>
<string name="media_output_dialog_accessibility_seekbar" msgid="5332843993805568978">"Hljóðstyrkur"</string>
- <!-- no translation found for media_output_dialog_volume_percentage (1613984910585111798) -->
- <skip />
+ <string name="media_output_dialog_volume_percentage" msgid="1613984910585111798">"<xliff:g id="PERCENTAGE">%1$d</xliff:g>%%"</string>
<string name="media_output_first_broadcast_title" msgid="6292237789860753022">"Svona virkar útsending"</string>
<string name="media_output_broadcast" msgid="3555580945878071543">"Útsending"</string>
<string name="media_output_first_notify_broadcast_message" msgid="6353857724136398494">"Fólk nálægt þér með samhæf Bluetooth-tæki getur hlustað á efnið sem þú sendir út"</string>
@@ -987,15 +986,13 @@
<string name="dream_overlay_status_bar_camera_mic_off" msgid="3199425257833773569">"Slökkt á myndavél og hljóðnema"</string>
<string name="dream_overlay_status_bar_notification_indicator" msgid="8091389255691081711">"{count,plural, =1{# tilkynning}one{# tilkynning}other{# tilkynningar}}"</string>
<string name="dream_overlay_weather_complication_desc" msgid="824503662089783824">"<xliff:g id="WEATHER_CONDITION">%1$s</xliff:g>, <xliff:g id="TEMPERATURE">%2$s</xliff:g>"</string>
- <!-- no translation found for note_task_button_label (8718616095800343136) -->
- <skip />
+ <string name="note_task_button_label" msgid="8718616095800343136">"Glósugerð"</string>
<string name="broadcasting_description_is_broadcasting" msgid="765627502786404290">"Útsending í gangi"</string>
<string name="bt_le_audio_broadcast_dialog_title" msgid="3605428497924077811">"Hætta að senda út <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string>
<string name="bt_le_audio_broadcast_dialog_sub_title" msgid="7889684551194225793">"Ef þú sendir út <xliff:g id="SWITCHAPP">%1$s</xliff:g> eða skiptir um úttak lýkur yfirstandandi útsendingu"</string>
<string name="bt_le_audio_broadcast_dialog_switch_app" msgid="6098768269397105733">"Senda út <xliff:g id="SWITCHAPP">%1$s</xliff:g>"</string>
<string name="bt_le_audio_broadcast_dialog_different_output" msgid="7885102097302562674">"Skipta um úttak"</string>
<string name="bt_le_audio_broadcast_dialog_unknown_name" msgid="3791472237793443044">"Óþekkt"</string>
- <string name="dream_date_complication_date_format" msgid="8191225366513860104">"EEE, MMM d"</string>
<string name="dream_time_complication_12_hr_time_format" msgid="4691197486690291529">"k:mm"</string>
<string name="dream_time_complication_24_hr_time_format" msgid="6248280719733640813">"kk:mm"</string>
<string name="keyguard_affordance_enablement_dialog_action_template" msgid="8164857863036314664">"Opna <xliff:g id="APPNAME">%1$s</xliff:g>"</string>
diff --git a/packages/SystemUI/res/values-it/strings.xml b/packages/SystemUI/res/values-it/strings.xml
index f6c8354..0ca503d 100644
--- a/packages/SystemUI/res/values-it/strings.xml
+++ b/packages/SystemUI/res/values-it/strings.xml
@@ -872,8 +872,7 @@
<string name="media_output_dialog_button_stop_casting" msgid="6581379537930199189">"Interrompi trasmissione"</string>
<string name="media_output_dialog_accessibility_title" msgid="4681741064190167888">"Dispositivi disponibili per l\'uscita audio."</string>
<string name="media_output_dialog_accessibility_seekbar" msgid="5332843993805568978">"Volume"</string>
- <!-- no translation found for media_output_dialog_volume_percentage (1613984910585111798) -->
- <skip />
+ <string name="media_output_dialog_volume_percentage" msgid="1613984910585111798">"<xliff:g id="PERCENTAGE">%1$d</xliff:g>%%"</string>
<string name="media_output_first_broadcast_title" msgid="6292237789860753022">"Come funziona la trasmissione"</string>
<string name="media_output_broadcast" msgid="3555580945878071543">"Annuncio"</string>
<string name="media_output_first_notify_broadcast_message" msgid="6353857724136398494">"Le persone vicine a te che hanno dispositivi Bluetooth compatibili possono ascoltare i contenuti multimediali che stai trasmettendo"</string>
@@ -987,15 +986,13 @@
<string name="dream_overlay_status_bar_camera_mic_off" msgid="3199425257833773569">"Fotocamera e microfono non attivi"</string>
<string name="dream_overlay_status_bar_notification_indicator" msgid="8091389255691081711">"{count,plural, =1{# notifica}many{# notifiche}other{# notifiche}}"</string>
<string name="dream_overlay_weather_complication_desc" msgid="824503662089783824">"<xliff:g id="WEATHER_CONDITION">%1$s</xliff:g>, <xliff:g id="TEMPERATURE">%2$s</xliff:g>"</string>
- <!-- no translation found for note_task_button_label (8718616095800343136) -->
- <skip />
+ <string name="note_task_button_label" msgid="8718616095800343136">"Aggiunta di note"</string>
<string name="broadcasting_description_is_broadcasting" msgid="765627502786404290">"Trasmissione in corso…"</string>
<string name="bt_le_audio_broadcast_dialog_title" msgid="3605428497924077811">"Vuoi interrompere la trasmissione dell\'app <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string>
<string name="bt_le_audio_broadcast_dialog_sub_title" msgid="7889684551194225793">"Se trasmetti l\'app <xliff:g id="SWITCHAPP">%1$s</xliff:g> o cambi l\'uscita, la trasmissione attuale viene interrotta"</string>
<string name="bt_le_audio_broadcast_dialog_switch_app" msgid="6098768269397105733">"Trasmetti l\'app <xliff:g id="SWITCHAPP">%1$s</xliff:g>"</string>
<string name="bt_le_audio_broadcast_dialog_different_output" msgid="7885102097302562674">"Cambia uscita"</string>
<string name="bt_le_audio_broadcast_dialog_unknown_name" msgid="3791472237793443044">"Unknown"</string>
- <string name="dream_date_complication_date_format" msgid="8191225366513860104">"EEE, MMM g"</string>
<string name="dream_time_complication_12_hr_time_format" msgid="4691197486690291529">"h:mm"</string>
<string name="dream_time_complication_24_hr_time_format" msgid="6248280719733640813">"kk:mm"</string>
<string name="keyguard_affordance_enablement_dialog_action_template" msgid="8164857863036314664">"Apri <xliff:g id="APPNAME">%1$s</xliff:g>"</string>
diff --git a/packages/SystemUI/res/values-iw/strings.xml b/packages/SystemUI/res/values-iw/strings.xml
index 8e12a0b..5df872b 100644
--- a/packages/SystemUI/res/values-iw/strings.xml
+++ b/packages/SystemUI/res/values-iw/strings.xml
@@ -872,8 +872,7 @@
<string name="media_output_dialog_button_stop_casting" msgid="6581379537930199189">"עצירת ההעברה (casting)"</string>
<string name="media_output_dialog_accessibility_title" msgid="4681741064190167888">"מכשירים זמינים לפלט אודיו."</string>
<string name="media_output_dialog_accessibility_seekbar" msgid="5332843993805568978">"עוצמת הקול"</string>
- <!-- no translation found for media_output_dialog_volume_percentage (1613984910585111798) -->
- <skip />
+ <string name="media_output_dialog_volume_percentage" msgid="1613984910585111798">"<xliff:g id="PERCENTAGE">%1$d</xliff:g>%%"</string>
<string name="media_output_first_broadcast_title" msgid="6292237789860753022">"הסבר על שידורים"</string>
<string name="media_output_broadcast" msgid="3555580945878071543">"שידור"</string>
<string name="media_output_first_notify_broadcast_message" msgid="6353857724136398494">"אנשים בקרבת מקום עם מכשירי Bluetooth תואמים יכולים להאזין למדיה שמשודרת על ידך"</string>
@@ -987,15 +986,13 @@
<string name="dream_overlay_status_bar_camera_mic_off" msgid="3199425257833773569">"המצלמה והמיקרופון כבויים"</string>
<string name="dream_overlay_status_bar_notification_indicator" msgid="8091389255691081711">"{count,plural, =1{התראה אחת}one{# התראות}two{# התראות}other{# התראות}}"</string>
<string name="dream_overlay_weather_complication_desc" msgid="824503662089783824">"<xliff:g id="WEATHER_CONDITION">%1$s</xliff:g>, <xliff:g id="TEMPERATURE">%2$s</xliff:g>"</string>
- <!-- no translation found for note_task_button_label (8718616095800343136) -->
- <skip />
+ <string name="note_task_button_label" msgid="8718616095800343136">"כתיבת הערות"</string>
<string name="broadcasting_description_is_broadcasting" msgid="765627502786404290">"שידור"</string>
<string name="bt_le_audio_broadcast_dialog_title" msgid="3605428497924077811">"האם להפסיק לשדר את התוכן מאפליקציית <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string>
<string name="bt_le_audio_broadcast_dialog_sub_title" msgid="7889684551194225793">"אם משדרים את התוכן מאפליקציית <xliff:g id="SWITCHAPP">%1$s</xliff:g> או משנים את הפלט, השידור הנוכחי יפסיק לפעול"</string>
<string name="bt_le_audio_broadcast_dialog_switch_app" msgid="6098768269397105733">"שידור תוכן מאפליקציית <xliff:g id="SWITCHAPP">%1$s</xliff:g>"</string>
<string name="bt_le_audio_broadcast_dialog_different_output" msgid="7885102097302562674">"שינוי הפלט"</string>
<string name="bt_le_audio_broadcast_dialog_unknown_name" msgid="3791472237793443044">"לא ידוע"</string>
- <string name="dream_date_complication_date_format" msgid="8191225366513860104">"יום EEE, d בMMM"</string>
<string name="dream_time_complication_12_hr_time_format" msgid="4691197486690291529">"h:mm"</string>
<string name="dream_time_complication_24_hr_time_format" msgid="6248280719733640813">"kk:mm"</string>
<string name="keyguard_affordance_enablement_dialog_action_template" msgid="8164857863036314664">"פתיחת <xliff:g id="APPNAME">%1$s</xliff:g>"</string>
diff --git a/packages/SystemUI/res/values-ja/strings.xml b/packages/SystemUI/res/values-ja/strings.xml
index 0cacdbd..f15540c 100644
--- a/packages/SystemUI/res/values-ja/strings.xml
+++ b/packages/SystemUI/res/values-ja/strings.xml
@@ -993,7 +993,6 @@
<string name="bt_le_audio_broadcast_dialog_switch_app" msgid="6098768269397105733">"<xliff:g id="SWITCHAPP">%1$s</xliff:g> をブロードキャスト"</string>
<string name="bt_le_audio_broadcast_dialog_different_output" msgid="7885102097302562674">"出力を変更"</string>
<string name="bt_le_audio_broadcast_dialog_unknown_name" msgid="3791472237793443044">"不明"</string>
- <string name="dream_date_complication_date_format" msgid="8191225366513860104">"EEE, MMM d"</string>
<string name="dream_time_complication_12_hr_time_format" msgid="4691197486690291529">"h:mm"</string>
<string name="dream_time_complication_24_hr_time_format" msgid="6248280719733640813">"kk:mm"</string>
<string name="keyguard_affordance_enablement_dialog_action_template" msgid="8164857863036314664">"<xliff:g id="APPNAME">%1$s</xliff:g> を開く"</string>
diff --git a/packages/SystemUI/res/values-ka/strings.xml b/packages/SystemUI/res/values-ka/strings.xml
index 038937d..0626dd6 100644
--- a/packages/SystemUI/res/values-ka/strings.xml
+++ b/packages/SystemUI/res/values-ka/strings.xml
@@ -993,7 +993,6 @@
<string name="bt_le_audio_broadcast_dialog_switch_app" msgid="6098768269397105733">"<xliff:g id="SWITCHAPP">%1$s</xliff:g>-ის ტრანსლაცია"</string>
<string name="bt_le_audio_broadcast_dialog_different_output" msgid="7885102097302562674">"აუდიოს გამოსასვლელის შეცვლა"</string>
<string name="bt_le_audio_broadcast_dialog_unknown_name" msgid="3791472237793443044">"უცნობი"</string>
- <string name="dream_date_complication_date_format" msgid="8191225366513860104">"დდდ, თთთ თ"</string>
<string name="dream_time_complication_12_hr_time_format" msgid="4691197486690291529">"სთ:წთ"</string>
<string name="dream_time_complication_24_hr_time_format" msgid="6248280719733640813">"სთ:წთ"</string>
<string name="keyguard_affordance_enablement_dialog_action_template" msgid="8164857863036314664">"<xliff:g id="APPNAME">%1$s</xliff:g> აპის გახსნა"</string>
diff --git a/packages/SystemUI/res/values-kk/strings.xml b/packages/SystemUI/res/values-kk/strings.xml
index 91c0b35..1692bd4 100644
--- a/packages/SystemUI/res/values-kk/strings.xml
+++ b/packages/SystemUI/res/values-kk/strings.xml
@@ -872,8 +872,7 @@
<string name="media_output_dialog_button_stop_casting" msgid="6581379537930199189">"Трансляцияны тоқтату"</string>
<string name="media_output_dialog_accessibility_title" msgid="4681741064190167888">"Аудио шығыс үшін қолжетімді құрылғылар бар."</string>
<string name="media_output_dialog_accessibility_seekbar" msgid="5332843993805568978">"Дыбыс деңгейі"</string>
- <!-- no translation found for media_output_dialog_volume_percentage (1613984910585111798) -->
- <skip />
+ <string name="media_output_dialog_volume_percentage" msgid="1613984910585111798">"<xliff:g id="PERCENTAGE">%1$d</xliff:g>%%"</string>
<string name="media_output_first_broadcast_title" msgid="6292237789860753022">"Тарату қалай жүзеге асады"</string>
<string name="media_output_broadcast" msgid="3555580945878071543">"Тарату"</string>
<string name="media_output_first_notify_broadcast_message" msgid="6353857724136398494">"Үйлесімді Bluetooth құрылғылары бар маңайдағы адамдар сіз таратып жатқан медиамазмұнды тыңдай алады."</string>
@@ -987,15 +986,13 @@
<string name="dream_overlay_status_bar_camera_mic_off" msgid="3199425257833773569">"Камера мен микрофон өшірулі"</string>
<string name="dream_overlay_status_bar_notification_indicator" msgid="8091389255691081711">"{count,plural, =1{# хабарландыру}other{# хабарландыру}}"</string>
<string name="dream_overlay_weather_complication_desc" msgid="824503662089783824">"<xliff:g id="WEATHER_CONDITION">%1$s</xliff:g>, <xliff:g id="TEMPERATURE">%2$s</xliff:g>"</string>
- <!-- no translation found for note_task_button_label (8718616095800343136) -->
- <skip />
+ <string name="note_task_button_label" msgid="8718616095800343136">"Ескертпе жазу"</string>
<string name="broadcasting_description_is_broadcasting" msgid="765627502786404290">"Таратуда"</string>
<string name="bt_le_audio_broadcast_dialog_title" msgid="3605428497924077811">"<xliff:g id="APP_NAME">%1$s</xliff:g> қолданбасын таратуды тоқтатасыз ба?"</string>
<string name="bt_le_audio_broadcast_dialog_sub_title" msgid="7889684551194225793">"<xliff:g id="SWITCHAPP">%1$s</xliff:g> қолданбасын таратсаңыз немесе аудио шығысын өзгертсеңіз, қазіргі тарату сеансы тоқтайды."</string>
<string name="bt_le_audio_broadcast_dialog_switch_app" msgid="6098768269397105733">"<xliff:g id="SWITCHAPP">%1$s</xliff:g> қолданбасын тарату"</string>
<string name="bt_le_audio_broadcast_dialog_different_output" msgid="7885102097302562674">"Аудио шығысын өзгерту"</string>
<string name="bt_le_audio_broadcast_dialog_unknown_name" msgid="3791472237793443044">"Белгісіз"</string>
- <string name="dream_date_complication_date_format" msgid="8191225366513860104">"d MMM EEEE"</string>
<string name="dream_time_complication_12_hr_time_format" msgid="4691197486690291529">"h:mm"</string>
<string name="dream_time_complication_24_hr_time_format" msgid="6248280719733640813">"kk:mm"</string>
<string name="keyguard_affordance_enablement_dialog_action_template" msgid="8164857863036314664">"<xliff:g id="APPNAME">%1$s</xliff:g> ашу"</string>
diff --git a/packages/SystemUI/res/values-km/strings.xml b/packages/SystemUI/res/values-km/strings.xml
index 51e5b53..9158663 100644
--- a/packages/SystemUI/res/values-km/strings.xml
+++ b/packages/SystemUI/res/values-km/strings.xml
@@ -872,8 +872,7 @@
<string name="media_output_dialog_button_stop_casting" msgid="6581379537930199189">"បញ្ឈប់ការភ្ជាប់"</string>
<string name="media_output_dialog_accessibility_title" msgid="4681741064190167888">"ឧបករណ៍ដែលអាចប្រើបានសម្រាប់ឧបករណ៍បញ្ចេញសំឡេង។"</string>
<string name="media_output_dialog_accessibility_seekbar" msgid="5332843993805568978">"កម្រិតសំឡេង"</string>
- <!-- no translation found for media_output_dialog_volume_percentage (1613984910585111798) -->
- <skip />
+ <string name="media_output_dialog_volume_percentage" msgid="1613984910585111798">"<xliff:g id="PERCENTAGE">%1$d</xliff:g>%%"</string>
<string name="media_output_first_broadcast_title" msgid="6292237789860753022">"របៀបដែលការផ្សាយដំណើរការ"</string>
<string name="media_output_broadcast" msgid="3555580945878071543">"ការផ្សាយ"</string>
<string name="media_output_first_notify_broadcast_message" msgid="6353857724136398494">"មនុស្សនៅជិតអ្នកដែលមានឧបករណ៍ប៊្លូធូសត្រូវគ្នាអាចស្តាប់មេឌៀដែលអ្នកកំពុងផ្សាយបាន"</string>
@@ -987,15 +986,13 @@
<string name="dream_overlay_status_bar_camera_mic_off" msgid="3199425257833773569">"កាមេរ៉ា និងមីក្រូហ្វូនត្រូវបានបិទ"</string>
<string name="dream_overlay_status_bar_notification_indicator" msgid="8091389255691081711">"{count,plural, =1{ការជូនដំណឹង #}other{ការជូនដំណឹង #}}"</string>
<string name="dream_overlay_weather_complication_desc" msgid="824503662089783824">"<xliff:g id="WEATHER_CONDITION">%1$s</xliff:g>, <xliff:g id="TEMPERATURE">%2$s</xliff:g>"</string>
- <!-- no translation found for note_task_button_label (8718616095800343136) -->
- <skip />
+ <string name="note_task_button_label" msgid="8718616095800343136">"ការកត់ត្រា"</string>
<string name="broadcasting_description_is_broadcasting" msgid="765627502786404290">"ការផ្សាយ"</string>
<string name="bt_le_audio_broadcast_dialog_title" msgid="3605428497924077811">"បញ្ឈប់ការផ្សាយ <xliff:g id="APP_NAME">%1$s</xliff:g> ឬ?"</string>
<string name="bt_le_audio_broadcast_dialog_sub_title" msgid="7889684551194225793">"ប្រសិនបើអ្នកផ្សាយ <xliff:g id="SWITCHAPP">%1$s</xliff:g> ឬប្ដូរឧបករណ៍បញ្ចេញសំឡេង ការផ្សាយបច្ចុប្បន្នរបស់អ្នកនឹងបញ្ឈប់"</string>
<string name="bt_le_audio_broadcast_dialog_switch_app" msgid="6098768269397105733">"ការផ្សាយ <xliff:g id="SWITCHAPP">%1$s</xliff:g>"</string>
<string name="bt_le_audio_broadcast_dialog_different_output" msgid="7885102097302562674">"ប្ដូរឧបករណ៍បញ្ចេញសំឡេង"</string>
<string name="bt_le_audio_broadcast_dialog_unknown_name" msgid="3791472237793443044">"មិនស្គាល់"</string>
- <string name="dream_date_complication_date_format" msgid="8191225366513860104">"EEE, MMM d"</string>
<string name="dream_time_complication_12_hr_time_format" msgid="4691197486690291529">"h:mm"</string>
<string name="dream_time_complication_24_hr_time_format" msgid="6248280719733640813">"kk:mm"</string>
<string name="keyguard_affordance_enablement_dialog_action_template" msgid="8164857863036314664">"បើក <xliff:g id="APPNAME">%1$s</xliff:g>"</string>
diff --git a/packages/SystemUI/res/values-kn/strings.xml b/packages/SystemUI/res/values-kn/strings.xml
index 5baf68f..abfa865 100644
--- a/packages/SystemUI/res/values-kn/strings.xml
+++ b/packages/SystemUI/res/values-kn/strings.xml
@@ -993,7 +993,6 @@
<string name="bt_le_audio_broadcast_dialog_switch_app" msgid="6098768269397105733">"<xliff:g id="SWITCHAPP">%1$s</xliff:g> ಅನ್ನು ಪ್ರಸಾರ ಮಾಡಿ"</string>
<string name="bt_le_audio_broadcast_dialog_different_output" msgid="7885102097302562674">"ಔಟ್ಪುಟ್ ಅನ್ನು ಬದಲಾಯಿಸಿ"</string>
<string name="bt_le_audio_broadcast_dialog_unknown_name" msgid="3791472237793443044">"ಅಪರಿಚಿತ"</string>
- <string name="dream_date_complication_date_format" msgid="8191225366513860104">"EEE, MMM d"</string>
<string name="dream_time_complication_12_hr_time_format" msgid="4691197486690291529">"h:mm"</string>
<string name="dream_time_complication_24_hr_time_format" msgid="6248280719733640813">"kk:mm"</string>
<string name="keyguard_affordance_enablement_dialog_action_template" msgid="8164857863036314664">"<xliff:g id="APPNAME">%1$s</xliff:g> ಅನ್ನು ತೆರೆಯಿರಿ"</string>
diff --git a/packages/SystemUI/res/values-ko/strings.xml b/packages/SystemUI/res/values-ko/strings.xml
index 9b8e46a..d1ca240 100644
--- a/packages/SystemUI/res/values-ko/strings.xml
+++ b/packages/SystemUI/res/values-ko/strings.xml
@@ -872,8 +872,7 @@
<string name="media_output_dialog_button_stop_casting" msgid="6581379537930199189">"전송 중지"</string>
<string name="media_output_dialog_accessibility_title" msgid="4681741064190167888">"오디오 출력에 사용 가능한 기기입니다."</string>
<string name="media_output_dialog_accessibility_seekbar" msgid="5332843993805568978">"볼륨"</string>
- <!-- no translation found for media_output_dialog_volume_percentage (1613984910585111798) -->
- <skip />
+ <string name="media_output_dialog_volume_percentage" msgid="1613984910585111798">"<xliff:g id="PERCENTAGE">%1$d</xliff:g>%%"</string>
<string name="media_output_first_broadcast_title" msgid="6292237789860753022">"브로드캐스팅 작동 원리"</string>
<string name="media_output_broadcast" msgid="3555580945878071543">"브로드캐스트"</string>
<string name="media_output_first_notify_broadcast_message" msgid="6353857724136398494">"호환되는 블루투스 기기를 가진 근처의 사용자가 내가 브로드캐스트 중인 미디어를 수신 대기할 수 있습니다."</string>
@@ -987,15 +986,13 @@
<string name="dream_overlay_status_bar_camera_mic_off" msgid="3199425257833773569">"카메라 및 마이크가 사용 중지되었습니다."</string>
<string name="dream_overlay_status_bar_notification_indicator" msgid="8091389255691081711">"{count,plural, =1{알림 #개}other{알림 #개}}"</string>
<string name="dream_overlay_weather_complication_desc" msgid="824503662089783824">"<xliff:g id="WEATHER_CONDITION">%1$s</xliff:g>, <xliff:g id="TEMPERATURE">%2$s</xliff:g>"</string>
- <!-- no translation found for note_task_button_label (8718616095800343136) -->
- <skip />
+ <string name="note_task_button_label" msgid="8718616095800343136">"메모"</string>
<string name="broadcasting_description_is_broadcasting" msgid="765627502786404290">"방송 중"</string>
<string name="bt_le_audio_broadcast_dialog_title" msgid="3605428497924077811">"<xliff:g id="APP_NAME">%1$s</xliff:g> 방송을 중지하시겠습니까?"</string>
<string name="bt_le_audio_broadcast_dialog_sub_title" msgid="7889684551194225793">"<xliff:g id="SWITCHAPP">%1$s</xliff:g> 앱을 방송하거나 출력을 변경하면 기존 방송이 중단됩니다"</string>
<string name="bt_le_audio_broadcast_dialog_switch_app" msgid="6098768269397105733">"<xliff:g id="SWITCHAPP">%1$s</xliff:g> 방송"</string>
<string name="bt_le_audio_broadcast_dialog_different_output" msgid="7885102097302562674">"출력 변경"</string>
<string name="bt_le_audio_broadcast_dialog_unknown_name" msgid="3791472237793443044">"알 수 없음"</string>
- <string name="dream_date_complication_date_format" msgid="8191225366513860104">"MMM d일 EEE"</string>
<string name="dream_time_complication_12_hr_time_format" msgid="4691197486690291529">"h:mm"</string>
<string name="dream_time_complication_24_hr_time_format" msgid="6248280719733640813">"kk:mm"</string>
<string name="keyguard_affordance_enablement_dialog_action_template" msgid="8164857863036314664">"<xliff:g id="APPNAME">%1$s</xliff:g> 열기"</string>
diff --git a/packages/SystemUI/res/values-ky/strings.xml b/packages/SystemUI/res/values-ky/strings.xml
index bb55c62..93092ce 100644
--- a/packages/SystemUI/res/values-ky/strings.xml
+++ b/packages/SystemUI/res/values-ky/strings.xml
@@ -872,8 +872,7 @@
<string name="media_output_dialog_button_stop_casting" msgid="6581379537930199189">"Тышкы экранга чыгарууну токтотуу"</string>
<string name="media_output_dialog_accessibility_title" msgid="4681741064190167888">"Аудио чыгаруу үчүн жеткиликтүү түзмөктөр."</string>
<string name="media_output_dialog_accessibility_seekbar" msgid="5332843993805568978">"Үндүн катуулугу"</string>
- <!-- no translation found for media_output_dialog_volume_percentage (1613984910585111798) -->
- <skip />
+ <string name="media_output_dialog_volume_percentage" msgid="1613984910585111798">"<xliff:g id="PERCENTAGE">%1$d</xliff:g>%%"</string>
<string name="media_output_first_broadcast_title" msgid="6292237789860753022">"Кабарлоо кантип иштейт"</string>
<string name="media_output_broadcast" msgid="3555580945878071543">"Кабарлоо"</string>
<string name="media_output_first_notify_broadcast_message" msgid="6353857724136398494">"Шайкеш Bluetooth түзмөктөрү болгон жакын жердеги кишилер кабарлап жаткан медиаңызды уга алышат"</string>
@@ -987,15 +986,13 @@
<string name="dream_overlay_status_bar_camera_mic_off" msgid="3199425257833773569">"Камера жана микрофон өчүк"</string>
<string name="dream_overlay_status_bar_notification_indicator" msgid="8091389255691081711">"{count,plural, =1{# билдирме}other{# билдирме}}"</string>
<string name="dream_overlay_weather_complication_desc" msgid="824503662089783824">"<xliff:g id="WEATHER_CONDITION">%1$s</xliff:g>, <xliff:g id="TEMPERATURE">%2$s</xliff:g>"</string>
- <!-- no translation found for note_task_button_label (8718616095800343136) -->
- <skip />
+ <string name="note_task_button_label" msgid="8718616095800343136">"Эскертме жазуу"</string>
<string name="broadcasting_description_is_broadcasting" msgid="765627502786404290">"Кеңири таратуу"</string>
<string name="bt_le_audio_broadcast_dialog_title" msgid="3605428497924077811">"<xliff:g id="APP_NAME">%1$s</xliff:g> колдонмосунда кабарлоо токтотулсунбу?"</string>
<string name="bt_le_audio_broadcast_dialog_sub_title" msgid="7889684551194225793">"Эгер <xliff:g id="SWITCHAPP">%1$s</xliff:g> колдонмосунда кабарласаңыз же аудионун чыгуусун өзгөртсөңүз, учурдагы кабарлоо токтотулат"</string>
<string name="bt_le_audio_broadcast_dialog_switch_app" msgid="6098768269397105733">"<xliff:g id="SWITCHAPP">%1$s</xliff:g> колдонмосунда кабарлоо"</string>
<string name="bt_le_audio_broadcast_dialog_different_output" msgid="7885102097302562674">"Аудионун чыгуусун өзгөртүү"</string>
<string name="bt_le_audio_broadcast_dialog_unknown_name" msgid="3791472237793443044">"Белгисиз"</string>
- <string name="dream_date_complication_date_format" msgid="8191225366513860104">"EEE, MMM d"</string>
<string name="dream_time_complication_12_hr_time_format" msgid="4691197486690291529">"h:mm"</string>
<string name="dream_time_complication_24_hr_time_format" msgid="6248280719733640813">"kk:mm"</string>
<string name="keyguard_affordance_enablement_dialog_action_template" msgid="8164857863036314664">"<xliff:g id="APPNAME">%1$s</xliff:g> ачуу"</string>
diff --git a/packages/SystemUI/res/values-lo/strings.xml b/packages/SystemUI/res/values-lo/strings.xml
index da353e6..906c723 100644
--- a/packages/SystemUI/res/values-lo/strings.xml
+++ b/packages/SystemUI/res/values-lo/strings.xml
@@ -872,8 +872,7 @@
<string name="media_output_dialog_button_stop_casting" msgid="6581379537930199189">"ຢຸດການສົ່ງສັນຍານ"</string>
<string name="media_output_dialog_accessibility_title" msgid="4681741064190167888">"ອຸປະກອນທີ່ສາມາດໃຊ້ໄດ້ສຳລັບເອົ້າພຸດສຽງ."</string>
<string name="media_output_dialog_accessibility_seekbar" msgid="5332843993805568978">"ລະດັບສຽງ"</string>
- <!-- no translation found for media_output_dialog_volume_percentage (1613984910585111798) -->
- <skip />
+ <string name="media_output_dialog_volume_percentage" msgid="1613984910585111798">"<xliff:g id="PERCENTAGE">%1$d</xliff:g>%%"</string>
<string name="media_output_first_broadcast_title" msgid="6292237789860753022">"ການອອກອາກາດເຮັດວຽກແນວໃດ"</string>
<string name="media_output_broadcast" msgid="3555580945878071543">"ອອກອາກາດ"</string>
<string name="media_output_first_notify_broadcast_message" msgid="6353857724136398494">"ຄົນທີ່ຢູ່ໃກ້ທ່ານທີ່ມີອຸປະກອນ Bluetooth ທີ່ເຂົ້າກັນໄດ້ຈະສາມາດຟັງມີເດຍທີ່ທ່ານກຳລັງອອກອາກາດຢູ່ໄດ້"</string>
@@ -987,15 +986,13 @@
<string name="dream_overlay_status_bar_camera_mic_off" msgid="3199425257833773569">"ປິດກ້ອງຖ່າຍຮູບ ແລະ ໄມແລ້ວ"</string>
<string name="dream_overlay_status_bar_notification_indicator" msgid="8091389255691081711">"{count,plural, =1{# ການແຈ້ງເຕືອນ}other{# ການແຈ້ງເຕືອນ}}"</string>
<string name="dream_overlay_weather_complication_desc" msgid="824503662089783824">"<xliff:g id="WEATHER_CONDITION">%1$s</xliff:g>, <xliff:g id="TEMPERATURE">%2$s</xliff:g>"</string>
- <!-- no translation found for note_task_button_label (8718616095800343136) -->
- <skip />
+ <string name="note_task_button_label" msgid="8718616095800343136">"ການຈົດບັນທຶກ"</string>
<string name="broadcasting_description_is_broadcasting" msgid="765627502786404290">"ກຳລັງອອກອາກາດ"</string>
<string name="bt_le_audio_broadcast_dialog_title" msgid="3605428497924077811">"ຢຸດການອອກອາກາດ <xliff:g id="APP_NAME">%1$s</xliff:g> ບໍ?"</string>
<string name="bt_le_audio_broadcast_dialog_sub_title" msgid="7889684551194225793">"ຫາກທ່ານອອກອາກາດ <xliff:g id="SWITCHAPP">%1$s</xliff:g> ຫຼື ປ່ຽນເອົ້າພຸດ, ການອອກອາກາດປັດຈຸບັນຂອງທ່ານຈະຢຸດ"</string>
<string name="bt_le_audio_broadcast_dialog_switch_app" msgid="6098768269397105733">"ອອກອາກາດ <xliff:g id="SWITCHAPP">%1$s</xliff:g>"</string>
<string name="bt_le_audio_broadcast_dialog_different_output" msgid="7885102097302562674">"ປ່ຽນເອົ້າພຸດ"</string>
<string name="bt_le_audio_broadcast_dialog_unknown_name" msgid="3791472237793443044">"ບໍ່ຮູ້ຈັກ"</string>
- <string name="dream_date_complication_date_format" msgid="8191225366513860104">"EEE, MMM d"</string>
<string name="dream_time_complication_12_hr_time_format" msgid="4691197486690291529">"ຊມ:ນທ"</string>
<string name="dream_time_complication_24_hr_time_format" msgid="6248280719733640813">"ຊມ:ນທ"</string>
<string name="keyguard_affordance_enablement_dialog_action_template" msgid="8164857863036314664">"ເປີດ <xliff:g id="APPNAME">%1$s</xliff:g>"</string>
diff --git a/packages/SystemUI/res/values-lt/strings.xml b/packages/SystemUI/res/values-lt/strings.xml
index 96c4371..789b2b9 100644
--- a/packages/SystemUI/res/values-lt/strings.xml
+++ b/packages/SystemUI/res/values-lt/strings.xml
@@ -872,8 +872,7 @@
<string name="media_output_dialog_button_stop_casting" msgid="6581379537930199189">"Sustabdyti perdavimą"</string>
<string name="media_output_dialog_accessibility_title" msgid="4681741064190167888">"Pasiekiami garso išvesties įrenginiai."</string>
<string name="media_output_dialog_accessibility_seekbar" msgid="5332843993805568978">"Garsumas"</string>
- <!-- no translation found for media_output_dialog_volume_percentage (1613984910585111798) -->
- <skip />
+ <string name="media_output_dialog_volume_percentage" msgid="1613984910585111798">"<xliff:g id="PERCENTAGE">%1$d</xliff:g> %%"</string>
<string name="media_output_first_broadcast_title" msgid="6292237789860753022">"Kaip veikia transliacija"</string>
<string name="media_output_broadcast" msgid="3555580945878071543">"Transliacija"</string>
<string name="media_output_first_notify_broadcast_message" msgid="6353857724136398494">"Netoliese esantys žmonės, turintys suderinamus „Bluetooth“ įrenginius, gali klausyti jūsų transliuojamos medijos"</string>
@@ -987,15 +986,13 @@
<string name="dream_overlay_status_bar_camera_mic_off" msgid="3199425257833773569">"Vaizdo kamera ir mikrofonas išjungti"</string>
<string name="dream_overlay_status_bar_notification_indicator" msgid="8091389255691081711">"{count,plural, =1{# pranešimas}one{# pranešimas}few{# pranešimai}many{# pranešimo}other{# pranešimų}}"</string>
<string name="dream_overlay_weather_complication_desc" msgid="824503662089783824">"<xliff:g id="WEATHER_CONDITION">%1$s</xliff:g>, <xliff:g id="TEMPERATURE">%2$s</xliff:g>"</string>
- <!-- no translation found for note_task_button_label (8718616095800343136) -->
- <skip />
+ <string name="note_task_button_label" msgid="8718616095800343136">"Užrašų kūrimas"</string>
<string name="broadcasting_description_is_broadcasting" msgid="765627502786404290">"Transliavimas"</string>
<string name="bt_le_audio_broadcast_dialog_title" msgid="3605428497924077811">"Sustabdyti „<xliff:g id="APP_NAME">%1$s</xliff:g>“ transliaciją?"</string>
<string name="bt_le_audio_broadcast_dialog_sub_title" msgid="7889684551194225793">"Jei transliuosite „<xliff:g id="SWITCHAPP">%1$s</xliff:g>“ arba pakeisite išvestį, dabartinė transliacija bus sustabdyta"</string>
<string name="bt_le_audio_broadcast_dialog_switch_app" msgid="6098768269397105733">"Transliuoti „<xliff:g id="SWITCHAPP">%1$s</xliff:g>“"</string>
<string name="bt_le_audio_broadcast_dialog_different_output" msgid="7885102097302562674">"Keisti išvestį"</string>
<string name="bt_le_audio_broadcast_dialog_unknown_name" msgid="3791472237793443044">"Nežinoma"</string>
- <string name="dream_date_complication_date_format" msgid="8191225366513860104">"EEE, MMM d"</string>
<string name="dream_time_complication_12_hr_time_format" msgid="4691197486690291529">"h:mm"</string>
<string name="dream_time_complication_24_hr_time_format" msgid="6248280719733640813">"kk:mm"</string>
<string name="keyguard_affordance_enablement_dialog_action_template" msgid="8164857863036314664">"Atidaryti „<xliff:g id="APPNAME">%1$s</xliff:g>“"</string>
diff --git a/packages/SystemUI/res/values-lv/strings.xml b/packages/SystemUI/res/values-lv/strings.xml
index 1c22380..dbdbf71 100644
--- a/packages/SystemUI/res/values-lv/strings.xml
+++ b/packages/SystemUI/res/values-lv/strings.xml
@@ -872,8 +872,7 @@
<string name="media_output_dialog_button_stop_casting" msgid="6581379537930199189">"Apturēt apraidi"</string>
<string name="media_output_dialog_accessibility_title" msgid="4681741064190167888">"Audio izvadei pieejamās ierīces."</string>
<string name="media_output_dialog_accessibility_seekbar" msgid="5332843993805568978">"Skaļums"</string>
- <!-- no translation found for media_output_dialog_volume_percentage (1613984910585111798) -->
- <skip />
+ <string name="media_output_dialog_volume_percentage" msgid="1613984910585111798">"<xliff:g id="PERCENTAGE">%1$d</xliff:g>%%"</string>
<string name="media_output_first_broadcast_title" msgid="6292237789860753022">"Kā darbojas apraide"</string>
<string name="media_output_broadcast" msgid="3555580945878071543">"Apraide"</string>
<string name="media_output_first_notify_broadcast_message" msgid="6353857724136398494">"Tuvumā esošās personas ar saderīgām Bluetooth ierīcēm var klausīties jūsu apraidīto multivides saturu."</string>
@@ -987,15 +986,13 @@
<string name="dream_overlay_status_bar_camera_mic_off" msgid="3199425257833773569">"Kamera un mikrofons ir izslēgti"</string>
<string name="dream_overlay_status_bar_notification_indicator" msgid="8091389255691081711">"{count,plural, =1{# paziņojums}zero{# paziņojumu}one{# paziņojums}other{# paziņojumi}}"</string>
<string name="dream_overlay_weather_complication_desc" msgid="824503662089783824">"<xliff:g id="WEATHER_CONDITION">%1$s</xliff:g>, <xliff:g id="TEMPERATURE">%2$s</xliff:g>"</string>
- <!-- no translation found for note_task_button_label (8718616095800343136) -->
- <skip />
+ <string name="note_task_button_label" msgid="8718616095800343136">"Piezīmju pierakstīšana"</string>
<string name="broadcasting_description_is_broadcasting" msgid="765627502786404290">"Notiek apraidīšana"</string>
<string name="bt_le_audio_broadcast_dialog_title" msgid="3605428497924077811">"Vai apturēt lietotnes <xliff:g id="APP_NAME">%1$s</xliff:g> apraidīšanu?"</string>
<string name="bt_le_audio_broadcast_dialog_sub_title" msgid="7889684551194225793">"Ja sāksiet lietotnes <xliff:g id="SWITCHAPP">%1$s</xliff:g> apraidīšanu vai mainīsiet izvadi, pašreizējā apraide tiks apturēta"</string>
<string name="bt_le_audio_broadcast_dialog_switch_app" msgid="6098768269397105733">"Lietotnes <xliff:g id="SWITCHAPP">%1$s</xliff:g> apraide"</string>
<string name="bt_le_audio_broadcast_dialog_different_output" msgid="7885102097302562674">"Izvades maiņa"</string>
<string name="bt_le_audio_broadcast_dialog_unknown_name" msgid="3791472237793443044">"Nezināms"</string>
- <string name="dream_date_complication_date_format" msgid="8191225366513860104">"EEE, d. MMM"</string>
<string name="dream_time_complication_12_hr_time_format" msgid="4691197486690291529">"hh:mm"</string>
<string name="dream_time_complication_24_hr_time_format" msgid="6248280719733640813">"kk:mm"</string>
<string name="keyguard_affordance_enablement_dialog_action_template" msgid="8164857863036314664">"Atvērt lietotni <xliff:g id="APPNAME">%1$s</xliff:g>"</string>
diff --git a/packages/SystemUI/res/values-mk/strings.xml b/packages/SystemUI/res/values-mk/strings.xml
index 603151f..2d35cdb 100644
--- a/packages/SystemUI/res/values-mk/strings.xml
+++ b/packages/SystemUI/res/values-mk/strings.xml
@@ -993,7 +993,6 @@
<string name="bt_le_audio_broadcast_dialog_switch_app" msgid="6098768269397105733">"Емитување на <xliff:g id="SWITCHAPP">%1$s</xliff:g>"</string>
<string name="bt_le_audio_broadcast_dialog_different_output" msgid="7885102097302562674">"Променете излез"</string>
<string name="bt_le_audio_broadcast_dialog_unknown_name" msgid="3791472237793443044">"Непознато"</string>
- <string name="dream_date_complication_date_format" msgid="8191225366513860104">"EEE, MMM d"</string>
<string name="dream_time_complication_12_hr_time_format" msgid="4691197486690291529">"h:mm"</string>
<string name="dream_time_complication_24_hr_time_format" msgid="6248280719733640813">"kk:mm"</string>
<string name="keyguard_affordance_enablement_dialog_action_template" msgid="8164857863036314664">"Отворете ја <xliff:g id="APPNAME">%1$s</xliff:g>"</string>
diff --git a/packages/SystemUI/res/values-ml/strings.xml b/packages/SystemUI/res/values-ml/strings.xml
index 5df7305..409b578 100644
--- a/packages/SystemUI/res/values-ml/strings.xml
+++ b/packages/SystemUI/res/values-ml/strings.xml
@@ -872,8 +872,7 @@
<string name="media_output_dialog_button_stop_casting" msgid="6581379537930199189">"കാസ്റ്റ് ചെയ്യുന്നത് നിർത്തുക"</string>
<string name="media_output_dialog_accessibility_title" msgid="4681741064190167888">"ഓഡിയോ ഔട്ട്പുട്ടിന് ലഭ്യമായ ഉപകരണങ്ങൾ."</string>
<string name="media_output_dialog_accessibility_seekbar" msgid="5332843993805568978">"വോളിയം"</string>
- <!-- no translation found for media_output_dialog_volume_percentage (1613984910585111798) -->
- <skip />
+ <string name="media_output_dialog_volume_percentage" msgid="1613984910585111798">"<xliff:g id="PERCENTAGE">%1$d</xliff:g>%%"</string>
<string name="media_output_first_broadcast_title" msgid="6292237789860753022">"ബ്രോഡ്കാസ്റ്റ് എങ്ങനെയാണ് പ്രവർത്തിക്കുന്നത്"</string>
<string name="media_output_broadcast" msgid="3555580945878071543">"ബ്രോഡ്കാസ്റ്റ്"</string>
<string name="media_output_first_notify_broadcast_message" msgid="6353857724136398494">"അനുയോജ്യമായ Bluetooth ഉപകരണങ്ങളോടെ സമീപമുള്ള ആളുകൾക്ക് നിങ്ങൾ ബ്രോഡ്കാസ്റ്റ് ചെയ്യുന്ന മീഡിയ കേൾക്കാനാകും"</string>
@@ -987,15 +986,13 @@
<string name="dream_overlay_status_bar_camera_mic_off" msgid="3199425257833773569">"ക്യാമറയും മൈക്കും ഓഫാണ്"</string>
<string name="dream_overlay_status_bar_notification_indicator" msgid="8091389255691081711">"{count,plural, =1{# അറിയിപ്പ്}other{# അറിയിപ്പുകൾ}}"</string>
<string name="dream_overlay_weather_complication_desc" msgid="824503662089783824">"<xliff:g id="WEATHER_CONDITION">%1$s</xliff:g>, <xliff:g id="TEMPERATURE">%2$s</xliff:g>"</string>
- <!-- no translation found for note_task_button_label (8718616095800343136) -->
- <skip />
+ <string name="note_task_button_label" msgid="8718616095800343136">"കുറിപ്പ് രേഖപ്പെടുത്തൽ"</string>
<string name="broadcasting_description_is_broadcasting" msgid="765627502786404290">"പ്രക്ഷേപണം ചെയ്യുന്നു"</string>
<string name="bt_le_audio_broadcast_dialog_title" msgid="3605428497924077811">"<xliff:g id="APP_NAME">%1$s</xliff:g> ബ്രോഡ്കാസ്റ്റ് ചെയ്യുന്നത് അവസാനിപ്പിക്കണോ?"</string>
<string name="bt_le_audio_broadcast_dialog_sub_title" msgid="7889684551194225793">"നിങ്ങൾ <xliff:g id="SWITCHAPP">%1$s</xliff:g> ബ്രോഡ്കാസ്റ്റ് ചെയ്യുകയോ ഔട്ട്പുട്ട് മാറ്റുകയോ ചെയ്താൽ നിങ്ങളുടെ നിലവിലുള്ള ബ്രോഡ്കാസ്റ്റ് അവസാനിക്കും"</string>
<string name="bt_le_audio_broadcast_dialog_switch_app" msgid="6098768269397105733">"<xliff:g id="SWITCHAPP">%1$s</xliff:g> ബ്രോഡ്കാസ്റ്റ് ചെയ്യുക"</string>
<string name="bt_le_audio_broadcast_dialog_different_output" msgid="7885102097302562674">"ഔട്ട്പുട്ട് മാറ്റുക"</string>
<string name="bt_le_audio_broadcast_dialog_unknown_name" msgid="3791472237793443044">"അജ്ഞാതം"</string>
- <string name="dream_date_complication_date_format" msgid="8191225366513860104">"EEE, MMM d"</string>
<string name="dream_time_complication_12_hr_time_format" msgid="4691197486690291529">"h:mm"</string>
<string name="dream_time_complication_24_hr_time_format" msgid="6248280719733640813">"kk:mm"</string>
<string name="keyguard_affordance_enablement_dialog_action_template" msgid="8164857863036314664">"<xliff:g id="APPNAME">%1$s</xliff:g> തുറക്കുക"</string>
diff --git a/packages/SystemUI/res/values-mn/strings.xml b/packages/SystemUI/res/values-mn/strings.xml
index aa57e81..8c3a70f 100644
--- a/packages/SystemUI/res/values-mn/strings.xml
+++ b/packages/SystemUI/res/values-mn/strings.xml
@@ -872,8 +872,7 @@
<string name="media_output_dialog_button_stop_casting" msgid="6581379537930199189">"Дамжуулахыг зогсоох"</string>
<string name="media_output_dialog_accessibility_title" msgid="4681741064190167888">"Аудио гаралт хийх боломжтой төхөөрөмжүүд."</string>
<string name="media_output_dialog_accessibility_seekbar" msgid="5332843993805568978">"Дууны түвшин"</string>
- <!-- no translation found for media_output_dialog_volume_percentage (1613984910585111798) -->
- <skip />
+ <string name="media_output_dialog_volume_percentage" msgid="1613984910585111798">"<xliff:g id="PERCENTAGE">%1$d</xliff:g>%%"</string>
<string name="media_output_first_broadcast_title" msgid="6292237789860753022">"Нэвтрүүлэлт хэрхэн ажилладаг вэ?"</string>
<string name="media_output_broadcast" msgid="3555580945878071543">"Нэвтрүүлэлт"</string>
<string name="media_output_first_notify_broadcast_message" msgid="6353857724136398494">"Тохиромжтой Bluetooth төхөөрөмжүүдтэй таны ойролцоох хүмүүс таны нэвтрүүлж буй медиаг сонсох боломжтой"</string>
@@ -987,15 +986,13 @@
<string name="dream_overlay_status_bar_camera_mic_off" msgid="3199425257833773569">"Камер болон микрофон унтраалттай байна"</string>
<string name="dream_overlay_status_bar_notification_indicator" msgid="8091389255691081711">"{count,plural, =1{# мэдэгдэл}other{# мэдэгдэл}}"</string>
<string name="dream_overlay_weather_complication_desc" msgid="824503662089783824">"<xliff:g id="WEATHER_CONDITION">%1$s</xliff:g>, <xliff:g id="TEMPERATURE">%2$s</xliff:g>"</string>
- <!-- no translation found for note_task_button_label (8718616095800343136) -->
- <skip />
+ <string name="note_task_button_label" msgid="8718616095800343136">"Тэмдэглэл хөтлөх"</string>
<string name="broadcasting_description_is_broadcasting" msgid="765627502786404290">"Нэвтрүүлэлт"</string>
<string name="bt_le_audio_broadcast_dialog_title" msgid="3605428497924077811">"<xliff:g id="APP_NAME">%1$s</xliff:g>-г нэвтрүүлэхээ зогсоох уу?"</string>
<string name="bt_le_audio_broadcast_dialog_sub_title" msgid="7889684551194225793">"Хэрэв та <xliff:g id="SWITCHAPP">%1$s</xliff:g>-г нэвтрүүлсэн эсвэл гаралтыг өөрчилсөн бол таны одоогийн нэвтрүүлэлтийг зогсооно"</string>
<string name="bt_le_audio_broadcast_dialog_switch_app" msgid="6098768269397105733">"<xliff:g id="SWITCHAPP">%1$s</xliff:g>-г нэвтрүүлэх"</string>
<string name="bt_le_audio_broadcast_dialog_different_output" msgid="7885102097302562674">"Гаралтыг өөрчлөх"</string>
<string name="bt_le_audio_broadcast_dialog_unknown_name" msgid="3791472237793443044">"Тодорхойгүй"</string>
- <string name="dream_date_complication_date_format" msgid="8191225366513860104">"MMM d EEE"</string>
<string name="dream_time_complication_12_hr_time_format" msgid="4691197486690291529">"h:mm"</string>
<string name="dream_time_complication_24_hr_time_format" msgid="6248280719733640813">"kk:mm"</string>
<string name="keyguard_affordance_enablement_dialog_action_template" msgid="8164857863036314664">"<xliff:g id="APPNAME">%1$s</xliff:g>-г нээх"</string>
diff --git a/packages/SystemUI/res/values-mr/strings.xml b/packages/SystemUI/res/values-mr/strings.xml
index 7f3c28c..6583057 100644
--- a/packages/SystemUI/res/values-mr/strings.xml
+++ b/packages/SystemUI/res/values-mr/strings.xml
@@ -872,8 +872,7 @@
<string name="media_output_dialog_button_stop_casting" msgid="6581379537930199189">"कास्ट करणे थांबवा"</string>
<string name="media_output_dialog_accessibility_title" msgid="4681741064190167888">"ऑडिओ आउटपुटसाठी उपलब्ध डिव्हाइस."</string>
<string name="media_output_dialog_accessibility_seekbar" msgid="5332843993805568978">"व्हॉल्यूम"</string>
- <!-- no translation found for media_output_dialog_volume_percentage (1613984910585111798) -->
- <skip />
+ <string name="media_output_dialog_volume_percentage" msgid="1613984910585111798">"<xliff:g id="PERCENTAGE">%1$d</xliff:g>%%"</string>
<string name="media_output_first_broadcast_title" msgid="6292237789860753022">"ब्रॉडकास्टिंग कसे काम करते"</string>
<string name="media_output_broadcast" msgid="3555580945878071543">"ब्रॉडकास्ट करा"</string>
<string name="media_output_first_notify_broadcast_message" msgid="6353857724136398494">"कंपॅटिबल ब्लूटूथ डिव्हाइस असलेले तुमच्या जवळपासचे लोक हे तुम्ही ब्रॉडकास्ट करत असलेला मीडिया ऐकू शकतात"</string>
@@ -987,15 +986,13 @@
<string name="dream_overlay_status_bar_camera_mic_off" msgid="3199425257833773569">"कॅमेरा आणि माइक बंद आहेत"</string>
<string name="dream_overlay_status_bar_notification_indicator" msgid="8091389255691081711">"{count,plural, =1{# सूचना}other{# सूचना}}"</string>
<string name="dream_overlay_weather_complication_desc" msgid="824503662089783824">"<xliff:g id="WEATHER_CONDITION">%1$s</xliff:g>, <xliff:g id="TEMPERATURE">%2$s</xliff:g>"</string>
- <!-- no translation found for note_task_button_label (8718616095800343136) -->
- <skip />
+ <string name="note_task_button_label" msgid="8718616095800343136">"नोटटेकिंग"</string>
<string name="broadcasting_description_is_broadcasting" msgid="765627502786404290">"ब्रॉडकास्ट करत आहे"</string>
<string name="bt_le_audio_broadcast_dialog_title" msgid="3605428497924077811">"<xliff:g id="APP_NAME">%1$s</xliff:g> चे प्रसारण थांबवायचे आहे का?"</string>
<string name="bt_le_audio_broadcast_dialog_sub_title" msgid="7889684551194225793">"तुम्ही <xliff:g id="SWITCHAPP">%1$s</xliff:g> चे प्रसारण केल्यास किंवा आउटपुट बदलल्यास, तुमचे सध्याचे प्रसारण बंद होईल"</string>
<string name="bt_le_audio_broadcast_dialog_switch_app" msgid="6098768269397105733">"<xliff:g id="SWITCHAPP">%1$s</xliff:g> चे प्रसारण करा"</string>
<string name="bt_le_audio_broadcast_dialog_different_output" msgid="7885102097302562674">"आउटपूट बदला"</string>
<string name="bt_le_audio_broadcast_dialog_unknown_name" msgid="3791472237793443044">"अज्ञात"</string>
- <string name="dream_date_complication_date_format" msgid="8191225366513860104">"EEE, MMM d"</string>
<string name="dream_time_complication_12_hr_time_format" msgid="4691197486690291529">"h:mm"</string>
<string name="dream_time_complication_24_hr_time_format" msgid="6248280719733640813">"kk:mm"</string>
<string name="keyguard_affordance_enablement_dialog_action_template" msgid="8164857863036314664">"<xliff:g id="APPNAME">%1$s</xliff:g> उघडा"</string>
diff --git a/packages/SystemUI/res/values-ms/strings.xml b/packages/SystemUI/res/values-ms/strings.xml
index bfbc8ea..0140112 100644
--- a/packages/SystemUI/res/values-ms/strings.xml
+++ b/packages/SystemUI/res/values-ms/strings.xml
@@ -993,7 +993,6 @@
<string name="bt_le_audio_broadcast_dialog_switch_app" msgid="6098768269397105733">"Siarkan <xliff:g id="SWITCHAPP">%1$s</xliff:g>"</string>
<string name="bt_le_audio_broadcast_dialog_different_output" msgid="7885102097302562674">"Tukar output"</string>
<string name="bt_le_audio_broadcast_dialog_unknown_name" msgid="3791472237793443044">"Tidak diketahui"</string>
- <string name="dream_date_complication_date_format" msgid="8191225366513860104">"EEE, d MMM"</string>
<string name="dream_time_complication_12_hr_time_format" msgid="4691197486690291529">"h:mm"</string>
<string name="dream_time_complication_24_hr_time_format" msgid="6248280719733640813">"kk:mm"</string>
<string name="keyguard_affordance_enablement_dialog_action_template" msgid="8164857863036314664">"Buka <xliff:g id="APPNAME">%1$s</xliff:g>"</string>
diff --git a/packages/SystemUI/res/values-my/strings.xml b/packages/SystemUI/res/values-my/strings.xml
index 909b9ab..164d662 100644
--- a/packages/SystemUI/res/values-my/strings.xml
+++ b/packages/SystemUI/res/values-my/strings.xml
@@ -874,8 +874,7 @@
<string name="media_output_dialog_button_stop_casting" msgid="6581379537930199189">"ကာစ် ရပ်ရန်"</string>
<string name="media_output_dialog_accessibility_title" msgid="4681741064190167888">"အသံအထွက်အတွက် ရရှိနိုင်သောစက်များ။"</string>
<string name="media_output_dialog_accessibility_seekbar" msgid="5332843993805568978">"အသံအတိုးအကျယ်"</string>
- <!-- no translation found for media_output_dialog_volume_percentage (1613984910585111798) -->
- <skip />
+ <string name="media_output_dialog_volume_percentage" msgid="1613984910585111798">"<xliff:g id="PERCENTAGE">%1$d</xliff:g>%%"</string>
<string name="media_output_first_broadcast_title" msgid="6292237789860753022">"ထုတ်လွှင့်မှုဆောင်ရွက်ပုံ"</string>
<string name="media_output_broadcast" msgid="3555580945878071543">"ထုတ်လွှင့်ခြင်း"</string>
<string name="media_output_first_notify_broadcast_message" msgid="6353857724136398494">"အနီးရှိတွဲသုံးနိုင်သော ဘလူးတုသ်သုံးစက် အသုံးပြုသူများက သင်ထုတ်လွှင့်နေသော မီဒီယာကို နားဆင်နိုင်သည်"</string>
@@ -989,15 +988,13 @@
<string name="dream_overlay_status_bar_camera_mic_off" msgid="3199425257833773569">"ကင်မရာနှင့် မိုက် ပိတ်ထားသည်"</string>
<string name="dream_overlay_status_bar_notification_indicator" msgid="8091389255691081711">"{count,plural, =1{အကြောင်းကြားချက် # ခု}other{အကြောင်းကြားချက် # ခု}}"</string>
<string name="dream_overlay_weather_complication_desc" msgid="824503662089783824">"<xliff:g id="WEATHER_CONDITION">%1$s</xliff:g>၊ <xliff:g id="TEMPERATURE">%2$s</xliff:g>"</string>
- <!-- no translation found for note_task_button_label (8718616095800343136) -->
- <skip />
+ <string name="note_task_button_label" msgid="8718616095800343136">"မှတ်စုလိုက်ခြင်း"</string>
<string name="broadcasting_description_is_broadcasting" msgid="765627502786404290">"ထုတ်လွှင့်ခြင်း"</string>
<string name="bt_le_audio_broadcast_dialog_title" msgid="3605428497924077811">"<xliff:g id="APP_NAME">%1$s</xliff:g> ထုတ်လွှင့်ခြင်းကို ရပ်မလား။"</string>
<string name="bt_le_audio_broadcast_dialog_sub_title" msgid="7889684551194225793">"<xliff:g id="SWITCHAPP">%1$s</xliff:g> ကို ထုတ်လွှင့်သောအခါ (သို့) အထွက်ကို ပြောင်းသောအခါ သင့်လက်ရှိထုတ်လွှင့်ခြင်း ရပ်သွားမည်"</string>
<string name="bt_le_audio_broadcast_dialog_switch_app" msgid="6098768269397105733">"<xliff:g id="SWITCHAPP">%1$s</xliff:g> ထုတ်လွှင့်ခြင်း"</string>
<string name="bt_le_audio_broadcast_dialog_different_output" msgid="7885102097302562674">"အထွက်ကို ပြောင်းခြင်း"</string>
<string name="bt_le_audio_broadcast_dialog_unknown_name" msgid="3791472237793443044">"မသိ"</string>
- <string name="dream_date_complication_date_format" msgid="8191225366513860104">"EEE၊ MMM d"</string>
<string name="dream_time_complication_12_hr_time_format" msgid="4691197486690291529">"h:mm"</string>
<string name="dream_time_complication_24_hr_time_format" msgid="6248280719733640813">"kk:mm"</string>
<string name="keyguard_affordance_enablement_dialog_action_template" msgid="8164857863036314664">"<xliff:g id="APPNAME">%1$s</xliff:g> ဖွင့်ရန်"</string>
diff --git a/packages/SystemUI/res/values-nb/strings.xml b/packages/SystemUI/res/values-nb/strings.xml
index 95846f0..6288a15 100644
--- a/packages/SystemUI/res/values-nb/strings.xml
+++ b/packages/SystemUI/res/values-nb/strings.xml
@@ -872,8 +872,7 @@
<string name="media_output_dialog_button_stop_casting" msgid="6581379537930199189">"Stopp castingen"</string>
<string name="media_output_dialog_accessibility_title" msgid="4681741064190167888">"Tilgjengelige enheter for lydutgang."</string>
<string name="media_output_dialog_accessibility_seekbar" msgid="5332843993805568978">"Volum"</string>
- <!-- no translation found for media_output_dialog_volume_percentage (1613984910585111798) -->
- <skip />
+ <string name="media_output_dialog_volume_percentage" msgid="1613984910585111798">"<xliff:g id="PERCENTAGE">%1$d</xliff:g> %%"</string>
<string name="media_output_first_broadcast_title" msgid="6292237789860753022">"Slik fungerer kringkasting"</string>
<string name="media_output_broadcast" msgid="3555580945878071543">"Kringkasting"</string>
<string name="media_output_first_notify_broadcast_message" msgid="6353857724136398494">"Folk i nærheten med kompatible Bluetooth-enheter kan lytte til mediene du kringkaster"</string>
@@ -987,15 +986,13 @@
<string name="dream_overlay_status_bar_camera_mic_off" msgid="3199425257833773569">"Kamera og mikrofon er av"</string>
<string name="dream_overlay_status_bar_notification_indicator" msgid="8091389255691081711">"{count,plural, =1{# varsel}other{# varsler}}"</string>
<string name="dream_overlay_weather_complication_desc" msgid="824503662089783824">"<xliff:g id="WEATHER_CONDITION">%1$s</xliff:g>, <xliff:g id="TEMPERATURE">%2$s</xliff:g>"</string>
- <!-- no translation found for note_task_button_label (8718616095800343136) -->
- <skip />
+ <string name="note_task_button_label" msgid="8718616095800343136">"Notatskriving"</string>
<string name="broadcasting_description_is_broadcasting" msgid="765627502786404290">"Kringkaster"</string>
<string name="bt_le_audio_broadcast_dialog_title" msgid="3605428497924077811">"Vil du stoppe kringkastingen av <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string>
<string name="bt_le_audio_broadcast_dialog_sub_title" msgid="7889684551194225793">"Hvis du kringkaster <xliff:g id="SWITCHAPP">%1$s</xliff:g> eller endrer utgangen, stopper den nåværende kringkastingen din"</string>
<string name="bt_le_audio_broadcast_dialog_switch_app" msgid="6098768269397105733">"Kringkast <xliff:g id="SWITCHAPP">%1$s</xliff:g>"</string>
<string name="bt_le_audio_broadcast_dialog_different_output" msgid="7885102097302562674">"Endre utgang"</string>
<string name="bt_le_audio_broadcast_dialog_unknown_name" msgid="3791472237793443044">"Ukjent"</string>
- <string name="dream_date_complication_date_format" msgid="8191225366513860104">"EEE d. MMM"</string>
<string name="dream_time_complication_12_hr_time_format" msgid="4691197486690291529">"h:mm"</string>
<string name="dream_time_complication_24_hr_time_format" msgid="6248280719733640813">"kk:mm"</string>
<string name="keyguard_affordance_enablement_dialog_action_template" msgid="8164857863036314664">"Åpne <xliff:g id="APPNAME">%1$s</xliff:g>"</string>
diff --git a/packages/SystemUI/res/values-ne/strings.xml b/packages/SystemUI/res/values-ne/strings.xml
index b04e368..563d608 100644
--- a/packages/SystemUI/res/values-ne/strings.xml
+++ b/packages/SystemUI/res/values-ne/strings.xml
@@ -500,8 +500,7 @@
<string name="wallet_error_generic" msgid="257704570182963611">"तपाईंका कार्डहरू प्राप्त गर्ने क्रममा समस्या भयो, कृपया पछि फेरि प्रयास गर्नुहोस्"</string>
<string name="wallet_lockscreen_settings_label" msgid="3539105300870383570">"लक स्क्रिनसम्बन्धी सेटिङ"</string>
<string name="qr_code_scanner_title" msgid="1938155688725760702">"QR कोड स्क्यानर"</string>
- <!-- no translation found for qr_code_scanner_updating_secondary_label (8344598017007876352) -->
- <skip />
+ <string name="qr_code_scanner_updating_secondary_label" msgid="8344598017007876352">"अपडेट गरिँदै छ"</string>
<string name="status_bar_work" msgid="5238641949837091056">"कार्य प्रोफाइल"</string>
<string name="status_bar_airplane" msgid="4848702508684541009">"हवाइजहाज मोड"</string>
<string name="zen_alarm_warning" msgid="7844303238486849503">"तपाईँले आफ्नो अर्को अलार्म <xliff:g id="WHEN">%1$s</xliff:g> सुन्नुहुने छैन"</string>
@@ -873,8 +872,7 @@
<string name="media_output_dialog_button_stop_casting" msgid="6581379537930199189">"कास्ट गर्न छाड्नुहोस्"</string>
<string name="media_output_dialog_accessibility_title" msgid="4681741064190167888">"अडियो आउटपुटका लागि उपलब्ध डिभाइसहरू।"</string>
<string name="media_output_dialog_accessibility_seekbar" msgid="5332843993805568978">"भोल्युम"</string>
- <!-- no translation found for media_output_dialog_volume_percentage (1613984910585111798) -->
- <skip />
+ <string name="media_output_dialog_volume_percentage" msgid="1613984910585111798">"<xliff:g id="PERCENTAGE">%1$d</xliff:g>%%"</string>
<string name="media_output_first_broadcast_title" msgid="6292237789860753022">"प्रसारण गर्ने सुविधाले कसरी काम गर्छ"</string>
<string name="media_output_broadcast" msgid="3555580945878071543">"प्रसारण"</string>
<string name="media_output_first_notify_broadcast_message" msgid="6353857724136398494">"कम्प्याटिबल ब्लुटुथ डिभाइस भएका नजिकैका मान्छेहरू तपाईंले प्रसारण गरिरहनुभएको मिडिया सुन्न सक्छन्"</string>
@@ -988,31 +986,22 @@
<string name="dream_overlay_status_bar_camera_mic_off" msgid="3199425257833773569">"क्यामेरा र माइक अफ छन्"</string>
<string name="dream_overlay_status_bar_notification_indicator" msgid="8091389255691081711">"{count,plural, =1{# वटा सूचना}other{# वटा सूचनाहरू}}"</string>
<string name="dream_overlay_weather_complication_desc" msgid="824503662089783824">"<xliff:g id="WEATHER_CONDITION">%1$s</xliff:g>, <xliff:g id="TEMPERATURE">%2$s</xliff:g>"</string>
- <!-- no translation found for note_task_button_label (8718616095800343136) -->
- <skip />
+ <string name="note_task_button_label" msgid="8718616095800343136">"टिपोट गर्ने कार्य"</string>
<string name="broadcasting_description_is_broadcasting" msgid="765627502786404290">"प्रसारण गरिँदै छ"</string>
<string name="bt_le_audio_broadcast_dialog_title" msgid="3605428497924077811">"<xliff:g id="APP_NAME">%1$s</xliff:g> ब्रोडकास्ट गर्न छाड्ने हो?"</string>
<string name="bt_le_audio_broadcast_dialog_sub_title" msgid="7889684551194225793">"तपाईंले <xliff:g id="SWITCHAPP">%1$s</xliff:g> ब्रोडकास्ट गर्नुभयो वा आउटपुट परिवर्तन गर्नुभयो भने तपाईंको हालको ब्रोडकास्ट रोकिने छ"</string>
<string name="bt_le_audio_broadcast_dialog_switch_app" msgid="6098768269397105733">"<xliff:g id="SWITCHAPP">%1$s</xliff:g> ब्रोडकास्ट गर्नुहोस्"</string>
<string name="bt_le_audio_broadcast_dialog_different_output" msgid="7885102097302562674">"आउटपुट परिवर्तन गर्नुहोस्"</string>
<string name="bt_le_audio_broadcast_dialog_unknown_name" msgid="3791472237793443044">"अज्ञात"</string>
- <string name="dream_date_complication_date_format" msgid="8191225366513860104">"EEE, MMM d"</string>
<string name="dream_time_complication_12_hr_time_format" msgid="4691197486690291529">"h:mm"</string>
<string name="dream_time_complication_24_hr_time_format" msgid="6248280719733640813">"kk:mm"</string>
- <!-- no translation found for keyguard_affordance_enablement_dialog_action_template (8164857863036314664) -->
- <skip />
- <!-- no translation found for keyguard_affordance_enablement_dialog_message (2790910660524887941) -->
- <skip />
- <!-- no translation found for keyguard_affordance_enablement_dialog_wallet_instruction_1 (8439655049139819278) -->
- <skip />
- <!-- no translation found for keyguard_affordance_enablement_dialog_wallet_instruction_2 (4321089250629477835) -->
- <skip />
- <!-- no translation found for keyguard_affordance_enablement_dialog_qr_scanner_instruction (5355839079232119791) -->
- <skip />
- <!-- no translation found for keyguard_affordance_enablement_dialog_home_instruction_1 (8438311171750568633) -->
- <skip />
- <!-- no translation found for keyguard_affordance_enablement_dialog_home_instruction_2 (8308525385889021652) -->
- <skip />
+ <string name="keyguard_affordance_enablement_dialog_action_template" msgid="8164857863036314664">"<xliff:g id="APPNAME">%1$s</xliff:g> खोल्नुहोस्"</string>
+ <string name="keyguard_affordance_enablement_dialog_message" msgid="2790910660524887941">"<xliff:g id="APPNAME">%1$s</xliff:g> एपलाई सर्टकटका रूपमा हाल्न, निम्न कुराको सुनिश्चित गर्नुहोस्:"</string>
+ <string name="keyguard_affordance_enablement_dialog_wallet_instruction_1" msgid="8439655049139819278">"• एप सेटअप गरिएको छ"</string>
+ <string name="keyguard_affordance_enablement_dialog_wallet_instruction_2" msgid="4321089250629477835">"• Wallet मा कम्तीमा एउटा कार्ड हालिएको छ"</string>
+ <string name="keyguard_affordance_enablement_dialog_qr_scanner_instruction" msgid="5355839079232119791">"• क्यामेरा एप इन्स्टल गरिएको छ"</string>
+ <string name="keyguard_affordance_enablement_dialog_home_instruction_1" msgid="8438311171750568633">"• एप सेटअप गरिएको छ"</string>
+ <string name="keyguard_affordance_enablement_dialog_home_instruction_2" msgid="8308525385889021652">"• कम्तीमा एउटा डिभाइस उपलब्ध छ"</string>
<string name="rear_display_bottom_sheet_cancel" msgid="3461468855493357248">"रद्द गर्नुहोस्"</string>
<string name="rear_display_bottom_sheet_confirm" msgid="4383356544661421206">"अहिले नै फ्लिप गर्नुहोस्"</string>
<string name="rear_display_fold_bottom_sheet_title" msgid="6081542277622721548">"अझ राम्रो सेल्फी खिच्न फोन अनफोल्ड गर्नुहोस्"</string>
diff --git a/packages/SystemUI/res/values-nl/strings.xml b/packages/SystemUI/res/values-nl/strings.xml
index 0c05b12..ee73a6f 100644
--- a/packages/SystemUI/res/values-nl/strings.xml
+++ b/packages/SystemUI/res/values-nl/strings.xml
@@ -872,8 +872,7 @@
<string name="media_output_dialog_button_stop_casting" msgid="6581379537930199189">"Casten stoppen"</string>
<string name="media_output_dialog_accessibility_title" msgid="4681741064190167888">"Beschikbare apparaten voor audio-uitvoer."</string>
<string name="media_output_dialog_accessibility_seekbar" msgid="5332843993805568978">"Volume"</string>
- <!-- no translation found for media_output_dialog_volume_percentage (1613984910585111798) -->
- <skip />
+ <string name="media_output_dialog_volume_percentage" msgid="1613984910585111798">"<xliff:g id="PERCENTAGE">%1$d</xliff:g>%%"</string>
<string name="media_output_first_broadcast_title" msgid="6292237789860753022">"Hoe uitzenden werkt"</string>
<string name="media_output_broadcast" msgid="3555580945878071543">"Uitzending"</string>
<string name="media_output_first_notify_broadcast_message" msgid="6353857724136398494">"Mensen bij jou in de buurt met geschikte bluetooth-apparaten kunnen luisteren naar de media die je uitzendt"</string>
@@ -987,15 +986,13 @@
<string name="dream_overlay_status_bar_camera_mic_off" msgid="3199425257833773569">"Camera en microfoon staan uit"</string>
<string name="dream_overlay_status_bar_notification_indicator" msgid="8091389255691081711">"{count,plural, =1{# melding}other{# meldingen}}"</string>
<string name="dream_overlay_weather_complication_desc" msgid="824503662089783824">"<xliff:g id="WEATHER_CONDITION">%1$s</xliff:g>, <xliff:g id="TEMPERATURE">%2$s</xliff:g>"</string>
- <!-- no translation found for note_task_button_label (8718616095800343136) -->
- <skip />
+ <string name="note_task_button_label" msgid="8718616095800343136">"Aantekeningen maken"</string>
<string name="broadcasting_description_is_broadcasting" msgid="765627502786404290">"Uitzending"</string>
<string name="bt_le_audio_broadcast_dialog_title" msgid="3605428497924077811">"Uitzending van <xliff:g id="APP_NAME">%1$s</xliff:g> stopzetten?"</string>
<string name="bt_le_audio_broadcast_dialog_sub_title" msgid="7889684551194225793">"Als je <xliff:g id="SWITCHAPP">%1$s</xliff:g> uitzendt of de uitvoer wijzigt, wordt je huidige uitzending gestopt"</string>
<string name="bt_le_audio_broadcast_dialog_switch_app" msgid="6098768269397105733">"<xliff:g id="SWITCHAPP">%1$s</xliff:g> uitzenden"</string>
<string name="bt_le_audio_broadcast_dialog_different_output" msgid="7885102097302562674">"Uitvoer wijzigen"</string>
<string name="bt_le_audio_broadcast_dialog_unknown_name" msgid="3791472237793443044">"Onbekend"</string>
- <string name="dream_date_complication_date_format" msgid="8191225366513860104">"EEE d mmm"</string>
<string name="dream_time_complication_12_hr_time_format" msgid="4691197486690291529">"u:mm"</string>
<string name="dream_time_complication_24_hr_time_format" msgid="6248280719733640813">"kk:mm"</string>
<string name="keyguard_affordance_enablement_dialog_action_template" msgid="8164857863036314664">"<xliff:g id="APPNAME">%1$s</xliff:g> openen"</string>
diff --git a/packages/SystemUI/res/values-or/strings.xml b/packages/SystemUI/res/values-or/strings.xml
index 307b7b9..455f1f9 100644
--- a/packages/SystemUI/res/values-or/strings.xml
+++ b/packages/SystemUI/res/values-or/strings.xml
@@ -872,8 +872,7 @@
<string name="media_output_dialog_button_stop_casting" msgid="6581379537930199189">"କାଷ୍ଟ କରିବା ବନ୍ଦ କରନ୍ତୁ"</string>
<string name="media_output_dialog_accessibility_title" msgid="4681741064190167888">"ଅଡିଓ ଆଉଟପୁଟ ପାଇଁ ଉପଲବ୍ଧ ଡିଭାଇସଗୁଡ଼ିକ।"</string>
<string name="media_output_dialog_accessibility_seekbar" msgid="5332843993805568978">"ଭଲ୍ୟୁମ"</string>
- <!-- no translation found for media_output_dialog_volume_percentage (1613984910585111798) -->
- <skip />
+ <string name="media_output_dialog_volume_percentage" msgid="1613984910585111798">"<xliff:g id="PERCENTAGE">%1$d</xliff:g>%%"</string>
<string name="media_output_first_broadcast_title" msgid="6292237789860753022">"ବ୍ରଡକାଷ୍ଟିଂ କିପରି କାମ କରେ"</string>
<string name="media_output_broadcast" msgid="3555580945878071543">"ବ୍ରଡକାଷ୍ଟ"</string>
<string name="media_output_first_notify_broadcast_message" msgid="6353857724136398494">"ଆପଣଙ୍କ ଆଖପାଖର କମ୍ପାଟିବଲ ବ୍ଲୁଟୁଥ ଡିଭାଇସ ଥିବା ଲୋକମାନେ ଆପଣ ବ୍ରଡକାଷ୍ଟ କରୁଥିବା ମିଡିଆ ଶୁଣିପାରିବେ"</string>
@@ -987,15 +986,13 @@
<string name="dream_overlay_status_bar_camera_mic_off" msgid="3199425257833773569">"କ୍ୟାମେରା ଏବଂ ମାଇକ ବନ୍ଦ ଅଛି"</string>
<string name="dream_overlay_status_bar_notification_indicator" msgid="8091389255691081711">"{count,plural, =1{#ଟି ବିଜ୍ଞପ୍ତି}other{#ଟି ବିଜ୍ଞପ୍ତି}}"</string>
<string name="dream_overlay_weather_complication_desc" msgid="824503662089783824">"<xliff:g id="WEATHER_CONDITION">%1$s</xliff:g>, <xliff:g id="TEMPERATURE">%2$s</xliff:g>"</string>
- <!-- no translation found for note_task_button_label (8718616095800343136) -->
- <skip />
+ <string name="note_task_button_label" msgid="8718616095800343136">"ନୋଟଟେକିଂ"</string>
<string name="broadcasting_description_is_broadcasting" msgid="765627502786404290">"ବ୍ରଡକାଷ୍ଟ କରୁଛି"</string>
<string name="bt_le_audio_broadcast_dialog_title" msgid="3605428497924077811">"<xliff:g id="APP_NAME">%1$s</xliff:g> ବ୍ରଡକାଷ୍ଟ କରିବା ବନ୍ଦ କରିବେ?"</string>
<string name="bt_le_audio_broadcast_dialog_sub_title" msgid="7889684551194225793">"ଯଦି ଆପଣ <xliff:g id="SWITCHAPP">%1$s</xliff:g> ବ୍ରଡକାଷ୍ଟ କରନ୍ତି କିମ୍ବା ଆଉଟପୁଟ ବଦଳାନ୍ତି, ତେବେ ଆପଣଙ୍କ ବର୍ତ୍ତମାନର ବ୍ରଡକାଷ୍ଟ ବନ୍ଦ ହୋଇଯିବ"</string>
<string name="bt_le_audio_broadcast_dialog_switch_app" msgid="6098768269397105733">"<xliff:g id="SWITCHAPP">%1$s</xliff:g> ବ୍ରଡକାଷ୍ଟ କରନ୍ତୁ"</string>
<string name="bt_le_audio_broadcast_dialog_different_output" msgid="7885102097302562674">"ଆଉଟପୁଟ ବଦଳାନ୍ତୁ"</string>
<string name="bt_le_audio_broadcast_dialog_unknown_name" msgid="3791472237793443044">"ଅଜଣା"</string>
- <string name="dream_date_complication_date_format" msgid="8191225366513860104">"EEE, MMM d"</string>
<string name="dream_time_complication_12_hr_time_format" msgid="4691197486690291529">"h:mm"</string>
<string name="dream_time_complication_24_hr_time_format" msgid="6248280719733640813">"kk:mm"</string>
<string name="keyguard_affordance_enablement_dialog_action_template" msgid="8164857863036314664">"<xliff:g id="APPNAME">%1$s</xliff:g> ଖୋଲନ୍ତୁ"</string>
diff --git a/packages/SystemUI/res/values-pa/strings.xml b/packages/SystemUI/res/values-pa/strings.xml
index ccf27d7..3e3d3e8 100644
--- a/packages/SystemUI/res/values-pa/strings.xml
+++ b/packages/SystemUI/res/values-pa/strings.xml
@@ -872,8 +872,7 @@
<string name="media_output_dialog_button_stop_casting" msgid="6581379537930199189">"ਕਾਸਟ ਕਰਨਾ ਬੰਦ ਕਰੋ"</string>
<string name="media_output_dialog_accessibility_title" msgid="4681741064190167888">"ਆਡੀਓ ਆਊਟਪੁੱਟ ਲਈ ਉਪਲਬਧ ਡੀਵਾਈਸ।"</string>
<string name="media_output_dialog_accessibility_seekbar" msgid="5332843993805568978">"ਅਵਾਜ਼"</string>
- <!-- no translation found for media_output_dialog_volume_percentage (1613984910585111798) -->
- <skip />
+ <string name="media_output_dialog_volume_percentage" msgid="1613984910585111798">"<xliff:g id="PERCENTAGE">%1$d</xliff:g>%%"</string>
<string name="media_output_first_broadcast_title" msgid="6292237789860753022">"ਪ੍ਰਸਾਰਨ ਕਿਵੇਂ ਕੰਮ ਕਰਦਾ ਹੈ"</string>
<string name="media_output_broadcast" msgid="3555580945878071543">"ਪ੍ਰਸਾਰਨ"</string>
<string name="media_output_first_notify_broadcast_message" msgid="6353857724136398494">"ਅਨੁਰੂਪ ਬਲੂਟੁੱਥ ਡੀਵਾਈਸਾਂ ਨਾਲ ਨਜ਼ਦੀਕੀ ਲੋਕ ਤੁਹਾਡੇ ਵੱਲੋਂ ਪ੍ਰਸਾਰਨ ਕੀਤੇ ਜਾ ਰਹੇ ਮੀਡੀਆ ਨੂੰ ਸੁਣ ਸਕਦੇ ਹਨ"</string>
@@ -987,15 +986,13 @@
<string name="dream_overlay_status_bar_camera_mic_off" msgid="3199425257833773569">"ਕੈਮਰਾ ਅਤੇ ਮਾਈਕ ਬੰਦ ਹਨ"</string>
<string name="dream_overlay_status_bar_notification_indicator" msgid="8091389255691081711">"{count,plural, =1{# ਸੂਚਨਾ}one{# ਸੂਚਨਾ}other{# ਸੂਚਨਾਵਾਂ}}"</string>
<string name="dream_overlay_weather_complication_desc" msgid="824503662089783824">"<xliff:g id="WEATHER_CONDITION">%1$s</xliff:g>, <xliff:g id="TEMPERATURE">%2$s</xliff:g>"</string>
- <!-- no translation found for note_task_button_label (8718616095800343136) -->
- <skip />
+ <string name="note_task_button_label" msgid="8718616095800343136">"ਨੋਟ ਬਣਾਉਣਾ"</string>
<string name="broadcasting_description_is_broadcasting" msgid="765627502786404290">"ਪ੍ਰਸਾਰਨ"</string>
<string name="bt_le_audio_broadcast_dialog_title" msgid="3605428497924077811">"ਕੀ <xliff:g id="APP_NAME">%1$s</xliff:g> ਦੇ ਪ੍ਰਸਾਰਨ ਨੂੰ ਰੋਕਣਾ ਹੈ?"</string>
<string name="bt_le_audio_broadcast_dialog_sub_title" msgid="7889684551194225793">"ਜੇ ਤੁਸੀਂ <xliff:g id="SWITCHAPP">%1$s</xliff:g> ਦਾ ਪ੍ਰਸਾਰਨ ਕਰਦੇ ਹੋ ਜਾਂ ਆਊਟਪੁੱਟ ਬਦਲਦੇ ਹੋ, ਤਾਂ ਤੁਹਾਡਾ ਮੌਜੂਦਾ ਪ੍ਰਸਾਰਨ ਰੁਕ ਜਾਵੇਗਾ"</string>
<string name="bt_le_audio_broadcast_dialog_switch_app" msgid="6098768269397105733">"<xliff:g id="SWITCHAPP">%1$s</xliff:g> ਦਾ ਪ੍ਰਸਾਰਨ ਕਰੋ"</string>
<string name="bt_le_audio_broadcast_dialog_different_output" msgid="7885102097302562674">"ਆਊਟਪੁੱਟ ਬਦਲੋ"</string>
<string name="bt_le_audio_broadcast_dialog_unknown_name" msgid="3791472237793443044">"ਅਗਿਆਤ"</string>
- <string name="dream_date_complication_date_format" msgid="8191225366513860104">"EEE, MMM d"</string>
<string name="dream_time_complication_12_hr_time_format" msgid="4691197486690291529">"h:mm"</string>
<string name="dream_time_complication_24_hr_time_format" msgid="6248280719733640813">"kk:mm"</string>
<string name="keyguard_affordance_enablement_dialog_action_template" msgid="8164857863036314664">"<xliff:g id="APPNAME">%1$s</xliff:g> ਖੋਲ੍ਹੋ"</string>
diff --git a/packages/SystemUI/res/values-pl/strings.xml b/packages/SystemUI/res/values-pl/strings.xml
index c82f75c..1b68eec 100644
--- a/packages/SystemUI/res/values-pl/strings.xml
+++ b/packages/SystemUI/res/values-pl/strings.xml
@@ -872,8 +872,7 @@
<string name="media_output_dialog_button_stop_casting" msgid="6581379537930199189">"Zatrzymaj przesyłanie"</string>
<string name="media_output_dialog_accessibility_title" msgid="4681741064190167888">"Dostępne urządzenia do odtwarzania dźwięku."</string>
<string name="media_output_dialog_accessibility_seekbar" msgid="5332843993805568978">"Głośność"</string>
- <!-- no translation found for media_output_dialog_volume_percentage (1613984910585111798) -->
- <skip />
+ <string name="media_output_dialog_volume_percentage" msgid="1613984910585111798">"<xliff:g id="PERCENTAGE">%1$d</xliff:g>%%"</string>
<string name="media_output_first_broadcast_title" msgid="6292237789860753022">"Jak działa transmitowanie"</string>
<string name="media_output_broadcast" msgid="3555580945878071543">"Transmisja"</string>
<string name="media_output_first_notify_broadcast_message" msgid="6353857724136398494">"Osoby w pobliżu ze zgodnymi urządzeniami Bluetooth mogą słuchać transmitowanych przez Ciebie multimediów"</string>
@@ -987,15 +986,13 @@
<string name="dream_overlay_status_bar_camera_mic_off" msgid="3199425257833773569">"Aparat i mikrofon są wyłączone"</string>
<string name="dream_overlay_status_bar_notification_indicator" msgid="8091389255691081711">"{count,plural, =1{# powiadomienie}few{# powiadomienia}many{# powiadomień}other{# powiadomienia}}"</string>
<string name="dream_overlay_weather_complication_desc" msgid="824503662089783824">"<xliff:g id="WEATHER_CONDITION">%1$s</xliff:g>, <xliff:g id="TEMPERATURE">%2$s</xliff:g>"</string>
- <!-- no translation found for note_task_button_label (8718616095800343136) -->
- <skip />
+ <string name="note_task_button_label" msgid="8718616095800343136">"Notatki"</string>
<string name="broadcasting_description_is_broadcasting" msgid="765627502786404290">"Transmisja"</string>
<string name="bt_le_audio_broadcast_dialog_title" msgid="3605428497924077811">"Zatrzymaj transmisję aplikacji <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string>
<string name="bt_le_audio_broadcast_dialog_sub_title" msgid="7889684551194225793">"Jeśli transmitujesz aplikację <xliff:g id="SWITCHAPP">%1$s</xliff:g> lub zmieniasz dane wyjściowe, Twoja obecna transmisja zostanie zakończona"</string>
<string name="bt_le_audio_broadcast_dialog_switch_app" msgid="6098768269397105733">"Transmisja aplikacji <xliff:g id="SWITCHAPP">%1$s</xliff:g>"</string>
<string name="bt_le_audio_broadcast_dialog_different_output" msgid="7885102097302562674">"Zmień dane wyjściowe"</string>
<string name="bt_le_audio_broadcast_dialog_unknown_name" msgid="3791472237793443044">"Brak informacji"</string>
- <string name="dream_date_complication_date_format" msgid="8191225366513860104">"EEE, d MMM"</string>
<string name="dream_time_complication_12_hr_time_format" msgid="4691197486690291529">"h:mm"</string>
<string name="dream_time_complication_24_hr_time_format" msgid="6248280719733640813">"kk:mm"</string>
<string name="keyguard_affordance_enablement_dialog_action_template" msgid="8164857863036314664">"Otwórz: <xliff:g id="APPNAME">%1$s</xliff:g>"</string>
diff --git a/packages/SystemUI/res/values-pt-rBR/strings.xml b/packages/SystemUI/res/values-pt-rBR/strings.xml
index f6a1c5c..0e7c68a 100644
--- a/packages/SystemUI/res/values-pt-rBR/strings.xml
+++ b/packages/SystemUI/res/values-pt-rBR/strings.xml
@@ -993,7 +993,6 @@
<string name="bt_le_audio_broadcast_dialog_switch_app" msgid="6098768269397105733">"Transmitir <xliff:g id="SWITCHAPP">%1$s</xliff:g>"</string>
<string name="bt_le_audio_broadcast_dialog_different_output" msgid="7885102097302562674">"Mudar saída"</string>
<string name="bt_le_audio_broadcast_dialog_unknown_name" msgid="3791472237793443044">"Desconhecido"</string>
- <string name="dream_date_complication_date_format" msgid="8191225366513860104">"EEE, d de MMM"</string>
<string name="dream_time_complication_12_hr_time_format" msgid="4691197486690291529">"h:mm"</string>
<string name="dream_time_complication_24_hr_time_format" msgid="6248280719733640813">"kk:mm"</string>
<string name="keyguard_affordance_enablement_dialog_action_template" msgid="8164857863036314664">"Abrir <xliff:g id="APPNAME">%1$s</xliff:g>"</string>
@@ -1009,8 +1008,6 @@
<string name="rear_display_unfold_bottom_sheet_title" msgid="2137403802960396357">"Usar o display frontal para tirar uma selfie melhor?"</string>
<string name="rear_display_bottom_sheet_description" msgid="1852662982816810352">"Use a câmera traseira para tirar uma foto mais ampla e com maior resolução."</string>
<string name="rear_display_bottom_sheet_warning" msgid="800995919558238930"><b>"✱ Esta tela vai ser desativada"</b></string>
- <!-- no translation found for rear_display_accessibility_folded_animation (1538121649587978179) -->
- <skip />
- <!-- no translation found for rear_display_accessibility_unfolded_animation (1946153682258289040) -->
- <skip />
+ <string name="rear_display_accessibility_folded_animation" msgid="1538121649587978179">"Dispositivo dobrável sendo aberto"</string>
+ <string name="rear_display_accessibility_unfolded_animation" msgid="1946153682258289040">"Dispositivo dobrável sendo virado"</string>
</resources>
diff --git a/packages/SystemUI/res/values-pt-rPT/strings.xml b/packages/SystemUI/res/values-pt-rPT/strings.xml
index 98b5c76..a38550f 100644
--- a/packages/SystemUI/res/values-pt-rPT/strings.xml
+++ b/packages/SystemUI/res/values-pt-rPT/strings.xml
@@ -872,8 +872,7 @@
<string name="media_output_dialog_button_stop_casting" msgid="6581379537930199189">"Parar transmissão"</string>
<string name="media_output_dialog_accessibility_title" msgid="4681741064190167888">"Dispositivos disponíveis para a saída de áudio."</string>
<string name="media_output_dialog_accessibility_seekbar" msgid="5332843993805568978">"Volume"</string>
- <!-- no translation found for media_output_dialog_volume_percentage (1613984910585111798) -->
- <skip />
+ <string name="media_output_dialog_volume_percentage" msgid="1613984910585111798">"<xliff:g id="PERCENTAGE">%1$d</xliff:g>%%"</string>
<string name="media_output_first_broadcast_title" msgid="6292237789860753022">"Como funciona a transmissão"</string>
<string name="media_output_broadcast" msgid="3555580945878071543">"Transmissão"</string>
<string name="media_output_first_notify_broadcast_message" msgid="6353857724136398494">"As pessoas próximas de si com dispositivos Bluetooth compatíveis podem ouvir o conteúdo multimédia que está a transmitir"</string>
@@ -987,15 +986,13 @@
<string name="dream_overlay_status_bar_camera_mic_off" msgid="3199425257833773569">"A câmara e o microfone estão desativados"</string>
<string name="dream_overlay_status_bar_notification_indicator" msgid="8091389255691081711">"{count,plural, =1{# notificação}many{# notificações}other{# notificações}}"</string>
<string name="dream_overlay_weather_complication_desc" msgid="824503662089783824">"<xliff:g id="WEATHER_CONDITION">%1$s</xliff:g>, <xliff:g id="TEMPERATURE">%2$s</xliff:g>"</string>
- <!-- no translation found for note_task_button_label (8718616095800343136) -->
- <skip />
+ <string name="note_task_button_label" msgid="8718616095800343136">"Tomar notas"</string>
<string name="broadcasting_description_is_broadcasting" msgid="765627502786404290">"A transmitir"</string>
<string name="bt_le_audio_broadcast_dialog_title" msgid="3605428497924077811">"Interromper a transmissão da app <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string>
<string name="bt_le_audio_broadcast_dialog_sub_title" msgid="7889684551194225793">"Se transmitir a app <xliff:g id="SWITCHAPP">%1$s</xliff:g> ou alterar a saída, a sua transmissão atual é interrompida"</string>
<string name="bt_le_audio_broadcast_dialog_switch_app" msgid="6098768269397105733">"Transmita a app <xliff:g id="SWITCHAPP">%1$s</xliff:g>"</string>
<string name="bt_le_audio_broadcast_dialog_different_output" msgid="7885102097302562674">"Altere a saída"</string>
<string name="bt_le_audio_broadcast_dialog_unknown_name" msgid="3791472237793443044">"Desconhecida"</string>
- <string name="dream_date_complication_date_format" msgid="8191225366513860104">"EEE, d de MMM"</string>
<string name="dream_time_complication_12_hr_time_format" msgid="4691197486690291529">"h:mm"</string>
<string name="dream_time_complication_24_hr_time_format" msgid="6248280719733640813">"kk:mm"</string>
<string name="keyguard_affordance_enablement_dialog_action_template" msgid="8164857863036314664">"Abrir <xliff:g id="APPNAME">%1$s</xliff:g>"</string>
diff --git a/packages/SystemUI/res/values-pt/strings.xml b/packages/SystemUI/res/values-pt/strings.xml
index f6a1c5c..0e7c68a 100644
--- a/packages/SystemUI/res/values-pt/strings.xml
+++ b/packages/SystemUI/res/values-pt/strings.xml
@@ -993,7 +993,6 @@
<string name="bt_le_audio_broadcast_dialog_switch_app" msgid="6098768269397105733">"Transmitir <xliff:g id="SWITCHAPP">%1$s</xliff:g>"</string>
<string name="bt_le_audio_broadcast_dialog_different_output" msgid="7885102097302562674">"Mudar saída"</string>
<string name="bt_le_audio_broadcast_dialog_unknown_name" msgid="3791472237793443044">"Desconhecido"</string>
- <string name="dream_date_complication_date_format" msgid="8191225366513860104">"EEE, d de MMM"</string>
<string name="dream_time_complication_12_hr_time_format" msgid="4691197486690291529">"h:mm"</string>
<string name="dream_time_complication_24_hr_time_format" msgid="6248280719733640813">"kk:mm"</string>
<string name="keyguard_affordance_enablement_dialog_action_template" msgid="8164857863036314664">"Abrir <xliff:g id="APPNAME">%1$s</xliff:g>"</string>
@@ -1009,8 +1008,6 @@
<string name="rear_display_unfold_bottom_sheet_title" msgid="2137403802960396357">"Usar o display frontal para tirar uma selfie melhor?"</string>
<string name="rear_display_bottom_sheet_description" msgid="1852662982816810352">"Use a câmera traseira para tirar uma foto mais ampla e com maior resolução."</string>
<string name="rear_display_bottom_sheet_warning" msgid="800995919558238930"><b>"✱ Esta tela vai ser desativada"</b></string>
- <!-- no translation found for rear_display_accessibility_folded_animation (1538121649587978179) -->
- <skip />
- <!-- no translation found for rear_display_accessibility_unfolded_animation (1946153682258289040) -->
- <skip />
+ <string name="rear_display_accessibility_folded_animation" msgid="1538121649587978179">"Dispositivo dobrável sendo aberto"</string>
+ <string name="rear_display_accessibility_unfolded_animation" msgid="1946153682258289040">"Dispositivo dobrável sendo virado"</string>
</resources>
diff --git a/packages/SystemUI/res/values-ro/strings.xml b/packages/SystemUI/res/values-ro/strings.xml
index e9257fb..ba5eea3 100644
--- a/packages/SystemUI/res/values-ro/strings.xml
+++ b/packages/SystemUI/res/values-ro/strings.xml
@@ -993,7 +993,6 @@
<string name="bt_le_audio_broadcast_dialog_switch_app" msgid="6098768269397105733">"Transmite <xliff:g id="SWITCHAPP">%1$s</xliff:g>"</string>
<string name="bt_le_audio_broadcast_dialog_different_output" msgid="7885102097302562674">"Schimbă rezultatul"</string>
<string name="bt_le_audio_broadcast_dialog_unknown_name" msgid="3791472237793443044">"Necunoscută"</string>
- <string name="dream_date_complication_date_format" msgid="8191225366513860104">"EE, z LLL"</string>
<string name="dream_time_complication_12_hr_time_format" msgid="4691197486690291529">"h:mm"</string>
<string name="dream_time_complication_24_hr_time_format" msgid="6248280719733640813">"kk:mm"</string>
<string name="keyguard_affordance_enablement_dialog_action_template" msgid="8164857863036314664">"Deschide <xliff:g id="APPNAME">%1$s</xliff:g>"</string>
diff --git a/packages/SystemUI/res/values-ru/strings.xml b/packages/SystemUI/res/values-ru/strings.xml
index 5128e0e..5a3f098 100644
--- a/packages/SystemUI/res/values-ru/strings.xml
+++ b/packages/SystemUI/res/values-ru/strings.xml
@@ -993,7 +993,6 @@
<string name="bt_le_audio_broadcast_dialog_switch_app" msgid="6098768269397105733">"Транслировать \"<xliff:g id="SWITCHAPP">%1$s</xliff:g>\""</string>
<string name="bt_le_audio_broadcast_dialog_different_output" msgid="7885102097302562674">"Транслировать на другое устройство"</string>
<string name="bt_le_audio_broadcast_dialog_unknown_name" msgid="3791472237793443044">"Неизвестно"</string>
- <string name="dream_date_complication_date_format" msgid="8191225366513860104">"d MMM EEEE"</string>
<string name="dream_time_complication_12_hr_time_format" msgid="4691197486690291529">"h:mm"</string>
<string name="dream_time_complication_24_hr_time_format" msgid="6248280719733640813">"kk:mm"</string>
<string name="keyguard_affordance_enablement_dialog_action_template" msgid="8164857863036314664">"Открыть \"<xliff:g id="APPNAME">%1$s</xliff:g>\""</string>
diff --git a/packages/SystemUI/res/values-si/strings.xml b/packages/SystemUI/res/values-si/strings.xml
index f176795..d1fa46e 100644
--- a/packages/SystemUI/res/values-si/strings.xml
+++ b/packages/SystemUI/res/values-si/strings.xml
@@ -872,8 +872,7 @@
<string name="media_output_dialog_button_stop_casting" msgid="6581379537930199189">"විකාශය නවතන්න"</string>
<string name="media_output_dialog_accessibility_title" msgid="4681741064190167888">"ශ්රව්ය ප්රතිදානය සඳහා තිබෙන උපාංග."</string>
<string name="media_output_dialog_accessibility_seekbar" msgid="5332843993805568978">"හඬ පරිමාව"</string>
- <!-- no translation found for media_output_dialog_volume_percentage (1613984910585111798) -->
- <skip />
+ <string name="media_output_dialog_volume_percentage" msgid="1613984910585111798">"<xliff:g id="PERCENTAGE">%1$d</xliff:g>%%"</string>
<string name="media_output_first_broadcast_title" msgid="6292237789860753022">"විකාශනය ක්රියා කරන ආකාරය"</string>
<string name="media_output_broadcast" msgid="3555580945878071543">"විකාශනය"</string>
<string name="media_output_first_notify_broadcast_message" msgid="6353857724136398494">"ගැළපෙන බ්ලූටූත් උපාංග සහිත ඔබ අවට සිටින පුද්ගලයින්ට ඔබ විකාශනය කරන මාධ්යයට සවන් දිය හැකිය"</string>
@@ -987,15 +986,13 @@
<string name="dream_overlay_status_bar_camera_mic_off" msgid="3199425257833773569">"කැමරාව සහ මයික් ක්රියාවිරහිතයි"</string>
<string name="dream_overlay_status_bar_notification_indicator" msgid="8091389255691081711">"{count,plural, =1{දැනුම්දීම් #ක්}one{දැනුම්දීම් #ක්}other{දැනුම්දීම් #ක්}}"</string>
<string name="dream_overlay_weather_complication_desc" msgid="824503662089783824">"<xliff:g id="WEATHER_CONDITION">%1$s</xliff:g>, <xliff:g id="TEMPERATURE">%2$s</xliff:g>"</string>
- <!-- no translation found for note_task_button_label (8718616095800343136) -->
- <skip />
+ <string name="note_task_button_label" msgid="8718616095800343136">"සටහන් කර ගැනීම"</string>
<string name="broadcasting_description_is_broadcasting" msgid="765627502786404290">"විකාශනය කරමින්"</string>
<string name="bt_le_audio_broadcast_dialog_title" msgid="3605428497924077811">"<xliff:g id="APP_NAME">%1$s</xliff:g> විකාශනය කිරීම නවත්වන්නද?"</string>
<string name="bt_le_audio_broadcast_dialog_sub_title" msgid="7889684551194225793">"ඔබ <xliff:g id="SWITCHAPP">%1$s</xliff:g> විකාශනය කළහොත් හෝ ප්රතිදානය වෙනස් කළහොත්, ඔබගේ වත්මන් විකාශනය නවතිනු ඇත."</string>
<string name="bt_le_audio_broadcast_dialog_switch_app" msgid="6098768269397105733">"<xliff:g id="SWITCHAPP">%1$s</xliff:g> විකාශනය"</string>
<string name="bt_le_audio_broadcast_dialog_different_output" msgid="7885102097302562674">"ප්රතිදානය වෙනස් කරන්න"</string>
<string name="bt_le_audio_broadcast_dialog_unknown_name" msgid="3791472237793443044">"නොදනී"</string>
- <string name="dream_date_complication_date_format" msgid="8191225366513860104">"EEE, MMM d"</string>
<string name="dream_time_complication_12_hr_time_format" msgid="4691197486690291529">"h:mm"</string>
<string name="dream_time_complication_24_hr_time_format" msgid="6248280719733640813">"kk:mm"</string>
<string name="keyguard_affordance_enablement_dialog_action_template" msgid="8164857863036314664">"<xliff:g id="APPNAME">%1$s</xliff:g> විවෘත කරන්න"</string>
diff --git a/packages/SystemUI/res/values-sk/strings.xml b/packages/SystemUI/res/values-sk/strings.xml
index e4c978b..2573733 100644
--- a/packages/SystemUI/res/values-sk/strings.xml
+++ b/packages/SystemUI/res/values-sk/strings.xml
@@ -993,7 +993,6 @@
<string name="bt_le_audio_broadcast_dialog_switch_app" msgid="6098768269397105733">"Vysielanie aplikácie <xliff:g id="SWITCHAPP">%1$s</xliff:g>"</string>
<string name="bt_le_audio_broadcast_dialog_different_output" msgid="7885102097302562674">"Zmena výstupu"</string>
<string name="bt_le_audio_broadcast_dialog_unknown_name" msgid="3791472237793443044">"Neznáme"</string>
- <string name="dream_date_complication_date_format" msgid="8191225366513860104">"EEE, d. MMM"</string>
<string name="dream_time_complication_12_hr_time_format" msgid="4691197486690291529">"h:mm"</string>
<string name="dream_time_complication_24_hr_time_format" msgid="6248280719733640813">"kk:mm"</string>
<string name="keyguard_affordance_enablement_dialog_action_template" msgid="8164857863036314664">"Otvoriť <xliff:g id="APPNAME">%1$s</xliff:g>"</string>
diff --git a/packages/SystemUI/res/values-sl/strings.xml b/packages/SystemUI/res/values-sl/strings.xml
index ef5e63a..fa63f87 100644
--- a/packages/SystemUI/res/values-sl/strings.xml
+++ b/packages/SystemUI/res/values-sl/strings.xml
@@ -872,8 +872,7 @@
<string name="media_output_dialog_button_stop_casting" msgid="6581379537930199189">"Ustavi predvajanje"</string>
<string name="media_output_dialog_accessibility_title" msgid="4681741064190167888">"Razpoložljive naprave za zvočni izhod"</string>
<string name="media_output_dialog_accessibility_seekbar" msgid="5332843993805568978">"Glasnost"</string>
- <!-- no translation found for media_output_dialog_volume_percentage (1613984910585111798) -->
- <skip />
+ <string name="media_output_dialog_volume_percentage" msgid="1613984910585111798">"<xliff:g id="PERCENTAGE">%1$d</xliff:g> %%"</string>
<string name="media_output_first_broadcast_title" msgid="6292237789860753022">"Kako deluje oddajanje"</string>
<string name="media_output_broadcast" msgid="3555580945878071543">"Oddajanje"</string>
<string name="media_output_first_notify_broadcast_message" msgid="6353857724136398494">"Osebe v bližini z združljivo napravo Bluetooth lahko poslušajo predstavnost, ki jo oddajate."</string>
@@ -987,15 +986,13 @@
<string name="dream_overlay_status_bar_camera_mic_off" msgid="3199425257833773569">"Fotoaparat in mikrofon sta izklopljena."</string>
<string name="dream_overlay_status_bar_notification_indicator" msgid="8091389255691081711">"{count,plural, =1{# obvestilo}one{# obvestilo}two{# obvestili}few{# obvestila}other{# obvestil}}"</string>
<string name="dream_overlay_weather_complication_desc" msgid="824503662089783824">"<xliff:g id="WEATHER_CONDITION">%1$s</xliff:g>, <xliff:g id="TEMPERATURE">%2$s</xliff:g>"</string>
- <!-- no translation found for note_task_button_label (8718616095800343136) -->
- <skip />
+ <string name="note_task_button_label" msgid="8718616095800343136">"Ustvarjanje zapiskov"</string>
<string name="broadcasting_description_is_broadcasting" msgid="765627502786404290">"Oddajanje"</string>
<string name="bt_le_audio_broadcast_dialog_title" msgid="3605428497924077811">"Želite ustaviti oddajanje aplikacije <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string>
<string name="bt_le_audio_broadcast_dialog_sub_title" msgid="7889684551194225793">"Če oddajate aplikacijo <xliff:g id="SWITCHAPP">%1$s</xliff:g> ali spremenite izhod, bo trenutno oddajanje ustavljeno."</string>
<string name="bt_le_audio_broadcast_dialog_switch_app" msgid="6098768269397105733">"Oddajaj aplikacijo <xliff:g id="SWITCHAPP">%1$s</xliff:g>"</string>
<string name="bt_le_audio_broadcast_dialog_different_output" msgid="7885102097302562674">"Sprememba izhoda"</string>
<string name="bt_le_audio_broadcast_dialog_unknown_name" msgid="3791472237793443044">"Neznano"</string>
- <string name="dream_date_complication_date_format" msgid="8191225366513860104">"EEE, d. MMM"</string>
<string name="dream_time_complication_12_hr_time_format" msgid="4691197486690291529">"h:mm"</string>
<string name="dream_time_complication_24_hr_time_format" msgid="6248280719733640813">"kk:mm"</string>
<string name="keyguard_affordance_enablement_dialog_action_template" msgid="8164857863036314664">"Odpri aplikacijo <xliff:g id="APPNAME">%1$s</xliff:g>"</string>
diff --git a/packages/SystemUI/res/values-sq/strings.xml b/packages/SystemUI/res/values-sq/strings.xml
index 24e2616..6eb745c 100644
--- a/packages/SystemUI/res/values-sq/strings.xml
+++ b/packages/SystemUI/res/values-sq/strings.xml
@@ -993,7 +993,6 @@
<string name="bt_le_audio_broadcast_dialog_switch_app" msgid="6098768269397105733">"Transmeto <xliff:g id="SWITCHAPP">%1$s</xliff:g>"</string>
<string name="bt_le_audio_broadcast_dialog_different_output" msgid="7885102097302562674">"Ndrysho daljen"</string>
<string name="bt_le_audio_broadcast_dialog_unknown_name" msgid="3791472237793443044">"I panjohur"</string>
- <string name="dream_date_complication_date_format" msgid="8191225366513860104">"EEE, MMM d"</string>
<string name="dream_time_complication_12_hr_time_format" msgid="4691197486690291529">"h:mm"</string>
<string name="dream_time_complication_24_hr_time_format" msgid="6248280719733640813">"kk:mm"</string>
<string name="keyguard_affordance_enablement_dialog_action_template" msgid="8164857863036314664">"Hap \"<xliff:g id="APPNAME">%1$s</xliff:g>\""</string>
diff --git a/packages/SystemUI/res/values-sr/strings.xml b/packages/SystemUI/res/values-sr/strings.xml
index 9464ac5..75a4559 100644
--- a/packages/SystemUI/res/values-sr/strings.xml
+++ b/packages/SystemUI/res/values-sr/strings.xml
@@ -993,7 +993,6 @@
<string name="bt_le_audio_broadcast_dialog_switch_app" msgid="6098768269397105733">"Емитујте апликацију <xliff:g id="SWITCHAPP">%1$s</xliff:g>"</string>
<string name="bt_le_audio_broadcast_dialog_different_output" msgid="7885102097302562674">"Промените излаз"</string>
<string name="bt_le_audio_broadcast_dialog_unknown_name" msgid="3791472237793443044">"Непознато"</string>
- <string name="dream_date_complication_date_format" msgid="8191225366513860104">"ДДД, д. МММ"</string>
<string name="dream_time_complication_12_hr_time_format" msgid="4691197486690291529">"с:мин"</string>
<string name="dream_time_complication_24_hr_time_format" msgid="6248280719733640813">"ч:мин"</string>
<string name="keyguard_affordance_enablement_dialog_action_template" msgid="8164857863036314664">"Отворите: <xliff:g id="APPNAME">%1$s</xliff:g>"</string>
diff --git a/packages/SystemUI/res/values-sv/strings.xml b/packages/SystemUI/res/values-sv/strings.xml
index f5cf437..39b40e9 100644
--- a/packages/SystemUI/res/values-sv/strings.xml
+++ b/packages/SystemUI/res/values-sv/strings.xml
@@ -872,8 +872,7 @@
<string name="media_output_dialog_button_stop_casting" msgid="6581379537930199189">"Sluta casta"</string>
<string name="media_output_dialog_accessibility_title" msgid="4681741064190167888">"Enheter som är tillgängliga för ljudutdata."</string>
<string name="media_output_dialog_accessibility_seekbar" msgid="5332843993805568978">"Volym"</string>
- <!-- no translation found for media_output_dialog_volume_percentage (1613984910585111798) -->
- <skip />
+ <string name="media_output_dialog_volume_percentage" msgid="1613984910585111798">"<xliff:g id="PERCENTAGE">%1$d</xliff:g> %%"</string>
<string name="media_output_first_broadcast_title" msgid="6292237789860753022">"Så fungerar utsändning"</string>
<string name="media_output_broadcast" msgid="3555580945878071543">"Utsändning"</string>
<string name="media_output_first_notify_broadcast_message" msgid="6353857724136398494">"Personer i närheten med kompatibla Bluetooth-enheter kan lyssna på medieinnehåll som du sänder ut"</string>
@@ -987,15 +986,13 @@
<string name="dream_overlay_status_bar_camera_mic_off" msgid="3199425257833773569">"Kameran och mikrofonen är avstängda"</string>
<string name="dream_overlay_status_bar_notification_indicator" msgid="8091389255691081711">"{count,plural, =1{# avisering}other{# aviseringar}}"</string>
<string name="dream_overlay_weather_complication_desc" msgid="824503662089783824">"<xliff:g id="WEATHER_CONDITION">%1$s</xliff:g>, <xliff:g id="TEMPERATURE">%2$s</xliff:g>"</string>
- <!-- no translation found for note_task_button_label (8718616095800343136) -->
- <skip />
+ <string name="note_task_button_label" msgid="8718616095800343136">"Anteckningar"</string>
<string name="broadcasting_description_is_broadcasting" msgid="765627502786404290">"Sänder"</string>
<string name="bt_le_audio_broadcast_dialog_title" msgid="3605428497924077811">"Vill du sluta sända från <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string>
<string name="bt_le_audio_broadcast_dialog_sub_title" msgid="7889684551194225793">"Om en utsändning från <xliff:g id="SWITCHAPP">%1$s</xliff:g> pågår eller om du byter ljudutgång avbryts den nuvarande utsändningen"</string>
<string name="bt_le_audio_broadcast_dialog_switch_app" msgid="6098768269397105733">"Sänd från <xliff:g id="SWITCHAPP">%1$s</xliff:g>"</string>
<string name="bt_le_audio_broadcast_dialog_different_output" msgid="7885102097302562674">"Byt ljudutgång"</string>
<string name="bt_le_audio_broadcast_dialog_unknown_name" msgid="3791472237793443044">"Okänt"</string>
- <string name="dream_date_complication_date_format" msgid="8191225366513860104">"EEE, d MMM"</string>
<string name="dream_time_complication_12_hr_time_format" msgid="4691197486690291529">"h.mm"</string>
<string name="dream_time_complication_24_hr_time_format" msgid="6248280719733640813">"kk.mm"</string>
<string name="keyguard_affordance_enablement_dialog_action_template" msgid="8164857863036314664">"Öppna <xliff:g id="APPNAME">%1$s</xliff:g>"</string>
diff --git a/packages/SystemUI/res/values-sw/strings.xml b/packages/SystemUI/res/values-sw/strings.xml
index e0043e3..ccf3345 100644
--- a/packages/SystemUI/res/values-sw/strings.xml
+++ b/packages/SystemUI/res/values-sw/strings.xml
@@ -995,7 +995,6 @@
<string name="bt_le_audio_broadcast_dialog_switch_app" msgid="6098768269397105733">"Tangaza kwenye <xliff:g id="SWITCHAPP">%1$s</xliff:g>"</string>
<string name="bt_le_audio_broadcast_dialog_different_output" msgid="7885102097302562674">"Badilisha maudhui"</string>
<string name="bt_le_audio_broadcast_dialog_unknown_name" msgid="3791472237793443044">"Haijulikani"</string>
- <string name="dream_date_complication_date_format" msgid="8191225366513860104">"EEE, d MMM"</string>
<string name="dream_time_complication_12_hr_time_format" msgid="4691197486690291529">"saa:dk"</string>
<string name="dream_time_complication_24_hr_time_format" msgid="6248280719733640813">"kk:dk"</string>
<string name="keyguard_affordance_enablement_dialog_action_template" msgid="8164857863036314664">"Fungua <xliff:g id="APPNAME">%1$s</xliff:g>"</string>
diff --git a/packages/SystemUI/res/values-ta/strings.xml b/packages/SystemUI/res/values-ta/strings.xml
index f800cd0..9f99b76 100644
--- a/packages/SystemUI/res/values-ta/strings.xml
+++ b/packages/SystemUI/res/values-ta/strings.xml
@@ -872,8 +872,7 @@
<string name="media_output_dialog_button_stop_casting" msgid="6581379537930199189">"அலைபரப்புவதை நிறுத்து"</string>
<string name="media_output_dialog_accessibility_title" msgid="4681741064190167888">"ஆடியோ அவுட்புட்டுக்குக் கிடைக்கும் சாதனங்கள்."</string>
<string name="media_output_dialog_accessibility_seekbar" msgid="5332843993805568978">"ஒலியளவு"</string>
- <!-- no translation found for media_output_dialog_volume_percentage (1613984910585111798) -->
- <skip />
+ <string name="media_output_dialog_volume_percentage" msgid="1613984910585111798">"<xliff:g id="PERCENTAGE">%1$d</xliff:g>%%"</string>
<string name="media_output_first_broadcast_title" msgid="6292237789860753022">"பிராட்காஸ்ட் எவ்வாறு செயல்படுகிறது?"</string>
<string name="media_output_broadcast" msgid="3555580945878071543">"பிராட்காஸ்ட்"</string>
<string name="media_output_first_notify_broadcast_message" msgid="6353857724136398494">"நீங்கள் பிராட்காஸ்ட் செய்யும் மீடியாவை அருகிலுள்ளவர்கள் இணக்கமான புளூடூத் சாதனங்கள் மூலம் கேட்கலாம்"</string>
@@ -987,15 +986,13 @@
<string name="dream_overlay_status_bar_camera_mic_off" msgid="3199425257833773569">"கேமராவும் மைக்கும் ஆஃப் செய்யப்பட்டுள்ளன"</string>
<string name="dream_overlay_status_bar_notification_indicator" msgid="8091389255691081711">"{count,plural, =1{# அறிவிப்பு}other{# அறிவிப்புகள்}}"</string>
<string name="dream_overlay_weather_complication_desc" msgid="824503662089783824">"<xliff:g id="WEATHER_CONDITION">%1$s</xliff:g>, <xliff:g id="TEMPERATURE">%2$s</xliff:g>"</string>
- <!-- no translation found for note_task_button_label (8718616095800343136) -->
- <skip />
+ <string name="note_task_button_label" msgid="8718616095800343136">"குறிப்பெடுத்தல்"</string>
<string name="broadcasting_description_is_broadcasting" msgid="765627502786404290">"ஒலிபரப்புதல்"</string>
<string name="bt_le_audio_broadcast_dialog_title" msgid="3605428497924077811">"<xliff:g id="APP_NAME">%1$s</xliff:g> ஆப்ஸ் ஒலிபரப்பப்படுவதை நிறுத்தவா?"</string>
<string name="bt_le_audio_broadcast_dialog_sub_title" msgid="7889684551194225793">"நீங்கள் <xliff:g id="SWITCHAPP">%1$s</xliff:g> ஆப்ஸை ஒலிபரப்பினாலோ அவுட்புட்டை மாற்றினாலோ உங்களின் தற்போதைய ஒலிபரப்பு நிறுத்தப்படும்"</string>
<string name="bt_le_audio_broadcast_dialog_switch_app" msgid="6098768269397105733">"<xliff:g id="SWITCHAPP">%1$s</xliff:g> ஆப்ஸை ஒலிபரப்பு"</string>
<string name="bt_le_audio_broadcast_dialog_different_output" msgid="7885102097302562674">"அவுட்புட்டை மாற்று"</string>
<string name="bt_le_audio_broadcast_dialog_unknown_name" msgid="3791472237793443044">"தெரியவில்லை"</string>
- <string name="dream_date_complication_date_format" msgid="8191225366513860104">"EEE, MMM d"</string>
<string name="dream_time_complication_12_hr_time_format" msgid="4691197486690291529">"h:mm"</string>
<string name="dream_time_complication_24_hr_time_format" msgid="6248280719733640813">"kk:mm"</string>
<string name="keyguard_affordance_enablement_dialog_action_template" msgid="8164857863036314664">"<xliff:g id="APPNAME">%1$s</xliff:g> ஆப்ஸைத் திற"</string>
diff --git a/packages/SystemUI/res/values-te/strings.xml b/packages/SystemUI/res/values-te/strings.xml
index f707088..4e506d4 100644
--- a/packages/SystemUI/res/values-te/strings.xml
+++ b/packages/SystemUI/res/values-te/strings.xml
@@ -66,7 +66,7 @@
<string name="usb_contaminant_title" msgid="894052515034594113">"USB పోర్ట్ నిలిపివేయబడింది"</string>
<string name="usb_contaminant_message" msgid="7730476585174719805">"మీ పరికరంలోకి నీరు లేదా చెత్తాచెదారం చేరిపోకుండా కాపాడటానికి, USB పోర్ట్ నిలిపివేయబడుతుంది, అలాగే యాక్సెసరీలు వేటిని గుర్తించదు.\n\nUSB పోర్ట్ను ఉపయోగించడం సురక్షితమేనని నిర్ధారించుకున్న తర్వాత, మళ్లీ మీకో నోటిఫికేషన్ రూపంలో తెలియజేయబడుతుంది."</string>
<string name="usb_port_enabled" msgid="531823867664717018">"ఛార్జర్లు, యాక్సెసరీలను గుర్తించే విధంగా USB పోర్ట్ ప్రారంభించబడింది"</string>
- <string name="usb_disable_contaminant_detection" msgid="3827082183595978641">"USBని ప్రారంభించు"</string>
+ <string name="usb_disable_contaminant_detection" msgid="3827082183595978641">"USBని ప్రారంభించండి"</string>
<string name="learn_more" msgid="4690632085667273811">"మరింత తెలుసుకోండి"</string>
<string name="global_action_screenshot" msgid="2760267567509131654">"స్క్రీన్షాట్"</string>
<string name="global_action_smart_lock_disabled" msgid="9097102067802412936">"Smart Lock డిజేబుల్ చేయబడింది"</string>
@@ -105,7 +105,7 @@
<string name="screenrecord_device_audio_description" msgid="4922694220572186193">"మీ పరికరం నుండి వచ్చే మ్యూజిక్, కాల్స్, రింగ్టోన్ల వంటి ధ్వనులు"</string>
<string name="screenrecord_mic_label" msgid="2111264835791332350">"మైక్రోఫోన్"</string>
<string name="screenrecord_device_audio_and_mic_label" msgid="1831323771978646841">"పరికరం ఆడియో, మైక్రోఫోన్"</string>
- <string name="screenrecord_start" msgid="330991441575775004">"ప్రారంభించు"</string>
+ <string name="screenrecord_start" msgid="330991441575775004">"ప్రారంభించండి"</string>
<string name="screenrecord_ongoing_screen_only" msgid="4459670242451527727">"స్క్రీన్ రికార్డింగ్ చేయబడుతోంది"</string>
<string name="screenrecord_ongoing_screen_and_audio" msgid="5351133763125180920">"స్క్రీన్, ఆడియో రికార్డింగ్ చేయబడుతున్నాయి"</string>
<string name="screenrecord_taps_label" msgid="1595690528298857649">"స్క్రీన్పై తాకే స్థానాలను చూపు"</string>
@@ -289,7 +289,7 @@
<string name="quick_settings_nfc_off" msgid="3465000058515424663">"NFC నిలిపివేయబడింది"</string>
<string name="quick_settings_nfc_on" msgid="1004976611203202230">"NFC ప్రారంభించబడింది"</string>
<string name="quick_settings_screen_record_label" msgid="8650355346742003694">"స్క్రీన్ రికార్డ్"</string>
- <string name="quick_settings_screen_record_start" msgid="1574725369331638985">"ప్రారంభించు"</string>
+ <string name="quick_settings_screen_record_start" msgid="1574725369331638985">"ప్రారంభించండి"</string>
<string name="quick_settings_screen_record_stop" msgid="8087348522976412119">"ఆపు"</string>
<string name="quick_settings_onehanded_label" msgid="2416537930246274991">"వన్-హ్యాండెడ్ మోడ్"</string>
<string name="sensor_privacy_start_use_mic_dialog_title" msgid="563796653825944944">"పరికరం మైక్రోఫోన్ను అన్బ్లాక్ చేయమంటారా?"</string>
@@ -356,7 +356,7 @@
<string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"ఈ సెషన్లోని అన్ని యాప్లు మరియు డేటా తొలగించబడతాయి."</string>
<string name="guest_wipe_session_title" msgid="7147965814683990944">"గెస్ట్కు తిరిగి స్వాగతం!"</string>
<string name="guest_wipe_session_message" msgid="3393823610257065457">"మీరు మీ సెషన్ని కొనసాగించాలనుకుంటున్నారా?"</string>
- <string name="guest_wipe_session_wipe" msgid="8056836584445473309">"మొదటి నుండి ప్రారంభించు"</string>
+ <string name="guest_wipe_session_wipe" msgid="8056836584445473309">"మొదటి నుండి ప్రారంభించండి"</string>
<string name="guest_wipe_session_dontwipe" msgid="3211052048269304205">"అవును, కొనసాగించు"</string>
<string name="guest_notification_app_name" msgid="2110425506754205509">"గెస్ట్ మోడ్"</string>
<string name="guest_notification_session_active" msgid="5567273684713471450">"మీరు గెస్ట్ మోడ్లో ఉన్నారు"</string>
@@ -391,7 +391,7 @@
<string name="notification_section_header_conversations" msgid="821834744538345661">"సంభాషణలు"</string>
<string name="accessibility_notification_section_header_gentle_clear_all" msgid="6490207897764933919">"అన్ని నిశ్శబ్ద నోటిఫికేషన్లను క్లియర్ చేస్తుంది"</string>
<string name="dnd_suppressing_shade_text" msgid="5588252250634464042">"అంతరాయం కలిగించవద్దు ద్వారా నోటిఫికేషన్లు పాజ్ చేయబడ్డాయి"</string>
- <string name="media_projection_action_text" msgid="3634906766918186440">"ఇప్పుడే ప్రారంభించు"</string>
+ <string name="media_projection_action_text" msgid="3634906766918186440">"ఇప్పుడే ప్రారంభించండి"</string>
<string name="empty_shade_text" msgid="8935967157319717412">"నోటిఫికేషన్లు లేవు"</string>
<string name="no_unseen_notif_text" msgid="395512586119868682">"కొత్త నోటిఫికేషన్లు ఏవీ లేవు"</string>
<string name="unlock_to_see_notif_text" msgid="7439033907167561227">"పాత నోటిఫికేషన్ల కోసం అన్లాక్ చేయండి"</string>
@@ -442,7 +442,7 @@
<string name="volume_odi_captions_tip" msgid="8825655463280990941">"మీడియాకు ఆటోమేటిక్ క్యాప్షన్లు"</string>
<string name="accessibility_volume_close_odi_captions_tip" msgid="8924753283621160480">"క్యాప్షన్ల చిట్కాను మూసివేయండి"</string>
<string name="volume_odi_captions_content_description" msgid="4172765742046013630">"క్యాప్షన్లు ఓవర్లే"</string>
- <string name="volume_odi_captions_hint_enable" msgid="2073091194012843195">"ప్రారంభించు"</string>
+ <string name="volume_odi_captions_hint_enable" msgid="2073091194012843195">"ప్రారంభించండి"</string>
<string name="volume_odi_captions_hint_disable" msgid="2518846326748183407">"నిలిపివేయండి"</string>
<string name="sound_settings" msgid="8874581353127418308">"సౌండ్ & వైబ్రేషన్"</string>
<string name="volume_panel_dialog_settings_button" msgid="2513228491513390310">"సెట్టింగ్లు"</string>
@@ -872,8 +872,7 @@
<string name="media_output_dialog_button_stop_casting" msgid="6581379537930199189">"ప్రసారాన్ని ఆపివేయండి"</string>
<string name="media_output_dialog_accessibility_title" msgid="4681741064190167888">"ఆడియో అవుట్పుట్ కోసం అందుబాటులో ఉన్న పరికరాలు."</string>
<string name="media_output_dialog_accessibility_seekbar" msgid="5332843993805568978">"వాల్యూమ్"</string>
- <!-- no translation found for media_output_dialog_volume_percentage (1613984910585111798) -->
- <skip />
+ <string name="media_output_dialog_volume_percentage" msgid="1613984910585111798">"<xliff:g id="PERCENTAGE">%1$d</xliff:g>%%"</string>
<string name="media_output_first_broadcast_title" msgid="6292237789860753022">"ప్రసారం కావడం అనేది ఎలా పని చేస్తుంది"</string>
<string name="media_output_broadcast" msgid="3555580945878071543">"ప్రసారం"</string>
<string name="media_output_first_notify_broadcast_message" msgid="6353857724136398494">"మీకు సమీపంలో ఉన్న వ్యక్తులు అనుకూలత ఉన్న బ్లూటూత్ పరికరాలతో మీరు ప్రసారం చేస్తున్న మీడియాను వినగలరు"</string>
@@ -987,15 +986,13 @@
<string name="dream_overlay_status_bar_camera_mic_off" msgid="3199425257833773569">"కెమెరా, మైక్ ఆఫ్లో ఉన్నాయి"</string>
<string name="dream_overlay_status_bar_notification_indicator" msgid="8091389255691081711">"{count,plural, =1{# నోటిఫికేషన్}other{# నోటిఫికేషన్లు}}"</string>
<string name="dream_overlay_weather_complication_desc" msgid="824503662089783824">"<xliff:g id="WEATHER_CONDITION">%1$s</xliff:g>, <xliff:g id="TEMPERATURE">%2$s</xliff:g>"</string>
- <!-- no translation found for note_task_button_label (8718616095800343136) -->
- <skip />
+ <string name="note_task_button_label" msgid="8718616095800343136">"నోట్టేకింగ్"</string>
<string name="broadcasting_description_is_broadcasting" msgid="765627502786404290">"ప్రసారం చేస్తోంది"</string>
<string name="bt_le_audio_broadcast_dialog_title" msgid="3605428497924077811">"<xliff:g id="APP_NAME">%1$s</xliff:g> ప్రసారం చేయడాన్ని ఆపివేయాలా?"</string>
<string name="bt_le_audio_broadcast_dialog_sub_title" msgid="7889684551194225793">"మీరు <xliff:g id="SWITCHAPP">%1$s</xliff:g> ప్రసారం చేస్తే లేదా అవుట్పుట్ను మార్చినట్లయితే, మీ ప్రస్తుత ప్రసారం ఆగిపోతుంది"</string>
<string name="bt_le_audio_broadcast_dialog_switch_app" msgid="6098768269397105733">"<xliff:g id="SWITCHAPP">%1$s</xliff:g> ప్రసారం చేయండి"</string>
<string name="bt_le_audio_broadcast_dialog_different_output" msgid="7885102097302562674">"అవుట్పుట్ను మార్చండి"</string>
<string name="bt_le_audio_broadcast_dialog_unknown_name" msgid="3791472237793443044">"తెలియదు"</string>
- <string name="dream_date_complication_date_format" msgid="8191225366513860104">"EEE, MMM d"</string>
<string name="dream_time_complication_12_hr_time_format" msgid="4691197486690291529">"h:mm"</string>
<string name="dream_time_complication_24_hr_time_format" msgid="6248280719733640813">"kk:mm"</string>
<string name="keyguard_affordance_enablement_dialog_action_template" msgid="8164857863036314664">"<xliff:g id="APPNAME">%1$s</xliff:g>ను తెరవండి"</string>
diff --git a/packages/SystemUI/res/values-th/strings.xml b/packages/SystemUI/res/values-th/strings.xml
index 40b997d..544dde4 100644
--- a/packages/SystemUI/res/values-th/strings.xml
+++ b/packages/SystemUI/res/values-th/strings.xml
@@ -872,8 +872,7 @@
<string name="media_output_dialog_button_stop_casting" msgid="6581379537930199189">"หยุดแคสต์"</string>
<string name="media_output_dialog_accessibility_title" msgid="4681741064190167888">"อุปกรณ์ที่พร้อมใช้งานสำหรับเอาต์พุตเสียง"</string>
<string name="media_output_dialog_accessibility_seekbar" msgid="5332843993805568978">"ระดับเสียง"</string>
- <!-- no translation found for media_output_dialog_volume_percentage (1613984910585111798) -->
- <skip />
+ <string name="media_output_dialog_volume_percentage" msgid="1613984910585111798">"<xliff:g id="PERCENTAGE">%1$d</xliff:g>%%"</string>
<string name="media_output_first_broadcast_title" msgid="6292237789860753022">"วิธีการทำงานของการออกอากาศ"</string>
<string name="media_output_broadcast" msgid="3555580945878071543">"ประกาศ"</string>
<string name="media_output_first_notify_broadcast_message" msgid="6353857724136398494">"ผู้ที่อยู่ใกล้คุณและมีอุปกรณ์บลูทูธที่รองรับสามารถรับฟังสื่อที่คุณกำลังออกอากาศได้"</string>
@@ -987,15 +986,13 @@
<string name="dream_overlay_status_bar_camera_mic_off" msgid="3199425257833773569">"กล้องและไมค์ปิดอยู่"</string>
<string name="dream_overlay_status_bar_notification_indicator" msgid="8091389255691081711">"{count,plural, =1{การแจ้งเตือน # รายการ}other{การแจ้งเตือน # รายการ}}"</string>
<string name="dream_overlay_weather_complication_desc" msgid="824503662089783824">"<xliff:g id="WEATHER_CONDITION">%1$s</xliff:g>, <xliff:g id="TEMPERATURE">%2$s</xliff:g>"</string>
- <!-- no translation found for note_task_button_label (8718616095800343136) -->
- <skip />
+ <string name="note_task_button_label" msgid="8718616095800343136">"การจดบันทึก"</string>
<string name="broadcasting_description_is_broadcasting" msgid="765627502786404290">"กำลังออกอากาศ"</string>
<string name="bt_le_audio_broadcast_dialog_title" msgid="3605428497924077811">"หยุดการออกอากาศ <xliff:g id="APP_NAME">%1$s</xliff:g> ไหม"</string>
<string name="bt_le_audio_broadcast_dialog_sub_title" msgid="7889684551194225793">"หากคุณออกอากาศ <xliff:g id="SWITCHAPP">%1$s</xliff:g> หรือเปลี่ยนแปลงเอาต์พุต การออกอากาศในปัจจุบันจะหยุดลง"</string>
<string name="bt_le_audio_broadcast_dialog_switch_app" msgid="6098768269397105733">"ออกอากาศ <xliff:g id="SWITCHAPP">%1$s</xliff:g>"</string>
<string name="bt_le_audio_broadcast_dialog_different_output" msgid="7885102097302562674">"เปลี่ยนเอาต์พุต"</string>
<string name="bt_le_audio_broadcast_dialog_unknown_name" msgid="3791472237793443044">"ไม่ทราบ"</string>
- <string name="dream_date_complication_date_format" msgid="8191225366513860104">"EEE d MMM"</string>
<string name="dream_time_complication_12_hr_time_format" msgid="4691197486690291529">"HH:mm"</string>
<string name="dream_time_complication_24_hr_time_format" msgid="6248280719733640813">"kk:mm"</string>
<string name="keyguard_affordance_enablement_dialog_action_template" msgid="8164857863036314664">"เปิด <xliff:g id="APPNAME">%1$s</xliff:g>"</string>
diff --git a/packages/SystemUI/res/values-tl/strings.xml b/packages/SystemUI/res/values-tl/strings.xml
index a0b4e29a..c27b133 100644
--- a/packages/SystemUI/res/values-tl/strings.xml
+++ b/packages/SystemUI/res/values-tl/strings.xml
@@ -872,8 +872,7 @@
<string name="media_output_dialog_button_stop_casting" msgid="6581379537930199189">"Ihinto ang pag-cast"</string>
<string name="media_output_dialog_accessibility_title" msgid="4681741064190167888">"Mga available na device para sa audio output."</string>
<string name="media_output_dialog_accessibility_seekbar" msgid="5332843993805568978">"Volume"</string>
- <!-- no translation found for media_output_dialog_volume_percentage (1613984910585111798) -->
- <skip />
+ <string name="media_output_dialog_volume_percentage" msgid="1613984910585111798">"<xliff:g id="PERCENTAGE">%1$d</xliff:g>%%"</string>
<string name="media_output_first_broadcast_title" msgid="6292237789860753022">"Paano gumagana ang pag-broadcast"</string>
<string name="media_output_broadcast" msgid="3555580945878071543">"Broadcast"</string>
<string name="media_output_first_notify_broadcast_message" msgid="6353857724136398494">"Makakapakinig ang mga taong malapit sa iyo na may mga compatible na Bluetooth device sa media na bino-broadcast mo"</string>
@@ -987,15 +986,13 @@
<string name="dream_overlay_status_bar_camera_mic_off" msgid="3199425257833773569">"Naka-off ang camera at mikropono"</string>
<string name="dream_overlay_status_bar_notification_indicator" msgid="8091389255691081711">"{count,plural, =1{# notification}one{# notification}other{# na notification}}"</string>
<string name="dream_overlay_weather_complication_desc" msgid="824503662089783824">"<xliff:g id="WEATHER_CONDITION">%1$s</xliff:g>, <xliff:g id="TEMPERATURE">%2$s</xliff:g>"</string>
- <!-- no translation found for note_task_button_label (8718616095800343136) -->
- <skip />
+ <string name="note_task_button_label" msgid="8718616095800343136">"Pagtatala"</string>
<string name="broadcasting_description_is_broadcasting" msgid="765627502786404290">"Nagbo-broadcast"</string>
<string name="bt_le_audio_broadcast_dialog_title" msgid="3605428497924077811">"Ihinto ang pag-broadcast ng <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string>
<string name="bt_le_audio_broadcast_dialog_sub_title" msgid="7889684551194225793">"Kung magbo-broadcast ka ng <xliff:g id="SWITCHAPP">%1$s</xliff:g> o babaguhin mo ang output, hihinto ang iyong kasalukuyang broadcast"</string>
<string name="bt_le_audio_broadcast_dialog_switch_app" msgid="6098768269397105733">"I-broadcast ang <xliff:g id="SWITCHAPP">%1$s</xliff:g>"</string>
<string name="bt_le_audio_broadcast_dialog_different_output" msgid="7885102097302562674">"Baguhin ang output"</string>
<string name="bt_le_audio_broadcast_dialog_unknown_name" msgid="3791472237793443044">"Hindi alam"</string>
- <string name="dream_date_complication_date_format" msgid="8191225366513860104">"EEE, MMM d"</string>
<string name="dream_time_complication_12_hr_time_format" msgid="4691197486690291529">"h:mm"</string>
<string name="dream_time_complication_24_hr_time_format" msgid="6248280719733640813">"kk:mm"</string>
<string name="keyguard_affordance_enablement_dialog_action_template" msgid="8164857863036314664">"Buksan ang <xliff:g id="APPNAME">%1$s</xliff:g>"</string>
diff --git a/packages/SystemUI/res/values-tr/strings.xml b/packages/SystemUI/res/values-tr/strings.xml
index 4fb45b1..90cd4a7 100644
--- a/packages/SystemUI/res/values-tr/strings.xml
+++ b/packages/SystemUI/res/values-tr/strings.xml
@@ -872,8 +872,7 @@
<string name="media_output_dialog_button_stop_casting" msgid="6581379537930199189">"Yayını durdur"</string>
<string name="media_output_dialog_accessibility_title" msgid="4681741064190167888">"Ses çıkışı için kullanılabilir cihazlar."</string>
<string name="media_output_dialog_accessibility_seekbar" msgid="5332843993805568978">"Ses düzeyi"</string>
- <!-- no translation found for media_output_dialog_volume_percentage (1613984910585111798) -->
- <skip />
+ <string name="media_output_dialog_volume_percentage" msgid="1613984910585111798">"%%<xliff:g id="PERCENTAGE">%1$d</xliff:g>"</string>
<string name="media_output_first_broadcast_title" msgid="6292237789860753022">"Yayınlamanın işleyiş şekli"</string>
<string name="media_output_broadcast" msgid="3555580945878071543">"Anons"</string>
<string name="media_output_first_notify_broadcast_message" msgid="6353857724136398494">"Yakınınızda ve uyumlu Bluetooth cihazları olan kişiler yayınladığınız medya içeriğini dinleyebilir"</string>
@@ -987,15 +986,13 @@
<string name="dream_overlay_status_bar_camera_mic_off" msgid="3199425257833773569">"Kamera ve mikrofon kapalı"</string>
<string name="dream_overlay_status_bar_notification_indicator" msgid="8091389255691081711">"{count,plural, =1{# bildirim}other{# bildirim}}"</string>
<string name="dream_overlay_weather_complication_desc" msgid="824503662089783824">"<xliff:g id="WEATHER_CONDITION">%1$s</xliff:g>, <xliff:g id="TEMPERATURE">%2$s</xliff:g>"</string>
- <!-- no translation found for note_task_button_label (8718616095800343136) -->
- <skip />
+ <string name="note_task_button_label" msgid="8718616095800343136">"Not alma"</string>
<string name="broadcasting_description_is_broadcasting" msgid="765627502786404290">"Yayınlama"</string>
<string name="bt_le_audio_broadcast_dialog_title" msgid="3605428497924077811">"<xliff:g id="APP_NAME">%1$s</xliff:g> uygulamasında anons durdurulsun mu?"</string>
<string name="bt_le_audio_broadcast_dialog_sub_title" msgid="7889684551194225793">"<xliff:g id="SWITCHAPP">%1$s</xliff:g> uygulamasında anons yapar veya çıkışı değiştirirseniz mevcut anonsunuz duraklatılır"</string>
<string name="bt_le_audio_broadcast_dialog_switch_app" msgid="6098768269397105733">"<xliff:g id="SWITCHAPP">%1$s</xliff:g> uygulamasında anons yapın"</string>
<string name="bt_le_audio_broadcast_dialog_different_output" msgid="7885102097302562674">"Çıkışı değiştirme"</string>
<string name="bt_le_audio_broadcast_dialog_unknown_name" msgid="3791472237793443044">"Bilinmiyor"</string>
- <string name="dream_date_complication_date_format" msgid="8191225366513860104">"d MMM, EEE"</string>
<string name="dream_time_complication_12_hr_time_format" msgid="4691197486690291529">"h:mm"</string>
<string name="dream_time_complication_24_hr_time_format" msgid="6248280719733640813">"kk:dd"</string>
<string name="keyguard_affordance_enablement_dialog_action_template" msgid="8164857863036314664">"<xliff:g id="APPNAME">%1$s</xliff:g> uygulamasını aç"</string>
diff --git a/packages/SystemUI/res/values-uk/strings.xml b/packages/SystemUI/res/values-uk/strings.xml
index 5992b01..76e99ef 100644
--- a/packages/SystemUI/res/values-uk/strings.xml
+++ b/packages/SystemUI/res/values-uk/strings.xml
@@ -872,8 +872,7 @@
<string name="media_output_dialog_button_stop_casting" msgid="6581379537930199189">"Припинити трансляцію"</string>
<string name="media_output_dialog_accessibility_title" msgid="4681741064190167888">"Доступні пристрої для відтворення звуку."</string>
<string name="media_output_dialog_accessibility_seekbar" msgid="5332843993805568978">"Гучність"</string>
- <!-- no translation found for media_output_dialog_volume_percentage (1613984910585111798) -->
- <skip />
+ <string name="media_output_dialog_volume_percentage" msgid="1613984910585111798">"<xliff:g id="PERCENTAGE">%1$d</xliff:g>%%"</string>
<string name="media_output_first_broadcast_title" msgid="6292237789860753022">"Як працює трансляція"</string>
<string name="media_output_broadcast" msgid="3555580945878071543">"Трансляція"</string>
<string name="media_output_first_notify_broadcast_message" msgid="6353857724136398494">"Люди поблизу, які мають сумісні пристрої з Bluetooth, можуть слухати медіаконтент, який ви транслюєте."</string>
@@ -987,15 +986,13 @@
<string name="dream_overlay_status_bar_camera_mic_off" msgid="3199425257833773569">"Камеру й мікрофон вимкнено"</string>
<string name="dream_overlay_status_bar_notification_indicator" msgid="8091389255691081711">"{count,plural, =1{# сповіщення}one{# сповіщення}few{# сповіщення}many{# сповіщень}other{# сповіщення}}"</string>
<string name="dream_overlay_weather_complication_desc" msgid="824503662089783824">"<xliff:g id="WEATHER_CONDITION">%1$s</xliff:g>, <xliff:g id="TEMPERATURE">%2$s</xliff:g>"</string>
- <!-- no translation found for note_task_button_label (8718616095800343136) -->
- <skip />
+ <string name="note_task_button_label" msgid="8718616095800343136">"Створення нотаток"</string>
<string name="broadcasting_description_is_broadcasting" msgid="765627502786404290">"Трансляція"</string>
<string name="bt_le_audio_broadcast_dialog_title" msgid="3605428497924077811">"Зупинити трансляцію з додатка <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string>
<string name="bt_le_audio_broadcast_dialog_sub_title" msgid="7889684551194225793">"Якщо ви зміните додаток (<xliff:g id="SWITCHAPP">%1$s</xliff:g>) або аудіовихід, поточну трансляцію буде припинено"</string>
<string name="bt_le_audio_broadcast_dialog_switch_app" msgid="6098768269397105733">"Змінити додаток для трансляції на <xliff:g id="SWITCHAPP">%1$s</xliff:g>"</string>
<string name="bt_le_audio_broadcast_dialog_different_output" msgid="7885102097302562674">"Змінити аудіовихід"</string>
<string name="bt_le_audio_broadcast_dialog_unknown_name" msgid="3791472237793443044">"Невідомо"</string>
- <string name="dream_date_complication_date_format" msgid="8191225366513860104">"EEE, d MMM"</string>
<string name="dream_time_complication_12_hr_time_format" msgid="4691197486690291529">"h:mm"</string>
<string name="dream_time_complication_24_hr_time_format" msgid="6248280719733640813">"kk:mm"</string>
<string name="keyguard_affordance_enablement_dialog_action_template" msgid="8164857863036314664">"Відкрити <xliff:g id="APPNAME">%1$s</xliff:g>"</string>
diff --git a/packages/SystemUI/res/values-ur/strings.xml b/packages/SystemUI/res/values-ur/strings.xml
index 1a0ce6f..7f80598 100644
--- a/packages/SystemUI/res/values-ur/strings.xml
+++ b/packages/SystemUI/res/values-ur/strings.xml
@@ -993,7 +993,6 @@
<string name="bt_le_audio_broadcast_dialog_switch_app" msgid="6098768269397105733">"<xliff:g id="SWITCHAPP">%1$s</xliff:g> پر براڈکاسٹ کریں"</string>
<string name="bt_le_audio_broadcast_dialog_different_output" msgid="7885102097302562674">"آؤٹ پٹ تبدیل کریں"</string>
<string name="bt_le_audio_broadcast_dialog_unknown_name" msgid="3791472237793443044">"نامعلوم"</string>
- <string name="dream_date_complication_date_format" msgid="8191225366513860104">"EEE, MMM d"</string>
<string name="dream_time_complication_12_hr_time_format" msgid="4691197486690291529">"h:mm"</string>
<string name="dream_time_complication_24_hr_time_format" msgid="6248280719733640813">"kk:mm"</string>
<string name="keyguard_affordance_enablement_dialog_action_template" msgid="8164857863036314664">"<xliff:g id="APPNAME">%1$s</xliff:g> کھولیں"</string>
diff --git a/packages/SystemUI/res/values-uz/strings.xml b/packages/SystemUI/res/values-uz/strings.xml
index 257edd4..99e8abc 100644
--- a/packages/SystemUI/res/values-uz/strings.xml
+++ b/packages/SystemUI/res/values-uz/strings.xml
@@ -993,7 +993,6 @@
<string name="bt_le_audio_broadcast_dialog_switch_app" msgid="6098768269397105733">"<xliff:g id="SWITCHAPP">%1$s</xliff:g> ilovasiga translatsiya"</string>
<string name="bt_le_audio_broadcast_dialog_different_output" msgid="7885102097302562674">"Ovoz chiqishini oʻzgartirish"</string>
<string name="bt_le_audio_broadcast_dialog_unknown_name" msgid="3791472237793443044">"Noaniq"</string>
- <string name="dream_date_complication_date_format" msgid="8191225366513860104">"EEE, MMM d"</string>
<string name="dream_time_complication_12_hr_time_format" msgid="4691197486690291529">"s:dd"</string>
<string name="dream_time_complication_24_hr_time_format" msgid="6248280719733640813">"kk:mm"</string>
<string name="keyguard_affordance_enablement_dialog_action_template" msgid="8164857863036314664">"Ochish: <xliff:g id="APPNAME">%1$s</xliff:g>"</string>
diff --git a/packages/SystemUI/res/values-vi/strings.xml b/packages/SystemUI/res/values-vi/strings.xml
index 53efd2d..88bf8de 100644
--- a/packages/SystemUI/res/values-vi/strings.xml
+++ b/packages/SystemUI/res/values-vi/strings.xml
@@ -872,8 +872,7 @@
<string name="media_output_dialog_button_stop_casting" msgid="6581379537930199189">"Dừng truyền"</string>
<string name="media_output_dialog_accessibility_title" msgid="4681741064190167888">"Các thiết bị có sẵn để xuất âm thanh."</string>
<string name="media_output_dialog_accessibility_seekbar" msgid="5332843993805568978">"Âm lượng"</string>
- <!-- no translation found for media_output_dialog_volume_percentage (1613984910585111798) -->
- <skip />
+ <string name="media_output_dialog_volume_percentage" msgid="1613984910585111798">"<xliff:g id="PERCENTAGE">%1$d</xliff:g>%%"</string>
<string name="media_output_first_broadcast_title" msgid="6292237789860753022">"Cách tính năng truyền hoạt động"</string>
<string name="media_output_broadcast" msgid="3555580945878071543">"Truyền"</string>
<string name="media_output_first_notify_broadcast_message" msgid="6353857724136398494">"Những người ở gần có thiết bị Bluetooth tương thích có thể nghe nội dung nghe nhìn bạn đang truyền"</string>
@@ -987,15 +986,13 @@
<string name="dream_overlay_status_bar_camera_mic_off" msgid="3199425257833773569">"Máy ảnh và micrô đang tắt"</string>
<string name="dream_overlay_status_bar_notification_indicator" msgid="8091389255691081711">"{count,plural, =1{# thông báo}other{# thông báo}}"</string>
<string name="dream_overlay_weather_complication_desc" msgid="824503662089783824">"<xliff:g id="WEATHER_CONDITION">%1$s</xliff:g>, <xliff:g id="TEMPERATURE">%2$s</xliff:g>"</string>
- <!-- no translation found for note_task_button_label (8718616095800343136) -->
- <skip />
+ <string name="note_task_button_label" msgid="8718616095800343136">"Ghi chú"</string>
<string name="broadcasting_description_is_broadcasting" msgid="765627502786404290">"Phát sóng"</string>
<string name="bt_le_audio_broadcast_dialog_title" msgid="3605428497924077811">"Dừng phát <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string>
<string name="bt_le_audio_broadcast_dialog_sub_title" msgid="7889684551194225793">"Nếu bạn phát <xliff:g id="SWITCHAPP">%1$s</xliff:g> hoặc thay đổi đầu ra, phiên truyền phát hiện tại sẽ dừng"</string>
<string name="bt_le_audio_broadcast_dialog_switch_app" msgid="6098768269397105733">"Phát <xliff:g id="SWITCHAPP">%1$s</xliff:g>"</string>
<string name="bt_le_audio_broadcast_dialog_different_output" msgid="7885102097302562674">"Thay đổi đầu ra"</string>
<string name="bt_le_audio_broadcast_dialog_unknown_name" msgid="3791472237793443044">"Không xác định"</string>
- <string name="dream_date_complication_date_format" msgid="8191225366513860104">"EEE, MMM d"</string>
<string name="dream_time_complication_12_hr_time_format" msgid="4691197486690291529">"h:mm"</string>
<string name="dream_time_complication_24_hr_time_format" msgid="6248280719733640813">"kk:mm"</string>
<string name="keyguard_affordance_enablement_dialog_action_template" msgid="8164857863036314664">"Mở <xliff:g id="APPNAME">%1$s</xliff:g>"</string>
diff --git a/packages/SystemUI/res/values-zh-rCN/strings.xml b/packages/SystemUI/res/values-zh-rCN/strings.xml
index c339e8d..81b61c6 100644
--- a/packages/SystemUI/res/values-zh-rCN/strings.xml
+++ b/packages/SystemUI/res/values-zh-rCN/strings.xml
@@ -872,8 +872,7 @@
<string name="media_output_dialog_button_stop_casting" msgid="6581379537930199189">"停止投放"</string>
<string name="media_output_dialog_accessibility_title" msgid="4681741064190167888">"音频输出的可用设备。"</string>
<string name="media_output_dialog_accessibility_seekbar" msgid="5332843993805568978">"音量"</string>
- <!-- no translation found for media_output_dialog_volume_percentage (1613984910585111798) -->
- <skip />
+ <string name="media_output_dialog_volume_percentage" msgid="1613984910585111798">"<xliff:g id="PERCENTAGE">%1$d</xliff:g>%%"</string>
<string name="media_output_first_broadcast_title" msgid="6292237789860753022">"广播的运作方式"</string>
<string name="media_output_broadcast" msgid="3555580945878071543">"广播"</string>
<string name="media_output_first_notify_broadcast_message" msgid="6353857724136398494">"附近使用兼容蓝牙设备的用户可以收听您广播的媒体内容"</string>
@@ -987,15 +986,13 @@
<string name="dream_overlay_status_bar_camera_mic_off" msgid="3199425257833773569">"摄像头和麦克风已关闭"</string>
<string name="dream_overlay_status_bar_notification_indicator" msgid="8091389255691081711">"{count,plural, =1{# 条通知}other{# 条通知}}"</string>
<string name="dream_overlay_weather_complication_desc" msgid="824503662089783824">"<xliff:g id="WEATHER_CONDITION">%1$s</xliff:g>,<xliff:g id="TEMPERATURE">%2$s</xliff:g>"</string>
- <!-- no translation found for note_task_button_label (8718616095800343136) -->
- <skip />
+ <string name="note_task_button_label" msgid="8718616095800343136">"记录"</string>
<string name="broadcasting_description_is_broadcasting" msgid="765627502786404290">"正在广播"</string>
<string name="bt_le_audio_broadcast_dialog_title" msgid="3605428497924077811">"要停止广播“<xliff:g id="APP_NAME">%1$s</xliff:g>”的内容吗?"</string>
<string name="bt_le_audio_broadcast_dialog_sub_title" msgid="7889684551194225793">"如果广播“<xliff:g id="SWITCHAPP">%1$s</xliff:g>”的内容或更改输出来源,当前的广播就会停止"</string>
<string name="bt_le_audio_broadcast_dialog_switch_app" msgid="6098768269397105733">"广播“<xliff:g id="SWITCHAPP">%1$s</xliff:g>”的内容"</string>
<string name="bt_le_audio_broadcast_dialog_different_output" msgid="7885102097302562674">"更改输出来源"</string>
<string name="bt_le_audio_broadcast_dialog_unknown_name" msgid="3791472237793443044">"未知"</string>
- <string name="dream_date_complication_date_format" msgid="8191225366513860104">"EEE, MMM d"</string>
<string name="dream_time_complication_12_hr_time_format" msgid="4691197486690291529">"h:mm"</string>
<string name="dream_time_complication_24_hr_time_format" msgid="6248280719733640813">"kk:mm"</string>
<string name="keyguard_affordance_enablement_dialog_action_template" msgid="8164857863036314664">"打开<xliff:g id="APPNAME">%1$s</xliff:g>"</string>
diff --git a/packages/SystemUI/res/values-zh-rHK/strings.xml b/packages/SystemUI/res/values-zh-rHK/strings.xml
index 6ed2835..3d7ba058 100644
--- a/packages/SystemUI/res/values-zh-rHK/strings.xml
+++ b/packages/SystemUI/res/values-zh-rHK/strings.xml
@@ -872,8 +872,7 @@
<string name="media_output_dialog_button_stop_casting" msgid="6581379537930199189">"停止投放"</string>
<string name="media_output_dialog_accessibility_title" msgid="4681741064190167888">"可用作音訊輸出的裝置"</string>
<string name="media_output_dialog_accessibility_seekbar" msgid="5332843993805568978">"音量"</string>
- <!-- no translation found for media_output_dialog_volume_percentage (1613984910585111798) -->
- <skip />
+ <string name="media_output_dialog_volume_percentage" msgid="1613984910585111798">"<xliff:g id="PERCENTAGE">%1$d</xliff:g>%%"</string>
<string name="media_output_first_broadcast_title" msgid="6292237789860753022">"廣播運作方式"</string>
<string name="media_output_broadcast" msgid="3555580945878071543">"廣播"</string>
<string name="media_output_first_notify_broadcast_message" msgid="6353857724136398494">"附近有兼容藍牙裝置的人可收聽您正在廣播的媒體內容"</string>
@@ -987,15 +986,13 @@
<string name="dream_overlay_status_bar_camera_mic_off" msgid="3199425257833773569">"相機和麥克風已關閉"</string>
<string name="dream_overlay_status_bar_notification_indicator" msgid="8091389255691081711">"{count,plural, =1{# 則通知}other{# 則通知}}"</string>
<string name="dream_overlay_weather_complication_desc" msgid="824503662089783824">"<xliff:g id="WEATHER_CONDITION">%1$s</xliff:g>,<xliff:g id="TEMPERATURE">%2$s</xliff:g>"</string>
- <!-- no translation found for note_task_button_label (8718616095800343136) -->
- <skip />
+ <string name="note_task_button_label" msgid="8718616095800343136">"做筆記"</string>
<string name="broadcasting_description_is_broadcasting" msgid="765627502786404290">"廣播"</string>
<string name="bt_le_audio_broadcast_dialog_title" msgid="3605428497924077811">"要停止廣播「<xliff:g id="APP_NAME">%1$s</xliff:g>」的內容嗎?"</string>
<string name="bt_le_audio_broadcast_dialog_sub_title" msgid="7889684551194225793">"如要廣播「<xliff:g id="SWITCHAPP">%1$s</xliff:g>」的內容或變更輸出來源,系統就會停止廣播目前的內容"</string>
<string name="bt_le_audio_broadcast_dialog_switch_app" msgid="6098768269397105733">"廣播「<xliff:g id="SWITCHAPP">%1$s</xliff:g>」的內容"</string>
<string name="bt_le_audio_broadcast_dialog_different_output" msgid="7885102097302562674">"變更輸出來源"</string>
<string name="bt_le_audio_broadcast_dialog_unknown_name" msgid="3791472237793443044">"不明"</string>
- <string name="dream_date_complication_date_format" msgid="8191225366513860104">"MMM d EEE"</string>
<string name="dream_time_complication_12_hr_time_format" msgid="4691197486690291529">"h:mm"</string>
<string name="dream_time_complication_24_hr_time_format" msgid="6248280719733640813">"kk:mm"</string>
<string name="keyguard_affordance_enablement_dialog_action_template" msgid="8164857863036314664">"開啟「<xliff:g id="APPNAME">%1$s</xliff:g>」"</string>
diff --git a/packages/SystemUI/res/values-zh-rTW/strings.xml b/packages/SystemUI/res/values-zh-rTW/strings.xml
index b1698d5..47c0561 100644
--- a/packages/SystemUI/res/values-zh-rTW/strings.xml
+++ b/packages/SystemUI/res/values-zh-rTW/strings.xml
@@ -872,8 +872,7 @@
<string name="media_output_dialog_button_stop_casting" msgid="6581379537930199189">"停止投放"</string>
<string name="media_output_dialog_accessibility_title" msgid="4681741064190167888">"可用於輸出音訊的裝置。"</string>
<string name="media_output_dialog_accessibility_seekbar" msgid="5332843993805568978">"音量"</string>
- <!-- no translation found for media_output_dialog_volume_percentage (1613984910585111798) -->
- <skip />
+ <string name="media_output_dialog_volume_percentage" msgid="1613984910585111798">"<xliff:g id="PERCENTAGE">%1$d</xliff:g>%%"</string>
<string name="media_output_first_broadcast_title" msgid="6292237789860753022">"廣播功能的運作方式"</string>
<string name="media_output_broadcast" msgid="3555580945878071543">"廣播"</string>
<string name="media_output_first_notify_broadcast_message" msgid="6353857724136398494">"如果附近的人有相容的藍牙裝置,就可以聽到你正在廣播的媒體內容"</string>
@@ -987,15 +986,13 @@
<string name="dream_overlay_status_bar_camera_mic_off" msgid="3199425257833773569">"已關閉相機和麥克風"</string>
<string name="dream_overlay_status_bar_notification_indicator" msgid="8091389255691081711">"{count,plural, =1{# 則通知}other{# 則通知}}"</string>
<string name="dream_overlay_weather_complication_desc" msgid="824503662089783824">"<xliff:g id="WEATHER_CONDITION">%1$s</xliff:g>,<xliff:g id="TEMPERATURE">%2$s</xliff:g>"</string>
- <!-- no translation found for note_task_button_label (8718616095800343136) -->
- <skip />
+ <string name="note_task_button_label" msgid="8718616095800343136">"做筆記"</string>
<string name="broadcasting_description_is_broadcasting" msgid="765627502786404290">"廣播"</string>
<string name="bt_le_audio_broadcast_dialog_title" msgid="3605428497924077811">"要停止播送「<xliff:g id="APP_NAME">%1$s</xliff:g>」的內容嗎?"</string>
<string name="bt_le_audio_broadcast_dialog_sub_title" msgid="7889684551194225793">"如果播送「<xliff:g id="SWITCHAPP">%1$s</xliff:g>」的內容或變更輸出來源,系統就會停止播送目前的內容"</string>
<string name="bt_le_audio_broadcast_dialog_switch_app" msgid="6098768269397105733">"播送「<xliff:g id="SWITCHAPP">%1$s</xliff:g>」的內容"</string>
<string name="bt_le_audio_broadcast_dialog_different_output" msgid="7885102097302562674">"變更輸出來源"</string>
<string name="bt_le_audio_broadcast_dialog_unknown_name" msgid="3791472237793443044">"不明"</string>
- <string name="dream_date_complication_date_format" msgid="8191225366513860104">"MMM d EEE"</string>
<string name="dream_time_complication_12_hr_time_format" msgid="4691197486690291529">"h:mm"</string>
<string name="dream_time_complication_24_hr_time_format" msgid="6248280719733640813">"kk:mm"</string>
<string name="keyguard_affordance_enablement_dialog_action_template" msgid="8164857863036314664">"開啟「<xliff:g id="APPNAME">%1$s</xliff:g>」"</string>
diff --git a/packages/SystemUI/res/values-zu/strings.xml b/packages/SystemUI/res/values-zu/strings.xml
index 38a6136..b778519 100644
--- a/packages/SystemUI/res/values-zu/strings.xml
+++ b/packages/SystemUI/res/values-zu/strings.xml
@@ -872,8 +872,7 @@
<string name="media_output_dialog_button_stop_casting" msgid="6581379537930199189">"Misa ukusakaza"</string>
<string name="media_output_dialog_accessibility_title" msgid="4681741064190167888">"Amadivayisi atholakalayo okukhipha umsindo."</string>
<string name="media_output_dialog_accessibility_seekbar" msgid="5332843993805568978">"Ivolumu"</string>
- <!-- no translation found for media_output_dialog_volume_percentage (1613984910585111798) -->
- <skip />
+ <string name="media_output_dialog_volume_percentage" msgid="1613984910585111798">"<xliff:g id="PERCENTAGE">%1$d</xliff:g>%%"</string>
<string name="media_output_first_broadcast_title" msgid="6292237789860753022">"Indlela ukusakaza okusebenza ngayo"</string>
<string name="media_output_broadcast" msgid="3555580945878071543">"Sakaza"</string>
<string name="media_output_first_notify_broadcast_message" msgid="6353857724136398494">"Abantu abaseduze nawe abanamadivayisi e-Bluetooth ahambisanayo bangalalela imidiya oyisakazayo"</string>
@@ -987,15 +986,13 @@
<string name="dream_overlay_status_bar_camera_mic_off" msgid="3199425257833773569">"Ikhamera nemakrofoni kuvaliwe"</string>
<string name="dream_overlay_status_bar_notification_indicator" msgid="8091389255691081711">"{count,plural, =1{Isaziso esingu-#}one{Izaziso ezingu-#}other{Izaziso ezingu-#}}"</string>
<string name="dream_overlay_weather_complication_desc" msgid="824503662089783824">"<xliff:g id="WEATHER_CONDITION">%1$s</xliff:g>, <xliff:g id="TEMPERATURE">%2$s</xliff:g>"</string>
- <!-- no translation found for note_task_button_label (8718616095800343136) -->
- <skip />
+ <string name="note_task_button_label" msgid="8718616095800343136">"Ukuthatha amanothi"</string>
<string name="broadcasting_description_is_broadcasting" msgid="765627502786404290">"Ukusakaza"</string>
<string name="bt_le_audio_broadcast_dialog_title" msgid="3605428497924077811">"Misa ukusakaza i-<xliff:g id="APP_NAME">%1$s</xliff:g>?"</string>
<string name="bt_le_audio_broadcast_dialog_sub_title" msgid="7889684551194225793">"Uma usakaza i-<xliff:g id="SWITCHAPP">%1$s</xliff:g> noma ushintsha okuphumayo, ukusakaza kwakho kwamanje kuzoma"</string>
<string name="bt_le_audio_broadcast_dialog_switch_app" msgid="6098768269397105733">"Sakaza i-<xliff:g id="SWITCHAPP">%1$s</xliff:g>"</string>
<string name="bt_le_audio_broadcast_dialog_different_output" msgid="7885102097302562674">"Shintsha okuphumayo"</string>
<string name="bt_le_audio_broadcast_dialog_unknown_name" msgid="3791472237793443044">"Akwaziwa"</string>
- <string name="dream_date_complication_date_format" msgid="8191225366513860104">"EEE, MMM d"</string>
<string name="dream_time_complication_12_hr_time_format" msgid="4691197486690291529">"h:mm"</string>
<string name="dream_time_complication_24_hr_time_format" msgid="6248280719733640813">"kk:mm"</string>
<string name="keyguard_affordance_enablement_dialog_action_template" msgid="8164857863036314664">"Vula i-<xliff:g id="APPNAME">%1$s</xliff:g>"</string>
diff --git a/packages/SystemUI/res/values/config.xml b/packages/SystemUI/res/values/config.xml
index 4cda8c7..2d756ae 100644
--- a/packages/SystemUI/res/values/config.xml
+++ b/packages/SystemUI/res/values/config.xml
@@ -773,15 +773,15 @@
<integer name="complicationFadeOutDelayMs">200</integer>
<!-- Duration in milliseconds of the dream in un-blur animation. -->
- <integer name="config_dreamOverlayInBlurDurationMs">249</integer>
- <!-- Delay in milliseconds of the dream in un-blur animation. -->
- <integer name="config_dreamOverlayInBlurDelayMs">133</integer>
+ <integer name="config_dreamOverlayInBlurDurationMs">250</integer>
<!-- Duration in milliseconds of the dream in complications fade-in animation. -->
- <integer name="config_dreamOverlayInComplicationsDurationMs">282</integer>
- <!-- Delay in milliseconds of the dream in top complications fade-in animation. -->
- <integer name="config_dreamOverlayInTopComplicationsDelayMs">216</integer>
- <!-- Delay in milliseconds of the dream in bottom complications fade-in animation. -->
- <integer name="config_dreamOverlayInBottomComplicationsDelayMs">299</integer>
+ <integer name="config_dreamOverlayInComplicationsDurationMs">250</integer>
+ <!-- Duration in milliseconds of the y-translation animation when entering a dream -->
+ <integer name="config_dreamOverlayInTranslationYDurationMs">917</integer>
+
+ <!-- Delay in milliseconds before switching to the dock user and dreaming if a secondary user is
+ active when the device is locked and docked. 0 indicates disabled. Default is 1 minute. -->
+ <integer name="config_defaultDockUserTimeoutMs">60000</integer>
<!-- Icons that don't show in a collapsed non-keyguard statusbar -->
<string-array name="config_collapsed_statusbar_icon_blocklist" translatable="false">
@@ -824,6 +824,8 @@
slot. If the user did make a choice, even if the choice is the "None" option, the default is
ignored. -->
<string-array name="config_keyguardQuickAffordanceDefaults" translatable="false">
+ <item>bottom_start:home</item>
+ <item>bottom_end:wallet</item>
</string-array>
</resources>
diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml
index c10e7528..e8a8534 100644
--- a/packages/SystemUI/res/values/dimens.xml
+++ b/packages/SystemUI/res/values/dimens.xml
@@ -758,6 +758,8 @@
<dimen name="keyguard_affordance_fixed_height">48dp</dimen>
<dimen name="keyguard_affordance_fixed_width">48dp</dimen>
<dimen name="keyguard_affordance_fixed_radius">24dp</dimen>
+ <!-- Amount the button should shake when it's not long-pressed for long enough. -->
+ <dimen name="keyguard_affordance_shake_amplitude">8dp</dimen>
<dimen name="keyguard_affordance_horizontal_offset">32dp</dimen>
<dimen name="keyguard_affordance_vertical_offset">32dp</dimen>
@@ -1505,6 +1507,8 @@
<dimen name="dream_overlay_status_bar_extra_margin">8dp</dimen>
<!-- Dream overlay complications related dimensions -->
+ <!-- The blur radius applied to the dream overlay when entering and exiting dreams -->
+ <dimen name="dream_overlay_anim_blur_radius">50dp</dimen>
<dimen name="dream_overlay_complication_clock_time_text_size">86dp</dimen>
<dimen name="dream_overlay_complication_clock_time_translation_y">28dp</dimen>
<dimen name="dream_overlay_complication_home_controls_padding">28dp</dimen>
@@ -1558,6 +1562,7 @@
<dimen name="dream_overlay_complication_margin">0dp</dimen>
<dimen name="dream_overlay_y_offset">80dp</dimen>
+ <dimen name="dream_overlay_entry_y_offset">40dp</dimen>
<dimen name="dream_overlay_exit_y_offset">40dp</dimen>
<dimen name="status_view_margin_horizontal">0dp</dimen>
diff --git a/packages/SystemUI/res/values/flags.xml b/packages/SystemUI/res/values/flags.xml
index fd2e324..c5ffc94 100644
--- a/packages/SystemUI/res/values/flags.xml
+++ b/packages/SystemUI/res/values/flags.xml
@@ -30,6 +30,9 @@
<bool name="flag_charging_ripple">false</bool>
+ <!-- Whether to show chipbar UI whenever the device is unlocked by ActiveUnlock. -->
+ <bool name="flag_active_unlock_chipbar">true</bool>
+
<bool name="flag_smartspace">false</bool>
<!-- Whether the user switcher chip shows in the status bar. When true, the multi user
diff --git a/packages/SystemUI/res/values/ids.xml b/packages/SystemUI/res/values/ids.xml
index 7ca42f7..b5ac940 100644
--- a/packages/SystemUI/res/values/ids.xml
+++ b/packages/SystemUI/res/values/ids.xml
@@ -142,6 +142,9 @@
<item type="id" name="row_tag_for_content_view" />
+ <!-- Chipbar -->
+ <item type="id" name="tag_chipbar_info"/>
+
<!-- Optional cancel button on Keyguard -->
<item type="id" name="cancel_button"/>
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index 643f831..ca2cf1a 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -2740,6 +2740,12 @@
-->
<string name="keyguard_affordance_enablement_dialog_home_instruction_2">• At least one device is available</string>
+ <!--
+ Error message shown when a button should be pressed and held to activate it, usually shown when
+ the user attempted to tap the button or held it for too short a time. [CHAR LIMIT=32].
+ -->
+ <string name="keyguard_affordance_press_too_short">Press and hold to activate</string>
+
<!-- Text for education page of cancel button to hide the page. [CHAR_LIMIT=NONE] -->
<string name="rear_display_bottom_sheet_cancel">Cancel</string>
<!-- Text for the user to confirm they flipped the device around. [CHAR_LIMIT=NONE] -->
@@ -2756,4 +2762,7 @@
<string name="rear_display_accessibility_folded_animation">Foldable device being unfolded</string>
<!-- Text for education page content description for unfolded animation. [CHAR_LIMIT=NONE] -->
<string name="rear_display_accessibility_unfolded_animation">Foldable device being flipped around</string>
+
+ <!-- Title for notification of low stylus battery. [CHAR_LIMIT=NONE] -->
+ <string name="stylus_battery_low">Stylus battery low</string>
</resources>
diff --git a/packages/SystemUI/res/xml/media_session_expanded.xml b/packages/SystemUI/res/xml/media_session_expanded.xml
index 64c2ef1..7de0a5e 100644
--- a/packages/SystemUI/res/xml/media_session_expanded.xml
+++ b/packages/SystemUI/res/xml/media_session_expanded.xml
@@ -88,20 +88,18 @@
The chain is set to "spread" so that the progress bar can be weighted to fill any empty space.
-->
<Constraint
- android:id="@+id/media_scrubbing_elapsed_time"
+ android:id="@+id/actionPrev"
android:layout_width="48dp"
android:layout_height="48dp"
app:layout_constraintLeft_toLeftOf="parent"
- app:layout_constraintRight_toLeftOf="@id/actionPrev"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintHorizontal_chainStyle="spread" />
<Constraint
- android:id="@+id/actionPrev"
+ android:id="@+id/media_scrubbing_elapsed_time"
android:layout_width="48dp"
android:layout_height="48dp"
- app:layout_constraintLeft_toRightOf="@id/media_scrubbing_elapsed_time"
- app:layout_constraintRight_toLeftOf="@id/media_progress_bar"
+ app:layout_constraintLeft_toRightOf="@id/actionPrev"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintHorizontal_chainStyle="spread" />
@@ -109,7 +107,7 @@
android:id="@+id/media_progress_bar"
android:layout_width="0dp"
android:layout_height="48dp"
- app:layout_constraintLeft_toRightOf="@id/actionPrev"
+ app:layout_constraintLeft_toRightOf="@id/media_scrubbing_elapsed_time"
app:layout_constraintRight_toLeftOf="@id/actionNext"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintHorizontal_weight="1" />
@@ -118,7 +116,6 @@
android:id="@+id/actionNext"
android:layout_width="48dp"
android:layout_height="48dp"
- app:layout_constraintLeft_toRightOf="@id/media_progress_bar"
app:layout_constraintRight_toLeftOf="@id/media_scrubbing_total_time"
app:layout_constraintBottom_toBottomOf="parent" />
@@ -126,7 +123,6 @@
android:id="@+id/media_scrubbing_total_time"
android:layout_width="48dp"
android:layout_height="48dp"
- app:layout_constraintLeft_toRightOf="@id/actionNext"
app:layout_constraintRight_toLeftOf="@id/action0"
app:layout_constraintBottom_toBottomOf="parent" />
@@ -134,7 +130,6 @@
android:id="@+id/action0"
android:layout_width="48dp"
android:layout_height="48dp"
- app:layout_constraintLeft_toRightOf="@id/media_scrubbing_total_time"
app:layout_constraintRight_toLeftOf="@id/action1"
app:layout_constraintBottom_toBottomOf="parent" />
@@ -142,7 +137,6 @@
android:id="@+id/action1"
android:layout_width="48dp"
android:layout_height="48dp"
- app:layout_constraintLeft_toRightOf="@id/action0"
app:layout_constraintRight_toLeftOf="@id/action2"
app:layout_constraintBottom_toBottomOf="parent" />
@@ -150,7 +144,6 @@
android:id="@+id/action2"
android:layout_width="48dp"
android:layout_height="48dp"
- app:layout_constraintLeft_toRightOf="@id/action1"
app:layout_constraintRight_toLeftOf="@id/action3"
app:layout_constraintBottom_toBottomOf="parent" />
@@ -158,7 +151,6 @@
android:id="@+id/action3"
android:layout_width="48dp"
android:layout_height="48dp"
- app:layout_constraintLeft_toRightOf="@id/action2"
app:layout_constraintRight_toLeftOf="@id/action4"
app:layout_constraintBottom_toBottomOf="parent" />
@@ -166,7 +158,6 @@
android:id="@+id/action4"
android:layout_width="48dp"
android:layout_height="48dp"
- app:layout_constraintLeft_toRightOf="@id/action3"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintBottom_toBottomOf="parent" />
</ConstraintSet>
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/rotation/RotationButtonController.java b/packages/SystemUI/shared/src/com/android/systemui/shared/rotation/RotationButtonController.java
index 6087655..8ee893c 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/rotation/RotationButtonController.java
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/rotation/RotationButtonController.java
@@ -71,7 +71,7 @@
*/
public class RotationButtonController {
- private static final String TAG = "StatusBar/RotationButtonController";
+ private static final String TAG = "RotationButtonController";
private static final int BUTTON_FADE_IN_OUT_DURATION_MS = 100;
private static final int NAVBAR_HIDDEN_PENDING_ICON_TIMEOUT_MS = 20000;
private static final Interpolator LINEAR_INTERPOLATOR = new LinearInterpolator();
@@ -377,6 +377,7 @@
}
// Prepare to show the navbar icon by updating the icon style to change anim params
+ Log.i(TAG, "onRotationProposal(rotation=" + rotation + ")");
mLastRotationSuggestion = rotation; // Remember rotation for click
final boolean rotationCCW = Utilities.isRotationAnimationCCW(windowRotation, rotation);
if (windowRotation == Surface.ROTATION_0 || windowRotation == Surface.ROTATION_180) {
@@ -499,6 +500,7 @@
mUiEventLogger.log(RotationButtonEvent.ROTATION_SUGGESTION_ACCEPTED);
incrementNumAcceptedRotationSuggestionsIfNeeded();
setRotationLockedAtAngle(mLastRotationSuggestion);
+ Log.i(TAG, "onRotateSuggestionClick() mLastRotationSuggestion=" + mLastRotationSuggestion);
v.performHapticFeedback(HapticFeedbackConstants.VIRTUAL_KEY);
}
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitch.java b/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitch.java
index 40423cd..62babad 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitch.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitch.java
@@ -9,6 +9,7 @@
import android.util.AttributeSet;
import android.util.Log;
import android.view.View;
+import android.view.ViewGroup;
import android.widget.FrameLayout;
import android.widget.RelativeLayout;
@@ -43,6 +44,21 @@
public static final int LARGE = 0;
public static final int SMALL = 1;
+ /** Returns a region for the large clock to position itself, based on the given parent. */
+ public static Rect getLargeClockRegion(ViewGroup parent) {
+ int largeClockTopMargin = parent.getResources()
+ .getDimensionPixelSize(R.dimen.keyguard_large_clock_top_margin);
+ int targetHeight = parent.getResources()
+ .getDimensionPixelSize(R.dimen.large_clock_text_size) * 2;
+ int top = parent.getHeight() / 2 - targetHeight / 2
+ + largeClockTopMargin / 2;
+ return new Rect(
+ parent.getLeft(),
+ top,
+ parent.getRight(),
+ top + targetHeight);
+ }
+
/**
* Frame for small/large clocks
*/
@@ -129,17 +145,8 @@
}
if (mLargeClockFrame.isLaidOut()) {
- int largeClockTopMargin = getResources()
- .getDimensionPixelSize(R.dimen.keyguard_large_clock_top_margin);
- int targetHeight = getResources()
- .getDimensionPixelSize(R.dimen.large_clock_text_size) * 2;
- int top = mLargeClockFrame.getHeight() / 2 - targetHeight / 2
- + largeClockTopMargin / 2;
- mClock.getLargeClock().getEvents().onTargetRegionChanged(new Rect(
- mLargeClockFrame.getLeft(),
- top,
- mLargeClockFrame.getRight(),
- top + targetHeight));
+ mClock.getLargeClock().getEvents().onTargetRegionChanged(
+ getLargeClockRegion(mLargeClockFrame));
}
}
}
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardPasswordView.java b/packages/SystemUI/src/com/android/keyguard/KeyguardPasswordView.java
index c985fd7..c1fae9e 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardPasswordView.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardPasswordView.java
@@ -24,6 +24,7 @@
import static com.android.keyguard.KeyguardSecurityView.PROMPT_REASON_PREPARE_FOR_UPDATE;
import static com.android.keyguard.KeyguardSecurityView.PROMPT_REASON_RESTART;
import static com.android.keyguard.KeyguardSecurityView.PROMPT_REASON_TIMEOUT;
+import static com.android.keyguard.KeyguardSecurityView.PROMPT_REASON_TRUSTAGENT_EXPIRED;
import static com.android.keyguard.KeyguardSecurityView.PROMPT_REASON_USER_REQUEST;
import android.animation.Animator;
@@ -107,6 +108,8 @@
return R.string.kg_prompt_reason_timeout_password;
case PROMPT_REASON_NON_STRONG_BIOMETRIC_TIMEOUT:
return R.string.kg_prompt_reason_timeout_password;
+ case PROMPT_REASON_TRUSTAGENT_EXPIRED:
+ return R.string.kg_prompt_reason_timeout_password;
case PROMPT_REASON_NONE:
return 0;
default:
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardPatternViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardPatternViewController.java
index 571d274..0c17489 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardPatternViewController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardPatternViewController.java
@@ -313,6 +313,9 @@
case PROMPT_REASON_NON_STRONG_BIOMETRIC_TIMEOUT:
mMessageAreaController.setMessage(R.string.kg_prompt_reason_timeout_pattern);
break;
+ case PROMPT_REASON_TRUSTAGENT_EXPIRED:
+ mMessageAreaController.setMessage(R.string.kg_prompt_reason_timeout_pattern);
+ break;
case PROMPT_REASON_NONE:
break;
default:
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardPinBasedInputView.java b/packages/SystemUI/src/com/android/keyguard/KeyguardPinBasedInputView.java
index c46e33d..0a91150 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardPinBasedInputView.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardPinBasedInputView.java
@@ -22,6 +22,7 @@
import static com.android.keyguard.KeyguardSecurityView.PROMPT_REASON_PREPARE_FOR_UPDATE;
import static com.android.keyguard.KeyguardSecurityView.PROMPT_REASON_RESTART;
import static com.android.keyguard.KeyguardSecurityView.PROMPT_REASON_TIMEOUT;
+import static com.android.keyguard.KeyguardSecurityView.PROMPT_REASON_TRUSTAGENT_EXPIRED;
import static com.android.keyguard.KeyguardSecurityView.PROMPT_REASON_USER_REQUEST;
import android.animation.Animator;
@@ -123,6 +124,8 @@
return R.string.kg_prompt_reason_timeout_pin;
case PROMPT_REASON_NON_STRONG_BIOMETRIC_TIMEOUT:
return R.string.kg_prompt_reason_timeout_pin;
+ case PROMPT_REASON_TRUSTAGENT_EXPIRED:
+ return R.string.kg_prompt_reason_timeout_pin;
case PROMPT_REASON_NONE:
return 0;
default:
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityView.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityView.java
index ac00e94..67d77e5 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityView.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityView.java
@@ -61,6 +61,12 @@
int PROMPT_REASON_NON_STRONG_BIOMETRIC_TIMEOUT = 7;
/**
+ * Some auth is required because the trustagent expired either from timeout or manually by the
+ * user
+ */
+ int PROMPT_REASON_TRUSTAGENT_EXPIRED = 8;
+
+ /**
* Reset the view and prepare to take input. This should do things like clearing the
* password or pattern and clear error messages.
*/
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
index ec4b780..8283ce8 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
@@ -480,7 +480,7 @@
}
@Override
- public void onTrustChanged(boolean enabled, int userId, int flags,
+ public void onTrustChanged(boolean enabled, boolean newlyUnlocked, int userId, int flags,
List<String> trustGrantedMessages) {
Assert.isMainThread();
boolean wasTrusted = mUserHasTrust.get(userId, false);
diff --git a/packages/SystemUI/src/com/android/keyguard/mediator/ScreenOnCoordinator.kt b/packages/SystemUI/src/com/android/keyguard/mediator/ScreenOnCoordinator.kt
index 4b7e9a5..98ac2c0 100644
--- a/packages/SystemUI/src/com/android/keyguard/mediator/ScreenOnCoordinator.kt
+++ b/packages/SystemUI/src/com/android/keyguard/mediator/ScreenOnCoordinator.kt
@@ -16,31 +16,25 @@
package com.android.keyguard.mediator
+import android.annotation.BinderThread
import android.os.Trace
-
import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.keyguard.ScreenLifecycle
-import com.android.systemui.util.concurrency.Execution
-import com.android.systemui.util.concurrency.PendingTasksContainer
import com.android.systemui.unfold.SysUIUnfoldComponent
+import com.android.systemui.util.concurrency.PendingTasksContainer
import com.android.systemui.util.kotlin.getOrNull
-
import java.util.Optional
-
import javax.inject.Inject
/**
* Coordinates screen on/turning on animations for the KeyguardViewMediator. Specifically for
* screen on events, this will invoke the onDrawn Runnable after all tasks have completed. This
- * should route back to the KeyguardService, which informs the system_server that keyguard has
- * drawn.
+ * should route back to the [com.android.systemui.keyguard.KeyguardService], which informs
+ * the system_server that keyguard has drawn.
*/
@SysUISingleton
class ScreenOnCoordinator @Inject constructor(
- screenLifecycle: ScreenLifecycle,
- unfoldComponent: Optional<SysUIUnfoldComponent>,
- private val execution: Execution
-) : ScreenLifecycle.Observer {
+ unfoldComponent: Optional<SysUIUnfoldComponent>
+) {
private val unfoldLightRevealAnimation = unfoldComponent.map(
SysUIUnfoldComponent::getUnfoldLightRevealOverlayAnimation).getOrNull()
@@ -48,15 +42,12 @@
SysUIUnfoldComponent::getFoldAodAnimationController).getOrNull()
private val pendingTasks = PendingTasksContainer()
- init {
- screenLifecycle.addObserver(this)
- }
-
/**
* When turning on, registers tasks that may need to run before invoking [onDrawn].
+ * This is called on a binder thread from [com.android.systemui.keyguard.KeyguardService].
*/
- override fun onScreenTurningOn(onDrawn: Runnable) {
- execution.assertIsMainThread()
+ @BinderThread
+ fun onScreenTurningOn(onDrawn: Runnable) {
Trace.beginSection("ScreenOnCoordinator#onScreenTurningOn")
pendingTasks.reset()
@@ -68,11 +59,13 @@
Trace.endSection()
}
- override fun onScreenTurnedOn() {
- execution.assertIsMainThread()
-
+ /**
+ * Called when screen is fully turned on and screen on blocker is removed.
+ * This is called on a binder thread from [com.android.systemui.keyguard.KeyguardService].
+ */
+ @BinderThread
+ fun onScreenTurnedOn() {
foldAodAnimationController?.onScreenTurnedOn()
-
pendingTasks.reset()
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/camera/CameraGestureHelper.kt b/packages/SystemUI/src/com/android/systemui/camera/CameraGestureHelper.kt
index e2ef247..58d40d3 100644
--- a/packages/SystemUI/src/com/android/systemui/camera/CameraGestureHelper.kt
+++ b/packages/SystemUI/src/com/android/systemui/camera/CameraGestureHelper.kt
@@ -28,7 +28,6 @@
import android.os.UserHandle
import android.util.Log
import android.view.WindowManager
-import androidx.annotation.VisibleForTesting
import com.android.keyguard.KeyguardUpdateMonitor
import com.android.systemui.ActivityIntentHelper
import com.android.systemui.dagger.qualifiers.Main
@@ -83,7 +82,7 @@
*/
fun launchCamera(source: Int) {
val intent: Intent = getStartCameraIntent()
- intent.putExtra(EXTRA_CAMERA_LAUNCH_SOURCE, source)
+ intent.putExtra(CameraIntents.EXTRA_LAUNCH_SOURCE, source)
val wouldLaunchResolverActivity = activityIntentHelper.wouldLaunchResolverActivity(
intent, KeyguardUpdateMonitor.getCurrentUser()
)
@@ -149,9 +148,4 @@
cameraIntents.getInsecureCameraIntent()
}
}
-
- companion object {
- @VisibleForTesting
- const val EXTRA_CAMERA_LAUNCH_SOURCE = "com.android.systemui.camera_launch_source"
- }
}
diff --git a/packages/SystemUI/src/com/android/systemui/camera/CameraIntents.kt b/packages/SystemUI/src/com/android/systemui/camera/CameraIntents.kt
index f8a2002..867faf9 100644
--- a/packages/SystemUI/src/com/android/systemui/camera/CameraIntents.kt
+++ b/packages/SystemUI/src/com/android/systemui/camera/CameraIntents.kt
@@ -29,6 +29,7 @@
MediaStore.INTENT_ACTION_STILL_IMAGE_CAMERA_SECURE
val DEFAULT_INSECURE_CAMERA_INTENT_ACTION =
MediaStore.INTENT_ACTION_STILL_IMAGE_CAMERA
+ const val EXTRA_LAUNCH_SOURCE = "com.android.systemui.camera_launch_source"
@JvmStatic
fun getOverrideCameraPackage(context: Context): String? {
diff --git a/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardOverlayController.java b/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardOverlayController.java
index 63c2065..f97d6af 100644
--- a/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardOverlayController.java
+++ b/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardOverlayController.java
@@ -22,6 +22,7 @@
import static com.android.internal.config.sysui.SystemUiDeviceConfigFlags.CLIPBOARD_OVERLAY_SHOW_ACTIONS;
import static com.android.internal.config.sysui.SystemUiDeviceConfigFlags.CLIPBOARD_OVERLAY_SHOW_EDIT_BUTTON;
+import static com.android.systemui.clipboardoverlay.ClipboardOverlayEvent.CLIPBOARD_OVERLAY_ACTION_SHOWN;
import static com.android.systemui.clipboardoverlay.ClipboardOverlayEvent.CLIPBOARD_OVERLAY_ACTION_TAPPED;
import static com.android.systemui.clipboardoverlay.ClipboardOverlayEvent.CLIPBOARD_OVERLAY_DISMISSED_OTHER;
import static com.android.systemui.clipboardoverlay.ClipboardOverlayEvent.CLIPBOARD_OVERLAY_DISMISS_TAPPED;
@@ -41,7 +42,6 @@
import android.content.BroadcastReceiver;
import android.content.ClipData;
import android.content.ClipDescription;
-import android.content.ComponentName;
import android.content.ContentResolver;
import android.content.Context;
import android.content.Intent;
@@ -51,7 +51,6 @@
import android.hardware.display.DisplayManager;
import android.hardware.input.InputManager;
import android.net.Uri;
-import android.os.AsyncTask;
import android.os.Looper;
import android.provider.DeviceConfig;
import android.text.TextUtils;
@@ -62,10 +61,6 @@
import android.view.InputEventReceiver;
import android.view.InputMonitor;
import android.view.MotionEvent;
-import android.view.textclassifier.TextClassification;
-import android.view.textclassifier.TextClassificationManager;
-import android.view.textclassifier.TextClassifier;
-import android.view.textclassifier.TextLinks;
import androidx.annotation.NonNull;
@@ -74,12 +69,13 @@
import com.android.systemui.broadcast.BroadcastDispatcher;
import com.android.systemui.broadcast.BroadcastSender;
import com.android.systemui.clipboardoverlay.dagger.ClipboardOverlayModule.OverlayWindowContext;
+import com.android.systemui.dagger.qualifiers.Background;
import com.android.systemui.flags.FeatureFlags;
import com.android.systemui.screenshot.TimeoutHandler;
import java.io.IOException;
-import java.util.ArrayList;
import java.util.Optional;
+import java.util.concurrent.Executor;
import javax.inject.Inject;
@@ -102,9 +98,9 @@
private final DisplayManager mDisplayManager;
private final ClipboardOverlayWindow mWindow;
private final TimeoutHandler mTimeoutHandler;
- private final TextClassifier mTextClassifier;
private final ClipboardOverlayUtils mClipboardUtils;
private final FeatureFlags mFeatureFlags;
+ private final Executor mBgExecutor;
private final ClipboardOverlayView mView;
@@ -189,6 +185,7 @@
TimeoutHandler timeoutHandler,
FeatureFlags featureFlags,
ClipboardOverlayUtils clipboardUtils,
+ @Background Executor bgExecutor,
UiEventLogger uiEventLogger) {
mBroadcastDispatcher = broadcastDispatcher;
mDisplayManager = requireNonNull(context.getSystemService(DisplayManager.class));
@@ -204,14 +201,12 @@
hideImmediate();
});
- mTextClassifier = requireNonNull(context.getSystemService(TextClassificationManager.class))
- .getTextClassifier();
-
mTimeoutHandler = timeoutHandler;
mTimeoutHandler.setDefaultTimeoutMillis(CLIPBOARD_DEFAULT_TIMEOUT_MILLIS);
mFeatureFlags = featureFlags;
mClipboardUtils = clipboardUtils;
+ mBgExecutor = bgExecutor;
mView.setCallbacks(mClipboardCallbacks);
@@ -281,7 +276,7 @@
if (isRemote || DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_SYSTEMUI,
CLIPBOARD_OVERLAY_SHOW_ACTIONS, false)) {
if (item.getTextLinks() != null) {
- AsyncTask.execute(() -> classifyText(clipData.getItemAt(0), clipSource));
+ classifyText(clipData.getItemAt(0), clipSource);
}
}
if (isSensitive) {
@@ -338,22 +333,18 @@
}
private void classifyText(ClipData.Item item, String source) {
- ArrayList<RemoteAction> actions = new ArrayList<>();
- for (TextLinks.TextLink link : item.getTextLinks().getLinks()) {
- TextClassification classification = mTextClassifier.classifyText(
- item.getText(), link.getStart(), link.getEnd(), null);
- actions.addAll(classification.getActions());
- }
- mView.post(() -> {
- Optional<RemoteAction> action = actions.stream().filter(remoteAction -> {
- ComponentName component = remoteAction.getActionIntent().getIntent().getComponent();
- return component != null && !TextUtils.equals(source, component.getPackageName());
- }).findFirst();
- mView.resetActionChips();
- action.ifPresent(remoteAction -> mView.setActionChip(remoteAction, () -> {
- mClipboardLogger.logSessionComplete(CLIPBOARD_OVERLAY_ACTION_TAPPED);
- animateOut();
- }));
+ mBgExecutor.execute(() -> {
+ Optional<RemoteAction> action = mClipboardUtils.getAction(item, source);
+ mView.post(() -> {
+ mView.resetActionChips();
+ action.ifPresent(remoteAction -> {
+ mView.setActionChip(remoteAction, () -> {
+ mClipboardLogger.logSessionComplete(CLIPBOARD_OVERLAY_ACTION_TAPPED);
+ animateOut();
+ });
+ mClipboardLogger.logUnguarded(CLIPBOARD_OVERLAY_ACTION_SHOWN);
+ });
+ });
});
}
@@ -539,6 +530,10 @@
mClipSource = clipSource;
}
+ void logUnguarded(@NonNull UiEventLogger.UiEventEnum event) {
+ mUiEventLogger.log(event, 0, mClipSource);
+ }
+
void logSessionComplete(@NonNull UiEventLogger.UiEventEnum event) {
if (!mGuarded) {
mGuarded = true;
diff --git a/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardOverlayEvent.java b/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardOverlayEvent.java
index a0b2ab9..9917507 100644
--- a/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardOverlayEvent.java
+++ b/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardOverlayEvent.java
@@ -28,6 +28,8 @@
CLIPBOARD_OVERLAY_EDIT_TAPPED(951),
@UiEvent(doc = "clipboard share tapped")
CLIPBOARD_OVERLAY_SHARE_TAPPED(1067),
+ @UiEvent(doc = "clipboard smart action shown")
+ CLIPBOARD_OVERLAY_ACTION_SHOWN(1260),
@UiEvent(doc = "clipboard action tapped")
CLIPBOARD_OVERLAY_ACTION_TAPPED(952),
@UiEvent(doc = "clipboard remote copy tapped")
diff --git a/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardOverlayUtils.java b/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardOverlayUtils.java
index c194e66..785e4a0 100644
--- a/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardOverlayUtils.java
+++ b/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardOverlayUtils.java
@@ -16,22 +16,34 @@
package com.android.systemui.clipboardoverlay;
+import android.app.RemoteAction;
import android.content.ClipData;
import android.content.ClipDescription;
import android.content.ComponentName;
import android.content.Context;
import android.os.Build;
import android.provider.DeviceConfig;
+import android.text.TextUtils;
+import android.view.textclassifier.TextClassification;
+import android.view.textclassifier.TextClassificationManager;
+import android.view.textclassifier.TextClassifier;
+import android.view.textclassifier.TextLinks;
import com.android.internal.config.sysui.SystemUiDeviceConfigFlags;
import com.android.systemui.R;
+import java.util.ArrayList;
+import java.util.Optional;
+
import javax.inject.Inject;
class ClipboardOverlayUtils {
+ private final TextClassifier mTextClassifier;
+
@Inject
- ClipboardOverlayUtils() {
+ ClipboardOverlayUtils(TextClassificationManager textClassificationManager) {
+ mTextClassifier = textClassificationManager.getTextClassifier();
}
boolean isRemoteCopy(Context context, ClipData clipData, String clipSource) {
@@ -52,4 +64,21 @@
}
return false;
}
+
+ public Optional<RemoteAction> getAction(ClipData.Item item, String source) {
+ return getActions(item).stream().filter(remoteAction -> {
+ ComponentName component = remoteAction.getActionIntent().getIntent().getComponent();
+ return component != null && !TextUtils.equals(source, component.getPackageName());
+ }).findFirst();
+ }
+
+ private ArrayList<RemoteAction> getActions(ClipData.Item item) {
+ ArrayList<RemoteAction> actions = new ArrayList<>();
+ for (TextLinks.TextLink link : item.getTextLinks().getLinks()) {
+ TextClassification classification = mTextClassifier.classifyText(
+ item.getText(), link.getStart(), link.getEnd(), null);
+ actions.addAll(classification.getActions());
+ }
+ return actions;
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/FrameworkServicesModule.java b/packages/SystemUI/src/com/android/systemui/dagger/FrameworkServicesModule.java
index 7864f19..f902252 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/FrameworkServicesModule.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/FrameworkServicesModule.java
@@ -92,6 +92,9 @@
import android.view.accessibility.AccessibilityManager;
import android.view.accessibility.CaptioningManager;
import android.view.inputmethod.InputMethodManager;
+import android.view.textclassifier.TextClassificationManager;
+
+import androidx.core.app.NotificationManagerCompat;
import com.android.internal.app.IBatteryStats;
import com.android.internal.appwidget.IAppWidgetService;
@@ -389,6 +392,12 @@
return context.getSystemService(NotificationManager.class);
}
+ @Provides
+ @Singleton
+ static NotificationManagerCompat provideNotificationManagerCompat(Context context) {
+ return NotificationManagerCompat.from(context);
+ }
+
/** */
@Provides
@Singleton
@@ -623,4 +632,10 @@
static BluetoothAdapter provideBluetoothAdapter(BluetoothManager bluetoothManager) {
return bluetoothManager.getAdapter();
}
+
+ @Provides
+ @Singleton
+ static TextClassificationManager provideTextClassificationManager(Context context) {
+ return context.getSystemService(TextClassificationManager.class);
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/SystemUICoreStartableModule.kt b/packages/SystemUI/src/com/android/systemui/dagger/SystemUICoreStartableModule.kt
index 0fbe0ac..a4252bb 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/SystemUICoreStartableModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/dagger/SystemUICoreStartableModule.kt
@@ -42,6 +42,7 @@
import com.android.systemui.shortcut.ShortcutKeyDispatcher
import com.android.systemui.statusbar.notification.InstantAppNotifier
import com.android.systemui.statusbar.phone.KeyguardLiftController
+import com.android.systemui.stylus.StylusUsiPowerStartable
import com.android.systemui.temporarydisplay.chipbar.ChipbarCoordinator
import com.android.systemui.theme.ThemeOverlayController
import com.android.systemui.toast.ToastUI
@@ -251,4 +252,10 @@
@IntoMap
@ClassKey(RearDisplayDialogController::class)
abstract fun bindRearDisplayDialogController(sysui: RearDisplayDialogController): CoreStartable
+
+ /** Inject into StylusUsiPowerStartable) */
+ @Binds
+ @IntoMap
+ @ClassKey(StylusUsiPowerStartable::class)
+ abstract fun bindStylusUsiPowerStartable(sysui: StylusUsiPowerStartable): CoreStartable
}
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayAnimationsController.kt b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayAnimationsController.kt
index 0087c84..9b8ef71 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayAnimationsController.kt
+++ b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayAnimationsController.kt
@@ -21,11 +21,12 @@
import android.animation.ValueAnimator
import android.view.View
import android.view.animation.Interpolator
-import androidx.annotation.FloatRange
import androidx.core.animation.doOnEnd
import com.android.systemui.animation.Interpolators
import com.android.systemui.dreams.complication.ComplicationHostViewController
import com.android.systemui.dreams.complication.ComplicationLayoutParams
+import com.android.systemui.dreams.complication.ComplicationLayoutParams.POSITION_BOTTOM
+import com.android.systemui.dreams.complication.ComplicationLayoutParams.POSITION_TOP
import com.android.systemui.dreams.complication.ComplicationLayoutParams.Position
import com.android.systemui.dreams.dagger.DreamOverlayModule
import com.android.systemui.statusbar.BlurUtils
@@ -41,16 +42,15 @@
private val mComplicationHostViewController: ComplicationHostViewController,
private val mStatusBarViewController: DreamOverlayStatusBarViewController,
private val mOverlayStateController: DreamOverlayStateController,
+ @Named(DreamOverlayModule.DREAM_BLUR_RADIUS) private val mDreamBlurRadius: Int,
@Named(DreamOverlayModule.DREAM_IN_BLUR_ANIMATION_DURATION)
private val mDreamInBlurAnimDurationMs: Long,
- @Named(DreamOverlayModule.DREAM_IN_BLUR_ANIMATION_DELAY)
- private val mDreamInBlurAnimDelayMs: Long,
@Named(DreamOverlayModule.DREAM_IN_COMPLICATIONS_ANIMATION_DURATION)
private val mDreamInComplicationsAnimDurationMs: Long,
- @Named(DreamOverlayModule.DREAM_IN_TOP_COMPLICATIONS_ANIMATION_DELAY)
- private val mDreamInTopComplicationsAnimDelayMs: Long,
- @Named(DreamOverlayModule.DREAM_IN_BOTTOM_COMPLICATIONS_ANIMATION_DELAY)
- private val mDreamInBottomComplicationsAnimDelayMs: Long,
+ @Named(DreamOverlayModule.DREAM_IN_TRANSLATION_Y_DISTANCE)
+ private val mDreamInTranslationYDistance: Int,
+ @Named(DreamOverlayModule.DREAM_IN_TRANSLATION_Y_DURATION)
+ private val mDreamInTranslationYDurationMs: Long,
@Named(DreamOverlayModule.DREAM_OUT_TRANSLATION_Y_DISTANCE)
private val mDreamOutTranslationYDistance: Int,
@Named(DreamOverlayModule.DREAM_OUT_TRANSLATION_Y_DURATION)
@@ -74,7 +74,7 @@
*/
private var mCurrentAlphaAtPosition = mutableMapOf<Int, Float>()
- @FloatRange(from = 0.0, to = 1.0) private var mBlurProgress: Float = 0f
+ private var mCurrentBlurRadius: Float = 0f
/** Starts the dream content and dream overlay entry animations. */
@JvmOverloads
@@ -86,25 +86,23 @@
playTogether(
blurAnimator(
view = view,
- from = 1f,
- to = 0f,
+ fromBlurRadius = mDreamBlurRadius.toFloat(),
+ toBlurRadius = 0f,
durationMs = mDreamInBlurAnimDurationMs,
- delayMs = mDreamInBlurAnimDelayMs
+ interpolator = Interpolators.EMPHASIZED_DECELERATE
),
alphaAnimator(
from = 0f,
to = 1f,
durationMs = mDreamInComplicationsAnimDurationMs,
- delayMs = mDreamInTopComplicationsAnimDelayMs,
- position = ComplicationLayoutParams.POSITION_TOP
+ interpolator = Interpolators.LINEAR
),
- alphaAnimator(
- from = 0f,
- to = 1f,
- durationMs = mDreamInComplicationsAnimDurationMs,
- delayMs = mDreamInBottomComplicationsAnimDelayMs,
- position = ComplicationLayoutParams.POSITION_BOTTOM
- )
+ translationYAnimator(
+ from = mDreamInTranslationYDistance.toFloat(),
+ to = 0f,
+ durationMs = mDreamInTranslationYDurationMs,
+ interpolator = Interpolators.EMPHASIZED_DECELERATE
+ ),
)
doOnEnd {
mAnimator = null
@@ -130,47 +128,48 @@
view = view,
// Start the blurring wherever the entry animation ended, in
// case it was cancelled early.
- from = mBlurProgress,
- to = 1f,
- durationMs = mDreamOutBlurDurationMs
+ fromBlurRadius = mCurrentBlurRadius,
+ toBlurRadius = mDreamBlurRadius.toFloat(),
+ durationMs = mDreamOutBlurDurationMs,
+ interpolator = Interpolators.EMPHASIZED_ACCELERATE
),
translationYAnimator(
from = 0f,
to = mDreamOutTranslationYDistance.toFloat(),
durationMs = mDreamOutTranslationYDurationMs,
delayMs = mDreamOutTranslationYDelayBottomMs,
- position = ComplicationLayoutParams.POSITION_BOTTOM,
- animInterpolator = Interpolators.EMPHASIZED_ACCELERATE
+ positions = POSITION_BOTTOM,
+ interpolator = Interpolators.EMPHASIZED_ACCELERATE
),
translationYAnimator(
from = 0f,
to = mDreamOutTranslationYDistance.toFloat(),
durationMs = mDreamOutTranslationYDurationMs,
delayMs = mDreamOutTranslationYDelayTopMs,
- position = ComplicationLayoutParams.POSITION_TOP,
- animInterpolator = Interpolators.EMPHASIZED_ACCELERATE
+ positions = POSITION_TOP,
+ interpolator = Interpolators.EMPHASIZED_ACCELERATE
),
alphaAnimator(
from =
mCurrentAlphaAtPosition.getOrDefault(
- key = ComplicationLayoutParams.POSITION_BOTTOM,
+ key = POSITION_BOTTOM,
defaultValue = 1f
),
to = 0f,
durationMs = mDreamOutAlphaDurationMs,
delayMs = mDreamOutAlphaDelayBottomMs,
- position = ComplicationLayoutParams.POSITION_BOTTOM
+ positions = POSITION_BOTTOM
),
alphaAnimator(
from =
mCurrentAlphaAtPosition.getOrDefault(
- key = ComplicationLayoutParams.POSITION_TOP,
+ key = POSITION_TOP,
defaultValue = 1f
),
to = 0f,
durationMs = mDreamOutAlphaDurationMs,
delayMs = mDreamOutAlphaDelayTopMs,
- position = ComplicationLayoutParams.POSITION_TOP
+ positions = POSITION_TOP
)
)
doOnEnd {
@@ -194,20 +193,21 @@
private fun blurAnimator(
view: View,
- from: Float,
- to: Float,
+ fromBlurRadius: Float,
+ toBlurRadius: Float,
durationMs: Long,
- delayMs: Long = 0
+ delayMs: Long = 0,
+ interpolator: Interpolator = Interpolators.LINEAR
): Animator {
- return ValueAnimator.ofFloat(from, to).apply {
+ return ValueAnimator.ofFloat(fromBlurRadius, toBlurRadius).apply {
duration = durationMs
startDelay = delayMs
- interpolator = Interpolators.LINEAR
+ this.interpolator = interpolator
addUpdateListener { animator: ValueAnimator ->
- mBlurProgress = animator.animatedValue as Float
+ mCurrentBlurRadius = animator.animatedValue as Float
mBlurUtils.applyBlur(
viewRootImpl = view.viewRootImpl,
- radius = mBlurUtils.blurRadiusOfRatio(mBlurProgress).toInt(),
+ radius = mCurrentBlurRadius.toInt(),
opaque = false
)
}
@@ -218,18 +218,24 @@
from: Float,
to: Float,
durationMs: Long,
- delayMs: Long,
- @Position position: Int
+ delayMs: Long = 0,
+ @Position positions: Int = POSITION_TOP or POSITION_BOTTOM,
+ interpolator: Interpolator = Interpolators.LINEAR
): Animator {
return ValueAnimator.ofFloat(from, to).apply {
duration = durationMs
startDelay = delayMs
- interpolator = Interpolators.LINEAR
+ this.interpolator = interpolator
addUpdateListener { va: ValueAnimator ->
- setElementsAlphaAtPosition(
- alpha = va.animatedValue as Float,
- position = position,
- fadingOut = to < from
+ ComplicationLayoutParams.iteratePositions(
+ { position: Int ->
+ setElementsAlphaAtPosition(
+ alpha = va.animatedValue as Float,
+ position = position,
+ fadingOut = to < from
+ )
+ },
+ positions
)
}
}
@@ -239,16 +245,21 @@
from: Float,
to: Float,
durationMs: Long,
- delayMs: Long,
- @Position position: Int,
- animInterpolator: Interpolator
+ delayMs: Long = 0,
+ @Position positions: Int = POSITION_TOP or POSITION_BOTTOM,
+ interpolator: Interpolator = Interpolators.LINEAR
): Animator {
return ValueAnimator.ofFloat(from, to).apply {
duration = durationMs
startDelay = delayMs
- interpolator = animInterpolator
+ this.interpolator = interpolator
addUpdateListener { va: ValueAnimator ->
- setElementsTranslationYAtPosition(va.animatedValue as Float, position)
+ ComplicationLayoutParams.iteratePositions(
+ { position: Int ->
+ setElementsTranslationYAtPosition(va.animatedValue as Float, position)
+ },
+ positions
+ )
}
}
}
@@ -263,7 +274,7 @@
CrossFadeHelper.fadeIn(view, alpha, /* remap= */ false)
}
}
- if (position == ComplicationLayoutParams.POSITION_TOP) {
+ if (position == POSITION_TOP) {
mStatusBarViewController.setFadeAmount(alpha, fadingOut)
}
}
@@ -273,7 +284,7 @@
mComplicationHostViewController.getViewsAtPosition(position).forEach { v ->
v.translationY = translationY
}
- if (position == ComplicationLayoutParams.POSITION_TOP) {
+ if (position == POSITION_TOP) {
mStatusBarViewController.setTranslationY(translationY)
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/complication/ComplicationLayoutParams.java b/packages/SystemUI/src/com/android/systemui/dreams/complication/ComplicationLayoutParams.java
index 1755cb92..99e19fc 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/complication/ComplicationLayoutParams.java
+++ b/packages/SystemUI/src/com/android/systemui/dreams/complication/ComplicationLayoutParams.java
@@ -251,9 +251,17 @@
* position specified for this {@link ComplicationLayoutParams}.
*/
public void iteratePositions(Consumer<Integer> consumer) {
+ iteratePositions(consumer, mPosition);
+ }
+
+ /**
+ * Iterates over the defined positions and invokes the specified {@link Consumer} for each
+ * position specified by the given {@code position}.
+ */
+ public static void iteratePositions(Consumer<Integer> consumer, @Position int position) {
for (int currentPosition = FIRST_POSITION; currentPosition <= LAST_POSITION;
currentPosition <<= 1) {
- if ((mPosition & currentPosition) == currentPosition) {
+ if ((position & currentPosition) == currentPosition) {
consumer.accept(currentPosition);
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/dagger/DreamOverlayModule.java b/packages/SystemUI/src/com/android/systemui/dreams/dagger/DreamOverlayModule.java
index ed0e1d9..4f1ac1a 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/dagger/DreamOverlayModule.java
+++ b/packages/SystemUI/src/com/android/systemui/dreams/dagger/DreamOverlayModule.java
@@ -47,14 +47,14 @@
public static final String BURN_IN_PROTECTION_UPDATE_INTERVAL =
"burn_in_protection_update_interval";
public static final String MILLIS_UNTIL_FULL_JITTER = "millis_until_full_jitter";
+ public static final String DREAM_BLUR_RADIUS = "DREAM_BLUR_RADIUS";
public static final String DREAM_IN_BLUR_ANIMATION_DURATION = "dream_in_blur_anim_duration";
- public static final String DREAM_IN_BLUR_ANIMATION_DELAY = "dream_in_blur_anim_delay";
public static final String DREAM_IN_COMPLICATIONS_ANIMATION_DURATION =
"dream_in_complications_anim_duration";
- public static final String DREAM_IN_TOP_COMPLICATIONS_ANIMATION_DELAY =
- "dream_in_top_complications_anim_delay";
- public static final String DREAM_IN_BOTTOM_COMPLICATIONS_ANIMATION_DELAY =
- "dream_in_bottom_complications_anim_delay";
+ public static final String DREAM_IN_TRANSLATION_Y_DISTANCE =
+ "dream_in_complications_translation_y";
+ public static final String DREAM_IN_TRANSLATION_Y_DURATION =
+ "dream_in_complications_translation_y_duration";
public static final String DREAM_OUT_TRANSLATION_Y_DISTANCE =
"dream_out_complications_translation_y";
public static final String DREAM_OUT_TRANSLATION_Y_DURATION =
@@ -139,6 +139,15 @@
}
/**
+ * The blur radius applied to the dream overlay at dream entry and exit.
+ */
+ @Provides
+ @Named(DREAM_BLUR_RADIUS)
+ static int providesDreamBlurRadius(@Main Resources resources) {
+ return resources.getDimensionPixelSize(R.dimen.dream_overlay_anim_blur_radius);
+ }
+
+ /**
* Duration in milliseconds of the dream in un-blur animation.
*/
@Provides
@@ -148,15 +157,6 @@
}
/**
- * Delay in milliseconds of the dream in un-blur animation.
- */
- @Provides
- @Named(DREAM_IN_BLUR_ANIMATION_DELAY)
- static long providesDreamInBlurAnimationDelay(@Main Resources resources) {
- return (long) resources.getInteger(R.integer.config_dreamOverlayInBlurDelayMs);
- }
-
- /**
* Duration in milliseconds of the dream in complications fade-in animation.
*/
@Provides
@@ -166,22 +166,23 @@
}
/**
- * Delay in milliseconds of the dream in top complications fade-in animation.
+ * Provides the number of pixels to translate complications when entering a dream.
*/
@Provides
- @Named(DREAM_IN_TOP_COMPLICATIONS_ANIMATION_DELAY)
- static long providesDreamInTopComplicationsAnimationDelay(@Main Resources resources) {
- return (long) resources.getInteger(R.integer.config_dreamOverlayInTopComplicationsDelayMs);
+ @Named(DREAM_IN_TRANSLATION_Y_DISTANCE)
+ @DreamOverlayComponent.DreamOverlayScope
+ static int providesDreamInComplicationsTranslationY(@Main Resources resources) {
+ return resources.getDimensionPixelSize(R.dimen.dream_overlay_entry_y_offset);
}
/**
- * Delay in milliseconds of the dream in bottom complications fade-in animation.
+ * Provides the duration in ms of the y-translation when dream enters.
*/
@Provides
- @Named(DREAM_IN_BOTTOM_COMPLICATIONS_ANIMATION_DELAY)
- static long providesDreamInBottomComplicationsAnimationDelay(@Main Resources resources) {
- return (long) resources.getInteger(
- R.integer.config_dreamOverlayInBottomComplicationsDelayMs);
+ @Named(DREAM_IN_TRANSLATION_Y_DURATION)
+ @DreamOverlayComponent.DreamOverlayScope
+ static long providesDreamInComplicationsTranslationYDuration(@Main Resources resources) {
+ return (long) resources.getInteger(R.integer.config_dreamOverlayInTranslationYDurationMs);
}
/**
diff --git a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
index efb513d..ff0c821 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
+++ b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
@@ -165,7 +165,9 @@
/** Shows chipbar UI whenever the device is unlocked by ActiveUnlock (watch). */
// TODO(b/256513609): Tracking Bug
- @JvmField val ACTIVE_UNLOCK_CHIPBAR = releasedFlag(217, "active_unlock_chipbar")
+ @JvmField
+ val ACTIVE_UNLOCK_CHIPBAR =
+ resourceBooleanFlag(217, R.bool.flag_active_unlock_chipbar, "active_unlock_chipbar")
/**
* Migrates control of the LightRevealScrim's reveal effect and amount from legacy code to the
@@ -449,6 +451,11 @@
// TODO(b/261538825): Tracking Bug
@JvmField
val OUTPUT_SWITCHER_ADVANCED_LAYOUT = unreleasedFlag(2500, "output_switcher_advanced_layout")
+ @JvmField
+ val OUTPUT_SWITCHER_ROUTES_PROCESSING =
+ unreleasedFlag(2501, "output_switcher_routes_processing")
+ @JvmField
+ val OUTPUT_SWITCHER_DEVICE_STATUS = unreleasedFlag(2502, "output_switcher_device_status")
// TODO(b259590361): Tracking bug
val EXPERIMENTAL_FLAG = unreleasedFlag(2, "exp_flag_release")
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardLifecyclesDispatcher.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardLifecyclesDispatcher.java
index 822b1cf..757afb6 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardLifecyclesDispatcher.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardLifecyclesDispatcher.java
@@ -18,11 +18,8 @@
import android.os.Handler;
import android.os.Message;
-import android.os.RemoteException;
import android.os.Trace;
-import android.util.Log;
-import com.android.internal.policy.IKeyguardDrawnCallback;
import com.android.systemui.dagger.SysUISingleton;
import javax.inject.Inject;
@@ -80,33 +77,10 @@
private Handler mHandler = new Handler() {
@Override
public void handleMessage(Message msg) {
- final Object obj = msg.obj;
switch (msg.what) {
case SCREEN_TURNING_ON:
Trace.beginSection("KeyguardLifecyclesDispatcher#SCREEN_TURNING_ON");
- final String onDrawWaitingTraceTag =
- "Waiting for KeyguardDrawnCallback#onDrawn";
- int traceCookie = System.identityHashCode(msg);
- Trace.beginAsyncSection(onDrawWaitingTraceTag, traceCookie);
- // Ensure the drawn callback is only ever called once
- mScreenLifecycle.dispatchScreenTurningOn(new Runnable() {
- boolean mInvoked;
- @Override
- public void run() {
- if (obj == null) return;
- if (!mInvoked) {
- mInvoked = true;
- try {
- Trace.endAsyncSection(onDrawWaitingTraceTag, traceCookie);
- ((IKeyguardDrawnCallback) obj).onDrawn();
- } catch (RemoteException e) {
- Log.w(TAG, "Exception calling onDrawn():", e);
- }
- } else {
- Log.w(TAG, "KeyguardDrawnCallback#onDrawn() invoked > 1 times");
- }
- }
- });
+ mScreenLifecycle.dispatchScreenTurningOn();
Trace.endSection();
break;
case SCREEN_TURNED_ON:
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardQuickAffordanceProvider.kt b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardQuickAffordanceProvider.kt
index 4ae37c5..cbcede0 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardQuickAffordanceProvider.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardQuickAffordanceProvider.kt
@@ -21,14 +21,18 @@
import android.content.ContentValues
import android.content.Context
import android.content.UriMatcher
+import android.content.pm.PackageManager
import android.content.pm.ProviderInfo
import android.database.Cursor
import android.database.MatrixCursor
import android.net.Uri
+import android.os.Binder
+import android.os.Bundle
import android.util.Log
import com.android.systemui.SystemUIAppComponentFactoryBase
import com.android.systemui.SystemUIAppComponentFactoryBase.ContextAvailableCallback
import com.android.systemui.keyguard.domain.interactor.KeyguardQuickAffordanceInteractor
+import com.android.systemui.keyguard.ui.preview.KeyguardRemotePreviewManager
import com.android.systemui.shared.quickaffordance.data.content.KeyguardQuickAffordanceProviderContract as Contract
import javax.inject.Inject
import kotlinx.coroutines.runBlocking
@@ -37,6 +41,7 @@
ContentProvider(), SystemUIAppComponentFactoryBase.ContextInitializer {
@Inject lateinit var interactor: KeyguardQuickAffordanceInteractor
+ @Inject lateinit var previewManager: KeyguardRemotePreviewManager
private lateinit var contextAvailableCallback: ContextAvailableCallback
@@ -149,6 +154,21 @@
return deleteSelection(uri, selectionArgs)
}
+ override fun call(method: String, arg: String?, extras: Bundle?): Bundle? {
+ return if (
+ requireContext()
+ .checkPermission(
+ android.Manifest.permission.BIND_WALLPAPER,
+ Binder.getCallingPid(),
+ Binder.getCallingUid(),
+ ) == PackageManager.PERMISSION_GRANTED
+ ) {
+ previewManager.preview(extras)
+ } else {
+ null
+ }
+ }
+
private fun insertSelection(values: ContentValues?): Uri? {
if (values == null) {
throw IllegalArgumentException("Cannot insert selection, no values passed in!")
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java
index c332a0d..f4a1227 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java
@@ -78,6 +78,7 @@
import com.android.internal.policy.IKeyguardExitCallback;
import com.android.internal.policy.IKeyguardService;
import com.android.internal.policy.IKeyguardStateCallback;
+import com.android.keyguard.mediator.ScreenOnCoordinator;
import com.android.systemui.SystemUIApplication;
import com.android.wm.shell.transition.ShellTransitions;
import com.android.wm.shell.transition.Transitions;
@@ -120,6 +121,7 @@
private final KeyguardViewMediator mKeyguardViewMediator;
private final KeyguardLifecyclesDispatcher mKeyguardLifecyclesDispatcher;
+ private final ScreenOnCoordinator mScreenOnCoordinator;
private final ShellTransitions mShellTransitions;
private static int newModeToLegacyMode(int newMode) {
@@ -283,10 +285,12 @@
@Inject
public KeyguardService(KeyguardViewMediator keyguardViewMediator,
KeyguardLifecyclesDispatcher keyguardLifecyclesDispatcher,
+ ScreenOnCoordinator screenOnCoordinator,
ShellTransitions shellTransitions) {
super();
mKeyguardViewMediator = keyguardViewMediator;
mKeyguardLifecyclesDispatcher = keyguardLifecyclesDispatcher;
+ mScreenOnCoordinator = screenOnCoordinator;
mShellTransitions = shellTransitions;
}
@@ -583,6 +587,31 @@
checkPermission();
mKeyguardLifecyclesDispatcher.dispatch(KeyguardLifecyclesDispatcher.SCREEN_TURNING_ON,
callback);
+
+ final String onDrawWaitingTraceTag = "Waiting for KeyguardDrawnCallback#onDrawn";
+ final int traceCookie = System.identityHashCode(callback);
+ Trace.beginAsyncSection(onDrawWaitingTraceTag, traceCookie);
+
+ // Ensure the drawn callback is only ever called once
+ mScreenOnCoordinator.onScreenTurningOn(new Runnable() {
+ boolean mInvoked;
+ @Override
+ public void run() {
+ if (callback == null) return;
+ if (!mInvoked) {
+ mInvoked = true;
+ try {
+ Trace.endAsyncSection(onDrawWaitingTraceTag, traceCookie);
+ callback.onDrawn();
+ } catch (RemoteException e) {
+ Log.w(TAG, "Exception calling onDrawn():", e);
+ }
+ } else {
+ Log.w(TAG, "KeyguardDrawnCallback#onDrawn() invoked > 1 times");
+ }
+ }
+ });
+
Trace.endSection();
}
@@ -591,6 +620,7 @@
Trace.beginSection("KeyguardService.mBinder#onScreenTurnedOn");
checkPermission();
mKeyguardLifecyclesDispatcher.dispatch(KeyguardLifecyclesDispatcher.SCREEN_TURNED_ON);
+ mScreenOnCoordinator.onScreenTurnedOn();
Trace.endSection();
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
index d4b6386..d231870 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
@@ -25,6 +25,7 @@
import static com.android.internal.jank.InteractionJankMonitor.CUJ_LOCKSCREEN_OCCLUSION;
import static com.android.internal.jank.InteractionJankMonitor.CUJ_LOCKSCREEN_TRANSITION_FROM_AOD;
import static com.android.internal.jank.InteractionJankMonitor.CUJ_LOCKSCREEN_UNLOCK_ANIMATION;
+import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.SOME_AUTH_REQUIRED_AFTER_TRUSTAGENT_EXPIRED;
import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.SOME_AUTH_REQUIRED_AFTER_USER_REQUEST;
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_LOCKOUT;
@@ -142,12 +143,12 @@
import com.android.systemui.statusbar.policy.UserSwitcherController;
import com.android.systemui.util.DeviceConfigProxy;
+import dagger.Lazy;
+
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.concurrent.Executor;
-import dagger.Lazy;
-
/**
* Mediates requests related to the keyguard. This includes queries about the
* state of the keyguard, power management events that effect whether the keyguard
@@ -722,7 +723,7 @@
@Override
public void keyguardDone(boolean strongAuth, int targetUserId) {
- if (targetUserId != mUserTracker.getUserId()) {
+ if (targetUserId != KeyguardUpdateMonitor.getCurrentUser()) {
return;
}
if (DEBUG) Log.d(TAG, "keyguardDone");
@@ -745,7 +746,7 @@
public void keyguardDonePending(boolean strongAuth, int targetUserId) {
Trace.beginSection("KeyguardViewMediator.mViewMediatorCallback#keyguardDonePending");
if (DEBUG) Log.d(TAG, "keyguardDonePending");
- if (targetUserId != mUserTracker.getUserId()) {
+ if (targetUserId != KeyguardUpdateMonitor.getCurrentUser()) {
Trace.endSection();
return;
}
@@ -821,6 +822,9 @@
} else if (trustAgentsEnabled
&& (strongAuth & SOME_AUTH_REQUIRED_AFTER_USER_REQUEST) != 0) {
return KeyguardSecurityView.PROMPT_REASON_USER_REQUEST;
+ } else if (trustAgentsEnabled
+ && (strongAuth & SOME_AUTH_REQUIRED_AFTER_TRUSTAGENT_EXPIRED) != 0) {
+ return KeyguardSecurityView.PROMPT_REASON_TRUSTAGENT_EXPIRED;
} else if (any && ((strongAuth & STRONG_AUTH_REQUIRED_AFTER_LOCKOUT) != 0
|| mUpdateMonitor.isFingerprintLockedOut())) {
return KeyguardSecurityView.PROMPT_REASON_AFTER_LOCKOUT;
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/LifecycleScreenStatusProvider.kt b/packages/SystemUI/src/com/android/systemui/keyguard/LifecycleScreenStatusProvider.kt
index 0a55294..0b04fb4 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/LifecycleScreenStatusProvider.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/LifecycleScreenStatusProvider.kt
@@ -46,7 +46,7 @@
listeners.forEach(ScreenListener::onScreenTurningOff)
}
- override fun onScreenTurningOn(ignored: Runnable) {
+ override fun onScreenTurningOn() {
listeners.forEach(ScreenListener::onScreenTurningOn)
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ScreenLifecycle.java b/packages/SystemUI/src/com/android/systemui/keyguard/ScreenLifecycle.java
index b348121..8535eda 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ScreenLifecycle.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ScreenLifecycle.java
@@ -18,8 +18,6 @@
import android.os.Trace;
-import androidx.annotation.NonNull;
-
import com.android.systemui.Dumpable;
import com.android.systemui.dump.DumpManager;
@@ -50,14 +48,9 @@
return mScreenState;
}
- /**
- * Dispatch screen turning on events to the registered observers
- *
- * @param onDrawn Invoke to notify the caller that the event has been processed
- */
- public void dispatchScreenTurningOn(@NonNull Runnable onDrawn) {
+ public void dispatchScreenTurningOn() {
setScreenState(SCREEN_TURNING_ON);
- dispatch(Observer::onScreenTurningOn, onDrawn);
+ dispatch(Observer::onScreenTurningOn);
}
public void dispatchScreenTurnedOn() {
@@ -87,12 +80,7 @@
}
public interface Observer {
- /**
- * Receive the screen turning on event
- *
- * @param onDrawn Invoke to notify the caller that the event has been processed
- */
- default void onScreenTurningOn(@NonNull Runnable onDrawn) {}
+ default void onScreenTurningOn() {}
default void onScreenTurnedOn() {}
default void onScreenTurningOff() {}
default void onScreenTurnedOff() {}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractor.kt
index 748c6e8..57668c7 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractor.kt
@@ -34,7 +34,6 @@
import com.android.systemui.keyguard.shared.quickaffordance.KeyguardQuickAffordancePosition
import com.android.systemui.plugins.ActivityStarter
import com.android.systemui.settings.UserTracker
-import com.android.systemui.shared.keyguard.shared.model.KeyguardQuickAffordanceSlots
import com.android.systemui.shared.quickaffordance.data.content.KeyguardQuickAffordanceProviderContract
import com.android.systemui.statusbar.policy.KeyguardStateController
import dagger.Lazy
@@ -62,12 +61,20 @@
private val isUsingRepository: Boolean
get() = featureFlags.isEnabled(Flags.CUSTOMIZABLE_LOCK_SCREEN_QUICK_AFFORDANCES)
+ /**
+ * Whether the UI should use the long press gesture to activate quick affordances.
+ *
+ * If `false`, the UI goes back to using single taps.
+ */
+ val useLongPress: Boolean
+ get() = featureFlags.isEnabled(Flags.CUSTOMIZABLE_LOCK_SCREEN_QUICK_AFFORDANCES)
+
/** Returns an observable for the quick affordance at the given position. */
fun quickAffordance(
position: KeyguardQuickAffordancePosition
): Flow<KeyguardQuickAffordanceModel> {
return combine(
- quickAffordanceInternal(position),
+ quickAffordanceAlwaysVisible(position),
keyguardInteractor.isDozing,
keyguardInteractor.isKeyguardShowing,
) { affordance, isDozing, isKeyguardShowing ->
@@ -80,6 +87,19 @@
}
/**
+ * Returns an observable for the quick affordance at the given position but always visible,
+ * regardless of lock screen state.
+ *
+ * This is useful for experiences like the lock screen preview mode, where the affordances must
+ * always be visible.
+ */
+ fun quickAffordanceAlwaysVisible(
+ position: KeyguardQuickAffordancePosition,
+ ): Flow<KeyguardQuickAffordanceModel> {
+ return quickAffordanceInternal(position)
+ }
+
+ /**
* Notifies that a quick affordance has been "triggered" (clicked) by the user.
*
* @param configKey The configuration key corresponding to the [KeyguardQuickAffordanceModel] of
@@ -290,15 +310,6 @@
}
}
- private fun KeyguardQuickAffordancePosition.toSlotId(): String {
- return when (this) {
- KeyguardQuickAffordancePosition.BOTTOM_START ->
- KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_START
- KeyguardQuickAffordancePosition.BOTTOM_END ->
- KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_END
- }
- }
-
private fun String.encode(slotId: String): String {
return "$slotId$DELIMITER$this"
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/LockscreenBouncerTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/LockscreenBouncerTransitionInteractor.kt
index 3218f96..5cb7d70 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/LockscreenBouncerTransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/LockscreenBouncerTransitionInteractor.kt
@@ -22,7 +22,7 @@
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.keyguard.data.repository.KeyguardTransitionRepository
import com.android.systemui.keyguard.shared.model.KeyguardState
-import com.android.systemui.keyguard.shared.model.StatusBarState.SHADE_LOCKED
+import com.android.systemui.keyguard.shared.model.StatusBarState.KEYGUARD
import com.android.systemui.keyguard.shared.model.TransitionInfo
import com.android.systemui.keyguard.shared.model.TransitionState
import com.android.systemui.keyguard.shared.model.WakefulnessState
@@ -50,27 +50,22 @@
override fun start() {
listenForDraggingUpToBouncer()
- listenForBouncerHiding()
+ listenForBouncer()
}
- private fun listenForBouncerHiding() {
+ private fun listenForBouncer() {
scope.launch {
keyguardInteractor.isBouncerShowing
.sample(
combine(
keyguardInteractor.wakefulnessModel,
keyguardTransitionInteractor.startedKeyguardTransitionStep,
- ) { wakefulnessModel, transitionStep ->
- Pair(wakefulnessModel, transitionStep)
- }
- ) { bouncerShowing, wakefulnessAndTransition ->
- Triple(
- bouncerShowing,
- wakefulnessAndTransition.first,
- wakefulnessAndTransition.second
- )
- }
- .collect { (isBouncerShowing, wakefulnessState, lastStartedTransitionStep) ->
+ ::Pair
+ ),
+ ::toTriple
+ )
+ .collect { triple ->
+ val (isBouncerShowing, wakefulnessState, lastStartedTransitionStep) = triple
if (
!isBouncerShowing && lastStartedTransitionStep.to == KeyguardState.BOUNCER
) {
@@ -91,7 +86,19 @@
animator = getAnimator(),
)
)
+ } else if (
+ isBouncerShowing && lastStartedTransitionStep.to == KeyguardState.LOCKSCREEN
+ ) {
+ keyguardTransitionRepository.startTransition(
+ TransitionInfo(
+ ownerName = name,
+ from = KeyguardState.LOCKSCREEN,
+ to = KeyguardState.BOUNCER,
+ animator = getAnimator(),
+ )
+ )
}
+ Unit
}
}
}
@@ -104,24 +111,20 @@
combine(
keyguardTransitionInteractor.finishedKeyguardState,
keyguardInteractor.statusBarState,
- ) { finishedKeyguardState, statusBarState ->
- Pair(finishedKeyguardState, statusBarState)
- }
- ) { shadeModel, keyguardStateAndStatusBarState ->
- Triple(
- shadeModel,
- keyguardStateAndStatusBarState.first,
- keyguardStateAndStatusBarState.second
- )
- }
- .collect { (shadeModel, keyguardState, statusBarState) ->
+ ::Pair
+ ),
+ ::toTriple
+ )
+ .collect { triple ->
+ val (shadeModel, keyguardState, statusBarState) = triple
+
val id = transitionId
if (id != null) {
// An existing `id` means a transition is started, and calls to
// `updateTransition` will control it until FINISHED
keyguardTransitionRepository.updateTransition(
id,
- shadeModel.expansionAmount,
+ 1f - shadeModel.expansionAmount,
if (
shadeModel.expansionAmount == 0f || shadeModel.expansionAmount == 1f
) {
@@ -137,7 +140,7 @@
if (
keyguardState == KeyguardState.LOCKSCREEN &&
shadeModel.isUserDragging &&
- statusBarState != SHADE_LOCKED
+ statusBarState == KEYGUARD
) {
transitionId =
keyguardTransitionRepository.startTransition(
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/shared/quickaffordance/KeyguardQuickAffordancePosition.kt b/packages/SystemUI/src/com/android/systemui/keyguard/shared/quickaffordance/KeyguardQuickAffordancePosition.kt
index a18b036..2581b59 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/shared/quickaffordance/KeyguardQuickAffordancePosition.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/shared/quickaffordance/KeyguardQuickAffordancePosition.kt
@@ -16,8 +16,17 @@
package com.android.systemui.keyguard.shared.quickaffordance
+import com.android.systemui.shared.keyguard.shared.model.KeyguardQuickAffordanceSlots
+
/** Enumerates all possible positions for quick affordances that can appear on the lock-screen. */
enum class KeyguardQuickAffordancePosition {
BOTTOM_START,
- BOTTOM_END,
+ BOTTOM_END;
+
+ fun toSlotId(): String {
+ return when (this) {
+ BOTTOM_START -> KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_START
+ BOTTOM_END -> KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_END
+ }
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBottomAreaViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBottomAreaViewBinder.kt
index cbe512f..ae8edfe 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBottomAreaViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBottomAreaViewBinder.kt
@@ -16,14 +16,19 @@
package com.android.systemui.keyguard.ui.binder
+import android.annotation.SuppressLint
import android.graphics.drawable.Animatable2
import android.util.Size
import android.util.TypedValue
+import android.view.MotionEvent
import android.view.View
+import android.view.ViewConfiguration
import android.view.ViewGroup
import android.view.ViewPropertyAnimator
import android.widget.ImageView
import android.widget.TextView
+import androidx.core.animation.CycleInterpolator
+import androidx.core.animation.ObjectAnimator
import androidx.core.view.isVisible
import androidx.core.view.updateLayoutParams
import androidx.lifecycle.Lifecycle
@@ -38,8 +43,10 @@
import com.android.systemui.keyguard.ui.viewmodel.KeyguardQuickAffordanceViewModel
import com.android.systemui.lifecycle.repeatWhenAttached
import com.android.systemui.plugins.FalsingManager
+import kotlin.math.pow
+import kotlin.math.sqrt
+import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.MutableStateFlow
-import kotlinx.coroutines.flow.collect
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.flatMapLatest
import kotlinx.coroutines.flow.map
@@ -52,6 +59,7 @@
* view-binding, binding each view only once. It is okay and expected for the same instance of the
* view-model to be reused for multiple view/view-binder bindings.
*/
+@OptIn(ExperimentalCoroutinesApi::class)
object KeyguardBottomAreaViewBinder {
private const val EXIT_DOZE_BUTTON_REVEAL_ANIMATION_DURATION_MS = 250L
@@ -84,7 +92,8 @@
fun bind(
view: ViewGroup,
viewModel: KeyguardBottomAreaViewModel,
- falsingManager: FalsingManager,
+ falsingManager: FalsingManager?,
+ messageDisplayer: (Int) -> Unit,
): Binding {
val indicationArea: View = view.requireViewById(R.id.keyguard_indication_area)
val ambientIndicationArea: View? = view.findViewById(R.id.ambient_indication_container)
@@ -108,6 +117,7 @@
view = startButton,
viewModel = buttonModel,
falsingManager = falsingManager,
+ messageDisplayer = messageDisplayer,
)
}
}
@@ -118,6 +128,7 @@
view = endButton,
viewModel = buttonModel,
falsingManager = falsingManager,
+ messageDisplayer = messageDisplayer,
)
}
}
@@ -222,10 +233,12 @@
}
}
+ @SuppressLint("ClickableViewAccessibility")
private fun updateButton(
view: ImageView,
viewModel: KeyguardQuickAffordanceViewModel,
- falsingManager: FalsingManager,
+ falsingManager: FalsingManager?,
+ messageDisplayer: (Int) -> Unit,
) {
if (!viewModel.isVisible) {
view.isVisible = false
@@ -281,21 +294,126 @@
},
)
)
+
view.backgroundTintList =
- Utils.getColorAttr(
- view.context,
- if (viewModel.isActivated) {
- com.android.internal.R.attr.colorAccentPrimary
- } else {
- com.android.internal.R.attr.colorSurface
- }
- )
+ if (!viewModel.isSelected) {
+ Utils.getColorAttr(
+ view.context,
+ if (viewModel.isActivated) {
+ com.android.internal.R.attr.colorAccentPrimary
+ } else {
+ com.android.internal.R.attr.colorSurface
+ }
+ )
+ } else {
+ null
+ }
view.isClickable = viewModel.isClickable
if (viewModel.isClickable) {
- view.setOnClickListener(OnClickListener(viewModel, falsingManager))
+ if (viewModel.useLongPress) {
+ view.setOnTouchListener(OnTouchListener(view, viewModel, messageDisplayer))
+ } else {
+ view.setOnClickListener(OnClickListener(viewModel, checkNotNull(falsingManager)))
+ }
} else {
view.setOnClickListener(null)
+ view.setOnTouchListener(null)
+ }
+
+ view.isSelected = viewModel.isSelected
+ }
+
+ private class OnTouchListener(
+ private val view: View,
+ private val viewModel: KeyguardQuickAffordanceViewModel,
+ private val messageDisplayer: (Int) -> Unit,
+ ) : View.OnTouchListener {
+
+ private val longPressDurationMs = ViewConfiguration.getLongPressTimeout().toLong()
+ private var longPressAnimator: ViewPropertyAnimator? = null
+ private var downTimestamp = 0L
+
+ @SuppressLint("ClickableViewAccessibility")
+ override fun onTouch(v: View?, event: MotionEvent?): Boolean {
+ return when (event?.actionMasked) {
+ MotionEvent.ACTION_DOWN ->
+ if (viewModel.configKey != null) {
+ downTimestamp = System.currentTimeMillis()
+ longPressAnimator =
+ view
+ .animate()
+ .scaleX(PRESSED_SCALE)
+ .scaleY(PRESSED_SCALE)
+ .setDuration(longPressDurationMs)
+ .withEndAction {
+ view.setOnClickListener {
+ viewModel.onClicked(
+ KeyguardQuickAffordanceViewModel.OnClickedParameters(
+ configKey = viewModel.configKey,
+ expandable = Expandable.fromView(view),
+ )
+ )
+ }
+ view.performClick()
+ view.setOnClickListener(null)
+ }
+ true
+ } else {
+ false
+ }
+ MotionEvent.ACTION_MOVE -> {
+ if (event.historySize > 0) {
+ val distance =
+ sqrt(
+ (event.y - event.getHistoricalY(0)).pow(2) +
+ (event.x - event.getHistoricalX(0)).pow(2)
+ )
+ if (distance > ViewConfiguration.getTouchSlop()) {
+ cancel()
+ }
+ }
+ true
+ }
+ MotionEvent.ACTION_UP -> {
+ if (System.currentTimeMillis() - downTimestamp < longPressDurationMs) {
+ messageDisplayer.invoke(R.string.keyguard_affordance_press_too_short)
+ val shakeAnimator =
+ ObjectAnimator.ofFloat(
+ view,
+ "translationX",
+ 0f,
+ view.context.resources
+ .getDimensionPixelSize(
+ R.dimen.keyguard_affordance_shake_amplitude
+ )
+ .toFloat(),
+ 0f,
+ )
+ shakeAnimator.duration = 300
+ shakeAnimator.interpolator = CycleInterpolator(5f)
+ shakeAnimator.start()
+ }
+ cancel()
+ true
+ }
+ MotionEvent.ACTION_CANCEL -> {
+ cancel()
+ true
+ }
+ else -> false
+ }
+ }
+
+ private fun cancel() {
+ downTimestamp = 0L
+ longPressAnimator?.cancel()
+ longPressAnimator = null
+ view.animate().scaleX(1f).scaleY(1f)
+ }
+
+ companion object {
+ private const val PRESSED_SCALE = 1.5f
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/preview/KeyguardPreviewRenderer.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/preview/KeyguardPreviewRenderer.kt
new file mode 100644
index 0000000..a5ae8ba5
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/preview/KeyguardPreviewRenderer.kt
@@ -0,0 +1,207 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+package com.android.systemui.keyguard.ui.preview
+
+import android.content.BroadcastReceiver
+import android.content.Context
+import android.content.Intent
+import android.content.IntentFilter
+import android.hardware.display.DisplayManager
+import android.os.Bundle
+import android.os.IBinder
+import android.view.Gravity
+import android.view.LayoutInflater
+import android.view.SurfaceControlViewHost
+import android.view.View
+import android.view.ViewGroup
+import android.view.WindowManager
+import android.widget.FrameLayout
+import com.android.keyguard.ClockEventController
+import com.android.keyguard.KeyguardClockSwitch
+import com.android.systemui.R
+import com.android.systemui.broadcast.BroadcastDispatcher
+import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.dagger.qualifiers.Main
+import com.android.systemui.keyguard.ui.viewmodel.KeyguardBottomAreaViewModel
+import com.android.systemui.shared.clocks.ClockRegistry
+import com.android.systemui.shared.quickaffordance.shared.model.KeyguardQuickAffordancePreviewConstants
+import com.android.systemui.statusbar.phone.KeyguardBottomAreaView
+import dagger.assisted.Assisted
+import dagger.assisted.AssistedInject
+import kotlinx.coroutines.CoroutineDispatcher
+import kotlinx.coroutines.DisposableHandle
+import kotlinx.coroutines.runBlocking
+
+/** Renders the preview of the lock screen. */
+class KeyguardPreviewRenderer
+@AssistedInject
+constructor(
+ @Application private val context: Context,
+ @Main private val mainDispatcher: CoroutineDispatcher,
+ private val bottomAreaViewModel: KeyguardBottomAreaViewModel,
+ displayManager: DisplayManager,
+ private val windowManager: WindowManager,
+ private val clockController: ClockEventController,
+ private val clockRegistry: ClockRegistry,
+ private val broadcastDispatcher: BroadcastDispatcher,
+ @Assisted bundle: Bundle,
+) {
+
+ val hostToken: IBinder? = bundle.getBinder(KEY_HOST_TOKEN)
+ private val width: Int = bundle.getInt(KEY_VIEW_WIDTH)
+ private val height: Int = bundle.getInt(KEY_VIEW_HEIGHT)
+
+ private var host: SurfaceControlViewHost
+
+ val surfacePackage: SurfaceControlViewHost.SurfacePackage
+ get() = host.surfacePackage
+
+ private var clockView: View? = null
+
+ private val disposables = mutableSetOf<DisposableHandle>()
+ private var isDestroyed = false
+
+ init {
+ bottomAreaViewModel.enablePreviewMode(
+ initiallySelectedSlotId =
+ bundle.getString(
+ KeyguardQuickAffordancePreviewConstants.KEY_INITIALLY_SELECTED_SLOT_ID,
+ ),
+ )
+ runBlocking(mainDispatcher) {
+ host =
+ SurfaceControlViewHost(
+ context,
+ displayManager.getDisplay(bundle.getInt(KEY_DISPLAY_ID)),
+ hostToken,
+ )
+ disposables.add(DisposableHandle { host.release() })
+ }
+ }
+
+ fun render() {
+ runBlocking(mainDispatcher) {
+ val rootView = FrameLayout(context)
+
+ setUpBottomArea(rootView)
+ setUpClock(rootView)
+
+ rootView.measure(
+ View.MeasureSpec.makeMeasureSpec(
+ windowManager.currentWindowMetrics.bounds.width(),
+ View.MeasureSpec.EXACTLY
+ ),
+ View.MeasureSpec.makeMeasureSpec(
+ windowManager.currentWindowMetrics.bounds.height(),
+ View.MeasureSpec.EXACTLY
+ ),
+ )
+ rootView.layout(0, 0, rootView.measuredWidth, rootView.measuredHeight)
+
+ // This aspect scales the view to fit in the surface and centers it
+ val scale: Float =
+ (width / rootView.measuredWidth.toFloat()).coerceAtMost(
+ height / rootView.measuredHeight.toFloat()
+ )
+
+ rootView.scaleX = scale
+ rootView.scaleY = scale
+ rootView.pivotX = 0f
+ rootView.pivotY = 0f
+ rootView.translationX = (width - scale * rootView.width) / 2
+ rootView.translationY = (height - scale * rootView.height) / 2
+
+ host.setView(rootView, rootView.measuredWidth, rootView.measuredHeight)
+ }
+ }
+
+ fun onSlotSelected(slotId: String) {
+ bottomAreaViewModel.onPreviewSlotSelected(slotId = slotId)
+ }
+
+ fun destroy() {
+ isDestroyed = true
+ disposables.forEach { it.dispose() }
+ }
+
+ private fun setUpBottomArea(parentView: ViewGroup) {
+ val bottomAreaView =
+ LayoutInflater.from(context)
+ .inflate(
+ R.layout.keyguard_bottom_area,
+ parentView,
+ false,
+ ) as KeyguardBottomAreaView
+ bottomAreaView.init(
+ viewModel = bottomAreaViewModel,
+ )
+ parentView.addView(
+ bottomAreaView,
+ FrameLayout.LayoutParams(
+ FrameLayout.LayoutParams.MATCH_PARENT,
+ FrameLayout.LayoutParams.WRAP_CONTENT,
+ Gravity.BOTTOM,
+ ),
+ )
+ }
+
+ private fun setUpClock(parentView: ViewGroup) {
+ val clockChangeListener = ClockRegistry.ClockChangeListener { onClockChanged(parentView) }
+ clockRegistry.registerClockChangeListener(clockChangeListener)
+ disposables.add(
+ DisposableHandle { clockRegistry.unregisterClockChangeListener(clockChangeListener) }
+ )
+
+ clockController.registerListeners(parentView)
+ disposables.add(DisposableHandle { clockController.unregisterListeners() })
+
+ val receiver =
+ object : BroadcastReceiver() {
+ override fun onReceive(context: Context?, intent: Intent?) {
+ clockController.clock?.events?.onTimeTick()
+ }
+ }
+ broadcastDispatcher.registerReceiver(
+ receiver,
+ IntentFilter().apply {
+ addAction(Intent.ACTION_TIME_TICK)
+ addAction(Intent.ACTION_TIME_CHANGED)
+ },
+ )
+ disposables.add(DisposableHandle { broadcastDispatcher.unregisterReceiver(receiver) })
+
+ onClockChanged(parentView)
+ }
+
+ private fun onClockChanged(parentView: ViewGroup) {
+ clockController.clock = clockRegistry.createCurrentClock()
+ clockController.clock
+ ?.largeClock
+ ?.events
+ ?.onTargetRegionChanged(KeyguardClockSwitch.getLargeClockRegion(parentView))
+ clockView?.let { parentView.removeView(it) }
+ clockView = clockController.clock?.largeClock?.view?.apply { parentView.addView(this) }
+ }
+
+ companion object {
+ private const val KEY_HOST_TOKEN = "host_token"
+ private const val KEY_VIEW_WIDTH = "width"
+ private const val KEY_VIEW_HEIGHT = "height"
+ private const val KEY_DISPLAY_ID = "display_id"
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/preview/KeyguardPreviewRendererFactory.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/preview/KeyguardPreviewRendererFactory.kt
new file mode 100644
index 0000000..be1d3a1
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/preview/KeyguardPreviewRendererFactory.kt
@@ -0,0 +1,26 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+package com.android.systemui.keyguard.ui.preview
+
+import android.os.Bundle
+import dagger.assisted.AssistedFactory
+
+@AssistedFactory
+interface KeyguardPreviewRendererFactory {
+ fun create(bundle: Bundle): KeyguardPreviewRenderer
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/preview/KeyguardRemotePreviewManager.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/preview/KeyguardRemotePreviewManager.kt
new file mode 100644
index 0000000..50722d5
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/preview/KeyguardRemotePreviewManager.kt
@@ -0,0 +1,138 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+package com.android.systemui.keyguard.ui.preview
+
+import android.os.Bundle
+import android.os.Handler
+import android.os.IBinder
+import android.os.Message
+import android.os.Messenger
+import android.util.ArrayMap
+import android.util.Log
+import androidx.annotation.VisibleForTesting
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.dagger.qualifiers.Main
+import com.android.systemui.shared.quickaffordance.shared.model.KeyguardQuickAffordancePreviewConstants
+import javax.inject.Inject
+import kotlinx.coroutines.CoroutineDispatcher
+import kotlinx.coroutines.runBlocking
+
+@SysUISingleton
+class KeyguardRemotePreviewManager
+@Inject
+constructor(
+ private val previewRendererFactory: KeyguardPreviewRendererFactory,
+ @Main private val mainDispatcher: CoroutineDispatcher,
+ @Background private val backgroundHandler: Handler,
+) {
+ private val activePreviews: ArrayMap<IBinder, PreviewLifecycleObserver> =
+ ArrayMap<IBinder, PreviewLifecycleObserver>()
+
+ fun preview(request: Bundle?): Bundle? {
+ if (request == null) {
+ return null
+ }
+
+ var observer: PreviewLifecycleObserver? = null
+ return try {
+ val renderer = previewRendererFactory.create(request)
+
+ // Destroy any previous renderer associated with this token.
+ activePreviews[renderer.hostToken]?.let { destroyObserver(it) }
+ observer = PreviewLifecycleObserver(renderer, mainDispatcher, ::destroyObserver)
+ activePreviews[renderer.hostToken] = observer
+ renderer.render()
+ renderer.hostToken?.linkToDeath(observer, 0)
+ val result = Bundle()
+ result.putParcelable(
+ KEY_PREVIEW_SURFACE_PACKAGE,
+ renderer.surfacePackage,
+ )
+ val messenger =
+ Messenger(
+ Handler(
+ backgroundHandler.looper,
+ observer,
+ )
+ )
+ val msg = Message.obtain()
+ msg.replyTo = messenger
+ result.putParcelable(KEY_PREVIEW_CALLBACK, msg)
+ result
+ } catch (e: Exception) {
+ Log.e(TAG, "Unable to generate preview", e)
+ observer?.let { destroyObserver(it) }
+ null
+ }
+ }
+
+ private fun destroyObserver(observer: PreviewLifecycleObserver) {
+ observer.onDestroy()?.let { hostToken ->
+ if (activePreviews[hostToken] === observer) {
+ activePreviews.remove(hostToken)
+ }
+ }
+ }
+
+ private class PreviewLifecycleObserver(
+ private val renderer: KeyguardPreviewRenderer,
+ private val mainDispatcher: CoroutineDispatcher,
+ private val requestDestruction: (PreviewLifecycleObserver) -> Unit,
+ ) : Handler.Callback, IBinder.DeathRecipient {
+
+ private var isDestroyed = false
+
+ override fun handleMessage(message: Message): Boolean {
+ when (message.what) {
+ KeyguardQuickAffordancePreviewConstants.MESSAGE_ID_SLOT_SELECTED -> {
+ message.data
+ .getString(
+ KeyguardQuickAffordancePreviewConstants.KEY_SLOT_ID,
+ )
+ ?.let { slotId -> renderer.onSlotSelected(slotId = slotId) }
+ }
+ else -> requestDestruction(this)
+ }
+
+ return true
+ }
+
+ override fun binderDied() {
+ requestDestruction(this)
+ }
+
+ fun onDestroy(): IBinder? {
+ if (isDestroyed) {
+ return null
+ }
+
+ isDestroyed = true
+ val hostToken = renderer.hostToken
+ hostToken?.unlinkToDeath(this, 0)
+ runBlocking(mainDispatcher) { renderer.destroy() }
+ return hostToken
+ }
+ }
+
+ companion object {
+ private const val TAG = "KeyguardRemotePreviewManager"
+ @VisibleForTesting const val KEY_PREVIEW_SURFACE_PACKAGE = "surface_package"
+ @VisibleForTesting const val KEY_PREVIEW_CALLBACK = "callback"
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBottomAreaViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBottomAreaViewModel.kt
index 227796f..5d85680 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBottomAreaViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBottomAreaViewModel.kt
@@ -24,13 +24,19 @@
import com.android.systemui.keyguard.domain.model.KeyguardQuickAffordanceModel
import com.android.systemui.keyguard.shared.quickaffordance.ActivationState
import com.android.systemui.keyguard.shared.quickaffordance.KeyguardQuickAffordancePosition
+import com.android.systemui.shared.keyguard.shared.model.KeyguardQuickAffordanceSlots
import javax.inject.Inject
+import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.distinctUntilChanged
+import kotlinx.coroutines.flow.flatMapLatest
+import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.flow.map
/** View-model for the keyguard bottom area view */
+@OptIn(ExperimentalCoroutinesApi::class)
class KeyguardBottomAreaViewModel
@Inject
constructor(
@@ -40,6 +46,20 @@
private val burnInHelperWrapper: BurnInHelperWrapper,
) {
/**
+ * Whether this view-model instance is powering the preview experience that renders exclusively
+ * in the wallpaper picker application. This should _always_ be `false` for the real lock screen
+ * experience.
+ */
+ private val isInPreviewMode = MutableStateFlow(false)
+
+ /**
+ * ID of the slot that's currently selected in the preview that renders exclusively in the
+ * wallpaper picker application. This is ignored for the actual, real lock screen experience.
+ */
+ private val selectedPreviewSlotId =
+ MutableStateFlow(KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_START)
+
+ /**
* Whether quick affordances are "opaque enough" to be considered visible to and interactive by
* the user. If they are not interactive, user input should not be allowed on them.
*
@@ -66,7 +86,14 @@
val isOverlayContainerVisible: Flow<Boolean> =
keyguardInteractor.isDozing.map { !it }.distinctUntilChanged()
/** An observable for the alpha level for the entire bottom area. */
- val alpha: Flow<Float> = bottomAreaInteractor.alpha.distinctUntilChanged()
+ val alpha: Flow<Float> =
+ isInPreviewMode.flatMapLatest { isInPreviewMode ->
+ if (isInPreviewMode) {
+ flowOf(1f)
+ } else {
+ bottomAreaInteractor.alpha.distinctUntilChanged()
+ }
+ }
/** An observable for whether the indication area should be padded. */
val isIndicationAreaPadded: Flow<Boolean> =
combine(startButton, endButton) { startButtonModel, endButtonModel ->
@@ -94,27 +121,61 @@
* Returns whether the keyguard bottom area should be constrained to the top of the lock icon
*/
fun shouldConstrainToTopOfLockIcon(): Boolean =
- bottomAreaInteractor.shouldConstrainToTopOfLockIcon()
+ bottomAreaInteractor.shouldConstrainToTopOfLockIcon()
+
+ /**
+ * Puts this view-model in "preview mode", which means it's being used for UI that is rendering
+ * the lock screen preview in wallpaper picker / settings and not the real experience on the
+ * lock screen.
+ *
+ * @param initiallySelectedSlotId The ID of the initial slot to render as the selected one.
+ */
+ fun enablePreviewMode(initiallySelectedSlotId: String?) {
+ isInPreviewMode.value = true
+ onPreviewSlotSelected(
+ initiallySelectedSlotId ?: KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_START
+ )
+ }
+
+ /**
+ * Notifies that a slot with the given ID has been selected in the preview experience that is
+ * rendering in the wallpaper picker. This is ignored for the real lock screen experience.
+ *
+ * @see enablePreviewMode
+ */
+ fun onPreviewSlotSelected(slotId: String) {
+ selectedPreviewSlotId.value = slotId
+ }
private fun button(
position: KeyguardQuickAffordancePosition
): Flow<KeyguardQuickAffordanceViewModel> {
- return combine(
- quickAffordanceInteractor.quickAffordance(position),
- bottomAreaInteractor.animateDozingTransitions.distinctUntilChanged(),
- areQuickAffordancesFullyOpaque,
- ) { model, animateReveal, isFullyOpaque ->
- model.toViewModel(
- animateReveal = animateReveal,
- isClickable = isFullyOpaque,
- )
- }
- .distinctUntilChanged()
+ return isInPreviewMode.flatMapLatest { isInPreviewMode ->
+ combine(
+ if (isInPreviewMode) {
+ quickAffordanceInteractor.quickAffordanceAlwaysVisible(position = position)
+ } else {
+ quickAffordanceInteractor.quickAffordance(position = position)
+ },
+ bottomAreaInteractor.animateDozingTransitions.distinctUntilChanged(),
+ areQuickAffordancesFullyOpaque,
+ selectedPreviewSlotId,
+ ) { model, animateReveal, isFullyOpaque, selectedPreviewSlotId ->
+ model.toViewModel(
+ animateReveal = !isInPreviewMode && animateReveal,
+ isClickable = isFullyOpaque && !isInPreviewMode,
+ isSelected =
+ (isInPreviewMode && selectedPreviewSlotId == position.toSlotId()),
+ )
+ }
+ .distinctUntilChanged()
+ }
}
private fun KeyguardQuickAffordanceModel.toViewModel(
animateReveal: Boolean,
isClickable: Boolean,
+ isSelected: Boolean,
): KeyguardQuickAffordanceViewModel {
return when (this) {
is KeyguardQuickAffordanceModel.Visible ->
@@ -131,6 +192,8 @@
},
isClickable = isClickable,
isActivated = activationState is ActivationState.Active,
+ isSelected = isSelected,
+ useLongPress = quickAffordanceInteractor.useLongPress,
)
is KeyguardQuickAffordanceModel.Hidden -> KeyguardQuickAffordanceViewModel()
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardQuickAffordanceViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardQuickAffordanceViewModel.kt
index 44f48f9..cf3a6da 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardQuickAffordanceViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardQuickAffordanceViewModel.kt
@@ -29,6 +29,8 @@
val onClicked: (OnClickedParameters) -> Unit = {},
val isClickable: Boolean = false,
val isActivated: Boolean = false,
+ val isSelected: Boolean = false,
+ val useLongPress: Boolean = false,
) {
data class OnClickedParameters(
val configKey: String,
diff --git a/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java b/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java
index ec2e340..48a68be 100644
--- a/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java
+++ b/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java
@@ -123,7 +123,7 @@
@SysUISingleton
@QSLog
public static LogBuffer provideQuickSettingsLogBuffer(LogBufferFactory factory) {
- return factory.create("QSLog", 500 /* maxSize */, false /* systrace */);
+ return factory.create("QSLog", 700 /* maxSize */, false /* systrace */);
}
/** Provides a logging buffer for {@link com.android.systemui.broadcast.BroadcastDispatcher} */
diff --git a/packages/SystemUI/src/com/android/systemui/media/dagger/MediaModule.java b/packages/SystemUI/src/com/android/systemui/media/dagger/MediaModule.java
index 3e5d337..bb833df 100644
--- a/packages/SystemUI/src/com/android/systemui/media/dagger/MediaModule.java
+++ b/packages/SystemUI/src/com/android/systemui/media/dagger/MediaModule.java
@@ -30,9 +30,11 @@
import com.android.systemui.media.taptotransfer.MediaTttCommandLineHelper;
import com.android.systemui.media.taptotransfer.MediaTttFlags;
import com.android.systemui.media.taptotransfer.common.MediaTttLogger;
+import com.android.systemui.media.taptotransfer.receiver.ChipReceiverInfo;
import com.android.systemui.media.taptotransfer.receiver.MediaTttReceiverLogger;
import com.android.systemui.media.taptotransfer.sender.MediaTttSenderLogger;
import com.android.systemui.plugins.log.LogBuffer;
+import com.android.systemui.temporarydisplay.chipbar.ChipbarInfo;
import java.util.Optional;
@@ -95,19 +97,19 @@
@Provides
@SysUISingleton
@MediaTttSenderLogger
- static MediaTttLogger providesMediaTttSenderLogger(
+ static MediaTttLogger<ChipbarInfo> providesMediaTttSenderLogger(
@MediaTttSenderLogBuffer LogBuffer buffer
) {
- return new MediaTttLogger("Sender", buffer);
+ return new MediaTttLogger<>("Sender", buffer);
}
@Provides
@SysUISingleton
@MediaTttReceiverLogger
- static MediaTttLogger providesMediaTttReceiverLogger(
+ static MediaTttLogger<ChipReceiverInfo> providesMediaTttReceiverLogger(
@MediaTttReceiverLogBuffer LogBuffer buffer
) {
- return new MediaTttLogger("Receiver", buffer);
+ return new MediaTttLogger<>("Receiver", buffer);
}
/** */
diff --git a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/common/MediaTttLogger.kt b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/common/MediaTttLogger.kt
index b55bedd..8aef938 100644
--- a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/common/MediaTttLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/common/MediaTttLogger.kt
@@ -18,17 +18,21 @@
import com.android.systemui.plugins.log.LogBuffer
import com.android.systemui.plugins.log.LogLevel
+import com.android.systemui.temporarydisplay.TemporaryViewInfo
import com.android.systemui.temporarydisplay.TemporaryViewLogger
/**
* A logger for media tap-to-transfer events.
*
* @param deviceTypeTag the type of device triggering the logs -- "Sender" or "Receiver".
+ *
+ * TODO(b/245610654): We should de-couple the sender and receiver loggers, since they're vastly
+ * different experiences.
*/
-class MediaTttLogger(
+class MediaTttLogger<T : TemporaryViewInfo>(
deviceTypeTag: String,
buffer: LogBuffer
-) : TemporaryViewLogger(buffer, BASE_TAG + deviceTypeTag) {
+) : TemporaryViewLogger<T>(buffer, BASE_TAG + deviceTypeTag) {
/** Logs a change in the chip state for the given [mediaRouteId]. */
fun logStateChange(stateName: String, mediaRouteId: String, packageName: String?) {
buffer.log(
diff --git a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/common/MediaTttUtils.kt b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/common/MediaTttUtils.kt
index 009595a..066c185 100644
--- a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/common/MediaTttUtils.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/common/MediaTttUtils.kt
@@ -25,6 +25,7 @@
import com.android.systemui.common.shared.model.ContentDescription
import com.android.systemui.common.shared.model.Icon
import com.android.systemui.common.shared.model.TintedIcon
+import com.android.systemui.temporarydisplay.TemporaryViewInfo
/** Utility methods for media tap-to-transfer. */
class MediaTttUtils {
@@ -47,7 +48,7 @@
fun getIconInfoFromPackageName(
context: Context,
appPackageName: String?,
- logger: MediaTttLogger
+ logger: MediaTttLogger<out TemporaryViewInfo>
): IconInfo {
if (appPackageName != null) {
val packageManager = context.packageManager
diff --git a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/receiver/ChipStateReceiver.kt b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/receiver/ChipStateReceiver.kt
index 40ea1e6..11348ad 100644
--- a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/receiver/ChipStateReceiver.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/receiver/ChipStateReceiver.kt
@@ -35,6 +35,14 @@
FAR_FROM_SENDER(
StatusBarManager.MEDIA_TRANSFER_RECEIVER_STATE_FAR_FROM_SENDER,
MediaTttReceiverUiEvents.MEDIA_TTT_RECEIVER_FAR_FROM_SENDER
+ ),
+ TRANSFER_TO_RECEIVER_SUCCEEDED(
+ StatusBarManager.MEDIA_TRANSFER_RECEIVER_STATE_TRANSFER_TO_RECEIVER_SUCCEEDED,
+ MediaTttReceiverUiEvents.MEDIA_TTT_RECEIVER_TRANSFER_TO_RECEIVER_SUCCEEDED,
+ ),
+ TRANSFER_TO_RECEIVER_FAILED(
+ StatusBarManager.MEDIA_TRANSFER_RECEIVER_STATE_TRANSFER_TO_RECEIVER_FAILED,
+ MediaTttReceiverUiEvents.MEDIA_TTT_RECEIVER_TRANSFER_TO_RECEIVER_FAILED,
);
companion object {
diff --git a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/receiver/MediaTttChipControllerReceiver.kt b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/receiver/MediaTttChipControllerReceiver.kt
index 1c3a53c..7b9d0b4 100644
--- a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/receiver/MediaTttChipControllerReceiver.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/receiver/MediaTttChipControllerReceiver.kt
@@ -45,8 +45,10 @@
import com.android.systemui.statusbar.policy.ConfigurationController
import com.android.systemui.temporarydisplay.TemporaryViewDisplayController
import com.android.systemui.temporarydisplay.TemporaryViewInfo
+import com.android.systemui.temporarydisplay.ViewPriority
import com.android.systemui.util.animation.AnimationUtil.Companion.frames
import com.android.systemui.util.concurrency.DelayableExecutor
+import com.android.systemui.util.time.SystemClock
import com.android.systemui.util.view.ViewUtil
import com.android.systemui.util.wakelock.WakeLock
import javax.inject.Inject
@@ -62,7 +64,7 @@
open class MediaTttChipControllerReceiver @Inject constructor(
private val commandQueue: CommandQueue,
context: Context,
- @MediaTttReceiverLogger logger: MediaTttLogger,
+ @MediaTttReceiverLogger logger: MediaTttLogger<ChipReceiverInfo>,
windowManager: WindowManager,
mainExecutor: DelayableExecutor,
accessibilityManager: AccessibilityManager,
@@ -73,7 +75,8 @@
private val uiEventLogger: MediaTttReceiverUiEventLogger,
private val viewUtil: ViewUtil,
wakeLockBuilder: WakeLock.Builder,
-) : TemporaryViewDisplayController<ChipReceiverInfo, MediaTttLogger>(
+ systemClock: SystemClock,
+) : TemporaryViewDisplayController<ChipReceiverInfo, MediaTttLogger<ChipReceiverInfo>>(
context,
logger,
windowManager,
@@ -83,6 +86,7 @@
powerManager,
R.layout.media_ttt_chip_receiver,
wakeLockBuilder,
+ systemClock,
) {
@SuppressLint("WrongConstant") // We're allowed to use LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS
override val windowLayoutParams = commonWindowLayoutParams.apply {
@@ -123,8 +127,8 @@
}
uiEventLogger.logReceiverStateChange(chipState)
- if (chipState == ChipStateReceiver.FAR_FROM_SENDER) {
- removeView(routeInfo.id, removalReason = ChipStateReceiver.FAR_FROM_SENDER.name)
+ if (chipState != ChipStateReceiver.CLOSE_TO_SENDER) {
+ removeView(routeInfo.id, removalReason = chipState.name)
return
}
if (appIcon == null) {
@@ -290,4 +294,5 @@
override val windowTitle: String = MediaTttUtils.WINDOW_TITLE_RECEIVER,
override val wakeReason: String = MediaTttUtils.WAKE_REASON_RECEIVER,
override val id: String,
+ override val priority: ViewPriority = ViewPriority.NORMAL,
) : TemporaryViewInfo()
diff --git a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/receiver/MediaTttReceiverUiEventLogger.kt b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/receiver/MediaTttReceiverUiEventLogger.kt
index 39a2763..6e515f2 100644
--- a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/receiver/MediaTttReceiverUiEventLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/receiver/MediaTttReceiverUiEventLogger.kt
@@ -34,7 +34,11 @@
@UiEvent(doc = "See android.app.StatusBarManager.MEDIA_TRANSFER_RECEIVER_* docs")
MEDIA_TTT_RECEIVER_CLOSE_TO_SENDER(982),
@UiEvent(doc = "See android.app.StatusBarManager.MEDIA_TRANSFER_RECEIVER_* docs")
- MEDIA_TTT_RECEIVER_FAR_FROM_SENDER(983);
+ MEDIA_TTT_RECEIVER_FAR_FROM_SENDER(983),
+ @UiEvent(doc = "See android.app.StatusBarManager.MEDIA_TRANSFER_RECEIVER_* docs")
+ MEDIA_TTT_RECEIVER_TRANSFER_TO_RECEIVER_SUCCEEDED(1263),
+ @UiEvent(doc = "See android.app.StatusBarManager.MEDIA_TRANSFER_RECEIVER_* docs")
+ MEDIA_TTT_RECEIVER_TRANSFER_TO_RECEIVER_FAILED(1264);
override fun getId() = metricId
}
diff --git a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/MediaTttSenderCoordinator.kt b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/MediaTttSenderCoordinator.kt
index ec1984d..9f44d98 100644
--- a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/MediaTttSenderCoordinator.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/MediaTttSenderCoordinator.kt
@@ -30,6 +30,7 @@
import com.android.systemui.media.taptotransfer.common.MediaTttLogger
import com.android.systemui.media.taptotransfer.common.MediaTttUtils
import com.android.systemui.statusbar.CommandQueue
+import com.android.systemui.temporarydisplay.ViewPriority
import com.android.systemui.temporarydisplay.chipbar.ChipbarCoordinator
import com.android.systemui.temporarydisplay.chipbar.ChipbarEndItem
import com.android.systemui.temporarydisplay.chipbar.ChipbarInfo
@@ -46,7 +47,7 @@
private val chipbarCoordinator: ChipbarCoordinator,
private val commandQueue: CommandQueue,
private val context: Context,
- @MediaTttSenderLogger private val logger: MediaTttLogger,
+ @MediaTttSenderLogger private val logger: MediaTttLogger<ChipbarInfo>,
private val mediaTttFlags: MediaTttFlags,
private val uiEventLogger: MediaTttSenderUiEventLogger,
) : CoreStartable {
@@ -146,7 +147,7 @@
routeInfo: MediaRoute2Info,
undoCallback: IUndoMediaTransferCallback?,
context: Context,
- logger: MediaTttLogger,
+ logger: MediaTttLogger<ChipbarInfo>,
): ChipbarInfo {
val packageName = routeInfo.clientPackageName
val otherDeviceName = routeInfo.name.toString()
@@ -180,6 +181,7 @@
wakeReason = MediaTttUtils.WAKE_REASON_SENDER,
timeoutMs = chipStateSender.timeout,
id = routeInfo.id,
+ priority = ViewPriority.NORMAL,
)
}
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarController.java b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarController.java
index e2f55f0..8914552 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarController.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarController.java
@@ -464,6 +464,8 @@
@Override
public void dump(@NonNull PrintWriter pw, @NonNull String[] args) {
+ pw.println("mIsTablet=" + mIsTablet);
+ pw.println("mNavMode=" + mNavMode);
for (int i = 0; i < mNavigationBars.size(); i++) {
if (i > 0) {
pw.println();
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java
index d03ac3b..13c5b48 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java
@@ -635,8 +635,9 @@
}
private void updateMLModelState() {
- boolean newState = mIsEnabled && DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_SYSTEMUI,
- SystemUiDeviceConfigFlags.USE_BACK_GESTURE_ML_MODEL, false);
+ boolean newState =
+ mIsGesturalModeEnabled && DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_SYSTEMUI,
+ SystemUiDeviceConfigFlags.USE_BACK_GESTURE_ML_MODEL, false);
if (newState == mUseMLModel) {
return;
@@ -766,7 +767,7 @@
// ML model
boolean withinMinRange = x < mMLEnableWidth + mLeftInset
|| x >= (mDisplaySize.x - mMLEnableWidth - mRightInset);
- if (!withinMinRange && mUseMLModel
+ if (!withinMinRange && mUseMLModel && !mMLModelIsLoading
&& (results = getBackGesturePredictionsCategory(x, y, app)) != -1) {
withinRange = (results == 1);
}
diff --git a/packages/SystemUI/src/com/android/systemui/notetask/OWNERS b/packages/SystemUI/src/com/android/systemui/notetask/OWNERS
new file mode 100644
index 0000000..7ccb316
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/notetask/OWNERS
@@ -0,0 +1,8 @@
+# Bug component: 1254381
+azappone@google.com
+achalke@google.com
+juliacr@google.com
+madym@google.com
+mgalhardo@google.com
+petrcermak@google.com
+vanjan@google.com
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/qs/PagedTileLayout.java b/packages/SystemUI/src/com/android/systemui/qs/PagedTileLayout.java
index 8ceee1a..7523d6e 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/PagedTileLayout.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/PagedTileLayout.java
@@ -11,7 +11,6 @@
import android.content.res.Configuration;
import android.os.Bundle;
import android.util.AttributeSet;
-import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
@@ -31,6 +30,7 @@
import com.android.systemui.plugins.qs.QSTile;
import com.android.systemui.qs.QSPanel.QSTileLayout;
import com.android.systemui.qs.QSPanelControllerBase.TileRecord;
+import com.android.systemui.qs.logging.QSLogger;
import java.util.ArrayList;
import java.util.List;
@@ -38,11 +38,9 @@
public class PagedTileLayout extends ViewPager implements QSTileLayout {
- private static final boolean DEBUG = false;
private static final String CURRENT_PAGE = "current_page";
private static final int NO_PAGE = -1;
- private static final String TAG = "PagedTileLayout";
private static final int REVEAL_SCROLL_DURATION_MILLIS = 750;
private static final float BOUNCE_ANIMATION_TENSION = 1.3f;
private static final long BOUNCE_ANIMATION_DURATION = 450L;
@@ -55,6 +53,7 @@
private final ArrayList<TileRecord> mTiles = new ArrayList<>();
private final ArrayList<TileLayout> mPages = new ArrayList<>();
+ private QSLogger mLogger;
@Nullable
private PageIndicator mPageIndicator;
private float mPageIndicatorPosition;
@@ -146,9 +145,15 @@
}
if (mLayoutOrientation != newConfig.orientation) {
mLayoutOrientation = newConfig.orientation;
- mDistributeTiles = true;
+ forceTilesRedistribution("orientation changed to " + mLayoutOrientation);
setCurrentItem(0, false);
mPageToRestore = 0;
+ } else {
+ // logging in case we missed redistribution because orientation was not changed
+ // while configuration changed, can be removed after b/255208946 is fixed
+ mLogger.d(
+ "Orientation didn't change, tiles might be not redistributed, new config",
+ newConfig);
}
}
@@ -226,7 +231,7 @@
// Keep on drawing until the animation has finished.
postInvalidateOnAnimation();
} catch (NullPointerException e) {
- Log.e(TAG, "FakeDragBy called before begin", e);
+ mLogger.logException("FakeDragBy called before begin", e);
// If we were trying to fake drag, it means we just added a new tile to the last
// page, so animate there.
final int lastPageNumber = mPages.size() - 1;
@@ -246,7 +251,7 @@
super.endFakeDrag();
} catch (NullPointerException e) {
// Not sure what's going on. Let's log it
- Log.e(TAG, "endFakeDrag called without velocityTracker", e);
+ mLogger.logException("endFakeDrag called without velocityTracker", e);
}
}
@@ -304,14 +309,14 @@
@Override
public void addTile(TileRecord tile) {
mTiles.add(tile);
- mDistributeTiles = true;
+ forceTilesRedistribution("adding new tile");
requestLayout();
}
@Override
public void removeTile(TileRecord tile) {
if (mTiles.remove(tile)) {
- mDistributeTiles = true;
+ forceTilesRedistribution("removing tile");
requestLayout();
}
}
@@ -367,19 +372,11 @@
final int tilesPerPageCount = mPages.get(0).maxTiles();
int index = 0;
final int totalTilesCount = mTiles.size();
- if (DEBUG) {
- Log.d(TAG, "Distributing tiles: "
- + "[tilesPerPageCount=" + tilesPerPageCount + "]"
- + "[totalTilesCount=" + totalTilesCount + "]"
- );
- }
+ mLogger.logTileDistributionInProgress(tilesPerPageCount, totalTilesCount);
for (int i = 0; i < totalTilesCount; i++) {
TileRecord tile = mTiles.get(i);
if (mPages.get(index).mRecords.size() == tilesPerPageCount) index++;
- if (DEBUG) {
- Log.d(TAG, "Adding " + tile.tile.getClass().getSimpleName() + " to "
- + index);
- }
+ mLogger.logTileDistributed(tile.tile.getClass().getSimpleName(), index);
mPages.get(index).addTile(tile);
}
}
@@ -394,11 +391,11 @@
return;
}
while (mPages.size() < numPages) {
- if (DEBUG) Log.d(TAG, "Adding page");
+ mLogger.d("Adding new page");
mPages.add(createTileLayout());
}
while (mPages.size() > numPages) {
- if (DEBUG) Log.d(TAG, "Removing page");
+ mLogger.d("Removing page");
mPages.remove(mPages.size() - 1);
}
mPageIndicator.setNumPages(mPages.size());
@@ -417,8 +414,12 @@
changed |= mPages.get(i).updateResources();
}
if (changed) {
- mDistributeTiles = true;
+ forceTilesRedistribution("resources in pages changed");
requestLayout();
+ } else {
+ // logging in case we missed redistribution because number of column in updateResources
+ // was not changed, can be removed after b/255208946 is fixed
+ mLogger.d("resource in pages didn't change, tiles might be not redistributed");
}
return changed;
}
@@ -430,7 +431,7 @@
for (int i = 0; i < mPages.size(); i++) {
if (mPages.get(i).setMinRows(minRows)) {
changed = true;
- mDistributeTiles = true;
+ forceTilesRedistribution("minRows changed in page");
}
}
return changed;
@@ -443,7 +444,7 @@
for (int i = 0; i < mPages.size(); i++) {
if (mPages.get(i).setMaxColumns(maxColumns)) {
changed = true;
- mDistributeTiles = true;
+ forceTilesRedistribution("maxColumns in pages changed");
}
}
return changed;
@@ -710,14 +711,14 @@
private final PagerAdapter mAdapter = new PagerAdapter() {
@Override
public void destroyItem(ViewGroup container, int position, Object object) {
- if (DEBUG) Log.d(TAG, "Destantiating " + position);
+ mLogger.d("Destantiating page at", position);
container.removeView((View) object);
updateListening();
}
@Override
public Object instantiateItem(ViewGroup container, int position) {
- if (DEBUG) Log.d(TAG, "Instantiating " + position);
+ mLogger.d("Instantiating page at", position);
if (isLayoutRtl()) {
position = mPages.size() - 1 - position;
}
@@ -745,10 +746,15 @@
* Force all tiles to be redistributed across pages.
* Should be called when one of the following changes: rows, columns, number of tiles.
*/
- public void forceTilesRedistribution() {
+ public void forceTilesRedistribution(String reason) {
+ mLogger.d("forcing tile redistribution across pages, reason", reason);
mDistributeTiles = true;
}
+ public void setLogger(QSLogger qsLogger) {
+ mLogger = qsLogger;
+ }
+
public interface PageListener {
int INVALID_PAGE = -1;
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java b/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java
index 6517ff3..7067c220 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java
@@ -43,6 +43,7 @@
import com.android.internal.widget.RemeasuringLinearLayout;
import com.android.systemui.R;
import com.android.systemui.plugins.qs.QSTile;
+import com.android.systemui.qs.logging.QSLogger;
import com.android.systemui.settings.brightness.BrightnessSliderController;
import com.android.systemui.tuner.TunerService;
import com.android.systemui.tuner.TunerService.Tunable;
@@ -106,6 +107,7 @@
private ViewGroup mMediaHostView;
private boolean mShouldMoveMediaOnExpansion = true;
private boolean mUsingCombinedHeaders = false;
+ private QSLogger mQsLogger;
public QSPanel(Context context, AttributeSet attrs) {
super(context, attrs);
@@ -122,7 +124,8 @@
}
- void initialize() {
+ void initialize(QSLogger qsLogger) {
+ mQsLogger = qsLogger;
mTileLayout = getOrCreateTileLayout();
if (mUsingMediaPlayer) {
@@ -206,6 +209,7 @@
if (mTileLayout == null) {
mTileLayout = (QSTileLayout) LayoutInflater.from(mContext)
.inflate(R.layout.qs_paged_tile_layout, this, false);
+ mTileLayout.setLogger(mQsLogger);
mTileLayout.setSquishinessFraction(mSquishinessFraction);
}
return mTileLayout;
@@ -735,6 +739,8 @@
default void setExpansion(float expansion, float proposedTranslation) {}
int getNumVisibleTiles();
+
+ default void setLogger(QSLogger qsLogger) { }
}
interface OnConfigurationChangedListener {
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSPanelController.java b/packages/SystemUI/src/com/android/systemui/qs/QSPanelController.java
index b2ca6b7..cabe1da 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSPanelController.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSPanelController.java
@@ -122,9 +122,8 @@
}
switchTileLayout(true);
mBrightnessMirrorHandler.onQsPanelAttached();
-
- ((PagedTileLayout) mView.getOrCreateTileLayout())
- .setOnTouchListener(mTileLayoutTouchListener);
+ PagedTileLayout pagedTileLayout= ((PagedTileLayout) mView.getOrCreateTileLayout());
+ pagedTileLayout.setOnTouchListener(mTileLayoutTouchListener);
}
@Override
@@ -150,7 +149,8 @@
@Override
protected void onSplitShadeChanged() {
- ((PagedTileLayout) mView.getOrCreateTileLayout()).forceTilesRedistribution();
+ ((PagedTileLayout) mView.getOrCreateTileLayout())
+ .forceTilesRedistribution("Split shade state changed");
}
/** */
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSPanelControllerBase.java b/packages/SystemUI/src/com/android/systemui/qs/QSPanelControllerBase.java
index 60d2c17..7bb672c 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSPanelControllerBase.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSPanelControllerBase.java
@@ -70,7 +70,7 @@
protected final MediaHost mMediaHost;
protected final MetricsLogger mMetricsLogger;
private final UiEventLogger mUiEventLogger;
- private final QSLogger mQSLogger;
+ protected final QSLogger mQSLogger;
private final DumpManager mDumpManager;
protected final ArrayList<TileRecord> mRecords = new ArrayList<>();
protected boolean mShouldUseSplitNotificationShade;
@@ -152,7 +152,7 @@
@Override
protected void onInit() {
- mView.initialize();
+ mView.initialize(mQSLogger);
mQSLogger.logAllTilesChangeListening(mView.isListening(), mView.getDumpableTag(), "");
}
@@ -430,6 +430,7 @@
pw.println(" horizontal layout: " + mUsingHorizontalLayout);
pw.println(" last orientation: " + mLastOrientation);
}
+ pw.println(" mShouldUseSplitNotificationShade: " + mShouldUseSplitNotificationShade);
}
public QSPanel.QSTileLayout getTileLayout() {
diff --git a/packages/SystemUI/src/com/android/systemui/qs/logging/QSLogger.kt b/packages/SystemUI/src/com/android/systemui/qs/logging/QSLogger.kt
index 9f6317f..d682853f5 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/logging/QSLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/logging/QSLogger.kt
@@ -21,10 +21,13 @@
import com.android.systemui.plugins.log.LogBuffer
import com.android.systemui.plugins.log.LogLevel
import com.android.systemui.plugins.log.LogLevel.DEBUG
+import com.android.systemui.plugins.log.LogLevel.ERROR
import com.android.systemui.plugins.log.LogLevel.VERBOSE
+import com.android.systemui.plugins.log.LogLevel.WARNING
import com.android.systemui.plugins.log.LogMessage
import com.android.systemui.plugins.qs.QSTile
import com.android.systemui.statusbar.StatusBarState
+import com.google.errorprone.annotations.CompileTimeConstant
import javax.inject.Inject
private const val TAG = "QSLog"
@@ -33,6 +36,26 @@
@QSLog private val buffer: LogBuffer
) {
+ fun d(@CompileTimeConstant msg: String) = buffer.log(TAG, DEBUG, msg)
+
+ fun e(@CompileTimeConstant msg: String) = buffer.log(TAG, ERROR, msg)
+
+ fun v(@CompileTimeConstant msg: String) = buffer.log(TAG, VERBOSE, msg)
+
+ fun w(@CompileTimeConstant msg: String) = buffer.log(TAG, WARNING, msg)
+
+ fun logException(@CompileTimeConstant logMsg: String, ex: Exception) {
+ buffer.log(TAG, ERROR, {}, { logMsg }, exception = ex)
+ }
+
+ fun v(@CompileTimeConstant msg: String, arg: Any) {
+ buffer.log(TAG, VERBOSE, { str1 = arg.toString() }, { "$msg: $str1" })
+ }
+
+ fun d(@CompileTimeConstant msg: String, arg: Any) {
+ buffer.log(TAG, DEBUG, { str1 = arg.toString() }, { "$msg: $str1" })
+ }
+
fun logTileAdded(tileSpec: String) {
log(DEBUG, {
str1 = tileSpec
@@ -236,6 +259,24 @@
})
}
+ fun logTileDistributionInProgress(tilesPerPageCount: Int, totalTilesCount: Int) {
+ log(DEBUG, {
+ int1 = tilesPerPageCount
+ int2 = totalTilesCount
+ }, {
+ "Distributing tiles: [tilesPerPageCount=$int1] [totalTilesCount=$int2]"
+ })
+ }
+
+ fun logTileDistributed(tileName: String, pageIndex: Int) {
+ log(DEBUG, {
+ str1 = tileName
+ int1 = pageIndex
+ }, {
+ "Adding $str1 to page number $int1"
+ })
+ }
+
private fun toStateString(state: Int): String {
return when (state) {
Tile.STATE_ACTIVE -> "active"
diff --git a/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java b/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java
index 00d129a..4d005be 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java
@@ -889,7 +889,7 @@
* Notifies the Launcher that screen is starting to turn on.
*/
@Override
- public void onScreenTurningOn(@NonNull Runnable ignored) {
+ public void onScreenTurningOn() {
try {
if (mOverviewProxy != null) {
mOverviewProxy.onScreenTurningOn();
diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
index bfc810b..6d6427a 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
@@ -40,6 +40,7 @@
import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_QUICK_SETTINGS_EXPANDED;
import static com.android.systemui.statusbar.StatusBarState.KEYGUARD;
import static com.android.systemui.statusbar.StatusBarState.SHADE;
+import static com.android.systemui.statusbar.StatusBarState.SHADE_LOCKED;
import static com.android.systemui.statusbar.VibratorHelper.TOUCH_VIBRATION_ATTRIBUTES;
import static com.android.systemui.statusbar.notification.stack.StackStateAnimator.ANIMATION_DURATION_FOLD_TO_AOD;
import static com.android.systemui.util.DumpUtilsKt.asIndenting;
@@ -1327,7 +1328,9 @@
mKeyguardBottomArea.init(
mKeyguardBottomAreaViewModel,
mFalsingManager,
- mLockIconViewController
+ mLockIconViewController,
+ stringResourceId ->
+ mKeyguardIndicationController.showTransientIndication(stringResourceId)
);
}
@@ -5480,6 +5483,15 @@
mBarState = statusBarState;
mKeyguardShowing = keyguardShowing;
+ boolean fromShadeToKeyguard = statusBarState == KEYGUARD
+ && (oldState == SHADE || oldState == SHADE_LOCKED);
+ if (mSplitShadeEnabled && fromShadeToKeyguard) {
+ // user can go to keyguard from different shade states and closing animation
+ // may not fully run - we always want to make sure we close QS when that happens
+ // as we never need QS open in fresh keyguard state
+ closeQs();
+ }
+
if (oldState == KEYGUARD && (goingToFullShade
|| statusBarState == StatusBarState.SHADE_LOCKED)) {
@@ -5499,27 +5511,12 @@
mKeyguardStatusBarViewController.animateKeyguardStatusBarIn();
mNotificationStackScrollLayoutController.resetScrollPosition();
- // Only animate header if the header is visible. If not, it will partially
- // animate out
- // the top of QS
- if (!mQsExpanded) {
- // TODO(b/185683835) Nicer clipping when using new spacial model
- if (mSplitShadeEnabled) {
- mQs.animateHeaderSlidingOut();
- }
- }
} else {
// this else branch means we are doing one of:
// - from KEYGUARD to SHADE (but not fully expanded as when swiping from the top)
// - from SHADE to KEYGUARD
// - from SHADE_LOCKED to SHADE
// - getting notified again about the current SHADE or KEYGUARD state
- if (mSplitShadeEnabled && oldState == SHADE && statusBarState == KEYGUARD) {
- // user can go to keyguard from different shade states and closing animation
- // may not fully run - we always want to make sure we close QS when that happens
- // as we never need QS open in fresh keyguard state
- closeQs();
- }
final boolean animatingUnlockedShadeToKeyguard = oldState == SHADE
&& statusBarState == KEYGUARD
&& mScreenOffAnimationController.isKeyguardShowDelayed();
diff --git a/packages/SystemUI/src/com/android/systemui/shade/ShadeEventsModule.java b/packages/SystemUI/src/com/android/systemui/shade/ShadeEventsModule.java
index 959c339..f87a1ed 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/ShadeEventsModule.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/ShadeEventsModule.java
@@ -16,14 +16,17 @@
package com.android.systemui.shade;
-import com.android.systemui.dagger.SysUISingleton;
+import com.android.systemui.shade.data.repository.ShadeRepository;
+import com.android.systemui.shade.data.repository.ShadeRepositoryImpl;
import dagger.Binds;
import dagger.Module;
-/** Provides a {@link ShadeStateEvents} in {@link SysUISingleton} scope. */
+/** Provides Shade-related events and information. */
@Module
public abstract class ShadeEventsModule {
@Binds
abstract ShadeStateEvents bindShadeEvents(ShadeExpansionStateManager impl);
+
+ @Binds abstract ShadeRepository shadeRepository(ShadeRepositoryImpl impl);
}
diff --git a/packages/SystemUI/src/com/android/systemui/shade/data/repository/ShadeRepository.kt b/packages/SystemUI/src/com/android/systemui/shade/data/repository/ShadeRepository.kt
index 09019a6..bf7a2be 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/data/repository/ShadeRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/data/repository/ShadeRepository.kt
@@ -27,11 +27,17 @@
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.distinctUntilChanged
+interface ShadeRepository {
+ /** ShadeModel information regarding shade expansion events */
+ val shadeModel: Flow<ShadeModel>
+}
+
/** Business logic for shade interactions */
@SysUISingleton
-class ShadeRepository @Inject constructor(shadeExpansionStateManager: ShadeExpansionStateManager) {
-
- val shadeModel: Flow<ShadeModel> =
+class ShadeRepositoryImpl
+@Inject
+constructor(shadeExpansionStateManager: ShadeExpansionStateManager) : ShadeRepository {
+ override val shadeModel: Flow<ShadeModel> =
conflatedCallbackFlow {
val callback =
object : ShadeExpansionListener {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/LaunchAnimationParameters.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/LaunchAnimationParameters.kt
index 42edb30..c22dbf6 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/LaunchAnimationParameters.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/LaunchAnimationParameters.kt
@@ -27,8 +27,10 @@
/**
* The top position of the notification at the start of the animation. This is needed in order
* to keep the notification at its place when launching a notification that is clipped rounded.
+ * This value is in absolute screen coordinates.
*/
- var startNotificationTop = 0f
+ var startNotificationTop = 0
+ var notificationParentTop = 0
var startClipTopAmount = 0
var parentStartClipTopAmount = 0
var progress = 0f
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationLaunchAnimatorController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationLaunchAnimatorController.kt
index 0d35fdc..798bbe8 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationLaunchAnimatorController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationLaunchAnimatorController.kt
@@ -92,11 +92,12 @@
)
params.startTranslationZ = notification.translationZ
- params.startNotificationTop = notification.translationY
+ params.startNotificationTop = location[1]
+ params.notificationParentTop = notificationListContainer
+ .getViewParentForNotification(notificationEntry).locationOnScreen[1]
params.startRoundedTopClipping = roundedTopClipping
params.startClipTopAmount = notification.clipTopAmount
if (notification.isChildInGroup) {
- params.startNotificationTop += notification.notificationParent.translationY
val locationOnScreen = notification.notificationParent.locationOnScreen[1]
val parentRoundedClip = (clipStartLocation - locationOnScreen).coerceAtLeast(0)
params.parentStartRoundedTopClipping = parentRoundedClip
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/Roundable.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/Roundable.kt
index 0eb0000..dc9b416 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/Roundable.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/Roundable.kt
@@ -306,9 +306,12 @@
*/
class RoundableState(
internal val targetView: View,
- roundable: Roundable,
- internal val maxRadius: Float,
+ private val roundable: Roundable,
+ maxRadius: Float,
) {
+ internal var maxRadius = maxRadius
+ private set
+
/** Animatable for top roundness */
private val topAnimatable = topAnimatable(roundable)
@@ -356,6 +359,13 @@
PropertyAnimator.setProperty(targetView, bottomAnimatable, value, DURATION, animated)
}
+ fun setMaxRadius(radius: Float) {
+ if (maxRadius != radius) {
+ maxRadius = radius
+ roundable.applyRoundnessAndInvalidate()
+ }
+ }
+
fun debugString() = buildString {
append("TargetView: ${targetView.hashCode()} ")
append("Top: $topRoundness ")
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/KeyguardCoordinator.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/KeyguardCoordinator.kt
index 4133802..76252d0 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/KeyguardCoordinator.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/KeyguardCoordinator.kt
@@ -16,8 +16,13 @@
package com.android.systemui.statusbar.notification.collection.coordinator
+import android.database.ContentObserver
+import android.os.UserHandle
+import android.provider.Settings
import androidx.annotation.VisibleForTesting
+import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow
import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.keyguard.data.repository.KeyguardRepository
import com.android.systemui.plugins.statusbar.StatusBarStateController
import com.android.systemui.statusbar.StatusBarState
@@ -30,11 +35,20 @@
import com.android.systemui.statusbar.notification.collection.provider.SectionHeaderVisibilityProvider
import com.android.systemui.statusbar.notification.collection.provider.SeenNotificationsProviderImpl
import com.android.systemui.statusbar.notification.interruption.KeyguardNotificationVisibilityProvider
+import com.android.systemui.util.settings.SecureSettings
+import com.android.systemui.util.settings.SettingsProxy
import javax.inject.Inject
import kotlin.time.Duration.Companion.seconds
+import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.channels.awaitClose
import kotlinx.coroutines.delay
+import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.collectLatest
+import kotlinx.coroutines.flow.conflate
+import kotlinx.coroutines.flow.flowOn
+import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.onStart
import kotlinx.coroutines.launch
/**
@@ -45,16 +59,19 @@
class KeyguardCoordinator
@Inject
constructor(
+ @Background private val bgDispatcher: CoroutineDispatcher,
private val keyguardNotificationVisibilityProvider: KeyguardNotificationVisibilityProvider,
private val keyguardRepository: KeyguardRepository,
private val notifPipelineFlags: NotifPipelineFlags,
@Application private val scope: CoroutineScope,
private val sectionHeaderVisibilityProvider: SectionHeaderVisibilityProvider,
+ private val secureSettings: SecureSettings,
private val seenNotifsProvider: SeenNotificationsProviderImpl,
private val statusBarStateController: StatusBarStateController,
) : Coordinator {
private val unseenNotifications = mutableSetOf<NotificationEntry>()
+ private var unseenFilterEnabled = false
override fun attach(pipeline: NotifPipeline) {
setupInvalidateNotifListCallbacks()
@@ -71,6 +88,7 @@
pipeline.addFinalizeFilter(unseenNotifFilter)
pipeline.addCollectionListener(collectionListener)
scope.launch { clearUnseenWhenKeyguardIsDismissed() }
+ scope.launch { invalidateWhenUnseenSettingChanges() }
}
private suspend fun clearUnseenWhenKeyguardIsDismissed() {
@@ -85,6 +103,36 @@
}
}
+ private suspend fun invalidateWhenUnseenSettingChanges() {
+ secureSettings
+ // emit whenever the setting has changed
+ .settingChangesForUser(
+ Settings.Secure.LOCK_SCREEN_SHOW_ONLY_UNSEEN_NOTIFICATIONS,
+ UserHandle.USER_ALL,
+ )
+ // perform a query immediately
+ .onStart { emit(Unit) }
+ // for each change, lookup the new value
+ .map {
+ secureSettings.getBoolForUser(
+ Settings.Secure.LOCK_SCREEN_SHOW_ONLY_UNSEEN_NOTIFICATIONS,
+ UserHandle.USER_CURRENT,
+ )
+ }
+ // perform lookups on the bg thread pool
+ .flowOn(bgDispatcher)
+ // only track the most recent emission, if events are happening faster than they can be
+ // consumed
+ .conflate()
+ // update local field and invalidate if necessary
+ .collect { setting ->
+ if (setting != unseenFilterEnabled) {
+ unseenFilterEnabled = setting
+ unseenNotifFilter.invalidateList("unseen setting changed")
+ }
+ }
+ }
+
private val collectionListener =
object : NotifCollectionListener {
override fun onEntryAdded(entry: NotificationEntry) {
@@ -112,6 +160,8 @@
override fun shouldFilterOut(entry: NotificationEntry, now: Long): Boolean =
when {
+ // Don't apply filter if the setting is disabled
+ !unseenFilterEnabled -> false
// Don't apply filter if the keyguard isn't currently showing
!keyguardRepository.isKeyguardShowing() -> false
// Don't apply the filter if the notification is unseen
@@ -165,3 +215,15 @@
private val SEEN_TIMEOUT = 5.seconds
}
}
+
+private fun SettingsProxy.settingChangesForUser(name: String, userHandle: Int): Flow<Unit> =
+ conflatedCallbackFlow {
+ val observer =
+ object : ContentObserver(null) {
+ override fun onChange(selfChange: Boolean) {
+ trySend(Unit)
+ }
+ }
+ registerContentObserverForUser(name, observer, userHandle)
+ awaitClose { unregisterContentObserver(observer) }
+ }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotificationMemory.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotificationMemory.kt
index 0380fff..1fcf17f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotificationMemory.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotificationMemory.kt
@@ -17,10 +17,14 @@
package com.android.systemui.statusbar.notification.logging
+import android.app.Notification
+
/** Describes usage of a notification. */
data class NotificationMemoryUsage(
val packageName: String,
+ val uid: Int,
val notificationKey: String,
+ val notification: Notification,
val objectUsage: NotificationObjectUsage,
val viewUsage: List<NotificationViewUsage>
)
@@ -34,7 +38,8 @@
val smallIcon: Int,
val largeIcon: Int,
val extras: Int,
- val style: String?,
+ /** Style type, integer from [android.stats.sysui.NotificationEnums] */
+ val style: Int,
val styleIcon: Int,
val bigPicture: Int,
val extender: Int,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotificationMemoryDumper.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotificationMemoryDumper.kt
new file mode 100644
index 0000000..ffd931c
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotificationMemoryDumper.kt
@@ -0,0 +1,173 @@
+/*
+ *
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.notification.logging
+
+import android.stats.sysui.NotificationEnums
+import com.android.systemui.Dumpable
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dump.DumpManager
+import com.android.systemui.statusbar.notification.collection.NotifPipeline
+import java.io.PrintWriter
+import javax.inject.Inject
+
+/** Dumps current notification memory use to bug reports for easier debugging. */
+@SysUISingleton
+class NotificationMemoryDumper
+@Inject
+constructor(val dumpManager: DumpManager, val notificationPipeline: NotifPipeline) : Dumpable {
+
+ fun init() {
+ dumpManager.registerNormalDumpable(javaClass.simpleName, this)
+ }
+
+ override fun dump(pw: PrintWriter, args: Array<out String>) {
+ val memoryUse =
+ NotificationMemoryMeter.notificationMemoryUse(notificationPipeline.allNotifs)
+ .sortedWith(compareBy({ it.packageName }, { it.notificationKey }))
+ dumpNotificationObjects(pw, memoryUse)
+ dumpNotificationViewUsage(pw, memoryUse)
+ }
+
+ /** Renders a table of notification object usage into passed [PrintWriter]. */
+ private fun dumpNotificationObjects(pw: PrintWriter, memoryUse: List<NotificationMemoryUsage>) {
+ pw.println("Notification Object Usage")
+ pw.println("-----------")
+ pw.println(
+ "Package".padEnd(35) +
+ "\t\tSmall\tLarge\t${"Style".padEnd(15)}\t\tStyle\tBig\tExtend.\tExtras\tCustom"
+ )
+ pw.println("".padEnd(35) + "\t\tIcon\tIcon\t${"".padEnd(15)}\t\tIcon\tPicture\t \t \tView")
+ pw.println()
+
+ memoryUse.forEach { use ->
+ pw.println(
+ use.packageName.padEnd(35) +
+ "\t\t" +
+ "${use.objectUsage.smallIcon}\t${use.objectUsage.largeIcon}\t" +
+ (styleEnumToString(use.objectUsage.style).take(15) ?: "").padEnd(15) +
+ "\t\t${use.objectUsage.styleIcon}\t" +
+ "${use.objectUsage.bigPicture}\t${use.objectUsage.extender}\t" +
+ "${use.objectUsage.extras}\t${use.objectUsage.hasCustomView}\t" +
+ use.notificationKey
+ )
+ }
+
+ // Calculate totals for easily glanceable summary.
+ data class Totals(
+ var smallIcon: Int = 0,
+ var largeIcon: Int = 0,
+ var styleIcon: Int = 0,
+ var bigPicture: Int = 0,
+ var extender: Int = 0,
+ var extras: Int = 0,
+ )
+
+ val totals =
+ memoryUse.fold(Totals()) { t, usage ->
+ t.smallIcon += usage.objectUsage.smallIcon
+ t.largeIcon += usage.objectUsage.largeIcon
+ t.styleIcon += usage.objectUsage.styleIcon
+ t.bigPicture += usage.objectUsage.bigPicture
+ t.extender += usage.objectUsage.extender
+ t.extras += usage.objectUsage.extras
+ t
+ }
+
+ pw.println()
+ pw.println("TOTALS")
+ pw.println(
+ "".padEnd(35) +
+ "\t\t" +
+ "${toKb(totals.smallIcon)}\t${toKb(totals.largeIcon)}\t" +
+ "".padEnd(15) +
+ "\t\t${toKb(totals.styleIcon)}\t" +
+ "${toKb(totals.bigPicture)}\t${toKb(totals.extender)}\t" +
+ toKb(totals.extras)
+ )
+ pw.println()
+ }
+
+ /** Renders a table of notification view usage into passed [PrintWriter] */
+ private fun dumpNotificationViewUsage(
+ pw: PrintWriter,
+ memoryUse: List<NotificationMemoryUsage>,
+ ) {
+
+ data class Totals(
+ var smallIcon: Int = 0,
+ var largeIcon: Int = 0,
+ var style: Int = 0,
+ var customViews: Int = 0,
+ var softwareBitmapsPenalty: Int = 0,
+ )
+
+ val totals = Totals()
+ pw.println("Notification View Usage")
+ pw.println("-----------")
+ pw.println("View Type".padEnd(24) + "\tSmall\tLarge\tStyle\tCustom\tSoftware")
+ pw.println("".padEnd(24) + "\tIcon\tIcon\tUse\tView\tBitmaps")
+ pw.println()
+ memoryUse
+ .filter { it.viewUsage.isNotEmpty() }
+ .forEach { use ->
+ pw.println(use.packageName + " " + use.notificationKey)
+ use.viewUsage.forEach { view ->
+ pw.println(
+ " ${view.viewType.toString().padEnd(24)}\t${view.smallIcon}" +
+ "\t${view.largeIcon}\t${view.style}" +
+ "\t${view.customViews}\t${view.softwareBitmapsPenalty}"
+ )
+
+ if (view.viewType == ViewType.TOTAL) {
+ totals.smallIcon += view.smallIcon
+ totals.largeIcon += view.largeIcon
+ totals.style += view.style
+ totals.customViews += view.customViews
+ totals.softwareBitmapsPenalty += view.softwareBitmapsPenalty
+ }
+ }
+ }
+ pw.println()
+ pw.println("TOTALS")
+ pw.println(
+ " ${"".padEnd(24)}\t${toKb(totals.smallIcon)}" +
+ "\t${toKb(totals.largeIcon)}\t${toKb(totals.style)}" +
+ "\t${toKb(totals.customViews)}\t${toKb(totals.softwareBitmapsPenalty)}"
+ )
+ pw.println()
+ }
+
+ private fun styleEnumToString(styleEnum: Int): String =
+ when (styleEnum) {
+ NotificationEnums.STYLE_UNSPECIFIED -> "Unspecified"
+ NotificationEnums.STYLE_NONE -> "None"
+ NotificationEnums.STYLE_BIG_PICTURE -> "BigPicture"
+ NotificationEnums.STYLE_BIG_TEXT -> "BigText"
+ NotificationEnums.STYLE_CALL -> "Call"
+ NotificationEnums.STYLE_DECORATED_CUSTOM_VIEW -> "DCustomView"
+ NotificationEnums.STYLE_INBOX -> "Inbox"
+ NotificationEnums.STYLE_MEDIA -> "Media"
+ NotificationEnums.STYLE_MESSAGING -> "Messaging"
+ NotificationEnums.STYLE_RANKER_GROUP -> "RankerGroup"
+ else -> "Unknown"
+ }
+
+ private fun toKb(bytes: Int): String {
+ return (bytes / 1024).toString() + " KB"
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotificationMemoryLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotificationMemoryLogger.kt
new file mode 100644
index 0000000..ec8501a
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotificationMemoryLogger.kt
@@ -0,0 +1,194 @@
+/*
+ *
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.notification.logging
+
+import android.app.StatsManager
+import android.util.StatsEvent
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.dagger.qualifiers.Main
+import com.android.systemui.shared.system.SysUiStatsLog
+import com.android.systemui.statusbar.notification.collection.NotifPipeline
+import com.android.systemui.util.traceSection
+import java.util.concurrent.Executor
+import javax.inject.Inject
+import kotlin.math.roundToInt
+import kotlinx.coroutines.CoroutineDispatcher
+import kotlinx.coroutines.runBlocking
+
+/** Periodically logs current state of notification memory consumption. */
+@SysUISingleton
+class NotificationMemoryLogger
+@Inject
+constructor(
+ private val notificationPipeline: NotifPipeline,
+ private val statsManager: StatsManager,
+ @Main private val mainDispatcher: CoroutineDispatcher,
+ @Background private val backgroundExecutor: Executor
+) : StatsManager.StatsPullAtomCallback {
+
+ /**
+ * This class is used to accumulate and aggregate data - the fields mirror values in statd Atom
+ * with ONE IMPORTANT difference - the values are in bytes, not KB!
+ */
+ internal data class NotificationMemoryUseAtomBuilder(val uid: Int, val style: Int) {
+ var count: Int = 0
+ var countWithInflatedViews: Int = 0
+ var smallIconObject: Int = 0
+ var smallIconBitmapCount: Int = 0
+ var largeIconObject: Int = 0
+ var largeIconBitmapCount: Int = 0
+ var bigPictureObject: Int = 0
+ var bigPictureBitmapCount: Int = 0
+ var extras: Int = 0
+ var extenders: Int = 0
+ var smallIconViews: Int = 0
+ var largeIconViews: Int = 0
+ var systemIconViews: Int = 0
+ var styleViews: Int = 0
+ var customViews: Int = 0
+ var softwareBitmaps: Int = 0
+ var seenCount = 0
+ }
+
+ fun init() {
+ statsManager.setPullAtomCallback(
+ SysUiStatsLog.NOTIFICATION_MEMORY_USE,
+ null,
+ backgroundExecutor,
+ this
+ )
+ }
+
+ /** Called by statsd to pull data. */
+ override fun onPullAtom(atomTag: Int, data: MutableList<StatsEvent>): Int =
+ traceSection("NML#onPullAtom") {
+ if (atomTag != SysUiStatsLog.NOTIFICATION_MEMORY_USE) {
+ return StatsManager.PULL_SKIP
+ }
+
+ // Notifications can only be retrieved on the main thread, so switch to that thread.
+ val notifications = getAllNotificationsOnMainThread()
+ val notificationMemoryUse =
+ NotificationMemoryMeter.notificationMemoryUse(notifications)
+ .sortedWith(
+ compareBy(
+ { it.packageName },
+ { it.objectUsage.style },
+ { it.notificationKey }
+ )
+ )
+ val usageData = aggregateMemoryUsageData(notificationMemoryUse)
+ usageData.forEach { (_, use) ->
+ data.add(
+ SysUiStatsLog.buildStatsEvent(
+ SysUiStatsLog.NOTIFICATION_MEMORY_USE,
+ use.uid,
+ use.style,
+ use.count,
+ use.countWithInflatedViews,
+ toKb(use.smallIconObject),
+ use.smallIconBitmapCount,
+ toKb(use.largeIconObject),
+ use.largeIconBitmapCount,
+ toKb(use.bigPictureObject),
+ use.bigPictureBitmapCount,
+ toKb(use.extras),
+ toKb(use.extenders),
+ toKb(use.smallIconViews),
+ toKb(use.largeIconViews),
+ toKb(use.systemIconViews),
+ toKb(use.styleViews),
+ toKb(use.customViews),
+ toKb(use.softwareBitmaps),
+ use.seenCount
+ )
+ )
+ }
+
+ return StatsManager.PULL_SUCCESS
+ }
+
+ private fun getAllNotificationsOnMainThread() =
+ runBlocking(mainDispatcher) {
+ traceSection("NML#getNotifications") { notificationPipeline.allNotifs }
+ }
+
+ /** Aggregates memory usage data by package and style, returning sums. */
+ private fun aggregateMemoryUsageData(
+ notificationMemoryUse: List<NotificationMemoryUsage>
+ ): Map<Pair<String, Int>, NotificationMemoryUseAtomBuilder> {
+ return notificationMemoryUse
+ .groupingBy { Pair(it.packageName, it.objectUsage.style) }
+ .aggregate {
+ _,
+ accumulator: NotificationMemoryUseAtomBuilder?,
+ element: NotificationMemoryUsage,
+ first ->
+ val use =
+ if (first) {
+ NotificationMemoryUseAtomBuilder(element.uid, element.objectUsage.style)
+ } else {
+ accumulator!!
+ }
+
+ use.count++
+ // If the views of the notification weren't inflated, the list of memory usage
+ // parameters will be empty.
+ if (element.viewUsage.isNotEmpty()) {
+ use.countWithInflatedViews++
+ }
+
+ use.smallIconObject += element.objectUsage.smallIcon
+ if (element.objectUsage.smallIcon > 0) {
+ use.smallIconBitmapCount++
+ }
+
+ use.largeIconObject += element.objectUsage.largeIcon
+ if (element.objectUsage.largeIcon > 0) {
+ use.largeIconBitmapCount++
+ }
+
+ use.bigPictureObject += element.objectUsage.bigPicture
+ if (element.objectUsage.bigPicture > 0) {
+ use.bigPictureBitmapCount++
+ }
+
+ use.extras += element.objectUsage.extras
+ use.extenders += element.objectUsage.extender
+
+ // Use totals count which are more accurate when aggregated
+ // in this manner.
+ element.viewUsage
+ .firstOrNull { vu -> vu.viewType == ViewType.TOTAL }
+ ?.let {
+ use.smallIconViews += it.smallIcon
+ use.largeIconViews += it.largeIcon
+ use.systemIconViews += it.systemIcons
+ use.styleViews += it.style
+ use.customViews += it.style
+ use.softwareBitmaps += it.softwareBitmapsPenalty
+ }
+
+ return@aggregate use
+ }
+ }
+
+ /** Rounds the passed value to the nearest KB - e.g. 700B rounds to 1KB. */
+ private fun toKb(value: Int): Int = (value.toFloat() / 1024f).roundToInt()
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotificationMemoryMeter.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotificationMemoryMeter.kt
index 7d39e18..41fb91e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotificationMemoryMeter.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotificationMemoryMeter.kt
@@ -1,12 +1,20 @@
package com.android.systemui.statusbar.notification.logging
import android.app.Notification
+import android.app.Notification.BigPictureStyle
+import android.app.Notification.BigTextStyle
+import android.app.Notification.CallStyle
+import android.app.Notification.DecoratedCustomViewStyle
+import android.app.Notification.InboxStyle
+import android.app.Notification.MediaStyle
+import android.app.Notification.MessagingStyle
import android.app.Person
import android.graphics.Bitmap
import android.graphics.drawable.Icon
import android.os.Bundle
import android.os.Parcel
import android.os.Parcelable
+import android.stats.sysui.NotificationEnums
import androidx.annotation.WorkerThread
import com.android.systemui.statusbar.notification.NotificationUtils
import com.android.systemui.statusbar.notification.collection.NotificationEntry
@@ -19,6 +27,7 @@
private const val TV_EXTENSIONS = "android.tv.EXTENSIONS"
private const val WEARABLE_EXTENSIONS = "android.wearable.EXTENSIONS"
private const val WEARABLE_EXTENSIONS_BACKGROUND = "background"
+ private const val AUTOGROUP_KEY = "ranker_group"
/** Returns a list of memory use entries for currently shown notifications. */
@WorkerThread
@@ -29,12 +38,15 @@
.asSequence()
.map { entry ->
val packageName = entry.sbn.packageName
+ val uid = entry.sbn.uid
val notificationObjectUsage =
notificationMemoryUse(entry.sbn.notification, hashSetOf())
val notificationViewUsage = NotificationMemoryViewWalker.getViewUsage(entry.row)
NotificationMemoryUsage(
packageName,
+ uid,
NotificationUtils.logKey(entry.sbn.key),
+ entry.sbn.notification,
notificationObjectUsage,
notificationViewUsage
)
@@ -49,7 +61,9 @@
): NotificationMemoryUsage {
return NotificationMemoryUsage(
entry.sbn.packageName,
+ entry.sbn.uid,
NotificationUtils.logKey(entry.sbn.key),
+ entry.sbn.notification,
notificationMemoryUse(entry.sbn.notification, seenBitmaps),
NotificationMemoryViewWalker.getViewUsage(entry.row)
)
@@ -116,7 +130,13 @@
val wearExtenderBackground =
computeParcelableUse(wearExtender, WEARABLE_EXTENSIONS_BACKGROUND, seenBitmaps)
- val style = notification.notificationStyle
+ val style =
+ if (notification.group == AUTOGROUP_KEY) {
+ NotificationEnums.STYLE_RANKER_GROUP
+ } else {
+ styleEnum(notification.notificationStyle)
+ }
+
val hasCustomView = notification.contentView != null || notification.bigContentView != null
val extrasSize = computeBundleSize(extras)
@@ -124,7 +144,7 @@
smallIcon = smallIconUse,
largeIcon = largeIconUse,
extras = extrasSize,
- style = style?.simpleName,
+ style = style,
styleIcon =
bigPictureIconUse +
peopleUse +
@@ -144,6 +164,25 @@
}
/**
+ * Returns logging style enum based on current style class.
+ *
+ * @return style value in [NotificationEnums]
+ */
+ private fun styleEnum(style: Class<out Notification.Style>?): Int =
+ when (style?.name) {
+ null -> NotificationEnums.STYLE_NONE
+ BigTextStyle::class.java.name -> NotificationEnums.STYLE_BIG_TEXT
+ BigPictureStyle::class.java.name -> NotificationEnums.STYLE_BIG_PICTURE
+ InboxStyle::class.java.name -> NotificationEnums.STYLE_INBOX
+ MediaStyle::class.java.name -> NotificationEnums.STYLE_MEDIA
+ DecoratedCustomViewStyle::class.java.name ->
+ NotificationEnums.STYLE_DECORATED_CUSTOM_VIEW
+ MessagingStyle::class.java.name -> NotificationEnums.STYLE_MESSAGING
+ CallStyle::class.java.name -> NotificationEnums.STYLE_CALL
+ else -> NotificationEnums.STYLE_UNSPECIFIED
+ }
+
+ /**
* Calculates size of the bundle data (excluding FDs and other shared objects like ashmem
* bitmaps). Can be slow.
*/
@@ -176,7 +215,7 @@
*
* @return memory usage in bytes or 0 if the icon is Uri/Resource based
*/
- private fun computeIconUse(icon: Icon?, seenBitmaps: HashSet<Int>) =
+ private fun computeIconUse(icon: Icon?, seenBitmaps: HashSet<Int>): Int =
when (icon?.type) {
Icon.TYPE_BITMAP -> computeBitmapUse(icon.bitmap, seenBitmaps)
Icon.TYPE_ADAPTIVE_BITMAP -> computeBitmapUse(icon.bitmap, seenBitmaps)
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotificationMemoryMonitor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotificationMemoryMonitor.kt
index c09cc43..f38c1e5 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotificationMemoryMonitor.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotificationMemoryMonitor.kt
@@ -18,11 +18,10 @@
package com.android.systemui.statusbar.notification.logging
import android.util.Log
-import com.android.systemui.Dumpable
import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.dump.DumpManager
-import com.android.systemui.statusbar.notification.collection.NotifPipeline
-import java.io.PrintWriter
+import com.android.systemui.flags.FeatureFlags
+import com.android.systemui.flags.Flags
+import dagger.Lazy
import javax.inject.Inject
/** This class monitors and logs current Notification memory use. */
@@ -30,9 +29,10 @@
class NotificationMemoryMonitor
@Inject
constructor(
- val notificationPipeline: NotifPipeline,
- val dumpManager: DumpManager,
-) : Dumpable {
+ private val featureFlags: FeatureFlags,
+ private val notificationMemoryDumper: NotificationMemoryDumper,
+ private val notificationMemoryLogger: Lazy<NotificationMemoryLogger>,
+) {
companion object {
private const val TAG = "NotificationMemory"
@@ -40,127 +40,10 @@
fun init() {
Log.d(TAG, "NotificationMemoryMonitor initialized.")
- dumpManager.registerDumpable(javaClass.simpleName, this)
- }
-
- override fun dump(pw: PrintWriter, args: Array<out String>) {
- val memoryUse =
- NotificationMemoryMeter.notificationMemoryUse(notificationPipeline.allNotifs)
- .sortedWith(compareBy({ it.packageName }, { it.notificationKey }))
- dumpNotificationObjects(pw, memoryUse)
- dumpNotificationViewUsage(pw, memoryUse)
- }
-
- /** Renders a table of notification object usage into passed [PrintWriter]. */
- private fun dumpNotificationObjects(pw: PrintWriter, memoryUse: List<NotificationMemoryUsage>) {
- pw.println("Notification Object Usage")
- pw.println("-----------")
- pw.println(
- "Package".padEnd(35) +
- "\t\tSmall\tLarge\t${"Style".padEnd(15)}\t\tStyle\tBig\tExtend.\tExtras\tCustom"
- )
- pw.println("".padEnd(35) + "\t\tIcon\tIcon\t${"".padEnd(15)}\t\tIcon\tPicture\t \t \tView")
- pw.println()
-
- memoryUse.forEach { use ->
- pw.println(
- use.packageName.padEnd(35) +
- "\t\t" +
- "${use.objectUsage.smallIcon}\t${use.objectUsage.largeIcon}\t" +
- (use.objectUsage.style?.take(15) ?: "").padEnd(15) +
- "\t\t${use.objectUsage.styleIcon}\t" +
- "${use.objectUsage.bigPicture}\t${use.objectUsage.extender}\t" +
- "${use.objectUsage.extras}\t${use.objectUsage.hasCustomView}\t" +
- use.notificationKey
- )
+ notificationMemoryDumper.init()
+ if (featureFlags.isEnabled(Flags.NOTIFICATION_MEMORY_LOGGING_ENABLED)) {
+ Log.d(TAG, "Notification memory logging enabled.")
+ notificationMemoryLogger.get().init()
}
-
- // Calculate totals for easily glanceable summary.
- data class Totals(
- var smallIcon: Int = 0,
- var largeIcon: Int = 0,
- var styleIcon: Int = 0,
- var bigPicture: Int = 0,
- var extender: Int = 0,
- var extras: Int = 0,
- )
-
- val totals =
- memoryUse.fold(Totals()) { t, usage ->
- t.smallIcon += usage.objectUsage.smallIcon
- t.largeIcon += usage.objectUsage.largeIcon
- t.styleIcon += usage.objectUsage.styleIcon
- t.bigPicture += usage.objectUsage.bigPicture
- t.extender += usage.objectUsage.extender
- t.extras += usage.objectUsage.extras
- t
- }
-
- pw.println()
- pw.println("TOTALS")
- pw.println(
- "".padEnd(35) +
- "\t\t" +
- "${toKb(totals.smallIcon)}\t${toKb(totals.largeIcon)}\t" +
- "".padEnd(15) +
- "\t\t${toKb(totals.styleIcon)}\t" +
- "${toKb(totals.bigPicture)}\t${toKb(totals.extender)}\t" +
- toKb(totals.extras)
- )
- pw.println()
- }
-
- /** Renders a table of notification view usage into passed [PrintWriter] */
- private fun dumpNotificationViewUsage(
- pw: PrintWriter,
- memoryUse: List<NotificationMemoryUsage>,
- ) {
-
- data class Totals(
- var smallIcon: Int = 0,
- var largeIcon: Int = 0,
- var style: Int = 0,
- var customViews: Int = 0,
- var softwareBitmapsPenalty: Int = 0,
- )
-
- val totals = Totals()
- pw.println("Notification View Usage")
- pw.println("-----------")
- pw.println("View Type".padEnd(24) + "\tSmall\tLarge\tStyle\tCustom\tSoftware")
- pw.println("".padEnd(24) + "\tIcon\tIcon\tUse\tView\tBitmaps")
- pw.println()
- memoryUse
- .filter { it.viewUsage.isNotEmpty() }
- .forEach { use ->
- pw.println(use.packageName + " " + use.notificationKey)
- use.viewUsage.forEach { view ->
- pw.println(
- " ${view.viewType.toString().padEnd(24)}\t${view.smallIcon}" +
- "\t${view.largeIcon}\t${view.style}" +
- "\t${view.customViews}\t${view.softwareBitmapsPenalty}"
- )
-
- if (view.viewType == ViewType.TOTAL) {
- totals.smallIcon += view.smallIcon
- totals.largeIcon += view.largeIcon
- totals.style += view.style
- totals.customViews += view.customViews
- totals.softwareBitmapsPenalty += view.softwareBitmapsPenalty
- }
- }
- }
- pw.println()
- pw.println("TOTALS")
- pw.println(
- " ${"".padEnd(24)}\t${toKb(totals.smallIcon)}" +
- "\t${toKb(totals.largeIcon)}\t${toKb(totals.style)}" +
- "\t${toKb(totals.customViews)}\t${toKb(totals.softwareBitmapsPenalty)}"
- )
- pw.println()
- }
-
- private fun toKb(bytes: Int): String {
- return (bytes / 1024).toString() + " KB"
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotificationMemoryViewWalker.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotificationMemoryViewWalker.kt
index a0bee15..2d04211 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotificationMemoryViewWalker.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotificationMemoryViewWalker.kt
@@ -50,7 +50,11 @@
/**
* Returns memory usage of public and private views contained in passed
- * [ExpandableNotificationRow]
+ * [ExpandableNotificationRow]. Each entry will correspond to one of the [ViewType] values with
+ * [ViewType.TOTAL] totalling all memory use. If a type of view is missing, the corresponding
+ * entry will not appear in resulting list.
+ *
+ * This will return an empty list if the ExpandableNotificationRow has no views inflated.
*/
fun getViewUsage(row: ExpandableNotificationRow?): List<NotificationViewUsage> {
if (row == null) {
@@ -58,42 +62,72 @@
}
// The ordering here is significant since it determines deduplication of seen drawables.
- return listOf(
- getViewUsage(ViewType.PRIVATE_EXPANDED_VIEW, row.privateLayout?.expandedChild),
- getViewUsage(ViewType.PRIVATE_CONTRACTED_VIEW, row.privateLayout?.contractedChild),
- getViewUsage(ViewType.PRIVATE_HEADS_UP_VIEW, row.privateLayout?.headsUpChild),
- getViewUsage(ViewType.PUBLIC_VIEW, row.publicLayout),
- getTotalUsage(row)
- )
+ val perViewUsages =
+ listOf(
+ getViewUsage(ViewType.PRIVATE_EXPANDED_VIEW, row.privateLayout?.expandedChild),
+ getViewUsage(
+ ViewType.PRIVATE_CONTRACTED_VIEW,
+ row.privateLayout?.contractedChild
+ ),
+ getViewUsage(ViewType.PRIVATE_HEADS_UP_VIEW, row.privateLayout?.headsUpChild),
+ getViewUsage(
+ ViewType.PUBLIC_VIEW,
+ row.publicLayout?.expandedChild,
+ row.publicLayout?.contractedChild,
+ row.publicLayout?.headsUpChild
+ ),
+ )
+ .filterNotNull()
+
+ return if (perViewUsages.isNotEmpty()) {
+ // Attach summed totals field only if there was any view actually measured.
+ // This reduces bug report noise and makes checks for collapsed views easier.
+ val totals = getTotalUsage(row)
+ if (totals == null) {
+ perViewUsages
+ } else {
+ perViewUsages + totals
+ }
+ } else {
+ listOf()
+ }
}
/**
* Calculate total usage of all views - we need to do a separate traversal to make sure we don't
* double count fields.
*/
- private fun getTotalUsage(row: ExpandableNotificationRow): NotificationViewUsage {
- val totalUsage = UsageBuilder()
+ private fun getTotalUsage(row: ExpandableNotificationRow): NotificationViewUsage? {
val seenObjects = hashSetOf<Int>()
-
- row.publicLayout?.let { computeViewHierarchyUse(it, totalUsage, seenObjects) }
- row.privateLayout?.let { child ->
- for (view in listOf(child.expandedChild, child.contractedChild, child.headsUpChild)) {
- (view as? ViewGroup)?.let { v ->
- computeViewHierarchyUse(v, totalUsage, seenObjects)
- }
- }
- }
- return totalUsage.build(ViewType.TOTAL)
+ return getViewUsage(
+ ViewType.TOTAL,
+ row.privateLayout?.expandedChild,
+ row.privateLayout?.contractedChild,
+ row.privateLayout?.headsUpChild,
+ row.publicLayout?.expandedChild,
+ row.publicLayout?.contractedChild,
+ row.publicLayout?.headsUpChild,
+ seenObjects = seenObjects
+ )
}
private fun getViewUsage(
type: ViewType,
- rootView: View?,
+ vararg rootViews: View?,
seenObjects: HashSet<Int> = hashSetOf()
- ): NotificationViewUsage {
- val usageBuilder = UsageBuilder()
- (rootView as? ViewGroup)?.let { computeViewHierarchyUse(it, usageBuilder, seenObjects) }
- return usageBuilder.build(type)
+ ): NotificationViewUsage? {
+ val usageBuilder = lazy { UsageBuilder() }
+ rootViews.forEach { rootView ->
+ (rootView as? ViewGroup)?.let { rootViewGroup ->
+ computeViewHierarchyUse(rootViewGroup, usageBuilder.value, seenObjects)
+ }
+ }
+
+ return if (usageBuilder.isInitialized()) {
+ usageBuilder.value.build(type)
+ } else {
+ null
+ }
}
private fun computeViewHierarchyUse(
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 c7c1634..44a231d 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
@@ -976,10 +976,10 @@
/**
* Updates states of all children.
*/
- public void updateChildrenStates(AmbientState ambientState) {
+ public void updateChildrenStates() {
if (mIsSummaryWithChildren) {
ExpandableViewState parentState = getViewState();
- mChildrenContainer.updateState(parentState, ambientState);
+ mChildrenContainer.updateState(parentState);
}
}
@@ -2223,6 +2223,7 @@
if (mNotificationParent != null) {
mNotificationParent.setClipTopAmount(0);
}
+ setTranslationX(0);
return;
}
@@ -2241,6 +2242,7 @@
setTranslationZ(translationZ);
float extraWidthForClipping = params.getWidth() - getWidth();
setExtraWidthForClipping(extraWidthForClipping);
+
int top;
if (params.getStartRoundedTopClipping() > 0) {
// If we were clipping initially, let's interpolate from the start position to the
@@ -2248,20 +2250,22 @@
float expandProgress = Interpolators.FAST_OUT_SLOW_IN.getInterpolation(
params.getProgress(0,
NotificationLaunchAnimatorController.ANIMATION_DURATION_TOP_ROUNDING));
- float startTop = params.getStartNotificationTop();
- top = (int) Math.min(MathUtils.lerp(startTop,
- params.getTop(), expandProgress),
+ int startTop = params.getStartNotificationTop();
+ top = (int) Math.min(MathUtils.lerp(startTop, params.getTop(), expandProgress),
startTop);
} else {
top = params.getTop();
}
int actualHeight = params.getBottom() - top;
setActualHeight(actualHeight);
+
+ int notificationStackTop = params.getNotificationParentTop();
+ top -= notificationStackTop;
int startClipTopAmount = params.getStartClipTopAmount();
int clipTopAmount = (int) MathUtils.lerp(startClipTopAmount, 0, params.getProgress());
if (mNotificationParent != null) {
- float parentY = mNotificationParent.getTranslationY();
- top -= parentY;
+ float parentTranslationY = mNotificationParent.getTranslationY();
+ top -= parentTranslationY;
mNotificationParent.setTranslationZ(translationZ);
// When the expanding notification is below its parent, the parent must be clipped
@@ -2270,15 +2274,14 @@
// pixels to show the expanding notification, while still taking the decreasing
// notification clipTopAmount into consideration, so 'top + clipTopAmount'.
int parentStartClipTopAmount = params.getParentStartClipTopAmount();
- int parentClipTopAmount = Math.min(parentStartClipTopAmount,
- top + clipTopAmount);
+ int parentClipTopAmount = Math.min(parentStartClipTopAmount, top + clipTopAmount);
mNotificationParent.setClipTopAmount(parentClipTopAmount);
mNotificationParent.setExtraWidthForClipping(extraWidthForClipping);
- float clipBottom = Math.max(params.getBottom(),
- parentY + mNotificationParent.getActualHeight()
+ float clipBottom = Math.max(params.getBottom() - notificationStackTop,
+ parentTranslationY + mNotificationParent.getActualHeight()
- mNotificationParent.getClipBottomAmount());
- float clipTop = Math.min(params.getTop(), parentY);
+ float clipTop = Math.min(params.getTop() - notificationStackTop, parentTranslationY);
int minimumHeightForClipping = (int) (clipBottom - clipTop);
mNotificationParent.setMinimumHeightForClipping(minimumHeightForClipping);
} else if (startClipTopAmount != 0) {
@@ -2286,6 +2289,9 @@
}
setTranslationY(top);
+ float absoluteCenterX = getLocationOnScreen()[0] + getWidth() / 2f - getTranslationX();
+ setTranslationX(params.getCenterX() - absoluteCenterX);
+
final float maxRadius = getMaxRadius();
mTopRoundnessDuringLaunchAnimation = params.getTopCornerRadius() / maxRadius;
mBottomRoundnessDuringLaunchAnimation = params.getBottomCornerRadius() / maxRadius;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableOutlineView.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableOutlineView.java
index 0213b96..2041245 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableOutlineView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableOutlineView.java
@@ -214,7 +214,11 @@
} else {
maxRadius = res.getDimensionPixelSize(R.dimen.notification_corner_radius);
}
- mRoundableState = new RoundableState(this, this, maxRadius);
+ if (mRoundableState == null) {
+ mRoundableState = new RoundableState(this, this, maxRadius);
+ } else {
+ mRoundableState.setMaxRadius(maxRadius);
+ }
setClipToOutline(mAlwaysRoundBothCorners);
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationChildrenContainer.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationChildrenContainer.java
index 4a8e2db..4d1451e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationChildrenContainer.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationChildrenContainer.java
@@ -616,9 +616,8 @@
* Update the state of all its children based on a linear layout algorithm.
*
* @param parentState the state of the parent
- * @param ambientState the ambient state containing ambient information
*/
- public void updateState(ExpandableViewState parentState, AmbientState ambientState) {
+ public void updateState(ExpandableViewState parentState) {
int childCount = mAttachedChildren.size();
int yPosition = mNotificationHeaderMargin + mCurrentHeaderTranslation;
boolean firstChild = true;
@@ -661,9 +660,17 @@
childState.height = intrinsicHeight;
childState.setYTranslation(yPosition + launchTransitionCompensation);
childState.hidden = false;
- // When the group is expanded, the children cast the shadows rather than the parent
- // so use the parent's elevation here.
- if (childrenExpandedAndNotAnimating && mEnableShadowOnChildNotifications) {
+ if (child.isExpandAnimationRunning() || mContainingNotification.hasExpandingChild()) {
+ // Not modifying translationZ during launch animation. The translationZ of the
+ // expanding child is handled inside ExpandableNotificationRow and the translationZ
+ // of the other children inside the group should remain unchanged. In particular,
+ // they should not take over the translationZ of the parent, since the parent has
+ // a positive translationZ set only for the expanding child to be drawn above other
+ // notifications.
+ childState.setZTranslation(child.getTranslationZ());
+ } else if (childrenExpandedAndNotAnimating && mEnableShadowOnChildNotifications) {
+ // When the group is expanded, the children cast the shadows rather than the parent
+ // so use the parent's elevation here.
childState.setZTranslation(parentState.getZTranslation());
} else {
childState.setZTranslation(0);
@@ -716,9 +723,15 @@
mHeaderViewState = new ViewState();
}
mHeaderViewState.initFrom(mNotificationHeader);
- mHeaderViewState.setZTranslation(childrenExpandedAndNotAnimating
- ? parentState.getZTranslation()
- : 0);
+
+ if (mContainingNotification.hasExpandingChild()) {
+ // Not modifying translationZ during expand animation.
+ mHeaderViewState.setZTranslation(mNotificationHeader.getTranslationZ());
+ } else if (childrenExpandedAndNotAnimating) {
+ mHeaderViewState.setZTranslation(parentState.getZTranslation());
+ } else {
+ mHeaderViewState.setZTranslation(0);
+ }
mHeaderViewState.setYTranslation(mCurrentHeaderTranslation);
mHeaderViewState.setAlpha(mHeaderVisibleAmount);
// The hiding is done automatically by the alpha, otherwise we'll pick it up again
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
index 21e2bd8..d22bfe8 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
@@ -5772,14 +5772,20 @@
|| mExpandingNotificationRow == null) {
return;
}
- int left = Math.min(mLaunchAnimationParams.getLeft(), mRoundedRectClippingLeft);
- int right = Math.max(mLaunchAnimationParams.getRight(), mRoundedRectClippingRight);
- int bottom = Math.max(mLaunchAnimationParams.getBottom(), mRoundedRectClippingBottom);
+ int[] absoluteCoords = new int[2];
+ getLocationOnScreen(absoluteCoords);
+
+ int left = Math.min(mLaunchAnimationParams.getLeft() - absoluteCoords[0],
+ mRoundedRectClippingLeft);
+ int right = Math.max(mLaunchAnimationParams.getRight() - absoluteCoords[0],
+ mRoundedRectClippingRight);
+ int bottom = Math.max(mLaunchAnimationParams.getBottom() - absoluteCoords[1],
+ mRoundedRectClippingBottom);
float expandProgress = Interpolators.FAST_OUT_SLOW_IN.getInterpolation(
mLaunchAnimationParams.getProgress(0,
NotificationLaunchAnimatorController.ANIMATION_DURATION_TOP_ROUNDING));
int top = (int) Math.min(MathUtils.lerp(mRoundedRectClippingTop,
- mLaunchAnimationParams.getTop(), expandProgress),
+ mLaunchAnimationParams.getTop() - absoluteCoords[1], expandProgress),
mRoundedRectClippingTop);
float topRadius = mLaunchAnimationParams.getTopCornerRadius();
float bottomRadius = mLaunchAnimationParams.getBottomCornerRadius();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithm.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithm.java
index aff7b4c..6e63960 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithm.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithm.java
@@ -128,7 +128,7 @@
updateSpeedBumpState(algorithmState, speedBumpIndex);
updateShelfState(algorithmState, ambientState);
updateAlphaState(algorithmState, ambientState);
- getNotificationChildrenStates(algorithmState, ambientState);
+ getNotificationChildrenStates(algorithmState);
}
private void updateAlphaState(StackScrollAlgorithmState algorithmState,
@@ -231,14 +231,13 @@
}
}
- private void getNotificationChildrenStates(StackScrollAlgorithmState algorithmState,
- AmbientState ambientState) {
+ private void getNotificationChildrenStates(StackScrollAlgorithmState algorithmState) {
int childCount = algorithmState.visibleChildren.size();
for (int i = 0; i < childCount; i++) {
ExpandableView v = algorithmState.visibleChildren.get(i);
if (v instanceof ExpandableNotificationRow) {
ExpandableNotificationRow row = (ExpandableNotificationRow) v;
- row.updateChildrenStates(ambientState);
+ row.updateChildrenStates();
}
}
}
@@ -871,8 +870,7 @@
}
for (int i = childCount - 1; i >= 0; i--) {
- childrenOnTop = updateChildZValue(i, childrenOnTop,
- algorithmState, ambientState, i == topHunIndex);
+ updateChildZValue(i, algorithmState, ambientState, i == topHunIndex);
}
}
@@ -882,15 +880,11 @@
*
* @param isTopHun Whether the child is a top HUN. A top HUN means a HUN that shows on the
* vertically top of screen. Top HUNs should have drop shadows
- * @param childrenOnTop It is greater than 0 when there's an existing HUN that is elevated
- * @return childrenOnTop The decimal part represents the fraction of the elevated HUN's height
- * that overlaps with QQS Panel. The integer part represents the count of
- * previous HUNs whose Z positions are greater than 0.
*/
- protected float updateChildZValue(int i, float childrenOnTop,
- StackScrollAlgorithmState algorithmState,
- AmbientState ambientState,
- boolean isTopHun) {
+ protected void updateChildZValue(int i,
+ StackScrollAlgorithmState algorithmState,
+ AmbientState ambientState,
+ boolean isTopHun) {
ExpandableView child = algorithmState.visibleChildren.get(i);
ExpandableViewState childViewState = child.getViewState();
float baseZ = ambientState.getBaseZHeight();
@@ -904,22 +898,16 @@
// Handles HUN shadow when Shade is opened, and AmbientState.mScrollY > 0
// Calculate the HUN's z-value based on its overlapping fraction with QQS Panel.
// When scrolling down shade to make HUN back to in-position in Notification Panel,
- // The over-lapping fraction goes to 0, and shadows hides gradually.
- if (childrenOnTop != 0.0f) {
- // To elevate the later HUN over previous HUN
- childrenOnTop++;
- } else {
- float overlap = ambientState.getTopPadding()
- + ambientState.getStackTranslation() - childViewState.getYTranslation();
- // To prevent over-shadow during HUN entry
- childrenOnTop += Math.min(
- 1.0f,
- overlap / childViewState.height
- );
- MathUtils.saturate(childrenOnTop);
+ // the overlapFraction goes to 0, and the pinned HUN's shadows hides gradually.
+ float overlap = ambientState.getTopPadding()
+ + ambientState.getStackTranslation() - childViewState.getYTranslation();
+
+ if (childViewState.height > 0) { // To avoid 0/0 problems
+ // To prevent over-shadow
+ float overlapFraction = MathUtils.saturate(overlap / childViewState.height);
+ childViewState.setZTranslation(baseZ
+ + overlapFraction * mPinnedZTranslationExtra);
}
- childViewState.setZTranslation(baseZ
- + childrenOnTop * mPinnedZTranslationExtra);
} else if (isTopHun) {
// In case this is a new view that has never been measured before, we don't want to
// elevate if we are currently expanded more than the notification
@@ -947,15 +935,14 @@
}
// Handles HUN shadow when shade is closed.
- // While HUN is showing and Shade is closed: headerVisibleAmount stays 0, shadow stays.
+ // While shade is closed, and during HUN's entry: headerVisibleAmount stays 0, shadow stays.
+ // While shade is closed, and HUN is showing: headerVisibleAmount stays 0, shadow stays.
// During HUN-to-Shade (eg. dragging down HUN to open Shade): headerVisibleAmount goes
// gradually from 0 to 1, shadow hides gradually.
// Header visibility is a deprecated concept, we are using headerVisibleAmount only because
// this value nicely goes from 0 to 1 during the HUN-to-Shade process.
-
childViewState.setZTranslation(childViewState.getZTranslation()
+ (1.0f - child.getHeaderVisibleAmount()) * mPinnedZTranslationExtra);
- return childrenOnTop;
}
public void setIsExpanded(boolean isExpanded) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesCommandQueueCallbacks.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesCommandQueueCallbacks.java
index 72ada0e..2dad8e0 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesCommandQueueCallbacks.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesCommandQueueCallbacks.java
@@ -371,6 +371,7 @@
if (!mKeyguardStateController.isShowing()) {
final Intent cameraIntent = CameraIntents.getInsecureCameraIntent(mContext);
+ cameraIntent.putExtra(CameraIntents.EXTRA_LAUNCH_SOURCE, source);
mCentralSurfaces.startActivityDismissingKeyguard(cameraIntent,
false /* onlyProvisioned */, true /* dismissShade */,
true /* disallowEnterPictureInPictureWhileLaunching */, null /* callback */, 0,
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 31cdb05..93da0b8 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
@@ -16,6 +16,7 @@
package com.android.systemui.statusbar.phone;
+import static android.app.StatusBarManager.DISABLE_HOME;
import static android.app.StatusBarManager.WINDOW_STATE_HIDDEN;
import static android.app.StatusBarManager.WINDOW_STATE_SHOWING;
import static android.app.StatusBarManager.WindowVisibleState;
@@ -70,6 +71,7 @@
import android.hardware.devicestate.DeviceStateManager;
import android.metrics.LogMaker;
import android.net.Uri;
+import android.os.Binder;
import android.os.Bundle;
import android.os.Handler;
import android.os.Looper;
@@ -1047,8 +1049,21 @@
// set the initial view visibility
int disabledFlags1 = result.mDisabledFlags1;
int disabledFlags2 = result.mDisabledFlags2;
- mInitController.addPostInitTask(
- () -> setUpDisableFlags(disabledFlags1, disabledFlags2));
+ mInitController.addPostInitTask(() -> {
+ setUpDisableFlags(disabledFlags1, disabledFlags2);
+ try {
+ // NOTE(b/262059863): Force-update the disable flags after applying the flags
+ // returned from registerStatusBar(). The result's disabled flags may be stale
+ // if StatusBarManager's disabled flags are updated between registering the bar and
+ // this handling this post-init task. We force an update in this case, and use a new
+ // token to not conflict with any other disabled flags already requested by SysUI
+ Binder token = new Binder();
+ mBarService.disable(DISABLE_HOME, token, mContext.getPackageName());
+ mBarService.disable(0, token, mContext.getPackageName());
+ } catch (RemoteException ex) {
+ ex.rethrowFromSystemServer();
+ }
+ });
mFalsingManager.addFalsingBeliefListener(mFalsingBeliefListener);
@@ -3559,7 +3574,7 @@
final ScreenLifecycle.Observer mScreenObserver = new ScreenLifecycle.Observer() {
@Override
- public void onScreenTurningOn(Runnable onDrawn) {
+ public void onScreenTurningOn() {
mFalsingCollector.onScreenTurningOn();
mNotificationPanelViewController.onScreenTurningOn();
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBottomAreaView.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBottomAreaView.kt
index 78b28d2..2ce1163 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBottomAreaView.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBottomAreaView.kt
@@ -23,7 +23,7 @@
import android.view.ViewPropertyAnimator
import android.view.WindowInsets
import android.widget.FrameLayout
-import com.android.keyguard.KeyguardUpdateMonitor
+import androidx.annotation.StringRes
import com.android.keyguard.LockIconViewController
import com.android.systemui.R
import com.android.systemui.keyguard.ui.binder.KeyguardBottomAreaViewBinder
@@ -51,21 +51,29 @@
defStyleRes,
) {
+ interface MessageDisplayer {
+ fun display(@StringRes stringResourceId: Int)
+ }
+
private var ambientIndicationArea: View? = null
private lateinit var binding: KeyguardBottomAreaViewBinder.Binding
- private lateinit var lockIconViewController: LockIconViewController
+ private var lockIconViewController: LockIconViewController? = null
/** Initializes the view. */
fun init(
viewModel: KeyguardBottomAreaViewModel,
- falsingManager: FalsingManager,
- lockIconViewController: LockIconViewController,
+ falsingManager: FalsingManager? = null,
+ lockIconViewController: LockIconViewController? = null,
+ messageDisplayer: MessageDisplayer? = null,
) {
- binding = bind(
+ binding =
+ bind(
this,
viewModel,
falsingManager,
- )
+ ) {
+ messageDisplayer?.display(it)
+ }
this.lockIconViewController = lockIconViewController
}
@@ -129,21 +137,21 @@
findViewById<View>(R.id.ambient_indication_container)?.let {
val (ambientLeft, ambientTop) = it.locationOnScreen
if (binding.shouldConstrainToTopOfLockIcon()) {
- //make top of ambient indication view the bottom of the lock icon
+ // make top of ambient indication view the bottom of the lock icon
it.layout(
- ambientLeft,
- lockIconViewController.bottom.toInt(),
- right - ambientLeft,
- ambientTop + it.measuredHeight
+ ambientLeft,
+ lockIconViewController?.bottom?.toInt() ?: 0,
+ right - ambientLeft,
+ ambientTop + it.measuredHeight
)
} else {
- //make bottom of ambient indication view the top of the lock icon
- val lockLocationTop = lockIconViewController.top
+ // make bottom of ambient indication view the top of the lock icon
+ val lockLocationTop = lockIconViewController?.top ?: 0
it.layout(
- ambientLeft,
- lockLocationTop.toInt() - it.measuredHeight,
- right - ambientLeft,
- lockLocationTop.toInt()
+ ambientLeft,
+ lockLocationTop.toInt() - it.measuredHeight,
+ right - ambientLeft,
+ lockLocationTop.toInt()
)
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/stylus/OWNERS b/packages/SystemUI/src/com/android/systemui/stylus/OWNERS
new file mode 100644
index 0000000..7ccb316
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/stylus/OWNERS
@@ -0,0 +1,8 @@
+# Bug component: 1254381
+azappone@google.com
+achalke@google.com
+juliacr@google.com
+madym@google.com
+mgalhardo@google.com
+petrcermak@google.com
+vanjan@google.com
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/stylus/StylusUsiPowerStartable.kt b/packages/SystemUI/src/com/android/systemui/stylus/StylusUsiPowerStartable.kt
new file mode 100644
index 0000000..11233dd
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/stylus/StylusUsiPowerStartable.kt
@@ -0,0 +1,118 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.stylus
+
+import android.hardware.BatteryState
+import android.hardware.input.InputManager
+import android.util.Log
+import android.view.InputDevice
+import com.android.systemui.CoreStartable
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.flags.FeatureFlags
+import com.android.systemui.flags.Flags
+import java.util.concurrent.Executor
+import javax.inject.Inject
+
+/**
+ * A [CoreStartable] that listens to USI stylus battery events, to manage the [StylusUsiPowerUI]
+ * notification controller.
+ */
+@SysUISingleton
+class StylusUsiPowerStartable
+@Inject
+constructor(
+ private val stylusManager: StylusManager,
+ private val inputManager: InputManager,
+ private val stylusUsiPowerUi: StylusUsiPowerUI,
+ private val featureFlags: FeatureFlags,
+ @Background private val executor: Executor,
+) : CoreStartable, StylusManager.StylusCallback, InputManager.InputDeviceBatteryListener {
+
+ override fun onStylusAdded(deviceId: Int) {
+ val device = inputManager.getInputDevice(deviceId) ?: return
+
+ if (!device.isExternal) {
+ registerBatteryListener(deviceId)
+ }
+ }
+
+ override fun onStylusBluetoothConnected(deviceId: Int, btAddress: String) {
+ stylusUsiPowerUi.refresh()
+ }
+
+ override fun onStylusBluetoothDisconnected(deviceId: Int, btAddress: String) {
+ stylusUsiPowerUi.refresh()
+ }
+
+ override fun onStylusRemoved(deviceId: Int) {
+ val device = inputManager.getInputDevice(deviceId) ?: return
+
+ if (!device.isExternal) {
+ unregisterBatteryListener(deviceId)
+ }
+ }
+
+ override fun onBatteryStateChanged(
+ deviceId: Int,
+ eventTimeMillis: Long,
+ batteryState: BatteryState
+ ) {
+ if (batteryState.isPresent) {
+ stylusUsiPowerUi.updateBatteryState(batteryState)
+ }
+ }
+
+ private fun registerBatteryListener(deviceId: Int) {
+ try {
+ inputManager.addInputDeviceBatteryListener(deviceId, executor, this)
+ } catch (e: SecurityException) {
+ Log.e(TAG, "$e: Failed to register battery listener for $deviceId.")
+ }
+ }
+
+ private fun unregisterBatteryListener(deviceId: Int) {
+ try {
+ inputManager.removeInputDeviceBatteryListener(deviceId, this)
+ } catch (e: SecurityException) {
+ Log.e(TAG, "$e: Failed to unregister battery listener for $deviceId.")
+ }
+ }
+
+ override fun start() {
+ if (!featureFlags.isEnabled(Flags.ENABLE_USI_BATTERY_NOTIFICATIONS)) return
+ addBatteryListenerForInternalStyluses()
+
+ stylusManager.registerCallback(this)
+ stylusManager.startListener()
+ }
+
+ private fun addBatteryListenerForInternalStyluses() {
+ // For most devices, an active stylus is represented by an internal InputDevice.
+ // This InputDevice will be present in InputManager before CoreStartables run,
+ // and will not be removed. In many cases, it reports the battery level of the stylus.
+ inputManager.inputDeviceIds
+ .asSequence()
+ .mapNotNull { inputManager.getInputDevice(it) }
+ .filter { it.supportsSource(InputDevice.SOURCE_STYLUS) }
+ .forEach { onStylusAdded(it.id) }
+ }
+
+ companion object {
+ private val TAG = StylusUsiPowerStartable::class.simpleName.orEmpty()
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/stylus/StylusUsiPowerUI.kt b/packages/SystemUI/src/com/android/systemui/stylus/StylusUsiPowerUI.kt
new file mode 100644
index 0000000..70a5b36
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/stylus/StylusUsiPowerUI.kt
@@ -0,0 +1,185 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.stylus
+
+import android.Manifest
+import android.app.PendingIntent
+import android.content.BroadcastReceiver
+import android.content.Context
+import android.content.Intent
+import android.content.IntentFilter
+import android.hardware.BatteryState
+import android.hardware.input.InputManager
+import android.os.Handler
+import android.os.UserHandle
+import android.view.InputDevice
+import androidx.core.app.NotificationCompat
+import androidx.core.app.NotificationManagerCompat
+import com.android.systemui.R
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.util.NotificationChannels
+import java.text.NumberFormat
+import javax.inject.Inject
+
+/**
+ * UI controller for the notification that shows when a USI stylus battery is low. The
+ * [StylusUsiPowerStartable], which listens to battery events, uses this controller.
+ */
+@SysUISingleton
+class StylusUsiPowerUI
+@Inject
+constructor(
+ private val context: Context,
+ private val notificationManager: NotificationManagerCompat,
+ private val inputManager: InputManager,
+ @Background private val handler: Handler,
+) {
+
+ // These values must only be accessed on the handler.
+ private var batteryCapacity = 1.0f
+ private var suppressed = false
+
+ fun init() {
+ val filter =
+ IntentFilter().also {
+ it.addAction(ACTION_DISMISSED_LOW_BATTERY)
+ it.addAction(ACTION_CLICKED_LOW_BATTERY)
+ }
+
+ context.registerReceiverAsUser(
+ receiver,
+ UserHandle.ALL,
+ filter,
+ Manifest.permission.DEVICE_POWER,
+ handler,
+ Context.RECEIVER_NOT_EXPORTED,
+ )
+ }
+
+ fun refresh() {
+ handler.post refreshNotification@{
+ if (!suppressed && !hasConnectedBluetoothStylus() && isBatteryBelowThreshold()) {
+ showOrUpdateNotification()
+ return@refreshNotification
+ }
+
+ if (!isBatteryBelowThreshold()) {
+ // Reset suppression when stylus battery is recharged, so that the next time
+ // it reaches a low battery, the notification will show again.
+ suppressed = false
+ }
+ hideNotification()
+ }
+ }
+
+ fun updateBatteryState(batteryState: BatteryState) {
+ handler.post updateBattery@{
+ if (batteryState.capacity == batteryCapacity) return@updateBattery
+
+ batteryCapacity = batteryState.capacity
+ refresh()
+ }
+ }
+
+ /**
+ * Suppression happens when the notification is dismissed by the user. This is to prevent
+ * further battery events with capacities below the threshold from reopening the suppressed
+ * notification.
+ *
+ * Suppression can only be removed when the battery has been recharged - thus restarting the
+ * notification cycle (i.e. next low battery event, notification should show).
+ */
+ fun updateSuppression(suppress: Boolean) {
+ handler.post updateSuppressed@{
+ if (suppressed == suppress) return@updateSuppressed
+
+ suppressed = suppress
+ refresh()
+ }
+ }
+
+ private fun hideNotification() {
+ notificationManager.cancel(USI_NOTIFICATION_ID)
+ }
+
+ private fun showOrUpdateNotification() {
+ val notification =
+ NotificationCompat.Builder(context, NotificationChannels.BATTERY)
+ .setSmallIcon(R.drawable.ic_power_low)
+ .setDeleteIntent(getPendingBroadcast(ACTION_DISMISSED_LOW_BATTERY))
+ .setContentIntent(getPendingBroadcast(ACTION_CLICKED_LOW_BATTERY))
+ .setContentTitle(context.getString(R.string.stylus_battery_low))
+ .setContentText(
+ context.getString(
+ R.string.battery_low_percent_format,
+ NumberFormat.getPercentInstance().format(batteryCapacity)
+ )
+ )
+ .setPriority(NotificationCompat.PRIORITY_DEFAULT)
+ .setLocalOnly(true)
+ .setAutoCancel(true)
+ .build()
+
+ notificationManager.notify(USI_NOTIFICATION_ID, notification)
+ }
+
+ private fun isBatteryBelowThreshold(): Boolean {
+ return batteryCapacity <= LOW_BATTERY_THRESHOLD
+ }
+
+ private fun hasConnectedBluetoothStylus(): Boolean {
+ // TODO(b/257936830): get bt address once input api available
+ return inputManager.inputDeviceIds.any { deviceId ->
+ inputManager.getInputDevice(deviceId).supportsSource(InputDevice.SOURCE_STYLUS)
+ }
+ }
+
+ private fun getPendingBroadcast(action: String): PendingIntent? {
+ return PendingIntent.getBroadcastAsUser(
+ context,
+ 0,
+ Intent(action),
+ PendingIntent.FLAG_IMMUTABLE,
+ UserHandle.CURRENT
+ )
+ }
+
+ private val receiver: BroadcastReceiver =
+ object : BroadcastReceiver() {
+ override fun onReceive(context: Context, intent: Intent) {
+ when (intent.action) {
+ ACTION_DISMISSED_LOW_BATTERY -> updateSuppression(true)
+ ACTION_CLICKED_LOW_BATTERY -> {
+ updateSuppression(true)
+ // TODO(b/261584943): open USI device details page
+ }
+ }
+ }
+ }
+
+ companion object {
+ // Low battery threshold matches CrOS, see:
+ // https://source.chromium.org/chromium/chromium/src/+/main:ash/system/power/peripheral_battery_notifier.cc;l=41
+ private const val LOW_BATTERY_THRESHOLD = 0.16f
+
+ private val USI_NOTIFICATION_ID = R.string.stylus_battery_low
+
+ private const val ACTION_DISMISSED_LOW_BATTERY = "StylusUsiPowerUI.dismiss"
+ private const val ACTION_CLICKED_LOW_BATTERY = "StylusUsiPowerUI.click"
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/temporarydisplay/TemporaryViewDisplayController.kt b/packages/SystemUI/src/com/android/systemui/temporarydisplay/TemporaryViewDisplayController.kt
index ea40208..db7315f 100644
--- a/packages/SystemUI/src/com/android/systemui/temporarydisplay/TemporaryViewDisplayController.kt
+++ b/packages/SystemUI/src/com/android/systemui/temporarydisplay/TemporaryViewDisplayController.kt
@@ -34,6 +34,7 @@
import com.android.systemui.dagger.qualifiers.Main
import com.android.systemui.statusbar.policy.ConfigurationController
import com.android.systemui.util.concurrency.DelayableExecutor
+import com.android.systemui.util.time.SystemClock
import com.android.systemui.util.wakelock.WakeLock
/**
@@ -44,8 +45,24 @@
*
* The generic type T is expected to contain all the information necessary for the subclasses to
* display the view in a certain state, since they receive <T> in [updateView].
+ *
+ * Some information about display ordering:
+ *
+ * [ViewPriority] defines different priorities for the incoming views. The incoming view will be
+ * displayed so long as its priority is equal to or greater than the currently displayed view.
+ * (Concretely, this means that a [ViewPriority.NORMAL] won't be displayed if a
+ * [ViewPriority.CRITICAL] is currently displayed. But otherwise, the incoming view will get
+ * displayed and kick out the old view).
+ *
+ * Once the currently displayed view times out, we *may* display a previously requested view if it
+ * still has enough time left before its own timeout. The same priority ordering applies.
+ *
+ * Note: [TemporaryViewInfo.id] is the identifier that we use to determine if a call to
+ * [displayView] will just update the current view with new information, or display a completely new
+ * view. This means that you *cannot* change the [TemporaryViewInfo.priority] or
+ * [TemporaryViewInfo.windowTitle] while using the same ID.
*/
-abstract class TemporaryViewDisplayController<T : TemporaryViewInfo, U : TemporaryViewLogger>(
+abstract class TemporaryViewDisplayController<T : TemporaryViewInfo, U : TemporaryViewLogger<T>>(
internal val context: Context,
internal val logger: U,
internal val windowManager: WindowManager,
@@ -55,6 +72,7 @@
private val powerManager: PowerManager,
@LayoutRes private val viewLayoutRes: Int,
private val wakeLockBuilder: WakeLock.Builder,
+ private val systemClock: SystemClock,
) : CoreStartable {
/**
* Window layout params that will be used as a starting point for the [windowLayoutParams] of
@@ -78,27 +96,18 @@
*/
internal abstract val windowLayoutParams: WindowManager.LayoutParams
- /** A container for all the display-related objects. Null if the view is not being displayed. */
- private var displayInfo: DisplayInfo? = null
-
- /** A [Runnable] that, when run, will cancel the pending timeout of the view. */
- private var cancelViewTimeout: Runnable? = null
-
/**
- * A wakelock that is acquired when view is displayed and screen off,
- * then released when view is removed.
+ * A list of the currently active views, ordered from highest priority in the beginning to
+ * lowest priority at the end.
+ *
+ * Whenever the current view disappears, the next-priority view will be displayed if it's still
+ * valid.
*/
- private var wakeLock: WakeLock? = null
+ internal val activeViews: MutableList<DisplayInfo> = mutableListOf()
- /** A string that keeps track of wakelock reason once it is acquired till it gets released */
- private var wakeReasonAcquired: String? = null
-
- /**
- * A stack of pairs of device id and temporary view info. This is used when there may be
- * multiple devices in range, and we want to always display the chip for the most recently
- * active device.
- */
- internal val activeViews: ArrayDeque<Pair<String, T>> = ArrayDeque()
+ private fun getCurrentDisplayInfo(): DisplayInfo? {
+ return activeViews.getOrNull(0)
+ }
/**
* Displays the view with the provided [newInfo].
@@ -107,94 +116,139 @@
* display the correct information in the view.
* @param onViewTimeout a runnable that runs after the view timeout.
*/
+ @Synchronized
fun displayView(newInfo: T, onViewTimeout: Runnable? = null) {
- val currentDisplayInfo = displayInfo
-
- // Update our list of active devices by removing it if necessary, then adding back at the
- // front of the list
- val id = newInfo.id
- val position = findAndRemoveFromActiveViewsList(id)
- activeViews.addFirst(Pair(id, newInfo))
-
- if (currentDisplayInfo != null &&
- currentDisplayInfo.info.windowTitle == newInfo.windowTitle) {
- // We're already displaying information in the correctly-titled window, so we just need
- // to update the view.
- currentDisplayInfo.info = newInfo
- updateView(currentDisplayInfo.info, currentDisplayInfo.view)
- } else {
- if (currentDisplayInfo != null) {
- // We're already displaying information but that information is under a different
- // window title. So, we need to remove the old window with the old title and add a
- // new window with the new title.
- removeView(
- id,
- removalReason = "New info has new window title: ${newInfo.windowTitle}"
- )
- }
-
- // At this point, we're guaranteed to no longer be displaying a view.
- // So, set up all our callbacks and inflate the view.
- configurationController.addCallback(displayScaleListener)
-
- wakeLock = if (!powerManager.isScreenOn) {
- // If the screen is off, fully wake it so the user can see the view.
- wakeLockBuilder
- .setTag(newInfo.windowTitle)
- .setLevelsAndFlags(
- PowerManager.FULL_WAKE_LOCK or
- PowerManager.ACQUIRE_CAUSES_WAKEUP
- )
- .build()
- } else {
- // Per b/239426653, we want the view to show over the dream state.
- // If the screen is on, using screen bright level will leave screen on the dream
- // state but ensure the screen will not go off before wake lock is released.
- wakeLockBuilder
- .setTag(newInfo.windowTitle)
- .setLevelsAndFlags(PowerManager.SCREEN_BRIGHT_WAKE_LOCK)
- .build()
- }
- wakeLock?.acquire(newInfo.wakeReason)
- wakeReasonAcquired = newInfo.wakeReason
- logger.logViewAddition(id, newInfo.windowTitle)
- inflateAndUpdateView(newInfo)
- }
-
- // Cancel and re-set the view timeout each time we get a new state.
val timeout = accessibilityManager.getRecommendedTimeoutMillis(
newInfo.timeoutMs,
// Not all views have controls so FLAG_CONTENT_CONTROLS might be superfluous, but
// include it just to be safe.
FLAG_CONTENT_ICONS or FLAG_CONTENT_TEXT or FLAG_CONTENT_CONTROLS
- )
+ )
+ val timeExpirationMillis = systemClock.currentTimeMillis() + timeout
- // Only cancel timeout of the most recent view displayed, as it will be reset.
- if (position == 0) {
- cancelViewTimeout?.run()
+ val currentDisplayInfo = getCurrentDisplayInfo()
+
+ // We're current displaying a chipbar with the same ID, we just need to update its info
+ if (currentDisplayInfo != null && currentDisplayInfo.info.id == newInfo.id) {
+ val view = checkNotNull(currentDisplayInfo.view) {
+ "First item in activeViews list must have a valid view"
+ }
+ logger.logViewUpdate(newInfo)
+ currentDisplayInfo.info = newInfo
+ currentDisplayInfo.timeExpirationMillis = timeExpirationMillis
+ updateTimeout(currentDisplayInfo, timeout, onViewTimeout)
+ updateView(newInfo, view)
+ return
}
- cancelViewTimeout = mainExecutor.executeDelayed(
+
+ val newDisplayInfo = DisplayInfo(
+ info = newInfo,
+ onViewTimeout = onViewTimeout,
+ timeExpirationMillis = timeExpirationMillis,
+ // Null values will be updated to non-null if/when this view actually gets displayed
+ view = null,
+ wakeLock = null,
+ cancelViewTimeout = null,
+ )
+
+ // We're not displaying anything, so just render this new info
+ if (currentDisplayInfo == null) {
+ addCallbacks()
+ activeViews.add(newDisplayInfo)
+ showNewView(newDisplayInfo, timeout)
+ return
+ }
+
+ // The currently displayed info takes higher priority than the new one.
+ // So, just store the new one in case the current one disappears.
+ if (currentDisplayInfo.info.priority > newInfo.priority) {
+ logger.logViewAdditionDelayed(newInfo)
+ // Remove any old information for this id (if it exists) and re-add it to the list in
+ // the right priority spot
+ removeFromActivesIfNeeded(newInfo.id)
+ var insertIndex = 0
+ while (insertIndex < activeViews.size &&
+ activeViews[insertIndex].info.priority > newInfo.priority) {
+ insertIndex++
+ }
+ activeViews.add(insertIndex, newDisplayInfo)
+ return
+ }
+
+ // Else: The newInfo should be displayed and the currentInfo should be hidden
+ hideView(currentDisplayInfo)
+ // Remove any old information for this id (if it exists) and put this info at the beginning
+ removeFromActivesIfNeeded(newDisplayInfo.info.id)
+ activeViews.add(0, newDisplayInfo)
+ showNewView(newDisplayInfo, timeout)
+ }
+
+ private fun showNewView(newDisplayInfo: DisplayInfo, timeout: Int) {
+ logger.logViewAddition(newDisplayInfo.info)
+ createAndAcquireWakeLock(newDisplayInfo)
+ updateTimeout(newDisplayInfo, timeout, newDisplayInfo.onViewTimeout)
+ inflateAndUpdateView(newDisplayInfo)
+ }
+
+ private fun createAndAcquireWakeLock(displayInfo: DisplayInfo) {
+ // TODO(b/262009503): Migrate off of isScrenOn, since it's deprecated.
+ val newWakeLock = if (!powerManager.isScreenOn) {
+ // If the screen is off, fully wake it so the user can see the view.
+ wakeLockBuilder
+ .setTag(displayInfo.info.windowTitle)
+ .setLevelsAndFlags(
+ PowerManager.FULL_WAKE_LOCK or
+ PowerManager.ACQUIRE_CAUSES_WAKEUP
+ )
+ .build()
+ } else {
+ // Per b/239426653, we want the view to show over the dream state.
+ // If the screen is on, using screen bright level will leave screen on the dream
+ // state but ensure the screen will not go off before wake lock is released.
+ wakeLockBuilder
+ .setTag(displayInfo.info.windowTitle)
+ .setLevelsAndFlags(PowerManager.SCREEN_BRIGHT_WAKE_LOCK)
+ .build()
+ }
+ displayInfo.wakeLock = newWakeLock
+ newWakeLock.acquire(displayInfo.info.wakeReason)
+ }
+
+ /**
+ * Creates a runnable that will remove [displayInfo] in [timeout] ms from now.
+ *
+ * @param onViewTimeout an optional runnable that will be run if the view times out.
+ * @return a runnable that, when run, will *cancel* the view's timeout.
+ */
+ private fun updateTimeout(displayInfo: DisplayInfo, timeout: Int, onViewTimeout: Runnable?) {
+ val cancelViewTimeout = mainExecutor.executeDelayed(
{
- removeView(id, REMOVAL_REASON_TIMEOUT)
+ removeView(displayInfo.info.id, REMOVAL_REASON_TIMEOUT)
onViewTimeout?.run()
},
timeout.toLong()
)
+
+ displayInfo.onViewTimeout = onViewTimeout
+ // Cancel old view timeout and re-set it.
+ displayInfo.cancelViewTimeout?.run()
+ displayInfo.cancelViewTimeout = cancelViewTimeout
}
- /** Inflates a new view, updates it with [newInfo], and adds the view to the window. */
- private fun inflateAndUpdateView(newInfo: T) {
+ /** Inflates a new view, updates it with [DisplayInfo.info], and adds the view to the window. */
+ private fun inflateAndUpdateView(displayInfo: DisplayInfo) {
+ val newInfo = displayInfo.info
val newView = LayoutInflater
.from(context)
.inflate(viewLayoutRes, null) as ViewGroup
- val newViewController = TouchableRegionViewController(newView, this::getTouchableRegion)
- newViewController.init()
+ displayInfo.view = newView
// We don't need to hold on to the view controller since we never set anything additional
// on it -- it will be automatically cleaned up when the view is detached.
- val newDisplayInfo = DisplayInfo(newView, newInfo)
- displayInfo = newDisplayInfo
- updateView(newDisplayInfo.info, newDisplayInfo.view)
+ val newViewController = TouchableRegionViewController(newView, this::getTouchableRegion)
+ newViewController.init()
+
+ updateView(newInfo, newView)
val paramsWithTitle = WindowManager.LayoutParams().also {
it.copyFrom(windowLayoutParams)
@@ -206,11 +260,15 @@
}
/** Removes then re-inflates the view. */
+ @Synchronized
private fun reinflateView() {
- val currentViewInfo = displayInfo ?: return
+ val currentDisplayInfo = getCurrentDisplayInfo() ?: return
- windowManager.removeView(currentViewInfo.view)
- inflateAndUpdateView(currentViewInfo.info)
+ val view = checkNotNull(currentDisplayInfo.view) {
+ "First item in activeViews list must have a valid view"
+ }
+ windowManager.removeView(view)
+ inflateAndUpdateView(currentDisplayInfo)
}
private val displayScaleListener = object : ConfigurationController.ConfigurationListener {
@@ -219,68 +277,109 @@
}
}
+ private fun addCallbacks() {
+ configurationController.addCallback(displayScaleListener)
+ }
+
+ private fun removeCallbacks() {
+ configurationController.removeCallback(displayScaleListener)
+ }
+
/**
- * Hides the view given its [id].
+ * Completely removes the view for the given [id], both visually and from our internal store.
*
* @param id the id of the device responsible of displaying the temp view.
* @param removalReason a short string describing why the view was removed (timeout, state
* change, etc.)
*/
+ @Synchronized
fun removeView(id: String, removalReason: String) {
- val currentDisplayInfo = displayInfo ?: return
-
- val removalPosition = findAndRemoveFromActiveViewsList(id)
- if (removalPosition == null) {
- logger.logViewRemovalIgnored(id, "view not found in the list")
- return
- }
- if (removalPosition != 0) {
- logger.logViewRemovalIgnored(id, "most recent view is being displayed.")
- return
- }
logger.logViewRemoval(id, removalReason)
- val newViewToDisplay = if (activeViews.isEmpty()) {
- null
- } else {
- activeViews[0].second
+ val displayInfo = activeViews.firstOrNull { it.info.id == id }
+ if (displayInfo == null) {
+ logger.logViewRemovalIgnored(id, "View not found in list")
+ return
}
- val currentView = currentDisplayInfo.view
- animateViewOut(currentView) {
- windowManager.removeView(currentView)
- wakeLock?.release(wakeReasonAcquired)
- }
+ val currentlyDisplayedView = activeViews[0]
+ // Remove immediately (instead as part of the animation end runnable) so that if a new view
+ // event comes in while this view is animating out, we still display the new view
+ // appropriately.
+ activeViews.remove(displayInfo)
- configurationController.removeCallback(displayScaleListener)
- // Re-set to null immediately (instead as part of the animation end runnable) so
- // that if a new view event comes in while this view is animating out, we still display
- // the new view appropriately.
- displayInfo = null
// No need to time the view out since it's already gone
- cancelViewTimeout?.run()
+ displayInfo.cancelViewTimeout?.run()
+
+ if (displayInfo.view == null) {
+ logger.logViewRemovalIgnored(id, "No view to remove")
+ return
+ }
+
+ if (currentlyDisplayedView.info.id != id) {
+ logger.logViewRemovalIgnored(id, "View isn't the currently displayed view")
+ return
+ }
+
+ removeViewFromWindow(displayInfo)
+
+ // Prune anything that's already timed out before determining if we should re-display a
+ // different chipbar.
+ removeTimedOutViews()
+ val newViewToDisplay = getCurrentDisplayInfo()
if (newViewToDisplay != null) {
- mainExecutor.executeDelayed({ displayView(newViewToDisplay)}, DISPLAY_VIEW_DELAY)
+ val timeout = newViewToDisplay.timeExpirationMillis - systemClock.currentTimeMillis()
+ // TODO(b/258019006): We may want to have a delay before showing the new view so
+ // that the UI translation looks a bit smoother. But, we expect this to happen
+ // rarely so it may not be worth the extra complexity.
+ showNewView(newViewToDisplay, timeout.toInt())
+ } else {
+ removeCallbacks()
}
}
/**
- * Finds and removes the active view with the given [id] from the stack, or null if there is no
- * active view with that ID
- *
- * @param id that temporary view belonged to.
- *
- * @return index of the view in the stack , otherwise null.
+ * Hides the view from the window, but keeps [displayInfo] around in [activeViews] in case it
+ * should be re-displayed later.
*/
- private fun findAndRemoveFromActiveViewsList(id: String): Int? {
- for (i in 0 until activeViews.size) {
- if (activeViews[i].first == id) {
- activeViews.removeAt(i)
- return i
- }
+ private fun hideView(displayInfo: DisplayInfo) {
+ logger.logViewHidden(displayInfo.info)
+ removeViewFromWindow(displayInfo)
+ }
+
+ private fun removeViewFromWindow(displayInfo: DisplayInfo) {
+ val view = displayInfo.view
+ if (view == null) {
+ logger.logViewRemovalIgnored(displayInfo.info.id, "View is null")
+ return
}
- return null
+ displayInfo.view = null // Need other places??
+ animateViewOut(view) {
+ windowManager.removeView(view)
+ displayInfo.wakeLock?.release(displayInfo.info.wakeReason)
+ }
+ }
+
+ @Synchronized
+ private fun removeTimedOutViews() {
+ val invalidViews = activeViews
+ .filter { it.timeExpirationMillis <
+ systemClock.currentTimeMillis() + MIN_REQUIRED_TIME_FOR_REDISPLAY }
+
+ invalidViews.forEach {
+ activeViews.remove(it)
+ logger.logViewExpiration(it.info)
+ }
+ }
+
+ @Synchronized
+ private fun removeFromActivesIfNeeded(id: String) {
+ val toRemove = activeViews.find { it.info.id == id }
+ toRemove?.let {
+ it.cancelViewTimeout?.run()
+ activeViews.remove(it)
+ }
}
/**
@@ -311,17 +410,47 @@
}
/** A container for all the display-related state objects. */
- private inner class DisplayInfo(
- /** The view currently being displayed. */
- val view: ViewGroup,
+ inner class DisplayInfo(
+ /**
+ * The view currently being displayed.
+ *
+ * Null if this info isn't currently being displayed.
+ */
+ var view: ViewGroup?,
- /** The info currently being displayed. */
+ /** The info that should be displayed if/when this is the highest priority view. */
var info: T,
+
+ /**
+ * The system time at which this display info should expire and never be displayed again.
+ */
+ var timeExpirationMillis: Long,
+
+ /**
+ * The wake lock currently held by this view. Must be released when the view disappears.
+ *
+ * Null if this info isn't currently being displayed.
+ */
+ var wakeLock: WakeLock?,
+
+ /**
+ * See [displayView].
+ */
+ var onViewTimeout: Runnable?,
+
+ /**
+ * A runnable that, when run, will cancel this view's timeout.
+ *
+ * Null if this info isn't currently being displayed.
+ */
+ var cancelViewTimeout: Runnable?,
)
+
+ // TODO(b/258019006): Add a dump method that dumps the currently active views.
}
private const val REMOVAL_REASON_TIMEOUT = "TIMEOUT"
-const val DISPLAY_VIEW_DELAY = 50L
+private const val MIN_REQUIRED_TIME_FOR_REDISPLAY = 1000
private data class IconInfo(
val iconName: String,
diff --git a/packages/SystemUI/src/com/android/systemui/temporarydisplay/TemporaryViewInfo.kt b/packages/SystemUI/src/com/android/systemui/temporarydisplay/TemporaryViewInfo.kt
index df83960..5596cf6 100644
--- a/packages/SystemUI/src/com/android/systemui/temporarydisplay/TemporaryViewInfo.kt
+++ b/packages/SystemUI/src/com/android/systemui/temporarydisplay/TemporaryViewInfo.kt
@@ -42,6 +42,20 @@
* The id of the temporary view.
*/
abstract val id: String
+
+ /** The priority for this view. */
+ abstract val priority: ViewPriority
}
const val DEFAULT_TIMEOUT_MILLIS = 10000
+
+/**
+ * The priority of the view being displayed.
+ *
+ * Must be ordered from lowest priority to highest priority. (CRITICAL is currently the highest
+ * priority.)
+ */
+enum class ViewPriority {
+ NORMAL,
+ CRITICAL,
+}
diff --git a/packages/SystemUI/src/com/android/systemui/temporarydisplay/TemporaryViewLogger.kt b/packages/SystemUI/src/com/android/systemui/temporarydisplay/TemporaryViewLogger.kt
index 133a384..ec6965a 100644
--- a/packages/SystemUI/src/com/android/systemui/temporarydisplay/TemporaryViewLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/temporarydisplay/TemporaryViewLogger.kt
@@ -20,20 +20,79 @@
import com.android.systemui.plugins.log.LogLevel
/** A logger for temporary view changes -- see [TemporaryViewDisplayController]. */
-open class TemporaryViewLogger(
+open class TemporaryViewLogger<T : TemporaryViewInfo>(
internal val buffer: LogBuffer,
internal val tag: String,
) {
- /** Logs that we added the view with the given [id] in a window titled [windowTitle]. */
- fun logViewAddition(id: String, windowTitle: String) {
+ fun logViewExpiration(info: T) {
buffer.log(
tag,
LogLevel.DEBUG,
{
- str1 = windowTitle
- str2 = id
+ str1 = info.id
+ str2 = info.windowTitle
+ str3 = info.priority.name
},
- { "View added. window=$str1 id=$str2" }
+ { "View timeout has already expired; removing. id=$str1 window=$str2 priority=$str3" }
+ )
+ }
+
+ fun logViewUpdate(info: T) {
+ buffer.log(
+ tag,
+ LogLevel.DEBUG,
+ {
+ str1 = info.id
+ str2 = info.windowTitle
+ str3 = info.priority.name
+ },
+ { "Existing view updated with new data. id=$str1 window=$str2 priority=$str3" }
+ )
+ }
+
+ fun logViewAdditionDelayed(info: T) {
+ buffer.log(
+ tag,
+ LogLevel.DEBUG,
+ {
+ str1 = info.id
+ str2 = info.windowTitle
+ str3 = info.priority.name
+ },
+ {
+ "New view can't be displayed because higher priority view is currently " +
+ "displayed. New view id=$str1 window=$str2 priority=$str3"
+ }
+ )
+ }
+
+ /** Logs that we added the view with the given information. */
+ fun logViewAddition(info: T) {
+ buffer.log(
+ tag,
+ LogLevel.DEBUG,
+ {
+ str1 = info.id
+ str2 = info.windowTitle
+ str3 = info.priority.name
+ },
+ { "View added. id=$str1 window=$str2 priority=$str3" }
+ )
+ }
+
+ fun logViewHidden(info: T) {
+ buffer.log(
+ tag,
+ LogLevel.DEBUG,
+ {
+ str1 = info.id
+ str2 = info.windowTitle
+ str3 = info.priority.name
+ },
+ {
+ "View hidden in favor of newer view. " +
+ "Hidden view id=$str1 window=$str2 priority=$str3"
+ }
)
}
diff --git a/packages/SystemUI/src/com/android/systemui/temporarydisplay/chipbar/ChipbarCoordinator.kt b/packages/SystemUI/src/com/android/systemui/temporarydisplay/chipbar/ChipbarCoordinator.kt
index 4d91e35..f37ef82 100644
--- a/packages/SystemUI/src/com/android/systemui/temporarydisplay/chipbar/ChipbarCoordinator.kt
+++ b/packages/SystemUI/src/com/android/systemui/temporarydisplay/chipbar/ChipbarCoordinator.kt
@@ -22,10 +22,13 @@
import android.view.Gravity
import android.view.MotionEvent
import android.view.View
+import android.view.View.ACCESSIBILITY_LIVE_REGION_ASSERTIVE
+import android.view.View.ACCESSIBILITY_LIVE_REGION_NONE
import android.view.ViewGroup
import android.view.WindowManager
import android.view.accessibility.AccessibilityManager
import android.widget.TextView
+import androidx.annotation.IdRes
import com.android.internal.widget.CachingIconView
import com.android.systemui.Gefingerpoken
import com.android.systemui.R
@@ -43,6 +46,7 @@
import com.android.systemui.statusbar.policy.ConfigurationController
import com.android.systemui.temporarydisplay.TemporaryViewDisplayController
import com.android.systemui.util.concurrency.DelayableExecutor
+import com.android.systemui.util.time.SystemClock
import com.android.systemui.util.view.ViewUtil
import com.android.systemui.util.wakelock.WakeLock
import javax.inject.Inject
@@ -77,6 +81,7 @@
private val viewUtil: ViewUtil,
private val vibratorHelper: VibratorHelper,
wakeLockBuilder: WakeLock.Builder,
+ systemClock: SystemClock,
) : TemporaryViewDisplayController<ChipbarInfo, ChipbarLogger>(
context,
logger,
@@ -87,6 +92,7 @@
powerManager,
R.layout.chipbar,
wakeLockBuilder,
+ systemClock,
) {
private lateinit var parent: ChipbarRootView
@@ -110,6 +116,8 @@
}
)
+ currentView.setTag(INFO_TAG, newInfo)
+
// Detect falsing touches on the chip.
parent = currentView.requireViewById(R.id.chipbar_root_view)
parent.touchHandler = object : Gefingerpoken {
@@ -162,8 +170,11 @@
} else {
""
}
- currentView.requireViewById<ViewGroup>(R.id.chipbar_inner).contentDescription =
- "$loadedIconDesc${newInfo.text.loadText(context)}"
+
+ val chipInnerView = currentView.getInnerView()
+ chipInnerView.contentDescription = "$loadedIconDesc${newInfo.text.loadText(context)}"
+ chipInnerView.accessibilityLiveRegion = ACCESSIBILITY_LIVE_REGION_ASSERTIVE
+ maybeGetAccessibilityFocus(newInfo, currentView)
// ---- Haptics ----
newInfo.vibrationEffect?.let {
@@ -171,23 +182,37 @@
}
}
+ private fun maybeGetAccessibilityFocus(info: ChipbarInfo?, view: ViewGroup) {
+ // Don't steal focus unless the chipbar has something interactable.
+ // (The chipbar is marked as a live region, so its content will be announced whenever the
+ // content changes.)
+ if (info?.endItem is ChipbarEndItem.Button) {
+ view.getInnerView().requestAccessibilityFocus()
+ } else {
+ view.getInnerView().clearAccessibilityFocus()
+ }
+ }
+
override fun animateViewIn(view: ViewGroup) {
- val chipInnerView = view.requireViewById<ViewGroup>(R.id.chipbar_inner)
ViewHierarchyAnimator.animateAddition(
- chipInnerView,
+ view.getInnerView(),
ViewHierarchyAnimator.Hotspot.TOP,
Interpolators.EMPHASIZED_DECELERATE,
duration = ANIMATION_IN_DURATION,
includeMargins = true,
includeFadeIn = true,
// We can only request focus once the animation finishes.
- onAnimationEnd = { chipInnerView.requestAccessibilityFocus() },
+ onAnimationEnd = {
+ maybeGetAccessibilityFocus(view.getTag(INFO_TAG) as ChipbarInfo?, view)
+ },
)
}
override fun animateViewOut(view: ViewGroup, onAnimationEnd: Runnable) {
+ val innerView = view.getInnerView()
+ innerView.accessibilityLiveRegion = ACCESSIBILITY_LIVE_REGION_NONE
ViewHierarchyAnimator.animateRemoval(
- view.requireViewById<ViewGroup>(R.id.chipbar_inner),
+ innerView,
ViewHierarchyAnimator.Hotspot.TOP,
Interpolators.EMPHASIZED_ACCELERATE,
ANIMATION_OUT_DURATION,
@@ -196,6 +221,10 @@
)
}
+ private fun ViewGroup.getInnerView(): ViewGroup {
+ return requireViewById(R.id.chipbar_inner)
+ }
+
override fun start() {}
override fun getTouchableRegion(view: View, outRect: Rect) {
@@ -213,3 +242,4 @@
private const val ANIMATION_IN_DURATION = 500L
private const val ANIMATION_OUT_DURATION = 250L
+@IdRes private val INFO_TAG = R.id.tag_chipbar_info
diff --git a/packages/SystemUI/src/com/android/systemui/temporarydisplay/chipbar/ChipbarInfo.kt b/packages/SystemUI/src/com/android/systemui/temporarydisplay/chipbar/ChipbarInfo.kt
index a3eef80..dd4bd26 100644
--- a/packages/SystemUI/src/com/android/systemui/temporarydisplay/chipbar/ChipbarInfo.kt
+++ b/packages/SystemUI/src/com/android/systemui/temporarydisplay/chipbar/ChipbarInfo.kt
@@ -22,6 +22,7 @@
import com.android.systemui.common.shared.model.Text
import com.android.systemui.common.shared.model.TintedIcon
import com.android.systemui.temporarydisplay.TemporaryViewInfo
+import com.android.systemui.temporarydisplay.ViewPriority
/**
* A container for all the state needed to display a chipbar via [ChipbarCoordinator].
@@ -42,6 +43,7 @@
override val wakeReason: String,
override val timeoutMs: Int,
override val id: String,
+ override val priority: ViewPriority,
) : TemporaryViewInfo() {
companion object {
@AttrRes const val DEFAULT_ICON_TINT_ATTR = android.R.attr.textColorPrimary
diff --git a/packages/SystemUI/src/com/android/systemui/temporarydisplay/chipbar/ChipbarLogger.kt b/packages/SystemUI/src/com/android/systemui/temporarydisplay/chipbar/ChipbarLogger.kt
index e477cd6..fcfbe0a 100644
--- a/packages/SystemUI/src/com/android/systemui/temporarydisplay/chipbar/ChipbarLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/temporarydisplay/chipbar/ChipbarLogger.kt
@@ -29,7 +29,7 @@
@Inject
constructor(
@ChipbarLog buffer: LogBuffer,
-) : TemporaryViewLogger(buffer, "ChipbarLog") {
+) : TemporaryViewLogger<ChipbarInfo>(buffer, "ChipbarLog") {
/**
* Logs that the chipbar was updated to display in a window named [windowTitle], with [text] and
* [endItemDesc].
diff --git a/packages/SystemUI/src/com/android/systemui/unfold/FoldAodAnimationController.kt b/packages/SystemUI/src/com/android/systemui/unfold/FoldAodAnimationController.kt
index 6216acd..101bd44 100644
--- a/packages/SystemUI/src/com/android/systemui/unfold/FoldAodAnimationController.kt
+++ b/packages/SystemUI/src/com/android/systemui/unfold/FoldAodAnimationController.kt
@@ -16,6 +16,7 @@
package com.android.systemui.unfold
+import android.annotation.BinderThread
import android.content.Context
import android.hardware.devicestate.DeviceStateManager
import android.os.PowerManager
@@ -41,7 +42,6 @@
import javax.inject.Inject
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Job
-import kotlinx.coroutines.flow.collect
import kotlinx.coroutines.launch
/**
@@ -52,7 +52,7 @@
class FoldAodAnimationController
@Inject
constructor(
- @Main private val executor: DelayableExecutor,
+ @Main private val mainExecutor: DelayableExecutor,
private val context: Context,
private val deviceStateManager: DeviceStateManager,
private val wakefulnessLifecycle: WakefulnessLifecycle,
@@ -89,7 +89,7 @@
override fun initialize(centralSurfaces: CentralSurfaces, lightRevealScrim: LightRevealScrim) {
this.centralSurfaces = centralSurfaces
- deviceStateManager.registerCallback(executor, FoldListener())
+ deviceStateManager.registerCallback(mainExecutor, FoldListener())
wakefulnessLifecycle.addObserver(this)
// TODO(b/254878364): remove this call to NPVC.getView()
@@ -139,7 +139,8 @@
* @param onReady callback when the animation is ready
* @see [com.android.systemui.keyguard.KeyguardViewMediator]
*/
- fun onScreenTurningOn(onReady: Runnable) {
+ @BinderThread
+ fun onScreenTurningOn(onReady: Runnable) = mainExecutor.execute {
if (shouldPlayAnimation) {
// The device was not dozing and going to sleep after folding, play the animation
@@ -179,12 +180,13 @@
}
}
- fun onScreenTurnedOn() {
+ @BinderThread
+ fun onScreenTurnedOn() = mainExecutor.execute {
if (shouldPlayAnimation) {
cancelAnimation?.run()
// Post starting the animation to the next frame to avoid junk due to inset changes
- cancelAnimation = executor.executeDelayed(startAnimationRunnable, /* delayMillis= */ 0)
+ cancelAnimation = mainExecutor.executeDelayed(startAnimationRunnable, /* delayMillis= */ 0)
shouldPlayAnimation = false
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/unfold/UnfoldLightRevealOverlayAnimation.kt b/packages/SystemUI/src/com/android/systemui/unfold/UnfoldLightRevealOverlayAnimation.kt
index b2ec27c..9cca950 100644
--- a/packages/SystemUI/src/com/android/systemui/unfold/UnfoldLightRevealOverlayAnimation.kt
+++ b/packages/SystemUI/src/com/android/systemui/unfold/UnfoldLightRevealOverlayAnimation.kt
@@ -15,6 +15,7 @@
*/
package com.android.systemui.unfold
+import android.annotation.BinderThread
import android.content.ContentResolver
import android.content.Context
import android.graphics.PixelFormat
@@ -34,9 +35,7 @@
import android.view.SurfaceSession
import android.view.WindowManager
import android.view.WindowlessWindowManager
-import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.dagger.qualifiers.Main
-import com.android.systemui.dagger.qualifiers.UiBackground
import com.android.systemui.statusbar.LightRevealEffect
import com.android.systemui.statusbar.LightRevealScrim
import com.android.systemui.statusbar.LinearLightRevealEffect
@@ -45,7 +44,7 @@
import com.android.systemui.unfold.UnfoldTransitionProgressProvider.TransitionProgressListener
import com.android.systemui.unfold.updates.RotationChangeProvider
import com.android.systemui.unfold.util.ScaleAwareTransitionProgressProvider.Companion.areAnimationsEnabled
-import com.android.systemui.util.Assert.isMainThread
+import com.android.systemui.util.concurrency.ThreadFactory
import com.android.systemui.util.traceSection
import com.android.wm.shell.displayareahelper.DisplayAreaHelper
import java.util.Optional
@@ -64,14 +63,16 @@
private val unfoldTransitionProgressProvider: UnfoldTransitionProgressProvider,
private val displayAreaHelper: Optional<DisplayAreaHelper>,
@Main private val executor: Executor,
- @UiBackground private val backgroundExecutor: Executor,
- @Background private val bgHandler: Handler,
+ private val threadFactory: ThreadFactory,
private val rotationChangeProvider: RotationChangeProvider,
) {
private val transitionListener = TransitionListener()
private val rotationWatcher = RotationWatcher()
+ private lateinit var bgHandler: Handler
+ private lateinit var bgExecutor: Executor
+
private lateinit var wwm: WindowlessWindowManager
private lateinit var unfoldedDisplayInfo: DisplayInfo
private lateinit var overlayContainer: SurfaceControl
@@ -84,7 +85,12 @@
private var currentRotation: Int = context.display!!.rotation
fun init() {
- deviceStateManager.registerCallback(executor, FoldListener())
+ // This method will be called only on devices where this animation is enabled,
+ // so normally this thread won't be created
+ bgHandler = threadFactory.buildHandlerOnNewThread(TAG)
+ bgExecutor = threadFactory.buildDelayableExecutorOnHandler(bgHandler)
+
+ deviceStateManager.registerCallback(bgExecutor, FoldListener())
unfoldTransitionProgressProvider.addCallback(transitionListener)
rotationChangeProvider.addCallback(rotationWatcher)
@@ -122,20 +128,23 @@
* @param onOverlayReady callback when the overlay is drawn and visible on the screen
* @see [com.android.systemui.keyguard.KeyguardViewMediator]
*/
+ @BinderThread
fun onScreenTurningOn(onOverlayReady: Runnable) {
- Trace.beginSection("UnfoldLightRevealOverlayAnimation#onScreenTurningOn")
- try {
- // Add the view only if we are unfolding and this is the first screen on
- if (!isFolded && !isUnfoldHandled && contentResolver.areAnimationsEnabled()) {
- executeInBackground { addOverlay(onOverlayReady, reason = UNFOLD) }
- isUnfoldHandled = true
- } else {
- // No unfold transition, immediately report that overlay is ready
- executeInBackground { ensureOverlayRemoved() }
- onOverlayReady.run()
+ executeInBackground {
+ Trace.beginSection("$TAG#onScreenTurningOn")
+ try {
+ // Add the view only if we are unfolding and this is the first screen on
+ if (!isFolded && !isUnfoldHandled && contentResolver.areAnimationsEnabled()) {
+ addOverlay(onOverlayReady, reason = UNFOLD)
+ isUnfoldHandled = true
+ } else {
+ // No unfold transition, immediately report that overlay is ready
+ ensureOverlayRemoved()
+ onOverlayReady.run()
+ }
+ } finally {
+ Trace.endSection()
}
- } finally {
- Trace.endSection()
}
}
@@ -154,17 +163,18 @@
LightRevealScrim(context, null).apply {
revealEffect = createLightRevealEffect()
isScrimOpaqueChangedListener = Consumer {}
- revealAmount = when (reason) {
- FOLD -> TRANSPARENT
- UNFOLD -> BLACK
- }
+ revealAmount =
+ when (reason) {
+ FOLD -> TRANSPARENT
+ UNFOLD -> BLACK
+ }
}
val params = getLayoutParams()
newRoot.setView(newView, params)
if (onOverlayReady != null) {
- Trace.beginAsyncSection("UnfoldLightRevealOverlayAnimation#relayout", 0)
+ Trace.beginAsyncSection("$TAG#relayout", 0)
newRoot.relayout(params) { transaction ->
val vsyncId = Choreographer.getSfInstance().vsyncId
@@ -179,8 +189,8 @@
transaction
.setFrameTimelineVsync(vsyncId + 1)
- .addTransactionCommittedListener(backgroundExecutor) {
- Trace.endAsyncSection("UnfoldLightRevealOverlayAnimation#relayout", 0)
+ .addTransactionCommittedListener(bgExecutor) {
+ Trace.endAsyncSection("$TAG#relayout", 0)
onOverlayReady.run()
}
.apply()
@@ -233,7 +243,8 @@
}
private fun getUnfoldedDisplayInfo(): DisplayInfo =
- displayManager.getDisplays(DisplayManager.DISPLAY_CATEGORY_ALL_INCLUDING_DISABLED)
+ displayManager
+ .getDisplays(DisplayManager.DISPLAY_CATEGORY_ALL_INCLUDING_DISABLED)
.asSequence()
.map { DisplayInfo().apply { it.getDisplayInfo(this) } }
.filter { it.type == Display.TYPE_INTERNAL }
@@ -261,10 +272,10 @@
private inner class RotationWatcher : RotationChangeProvider.RotationListener {
override fun onRotationChanged(newRotation: Int) {
- traceSection("UnfoldLightRevealOverlayAnimation#onRotationChanged") {
- if (currentRotation != newRotation) {
- currentRotation = newRotation
- executeInBackground {
+ executeInBackground {
+ traceSection("$TAG#onRotationChanged") {
+ if (currentRotation != newRotation) {
+ currentRotation = newRotation
scrimView?.revealEffect = createLightRevealEffect()
root?.relayout(getLayoutParams())
}
@@ -274,7 +285,10 @@
}
private fun executeInBackground(f: () -> Unit) {
- ensureInMainThread()
+ check(Looper.myLooper() != bgHandler.looper) {
+ "Trying to execute using background handler while already running" +
+ " in the background handler"
+ }
// The UiBackground executor is not used as it doesn't have a prepared looper.
bgHandler.post(f)
}
@@ -283,25 +297,25 @@
check(Looper.myLooper() == bgHandler.looper) { "Not being executed in the background!" }
}
- private fun ensureInMainThread() {
- isMainThread()
- }
-
private inner class FoldListener :
FoldStateListener(
context,
Consumer { isFolded ->
if (isFolded) {
- executeInBackground { ensureOverlayRemoved() }
+ ensureOverlayRemoved()
isUnfoldHandled = false
}
this.isFolded = isFolded
}
)
- private enum class AddOverlayReason { FOLD, UNFOLD }
+ private enum class AddOverlayReason {
+ FOLD,
+ UNFOLD
+ }
private companion object {
+ const val TAG = "UnfoldLightRevealOverlayAnimation"
const val ROTATION_ANIMATION_OVERLAY_Z_INDEX = Integer.MAX_VALUE
// Put the unfold overlay below the rotation animation screenshot to hide the moment
diff --git a/packages/SystemUI/src/com/android/systemui/user/UserSwitcherActivity.kt b/packages/SystemUI/src/com/android/systemui/user/UserSwitcherActivity.kt
index 7da2d47..52b7fb6 100644
--- a/packages/SystemUI/src/com/android/systemui/user/UserSwitcherActivity.kt
+++ b/packages/SystemUI/src/com/android/systemui/user/UserSwitcherActivity.kt
@@ -39,9 +39,9 @@
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.user_switcher_fullscreen)
- window.decorView.getWindowInsetsController().apply {
- setSystemBarsBehavior(BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE)
- hide(Type.systemBars())
+ window.decorView.windowInsetsController?.let { controller ->
+ controller.systemBarsBehavior = BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE
+ controller.hide(Type.systemBars())
}
val viewModel =
ViewModelProvider(this, viewModelFactory.get())[UserSwitcherViewModel::class.java]
diff --git a/packages/SystemUI/src/com/android/systemui/util/concurrency/PendingTasksContainer.kt b/packages/SystemUI/src/com/android/systemui/util/concurrency/PendingTasksContainer.kt
index 6cd384f..ceebcb7 100644
--- a/packages/SystemUI/src/com/android/systemui/util/concurrency/PendingTasksContainer.kt
+++ b/packages/SystemUI/src/com/android/systemui/util/concurrency/PendingTasksContainer.kt
@@ -25,8 +25,11 @@
*/
class PendingTasksContainer {
- private var pendingTasksCount: AtomicInteger = AtomicInteger(0)
- private var completionCallback: AtomicReference<Runnable> = AtomicReference()
+ @Volatile
+ private var pendingTasksCount = AtomicInteger(0)
+
+ @Volatile
+ private var completionCallback = AtomicReference<Runnable>()
/**
* Registers a task that we should wait for
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java
index 849ff08..2aef726 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java
@@ -823,7 +823,7 @@
mKeyguardUpdateMonitor.dispatchStartedWakingUp(PowerManager.WAKE_REASON_POWER_BUTTON);
mTestableLooper.processAllMessages();
lockscreenBypassIsAllowed();
- mKeyguardUpdateMonitor.onTrustChanged(true /* enabled */,
+ mKeyguardUpdateMonitor.onTrustChanged(true /* enabled */, true /* newlyUnlocked */,
KeyguardUpdateMonitor.getCurrentUser(), 0 /* flags */,
new ArrayList<>());
keyguardIsVisible();
@@ -834,7 +834,7 @@
public void testIgnoresAuth_whenTrustAgentOnKeyguard_withoutBypass() {
mKeyguardUpdateMonitor.dispatchStartedWakingUp(PowerManager.WAKE_REASON_POWER_BUTTON);
mTestableLooper.processAllMessages();
- mKeyguardUpdateMonitor.onTrustChanged(true /* enabled */,
+ mKeyguardUpdateMonitor.onTrustChanged(true /* enabled */, true /* newlyUnlocked */,
KeyguardUpdateMonitor.getCurrentUser(), 0 /* flags */, new ArrayList<>());
keyguardIsVisible();
verify(mFaceManager, never()).authenticate(any(), any(), any(), any(), anyInt(),
@@ -1049,8 +1049,8 @@
@Test
public void testGetUserCanSkipBouncer_whenTrust() {
int user = KeyguardUpdateMonitor.getCurrentUser();
- mKeyguardUpdateMonitor.onTrustChanged(true /* enabled */, user, 0 /* flags */,
- new ArrayList<>());
+ mKeyguardUpdateMonitor.onTrustChanged(true /* enabled */, true /* newlyUnlocked */,
+ user, 0 /* flags */, new ArrayList<>());
assertThat(mKeyguardUpdateMonitor.getUserCanSkipBouncer(user)).isTrue();
}
@@ -1314,7 +1314,7 @@
when(mStrongAuthTracker.hasUserAuthenticatedSinceBoot()).thenReturn(true);
// WHEN trust is enabled (ie: via smartlock)
- mKeyguardUpdateMonitor.onTrustChanged(true /* enabled */,
+ mKeyguardUpdateMonitor.onTrustChanged(true /* enabled */, true /* newlyUnlocked */,
KeyguardUpdateMonitor.getCurrentUser(), 0 /* flags */, new ArrayList<>());
// THEN we shouldn't listen for udfps
@@ -1418,7 +1418,7 @@
@Test
public void testShowTrustGrantedMessage_onTrustGranted() {
// WHEN trust is enabled (ie: via some trust agent) with a trustGranted string
- mKeyguardUpdateMonitor.onTrustChanged(true /* enabled */,
+ mKeyguardUpdateMonitor.onTrustChanged(true /* enabled */, true /* newlyUnlocked */,
KeyguardUpdateMonitor.getCurrentUser(), 0 /* flags */,
Arrays.asList("Unlocked by wearable"));
@@ -1870,6 +1870,7 @@
// WHEN onTrustChanged with TRUST_DISMISS_KEYGUARD flag
mKeyguardUpdateMonitor.onTrustChanged(
true /* enabled */,
+ true /* newlyUnlocked */,
getCurrentUser() /* userId */,
TrustAgentService.FLAG_GRANT_TRUST_DISMISS_KEYGUARD /* flags */,
null /* trustGrantedMessages */);
@@ -1893,6 +1894,7 @@
// WHEN onTrustChanged with TRUST_DISMISS_KEYGUARD flag
mKeyguardUpdateMonitor.onTrustChanged(
true /* enabled */,
+ true /* newlyUnlocked */,
getCurrentUser() /* userId */,
TrustAgentService.FLAG_GRANT_TRUST_DISMISS_KEYGUARD /* flags */,
null /* trustGrantedMessages */);
@@ -1917,6 +1919,7 @@
// WHEN onTrustChanged for a different user
mKeyguardUpdateMonitor.onTrustChanged(
true /* enabled */,
+ true /* newlyUnlocked */,
546 /* userId, not the current userId */,
0 /* flags */,
null /* trustGrantedMessages */);
@@ -1941,6 +1944,7 @@
// flags (temporary & rewable is active unlock)
mKeyguardUpdateMonitor.onTrustChanged(
true /* enabled */,
+ true /* newlyUnlocked */,
getCurrentUser() /* userId */,
TrustAgentService.FLAG_GRANT_TRUST_DISMISS_KEYGUARD
| TrustAgentService.FLAG_GRANT_TRUST_TEMPORARY_AND_RENEWABLE /* flags */,
@@ -1968,6 +1972,7 @@
// WHEN onTrustChanged with INITIATED_BY_USER flag
mKeyguardUpdateMonitor.onTrustChanged(
true /* enabled */,
+ true /* newlyUnlocked */,
getCurrentUser() /* userId, not the current userId */,
TrustAgentService.FLAG_GRANT_TRUST_INITIATED_BY_USER /* flags */,
null /* trustGrantedMessages */);
@@ -1992,6 +1997,7 @@
// WHEN onTrustChanged with INITIATED_BY_USER flag
mKeyguardUpdateMonitor.onTrustChanged(
true /* enabled */,
+ true /* newlyUnlocked */,
getCurrentUser() /* userId, not the current userId */,
TrustAgentService.FLAG_GRANT_TRUST_INITIATED_BY_USER
| TrustAgentService.FLAG_GRANT_TRUST_TEMPORARY_AND_RENEWABLE /* flags */,
@@ -2310,6 +2316,7 @@
private void currentUserDoesNotHaveTrust() {
mKeyguardUpdateMonitor.onTrustChanged(
false,
+ false,
KeyguardUpdateMonitor.getCurrentUser(),
-1,
new ArrayList<>()
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/mediator/ScreenOnCoordinatorTest.kt b/packages/SystemUI/tests/src/com/android/keyguard/mediator/ScreenOnCoordinatorTest.kt
index 5734c3d..34e78eb 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/mediator/ScreenOnCoordinatorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/keyguard/mediator/ScreenOnCoordinatorTest.kt
@@ -52,8 +52,6 @@
private lateinit var foldAodAnimationController: FoldAodAnimationController
@Mock
private lateinit var unfoldAnimation: UnfoldLightRevealOverlayAnimation
- @Mock
- private lateinit var screenLifecycle: ScreenLifecycle
@Captor
private lateinit var readyCaptor: ArgumentCaptor<Runnable>
@@ -69,13 +67,8 @@
.thenReturn(foldAodAnimationController)
screenOnCoordinator = ScreenOnCoordinator(
- screenLifecycle,
Optional.of(unfoldComponent),
- FakeExecution()
)
-
- // Make sure screen events are registered to observe
- verify(screenLifecycle).addObserver(screenOnCoordinator)
}
@Test
@@ -93,9 +86,7 @@
fun testUnfoldTransitionDisabledDrawnTasksReady_onScreenTurningOn_callsDrawnCallback() {
// Recreate with empty unfoldComponent
screenOnCoordinator = ScreenOnCoordinator(
- screenLifecycle,
Optional.empty(),
- FakeExecution()
)
screenOnCoordinator.onScreenTurningOn(runnable)
@@ -105,11 +96,11 @@
private fun onUnfoldOverlayReady() {
verify(unfoldAnimation).onScreenTurningOn(capture(readyCaptor))
- readyCaptor.getValue().run()
+ readyCaptor.value.run()
}
private fun onFoldAodReady() {
verify(foldAodAnimationController).onScreenTurningOn(capture(readyCaptor))
- readyCaptor.getValue().run()
+ readyCaptor.value.run()
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/camera/CameraGestureHelperTest.kt b/packages/SystemUI/tests/src/com/android/systemui/camera/CameraGestureHelperTest.kt
index ca94ea8..262b4b8 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/camera/CameraGestureHelperTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/camera/CameraGestureHelperTest.kt
@@ -301,7 +301,7 @@
val intent = intentCaptor.value
assertThat(CameraIntents.isSecureCameraIntent(intent)).isEqualTo(isSecure)
- assertThat(intent.getIntExtra(CameraGestureHelper.EXTRA_CAMERA_LAUNCH_SOURCE, -1))
+ assertThat(intent.getIntExtra(CameraIntents.EXTRA_LAUNCH_SOURCE, -1))
.isEqualTo(source)
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/clipboardoverlay/ClipboardOverlayControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/clipboardoverlay/ClipboardOverlayControllerTest.java
index d6e621f..b4e85c0 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/clipboardoverlay/ClipboardOverlayControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/clipboardoverlay/ClipboardOverlayControllerTest.java
@@ -16,12 +16,14 @@
package com.android.systemui.clipboardoverlay;
+import static com.android.systemui.clipboardoverlay.ClipboardOverlayEvent.CLIPBOARD_OVERLAY_ACTION_SHOWN;
import static com.android.systemui.clipboardoverlay.ClipboardOverlayEvent.CLIPBOARD_OVERLAY_DISMISS_TAPPED;
import static com.android.systemui.clipboardoverlay.ClipboardOverlayEvent.CLIPBOARD_OVERLAY_SHARE_TAPPED;
import static com.android.systemui.clipboardoverlay.ClipboardOverlayEvent.CLIPBOARD_OVERLAY_SWIPE_DISMISSED;
import static com.android.systemui.flags.Flags.CLIPBOARD_REMOTE_BEHAVIOR;
import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
@@ -29,10 +31,13 @@
import static org.mockito.Mockito.when;
import android.animation.Animator;
+import android.app.RemoteAction;
import android.content.ClipData;
import android.content.ClipDescription;
+import android.content.Context;
import android.net.Uri;
import android.os.PersistableBundle;
+import android.view.textclassifier.TextLinks;
import androidx.test.filters.SmallTest;
import androidx.test.runner.AndroidJUnit4;
@@ -42,6 +47,8 @@
import com.android.systemui.broadcast.BroadcastSender;
import com.android.systemui.flags.FakeFeatureFlags;
import com.android.systemui.screenshot.TimeoutHandler;
+import com.android.systemui.util.concurrency.FakeExecutor;
+import com.android.systemui.util.time.FakeSystemClock;
import org.junit.After;
import org.junit.Before;
@@ -50,7 +57,12 @@
import org.mockito.ArgumentCaptor;
import org.mockito.Captor;
import org.mockito.Mock;
+import org.mockito.Mockito;
import org.mockito.MockitoAnnotations;
+import org.mockito.invocation.InvocationOnMock;
+import org.mockito.stubbing.Answer;
+
+import java.util.Optional;
@SmallTest
@RunWith(AndroidJUnit4.class)
@@ -80,6 +92,8 @@
private ArgumentCaptor<ClipboardOverlayView.ClipboardOverlayCallbacks> mOverlayCallbacksCaptor;
private ClipboardOverlayView.ClipboardOverlayCallbacks mCallbacks;
+ private FakeExecutor mExecutor = new FakeExecutor(new FakeSystemClock());
+
@Before
public void setup() {
MockitoAnnotations.initMocks(this);
@@ -101,6 +115,7 @@
mTimeoutHandler,
mFeatureFlags,
mClipboardUtils,
+ mExecutor,
mUiEventLogger);
verify(mClipboardOverlayView).setCallbacks(mOverlayCallbacksCaptor.capture());
mCallbacks = mOverlayCallbacksCaptor.getValue();
@@ -237,4 +252,29 @@
verify(mUiEventLogger).log(CLIPBOARD_OVERLAY_DISMISS_TAPPED, 0, "second.package");
verifyNoMoreInteractions(mUiEventLogger);
}
+
+ @Test
+ public void test_logOnClipboardActionsShown() {
+ ClipData.Item item = mSampleClipData.getItemAt(0);
+ item.setTextLinks(Mockito.mock(TextLinks.class));
+ mFeatureFlags.set(CLIPBOARD_REMOTE_BEHAVIOR, true);
+ when(mClipboardUtils.isRemoteCopy(any(Context.class), any(ClipData.class), anyString()))
+ .thenReturn(true);
+ when(mClipboardUtils.getAction(any(ClipData.Item.class), anyString()))
+ .thenReturn(Optional.of(Mockito.mock(RemoteAction.class)));
+ when(mClipboardOverlayView.post(any(Runnable.class))).thenAnswer(new Answer<Object>() {
+ @Override
+ public Object answer(InvocationOnMock invocation) throws Throwable {
+ ((Runnable) invocation.getArgument(0)).run();
+ return null;
+ }
+ });
+
+ mOverlayController.setClipData(
+ new ClipData(mSampleClipData.getDescription(), item), "actionShownSource");
+ mExecutor.runAllReady();
+
+ verify(mUiEventLogger).log(CLIPBOARD_OVERLAY_ACTION_SHOWN, 0, "actionShownSource");
+ verifyNoMoreInteractions(mUiEventLogger);
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/clipboardoverlay/ClipboardOverlayUtilsTest.java b/packages/SystemUI/tests/src/com/android/systemui/clipboardoverlay/ClipboardOverlayUtilsTest.java
index 09b1699..aea6be3 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/clipboardoverlay/ClipboardOverlayUtilsTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/clipboardoverlay/ClipboardOverlayUtilsTest.java
@@ -16,13 +16,24 @@
package com.android.systemui.clipboardoverlay;
+import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.ArgumentMatchers.isNull;
+import static org.mockito.Mockito.when;
+import android.app.RemoteAction;
import android.content.ClipData;
import android.content.ClipDescription;
import android.os.PersistableBundle;
import android.testing.TestableResources;
+import android.util.ArrayMap;
+import android.view.textclassifier.TextClassification;
+import android.view.textclassifier.TextClassificationManager;
+import android.view.textclassifier.TextClassifier;
+import android.view.textclassifier.TextLinks;
import androidx.test.filters.SmallTest;
import androidx.test.runner.AndroidJUnit4;
@@ -30,19 +41,84 @@
import com.android.systemui.R;
import com.android.systemui.SysuiTestCase;
+import com.google.android.collect.Lists;
+
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;
+import org.mockito.MockitoAnnotations;
+
+import java.util.Map;
+import java.util.Optional;
@SmallTest
@RunWith(AndroidJUnit4.class)
public class ClipboardOverlayUtilsTest extends SysuiTestCase {
private ClipboardOverlayUtils mClipboardUtils;
+ @Mock
+ private TextClassificationManager mTextClassificationManager;
+ @Mock
+ private TextClassifier mTextClassifier;
+
+ @Mock
+ private ClipData.Item mClipDataItem;
@Before
public void setUp() {
- mClipboardUtils = new ClipboardOverlayUtils();
+ MockitoAnnotations.initMocks(this);
+
+ when(mTextClassificationManager.getTextClassifier()).thenReturn(mTextClassifier);
+ mClipboardUtils = new ClipboardOverlayUtils(mTextClassificationManager);
+ }
+
+ @Test
+ public void test_getAction_noLinks_returnsEmptyOptional() {
+ ClipData.Item item = new ClipData.Item("no text links");
+ item.setTextLinks(Mockito.mock(TextLinks.class));
+
+ Optional<RemoteAction> action = mClipboardUtils.getAction(item, "");
+
+ assertTrue(action.isEmpty());
+ }
+
+ @Test
+ public void test_getAction_returnsFirstLink() {
+ when(mClipDataItem.getTextLinks()).thenReturn(getFakeTextLinks());
+ when(mClipDataItem.getText()).thenReturn("");
+ RemoteAction actionA = constructRemoteAction("abc");
+ RemoteAction actionB = constructRemoteAction("def");
+ TextClassification classificationA = Mockito.mock(TextClassification.class);
+ when(classificationA.getActions()).thenReturn(Lists.newArrayList(actionA));
+ TextClassification classificationB = Mockito.mock(TextClassification.class);
+ when(classificationB.getActions()).thenReturn(Lists.newArrayList(actionB));
+ when(mTextClassifier.classifyText(anyString(), anyInt(), anyInt(), isNull())).thenReturn(
+ classificationA, classificationB);
+
+ RemoteAction result = mClipboardUtils.getAction(mClipDataItem, "def").orElse(null);
+
+ assertEquals(actionA, result);
+ }
+
+ @Test
+ public void test_getAction_skipsMatchingComponent() {
+ when(mClipDataItem.getTextLinks()).thenReturn(getFakeTextLinks());
+ when(mClipDataItem.getText()).thenReturn("");
+ RemoteAction actionA = constructRemoteAction("abc");
+ RemoteAction actionB = constructRemoteAction("def");
+ TextClassification classificationA = Mockito.mock(TextClassification.class);
+ when(classificationA.getActions()).thenReturn(Lists.newArrayList(actionA));
+ TextClassification classificationB = Mockito.mock(TextClassification.class);
+ when(classificationB.getActions()).thenReturn(Lists.newArrayList(actionB));
+ when(mTextClassifier.classifyText(anyString(), anyInt(), anyInt(), isNull())).thenReturn(
+ classificationA, classificationB);
+
+ RemoteAction result = mClipboardUtils.getAction(mClipDataItem, "abc").orElse(null);
+
+ assertEquals(actionB, result);
}
@Test
@@ -92,7 +168,7 @@
assertFalse(mClipboardUtils.isRemoteCopy(mContext, data, ""));
}
- static ClipData constructClipData(String[] mimeTypes, ClipData.Item item,
+ private static ClipData constructClipData(String[] mimeTypes, ClipData.Item item,
PersistableBundle extras) {
ClipDescription description = new ClipDescription("Test", mimeTypes);
if (extras != null) {
@@ -100,4 +176,20 @@
}
return new ClipData(description, item);
}
+
+ private static RemoteAction constructRemoteAction(String packageName) {
+ RemoteAction action = Mockito.mock(RemoteAction.class, Answers.RETURNS_DEEP_STUBS);
+ when(action.getActionIntent().getIntent().getComponent().getPackageName())
+ .thenReturn(packageName);
+ return action;
+ }
+
+ private static TextLinks getFakeTextLinks() {
+ TextLinks.Builder textLinks = new TextLinks.Builder("test");
+ final Map<String, Float> scores = new ArrayMap<>();
+ scores.put(TextClassifier.TYPE_EMAIL, 1f);
+ textLinks.addLink(0, 0, scores);
+ textLinks.addLink(0, 0, scores);
+ return textLinks.build();
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayAnimationsControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayAnimationsControllerTest.kt
index 99406ed..8e689cf 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayAnimationsControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayAnimationsControllerTest.kt
@@ -23,11 +23,11 @@
class DreamOverlayAnimationsControllerTest : SysuiTestCase() {
companion object {
+ private const val DREAM_BLUR_RADIUS = 50
private const val DREAM_IN_BLUR_ANIMATION_DURATION = 1L
- private const val DREAM_IN_BLUR_ANIMATION_DELAY = 2L
private const val DREAM_IN_COMPLICATIONS_ANIMATION_DURATION = 3L
- private const val DREAM_IN_TOP_COMPLICATIONS_ANIMATION_DELAY = 4L
- private const val DREAM_IN_BOTTOM_COMPLICATIONS_ANIMATION_DELAY = 5L
+ private const val DREAM_IN_TRANSLATION_Y_DISTANCE = 6
+ private const val DREAM_IN_TRANSLATION_Y_DURATION = 7L
private const val DREAM_OUT_TRANSLATION_Y_DISTANCE = 6
private const val DREAM_OUT_TRANSLATION_Y_DURATION = 7L
private const val DREAM_OUT_TRANSLATION_Y_DELAY_BOTTOM = 8L
@@ -54,11 +54,11 @@
hostViewController,
statusBarViewController,
stateController,
+ DREAM_BLUR_RADIUS,
DREAM_IN_BLUR_ANIMATION_DURATION,
- DREAM_IN_BLUR_ANIMATION_DELAY,
DREAM_IN_COMPLICATIONS_ANIMATION_DURATION,
- DREAM_IN_TOP_COMPLICATIONS_ANIMATION_DELAY,
- DREAM_IN_BOTTOM_COMPLICATIONS_ANIMATION_DELAY,
+ DREAM_IN_TRANSLATION_Y_DISTANCE,
+ DREAM_IN_TRANSLATION_Y_DURATION,
DREAM_OUT_TRANSLATION_Y_DISTANCE,
DREAM_OUT_TRANSLATION_Y_DURATION,
DREAM_OUT_TRANSLATION_Y_DELAY_BOTTOM,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/dreams/complication/ComplicationLayoutParamsTest.java b/packages/SystemUI/tests/src/com/android/systemui/dreams/complication/ComplicationLayoutParamsTest.java
index fdb4cc4..e414942 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/dreams/complication/ComplicationLayoutParamsTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/dreams/complication/ComplicationLayoutParamsTest.java
@@ -17,6 +17,10 @@
import static com.google.common.truth.Truth.assertThat;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
+
import android.testing.AndroidTestingRunner;
import androidx.test.filters.SmallTest;
@@ -29,6 +33,7 @@
import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;
+import java.util.function.Consumer;
@SmallTest
@RunWith(AndroidTestingRunner.class)
@@ -197,4 +202,19 @@
assertThat(paramsWithConstraint.constraintSpecified()).isTrue();
assertThat(paramsWithConstraint.getConstraint()).isEqualTo(constraint);
}
+
+ @Test
+ public void testIteratePositions() {
+ final int positions = ComplicationLayoutParams.POSITION_TOP
+ | ComplicationLayoutParams.POSITION_START
+ | ComplicationLayoutParams.POSITION_END;
+ final Consumer<Integer> consumer = mock(Consumer.class);
+
+ ComplicationLayoutParams.iteratePositions(consumer, positions);
+
+ verify(consumer).accept(ComplicationLayoutParams.POSITION_TOP);
+ verify(consumer).accept(ComplicationLayoutParams.POSITION_START);
+ verify(consumer).accept(ComplicationLayoutParams.POSITION_END);
+ verify(consumer, never()).accept(ComplicationLayoutParams.POSITION_BOTTOM);
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardQuickAffordanceProviderTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardQuickAffordanceProviderTest.kt
index cef452b..09c8e6a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardQuickAffordanceProviderTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardQuickAffordanceProviderTest.kt
@@ -20,7 +20,13 @@
import android.content.ContentValues
import android.content.pm.PackageManager
import android.content.pm.ProviderInfo
+import android.os.Bundle
+import android.os.Handler
+import android.os.IBinder
import android.os.UserHandle
+import android.testing.AndroidTestingRunner
+import android.testing.TestableLooper
+import android.view.SurfaceControlViewHost
import androidx.test.filters.SmallTest
import com.android.internal.widget.LockPatternUtils
import com.android.systemui.SystemUIAppComponentFactoryBase
@@ -36,6 +42,9 @@
import com.android.systemui.keyguard.data.repository.KeyguardQuickAffordanceRepository
import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
import com.android.systemui.keyguard.domain.interactor.KeyguardQuickAffordanceInteractor
+import com.android.systemui.keyguard.ui.preview.KeyguardPreviewRenderer
+import com.android.systemui.keyguard.ui.preview.KeyguardPreviewRendererFactory
+import com.android.systemui.keyguard.ui.preview.KeyguardRemotePreviewManager
import com.android.systemui.plugins.ActivityStarter
import com.android.systemui.settings.UserFileManager
import com.android.systemui.settings.UserTracker
@@ -43,40 +52,53 @@
import com.android.systemui.shared.quickaffordance.data.content.KeyguardQuickAffordanceProviderContract as Contract
import com.android.systemui.statusbar.policy.KeyguardStateController
import com.android.systemui.util.FakeSharedPreferences
+import com.android.systemui.util.mockito.any
import com.android.systemui.util.mockito.mock
import com.android.systemui.util.mockito.whenever
import com.android.systemui.util.settings.FakeSettings
import com.google.common.truth.Truth.assertThat
-import kotlinx.coroutines.CoroutineScope
-import kotlinx.coroutines.Dispatchers
-import kotlinx.coroutines.runBlocking
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.StandardTestDispatcher
+import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.runTest
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
-import org.junit.runners.JUnit4
import org.mockito.ArgumentMatchers.anyInt
import org.mockito.ArgumentMatchers.anyString
import org.mockito.Mock
import org.mockito.Mockito.verify
import org.mockito.MockitoAnnotations
+@OptIn(ExperimentalCoroutinesApi::class)
@SmallTest
-@RunWith(JUnit4::class)
+@RunWith(AndroidTestingRunner::class)
+@TestableLooper.RunWithLooper(setAsMainLooper = true)
class KeyguardQuickAffordanceProviderTest : SysuiTestCase() {
@Mock private lateinit var lockPatternUtils: LockPatternUtils
@Mock private lateinit var keyguardStateController: KeyguardStateController
@Mock private lateinit var userTracker: UserTracker
@Mock private lateinit var activityStarter: ActivityStarter
+ @Mock private lateinit var previewRendererFactory: KeyguardPreviewRendererFactory
+ @Mock private lateinit var previewRenderer: KeyguardPreviewRenderer
+ @Mock private lateinit var backgroundHandler: Handler
+ @Mock private lateinit var previewSurfacePackage: SurfaceControlViewHost.SurfacePackage
private lateinit var underTest: KeyguardQuickAffordanceProvider
+ private lateinit var testScope: TestScope
+
@Before
fun setUp() {
MockitoAnnotations.initMocks(this)
+ whenever(previewRenderer.surfacePackage).thenReturn(previewSurfacePackage)
+ whenever(previewRendererFactory.create(any())).thenReturn(previewRenderer)
+ whenever(backgroundHandler.looper).thenReturn(TestableLooper.get(this).looper)
underTest = KeyguardQuickAffordanceProvider()
- val scope = CoroutineScope(IMMEDIATE)
+ val testDispatcher = StandardTestDispatcher()
+ testScope = TestScope(testDispatcher)
val localUserSelectionManager =
KeyguardQuickAffordanceLocalUserSelectionManager(
context = context,
@@ -96,7 +118,7 @@
)
val remoteUserSelectionManager =
KeyguardQuickAffordanceRemoteUserSelectionManager(
- scope = scope,
+ scope = testScope.backgroundScope,
userTracker = userTracker,
clientFactory = FakeKeyguardQuickAffordanceProviderClientFactory(userTracker),
userHandle = UserHandle.SYSTEM,
@@ -104,7 +126,7 @@
val quickAffordanceRepository =
KeyguardQuickAffordanceRepository(
appContext = context,
- scope = scope,
+ scope = testScope.backgroundScope,
localUserSelectionManager = localUserSelectionManager,
remoteUserSelectionManager = remoteUserSelectionManager,
userTracker = userTracker,
@@ -123,8 +145,8 @@
),
legacySettingSyncer =
KeyguardQuickAffordanceLegacySettingSyncer(
- scope = scope,
- backgroundDispatcher = IMMEDIATE,
+ scope = testScope.backgroundScope,
+ backgroundDispatcher = testDispatcher,
secureSettings = FakeSettings(),
selectionsManager = localUserSelectionManager,
),
@@ -148,6 +170,12 @@
},
repository = { quickAffordanceRepository },
)
+ underTest.previewManager =
+ KeyguardRemotePreviewManager(
+ previewRendererFactory = previewRendererFactory,
+ mainDispatcher = testDispatcher,
+ backgroundHandler = backgroundHandler,
+ )
underTest.attachInfoForTesting(
context,
@@ -190,7 +218,7 @@
@Test
fun `insert and query selection`() =
- runBlocking(IMMEDIATE) {
+ testScope.runTest {
val slotId = KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_START
val affordanceId = AFFORDANCE_2
val affordanceName = AFFORDANCE_2_NAME
@@ -214,7 +242,7 @@
@Test
fun `query slots`() =
- runBlocking(IMMEDIATE) {
+ testScope.runTest {
assertThat(querySlots())
.isEqualTo(
listOf(
@@ -232,7 +260,7 @@
@Test
fun `query affordances`() =
- runBlocking(IMMEDIATE) {
+ testScope.runTest {
assertThat(queryAffordances())
.isEqualTo(
listOf(
@@ -252,7 +280,7 @@
@Test
fun `delete and query selection`() =
- runBlocking(IMMEDIATE) {
+ testScope.runTest {
insertSelection(
slotId = KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_START,
affordanceId = AFFORDANCE_1,
@@ -286,7 +314,7 @@
@Test
fun `delete all selections in a slot`() =
- runBlocking(IMMEDIATE) {
+ testScope.runTest {
insertSelection(
slotId = KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_START,
affordanceId = AFFORDANCE_1,
@@ -316,6 +344,23 @@
)
}
+ @Test
+ fun preview() =
+ testScope.runTest {
+ val hostToken: IBinder = mock()
+ whenever(previewRenderer.hostToken).thenReturn(hostToken)
+ val extras = Bundle()
+
+ val result = underTest.call("whatever", "anything", extras)
+
+ verify(previewRenderer).render()
+ verify(hostToken).linkToDeath(any(), anyInt())
+ assertThat(result!!).isNotNull()
+ assertThat(result.get(KeyguardRemotePreviewManager.KEY_PREVIEW_SURFACE_PACKAGE))
+ .isEqualTo(previewSurfacePackage)
+ assertThat(result.containsKey(KeyguardRemotePreviewManager.KEY_PREVIEW_CALLBACK))
+ }
+
private fun insertSelection(
slotId: String,
affordanceId: String,
@@ -451,7 +496,6 @@
)
companion object {
- private val IMMEDIATE = Dispatchers.Main.immediate
private const val AFFORDANCE_1 = "affordance_1"
private const val AFFORDANCE_2 = "affordance_2"
private const val AFFORDANCE_1_NAME = "affordance_1_name"
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ScreenLifecycleTest.java b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ScreenLifecycleTest.java
index f46d58d..70a0415 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ScreenLifecycleTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ScreenLifecycleTest.java
@@ -58,15 +58,15 @@
@Test
public void screenTurningOn() throws Exception {
Runnable onDrawn = () -> {};
- mScreen.dispatchScreenTurningOn(onDrawn);
+ mScreen.dispatchScreenTurningOn();
assertEquals(ScreenLifecycle.SCREEN_TURNING_ON, mScreen.getScreenState());
- verify(mScreenObserverMock).onScreenTurningOn(onDrawn);
+ verify(mScreenObserverMock).onScreenTurningOn();
}
@Test
public void screenTurnedOn() throws Exception {
- mScreen.dispatchScreenTurningOn(null);
+ mScreen.dispatchScreenTurningOn();
mScreen.dispatchScreenTurnedOn();
assertEquals(ScreenLifecycle.SCREEN_ON, mScreen.getScreenState());
@@ -75,7 +75,7 @@
@Test
public void screenTurningOff() throws Exception {
- mScreen.dispatchScreenTurningOn(null);
+ mScreen.dispatchScreenTurningOn();
mScreen.dispatchScreenTurnedOn();
mScreen.dispatchScreenTurningOff();
@@ -85,7 +85,7 @@
@Test
public void screenTurnedOff() throws Exception {
- mScreen.dispatchScreenTurningOn(null);
+ mScreen.dispatchScreenTurningOn();
mScreen.dispatchScreenTurnedOn();
mScreen.dispatchScreenTurningOff();
mScreen.dispatchScreenTurnedOff();
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardQuickAffordanceLocalUserSelectionManagerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardQuickAffordanceLocalUserSelectionManagerTest.kt
index 67091a9..3d65713 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardQuickAffordanceLocalUserSelectionManagerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardQuickAffordanceLocalUserSelectionManagerTest.kt
@@ -165,6 +165,10 @@
@Test
fun `remembers selections by user`() = runTest {
+ overrideResource(
+ R.array.config_keyguardQuickAffordanceDefaults,
+ arrayOf<String>(),
+ )
val slot1 = "slot_1"
val slot2 = "slot_2"
val affordance1 = "affordance_1"
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardQuickAffordanceRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardQuickAffordanceRepositoryTest.kt
index c40488a..c187a3f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardQuickAffordanceRepositoryTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardQuickAffordanceRepositoryTest.kt
@@ -114,6 +114,11 @@
userHandle = UserHandle.SYSTEM,
)
+ overrideResource(
+ R.array.config_keyguardQuickAffordanceDefaults,
+ arrayOf<String>(),
+ )
+
underTest =
KeyguardQuickAffordanceRepository(
appContext = context,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardTransitionRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardTransitionRepositoryTest.kt
index ce9c1da..5d2f0eb 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardTransitionRepositoryTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardTransitionRepositoryTest.kt
@@ -16,13 +16,10 @@
package com.android.systemui.keyguard.data.repository
-import android.animation.AnimationHandler.AnimationFrameCallbackProvider
import android.animation.ValueAnimator
import android.util.Log
import android.util.Log.TerribleFailure
import android.util.Log.TerribleFailureHandler
-import android.view.Choreographer.FrameCallback
-import androidx.test.filters.FlakyTest
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.animation.Interpolators
@@ -32,22 +29,17 @@
import com.android.systemui.keyguard.shared.model.TransitionInfo
import com.android.systemui.keyguard.shared.model.TransitionState
import com.android.systemui.keyguard.shared.model.TransitionStep
+import com.android.systemui.keyguard.util.KeyguardTransitionRunner
import com.google.common.truth.Truth.assertThat
import java.math.BigDecimal
import java.math.RoundingMode
import java.util.UUID
-import kotlinx.coroutines.CoroutineScope
-import kotlinx.coroutines.Dispatchers
-import kotlinx.coroutines.Job
-import kotlinx.coroutines.flow.MutableStateFlow
-import kotlinx.coroutines.flow.collect
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach
-import kotlinx.coroutines.launch
-import kotlinx.coroutines.runBlocking
-import kotlinx.coroutines.yield
+import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.runCurrent
+import kotlinx.coroutines.test.runTest
import org.junit.After
-import org.junit.Assert.fail
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
@@ -60,12 +52,14 @@
private lateinit var underTest: KeyguardTransitionRepository
private lateinit var oldWtfHandler: TerribleFailureHandler
private lateinit var wtfHandler: WtfHandler
+ private lateinit var runner: KeyguardTransitionRunner
@Before
fun setUp() {
underTest = KeyguardTransitionRepositoryImpl()
wtfHandler = WtfHandler()
oldWtfHandler = Log.setWtfHandler(wtfHandler)
+ runner = KeyguardTransitionRunner(underTest)
}
@After
@@ -75,56 +69,37 @@
@Test
fun `startTransition runs animator to completion`() =
- runBlocking(IMMEDIATE) {
- val (animator, provider) = setupAnimator(this)
-
+ TestScope().runTest {
val steps = mutableListOf<TransitionStep>()
val job = underTest.transition(AOD, LOCKSCREEN).onEach { steps.add(it) }.launchIn(this)
- underTest.startTransition(TransitionInfo(OWNER_NAME, AOD, LOCKSCREEN, animator))
-
- val startTime = System.currentTimeMillis()
- while (animator.isRunning()) {
- yield()
- if (System.currentTimeMillis() - startTime > MAX_TEST_DURATION) {
- fail("Failed test due to excessive runtime of: $MAX_TEST_DURATION")
- }
- }
+ runner.startTransition(
+ this,
+ TransitionInfo(OWNER_NAME, AOD, LOCKSCREEN, getAnimator()),
+ maxFrames = 100
+ )
assertSteps(steps, listWithStep(BigDecimal(.1)), AOD, LOCKSCREEN)
-
job.cancel()
- provider.stop()
}
@Test
- @FlakyTest(bugId = 260213291)
- fun `starting second transition will cancel the first transition`() {
- runBlocking(IMMEDIATE) {
- val (animator, provider) = setupAnimator(this)
-
+ fun `starting second transition will cancel the first transition`() =
+ TestScope().runTest {
val steps = mutableListOf<TransitionStep>()
val job = underTest.transition(AOD, LOCKSCREEN).onEach { steps.add(it) }.launchIn(this)
-
- underTest.startTransition(TransitionInfo(OWNER_NAME, AOD, LOCKSCREEN, animator))
- // 3 yields(), alternating with the animator, results in a value 0.1, which can be
- // canceled and tested against
- yield()
- yield()
- yield()
+ runner.startTransition(
+ this,
+ TransitionInfo(OWNER_NAME, AOD, LOCKSCREEN, getAnimator()),
+ maxFrames = 3,
+ )
// Now start 2nd transition, which will interrupt the first
val job2 = underTest.transition(LOCKSCREEN, AOD).onEach { steps.add(it) }.launchIn(this)
- val (animator2, provider2) = setupAnimator(this)
- underTest.startTransition(TransitionInfo(OWNER_NAME, LOCKSCREEN, AOD, animator2))
-
- val startTime = System.currentTimeMillis()
- while (animator2.isRunning()) {
- yield()
- if (System.currentTimeMillis() - startTime > MAX_TEST_DURATION) {
- fail("Failed test due to excessive runtime of: $MAX_TEST_DURATION")
- }
- }
+ runner.startTransition(
+ this,
+ TransitionInfo(OWNER_NAME, LOCKSCREEN, AOD, getAnimator()),
+ )
val firstTransitionSteps = listWithStep(step = BigDecimal(.1), stop = BigDecimal(.1))
assertSteps(steps.subList(0, 4), firstTransitionSteps, AOD, LOCKSCREEN)
@@ -134,31 +109,25 @@
job.cancel()
job2.cancel()
- provider.stop()
- provider2.stop()
}
- }
@Test
fun `Null animator enables manual control with updateTransition`() =
- runBlocking(IMMEDIATE) {
+ TestScope().runTest {
val steps = mutableListOf<TransitionStep>()
val job = underTest.transition(AOD, LOCKSCREEN).onEach { steps.add(it) }.launchIn(this)
val uuid =
underTest.startTransition(
- TransitionInfo(
- ownerName = OWNER_NAME,
- from = AOD,
- to = LOCKSCREEN,
- animator = null,
- )
+ TransitionInfo(OWNER_NAME, AOD, LOCKSCREEN, animator = null)
)
+ runCurrent()
checkNotNull(uuid).let {
underTest.updateTransition(it, 0.5f, TransitionState.RUNNING)
underTest.updateTransition(it, 1f, TransitionState.FINISHED)
}
+ runCurrent()
assertThat(steps.size).isEqualTo(3)
assertThat(steps[0])
@@ -256,57 +225,11 @@
assertThat(wtfHandler.failed).isFalse()
}
- private fun setupAnimator(
- scope: CoroutineScope
- ): Pair<ValueAnimator, TestFrameCallbackProvider> {
- val animator =
- ValueAnimator().apply {
- setInterpolator(Interpolators.LINEAR)
- setDuration(ANIMATION_DURATION)
- }
-
- val provider = TestFrameCallbackProvider(animator, scope)
- provider.start()
-
- return Pair(animator, provider)
- }
-
- /** Gives direct control over ValueAnimator. See [AnimationHandler] */
- private class TestFrameCallbackProvider(
- private val animator: ValueAnimator,
- private val scope: CoroutineScope,
- ) : AnimationFrameCallbackProvider {
-
- private var frameCount = 1L
- private var frames = MutableStateFlow(Pair<Long, FrameCallback?>(0L, null))
- private var job: Job? = null
-
- fun start() {
- animator.getAnimationHandler().setProvider(this)
-
- job =
- scope.launch {
- frames.collect {
- // Delay is required for AnimationHandler to properly register a callback
- yield()
- val (frameNumber, callback) = it
- callback?.doFrame(frameNumber)
- }
- }
+ private fun getAnimator(): ValueAnimator {
+ return ValueAnimator().apply {
+ setInterpolator(Interpolators.LINEAR)
+ setDuration(10)
}
-
- fun stop() {
- job?.cancel()
- animator.getAnimationHandler().setProvider(null)
- }
-
- override fun postFrameCallback(cb: FrameCallback) {
- frames.value = Pair(frameCount++, cb)
- }
- override fun postCommitCallback(runnable: Runnable) {}
- override fun getFrameTime() = frameCount
- override fun getFrameDelay() = 1L
- override fun setFrameDelay(delay: Long) {}
}
private class WtfHandler : TerribleFailureHandler {
@@ -317,9 +240,6 @@
}
companion object {
- private const val MAX_TEST_DURATION = 100L
- private const val ANIMATION_DURATION = 10L
- private const val OWNER_NAME = "Test"
- private val IMMEDIATE = Dispatchers.Main.immediate
+ private const val OWNER_NAME = "KeyguardTransitionRunner"
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorTest.kt
index 11fe905..58a8530 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorTest.kt
@@ -20,9 +20,11 @@
import android.os.UserHandle
import androidx.test.filters.SmallTest
import com.android.internal.widget.LockPatternUtils
+import com.android.systemui.R
import com.android.systemui.SysuiTestCase
import com.android.systemui.common.shared.model.ContentDescription
import com.android.systemui.common.shared.model.Icon
+import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.flags.FakeFeatureFlags
import com.android.systemui.flags.Flags
import com.android.systemui.keyguard.data.quickaffordance.BuiltInKeyguardQuickAffordanceKeys
@@ -49,14 +51,10 @@
import com.android.systemui.util.mockito.whenever
import com.android.systemui.util.settings.FakeSettings
import com.google.common.truth.Truth.assertThat
-import kotlinx.coroutines.CoroutineScope
-import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.ExperimentalCoroutinesApi
-import kotlinx.coroutines.flow.launchIn
-import kotlinx.coroutines.flow.onEach
-import kotlinx.coroutines.runBlocking
-import kotlinx.coroutines.test.runBlockingTest
-import kotlinx.coroutines.yield
+import kotlinx.coroutines.test.StandardTestDispatcher
+import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.runTest
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
@@ -78,6 +76,7 @@
private lateinit var underTest: KeyguardQuickAffordanceInteractor
+ private lateinit var testScope: TestScope
private lateinit var repository: FakeKeyguardRepository
private lateinit var homeControls: FakeKeyguardQuickAffordanceConfig
private lateinit var quickAccessWallet: FakeKeyguardQuickAffordanceConfig
@@ -99,7 +98,8 @@
)
qrCodeScanner =
FakeKeyguardQuickAffordanceConfig(BuiltInKeyguardQuickAffordanceKeys.QR_CODE_SCANNER)
- val scope = CoroutineScope(IMMEDIATE)
+ val testDispatcher = StandardTestDispatcher()
+ testScope = TestScope(testDispatcher)
val localUserSelectionManager =
KeyguardQuickAffordanceLocalUserSelectionManager(
@@ -120,7 +120,7 @@
)
val remoteUserSelectionManager =
KeyguardQuickAffordanceRemoteUserSelectionManager(
- scope = scope,
+ scope = testScope.backgroundScope,
userTracker = userTracker,
clientFactory = FakeKeyguardQuickAffordanceProviderClientFactory(userTracker),
userHandle = UserHandle.SYSTEM,
@@ -128,14 +128,14 @@
val quickAffordanceRepository =
KeyguardQuickAffordanceRepository(
appContext = context,
- scope = scope,
+ scope = testScope.backgroundScope,
localUserSelectionManager = localUserSelectionManager,
remoteUserSelectionManager = remoteUserSelectionManager,
userTracker = userTracker,
legacySettingSyncer =
KeyguardQuickAffordanceLegacySettingSyncer(
- scope = scope,
- backgroundDispatcher = IMMEDIATE,
+ scope = testScope.backgroundScope,
+ backgroundDispatcher = testDispatcher,
secureSettings = FakeSettings(),
selectionsManager = localUserSelectionManager,
),
@@ -175,88 +175,76 @@
}
@Test
- fun `quickAffordance - bottom start affordance is visible`() = runBlockingTest {
- val configKey = BuiltInKeyguardQuickAffordanceKeys.HOME_CONTROLS
- homeControls.setState(
- KeyguardQuickAffordanceConfig.LockScreenState.Visible(
- icon = ICON,
- activationState = ActivationState.Active,
+ fun `quickAffordance - bottom start affordance is visible`() =
+ testScope.runTest {
+ val configKey = BuiltInKeyguardQuickAffordanceKeys.HOME_CONTROLS
+ homeControls.setState(
+ KeyguardQuickAffordanceConfig.LockScreenState.Visible(
+ icon = ICON,
+ activationState = ActivationState.Active,
+ )
)
- )
- var latest: KeyguardQuickAffordanceModel? = null
- val job =
- underTest
- .quickAffordance(KeyguardQuickAffordancePosition.BOTTOM_START)
- .onEach { latest = it }
- .launchIn(this)
- // The interactor has an onStart { emit(Hidden) } to cover for upstream configs that don't
- // produce an initial value. We yield to give the coroutine time to emit the first real
- // value from our config.
- yield()
+ val collectedValue =
+ collectLastValue(
+ underTest.quickAffordance(KeyguardQuickAffordancePosition.BOTTOM_START)
+ )
- assertThat(latest).isInstanceOf(KeyguardQuickAffordanceModel.Visible::class.java)
- val visibleModel = latest as KeyguardQuickAffordanceModel.Visible
- assertThat(visibleModel.configKey).isEqualTo(configKey)
- assertThat(visibleModel.icon).isEqualTo(ICON)
- assertThat(visibleModel.icon.contentDescription)
- .isEqualTo(ContentDescription.Resource(res = CONTENT_DESCRIPTION_RESOURCE_ID))
- assertThat(visibleModel.activationState).isEqualTo(ActivationState.Active)
- job.cancel()
- }
+ assertThat(collectedValue())
+ .isInstanceOf(KeyguardQuickAffordanceModel.Visible::class.java)
+ val visibleModel = collectedValue() as KeyguardQuickAffordanceModel.Visible
+ assertThat(visibleModel.configKey).isEqualTo(configKey)
+ assertThat(visibleModel.icon).isEqualTo(ICON)
+ assertThat(visibleModel.icon.contentDescription)
+ .isEqualTo(ContentDescription.Resource(res = CONTENT_DESCRIPTION_RESOURCE_ID))
+ assertThat(visibleModel.activationState).isEqualTo(ActivationState.Active)
+ }
@Test
- fun `quickAffordance - bottom end affordance is visible`() = runBlockingTest {
- val configKey = BuiltInKeyguardQuickAffordanceKeys.QUICK_ACCESS_WALLET
- quickAccessWallet.setState(
- KeyguardQuickAffordanceConfig.LockScreenState.Visible(
- icon = ICON,
+ fun `quickAffordance - bottom end affordance is visible`() =
+ testScope.runTest {
+ val configKey = BuiltInKeyguardQuickAffordanceKeys.QUICK_ACCESS_WALLET
+ quickAccessWallet.setState(
+ KeyguardQuickAffordanceConfig.LockScreenState.Visible(
+ icon = ICON,
+ )
)
- )
- var latest: KeyguardQuickAffordanceModel? = null
- val job =
- underTest
- .quickAffordance(KeyguardQuickAffordancePosition.BOTTOM_END)
- .onEach { latest = it }
- .launchIn(this)
- // The interactor has an onStart { emit(Hidden) } to cover for upstream configs that don't
- // produce an initial value. We yield to give the coroutine time to emit the first real
- // value from our config.
- yield()
+ val collectedValue =
+ collectLastValue(
+ underTest.quickAffordance(KeyguardQuickAffordancePosition.BOTTOM_END)
+ )
- assertThat(latest).isInstanceOf(KeyguardQuickAffordanceModel.Visible::class.java)
- val visibleModel = latest as KeyguardQuickAffordanceModel.Visible
- assertThat(visibleModel.configKey).isEqualTo(configKey)
- assertThat(visibleModel.icon).isEqualTo(ICON)
- assertThat(visibleModel.icon.contentDescription)
- .isEqualTo(ContentDescription.Resource(res = CONTENT_DESCRIPTION_RESOURCE_ID))
- assertThat(visibleModel.activationState).isEqualTo(ActivationState.NotSupported)
- job.cancel()
- }
+ assertThat(collectedValue())
+ .isInstanceOf(KeyguardQuickAffordanceModel.Visible::class.java)
+ val visibleModel = collectedValue() as KeyguardQuickAffordanceModel.Visible
+ assertThat(visibleModel.configKey).isEqualTo(configKey)
+ assertThat(visibleModel.icon).isEqualTo(ICON)
+ assertThat(visibleModel.icon.contentDescription)
+ .isEqualTo(ContentDescription.Resource(res = CONTENT_DESCRIPTION_RESOURCE_ID))
+ assertThat(visibleModel.activationState).isEqualTo(ActivationState.NotSupported)
+ }
@Test
- fun `quickAffordance - bottom start affordance hidden while dozing`() = runBlockingTest {
- repository.setDozing(true)
- homeControls.setState(
- KeyguardQuickAffordanceConfig.LockScreenState.Visible(
- icon = ICON,
+ fun `quickAffordance - bottom start affordance hidden while dozing`() =
+ testScope.runTest {
+ repository.setDozing(true)
+ homeControls.setState(
+ KeyguardQuickAffordanceConfig.LockScreenState.Visible(
+ icon = ICON,
+ )
)
- )
- var latest: KeyguardQuickAffordanceModel? = null
- val job =
- underTest
- .quickAffordance(KeyguardQuickAffordancePosition.BOTTOM_START)
- .onEach { latest = it }
- .launchIn(this)
- assertThat(latest).isEqualTo(KeyguardQuickAffordanceModel.Hidden)
- job.cancel()
- }
+ val collectedValue =
+ collectLastValue(
+ underTest.quickAffordance(KeyguardQuickAffordancePosition.BOTTOM_START)
+ )
+ assertThat(collectedValue()).isEqualTo(KeyguardQuickAffordanceModel.Hidden)
+ }
@Test
fun `quickAffordance - bottom start affordance hidden when lockscreen is not showing`() =
- runBlockingTest {
+ testScope.runTest {
repository.setKeyguardShowing(false)
homeControls.setState(
KeyguardQuickAffordanceConfig.LockScreenState.Visible(
@@ -264,19 +252,50 @@
)
)
- var latest: KeyguardQuickAffordanceModel? = null
- val job =
- underTest
- .quickAffordance(KeyguardQuickAffordancePosition.BOTTOM_START)
- .onEach { latest = it }
- .launchIn(this)
- assertThat(latest).isEqualTo(KeyguardQuickAffordanceModel.Hidden)
- job.cancel()
+ val collectedValue =
+ collectLastValue(
+ underTest.quickAffordance(KeyguardQuickAffordancePosition.BOTTOM_START)
+ )
+ assertThat(collectedValue()).isEqualTo(KeyguardQuickAffordanceModel.Hidden)
+ }
+
+ @Test
+ fun `quickAffordanceAlwaysVisible - even when lock screen not showing and dozing`() =
+ testScope.runTest {
+ repository.setKeyguardShowing(false)
+ repository.setDozing(true)
+ val configKey = BuiltInKeyguardQuickAffordanceKeys.HOME_CONTROLS
+ homeControls.setState(
+ KeyguardQuickAffordanceConfig.LockScreenState.Visible(
+ icon = ICON,
+ activationState = ActivationState.Active,
+ )
+ )
+
+ val collectedValue =
+ collectLastValue(
+ underTest.quickAffordanceAlwaysVisible(
+ KeyguardQuickAffordancePosition.BOTTOM_START
+ )
+ )
+ assertThat(collectedValue())
+ .isInstanceOf(KeyguardQuickAffordanceModel.Visible::class.java)
+ val visibleModel = collectedValue() as KeyguardQuickAffordanceModel.Visible
+ assertThat(visibleModel.configKey).isEqualTo(configKey)
+ assertThat(visibleModel.icon).isEqualTo(ICON)
+ assertThat(visibleModel.icon.contentDescription)
+ .isEqualTo(ContentDescription.Resource(res = CONTENT_DESCRIPTION_RESOURCE_ID))
+ assertThat(visibleModel.activationState).isEqualTo(ActivationState.Active)
}
@Test
fun select() =
- runBlocking(IMMEDIATE) {
+ testScope.runTest {
+ overrideResource(
+ R.array.config_keyguardQuickAffordanceDefaults,
+ arrayOf<String>(),
+ )
+
featureFlags.set(Flags.CUSTOMIZABLE_LOCK_SCREEN_QUICK_AFFORDANCES, true)
homeControls.setState(
KeyguardQuickAffordanceConfig.LockScreenState.Visible(icon = ICON)
@@ -296,23 +315,18 @@
)
)
- var startConfig: KeyguardQuickAffordanceModel? = null
- val job1 =
- underTest
- .quickAffordance(KeyguardQuickAffordancePosition.BOTTOM_START)
- .onEach { startConfig = it }
- .launchIn(this)
- var endConfig: KeyguardQuickAffordanceModel? = null
- val job2 =
- underTest
- .quickAffordance(KeyguardQuickAffordancePosition.BOTTOM_END)
- .onEach { endConfig = it }
- .launchIn(this)
+ val startConfig =
+ collectLastValue(
+ underTest.quickAffordance(KeyguardQuickAffordancePosition.BOTTOM_START)
+ )
+ val endConfig =
+ collectLastValue(
+ underTest.quickAffordance(KeyguardQuickAffordancePosition.BOTTOM_END)
+ )
underTest.select(KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_START, homeControls.key)
- yield()
- yield()
- assertThat(startConfig)
+
+ assertThat(startConfig())
.isEqualTo(
KeyguardQuickAffordanceModel.Visible(
configKey =
@@ -322,7 +336,7 @@
activationState = ActivationState.NotSupported,
)
)
- assertThat(endConfig)
+ assertThat(endConfig())
.isEqualTo(
KeyguardQuickAffordanceModel.Hidden,
)
@@ -345,9 +359,8 @@
KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_START,
quickAccessWallet.key
)
- yield()
- yield()
- assertThat(startConfig)
+
+ assertThat(startConfig())
.isEqualTo(
KeyguardQuickAffordanceModel.Visible(
configKey =
@@ -357,7 +370,7 @@
activationState = ActivationState.NotSupported,
)
)
- assertThat(endConfig)
+ assertThat(endConfig())
.isEqualTo(
KeyguardQuickAffordanceModel.Hidden,
)
@@ -377,9 +390,8 @@
)
underTest.select(KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_END, qrCodeScanner.key)
- yield()
- yield()
- assertThat(startConfig)
+
+ assertThat(startConfig())
.isEqualTo(
KeyguardQuickAffordanceModel.Visible(
configKey =
@@ -389,7 +401,7 @@
activationState = ActivationState.NotSupported,
)
)
- assertThat(endConfig)
+ assertThat(endConfig())
.isEqualTo(
KeyguardQuickAffordanceModel.Visible(
configKey =
@@ -420,14 +432,11 @@
),
)
)
-
- job1.cancel()
- job2.cancel()
}
@Test
fun `unselect - one`() =
- runBlocking(IMMEDIATE) {
+ testScope.runTest {
featureFlags.set(Flags.CUSTOMIZABLE_LOCK_SCREEN_QUICK_AFFORDANCES, true)
homeControls.setState(
KeyguardQuickAffordanceConfig.LockScreenState.Visible(icon = ICON)
@@ -439,34 +448,23 @@
KeyguardQuickAffordanceConfig.LockScreenState.Visible(icon = ICON)
)
- var startConfig: KeyguardQuickAffordanceModel? = null
- val job1 =
- underTest
- .quickAffordance(KeyguardQuickAffordancePosition.BOTTOM_START)
- .onEach { startConfig = it }
- .launchIn(this)
- var endConfig: KeyguardQuickAffordanceModel? = null
- val job2 =
- underTest
- .quickAffordance(KeyguardQuickAffordancePosition.BOTTOM_END)
- .onEach { endConfig = it }
- .launchIn(this)
+ val startConfig =
+ collectLastValue(
+ underTest.quickAffordance(KeyguardQuickAffordancePosition.BOTTOM_START)
+ )
+ val endConfig =
+ collectLastValue(
+ underTest.quickAffordance(KeyguardQuickAffordancePosition.BOTTOM_END)
+ )
underTest.select(KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_START, homeControls.key)
- yield()
- yield()
underTest.select(KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_END, quickAccessWallet.key)
- yield()
- yield()
-
underTest.unselect(KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_START, homeControls.key)
- yield()
- yield()
- assertThat(startConfig)
+ assertThat(startConfig())
.isEqualTo(
KeyguardQuickAffordanceModel.Hidden,
)
- assertThat(endConfig)
+ assertThat(endConfig())
.isEqualTo(
KeyguardQuickAffordanceModel.Visible(
configKey =
@@ -495,14 +493,12 @@
KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_END,
quickAccessWallet.key
)
- yield()
- yield()
- assertThat(startConfig)
+ assertThat(startConfig())
.isEqualTo(
KeyguardQuickAffordanceModel.Hidden,
)
- assertThat(endConfig)
+ assertThat(endConfig())
.isEqualTo(
KeyguardQuickAffordanceModel.Hidden,
)
@@ -513,14 +509,11 @@
KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_END to emptyList(),
)
)
-
- job1.cancel()
- job2.cancel()
}
@Test
fun `unselect - all`() =
- runBlocking(IMMEDIATE) {
+ testScope.runTest {
featureFlags.set(Flags.CUSTOMIZABLE_LOCK_SCREEN_QUICK_AFFORDANCES, true)
homeControls.setState(
KeyguardQuickAffordanceConfig.LockScreenState.Visible(icon = ICON)
@@ -533,15 +526,8 @@
)
underTest.select(KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_START, homeControls.key)
- yield()
- yield()
underTest.select(KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_END, quickAccessWallet.key)
- yield()
- yield()
-
underTest.unselect(KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_START, null)
- yield()
- yield()
assertThat(underTest.getSelections())
.isEqualTo(
@@ -562,8 +548,6 @@
KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_END,
null,
)
- yield()
- yield()
assertThat(underTest.getSelections())
.isEqualTo(
@@ -584,6 +568,5 @@
)
}
private const val CONTENT_DESCRIPTION_RESOURCE_ID = 1337
- private val IMMEDIATE = Dispatchers.Main.immediate
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionScenariosTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionScenariosTest.kt
new file mode 100644
index 0000000..a6cf840
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionScenariosTest.kt
@@ -0,0 +1,140 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.keyguard.domain.interactor
+
+import android.animation.ValueAnimator
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.animation.Interpolators
+import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository
+import com.android.systemui.keyguard.data.repository.KeyguardTransitionRepository
+import com.android.systemui.keyguard.data.repository.KeyguardTransitionRepositoryImpl
+import com.android.systemui.keyguard.shared.model.KeyguardState
+import com.android.systemui.keyguard.shared.model.TransitionInfo
+import com.android.systemui.keyguard.shared.model.WakeSleepReason
+import com.android.systemui.keyguard.shared.model.WakefulnessModel
+import com.android.systemui.keyguard.shared.model.WakefulnessState
+import com.android.systemui.keyguard.util.KeyguardTransitionRunner
+import com.android.systemui.shade.data.repository.FakeShadeRepository
+import com.android.systemui.shade.data.repository.ShadeRepository
+import com.android.systemui.util.mockito.withArgCaptor
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.cancelChildren
+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.junit.runners.JUnit4
+import org.mockito.Mock
+import org.mockito.Mockito.verify
+import org.mockito.MockitoAnnotations
+
+/**
+ * Class for testing user journeys through the interactors. They will all be activated during setup,
+ * to ensure the expected transitions are still triggered.
+ */
+@SmallTest
+@RunWith(JUnit4::class)
+class KeyguardTransitionScenariosTest : SysuiTestCase() {
+ private lateinit var testScope: TestScope
+
+ private lateinit var keyguardRepository: FakeKeyguardRepository
+ private lateinit var shadeRepository: ShadeRepository
+
+ // Used to issue real transition steps for test input
+ private lateinit var runner: KeyguardTransitionRunner
+ private lateinit var transitionRepository: KeyguardTransitionRepository
+
+ // Used to verify transition requests for test output
+ @Mock private lateinit var mockTransitionRepository: KeyguardTransitionRepository
+
+ private lateinit var lockscreenBouncerTransitionInteractor:
+ LockscreenBouncerTransitionInteractor
+
+ @Before
+ fun setUp() {
+ MockitoAnnotations.initMocks(this)
+ testScope = TestScope()
+
+ keyguardRepository = FakeKeyguardRepository()
+ shadeRepository = FakeShadeRepository()
+
+ /* Used to issue full transition steps, to better simulate a real device */
+ transitionRepository = KeyguardTransitionRepositoryImpl()
+ runner = KeyguardTransitionRunner(transitionRepository)
+
+ lockscreenBouncerTransitionInteractor =
+ LockscreenBouncerTransitionInteractor(
+ scope = testScope,
+ keyguardInteractor = KeyguardInteractor(keyguardRepository),
+ shadeRepository = shadeRepository,
+ keyguardTransitionRepository = mockTransitionRepository,
+ keyguardTransitionInteractor = KeyguardTransitionInteractor(transitionRepository),
+ )
+ lockscreenBouncerTransitionInteractor.start()
+ }
+
+ @Test
+ fun `LOCKSCREEN to BOUNCER via bouncer showing call`() =
+ testScope.runTest {
+ // GIVEN a device that has at least woken up
+ keyguardRepository.setWakefulnessModel(startingToWake())
+ runCurrent()
+
+ // GIVEN a transition has run to LOCKSCREEN
+ runner.startTransition(
+ testScope,
+ TransitionInfo(
+ ownerName = "",
+ from = KeyguardState.OFF,
+ to = KeyguardState.LOCKSCREEN,
+ animator =
+ ValueAnimator().apply {
+ duration = 10
+ interpolator = Interpolators.LINEAR
+ },
+ )
+ )
+ runCurrent()
+
+ // WHEN the bouncer is set to show
+ keyguardRepository.setBouncerShowing(true)
+ runCurrent()
+
+ val info =
+ withArgCaptor<TransitionInfo> {
+ verify(mockTransitionRepository).startTransition(capture())
+ }
+ // THEN a transition to BOUNCER should occur
+ assertThat(info.ownerName).isEqualTo("LockscreenBouncerTransitionInteractor")
+ assertThat(info.from).isEqualTo(KeyguardState.LOCKSCREEN)
+ assertThat(info.to).isEqualTo(KeyguardState.BOUNCER)
+ assertThat(info.animator).isNotNull()
+
+ coroutineContext.cancelChildren()
+ }
+
+ private fun startingToWake() =
+ WakefulnessModel(
+ WakefulnessState.STARTING_TO_WAKE,
+ true,
+ WakeSleepReason.OTHER,
+ WakeSleepReason.OTHER
+ )
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBottomAreaViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBottomAreaViewModelTest.kt
index 83a5d0e..0abff88 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBottomAreaViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBottomAreaViewModelTest.kt
@@ -23,6 +23,7 @@
import com.android.systemui.SysuiTestCase
import com.android.systemui.animation.Expandable
import com.android.systemui.common.shared.model.Icon
+import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.doze.util.BurnInHelperWrapper
import com.android.systemui.flags.FakeFeatureFlags
import com.android.systemui.flags.Flags
@@ -44,20 +45,21 @@
import com.android.systemui.plugins.ActivityStarter
import com.android.systemui.settings.UserFileManager
import com.android.systemui.settings.UserTracker
+import com.android.systemui.shared.keyguard.shared.model.KeyguardQuickAffordanceSlots
import com.android.systemui.statusbar.policy.KeyguardStateController
import com.android.systemui.util.FakeSharedPreferences
import com.android.systemui.util.mockito.any
import com.android.systemui.util.mockito.mock
+import com.android.systemui.util.mockito.whenever
import com.android.systemui.util.settings.FakeSettings
import com.google.common.truth.Truth.assertThat
import kotlin.math.max
import kotlin.math.min
-import kotlinx.coroutines.CoroutineScope
-import kotlinx.coroutines.Dispatchers
-import kotlinx.coroutines.flow.launchIn
-import kotlinx.coroutines.flow.onEach
-import kotlinx.coroutines.test.runBlockingTest
-import kotlinx.coroutines.yield
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.test.StandardTestDispatcher
+import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.runTest
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
@@ -67,9 +69,9 @@
import org.mockito.Mock
import org.mockito.Mockito
import org.mockito.Mockito.verifyZeroInteractions
-import org.mockito.Mockito.`when` as whenever
import org.mockito.MockitoAnnotations
+@OptIn(ExperimentalCoroutinesApi::class)
@SmallTest
@RunWith(JUnit4::class)
class KeyguardBottomAreaViewModelTest : SysuiTestCase() {
@@ -83,6 +85,7 @@
private lateinit var underTest: KeyguardBottomAreaViewModel
+ private lateinit var testScope: TestScope
private lateinit var repository: FakeKeyguardRepository
private lateinit var registry: FakeKeyguardQuickAffordanceRegistry
private lateinit var homeControlsQuickAffordanceConfig: FakeKeyguardQuickAffordanceConfig
@@ -123,7 +126,8 @@
whenever(userTracker.userHandle).thenReturn(mock())
whenever(lockPatternUtils.getStrongAuthForUser(anyInt()))
.thenReturn(LockPatternUtils.StrongAuthTracker.STRONG_AUTH_NOT_REQUIRED)
- val scope = CoroutineScope(IMMEDIATE)
+ val testDispatcher = StandardTestDispatcher()
+ testScope = TestScope(testDispatcher)
val localUserSelectionManager =
KeyguardQuickAffordanceLocalUserSelectionManager(
context = context,
@@ -143,7 +147,7 @@
)
val remoteUserSelectionManager =
KeyguardQuickAffordanceRemoteUserSelectionManager(
- scope = scope,
+ scope = testScope.backgroundScope,
userTracker = userTracker,
clientFactory = FakeKeyguardQuickAffordanceProviderClientFactory(userTracker),
userHandle = UserHandle.SYSTEM,
@@ -151,14 +155,14 @@
val quickAffordanceRepository =
KeyguardQuickAffordanceRepository(
appContext = context,
- scope = scope,
+ scope = testScope.backgroundScope,
localUserSelectionManager = localUserSelectionManager,
remoteUserSelectionManager = remoteUserSelectionManager,
userTracker = userTracker,
legacySettingSyncer =
KeyguardQuickAffordanceLegacySettingSyncer(
- scope = scope,
- backgroundDispatcher = IMMEDIATE,
+ scope = testScope.backgroundScope,
+ backgroundDispatcher = testDispatcher,
secureSettings = FakeSettings(),
selectionsManager = localUserSelectionManager,
),
@@ -194,366 +198,394 @@
}
@Test
- fun `startButton - present - visible model - starts activity on click`() = runBlockingTest {
- repository.setKeyguardShowing(true)
- var latest: KeyguardQuickAffordanceViewModel? = null
- val job = underTest.startButton.onEach { latest = it }.launchIn(this)
+ fun `startButton - present - visible model - starts activity on click`() =
+ testScope.runTest {
+ repository.setKeyguardShowing(true)
+ val latest = collectLastValue(underTest.startButton)
- val testConfig =
- TestConfig(
- isVisible = true,
- isClickable = true,
- isActivated = true,
- icon = mock(),
- canShowWhileLocked = false,
- intent = Intent("action"),
- )
- val configKey =
- setUpQuickAffordanceModel(
- position = KeyguardQuickAffordancePosition.BOTTOM_START,
- testConfig = testConfig,
- )
-
- assertQuickAffordanceViewModel(
- viewModel = latest,
- testConfig = testConfig,
- configKey = configKey,
- )
- job.cancel()
- }
-
- @Test
- fun `endButton - present - visible model - do nothing on click`() = runBlockingTest {
- repository.setKeyguardShowing(true)
- var latest: KeyguardQuickAffordanceViewModel? = null
- val job = underTest.endButton.onEach { latest = it }.launchIn(this)
-
- val config =
- TestConfig(
- isVisible = true,
- isClickable = true,
- icon = mock(),
- canShowWhileLocked = false,
- intent = null, // This will cause it to tell the system that the click was handled.
- )
- val configKey =
- setUpQuickAffordanceModel(
- position = KeyguardQuickAffordancePosition.BOTTOM_END,
- testConfig = config,
- )
-
- assertQuickAffordanceViewModel(
- viewModel = latest,
- testConfig = config,
- configKey = configKey,
- )
- job.cancel()
- }
-
- @Test
- fun `startButton - not present - model is hidden`() = runBlockingTest {
- var latest: KeyguardQuickAffordanceViewModel? = null
- val job = underTest.startButton.onEach { latest = it }.launchIn(this)
-
- val config =
- TestConfig(
- isVisible = false,
- )
- val configKey =
- setUpQuickAffordanceModel(
- position = KeyguardQuickAffordancePosition.BOTTOM_START,
- testConfig = config,
- )
-
- assertQuickAffordanceViewModel(
- viewModel = latest,
- testConfig = config,
- configKey = configKey,
- )
- job.cancel()
- }
-
- @Test
- fun animateButtonReveal() = runBlockingTest {
- repository.setKeyguardShowing(true)
- val testConfig =
- TestConfig(
- isVisible = true,
- isClickable = true,
- icon = mock(),
- canShowWhileLocked = false,
- intent = Intent("action"),
- )
-
- setUpQuickAffordanceModel(
- position = KeyguardQuickAffordancePosition.BOTTOM_START,
- testConfig = testConfig,
- )
-
- val values = mutableListOf<Boolean>()
- val job = underTest.startButton.onEach { values.add(it.animateReveal) }.launchIn(this)
-
- repository.setAnimateDozingTransitions(true)
- yield()
- repository.setAnimateDozingTransitions(false)
- yield()
-
- // Note the extra false value in the beginning. This is to cover for the initial value
- // inserted by the quick affordance interactor which it does to cover for config
- // implementations that don't emit an initial value.
- assertThat(values).isEqualTo(listOf(false, false, true, false))
- job.cancel()
- }
-
- @Test
- fun isOverlayContainerVisible() = runBlockingTest {
- val values = mutableListOf<Boolean>()
- val job = underTest.isOverlayContainerVisible.onEach(values::add).launchIn(this)
-
- repository.setDozing(true)
- repository.setDozing(false)
-
- assertThat(values).isEqualTo(listOf(true, false, true))
- job.cancel()
- }
-
- @Test
- fun alpha() = runBlockingTest {
- val values = mutableListOf<Float>()
- val job = underTest.alpha.onEach(values::add).launchIn(this)
-
- repository.setBottomAreaAlpha(0.1f)
- repository.setBottomAreaAlpha(0.5f)
- repository.setBottomAreaAlpha(0.2f)
- repository.setBottomAreaAlpha(0f)
-
- assertThat(values).isEqualTo(listOf(1f, 0.1f, 0.5f, 0.2f, 0f))
- job.cancel()
- }
-
- @Test
- fun isIndicationAreaPadded() = runBlockingTest {
- repository.setKeyguardShowing(true)
- val values = mutableListOf<Boolean>()
- val job = underTest.isIndicationAreaPadded.onEach(values::add).launchIn(this)
-
- setUpQuickAffordanceModel(
- position = KeyguardQuickAffordancePosition.BOTTOM_START,
- testConfig =
+ val testConfig =
TestConfig(
isVisible = true,
isClickable = true,
+ isActivated = true,
icon = mock(),
- canShowWhileLocked = true,
+ canShowWhileLocked = false,
+ intent = Intent("action"),
)
- )
- setUpQuickAffordanceModel(
- position = KeyguardQuickAffordancePosition.BOTTOM_END,
- testConfig =
+ val configKey =
+ setUpQuickAffordanceModel(
+ position = KeyguardQuickAffordancePosition.BOTTOM_START,
+ testConfig = testConfig,
+ )
+
+ assertQuickAffordanceViewModel(
+ viewModel = latest(),
+ testConfig = testConfig,
+ configKey = configKey,
+ )
+ }
+
+ @Test
+ fun `startButton - in preview mode - visible even when keyguard not showing`() =
+ testScope.runTest {
+ underTest.enablePreviewMode(KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_START)
+ repository.setKeyguardShowing(false)
+ val latest = collectLastValue(underTest.startButton)
+
+ val icon: Icon = mock()
+ val configKey =
+ setUpQuickAffordanceModel(
+ position = KeyguardQuickAffordancePosition.BOTTOM_START,
+ testConfig =
+ TestConfig(
+ isVisible = true,
+ isClickable = true,
+ isActivated = true,
+ icon = icon,
+ canShowWhileLocked = false,
+ intent = Intent("action"),
+ ),
+ )
+
+ assertQuickAffordanceViewModel(
+ viewModel = latest(),
+ testConfig =
+ TestConfig(
+ isVisible = true,
+ isClickable = false,
+ isActivated = true,
+ icon = icon,
+ canShowWhileLocked = false,
+ intent = Intent("action"),
+ ),
+ configKey = configKey,
+ )
+ assertThat(latest()?.isSelected).isTrue()
+ }
+
+ @Test
+ fun `endButton - present - visible model - do nothing on click`() =
+ testScope.runTest {
+ repository.setKeyguardShowing(true)
+ val latest = collectLastValue(underTest.endButton)
+
+ val config =
TestConfig(
isVisible = true,
isClickable = true,
icon = mock(),
canShowWhileLocked = false,
+ intent =
+ null, // This will cause it to tell the system that the click was handled.
)
- )
- setUpQuickAffordanceModel(
- position = KeyguardQuickAffordancePosition.BOTTOM_START,
- testConfig =
+ val configKey =
+ setUpQuickAffordanceModel(
+ position = KeyguardQuickAffordancePosition.BOTTOM_END,
+ testConfig = config,
+ )
+
+ assertQuickAffordanceViewModel(
+ viewModel = latest(),
+ testConfig = config,
+ configKey = configKey,
+ )
+ }
+
+ @Test
+ fun `startButton - not present - model is hidden`() =
+ testScope.runTest {
+ val latest = collectLastValue(underTest.startButton)
+
+ val config =
TestConfig(
isVisible = false,
)
- )
- setUpQuickAffordanceModel(
- position = KeyguardQuickAffordancePosition.BOTTOM_END,
- testConfig =
+ val configKey =
+ setUpQuickAffordanceModel(
+ position = KeyguardQuickAffordancePosition.BOTTOM_START,
+ testConfig = config,
+ )
+
+ assertQuickAffordanceViewModel(
+ viewModel = latest(),
+ testConfig = config,
+ configKey = configKey,
+ )
+ }
+
+ @Test
+ fun animateButtonReveal() =
+ testScope.runTest {
+ repository.setKeyguardShowing(true)
+ val testConfig =
TestConfig(
- isVisible = false,
+ isVisible = true,
+ isClickable = true,
+ icon = mock(),
+ canShowWhileLocked = false,
+ intent = Intent("action"),
)
- )
- assertThat(values)
- .isEqualTo(
- listOf(
- // Initially, no button is visible so the indication area is not padded.
- false,
- // Once we add the first visible button, the indication area becomes padded.
- // This
- // continues to be true after we add the second visible button and even after we
- // make the first button not visible anymore.
- true,
- // Once both buttons are not visible, the indication area is, again, not padded.
- false,
+ setUpQuickAffordanceModel(
+ position = KeyguardQuickAffordancePosition.BOTTOM_START,
+ testConfig = testConfig,
+ )
+
+ val value = collectLastValue(underTest.startButton.map { it.animateReveal })
+
+ assertThat(value()).isFalse()
+ repository.setAnimateDozingTransitions(true)
+ assertThat(value()).isTrue()
+ repository.setAnimateDozingTransitions(false)
+ assertThat(value()).isFalse()
+ }
+
+ @Test
+ fun isOverlayContainerVisible() =
+ testScope.runTest {
+ val value = collectLastValue(underTest.isOverlayContainerVisible)
+
+ assertThat(value()).isTrue()
+ repository.setDozing(true)
+ assertThat(value()).isFalse()
+ repository.setDozing(false)
+ assertThat(value()).isTrue()
+ }
+
+ @Test
+ fun alpha() =
+ testScope.runTest {
+ val value = collectLastValue(underTest.alpha)
+
+ assertThat(value()).isEqualTo(1f)
+ repository.setBottomAreaAlpha(0.1f)
+ assertThat(value()).isEqualTo(0.1f)
+ repository.setBottomAreaAlpha(0.5f)
+ assertThat(value()).isEqualTo(0.5f)
+ repository.setBottomAreaAlpha(0.2f)
+ assertThat(value()).isEqualTo(0.2f)
+ repository.setBottomAreaAlpha(0f)
+ assertThat(value()).isEqualTo(0f)
+ }
+
+ @Test
+ fun `alpha - in preview mode - does not change`() =
+ testScope.runTest {
+ underTest.enablePreviewMode(null)
+ val value = collectLastValue(underTest.alpha)
+
+ assertThat(value()).isEqualTo(1f)
+ repository.setBottomAreaAlpha(0.1f)
+ assertThat(value()).isEqualTo(1f)
+ repository.setBottomAreaAlpha(0.5f)
+ assertThat(value()).isEqualTo(1f)
+ repository.setBottomAreaAlpha(0.2f)
+ assertThat(value()).isEqualTo(1f)
+ repository.setBottomAreaAlpha(0f)
+ assertThat(value()).isEqualTo(1f)
+ }
+
+ @Test
+ fun isIndicationAreaPadded() =
+ testScope.runTest {
+ repository.setKeyguardShowing(true)
+ val value = collectLastValue(underTest.isIndicationAreaPadded)
+
+ assertThat(value()).isFalse()
+ setUpQuickAffordanceModel(
+ position = KeyguardQuickAffordancePosition.BOTTOM_START,
+ testConfig =
+ TestConfig(
+ isVisible = true,
+ isClickable = true,
+ icon = mock(),
+ canShowWhileLocked = true,
+ )
+ )
+ assertThat(value()).isTrue()
+ setUpQuickAffordanceModel(
+ position = KeyguardQuickAffordancePosition.BOTTOM_END,
+ testConfig =
+ TestConfig(
+ isVisible = true,
+ isClickable = true,
+ icon = mock(),
+ canShowWhileLocked = false,
+ )
+ )
+ assertThat(value()).isTrue()
+ setUpQuickAffordanceModel(
+ position = KeyguardQuickAffordancePosition.BOTTOM_START,
+ testConfig =
+ TestConfig(
+ isVisible = false,
+ )
+ )
+ assertThat(value()).isTrue()
+ setUpQuickAffordanceModel(
+ position = KeyguardQuickAffordancePosition.BOTTOM_END,
+ testConfig =
+ TestConfig(
+ isVisible = false,
+ )
+ )
+ assertThat(value()).isFalse()
+ }
+
+ @Test
+ fun indicationAreaTranslationX() =
+ testScope.runTest {
+ val value = collectLastValue(underTest.indicationAreaTranslationX)
+
+ assertThat(value()).isEqualTo(0f)
+ repository.setClockPosition(100, 100)
+ assertThat(value()).isEqualTo(100f)
+ repository.setClockPosition(200, 100)
+ assertThat(value()).isEqualTo(200f)
+ repository.setClockPosition(200, 200)
+ assertThat(value()).isEqualTo(200f)
+ repository.setClockPosition(300, 100)
+ assertThat(value()).isEqualTo(300f)
+ }
+
+ @Test
+ fun indicationAreaTranslationY() =
+ testScope.runTest {
+ val value =
+ collectLastValue(underTest.indicationAreaTranslationY(DEFAULT_BURN_IN_OFFSET))
+
+ // Negative 0 - apparently there's a difference in floating point arithmetic - FML
+ assertThat(value()).isEqualTo(-0f)
+ val expected1 = setDozeAmountAndCalculateExpectedTranslationY(0.1f)
+ assertThat(value()).isEqualTo(expected1)
+ val expected2 = setDozeAmountAndCalculateExpectedTranslationY(0.2f)
+ assertThat(value()).isEqualTo(expected2)
+ val expected3 = setDozeAmountAndCalculateExpectedTranslationY(0.5f)
+ assertThat(value()).isEqualTo(expected3)
+ val expected4 = setDozeAmountAndCalculateExpectedTranslationY(1f)
+ assertThat(value()).isEqualTo(expected4)
+ }
+
+ @Test
+ fun `isClickable - true when alpha at threshold`() =
+ testScope.runTest {
+ repository.setKeyguardShowing(true)
+ repository.setBottomAreaAlpha(
+ KeyguardBottomAreaViewModel.AFFORDANCE_FULLY_OPAQUE_ALPHA_THRESHOLD
+ )
+
+ val testConfig =
+ TestConfig(
+ isVisible = true,
+ isClickable = true,
+ icon = mock(),
+ canShowWhileLocked = false,
+ intent = Intent("action"),
)
- )
- job.cancel()
- }
+ val configKey =
+ setUpQuickAffordanceModel(
+ position = KeyguardQuickAffordancePosition.BOTTOM_START,
+ testConfig = testConfig,
+ )
- @Test
- fun indicationAreaTranslationX() = runBlockingTest {
- val values = mutableListOf<Float>()
- val job = underTest.indicationAreaTranslationX.onEach(values::add).launchIn(this)
+ val latest = collectLastValue(underTest.startButton)
- repository.setClockPosition(100, 100)
- repository.setClockPosition(200, 100)
- repository.setClockPosition(200, 200)
- repository.setClockPosition(300, 100)
-
- assertThat(values).isEqualTo(listOf(0f, 100f, 200f, 300f))
- job.cancel()
- }
-
- @Test
- fun indicationAreaTranslationY() = runBlockingTest {
- val values = mutableListOf<Float>()
- val job =
- underTest
- .indicationAreaTranslationY(DEFAULT_BURN_IN_OFFSET)
- .onEach(values::add)
- .launchIn(this)
-
- val expectedTranslationValues =
- listOf(
- -0f, // Negative 0 - apparently there's a difference in floating point arithmetic -
- // FML
- setDozeAmountAndCalculateExpectedTranslationY(0.1f),
- setDozeAmountAndCalculateExpectedTranslationY(0.2f),
- setDozeAmountAndCalculateExpectedTranslationY(0.5f),
- setDozeAmountAndCalculateExpectedTranslationY(1f),
- )
-
- assertThat(values).isEqualTo(expectedTranslationValues)
- job.cancel()
- }
-
- @Test
- fun `isClickable - true when alpha at threshold`() = runBlockingTest {
- repository.setKeyguardShowing(true)
- repository.setBottomAreaAlpha(
- KeyguardBottomAreaViewModel.AFFORDANCE_FULLY_OPAQUE_ALPHA_THRESHOLD
- )
-
- val testConfig =
- TestConfig(
- isVisible = true,
- isClickable = true,
- icon = mock(),
- canShowWhileLocked = false,
- intent = Intent("action"),
- )
- val configKey =
- setUpQuickAffordanceModel(
- position = KeyguardQuickAffordancePosition.BOTTOM_START,
+ assertQuickAffordanceViewModel(
+ viewModel = latest(),
testConfig = testConfig,
+ configKey = configKey,
)
-
- var latest: KeyguardQuickAffordanceViewModel? = null
- val job = underTest.startButton.onEach { latest = it }.launchIn(this)
- // The interactor has an onStart { emit(Hidden) } to cover for upstream configs that don't
- // produce an initial value. We yield to give the coroutine time to emit the first real
- // value from our config.
- yield()
-
- assertQuickAffordanceViewModel(
- viewModel = latest,
- testConfig = testConfig,
- configKey = configKey,
- )
- job.cancel()
- }
+ }
@Test
- fun `isClickable - true when alpha above threshold`() = runBlockingTest {
- repository.setKeyguardShowing(true)
- var latest: KeyguardQuickAffordanceViewModel? = null
- val job = underTest.startButton.onEach { latest = it }.launchIn(this)
- repository.setBottomAreaAlpha(
- min(1f, KeyguardBottomAreaViewModel.AFFORDANCE_FULLY_OPAQUE_ALPHA_THRESHOLD + 0.1f),
- )
-
- val testConfig =
- TestConfig(
- isVisible = true,
- isClickable = true,
- icon = mock(),
- canShowWhileLocked = false,
- intent = Intent("action"),
+ fun `isClickable - true when alpha above threshold`() =
+ testScope.runTest {
+ repository.setKeyguardShowing(true)
+ val latest = collectLastValue(underTest.startButton)
+ repository.setBottomAreaAlpha(
+ min(1f, KeyguardBottomAreaViewModel.AFFORDANCE_FULLY_OPAQUE_ALPHA_THRESHOLD + 0.1f),
)
- val configKey =
- setUpQuickAffordanceModel(
- position = KeyguardQuickAffordancePosition.BOTTOM_START,
+
+ val testConfig =
+ TestConfig(
+ isVisible = true,
+ isClickable = true,
+ icon = mock(),
+ canShowWhileLocked = false,
+ intent = Intent("action"),
+ )
+ val configKey =
+ setUpQuickAffordanceModel(
+ position = KeyguardQuickAffordancePosition.BOTTOM_START,
+ testConfig = testConfig,
+ )
+
+ assertQuickAffordanceViewModel(
+ viewModel = latest(),
testConfig = testConfig,
+ configKey = configKey,
)
-
- assertQuickAffordanceViewModel(
- viewModel = latest,
- testConfig = testConfig,
- configKey = configKey,
- )
- job.cancel()
- }
+ }
@Test
- fun `isClickable - false when alpha below threshold`() = runBlockingTest {
- repository.setKeyguardShowing(true)
- var latest: KeyguardQuickAffordanceViewModel? = null
- val job = underTest.startButton.onEach { latest = it }.launchIn(this)
- repository.setBottomAreaAlpha(
- max(0f, KeyguardBottomAreaViewModel.AFFORDANCE_FULLY_OPAQUE_ALPHA_THRESHOLD - 0.1f),
- )
-
- val testConfig =
- TestConfig(
- isVisible = true,
- isClickable = false,
- icon = mock(),
- canShowWhileLocked = false,
- intent = Intent("action"),
+ fun `isClickable - false when alpha below threshold`() =
+ testScope.runTest {
+ repository.setKeyguardShowing(true)
+ val latest = collectLastValue(underTest.startButton)
+ repository.setBottomAreaAlpha(
+ max(0f, KeyguardBottomAreaViewModel.AFFORDANCE_FULLY_OPAQUE_ALPHA_THRESHOLD - 0.1f),
)
- val configKey =
- setUpQuickAffordanceModel(
- position = KeyguardQuickAffordancePosition.BOTTOM_START,
+
+ val testConfig =
+ TestConfig(
+ isVisible = true,
+ isClickable = false,
+ icon = mock(),
+ canShowWhileLocked = false,
+ intent = Intent("action"),
+ )
+ val configKey =
+ setUpQuickAffordanceModel(
+ position = KeyguardQuickAffordancePosition.BOTTOM_START,
+ testConfig = testConfig,
+ )
+
+ assertQuickAffordanceViewModel(
+ viewModel = latest(),
testConfig = testConfig,
+ configKey = configKey,
)
-
- assertQuickAffordanceViewModel(
- viewModel = latest,
- testConfig = testConfig,
- configKey = configKey,
- )
- job.cancel()
- }
+ }
@Test
- fun `isClickable - false when alpha at zero`() = runBlockingTest {
- repository.setKeyguardShowing(true)
- var latest: KeyguardQuickAffordanceViewModel? = null
- val job = underTest.startButton.onEach { latest = it }.launchIn(this)
- repository.setBottomAreaAlpha(0f)
+ fun `isClickable - false when alpha at zero`() =
+ testScope.runTest {
+ repository.setKeyguardShowing(true)
+ val latest = collectLastValue(underTest.startButton)
+ repository.setBottomAreaAlpha(0f)
- val testConfig =
- TestConfig(
- isVisible = true,
- isClickable = false,
- icon = mock(),
- canShowWhileLocked = false,
- intent = Intent("action"),
- )
- val configKey =
- setUpQuickAffordanceModel(
- position = KeyguardQuickAffordancePosition.BOTTOM_START,
+ val testConfig =
+ TestConfig(
+ isVisible = true,
+ isClickable = false,
+ icon = mock(),
+ canShowWhileLocked = false,
+ intent = Intent("action"),
+ )
+ val configKey =
+ setUpQuickAffordanceModel(
+ position = KeyguardQuickAffordancePosition.BOTTOM_START,
+ testConfig = testConfig,
+ )
+
+ assertQuickAffordanceViewModel(
+ viewModel = latest(),
testConfig = testConfig,
+ configKey = configKey,
)
+ }
- assertQuickAffordanceViewModel(
- viewModel = latest,
- testConfig = testConfig,
- configKey = configKey,
- )
- job.cancel()
- }
-
- private suspend fun setDozeAmountAndCalculateExpectedTranslationY(dozeAmount: Float): Float {
+ private fun setDozeAmountAndCalculateExpectedTranslationY(dozeAmount: Float): Float {
repository.setDozeAmount(dozeAmount)
return dozeAmount * (RETURNED_BURN_IN_OFFSET - DEFAULT_BURN_IN_OFFSET)
}
@@ -583,7 +615,6 @@
when (testConfig.isActivated) {
true -> ActivationState.Active
false -> ActivationState.Inactive
- null -> ActivationState.NotSupported
}
)
} else {
@@ -636,6 +667,5 @@
companion object {
private const val DEFAULT_BURN_IN_OFFSET = 5
private const val RETURNED_BURN_IN_OFFSET = 3
- private val IMMEDIATE = Dispatchers.Main.immediate
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/util/KeyguardTransitionRunner.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/util/KeyguardTransitionRunner.kt
new file mode 100644
index 0000000..c88f84a
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/util/KeyguardTransitionRunner.kt
@@ -0,0 +1,101 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.keyguard.util
+
+import android.animation.AnimationHandler.AnimationFrameCallbackProvider
+import android.animation.ValueAnimator
+import android.view.Choreographer.FrameCallback
+import com.android.systemui.keyguard.data.repository.KeyguardTransitionRepository
+import com.android.systemui.keyguard.shared.model.TransitionInfo
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.Job
+import kotlinx.coroutines.delay
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.collect
+import kotlinx.coroutines.launch
+import kotlinx.coroutines.withContext
+import org.junit.Assert.fail
+
+/**
+ * Gives direct control over ValueAnimator, in order to make transition tests deterministic. See
+ * [AnimationHandler]. Animators are required to be run on the main thread, so dispatch accordingly.
+ */
+class KeyguardTransitionRunner(
+ val repository: KeyguardTransitionRepository,
+) : AnimationFrameCallbackProvider {
+
+ private var frameCount = 1L
+ private var frames = MutableStateFlow(Pair<Long, FrameCallback?>(0L, null))
+ private var job: Job? = null
+ private var isTerminated = false
+
+ /**
+ * For transitions being directed by an animator. Will control the number of frames being
+ * generated so the values are deterministic.
+ */
+ suspend fun startTransition(scope: CoroutineScope, info: TransitionInfo, maxFrames: Int = 100) {
+ // AnimationHandler uses ThreadLocal storage, and ValueAnimators MUST start from main
+ // thread
+ withContext(Dispatchers.Main) {
+ info.animator!!.getAnimationHandler().setProvider(this@KeyguardTransitionRunner)
+ }
+
+ job =
+ scope.launch {
+ frames.collect {
+ val (frameNumber, callback) = it
+
+ isTerminated = frameNumber >= maxFrames
+ if (!isTerminated) {
+ withContext(Dispatchers.Main) { callback?.doFrame(frameNumber) }
+ }
+ }
+ }
+ withContext(Dispatchers.Main) { repository.startTransition(info) }
+
+ waitUntilComplete(info.animator!!)
+ }
+
+ suspend private fun waitUntilComplete(animator: ValueAnimator) {
+ withContext(Dispatchers.Main) {
+ val startTime = System.currentTimeMillis()
+ while (!isTerminated && animator.isRunning()) {
+ delay(1)
+ if (System.currentTimeMillis() - startTime > MAX_TEST_DURATION) {
+ fail("Failed test due to excessive runtime of: $MAX_TEST_DURATION")
+ }
+ }
+
+ animator.getAnimationHandler().setProvider(null)
+ }
+
+ job?.cancel()
+ }
+
+ override fun postFrameCallback(cb: FrameCallback) {
+ frames.value = Pair(frameCount++, cb)
+ }
+ override fun postCommitCallback(runnable: Runnable) {}
+ override fun getFrameTime() = frameCount
+ override fun getFrameDelay() = 1L
+ override fun setFrameDelay(delay: Long) {}
+
+ companion object {
+ private const val MAX_TEST_DURATION = 100L
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/common/MediaTttLoggerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/common/MediaTttLoggerTest.kt
index e009e86..0e7bf8d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/common/MediaTttLoggerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/common/MediaTttLoggerTest.kt
@@ -22,6 +22,7 @@
import com.android.systemui.log.LogBufferFactory
import com.android.systemui.plugins.log.LogBuffer
import com.android.systemui.plugins.log.LogcatEchoTracker
+import com.android.systemui.temporarydisplay.TemporaryViewInfo
import com.google.common.truth.Truth.assertThat
import java.io.PrintWriter
import java.io.StringWriter
@@ -33,7 +34,7 @@
class MediaTttLoggerTest : SysuiTestCase() {
private lateinit var buffer: LogBuffer
- private lateinit var logger: MediaTttLogger
+ private lateinit var logger: MediaTttLogger<TemporaryViewInfo>
@Before
fun setUp () {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/common/MediaTttUtilsTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/common/MediaTttUtilsTest.kt
index cce3e36..561867f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/common/MediaTttUtilsTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/common/MediaTttUtilsTest.kt
@@ -25,6 +25,7 @@
import com.android.systemui.common.shared.model.ContentDescription
import com.android.systemui.common.shared.model.ContentDescription.Companion.loadContentDescription
import com.android.systemui.common.shared.model.Icon
+import com.android.systemui.temporarydisplay.TemporaryViewInfo
import com.android.systemui.util.mockito.any
import com.google.common.truth.Truth.assertThat
import org.junit.Before
@@ -40,7 +41,7 @@
private lateinit var appIconFromPackageName: Drawable
@Mock private lateinit var packageManager: PackageManager
@Mock private lateinit var applicationInfo: ApplicationInfo
- @Mock private lateinit var logger: MediaTttLogger
+ @Mock private lateinit var logger: MediaTttLogger<TemporaryViewInfo>
@Before
fun setUp() {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/receiver/FakeMediaTttChipControllerReceiver.kt b/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/receiver/FakeMediaTttChipControllerReceiver.kt
index 4aa982e..bad3f03 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/receiver/FakeMediaTttChipControllerReceiver.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/receiver/FakeMediaTttChipControllerReceiver.kt
@@ -27,13 +27,14 @@
import com.android.systemui.statusbar.CommandQueue
import com.android.systemui.statusbar.policy.ConfigurationController
import com.android.systemui.util.concurrency.DelayableExecutor
+import com.android.systemui.util.time.SystemClock
import com.android.systemui.util.view.ViewUtil
import com.android.systemui.util.wakelock.WakeLock
class FakeMediaTttChipControllerReceiver(
commandQueue: CommandQueue,
context: Context,
- logger: MediaTttLogger,
+ logger: MediaTttLogger<ChipReceiverInfo>,
windowManager: WindowManager,
mainExecutor: DelayableExecutor,
accessibilityManager: AccessibilityManager,
@@ -44,6 +45,7 @@
uiEventLogger: MediaTttReceiverUiEventLogger,
viewUtil: ViewUtil,
wakeLockBuilder: WakeLock.Builder,
+ systemClock: SystemClock,
) :
MediaTttChipControllerReceiver(
commandQueue,
@@ -59,6 +61,7 @@
uiEventLogger,
viewUtil,
wakeLockBuilder,
+ systemClock,
) {
override fun animateViewOut(view: ViewGroup, onAnimationEnd: Runnable) {
// Just bypass the animation in tests
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/receiver/MediaTttChipControllerReceiverTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/receiver/MediaTttChipControllerReceiverTest.kt
index 23f7cdb..ef0bfb7 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/receiver/MediaTttChipControllerReceiverTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/receiver/MediaTttChipControllerReceiverTest.kt
@@ -67,7 +67,7 @@
@Mock
private lateinit var applicationInfo: ApplicationInfo
@Mock
- private lateinit var logger: MediaTttLogger
+ private lateinit var logger: MediaTttLogger<ChipReceiverInfo>
@Mock
private lateinit var accessibilityManager: AccessibilityManager
@Mock
@@ -128,6 +128,7 @@
receiverUiEventLogger,
viewUtil,
fakeWakeLockBuilder,
+ fakeClock,
)
controllerReceiver.start()
@@ -155,6 +156,7 @@
receiverUiEventLogger,
viewUtil,
fakeWakeLockBuilder,
+ fakeClock,
)
controllerReceiver.start()
@@ -193,6 +195,36 @@
}
@Test
+ fun commandQueueCallback_transferToReceiverSucceeded_noChipShown() {
+ commandQueueCallback.updateMediaTapToTransferReceiverDisplay(
+ StatusBarManager.MEDIA_TRANSFER_RECEIVER_STATE_TRANSFER_TO_RECEIVER_SUCCEEDED,
+ routeInfo,
+ null,
+ null
+ )
+
+ verify(windowManager, never()).addView(any(), any())
+ assertThat(uiEventLoggerFake.eventId(0)).isEqualTo(
+ MediaTttReceiverUiEvents.MEDIA_TTT_RECEIVER_TRANSFER_TO_RECEIVER_SUCCEEDED.id
+ )
+ }
+
+ @Test
+ fun commandQueueCallback_transferToReceiverFailed_noChipShown() {
+ commandQueueCallback.updateMediaTapToTransferReceiverDisplay(
+ StatusBarManager.MEDIA_TRANSFER_RECEIVER_STATE_TRANSFER_TO_RECEIVER_FAILED,
+ routeInfo,
+ null,
+ null
+ )
+
+ verify(windowManager, never()).addView(any(), any())
+ assertThat(uiEventLoggerFake.eventId(0)).isEqualTo(
+ MediaTttReceiverUiEvents.MEDIA_TTT_RECEIVER_TRANSFER_TO_RECEIVER_FAILED.id
+ )
+ }
+
+ @Test
fun commandQueueCallback_closeThenFar_chipShownThenHidden() {
commandQueueCallback.updateMediaTapToTransferReceiverDisplay(
StatusBarManager.MEDIA_TRANSFER_RECEIVER_STATE_CLOSE_TO_SENDER,
@@ -214,6 +246,48 @@
}
@Test
+ fun commandQueueCallback_closeThenSucceeded_chipShownThenHidden() {
+ commandQueueCallback.updateMediaTapToTransferReceiverDisplay(
+ StatusBarManager.MEDIA_TRANSFER_RECEIVER_STATE_CLOSE_TO_SENDER,
+ routeInfo,
+ null,
+ null
+ )
+
+ commandQueueCallback.updateMediaTapToTransferReceiverDisplay(
+ StatusBarManager.MEDIA_TRANSFER_RECEIVER_STATE_TRANSFER_TO_RECEIVER_SUCCEEDED,
+ routeInfo,
+ null,
+ null
+ )
+
+ val viewCaptor = ArgumentCaptor.forClass(View::class.java)
+ verify(windowManager).addView(viewCaptor.capture(), any())
+ verify(windowManager).removeView(viewCaptor.value)
+ }
+
+ @Test
+ fun commandQueueCallback_closeThenFailed_chipShownThenHidden() {
+ commandQueueCallback.updateMediaTapToTransferReceiverDisplay(
+ StatusBarManager.MEDIA_TRANSFER_RECEIVER_STATE_CLOSE_TO_SENDER,
+ routeInfo,
+ null,
+ null
+ )
+
+ commandQueueCallback.updateMediaTapToTransferReceiverDisplay(
+ StatusBarManager.MEDIA_TRANSFER_RECEIVER_STATE_TRANSFER_TO_RECEIVER_FAILED,
+ routeInfo,
+ null,
+ null
+ )
+
+ val viewCaptor = ArgumentCaptor.forClass(View::class.java)
+ verify(windowManager).addView(viewCaptor.capture(), any())
+ verify(windowManager).removeView(viewCaptor.value)
+ }
+
+ @Test
fun commandQueueCallback_closeThenFar_wakeLockAcquiredThenReleased() {
commandQueueCallback.updateMediaTapToTransferReceiverDisplay(
StatusBarManager.MEDIA_TRANSFER_RECEIVER_STATE_CLOSE_TO_SENDER,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/sender/MediaTttSenderCoordinatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/sender/MediaTttSenderCoordinatorTest.kt
index 311740e..b03a545 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/sender/MediaTttSenderCoordinatorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/sender/MediaTttSenderCoordinatorTest.kt
@@ -45,6 +45,7 @@
import com.android.systemui.statusbar.VibratorHelper
import com.android.systemui.statusbar.policy.ConfigurationController
import com.android.systemui.temporarydisplay.chipbar.ChipbarCoordinator
+import com.android.systemui.temporarydisplay.chipbar.ChipbarInfo
import com.android.systemui.temporarydisplay.chipbar.ChipbarLogger
import com.android.systemui.temporarydisplay.chipbar.FakeChipbarCoordinator
import com.android.systemui.util.concurrency.FakeExecutor
@@ -83,7 +84,7 @@
@Mock private lateinit var falsingManager: FalsingManager
@Mock private lateinit var falsingCollector: FalsingCollector
@Mock private lateinit var chipbarLogger: ChipbarLogger
- @Mock private lateinit var logger: MediaTttLogger
+ @Mock private lateinit var logger: MediaTttLogger<ChipbarInfo>
@Mock private lateinit var mediaTttFlags: MediaTttFlags
@Mock private lateinit var packageManager: PackageManager
@Mock private lateinit var powerManager: PowerManager
@@ -142,6 +143,7 @@
viewUtil,
vibratorHelper,
fakeWakeLockBuilder,
+ fakeClock,
)
chipbarCoordinator.start()
diff --git a/packages/SystemUI/tests/src/com/android/systemui/notetask/OWNERS b/packages/SystemUI/tests/src/com/android/systemui/notetask/OWNERS
new file mode 100644
index 0000000..7ccb316
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/notetask/OWNERS
@@ -0,0 +1,8 @@
+# Bug component: 1254381
+azappone@google.com
+achalke@google.com
+juliacr@google.com
+madym@google.com
+mgalhardo@google.com
+petrcermak@google.com
+vanjan@google.com
\ No newline at end of file
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelControllerBaseTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelControllerBaseTest.java
index caf8321..5058373 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelControllerBaseTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelControllerBaseTest.java
@@ -226,7 +226,8 @@
+ " " + mockTileViewString + "\n"
+ " media bounds: null\n"
+ " horizontal layout: false\n"
- + " last orientation: 0\n";
+ + " last orientation: 0\n"
+ + " mShouldUseSplitNotificationShade: false\n";
assertEquals(expected, w.getBuffer().toString());
}
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 5e082f6..6cf642c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelControllerTest.kt
@@ -135,10 +135,10 @@
fun configurationChange_onlySplitShadeConfigChanges_tileAreRedistributed() {
testableResources.addOverride(R.bool.config_use_split_notification_shade, false)
controller.mOnConfigurationChangedListener.onConfigurationChange(configuration)
- verify(pagedTileLayout, never()).forceTilesRedistribution()
+ verify(pagedTileLayout, never()).forceTilesRedistribution(any())
testableResources.addOverride(R.bool.config_use_split_notification_shade, true)
controller.mOnConfigurationChangedListener.onConfigurationChange(configuration)
- verify(pagedTileLayout).forceTilesRedistribution()
+ verify(pagedTileLayout).forceTilesRedistribution("Split shade state changed")
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelTest.kt
index 7c930b1..d52b296 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelTest.kt
@@ -27,6 +27,7 @@
import com.android.systemui.R
import com.android.systemui.SysuiTestCase
import com.android.systemui.plugins.qs.QSTile
+import com.android.systemui.qs.logging.QSLogger
import com.android.systemui.qs.tileimpl.QSIconViewImpl
import com.android.systemui.qs.tileimpl.QSTileViewImpl
import com.google.common.truth.Truth.assertThat
@@ -34,6 +35,7 @@
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
+import org.mockito.Mock
import org.mockito.Mockito.mock
import org.mockito.Mockito.verify
import org.mockito.MockitoAnnotations
@@ -42,6 +44,9 @@
@RunWithLooper
@SmallTest
class QSPanelTest : SysuiTestCase() {
+
+ @Mock private lateinit var qsLogger: QSLogger
+
private lateinit var testableLooper: TestableLooper
private lateinit var qsPanel: QSPanel
@@ -57,7 +62,7 @@
qsPanel = QSPanel(context, null)
qsPanel.mUsingMediaPlayer = true
- qsPanel.initialize()
+ qsPanel.initialize(qsLogger)
// QSPanel inflates a footer inside of it, mocking it here
footer = LinearLayout(context).apply { id = R.id.qs_footer }
qsPanel.addView(footer, MATCH_PARENT, 100)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/QuickQSPanelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/QuickQSPanelTest.kt
index a6a584d..3fba393 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/QuickQSPanelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/QuickQSPanelTest.kt
@@ -7,10 +7,12 @@
import android.widget.FrameLayout
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
+import com.android.systemui.qs.logging.QSLogger
import com.google.common.truth.Truth
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
+import org.mockito.Mock
import org.mockito.Mockito
import org.mockito.MockitoAnnotations
@@ -19,6 +21,8 @@
@SmallTest
class QuickQSPanelTest : SysuiTestCase() {
+ @Mock private lateinit var qsLogger: QSLogger
+
private lateinit var testableLooper: TestableLooper
private lateinit var quickQSPanel: QuickQSPanel
@@ -32,7 +36,7 @@
testableLooper.runWithLooper {
quickQSPanel = QuickQSPanel(mContext, null)
- quickQSPanel.initialize()
+ quickQSPanel.initialize(qsLogger)
quickQSPanel.onFinishInflate()
// Provides a parent with non-zero size for QSPanel
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerTest.java
index 0302dad..3512749 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerTest.java
@@ -1102,6 +1102,17 @@
mStatusBarStateController.setState(KEYGUARD);
+ assertThat(mNotificationPanelViewController.isQsExpanded()).isEqualTo(false);
+ assertThat(mNotificationPanelViewController.isQsExpandImmediate()).isEqualTo(false);
+ }
+
+ @Test
+ public void testLockedSplitShadeTransitioningToKeyguard_closesQS() {
+ enableSplitShade(true);
+ mStatusBarStateController.setState(SHADE_LOCKED);
+ mNotificationPanelViewController.setQsExpanded(true);
+
+ mStatusBarStateController.setState(KEYGUARD);
assertThat(mNotificationPanelViewController.isQsExpanded()).isEqualTo(false);
assertThat(mNotificationPanelViewController.isQsExpandImmediate()).isEqualTo(false);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/KeyguardCoordinatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/KeyguardCoordinatorTest.kt
index 5f19fac..be6b1dc 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/KeyguardCoordinatorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/KeyguardCoordinatorTest.kt
@@ -18,6 +18,8 @@
package com.android.systemui.statusbar.notification.collection.coordinator
import android.app.Notification
+import android.os.UserHandle
+import android.provider.Settings
import android.testing.AndroidTestingRunner
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
@@ -29,6 +31,7 @@
import com.android.systemui.statusbar.notification.collection.NotifPipeline
import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder
import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifFilter
+import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.Pluggable
import com.android.systemui.statusbar.notification.collection.notifcollection.NotifCollectionListener
import com.android.systemui.statusbar.notification.collection.provider.SectionHeaderVisibilityProvider
import com.android.systemui.statusbar.notification.collection.provider.SeenNotificationsProvider
@@ -38,6 +41,7 @@
import com.android.systemui.util.mockito.eq
import com.android.systemui.util.mockito.mock
import com.android.systemui.util.mockito.withArgCaptor
+import com.android.systemui.util.settings.FakeSettings
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.ExperimentalCoroutinesApi
@@ -47,6 +51,8 @@
import kotlinx.coroutines.test.runTest
import org.junit.Test
import org.junit.runner.RunWith
+import org.mockito.ArgumentMatchers.same
+import org.mockito.Mockito.anyString
import org.mockito.Mockito.clearInvocations
import org.mockito.Mockito.verify
import java.util.function.Consumer
@@ -176,6 +182,42 @@
}
@Test
+ fun unseenFilterInvalidatesWhenSettingChanges() {
+ whenever(notifPipelineFlags.shouldFilterUnseenNotifsOnKeyguard).thenReturn(true)
+
+ // GIVEN: Keyguard is not showing
+ keyguardRepository.setKeyguardShowing(false)
+ runKeyguardCoordinatorTest {
+ // GIVEN: A notification is present
+ val fakeEntry = NotificationEntryBuilder().build()
+ collectionListener.onEntryAdded(fakeEntry)
+
+ // GIVEN: The setting for filtering unseen notifications is disabled
+ showOnlyUnseenNotifsOnKeyguardSetting = false
+
+ // GIVEN: The pipeline has registered the unseen filter for invalidation
+ val invalidationListener: Pluggable.PluggableListener<NotifFilter> = mock()
+ unseenFilter.setInvalidationListener(invalidationListener)
+
+ // WHEN: The keyguard is now showing
+ keyguardRepository.setKeyguardShowing(true)
+ testScheduler.runCurrent()
+
+ // THEN: The notification is not filtered out
+ assertThat(unseenFilter.shouldFilterOut(fakeEntry, 0L)).isFalse()
+
+ // WHEN: The secure setting is changed
+ showOnlyUnseenNotifsOnKeyguardSetting = true
+
+ // THEN: The pipeline is invalidated
+ verify(invalidationListener).onPluggableInvalidated(same(unseenFilter), anyString())
+
+ // THEN: The notification is recognized as "seen" and is filtered out.
+ assertThat(unseenFilter.shouldFilterOut(fakeEntry, 0L)).isTrue()
+ }
+ }
+
+ @Test
fun unseenFilterAllowsNewNotif() {
whenever(notifPipelineFlags.shouldFilterUnseenNotifsOnKeyguard).thenReturn(true)
@@ -276,22 +318,32 @@
private fun runKeyguardCoordinatorTest(
testBlock: suspend KeyguardCoordinatorTestScope.() -> Unit
) {
- val testScope = TestScope(UnconfinedTestDispatcher())
+ val testDispatcher = UnconfinedTestDispatcher()
+ val testScope = TestScope(testDispatcher)
+ val fakeSettings = FakeSettings().apply {
+ putBool(Settings.Secure.LOCK_SCREEN_SHOW_ONLY_UNSEEN_NOTIFICATIONS, true)
+ }
val seenNotificationsProvider = SeenNotificationsProviderImpl()
val keyguardCoordinator =
KeyguardCoordinator(
+ testDispatcher,
keyguardNotifVisibilityProvider,
keyguardRepository,
notifPipelineFlags,
testScope.backgroundScope,
sectionHeaderVisibilityProvider,
+ fakeSettings,
seenNotificationsProvider,
statusBarStateController,
)
keyguardCoordinator.attach(notifPipeline)
testScope.runTest(dispatchTimeoutMs = 1.seconds.inWholeMilliseconds) {
- KeyguardCoordinatorTestScope(keyguardCoordinator, testScope, seenNotificationsProvider)
- .testBlock()
+ KeyguardCoordinatorTestScope(
+ keyguardCoordinator,
+ testScope,
+ seenNotificationsProvider,
+ fakeSettings,
+ ).testBlock()
}
}
@@ -299,6 +351,7 @@
private val keyguardCoordinator: KeyguardCoordinator,
private val scope: TestScope,
val seenNotificationsProvider: SeenNotificationsProvider,
+ private val fakeSettings: FakeSettings,
) : CoroutineScope by scope {
val testScheduler: TestCoroutineScheduler
get() = scope.testScheduler
@@ -316,5 +369,19 @@
val collectionListener: NotifCollectionListener by lazy {
withArgCaptor { verify(notifPipeline).addCollectionListener(capture()) }
}
+
+ var showOnlyUnseenNotifsOnKeyguardSetting: Boolean
+ get() =
+ fakeSettings.getBoolForUser(
+ Settings.Secure.LOCK_SCREEN_SHOW_ONLY_UNSEEN_NOTIFICATIONS,
+ UserHandle.USER_CURRENT,
+ )
+ set(value) {
+ fakeSettings.putBoolForUser(
+ Settings.Secure.LOCK_SCREEN_SHOW_ONLY_UNSEEN_NOTIFICATIONS,
+ value,
+ UserHandle.USER_CURRENT,
+ )
+ }
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/logging/NotificationMemoryLoggerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/logging/NotificationMemoryLoggerTest.kt
new file mode 100644
index 0000000..33b94e3
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/logging/NotificationMemoryLoggerTest.kt
@@ -0,0 +1,127 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.notification.logging
+
+import android.app.Notification
+import android.app.StatsManager
+import android.graphics.Bitmap
+import android.graphics.drawable.Icon
+import android.testing.AndroidTestingRunner
+import android.util.StatsEvent
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.shared.system.SysUiStatsLog
+import com.android.systemui.statusbar.notification.collection.NotifPipeline
+import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder
+import com.android.systemui.util.concurrency.FakeExecutor
+import com.android.systemui.util.mockito.mock
+import com.android.systemui.util.mockito.whenever
+import com.android.systemui.util.time.FakeSystemClock
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.Dispatchers
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mock
+import org.mockito.Mockito.verify
+import org.mockito.MockitoAnnotations
+
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+class NotificationMemoryLoggerTest : SysuiTestCase() {
+
+ private val bgExecutor = FakeExecutor(FakeSystemClock())
+ private val immediate = Dispatchers.Main.immediate
+
+ @Mock private lateinit var statsManager: StatsManager
+
+ @Before
+ fun setUp() {
+ MockitoAnnotations.initMocks(this)
+ }
+
+ @Test
+ fun onInit_registersCallback() {
+ val logger = createLoggerWithNotifications(listOf())
+ logger.init()
+ verify(statsManager)
+ .setPullAtomCallback(SysUiStatsLog.NOTIFICATION_MEMORY_USE, null, bgExecutor, logger)
+ }
+
+ @Test
+ fun onPullAtom_wrongAtomId_returnsSkip() {
+ val logger = createLoggerWithNotifications(listOf())
+ val data: MutableList<StatsEvent> = mutableListOf()
+ assertThat(logger.onPullAtom(111, data)).isEqualTo(StatsManager.PULL_SKIP)
+ assertThat(data).isEmpty()
+ }
+
+ @Test
+ fun onPullAtom_emptyNotifications_returnsZeros() {
+ val logger = createLoggerWithNotifications(listOf())
+ val data: MutableList<StatsEvent> = mutableListOf()
+ assertThat(logger.onPullAtom(SysUiStatsLog.NOTIFICATION_MEMORY_USE, data))
+ .isEqualTo(StatsManager.PULL_SUCCESS)
+ assertThat(data).isEmpty()
+ }
+
+ @Test
+ fun onPullAtom_notificationPassed_populatesData() {
+ val icon = Icon.createWithBitmap(Bitmap.createBitmap(300, 300, Bitmap.Config.ARGB_8888))
+ val notification =
+ Notification.Builder(context).setSmallIcon(icon).setContentTitle("title").build()
+ val logger = createLoggerWithNotifications(listOf(notification))
+ val data: MutableList<StatsEvent> = mutableListOf()
+
+ assertThat(logger.onPullAtom(SysUiStatsLog.NOTIFICATION_MEMORY_USE, data))
+ .isEqualTo(StatsManager.PULL_SUCCESS)
+ assertThat(data).hasSize(1)
+ }
+
+ @Test
+ fun onPullAtom_multipleNotificationsPassed_populatesData() {
+ val icon = Icon.createWithBitmap(Bitmap.createBitmap(300, 300, Bitmap.Config.ARGB_8888))
+ val notification =
+ Notification.Builder(context).setSmallIcon(icon).setContentTitle("title").build()
+ val iconTwo = Icon.createWithBitmap(Bitmap.createBitmap(300, 300, Bitmap.Config.ARGB_8888))
+
+ val notificationTwo =
+ Notification.Builder(context)
+ .setStyle(Notification.BigTextStyle().bigText("text"))
+ .setSmallIcon(iconTwo)
+ .setContentTitle("titleTwo")
+ .build()
+ val logger = createLoggerWithNotifications(listOf(notification, notificationTwo))
+ val data: MutableList<StatsEvent> = mutableListOf()
+
+ assertThat(logger.onPullAtom(SysUiStatsLog.NOTIFICATION_MEMORY_USE, data))
+ .isEqualTo(StatsManager.PULL_SUCCESS)
+ assertThat(data).hasSize(2)
+ }
+
+ private fun createLoggerWithNotifications(
+ notifications: List<Notification>
+ ): NotificationMemoryLogger {
+ val pipeline: NotifPipeline = mock()
+ val notifications =
+ notifications.map { notification ->
+ NotificationEntryBuilder().setTag("test").setNotification(notification).build()
+ }
+ whenever(pipeline.allNotifs).thenReturn(notifications)
+ return NotificationMemoryLogger(pipeline, statsManager, immediate, bgExecutor)
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/logging/NotificationMemoryMeterTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/logging/NotificationMemoryMeterTest.kt
index f69839b..072a497 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/logging/NotificationMemoryMeterTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/logging/NotificationMemoryMeterTest.kt
@@ -23,6 +23,7 @@
import android.content.Intent
import android.graphics.Bitmap
import android.graphics.drawable.Icon
+import android.stats.sysui.NotificationEnums
import android.testing.AndroidTestingRunner
import android.widget.RemoteViews
import androidx.test.filters.SmallTest
@@ -50,7 +51,27 @@
extras = 3316,
bigPicture = 0,
extender = 0,
- style = null,
+ style = NotificationEnums.STYLE_NONE,
+ styleIcon = 0,
+ hasCustomView = false,
+ )
+ }
+
+ @Test
+ fun currentNotificationMemoryUse_rankerGroupNotification() {
+ val notification = createBasicNotification().build()
+ val memoryUse =
+ NotificationMemoryMeter.notificationMemoryUse(
+ createNotificationEntry(createBasicNotification().setGroup("ranker_group").build())
+ )
+ assertNotificationObjectSizes(
+ memoryUse,
+ smallIcon = notification.smallIcon.bitmap.allocationByteCount,
+ largeIcon = notification.getLargeIcon().bitmap.allocationByteCount,
+ extras = 3316,
+ bigPicture = 0,
+ extender = 0,
+ style = NotificationEnums.STYLE_RANKER_GROUP,
styleIcon = 0,
hasCustomView = false,
)
@@ -69,7 +90,7 @@
extras = 3316,
bigPicture = 0,
extender = 0,
- style = null,
+ style = NotificationEnums.STYLE_NONE,
styleIcon = 0,
hasCustomView = false,
)
@@ -92,7 +113,7 @@
extras = 3384,
bigPicture = 0,
extender = 0,
- style = null,
+ style = NotificationEnums.STYLE_NONE,
styleIcon = 0,
hasCustomView = true,
)
@@ -112,7 +133,7 @@
extras = 3212,
bigPicture = 0,
extender = 0,
- style = null,
+ style = NotificationEnums.STYLE_NONE,
styleIcon = 0,
hasCustomView = false,
)
@@ -141,7 +162,7 @@
extras = 4092,
bigPicture = bigPicture.bitmap.allocationByteCount,
extender = 0,
- style = "BigPictureStyle",
+ style = NotificationEnums.STYLE_BIG_PICTURE,
styleIcon = bigPictureIcon.bitmap.allocationByteCount,
hasCustomView = false,
)
@@ -167,7 +188,7 @@
extras = 4084,
bigPicture = 0,
extender = 0,
- style = "CallStyle",
+ style = NotificationEnums.STYLE_CALL,
styleIcon = personIcon.bitmap.allocationByteCount,
hasCustomView = false,
)
@@ -203,7 +224,7 @@
extras = 5024,
bigPicture = 0,
extender = 0,
- style = "MessagingStyle",
+ style = NotificationEnums.STYLE_MESSAGING,
styleIcon =
personIcon.bitmap.allocationByteCount +
historicPersonIcon.bitmap.allocationByteCount,
@@ -225,7 +246,7 @@
extras = 3612,
bigPicture = 0,
extender = 556656,
- style = null,
+ style = NotificationEnums.STYLE_NONE,
styleIcon = 0,
hasCustomView = false,
)
@@ -246,7 +267,7 @@
extras = 3820,
bigPicture = 0,
extender = 388 + wearBackground.allocationByteCount,
- style = null,
+ style = NotificationEnums.STYLE_NONE,
styleIcon = 0,
hasCustomView = false,
)
@@ -272,7 +293,7 @@
extras: Int,
bigPicture: Int,
extender: Int,
- style: String?,
+ style: Int,
styleIcon: Int,
hasCustomView: Boolean,
) {
@@ -282,11 +303,7 @@
assertThat(memoryUse.objectUsage.smallIcon).isEqualTo(smallIcon)
assertThat(memoryUse.objectUsage.largeIcon).isEqualTo(largeIcon)
assertThat(memoryUse.objectUsage.bigPicture).isEqualTo(bigPicture)
- if (style == null) {
- assertThat(memoryUse.objectUsage.style).isNull()
- } else {
- assertThat(memoryUse.objectUsage.style).isEqualTo(style)
- }
+ assertThat(memoryUse.objectUsage.style).isEqualTo(style)
assertThat(memoryUse.objectUsage.styleIcon).isEqualTo(styleIcon)
assertThat(memoryUse.objectUsage.hasCustomView).isEqualTo(hasCustomView)
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/logging/NotificationMemoryViewWalkerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/logging/NotificationMemoryViewWalkerTest.kt
index 3a16fb3..a0f5048 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/logging/NotificationMemoryViewWalkerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/logging/NotificationMemoryViewWalkerTest.kt
@@ -8,6 +8,7 @@
import android.widget.RemoteViews
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
+import com.android.systemui.statusbar.notification.row.NotificationRowContentBinder
import com.android.systemui.statusbar.notification.row.NotificationTestHelper
import com.android.systemui.tests.R
import com.google.common.truth.Truth.assertThat
@@ -39,16 +40,84 @@
fun testViewWalker_plainNotification() {
val row = testHelper.createRow()
val result = NotificationMemoryViewWalker.getViewUsage(row)
- assertThat(result).hasSize(5)
- assertThat(result).contains(NotificationViewUsage(ViewType.PUBLIC_VIEW, 0, 0, 0, 0, 0, 0))
- assertThat(result)
- .contains(NotificationViewUsage(ViewType.PRIVATE_HEADS_UP_VIEW, 0, 0, 0, 0, 0, 0))
+ assertThat(result).hasSize(3)
assertThat(result)
.contains(NotificationViewUsage(ViewType.PRIVATE_EXPANDED_VIEW, 0, 0, 0, 0, 0, 0))
assertThat(result)
.contains(NotificationViewUsage(ViewType.PRIVATE_CONTRACTED_VIEW, 0, 0, 0, 0, 0, 0))
+ assertThat(result).contains(NotificationViewUsage(ViewType.TOTAL, 0, 0, 0, 0, 0, 0))
+ }
+
+ @Test
+ fun testViewWalker_plainNotification_withPublicView() {
+ val icon = Icon.createWithBitmap(Bitmap.createBitmap(20, 20, Bitmap.Config.ARGB_8888))
+ val publicIcon = Icon.createWithBitmap(Bitmap.createBitmap(40, 40, Bitmap.Config.ARGB_8888))
+ testHelper.setDefaultInflationFlags(NotificationRowContentBinder.FLAG_CONTENT_VIEW_ALL)
+ val row =
+ testHelper.createRow(
+ Notification.Builder(mContext)
+ .setContentText("Test")
+ .setContentTitle("title")
+ .setSmallIcon(icon)
+ .setPublicVersion(
+ Notification.Builder(mContext)
+ .setContentText("Public Test")
+ .setContentTitle("title")
+ .setSmallIcon(publicIcon)
+ .build()
+ )
+ .build()
+ )
+ val result = NotificationMemoryViewWalker.getViewUsage(row)
+ assertThat(result).hasSize(4)
assertThat(result)
- .contains(NotificationViewUsage(ViewType.PRIVATE_HEADS_UP_VIEW, 0, 0, 0, 0, 0, 0))
+ .contains(
+ NotificationViewUsage(
+ ViewType.PRIVATE_EXPANDED_VIEW,
+ icon.bitmap.allocationByteCount,
+ 0,
+ 0,
+ 0,
+ 0,
+ icon.bitmap.allocationByteCount
+ )
+ )
+ assertThat(result)
+ .contains(
+ NotificationViewUsage(
+ ViewType.PRIVATE_CONTRACTED_VIEW,
+ icon.bitmap.allocationByteCount,
+ 0,
+ 0,
+ 0,
+ 0,
+ icon.bitmap.allocationByteCount
+ )
+ )
+ assertThat(result)
+ .contains(
+ NotificationViewUsage(
+ ViewType.PUBLIC_VIEW,
+ publicIcon.bitmap.allocationByteCount,
+ 0,
+ 0,
+ 0,
+ 0,
+ publicIcon.bitmap.allocationByteCount
+ )
+ )
+ assertThat(result)
+ .contains(
+ NotificationViewUsage(
+ ViewType.TOTAL,
+ icon.bitmap.allocationByteCount + publicIcon.bitmap.allocationByteCount,
+ 0,
+ 0,
+ 0,
+ 0,
+ icon.bitmap.allocationByteCount + publicIcon.bitmap.allocationByteCount
+ )
+ )
}
@Test
@@ -67,7 +136,7 @@
.build()
)
val result = NotificationMemoryViewWalker.getViewUsage(row)
- assertThat(result).hasSize(5)
+ assertThat(result).hasSize(3)
assertThat(result)
.contains(
NotificationViewUsage(
@@ -95,8 +164,20 @@
icon.bitmap.allocationByteCount + largeIcon.bitmap.allocationByteCount
)
)
- // Due to deduplication, this should all be 0.
- assertThat(result).contains(NotificationViewUsage(ViewType.PUBLIC_VIEW, 0, 0, 0, 0, 0, 0))
+ assertThat(result)
+ .contains(
+ NotificationViewUsage(
+ ViewType.TOTAL,
+ icon.bitmap.allocationByteCount,
+ largeIcon.bitmap.allocationByteCount,
+ 0,
+ bigPicture.allocationByteCount,
+ 0,
+ bigPicture.allocationByteCount +
+ icon.bitmap.allocationByteCount +
+ largeIcon.bitmap.allocationByteCount
+ )
+ )
}
@Test
@@ -117,7 +198,7 @@
.build()
)
val result = NotificationMemoryViewWalker.getViewUsage(row)
- assertThat(result).hasSize(5)
+ assertThat(result).hasSize(3)
assertThat(result)
.contains(
NotificationViewUsage(
@@ -142,7 +223,17 @@
bitmap.allocationByteCount + icon.bitmap.allocationByteCount
)
)
- // Due to deduplication, this should all be 0.
- assertThat(result).contains(NotificationViewUsage(ViewType.PUBLIC_VIEW, 0, 0, 0, 0, 0, 0))
+ assertThat(result)
+ .contains(
+ NotificationViewUsage(
+ ViewType.TOTAL,
+ icon.bitmap.allocationByteCount,
+ 0,
+ 0,
+ 0,
+ bitmap.allocationByteCount,
+ bitmap.allocationByteCount + icon.bitmap.allocationByteCount
+ )
+ )
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ActivatableNotificationViewTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ActivatableNotificationViewTest.kt
index 5f57695..3f61af0 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ActivatableNotificationViewTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ActivatableNotificationViewTest.kt
@@ -26,6 +26,7 @@
import com.android.systemui.SysuiTestCase
import com.android.systemui.statusbar.notification.FakeShadowView
import com.android.systemui.statusbar.notification.NotificationUtils
+import com.android.systemui.statusbar.notification.SourceType
import com.android.systemui.util.mockito.mock
import com.google.common.truth.Truth.assertThat
import org.junit.Before
@@ -83,4 +84,17 @@
mView.updateBackgroundColors()
assertThat(mView.currentBackgroundTint).isEqualTo(mNormalColor)
}
+
+ @Test
+ fun roundnessShouldBeTheSame_after_onDensityOrFontScaleChanged() {
+ val roundableState = mView.roundableState
+ assertThat(mView.topRoundness).isEqualTo(0f)
+ mView.requestTopRoundness(1f, SourceType.from(""))
+ assertThat(mView.topRoundness).isEqualTo(1f)
+
+ mView.onDensityOrFontScaleChanged()
+
+ assertThat(mView.topRoundness).isEqualTo(1f)
+ assertThat(mView.roundableState.hashCode()).isEqualTo(roundableState.hashCode())
+ }
}
\ No newline at end of file
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithmTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithmTest.kt
index 4d9db8c..5832569 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithmTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithmTest.kt
@@ -518,7 +518,7 @@
val childHunView = createHunViewMock(
isShadeOpen = true,
fullyVisible = false,
- headerVisibleAmount = 1f
+ headerVisibleAmount = 1f,
)
val algorithmState = StackScrollAlgorithm.StackScrollAlgorithmState()
algorithmState.visibleChildren.add(childHunView)
@@ -526,7 +526,6 @@
// When: updateChildZValue() is called for the top HUN
stackScrollAlgorithm.updateChildZValue(
/* i= */ 0,
- /* childrenOnTop= */ 0.0f,
/* StackScrollAlgorithmState= */ algorithmState,
/* ambientState= */ ambientState,
/* shouldElevateHun= */ true
@@ -546,7 +545,7 @@
val childHunView = createHunViewMock(
isShadeOpen = true,
fullyVisible = false,
- headerVisibleAmount = 1f
+ headerVisibleAmount = 1f,
)
// Use half of the HUN's height as overlap
childHunView.viewState.yTranslation = (childHunView.viewState.height + 1 shr 1).toFloat()
@@ -556,7 +555,6 @@
// When: updateChildZValue() is called for the top HUN
stackScrollAlgorithm.updateChildZValue(
/* i= */ 0,
- /* childrenOnTop= */ 0.0f,
/* StackScrollAlgorithmState= */ algorithmState,
/* ambientState= */ ambientState,
/* shouldElevateHun= */ true
@@ -580,7 +578,7 @@
val childHunView = createHunViewMock(
isShadeOpen = true,
fullyVisible = true,
- headerVisibleAmount = 1f
+ headerVisibleAmount = 1f,
)
// HUN doesn't overlap with QQS Panel
childHunView.viewState.yTranslation = ambientState.topPadding +
@@ -591,7 +589,6 @@
// When: updateChildZValue() is called for the top HUN
stackScrollAlgorithm.updateChildZValue(
/* i= */ 0,
- /* childrenOnTop= */ 0.0f,
/* StackScrollAlgorithmState= */ algorithmState,
/* ambientState= */ ambientState,
/* shouldElevateHun= */ true
@@ -611,7 +608,7 @@
val childHunView = createHunViewMock(
isShadeOpen = false,
fullyVisible = false,
- headerVisibleAmount = 0f
+ headerVisibleAmount = 0f,
)
childHunView.viewState.yTranslation = 0f
// Shade is closed, thus childHunView's headerVisibleAmount is 0
@@ -622,7 +619,6 @@
// When: updateChildZValue() is called for the top HUN
stackScrollAlgorithm.updateChildZValue(
/* i= */ 0,
- /* childrenOnTop= */ 0.0f,
/* StackScrollAlgorithmState= */ algorithmState,
/* ambientState= */ ambientState,
/* shouldElevateHun= */ true
@@ -642,7 +638,7 @@
val childHunView = createHunViewMock(
isShadeOpen = false,
fullyVisible = false,
- headerVisibleAmount = 0.5f
+ headerVisibleAmount = 0.5f,
)
childHunView.viewState.yTranslation = 0f
// Shade is being opened, thus childHunView's headerVisibleAmount is between 0 and 1
@@ -654,7 +650,6 @@
// When: updateChildZValue() is called for the top HUN
stackScrollAlgorithm.updateChildZValue(
/* i= */ 0,
- /* childrenOnTop= */ 0.0f,
/* StackScrollAlgorithmState= */ algorithmState,
/* ambientState= */ ambientState,
/* shouldElevateHun= */ true
@@ -669,7 +664,7 @@
private fun createHunViewMock(
isShadeOpen: Boolean,
fullyVisible: Boolean,
- headerVisibleAmount: Float
+ headerVisibleAmount: Float,
) =
mock<ExpandableNotificationRow>().apply {
val childViewStateMock = createHunChildViewState(isShadeOpen, fullyVisible)
@@ -680,7 +675,10 @@
}
- private fun createHunChildViewState(isShadeOpen: Boolean, fullyVisible: Boolean) =
+ private fun createHunChildViewState(
+ isShadeOpen: Boolean,
+ fullyVisible: Boolean,
+ ) =
ExpandableViewState().apply {
// Mock the HUN's height with ambientState.topPadding +
// ambientState.stackTranslation
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/ui/view/ModernStatusBarWifiViewTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/ui/view/ModernStatusBarWifiViewTest.kt
index 22c0ea1..59c10cd 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/ui/view/ModernStatusBarWifiViewTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/ui/view/ModernStatusBarWifiViewTest.kt
@@ -52,6 +52,7 @@
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import org.junit.Before
+import org.junit.Ignore
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
@@ -234,6 +235,7 @@
}
@Test
+ @Ignore("b/262660044")
fun onDarkChanged_iconHasNewColor() {
whenever(statusBarPipelineFlags.useWifiDebugColoring()).thenReturn(false)
val view = ModernStatusBarWifiView.constructAndBind(context, SLOT_NAME, viewModel)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/stylus/OWNERS b/packages/SystemUI/tests/src/com/android/systemui/stylus/OWNERS
new file mode 100644
index 0000000..7ccb316
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/stylus/OWNERS
@@ -0,0 +1,8 @@
+# Bug component: 1254381
+azappone@google.com
+achalke@google.com
+juliacr@google.com
+madym@google.com
+mgalhardo@google.com
+petrcermak@google.com
+vanjan@google.com
\ No newline at end of file
diff --git a/packages/SystemUI/tests/src/com/android/systemui/stylus/StylusUsiPowerStartableTest.kt b/packages/SystemUI/tests/src/com/android/systemui/stylus/StylusUsiPowerStartableTest.kt
new file mode 100644
index 0000000..ff382a3
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/stylus/StylusUsiPowerStartableTest.kt
@@ -0,0 +1,158 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.stylus
+
+import android.hardware.BatteryState
+import android.hardware.input.InputManager
+import android.testing.AndroidTestingRunner
+import android.view.InputDevice
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.flags.FeatureFlags
+import com.android.systemui.flags.Flags
+import com.android.systemui.util.mockito.whenever
+import java.util.concurrent.Executor
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mock
+import org.mockito.Mockito.inOrder
+import org.mockito.Mockito.mock
+import org.mockito.Mockito.never
+import org.mockito.Mockito.times
+import org.mockito.Mockito.verify
+import org.mockito.Mockito.verifyNoMoreInteractions
+import org.mockito.MockitoAnnotations
+
+@RunWith(AndroidTestingRunner::class)
+@SmallTest
+class StylusUsiPowerStartableTest : SysuiTestCase() {
+ @Mock lateinit var inputManager: InputManager
+ @Mock lateinit var stylusManager: StylusManager
+ @Mock lateinit var stylusDevice: InputDevice
+ @Mock lateinit var externalDevice: InputDevice
+ @Mock lateinit var featureFlags: FeatureFlags
+ @Mock lateinit var stylusUsiPowerUi: StylusUsiPowerUI
+
+ lateinit var startable: StylusUsiPowerStartable
+
+ @Before
+ fun setUp() {
+ MockitoAnnotations.initMocks(this)
+
+ startable =
+ StylusUsiPowerStartable(
+ stylusManager,
+ inputManager,
+ stylusUsiPowerUi,
+ featureFlags,
+ DIRECT_EXECUTOR,
+ )
+
+ whenever(featureFlags.isEnabled(Flags.ENABLE_USI_BATTERY_NOTIFICATIONS)).thenReturn(true)
+
+ whenever(inputManager.getInputDevice(EXTERNAL_DEVICE_ID)).thenReturn(externalDevice)
+ whenever(inputManager.getInputDevice(STYLUS_DEVICE_ID)).thenReturn(stylusDevice)
+ whenever(inputManager.inputDeviceIds)
+ .thenReturn(intArrayOf(EXTERNAL_DEVICE_ID, STYLUS_DEVICE_ID))
+
+ whenever(stylusDevice.supportsSource(InputDevice.SOURCE_STYLUS)).thenReturn(true)
+ whenever(stylusDevice.isExternal).thenReturn(false)
+ whenever(stylusDevice.id).thenReturn(STYLUS_DEVICE_ID)
+ whenever(externalDevice.supportsSource(InputDevice.SOURCE_STYLUS)).thenReturn(true)
+ whenever(externalDevice.isExternal).thenReturn(true)
+ whenever(externalDevice.id).thenReturn(EXTERNAL_DEVICE_ID)
+ }
+
+ @Test
+ fun start_addsBatteryListenerForInternalStylus() {
+ startable.start()
+
+ verify(inputManager, times(1))
+ .addInputDeviceBatteryListener(STYLUS_DEVICE_ID, DIRECT_EXECUTOR, startable)
+ }
+
+ @Test
+ fun onStylusAdded_internalStylus_addsBatteryListener() {
+ startable.onStylusAdded(STYLUS_DEVICE_ID)
+
+ verify(inputManager, times(1))
+ .addInputDeviceBatteryListener(STYLUS_DEVICE_ID, DIRECT_EXECUTOR, startable)
+ }
+
+ @Test
+ fun onStylusAdded_externalStylus_doesNotAddBatteryListener() {
+ startable.onStylusAdded(EXTERNAL_DEVICE_ID)
+
+ verify(inputManager, never())
+ .addInputDeviceBatteryListener(EXTERNAL_DEVICE_ID, DIRECT_EXECUTOR, startable)
+ }
+
+ @Test
+ fun onStylusRemoved_registeredStylus_removesBatteryListener() {
+ startable.onStylusAdded(STYLUS_DEVICE_ID)
+ startable.onStylusRemoved(STYLUS_DEVICE_ID)
+
+ inOrder(inputManager).let {
+ it.verify(inputManager, times(1))
+ .addInputDeviceBatteryListener(STYLUS_DEVICE_ID, DIRECT_EXECUTOR, startable)
+ it.verify(inputManager, times(1))
+ .removeInputDeviceBatteryListener(STYLUS_DEVICE_ID, startable)
+ }
+ }
+
+ @Test
+ fun onStylusBluetoothConnected_refreshesNotification() {
+ startable.onStylusBluetoothConnected(STYLUS_DEVICE_ID, "ANY")
+
+ verify(stylusUsiPowerUi, times(1)).refresh()
+ }
+
+ @Test
+ fun onStylusBluetoothDisconnected_refreshesNotification() {
+ startable.onStylusBluetoothDisconnected(STYLUS_DEVICE_ID, "ANY")
+
+ verify(stylusUsiPowerUi, times(1)).refresh()
+ }
+
+ @Test
+ fun onBatteryStateChanged_batteryPresent_refreshesNotification() {
+ val batteryState = mock(BatteryState::class.java)
+ whenever(batteryState.isPresent).thenReturn(true)
+
+ startable.onBatteryStateChanged(STYLUS_DEVICE_ID, 123, batteryState)
+
+ verify(stylusUsiPowerUi, times(1)).updateBatteryState(batteryState)
+ }
+
+ @Test
+ fun onBatteryStateChanged_batteryNotPresent_noop() {
+ val batteryState = mock(BatteryState::class.java)
+ whenever(batteryState.isPresent).thenReturn(false)
+
+ startable.onBatteryStateChanged(STYLUS_DEVICE_ID, 123, batteryState)
+
+ verifyNoMoreInteractions(stylusUsiPowerUi)
+ }
+
+ companion object {
+ private val DIRECT_EXECUTOR = Executor { r -> r.run() }
+
+ private const val EXTERNAL_DEVICE_ID = 0
+ private const val STYLUS_DEVICE_ID = 1
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/stylus/StylusUsiPowerUiTest.kt b/packages/SystemUI/tests/src/com/android/systemui/stylus/StylusUsiPowerUiTest.kt
new file mode 100644
index 0000000..5987550
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/stylus/StylusUsiPowerUiTest.kt
@@ -0,0 +1,167 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.stylus
+
+import android.hardware.BatteryState
+import android.hardware.input.InputManager
+import android.os.Handler
+import android.testing.AndroidTestingRunner
+import android.view.InputDevice
+import androidx.core.app.NotificationManagerCompat
+import androidx.test.filters.SmallTest
+import com.android.systemui.R
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.util.mockito.any
+import com.android.systemui.util.mockito.eq
+import com.android.systemui.util.mockito.whenever
+import org.junit.Before
+import org.junit.Ignore
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mock
+import org.mockito.Mockito.inOrder
+import org.mockito.Mockito.times
+import org.mockito.Mockito.verify
+import org.mockito.Mockito.verifyNoMoreInteractions
+import org.mockito.MockitoAnnotations
+
+@RunWith(AndroidTestingRunner::class)
+@SmallTest
+class StylusUsiPowerUiTest : SysuiTestCase() {
+ @Mock lateinit var notificationManager: NotificationManagerCompat
+ @Mock lateinit var inputManager: InputManager
+ @Mock lateinit var handler: Handler
+ @Mock lateinit var btStylusDevice: InputDevice
+
+ private lateinit var stylusUsiPowerUi: StylusUsiPowerUI
+
+ @Before
+ fun setUp() {
+ MockitoAnnotations.initMocks(this)
+
+ whenever(handler.post(any())).thenAnswer {
+ (it.arguments[0] as Runnable).run()
+ true
+ }
+
+ whenever(inputManager.inputDeviceIds).thenReturn(intArrayOf())
+ whenever(inputManager.getInputDevice(0)).thenReturn(btStylusDevice)
+ whenever(btStylusDevice.supportsSource(InputDevice.SOURCE_STYLUS)).thenReturn(true)
+ // whenever(btStylusDevice.bluetoothAddress).thenReturn("SO:ME:AD:DR:ES")
+
+ stylusUsiPowerUi = StylusUsiPowerUI(mContext, notificationManager, inputManager, handler)
+ }
+
+ @Test
+ fun updateBatteryState_capacityBelowThreshold_notifies() {
+ stylusUsiPowerUi.updateBatteryState(FixedCapacityBatteryState(0.1f))
+
+ verify(notificationManager, times(1)).notify(eq(R.string.stylus_battery_low), any())
+ verifyNoMoreInteractions(notificationManager)
+ }
+
+ @Test
+ fun updateBatteryState_capacityAboveThreshold_cancelsNotificattion() {
+ stylusUsiPowerUi.updateBatteryState(FixedCapacityBatteryState(0.8f))
+
+ verify(notificationManager, times(1)).cancel(R.string.stylus_battery_low)
+ verifyNoMoreInteractions(notificationManager)
+ }
+
+ @Test
+ fun updateBatteryState_existingNotification_capacityAboveThreshold_cancelsNotification() {
+ stylusUsiPowerUi.updateBatteryState(FixedCapacityBatteryState(0.1f))
+ stylusUsiPowerUi.updateBatteryState(FixedCapacityBatteryState(0.8f))
+
+ inOrder(notificationManager).let {
+ it.verify(notificationManager, times(1)).notify(eq(R.string.stylus_battery_low), any())
+ it.verify(notificationManager, times(1)).cancel(R.string.stylus_battery_low)
+ it.verifyNoMoreInteractions()
+ }
+ }
+
+ @Test
+ fun updateBatteryState_existingNotification_capacityBelowThreshold_updatesNotification() {
+ stylusUsiPowerUi.updateBatteryState(FixedCapacityBatteryState(0.1f))
+ stylusUsiPowerUi.updateBatteryState(FixedCapacityBatteryState(0.15f))
+
+ verify(notificationManager, times(2)).notify(eq(R.string.stylus_battery_low), any())
+ verifyNoMoreInteractions(notificationManager)
+ }
+
+ @Test
+ fun updateBatteryState_capacityAboveThenBelowThreshold_hidesThenShowsNotification() {
+ stylusUsiPowerUi.updateBatteryState(FixedCapacityBatteryState(0.1f))
+ stylusUsiPowerUi.updateBatteryState(FixedCapacityBatteryState(0.5f))
+ stylusUsiPowerUi.updateBatteryState(FixedCapacityBatteryState(0.1f))
+
+ inOrder(notificationManager).let {
+ it.verify(notificationManager, times(1)).notify(eq(R.string.stylus_battery_low), any())
+ it.verify(notificationManager, times(1)).cancel(R.string.stylus_battery_low)
+ it.verify(notificationManager, times(1)).notify(eq(R.string.stylus_battery_low), any())
+ it.verifyNoMoreInteractions()
+ }
+ }
+
+ @Test
+ fun updateSuppression_noExistingNotification_cancelsNotification() {
+ stylusUsiPowerUi.updateSuppression(true)
+
+ verify(notificationManager, times(1)).cancel(R.string.stylus_battery_low)
+ verifyNoMoreInteractions(notificationManager)
+ }
+
+ @Test
+ fun updateSuppression_existingNotification_cancelsNotification() {
+ stylusUsiPowerUi.updateBatteryState(FixedCapacityBatteryState(0.1f))
+
+ stylusUsiPowerUi.updateSuppression(true)
+
+ inOrder(notificationManager).let {
+ it.verify(notificationManager, times(1)).notify(eq(R.string.stylus_battery_low), any())
+ it.verify(notificationManager, times(1)).cancel(R.string.stylus_battery_low)
+ it.verifyNoMoreInteractions()
+ }
+ }
+
+ @Test
+ @Ignore("TODO(b/257936830): get bt address once input api available")
+ fun refresh_hasConnectedBluetoothStylus_doesNotNotify() {
+ whenever(inputManager.inputDeviceIds).thenReturn(intArrayOf(0))
+
+ stylusUsiPowerUi.refresh()
+
+ verifyNoMoreInteractions(notificationManager)
+ }
+
+ @Test
+ @Ignore("TODO(b/257936830): get bt address once input api available")
+ fun refresh_hasConnectedBluetoothStylus_existingNotification_cancelsNotification() {
+ stylusUsiPowerUi.updateBatteryState(FixedCapacityBatteryState(0.1f))
+ whenever(inputManager.inputDeviceIds).thenReturn(intArrayOf(0))
+
+ stylusUsiPowerUi.refresh()
+
+ verify(notificationManager).cancel(R.string.stylus_battery_low)
+ }
+
+ class FixedCapacityBatteryState(private val capacity: Float) : BatteryState() {
+ override fun getCapacity() = capacity
+ override fun getStatus() = 0
+ override fun isPresent() = true
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/temporarydisplay/TemporaryViewDisplayControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/temporarydisplay/TemporaryViewDisplayControllerTest.kt
index 09f0d4a..82153d5 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/temporarydisplay/TemporaryViewDisplayControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/temporarydisplay/TemporaryViewDisplayControllerTest.kt
@@ -35,6 +35,7 @@
import com.android.systemui.util.mockito.argumentCaptor
import com.android.systemui.util.mockito.capture
import com.android.systemui.util.time.FakeSystemClock
+import com.android.systemui.util.time.SystemClock
import com.android.systemui.util.wakelock.WakeLock
import com.android.systemui.util.wakelock.WakeLockFake
import com.google.common.truth.Truth.assertThat
@@ -59,7 +60,7 @@
private lateinit var fakeWakeLock: WakeLockFake
@Mock
- private lateinit var logger: TemporaryViewLogger
+ private lateinit var logger: TemporaryViewLogger<ViewInfo>
@Mock
private lateinit var accessibilityManager: AccessibilityManager
@Mock
@@ -74,7 +75,7 @@
MockitoAnnotations.initMocks(this)
whenever(accessibilityManager.getRecommendedTimeoutMillis(any(), any()))
- .thenReturn(TIMEOUT_MS.toInt())
+ .thenAnswer { it.arguments[0] }
fakeClock = FakeSystemClock()
fakeExecutor = FakeExecutor(fakeClock)
@@ -84,14 +85,15 @@
fakeWakeLockBuilder.setWakeLock(fakeWakeLock)
underTest = TestController(
- context,
- logger,
- windowManager,
- fakeExecutor,
- accessibilityManager,
- configurationController,
- powerManager,
- fakeWakeLockBuilder,
+ context,
+ logger,
+ windowManager,
+ fakeExecutor,
+ accessibilityManager,
+ configurationController,
+ powerManager,
+ fakeWakeLockBuilder,
+ fakeClock,
)
underTest.start()
}
@@ -112,14 +114,14 @@
@Test
fun displayView_logged() {
- underTest.displayView(
- ViewInfo(
- name = "name",
- windowTitle = "Fake Window Title",
- )
+ val info = ViewInfo(
+ name = "name",
+ windowTitle = "Fake Window Title",
)
- verify(logger).logViewAddition("id", "Fake Window Title")
+ underTest.displayView(info)
+
+ verify(logger).logViewAddition(info)
}
@Test
@@ -168,10 +170,11 @@
}
@Test
- fun displayView_twiceWithDifferentWindowTitles_oldViewRemovedNewViewAdded() {
+ fun displayView_twiceWithDifferentIds_oldViewRemovedNewViewAdded() {
underTest.displayView(
ViewInfo(
name = "name",
+ id = "First",
windowTitle = "First Fake Window Title",
)
)
@@ -179,6 +182,7 @@
underTest.displayView(
ViewInfo(
name = "name",
+ id = "Second",
windowTitle = "Second Fake Window Title",
)
)
@@ -263,19 +267,69 @@
}
@Test
+ fun viewUpdatedWithNewOnViewTimeoutRunnable_newRunnableUsed() {
+ var runnable1Run = false
+ underTest.displayView(ViewInfo(name = "name", id = "id1", windowTitle = "1")) {
+ runnable1Run = true
+ }
+
+ var runnable2Run = false
+ underTest.displayView(ViewInfo(name = "name", id = "id1", windowTitle = "1")) {
+ runnable2Run = true
+ }
+
+ fakeClock.advanceTime(TIMEOUT_MS + 1)
+
+ assertThat(runnable1Run).isFalse()
+ assertThat(runnable2Run).isTrue()
+ }
+
+ @Test
+ fun multipleViewsWithDifferentIds_moreRecentReplacesOlder() {
+ underTest.displayView(
+ ViewInfo(
+ name = "name",
+ windowTitle = "First Fake Window Title",
+ id = "id1"
+ )
+ )
+
+ underTest.displayView(
+ ViewInfo(
+ name = "name",
+ windowTitle = "Second Fake Window Title",
+ id = "id2"
+ )
+ )
+
+ val viewCaptor = argumentCaptor<View>()
+ val windowParamsCaptor = argumentCaptor<WindowManager.LayoutParams>()
+
+ verify(windowManager, times(2)).addView(capture(viewCaptor), capture(windowParamsCaptor))
+
+ assertThat(windowParamsCaptor.allValues[0].title).isEqualTo("First Fake Window Title")
+ assertThat(windowParamsCaptor.allValues[1].title).isEqualTo("Second Fake Window Title")
+ verify(windowManager).removeView(viewCaptor.allValues[0])
+ verify(configurationController, never()).removeCallback(any())
+ }
+
+ @Test
fun multipleViewsWithDifferentIds_recentActiveViewIsDisplayed() {
underTest.displayView(ViewInfo("First name", id = "id1"))
verify(windowManager).addView(any(), any())
-
reset(windowManager)
+
underTest.displayView(ViewInfo("Second name", id = "id2"))
+
+ verify(windowManager).removeView(any())
+ verify(windowManager).addView(any(), any())
+ reset(windowManager)
+
underTest.removeView("id2", "test reason")
verify(windowManager).removeView(any())
-
- fakeClock.advanceTime(DISPLAY_VIEW_DELAY + 1)
-
+ verify(windowManager).addView(any(), any())
assertThat(underTest.mostRecentViewInfo?.id).isEqualTo("id1")
assertThat(underTest.mostRecentViewInfo?.name).isEqualTo("First name")
@@ -284,6 +338,7 @@
verify(windowManager).removeView(any())
assertThat(underTest.activeViews.size).isEqualTo(0)
+ verify(configurationController).removeCallback(any())
}
@Test
@@ -291,19 +346,28 @@
underTest.displayView(ViewInfo("First name", id = "id1"))
verify(windowManager).addView(any(), any())
-
reset(windowManager)
+
underTest.displayView(ViewInfo("Second name", id = "id2"))
+
+ verify(windowManager).removeView(any())
+ verify(windowManager).addView(any(), any())
+ reset(windowManager)
+
+ // WHEN an old view is removed
underTest.removeView("id1", "test reason")
+ // THEN we don't update anything
verify(windowManager, never()).removeView(any())
assertThat(underTest.mostRecentViewInfo?.id).isEqualTo("id2")
assertThat(underTest.mostRecentViewInfo?.name).isEqualTo("Second name")
+ verify(configurationController, never()).removeCallback(any())
fakeClock.advanceTime(TIMEOUT_MS + 1)
verify(windowManager).removeView(any())
assertThat(underTest.activeViews.size).isEqualTo(0)
+ verify(configurationController).removeCallback(any())
}
@Test
@@ -312,33 +376,31 @@
underTest.displayView(ViewInfo("Second name", id = "id2"))
underTest.displayView(ViewInfo("Third name", id = "id3"))
- verify(windowManager).addView(any(), any())
+ verify(windowManager, times(3)).addView(any(), any())
+ verify(windowManager, times(2)).removeView(any())
reset(windowManager)
underTest.removeView("id3", "test reason")
verify(windowManager).removeView(any())
-
- fakeClock.advanceTime(DISPLAY_VIEW_DELAY + 1)
-
assertThat(underTest.mostRecentViewInfo?.id).isEqualTo("id2")
assertThat(underTest.mostRecentViewInfo?.name).isEqualTo("Second name")
+ verify(configurationController, never()).removeCallback(any())
reset(windowManager)
underTest.removeView("id2", "test reason")
verify(windowManager).removeView(any())
-
- fakeClock.advanceTime(DISPLAY_VIEW_DELAY + 1)
-
assertThat(underTest.mostRecentViewInfo?.id).isEqualTo("id1")
assertThat(underTest.mostRecentViewInfo?.name).isEqualTo("First name")
+ verify(configurationController, never()).removeCallback(any())
reset(windowManager)
fakeClock.advanceTime(TIMEOUT_MS + 1)
verify(windowManager).removeView(any())
assertThat(underTest.activeViews.size).isEqualTo(0)
+ verify(configurationController).removeCallback(any())
}
@Test
@@ -347,18 +409,21 @@
underTest.displayView(ViewInfo("New name", id = "id1"))
verify(windowManager).addView(any(), any())
-
reset(windowManager)
+
underTest.displayView(ViewInfo("Second name", id = "id2"))
+
+ verify(windowManager).removeView(any())
+ verify(windowManager).addView(any(), any())
+ reset(windowManager)
+
underTest.removeView("id2", "test reason")
verify(windowManager).removeView(any())
-
- fakeClock.advanceTime(DISPLAY_VIEW_DELAY + 1)
-
+ verify(windowManager).addView(any(), any())
assertThat(underTest.mostRecentViewInfo?.id).isEqualTo("id1")
assertThat(underTest.mostRecentViewInfo?.name).isEqualTo("New name")
- assertThat(underTest.activeViews[0].second.name).isEqualTo("New name")
+ assertThat(underTest.activeViews[0].info.name).isEqualTo("New name")
reset(windowManager)
fakeClock.advanceTime(TIMEOUT_MS + 1)
@@ -368,19 +433,523 @@
}
@Test
- fun multipleViewsWithDifferentIds_viewsTimeouts_noViewLeftToDisplay() {
- underTest.displayView(ViewInfo("First name", id = "id1"))
- fakeClock.advanceTime(TIMEOUT_MS / 3)
- underTest.displayView(ViewInfo("Second name", id = "id2"))
- fakeClock.advanceTime(TIMEOUT_MS / 3)
- underTest.displayView(ViewInfo("Third name", id = "id3"))
+ fun multipleViews_mostRecentViewRemoved_otherViewsTimedOutAndNotDisplayed() {
+ underTest.displayView(ViewInfo("First name", id = "id1", timeoutMs = 4000))
+ fakeClock.advanceTime(1000)
+ underTest.displayView(ViewInfo("Second name", id = "id2", timeoutMs = 4000))
+ fakeClock.advanceTime(1000)
+ underTest.displayView(ViewInfo("Third name", id = "id3", timeoutMs = 20000))
reset(windowManager)
- fakeClock.advanceTime(TIMEOUT_MS + 1)
+ fakeClock.advanceTime(20000 + 1)
verify(windowManager).removeView(any())
verify(windowManager, never()).addView(any(), any())
assertThat(underTest.activeViews.size).isEqualTo(0)
+ verify(configurationController).removeCallback(any())
+ }
+
+ @Test
+ fun multipleViews_mostRecentViewRemoved_viewWithShortTimeLeftNotDisplayed() {
+ underTest.displayView(ViewInfo("First name", id = "id1", timeoutMs = 4000))
+ fakeClock.advanceTime(1000)
+ underTest.displayView(ViewInfo("Second name", id = "id2", timeoutMs = 2500))
+
+ reset(windowManager)
+ fakeClock.advanceTime(2500 + 1)
+ // At this point, 3501ms have passed, so id1 only has 499ms left which is not enough.
+ // So, it shouldn't be displayed.
+
+ verify(windowManager, never()).addView(any(), any())
+ assertThat(underTest.activeViews.size).isEqualTo(0)
+ verify(configurationController).removeCallback(any())
+ }
+
+ @Test
+ fun lowerThenHigherPriority_higherReplacesLower() {
+ underTest.displayView(
+ ViewInfo(
+ name = "normal",
+ windowTitle = "Normal Window Title",
+ id = "normal",
+ priority = ViewPriority.NORMAL,
+ )
+ )
+
+ val viewCaptor = argumentCaptor<View>()
+ val windowParamsCaptor = argumentCaptor<WindowManager.LayoutParams>()
+ verify(windowManager).addView(capture(viewCaptor), capture(windowParamsCaptor))
+ assertThat(windowParamsCaptor.value.title).isEqualTo("Normal Window Title")
+ reset(windowManager)
+
+ underTest.displayView(
+ ViewInfo(
+ name = "critical",
+ windowTitle = "Critical Window Title",
+ id = "critical",
+ priority = ViewPriority.CRITICAL,
+ )
+ )
+
+ verify(windowManager).removeView(viewCaptor.value)
+ verify(windowManager).addView(capture(viewCaptor), capture(windowParamsCaptor))
+ assertThat(windowParamsCaptor.value.title).isEqualTo("Critical Window Title")
+ verify(configurationController, never()).removeCallback(any())
+ }
+
+ @Test
+ fun lowerThenHigherPriority_lowerPriorityRedisplayed() {
+ underTest.displayView(
+ ViewInfo(
+ name = "normal",
+ windowTitle = "Normal Window Title",
+ id = "normal",
+ priority = ViewPriority.NORMAL,
+ timeoutMs = 10000
+ )
+ )
+
+ underTest.displayView(
+ ViewInfo(
+ name = "critical",
+ windowTitle = "Critical Window Title",
+ id = "critical",
+ priority = ViewPriority.CRITICAL,
+ timeoutMs = 2000
+ )
+ )
+
+ val viewCaptor = argumentCaptor<View>()
+ val windowParamsCaptor = argumentCaptor<WindowManager.LayoutParams>()
+ verify(windowManager, times(2)).addView(capture(viewCaptor), capture(windowParamsCaptor))
+ assertThat(windowParamsCaptor.allValues[0].title).isEqualTo("Normal Window Title")
+ assertThat(windowParamsCaptor.allValues[1].title).isEqualTo("Critical Window Title")
+ verify(windowManager).removeView(viewCaptor.allValues[0])
+
+ reset(windowManager)
+
+ // WHEN the critical's timeout has expired
+ fakeClock.advanceTime(2000 + 1)
+
+ // THEN the normal view is re-displayed
+ verify(windowManager).removeView(viewCaptor.allValues[1])
+ verify(windowManager).addView(any(), capture(windowParamsCaptor))
+ assertThat(windowParamsCaptor.value.title).isEqualTo("Normal Window Title")
+ verify(configurationController, never()).removeCallback(any())
+ }
+
+ @Test
+ fun lowerThenHigherPriority_lowerPriorityNotRedisplayedBecauseTimedOut() {
+ underTest.displayView(
+ ViewInfo(
+ name = "normal",
+ windowTitle = "Normal Window Title",
+ id = "normal",
+ priority = ViewPriority.NORMAL,
+ timeoutMs = 1000
+ )
+ )
+
+ underTest.displayView(
+ ViewInfo(
+ name = "critical",
+ windowTitle = "Critical Window Title",
+ id = "critical",
+ priority = ViewPriority.CRITICAL,
+ timeoutMs = 2000
+ )
+ )
+ reset(windowManager)
+
+ // WHEN the critical's timeout has expired
+ fakeClock.advanceTime(2000 + 1)
+
+ // THEN the normal view is not re-displayed since it already timed out
+ verify(windowManager).removeView(any())
+ verify(windowManager, never()).addView(any(), any())
+ assertThat(underTest.activeViews).isEmpty()
+ verify(configurationController).removeCallback(any())
+ }
+
+ @Test
+ fun higherThenLowerPriority_higherStaysDisplayed() {
+ underTest.displayView(
+ ViewInfo(
+ name = "critical",
+ windowTitle = "Critical Window Title",
+ id = "critical",
+ priority = ViewPriority.CRITICAL,
+ )
+ )
+
+ val viewCaptor = argumentCaptor<View>()
+ val windowParamsCaptor = argumentCaptor<WindowManager.LayoutParams>()
+ verify(windowManager).addView(capture(viewCaptor), capture(windowParamsCaptor))
+ assertThat(windowParamsCaptor.value.title).isEqualTo("Critical Window Title")
+ reset(windowManager)
+
+ underTest.displayView(
+ ViewInfo(
+ name = "normal",
+ windowTitle = "Normal Window Title",
+ id = "normal",
+ priority = ViewPriority.NORMAL,
+ )
+ )
+
+ verify(windowManager, never()).removeView(viewCaptor.value)
+ verify(windowManager, never()).addView(any(), any())
+ assertThat(underTest.activeViews.size).isEqualTo(2)
+ verify(configurationController, never()).removeCallback(any())
+ }
+
+ @Test
+ fun higherThenLowerPriority_lowerEventuallyDisplayed() {
+ underTest.displayView(
+ ViewInfo(
+ name = "critical",
+ windowTitle = "Critical Window Title",
+ id = "critical",
+ priority = ViewPriority.CRITICAL,
+ timeoutMs = 3000,
+ )
+ )
+
+ val viewCaptor = argumentCaptor<View>()
+ val windowParamsCaptor = argumentCaptor<WindowManager.LayoutParams>()
+ verify(windowManager).addView(capture(viewCaptor), capture(windowParamsCaptor))
+ assertThat(windowParamsCaptor.value.title).isEqualTo("Critical Window Title")
+ reset(windowManager)
+
+ underTest.displayView(
+ ViewInfo(
+ name = "normal",
+ windowTitle = "Normal Window Title",
+ id = "normal",
+ priority = ViewPriority.NORMAL,
+ timeoutMs = 5000,
+ )
+ )
+
+ verify(windowManager, never()).removeView(viewCaptor.value)
+ verify(windowManager, never()).addView(any(), any())
+ assertThat(underTest.activeViews.size).isEqualTo(2)
+
+ // WHEN the first critical view has timed out
+ fakeClock.advanceTime(3000 + 1)
+
+ // THEN the second normal view is displayed
+ verify(windowManager).removeView(viewCaptor.value)
+ verify(windowManager).addView(capture(viewCaptor), capture(windowParamsCaptor))
+ assertThat(windowParamsCaptor.value.title).isEqualTo("Normal Window Title")
+ assertThat(underTest.activeViews.size).isEqualTo(1)
+ verify(configurationController, never()).removeCallback(any())
+ }
+
+ @Test
+ fun higherThenLowerPriority_lowerNotDisplayedBecauseTimedOut() {
+ underTest.displayView(
+ ViewInfo(
+ name = "critical",
+ windowTitle = "Critical Window Title",
+ id = "critical",
+ priority = ViewPriority.CRITICAL,
+ timeoutMs = 3000,
+ )
+ )
+
+ val viewCaptor = argumentCaptor<View>()
+ val windowParamsCaptor = argumentCaptor<WindowManager.LayoutParams>()
+ verify(windowManager).addView(capture(viewCaptor), capture(windowParamsCaptor))
+ assertThat(windowParamsCaptor.value.title).isEqualTo("Critical Window Title")
+ reset(windowManager)
+
+ underTest.displayView(
+ ViewInfo(
+ name = "normal",
+ windowTitle = "Normal Window Title",
+ id = "normal",
+ priority = ViewPriority.NORMAL,
+ timeoutMs = 200,
+ )
+ )
+
+ verify(windowManager, never()).removeView(viewCaptor.value)
+ verify(windowManager, never()).addView(any(), any())
+ assertThat(underTest.activeViews.size).isEqualTo(2)
+ reset(windowManager)
+
+ // WHEN the first critical view has timed out
+ fakeClock.advanceTime(3000 + 1)
+
+ // THEN the second normal view is not displayed because it's already timed out
+ verify(windowManager).removeView(viewCaptor.value)
+ verify(windowManager, never()).addView(any(), any())
+ assertThat(underTest.activeViews).isEmpty()
+ verify(configurationController).removeCallback(any())
+ }
+
+ @Test
+ fun criticalThenNewCritical_newCriticalDisplayed() {
+ underTest.displayView(
+ ViewInfo(
+ name = "critical 1",
+ windowTitle = "Critical Window Title 1",
+ id = "critical1",
+ priority = ViewPriority.CRITICAL,
+ )
+ )
+
+ val viewCaptor = argumentCaptor<View>()
+ val windowParamsCaptor = argumentCaptor<WindowManager.LayoutParams>()
+ verify(windowManager).addView(capture(viewCaptor), capture(windowParamsCaptor))
+ assertThat(windowParamsCaptor.value.title).isEqualTo("Critical Window Title 1")
+ reset(windowManager)
+
+ underTest.displayView(
+ ViewInfo(
+ name = "critical 2",
+ windowTitle = "Critical Window Title 2",
+ id = "critical2",
+ priority = ViewPriority.CRITICAL,
+ )
+ )
+
+ verify(windowManager).removeView(viewCaptor.value)
+ verify(windowManager).addView(capture(viewCaptor), capture(windowParamsCaptor))
+ assertThat(windowParamsCaptor.value.title).isEqualTo("Critical Window Title 2")
+ assertThat(underTest.activeViews.size).isEqualTo(2)
+ verify(configurationController, never()).removeCallback(any())
+ }
+
+ @Test
+ fun normalThenNewNormal_newNormalDisplayed() {
+ underTest.displayView(
+ ViewInfo(
+ name = "normal 1",
+ windowTitle = "Normal Window Title 1",
+ id = "normal1",
+ priority = ViewPriority.NORMAL,
+ )
+ )
+
+ val viewCaptor = argumentCaptor<View>()
+ val windowParamsCaptor = argumentCaptor<WindowManager.LayoutParams>()
+ verify(windowManager).addView(capture(viewCaptor), capture(windowParamsCaptor))
+ assertThat(windowParamsCaptor.value.title).isEqualTo("Normal Window Title 1")
+ reset(windowManager)
+
+ underTest.displayView(
+ ViewInfo(
+ name = "normal 2",
+ windowTitle = "Normal Window Title 2",
+ id = "normal2",
+ priority = ViewPriority.NORMAL,
+ )
+ )
+
+ verify(windowManager).removeView(viewCaptor.value)
+ verify(windowManager).addView(capture(viewCaptor), capture(windowParamsCaptor))
+ assertThat(windowParamsCaptor.value.title).isEqualTo("Normal Window Title 2")
+ assertThat(underTest.activeViews.size).isEqualTo(2)
+ verify(configurationController, never()).removeCallback(any())
+ }
+
+ @Test
+ fun lowerPriorityViewUpdatedWhileHigherPriorityDisplayed_eventuallyDisplaysUpdated() {
+ // First, display a lower priority view
+ underTest.displayView(
+ ViewInfo(
+ name = "normal",
+ windowTitle = "Normal Window Title",
+ id = "normal",
+ priority = ViewPriority.NORMAL,
+ // At the end of the test, we'll verify that this information isn't re-displayed.
+ // Use a super long timeout so that, when we verify it wasn't re-displayed, we know
+ // that it wasn't because the view just timed out.
+ timeoutMs = 100000,
+ )
+ )
+
+ val viewCaptor = argumentCaptor<View>()
+ val windowParamsCaptor = argumentCaptor<WindowManager.LayoutParams>()
+ verify(windowManager).addView(capture(viewCaptor), capture(windowParamsCaptor))
+ assertThat(windowParamsCaptor.value.title).isEqualTo("Normal Window Title")
+ reset(windowManager)
+
+ // Then, display a higher priority view
+ fakeClock.advanceTime(1000)
+ underTest.displayView(
+ ViewInfo(
+ name = "critical",
+ windowTitle = "Critical Window Title",
+ id = "critical",
+ priority = ViewPriority.CRITICAL,
+ timeoutMs = 3000,
+ )
+ )
+
+ verify(windowManager).removeView(viewCaptor.value)
+ verify(windowManager).addView(capture(viewCaptor), capture(windowParamsCaptor))
+ assertThat(windowParamsCaptor.value.title).isEqualTo("Critical Window Title")
+ assertThat(underTest.activeViews.size).isEqualTo(2)
+ reset(windowManager)
+
+ // While the higher priority view is displayed, update the lower priority view with new
+ // information
+ fakeClock.advanceTime(1000)
+ val updatedViewInfo = ViewInfo(
+ name = "normal with update",
+ windowTitle = "Normal Window Title",
+ id = "normal",
+ priority = ViewPriority.NORMAL,
+ timeoutMs = 4000,
+ )
+ underTest.displayView(updatedViewInfo)
+
+ verify(windowManager, never()).removeView(viewCaptor.value)
+ verify(windowManager, never()).addView(any(), any())
+ assertThat(underTest.activeViews.size).isEqualTo(2)
+ reset(windowManager)
+
+ // WHEN the higher priority view times out
+ fakeClock.advanceTime(2001)
+
+ // THEN the higher priority view disappears and the lower priority view *with the updated
+ // information* gets displayed.
+ verify(windowManager).removeView(viewCaptor.value)
+ verify(windowManager).addView(capture(viewCaptor), capture(windowParamsCaptor))
+ assertThat(windowParamsCaptor.value.title).isEqualTo("Normal Window Title")
+ assertThat(underTest.activeViews.size).isEqualTo(1)
+ assertThat(underTest.mostRecentViewInfo).isEqualTo(updatedViewInfo)
+ reset(windowManager)
+
+ // WHEN the updated view times out
+ fakeClock.advanceTime(2001)
+
+ // THEN the old information is never displayed
+ verify(windowManager).removeView(viewCaptor.value)
+ verify(windowManager, never()).addView(any(), any())
+ assertThat(underTest.activeViews.size).isEqualTo(0)
+ }
+
+ @Test
+ fun oldViewUpdatedWhileNewViewDisplayed_eventuallyDisplaysUpdated() {
+ // First, display id1 view
+ underTest.displayView(
+ ViewInfo(
+ name = "name 1",
+ windowTitle = "Name 1 Title",
+ id = "id1",
+ priority = ViewPriority.NORMAL,
+ // At the end of the test, we'll verify that this information isn't re-displayed.
+ // Use a super long timeout so that, when we verify it wasn't re-displayed, we know
+ // that it wasn't because the view just timed out.
+ timeoutMs = 100000,
+ )
+ )
+
+ val viewCaptor = argumentCaptor<View>()
+ val windowParamsCaptor = argumentCaptor<WindowManager.LayoutParams>()
+ verify(windowManager).addView(capture(viewCaptor), capture(windowParamsCaptor))
+ assertThat(windowParamsCaptor.value.title).isEqualTo("Name 1 Title")
+ reset(windowManager)
+
+ // Then, display a new id2 view
+ fakeClock.advanceTime(1000)
+ underTest.displayView(
+ ViewInfo(
+ name = "name 2",
+ windowTitle = "Name 2 Title",
+ id = "id2",
+ priority = ViewPriority.NORMAL,
+ timeoutMs = 3000,
+ )
+ )
+
+ verify(windowManager).removeView(viewCaptor.value)
+ verify(windowManager).addView(capture(viewCaptor), capture(windowParamsCaptor))
+ assertThat(windowParamsCaptor.value.title).isEqualTo("Name 2 Title")
+ assertThat(underTest.activeViews.size).isEqualTo(2)
+ reset(windowManager)
+
+ // While the id2 view is displayed, re-display the id1 view with new information
+ fakeClock.advanceTime(1000)
+ val updatedViewInfo = ViewInfo(
+ name = "name 1 with update",
+ windowTitle = "Name 1 Title",
+ id = "id1",
+ priority = ViewPriority.NORMAL,
+ timeoutMs = 3000,
+ )
+ underTest.displayView(updatedViewInfo)
+
+ verify(windowManager).removeView(viewCaptor.value)
+ verify(windowManager).addView(capture(viewCaptor), capture(windowParamsCaptor))
+ assertThat(windowParamsCaptor.value.title).isEqualTo("Name 1 Title")
+ assertThat(underTest.activeViews.size).isEqualTo(2)
+ reset(windowManager)
+
+ // WHEN the id1 view with new information times out
+ fakeClock.advanceTime(3001)
+
+ // THEN the id1 view disappears and the old id1 information is never displayed
+ verify(windowManager).removeView(viewCaptor.value)
+ verify(windowManager, never()).addView(any(), any())
+ assertThat(underTest.activeViews.size).isEqualTo(0)
+ }
+
+ @Test
+ fun oldViewUpdatedWhileNewViewDisplayed_usesNewTimeout() {
+ // First, display id1 view
+ underTest.displayView(
+ ViewInfo(
+ name = "name 1",
+ windowTitle = "Name 1 Title",
+ id = "id1",
+ priority = ViewPriority.NORMAL,
+ timeoutMs = 5000,
+ )
+ )
+
+ // Then, display a new id2 view
+ fakeClock.advanceTime(1000)
+ underTest.displayView(
+ ViewInfo(
+ name = "name 2",
+ windowTitle = "Name 2 Title",
+ id = "id2",
+ priority = ViewPriority.NORMAL,
+ timeoutMs = 3000,
+ )
+ )
+ reset(windowManager)
+
+ // While the id2 view is displayed, re-display the id1 view with new information *and a
+ // longer timeout*
+ fakeClock.advanceTime(1000)
+ val updatedViewInfo = ViewInfo(
+ name = "name 1 with update",
+ windowTitle = "Name 1 Title",
+ id = "id1",
+ priority = ViewPriority.NORMAL,
+ timeoutMs = 30000,
+ )
+ underTest.displayView(updatedViewInfo)
+
+ val viewCaptor = argumentCaptor<View>()
+ val windowParamsCaptor = argumentCaptor<WindowManager.LayoutParams>()
+ verify(windowManager).addView(capture(viewCaptor), capture(windowParamsCaptor))
+ assertThat(windowParamsCaptor.value.title).isEqualTo("Name 1 Title")
+ assertThat(underTest.activeViews.size).isEqualTo(2)
+ reset(windowManager)
+
+ // WHEN id1's *old* timeout occurs
+ fakeClock.advanceTime(3001)
+
+ // THEN id1 is still displayed because it was updated with a new timeout
+ verify(windowManager, never()).removeView(viewCaptor.value)
+ assertThat(underTest.activeViews.size).isEqualTo(1)
}
@Test
@@ -395,6 +964,7 @@
verify(windowManager).removeView(any())
verify(logger).logViewRemoval(deviceId, reason)
+ verify(configurationController).removeCallback(any())
}
@Test
@@ -414,14 +984,15 @@
inner class TestController(
context: Context,
- logger: TemporaryViewLogger,
+ logger: TemporaryViewLogger<ViewInfo>,
windowManager: WindowManager,
@Main mainExecutor: DelayableExecutor,
accessibilityManager: AccessibilityManager,
configurationController: ConfigurationController,
powerManager: PowerManager,
wakeLockBuilder: WakeLock.Builder,
- ) : TemporaryViewDisplayController<ViewInfo, TemporaryViewLogger>(
+ systemClock: SystemClock,
+ ) : TemporaryViewDisplayController<ViewInfo, TemporaryViewLogger<ViewInfo>>(
context,
logger,
windowManager,
@@ -431,6 +1002,7 @@
powerManager,
R.layout.chipbar,
wakeLockBuilder,
+ systemClock,
) {
var mostRecentViewInfo: ViewInfo? = null
@@ -447,12 +1019,13 @@
override fun start() {}
}
- inner class ViewInfo(
+ data class ViewInfo(
val name: String,
override val windowTitle: String = "Window Title",
override val wakeReason: String = "WAKE_REASON",
- override val timeoutMs: Int = 1,
+ override val timeoutMs: Int = TIMEOUT_MS.toInt(),
override val id: String = "id",
+ override val priority: ViewPriority = ViewPriority.NORMAL,
) : TemporaryViewInfo()
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/temporarydisplay/TemporaryViewLoggerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/temporarydisplay/TemporaryViewLoggerTest.kt
index 116b8fe..2e66b20 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/temporarydisplay/TemporaryViewLoggerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/temporarydisplay/TemporaryViewLoggerTest.kt
@@ -32,7 +32,7 @@
@SmallTest
class TemporaryViewLoggerTest : SysuiTestCase() {
private lateinit var buffer: LogBuffer
- private lateinit var logger: TemporaryViewLogger
+ private lateinit var logger: TemporaryViewLogger<TemporaryViewInfo>
@Before
fun setUp() {
@@ -44,13 +44,22 @@
@Test
fun logViewAddition_bufferHasLog() {
- logger.logViewAddition("test id", "Test Window Title")
+ val info =
+ object : TemporaryViewInfo() {
+ override val id: String = "test id"
+ override val priority: ViewPriority = ViewPriority.CRITICAL
+ override val windowTitle: String = "Test Window Title"
+ override val wakeReason: String = "wake reason"
+ }
+
+ logger.logViewAddition(info)
val stringWriter = StringWriter()
buffer.dump(PrintWriter(stringWriter), tailLength = 0)
val actualString = stringWriter.toString()
assertThat(actualString).contains(TAG)
+ assertThat(actualString).contains("test id")
assertThat(actualString).contains("Test Window Title")
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/temporarydisplay/chipbar/ChipbarCoordinatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/temporarydisplay/chipbar/ChipbarCoordinatorTest.kt
index 7014f93..2e4d8e7 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/temporarydisplay/chipbar/ChipbarCoordinatorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/temporarydisplay/chipbar/ChipbarCoordinatorTest.kt
@@ -39,6 +39,7 @@
import com.android.systemui.plugins.FalsingManager
import com.android.systemui.statusbar.VibratorHelper
import com.android.systemui.statusbar.policy.ConfigurationController
+import com.android.systemui.temporarydisplay.ViewPriority
import com.android.systemui.util.concurrency.FakeExecutor
import com.android.systemui.util.mockito.any
import com.android.systemui.util.mockito.eq
@@ -105,6 +106,7 @@
viewUtil,
vibratorHelper,
fakeWakeLockBuilder,
+ fakeClock,
)
underTest.start()
}
@@ -408,6 +410,7 @@
wakeReason = WAKE_REASON,
timeoutMs = TIMEOUT,
id = DEVICE_ID,
+ priority = ViewPriority.NORMAL,
)
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/temporarydisplay/chipbar/FakeChipbarCoordinator.kt b/packages/SystemUI/tests/src/com/android/systemui/temporarydisplay/chipbar/FakeChipbarCoordinator.kt
index beedf9f..d5167b3 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/temporarydisplay/chipbar/FakeChipbarCoordinator.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/temporarydisplay/chipbar/FakeChipbarCoordinator.kt
@@ -26,6 +26,7 @@
import com.android.systemui.statusbar.VibratorHelper
import com.android.systemui.statusbar.policy.ConfigurationController
import com.android.systemui.util.concurrency.DelayableExecutor
+import com.android.systemui.util.time.SystemClock
import com.android.systemui.util.view.ViewUtil
import com.android.systemui.util.wakelock.WakeLock
@@ -43,6 +44,7 @@
viewUtil: ViewUtil,
vibratorHelper: VibratorHelper,
wakeLockBuilder: WakeLock.Builder,
+ systemClock: SystemClock,
) :
ChipbarCoordinator(
context,
@@ -57,6 +59,7 @@
viewUtil,
vibratorHelper,
wakeLockBuilder,
+ systemClock,
) {
override fun animateViewOut(view: ViewGroup, onAnimationEnd: Runnable) {
// Just bypass the animation in tests
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardRepository.kt
index 5c2a915..5501949 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardRepository.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardRepository.kt
@@ -120,6 +120,14 @@
_dozeAmount.value = dozeAmount
}
+ fun setWakefulnessModel(model: WakefulnessModel) {
+ _wakefulnessModel.value = model
+ }
+
+ fun setBouncerShowing(isShowing: Boolean) {
+ _isBouncerShowing.value = isShowing
+ }
+
fun setBiometricUnlockState(state: BiometricUnlockModel) {
_biometricUnlockState.tryEmit(state)
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/shade/data/repository/FakeShadeRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/shade/data/repository/FakeShadeRepository.kt
new file mode 100644
index 0000000..2c0a8fd
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/shade/data/repository/FakeShadeRepository.kt
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+package com.android.systemui.shade.data.repository
+
+import com.android.systemui.shade.domain.model.ShadeModel
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.MutableStateFlow
+
+/** Fake implementation of [KeyguardRepository] */
+class FakeShadeRepository : ShadeRepository {
+
+ private val _shadeModel = MutableStateFlow(ShadeModel())
+ override val shadeModel: Flow<ShadeModel> = _shadeModel
+
+ fun setShadeModel(model: ShadeModel) {
+ _shadeModel.value = model
+ }
+}
diff --git a/services/autofill/java/com/android/server/autofill/Session.java b/services/autofill/java/com/android/server/autofill/Session.java
index 5c11e2c..60890d5 100644
--- a/services/autofill/java/com/android/server/autofill/Session.java
+++ b/services/autofill/java/com/android/server/autofill/Session.java
@@ -24,6 +24,7 @@
import static android.service.autofill.FillEventHistory.Event.UI_TYPE_UNKNOWN;
import static android.service.autofill.FillRequest.FLAG_MANUAL_REQUEST;
import static android.service.autofill.FillRequest.FLAG_PASSWORD_INPUT_TYPE;
+import static android.service.autofill.FillRequest.FLAG_RESET_FILL_DIALOG_STATE;
import static android.service.autofill.FillRequest.FLAG_SUPPORTS_FILL_DIALOG;
import static android.service.autofill.FillRequest.FLAG_VIEW_NOT_FOCUSED;
import static android.service.autofill.FillRequest.INVALID_REQUEST_ID;
@@ -411,6 +412,14 @@
@GuardedBy("mLock")
private boolean mStartedLogEventWithoutFocus;
+ /**
+ * Keeps the fill dialog trigger ids of the last response. This invalidates
+ * the trigger ids of the previous response.
+ */
+ @Nullable
+ @GuardedBy("mLock")
+ private AutofillId[] mLastFillDialogTriggerIds;
+
void onSwitchInputMethodLocked() {
// One caveat is that for the case where the focus is on a field for which regular autofill
// returns null, and augmented autofill is triggered, and then the user switches the input
@@ -1171,6 +1180,8 @@
return;
}
+ mLastFillDialogTriggerIds = response.getFillDialogTriggerIds();
+
final int flags = response.getFlags();
if ((flags & FillResponse.FLAG_DELAY_FILL) != 0) {
Slog.v(TAG, "Service requested to wait for delayed fill response.");
@@ -1277,6 +1288,7 @@
+ (timedOut ? "timeout" : "failure"));
}
mService.resetLastResponse();
+ mLastFillDialogTriggerIds = null;
final LogMaker requestLog = mRequestLogs.get(requestId);
if (requestLog == null) {
Slog.w(TAG, "onFillRequestFailureOrTimeout(): no log for id " + requestId);
@@ -2973,6 +2985,11 @@
}
}
+ if ((flags & FLAG_RESET_FILL_DIALOG_STATE) != 0) {
+ if (sDebug) Log.d(TAG, "force to reset fill dialog state");
+ mSessionFlags.mFillDialogDisabled = false;
+ }
+
switch(action) {
case ACTION_START_SESSION:
// View is triggering autofill.
@@ -3402,10 +3419,8 @@
}
private boolean isFillDialogUiEnabled() {
- // TODO read from Settings or somewhere
- final boolean isSettingsEnabledFillDialog = true;
synchronized (mLock) {
- return isSettingsEnabledFillDialog && !mSessionFlags.mFillDialogDisabled;
+ return !mSessionFlags.mFillDialogDisabled;
}
}
@@ -3431,14 +3446,25 @@
AutofillId filledId, String filterText, int flags) {
if (!isFillDialogUiEnabled()) {
// Unsupported fill dialog UI
+ if (sDebug) Log.w(TAG, "requestShowFillDialog: fill dialog is disabled");
return false;
}
if ((flags & FillRequest.FLAG_IME_SHOWING) != 0) {
// IME is showing, fallback to normal suggestions UI
+ if (sDebug) Log.w(TAG, "requestShowFillDialog: IME is showing");
return false;
}
+ synchronized (mLock) {
+ if (mLastFillDialogTriggerIds == null
+ || !ArrayUtils.contains(mLastFillDialogTriggerIds, filledId)) {
+ // Last fill dialog triggered ids are changed.
+ if (sDebug) Log.w(TAG, "Last fill dialog triggered ids are changed.");
+ return false;
+ }
+ }
+
final Drawable serviceIcon = getServiceIcon();
getUiForShowing().showFillDialog(filledId, response, filterText,
@@ -4278,6 +4304,13 @@
if (mSessionFlags.mAugmentedAutofillOnly) {
pw.print(prefix); pw.println("For Augmented Autofill Only");
}
+ if (mSessionFlags.mFillDialogDisabled) {
+ pw.print(prefix); pw.println("Fill Dialog disabled");
+ }
+ if (mLastFillDialogTriggerIds != null) {
+ pw.print(prefix); pw.println("Last Fill Dialog trigger ids: ");
+ pw.println(mSelectedDatasetIds);
+ }
if (mAugmentedAutofillDestroyer != null) {
pw.print(prefix); pw.println("has mAugmentedAutofillDestroyer");
}
diff --git a/services/core/java/com/android/server/GestureLauncherService.java b/services/core/java/com/android/server/GestureLauncherService.java
index e529010..daba4c0 100644
--- a/services/core/java/com/android/server/GestureLauncherService.java
+++ b/services/core/java/com/android/server/GestureLauncherService.java
@@ -79,7 +79,7 @@
* completed faster than this, we assume it's not performed by human and the
* event gets ignored.
*/
- @VisibleForTesting static final int EMERGENCY_GESTURE_TAP_DETECTION_MIN_TIME_MS = 160;
+ @VisibleForTesting static final int EMERGENCY_GESTURE_TAP_DETECTION_MIN_TIME_MS = 200;
/**
* Interval in milliseconds in which the power button must be depressed in succession to be
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/Fingerprint21UdfpsMock.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/Fingerprint21UdfpsMock.java
index bea0f4f..846c2d9 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/Fingerprint21UdfpsMock.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/Fingerprint21UdfpsMock.java
@@ -417,7 +417,7 @@
}
@Override
- public void onTrustChanged(boolean enabled, int userId, int flags,
+ public void onTrustChanged(boolean enabled, boolean newlyUnlocked, int userId, int flags,
List<String> trustGrantedMessages) {
mUserHasTrust.put(userId, enabled);
}
diff --git a/services/core/java/com/android/server/display/DisplayDeviceConfig.java b/services/core/java/com/android/server/display/DisplayDeviceConfig.java
index 2be2d58..401d184 100644
--- a/services/core/java/com/android/server/display/DisplayDeviceConfig.java
+++ b/services/core/java/com/android/server/display/DisplayDeviceConfig.java
@@ -36,16 +36,19 @@
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.display.BrightnessSynchronizer;
import com.android.server.display.config.AutoBrightness;
+import com.android.server.display.config.BlockingZoneConfig;
import com.android.server.display.config.BrightnessThresholds;
import com.android.server.display.config.BrightnessThrottlingMap;
import com.android.server.display.config.BrightnessThrottlingPoint;
import com.android.server.display.config.Density;
+import com.android.server.display.config.DisplayBrightnessPoint;
import com.android.server.display.config.DisplayConfiguration;
import com.android.server.display.config.DisplayQuirks;
import com.android.server.display.config.HbmTiming;
import com.android.server.display.config.HighBrightnessMode;
import com.android.server.display.config.NitsMap;
import com.android.server.display.config.Point;
+import com.android.server.display.config.RefreshRateConfigs;
import com.android.server.display.config.RefreshRateRange;
import com.android.server.display.config.SdrHdrRatioMap;
import com.android.server.display.config.SdrHdrRatioPoint;
@@ -130,6 +133,35 @@
* </brightnessThrottlingMap>
* </thermalThrottling>
*
+ * <refreshRate>
+ * <lowerBlockingZoneConfigs>
+ * <defaultRefreshRate>75</defaultRefreshRate>
+ * <blockingZoneThreshold>
+ * <displayBrightnessPoint>
+ * <lux>50</lux>
+ * <nits>45.3</nits>
+ * </displayBrightnessPoint>
+ * <displayBrightnessPoint>
+ * <lux>60</lux>
+ * <nits>55.2</nits>
+ * </displayBrightnessPoint>
+ * </blockingZoneThreshold>
+ * </lowerBlockingZoneConfigs>
+ * <higherBlockingZoneConfigs>
+ * <defaultRefreshRate>90</defaultRefreshRate>
+ * <blockingZoneThreshold>
+ * <displayBrightnessPoint>
+ * <lux>500</lux>
+ * <nits>245.3</nits>
+ * </displayBrightnessPoint>
+ * <displayBrightnessPoint>
+ * <lux>600</lux>
+ * <nits>232.3</nits>
+ * </displayBrightnessPoint>
+ * </blockingZoneThreshold>
+ * </higherBlockingZoneConfigs>
+ * </refreshRate>
+ *
* <highBrightnessMode enabled="true">
* <transitionPoint>0.62</transitionPoint>
* <minimumLux>10000</minimumLux>
@@ -358,6 +390,9 @@
private static final String STABLE_ID_SUFFIX_FORMAT = "id_%d";
private static final String NO_SUFFIX_FORMAT = "%d";
private static final long STABLE_FLAG = 1L << 62;
+ private static final int DEFAULT_LOW_REFRESH_RATE = 60;
+ private static final int DEFAULT_HIGH_REFRESH_RATE = 0;
+ private static final int[] DEFAULT_BRIGHTNESS_THRESHOLDS = new int[]{};
private static final float[] DEFAULT_AMBIENT_THRESHOLD_LEVELS = new float[]{0f};
private static final float[] DEFAULT_AMBIENT_BRIGHTENING_THRESHOLDS = new float[]{100f};
@@ -512,6 +547,49 @@
// This stores the raw value loaded from the config file - true if not written.
private boolean mDdcAutoBrightnessAvailable = true;
+ /**
+ * The default peak refresh rate for a given device. This value prevents the framework from
+ * using higher refresh rates, even if display modes with higher refresh rates are available
+ * from hardware composer. Only has an effect if the value is non-zero.
+ */
+ private int mDefaultHighRefreshRate = DEFAULT_HIGH_REFRESH_RATE;
+
+ /**
+ * The default refresh rate for a given device. This value sets the higher default
+ * refresh rate. If the hardware composer on the device supports display modes with
+ * a higher refresh rate than the default value specified here, the framework may use those
+ * higher refresh rate modes if an app chooses one by setting preferredDisplayModeId or calling
+ * setFrameRate(). We have historically allowed fallback to mDefaultHighRefreshRate if
+ * mDefaultLowRefreshRate is set to 0, but this is not supported anymore.
+ */
+ private int mDefaultLowRefreshRate = DEFAULT_LOW_REFRESH_RATE;
+
+ /**
+ * The display uses different gamma curves for different refresh rates. It's hard for panel
+ * vendors to tune the curves to have exact same brightness for different refresh rate. So
+ * brightness flickers could be observed at switch time. The issue is worse at the gamma lower
+ * end. In addition, human eyes are more sensitive to the flicker at darker environment. To
+ * prevent flicker, we only support higher refresh rates if the display brightness is above a
+ * threshold. For example, no higher refresh rate if display brightness <= disp0 && ambient
+ * brightness <= amb0 || display brightness <= disp1 && ambient brightness <= amb1
+ */
+ private int[] mLowDisplayBrightnessThresholds = DEFAULT_BRIGHTNESS_THRESHOLDS;
+ private int[] mLowAmbientBrightnessThresholds = DEFAULT_BRIGHTNESS_THRESHOLDS;
+
+ /**
+ * The display uses different gamma curves for different refresh rates. It's hard for panel
+ * vendors to tune the curves to have exact same brightness for different refresh rate. So
+ * brightness flickers could be observed at switch time. The issue can be observed on the screen
+ * with even full white content at the high brightness. To prevent flickering, we support fixed
+ * refresh rates if the display and ambient brightness are equal to or above the provided
+ * thresholds. You can define multiple threshold levels as higher brightness environments may
+ * have lower display brightness requirements for the flickering is visible. For example, fixed
+ * refresh rate if display brightness >= disp0 && ambient brightness >= amb0 || display
+ * brightness >= disp1 && ambient brightness >= amb1
+ */
+ private int[] mHighDisplayBrightnessThresholds = DEFAULT_BRIGHTNESS_THRESHOLDS;
+ private int[] mHighAmbientBrightnessThresholds = DEFAULT_BRIGHTNESS_THRESHOLDS;
+
// Brightness Throttling data may be updated via the DeviceConfig. Here we store the original
// data, which comes from the ddc, and the current one, which may be the DeviceConfig
// overwritten value.
@@ -1196,15 +1274,15 @@
/**
* @return Default peak refresh rate of the associated display
*/
- public int getDefaultPeakRefreshRate() {
- return mContext.getResources().getInteger(R.integer.config_defaultPeakRefreshRate);
+ public int getDefaultHighRefreshRate() {
+ return mDefaultHighRefreshRate;
}
/**
* @return Default refresh rate of the associated display
*/
- public int getDefaultRefreshRate() {
- return mContext.getResources().getInteger(R.integer.config_defaultRefreshRate);
+ public int getDefaultLowRefreshRate() {
+ return mDefaultLowRefreshRate;
}
/**
@@ -1213,8 +1291,7 @@
* allowed
*/
public int[] getLowDisplayBrightnessThresholds() {
- return mContext.getResources().getIntArray(
- R.array.config_brightnessThresholdsOfPeakRefreshRate);
+ return mLowDisplayBrightnessThresholds;
}
/**
@@ -1223,8 +1300,7 @@
* allowed
*/
public int[] getLowAmbientBrightnessThresholds() {
- return mContext.getResources().getIntArray(
- R.array.config_ambientThresholdsOfPeakRefreshRate);
+ return mLowAmbientBrightnessThresholds;
}
/**
@@ -1233,8 +1309,7 @@
* allowed
*/
public int[] getHighDisplayBrightnessThresholds() {
- return mContext.getResources().getIntArray(
- R.array.config_highDisplayBrightnessThresholdsOfFixedRefreshRate);
+ return mHighDisplayBrightnessThresholds;
}
/**
@@ -1243,8 +1318,7 @@
* allowed
*/
public int[] getHighAmbientBrightnessThresholds() {
- return mContext.getResources().getIntArray(
- R.array.config_highAmbientBrightnessThresholdsOfFixedRefreshRate);
+ return mHighAmbientBrightnessThresholds;
}
@Override
@@ -1336,6 +1410,17 @@
+ ", mBrightnessLevelsNits= " + Arrays.toString(mBrightnessLevelsNits)
+ ", mDdcAutoBrightnessAvailable= " + mDdcAutoBrightnessAvailable
+ ", mAutoBrightnessAvailable= " + mAutoBrightnessAvailable
+ + "\n"
+ + ", mDefaultRefreshRate= " + mDefaultLowRefreshRate
+ + ", mDefaultPeakRefreshRate= " + mDefaultHighRefreshRate
+ + ", mLowDisplayBrightnessThresholds= "
+ + Arrays.toString(mLowDisplayBrightnessThresholds)
+ + ", mLowAmbientBrightnessThresholds= "
+ + Arrays.toString(mLowAmbientBrightnessThresholds)
+ + ", mHighDisplayBrightnessThresholds= "
+ + Arrays.toString(mHighDisplayBrightnessThresholds)
+ + ", mHighAmbientBrightnessThresholds= "
+ + Arrays.toString(mHighAmbientBrightnessThresholds)
+ "}";
}
@@ -1393,6 +1478,7 @@
loadAmbientHorizonFromDdc(config);
loadBrightnessChangeThresholds(config);
loadAutoBrightnessConfigValues(config);
+ loadRefreshRateSetting(config);
} else {
Slog.w(TAG, "DisplayDeviceConfig file is null");
}
@@ -1415,6 +1501,7 @@
useFallbackProxSensor();
loadAutoBrightnessConfigsFromConfigXml();
loadAutoBrightnessAvailableFromConfigXml();
+ loadRefreshRateSetting(null);
mLoadedFrom = "<config.xml>";
}
@@ -1625,6 +1712,143 @@
}
}
+ private void loadRefreshRateSetting(DisplayConfiguration config) {
+ final RefreshRateConfigs refreshRateConfigs =
+ (config == null) ? null : config.getRefreshRate();
+ BlockingZoneConfig lowerBlockingZoneConfig =
+ (refreshRateConfigs == null) ? null
+ : refreshRateConfigs.getLowerBlockingZoneConfigs();
+ BlockingZoneConfig higherBlockingZoneConfig =
+ (refreshRateConfigs == null) ? null
+ : refreshRateConfigs.getHigherBlockingZoneConfigs();
+ loadLowerRefreshRateBlockingZones(lowerBlockingZoneConfig);
+ loadHigherRefreshRateBlockingZones(higherBlockingZoneConfig);
+ }
+
+
+ /**
+ * Loads the refresh rate configurations pertaining to the upper blocking zones.
+ */
+ private void loadLowerRefreshRateBlockingZones(BlockingZoneConfig lowerBlockingZoneConfig) {
+ loadLowerBlockingZoneDefaultRefreshRate(lowerBlockingZoneConfig);
+ loadLowerBrightnessThresholds(lowerBlockingZoneConfig);
+ }
+
+ /**
+ * Loads the refresh rate configurations pertaining to the upper blocking zones.
+ */
+ private void loadHigherRefreshRateBlockingZones(BlockingZoneConfig upperBlockingZoneConfig) {
+ loadHigherBlockingZoneDefaultRefreshRate(upperBlockingZoneConfig);
+ loadHigherBrightnessThresholds(upperBlockingZoneConfig);
+ }
+
+ /**
+ * Loads the default peak refresh rate. Internally, this takes care of loading
+ * the value from the display config, and if not present, falls back to config.xml.
+ */
+ private void loadHigherBlockingZoneDefaultRefreshRate(
+ BlockingZoneConfig upperBlockingZoneConfig) {
+ if (upperBlockingZoneConfig == null) {
+ mDefaultHighRefreshRate = mContext.getResources().getInteger(
+ com.android.internal.R.integer.config_defaultPeakRefreshRate);
+ } else {
+ mDefaultHighRefreshRate =
+ upperBlockingZoneConfig.getDefaultRefreshRate().intValue();
+ }
+ }
+
+ /**
+ * Loads the default refresh rate. Internally, this takes care of loading
+ * the value from the display config, and if not present, falls back to config.xml.
+ */
+ private void loadLowerBlockingZoneDefaultRefreshRate(
+ BlockingZoneConfig lowerBlockingZoneConfig) {
+ if (lowerBlockingZoneConfig == null) {
+ mDefaultLowRefreshRate = mContext.getResources().getInteger(
+ com.android.internal.R.integer.config_defaultRefreshRate);
+ } else {
+ mDefaultLowRefreshRate =
+ lowerBlockingZoneConfig.getDefaultRefreshRate().intValue();
+ }
+ }
+
+ /**
+ * Loads the lower brightness thresholds for refresh rate switching. Internally, this takes care
+ * of loading the value from the display config, and if not present, falls back to config.xml.
+ */
+ private void loadLowerBrightnessThresholds(BlockingZoneConfig lowerBlockingZoneConfig) {
+ if (lowerBlockingZoneConfig == null) {
+ mLowDisplayBrightnessThresholds = mContext.getResources().getIntArray(
+ R.array.config_brightnessThresholdsOfPeakRefreshRate);
+ mLowAmbientBrightnessThresholds = mContext.getResources().getIntArray(
+ R.array.config_ambientThresholdsOfPeakRefreshRate);
+ if (mLowDisplayBrightnessThresholds == null || mLowAmbientBrightnessThresholds == null
+ || mLowDisplayBrightnessThresholds.length
+ != mLowAmbientBrightnessThresholds.length) {
+ throw new RuntimeException("display low brightness threshold array and ambient "
+ + "brightness threshold array have different length: "
+ + "mLowDisplayBrightnessThresholds="
+ + Arrays.toString(mLowDisplayBrightnessThresholds)
+ + ", mLowAmbientBrightnessThresholds="
+ + Arrays.toString(mLowAmbientBrightnessThresholds));
+ }
+ } else {
+ List<DisplayBrightnessPoint> lowerThresholdDisplayBrightnessPoints =
+ lowerBlockingZoneConfig.getBlockingZoneThreshold().getDisplayBrightnessPoint();
+ int size = lowerThresholdDisplayBrightnessPoints.size();
+ mLowDisplayBrightnessThresholds = new int[size];
+ mLowAmbientBrightnessThresholds = new int[size];
+ for (int i = 0; i < size; i++) {
+ // We are explicitly casting this value to an integer to be able to reuse the
+ // existing DisplayBrightnessPoint type. It is fine to do this because the round off
+ // will have the negligible and unnoticeable impact on the loaded thresholds.
+ mLowDisplayBrightnessThresholds[i] = (int) lowerThresholdDisplayBrightnessPoints
+ .get(i).getNits().floatValue();
+ mLowAmbientBrightnessThresholds[i] = lowerThresholdDisplayBrightnessPoints
+ .get(i).getLux().intValue();
+ }
+ }
+ }
+
+ /**
+ * Loads the higher brightness thresholds for refresh rate switching. Internally, this takes
+ * care of loading the value from the display config, and if not present, falls back to
+ * config.xml.
+ */
+ private void loadHigherBrightnessThresholds(BlockingZoneConfig blockingZoneConfig) {
+ if (blockingZoneConfig == null) {
+ mHighDisplayBrightnessThresholds = mContext.getResources().getIntArray(
+ R.array.config_highDisplayBrightnessThresholdsOfFixedRefreshRate);
+ mHighAmbientBrightnessThresholds = mContext.getResources().getIntArray(
+ R.array.config_highAmbientBrightnessThresholdsOfFixedRefreshRate);
+ if (mHighAmbientBrightnessThresholds == null || mHighDisplayBrightnessThresholds == null
+ || mHighAmbientBrightnessThresholds.length
+ != mHighDisplayBrightnessThresholds.length) {
+ throw new RuntimeException("display high brightness threshold array and ambient "
+ + "brightness threshold array have different length: "
+ + "mHighDisplayBrightnessThresholds="
+ + Arrays.toString(mHighDisplayBrightnessThresholds)
+ + ", mHighAmbientBrightnessThresholds="
+ + Arrays.toString(mHighAmbientBrightnessThresholds));
+ }
+ } else {
+ List<DisplayBrightnessPoint> higherThresholdDisplayBrightnessPoints =
+ blockingZoneConfig.getBlockingZoneThreshold().getDisplayBrightnessPoint();
+ int size = higherThresholdDisplayBrightnessPoints.size();
+ mHighDisplayBrightnessThresholds = new int[size];
+ mHighAmbientBrightnessThresholds = new int[size];
+ for (int i = 0; i < size; i++) {
+ // We are explicitly casting this value to an integer to be able to reuse the
+ // existing DisplayBrightnessPoint type. It is fine to do this because the round off
+ // will have the negligible and unnoticeable impact on the loaded thresholds.
+ mHighDisplayBrightnessThresholds[i] = (int) higherThresholdDisplayBrightnessPoints
+ .get(i).getNits().floatValue();
+ mHighAmbientBrightnessThresholds[i] = higherThresholdDisplayBrightnessPoints
+ .get(i).getLux().intValue();
+ }
+ }
+ }
+
private void loadAutoBrightnessConfigValues(DisplayConfiguration config) {
final AutoBrightness autoBrightness = config.getAutoBrightness();
loadAutoBrightnessBrighteningLightDebounce(autoBrightness);
diff --git a/services/core/java/com/android/server/display/DisplayManagerService.java b/services/core/java/com/android/server/display/DisplayManagerService.java
index f9c8f06..3da7d83 100644
--- a/services/core/java/com/android/server/display/DisplayManagerService.java
+++ b/services/core/java/com/android/server/display/DisplayManagerService.java
@@ -3652,44 +3652,21 @@
@Override
public Set<DisplayInfo> getPossibleDisplayInfo(int displayId) {
synchronized (mSyncRoot) {
- // Retrieve the group associated with this display id.
- final int displayGroupId =
- mLogicalDisplayMapper.getDisplayGroupIdFromDisplayIdLocked(displayId);
- if (displayGroupId == Display.INVALID_DISPLAY_GROUP) {
- Slog.w(TAG,
- "Can't get possible display info since display group for " + displayId
- + " does not exist");
- return new ArraySet<>();
- }
-
- // Assume any display in this group can be swapped out for the given display id.
Set<DisplayInfo> possibleInfo = new ArraySet<>();
- final DisplayGroup group = mLogicalDisplayMapper.getDisplayGroupLocked(
- displayGroupId);
- for (int i = 0; i < group.getSizeLocked(); i++) {
- final int id = group.getIdLocked(i);
- final LogicalDisplay logical = mLogicalDisplayMapper.getDisplayLocked(id);
- if (logical == null) {
- Slog.w(TAG,
- "Can't get possible display info since logical display for "
- + "display id " + id + " does not exist, as part of group "
- + displayGroupId);
- } else {
- possibleInfo.add(logical.getDisplayInfoLocked());
- }
- }
-
- // For the supported device states, retrieve the DisplayInfos for the logical
- // display layout.
+ // For each of supported device states, retrieve the display layout of that state,
+ // and return all of the DisplayInfos (one per state) for the given display id.
if (mDeviceStateManager == null) {
Slog.w(TAG, "Can't get supported states since DeviceStateManager not ready");
- } else {
- final int[] supportedStates =
- mDeviceStateManager.getSupportedStateIdentifiers();
- for (int state : supportedStates) {
- possibleInfo.addAll(
- mLogicalDisplayMapper.getDisplayInfoForStateLocked(state, displayId,
- displayGroupId));
+ return possibleInfo;
+ }
+ final int[] supportedStates =
+ mDeviceStateManager.getSupportedStateIdentifiers();
+ DisplayInfo displayInfo;
+ for (int state : supportedStates) {
+ displayInfo = mLogicalDisplayMapper.getDisplayInfoForStateLocked(state,
+ displayId);
+ if (displayInfo != null) {
+ possibleInfo.add(displayInfo);
}
}
return possibleInfo;
diff --git a/services/core/java/com/android/server/display/DisplayModeDirector.java b/services/core/java/com/android/server/display/DisplayModeDirector.java
index ecae833..6331a5d 100644
--- a/services/core/java/com/android/server/display/DisplayModeDirector.java
+++ b/services/core/java/com/android/server/display/DisplayModeDirector.java
@@ -1163,7 +1163,7 @@
mDefaultRefreshRate =
(displayDeviceConfig == null) ? (float) mContext.getResources().getInteger(
R.integer.config_defaultRefreshRate)
- : (float) displayDeviceConfig.getDefaultRefreshRate();
+ : (float) displayDeviceConfig.getDefaultLowRefreshRate();
}
public void observe() {
@@ -1250,7 +1250,7 @@
defaultPeakRefreshRate =
(displayDeviceConfig == null) ? (float) mContext.getResources().getInteger(
R.integer.config_defaultPeakRefreshRate)
- : (float) displayDeviceConfig.getDefaultPeakRefreshRate();
+ : (float) displayDeviceConfig.getDefaultHighRefreshRate();
}
mDefaultPeakRefreshRate = defaultPeakRefreshRate;
}
diff --git a/services/core/java/com/android/server/display/DisplayPowerController.java b/services/core/java/com/android/server/display/DisplayPowerController.java
index bb27651..2fc6fd2 100644
--- a/services/core/java/com/android/server/display/DisplayPowerController.java
+++ b/services/core/java/com/android/server/display/DisplayPowerController.java
@@ -459,6 +459,8 @@
// PowerManager.BRIGHTNESS_INVALID_FLOAT when there's no temporary adjustment set.
private float mTemporaryAutoBrightnessAdjustment;
+ private boolean mUseAutoBrightness;
+
private boolean mIsRbcActive;
// Whether there's a callback to tell listeners the display has changed scheduled to run. When
@@ -692,6 +694,7 @@
public void onSwitchUser(@UserIdInt int newUserId) {
handleSettingsChange(true /* userSwitch */);
+ handleBrightnessModeChange();
if (mBrightnessTracker != null) {
mBrightnessTracker.onSwitchUser(newUserId);
}
@@ -941,6 +944,10 @@
mContext.getContentResolver().registerContentObserver(
Settings.System.getUriFor(Settings.System.SCREEN_AUTO_BRIGHTNESS_ADJ),
false /*notifyForDescendants*/, mSettingsObserver, UserHandle.USER_ALL);
+ mContext.getContentResolver().registerContentObserver(
+ Settings.System.getUriFor(Settings.System.SCREEN_BRIGHTNESS_MODE),
+ false /*notifyForDescendants*/, mSettingsObserver, UserHandle.USER_ALL);
+ handleBrightnessModeChange();
}
private void setUpAutoBrightness(Resources resources, Handler handler) {
@@ -1344,11 +1351,11 @@
final boolean autoBrightnessEnabledInDoze =
mAllowAutoBrightnessWhileDozingConfig && Display.isDozeState(state);
- final boolean autoBrightnessEnabled = mPowerRequest.useAutoBrightness
+ final boolean autoBrightnessEnabled = mUseAutoBrightness
&& (state == Display.STATE_ON || autoBrightnessEnabledInDoze)
&& Float.isNaN(brightnessState)
&& mAutomaticBrightnessController != null;
- final boolean autoBrightnessDisabledDueToDisplayOff = mPowerRequest.useAutoBrightness
+ final boolean autoBrightnessDisabledDueToDisplayOff = mUseAutoBrightness
&& !(state == Display.STATE_ON || autoBrightnessEnabledInDoze);
final int autoBrightnessState = autoBrightnessEnabled
? AutomaticBrightnessController.AUTO_BRIGHTNESS_ENABLED
@@ -1700,7 +1707,7 @@
|| brightnessAdjustmentFlags != 0) {
float lastBrightness = mLastBrightnessEvent.brightness;
mTempBrightnessEvent.initialBrightness = lastBrightness;
- mTempBrightnessEvent.automaticBrightnessEnabled = mPowerRequest.useAutoBrightness;
+ mTempBrightnessEvent.automaticBrightnessEnabled = mUseAutoBrightness;
mLastBrightnessEvent.copyFrom(mTempBrightnessEvent);
BrightnessEvent newEvent = new BrightnessEvent(mTempBrightnessEvent);
@@ -2372,6 +2379,18 @@
sendUpdatePowerState();
}
+ private void handleBrightnessModeChange() {
+ final int screenBrightnessModeSetting = Settings.System.getIntForUser(
+ mContext.getContentResolver(),
+ Settings.System.SCREEN_BRIGHTNESS_MODE,
+ Settings.System.SCREEN_BRIGHTNESS_MODE_MANUAL, UserHandle.USER_CURRENT);
+ mHandler.post(() -> {
+ mUseAutoBrightness = screenBrightnessModeSetting
+ == Settings.System.SCREEN_BRIGHTNESS_MODE_AUTOMATIC;
+ updatePowerState();
+ });
+ }
+
private float getAutoBrightnessAdjustmentSetting() {
final float adj = Settings.System.getFloatForUser(mContext.getContentResolver(),
Settings.System.SCREEN_AUTO_BRIGHTNESS_ADJ, 0.0f, UserHandle.USER_CURRENT);
@@ -2461,7 +2480,7 @@
private void notifyBrightnessTrackerChanged(float brightness, boolean userInitiated,
boolean hadUserDataPoint) {
final float brightnessInNits = convertToNits(brightness);
- if (mPowerRequest.useAutoBrightness && brightnessInNits >= 0.0f
+ if (mUseAutoBrightness && brightnessInNits >= 0.0f
&& mAutomaticBrightnessController != null && mBrightnessTracker != null) {
// We only want to track changes on devices that can actually map the display backlight
// values into a physical brightness unit since the value provided by the API is in
@@ -3099,7 +3118,11 @@
@Override
public void onChange(boolean selfChange, Uri uri) {
- handleSettingsChange(false /* userSwitch */);
+ if (uri.equals(Settings.System.getUriFor(Settings.System.SCREEN_BRIGHTNESS_MODE))) {
+ handleBrightnessModeChange();
+ } else {
+ handleSettingsChange(false /* userSwitch */);
+ }
}
}
diff --git a/services/core/java/com/android/server/display/LogicalDisplayMapper.java b/services/core/java/com/android/server/display/LogicalDisplayMapper.java
index 778e418..bf576b8 100644
--- a/services/core/java/com/android/server/display/LogicalDisplayMapper.java
+++ b/services/core/java/com/android/server/display/LogicalDisplayMapper.java
@@ -19,6 +19,7 @@
import static android.view.Display.DEFAULT_DISPLAY;
import android.annotation.NonNull;
+import android.annotation.Nullable;
import android.content.Context;
import android.hardware.devicestate.DeviceStateManager;
import android.os.Handler;
@@ -28,7 +29,6 @@
import android.os.SystemClock;
import android.os.SystemProperties;
import android.text.TextUtils;
-import android.util.ArraySet;
import android.util.IndentingPrintWriter;
import android.util.Slog;
import android.util.SparseArray;
@@ -43,7 +43,6 @@
import java.io.PrintWriter;
import java.util.Arrays;
-import java.util.Set;
import java.util.function.Consumer;
/**
@@ -304,58 +303,44 @@
}
/**
- * Returns the set of {@link DisplayInfo} for this device state, only fetching the info that is
- * part of the same display group as the provided display id. The DisplayInfo represent the
- * logical display layouts possible for the given device state.
+ * Returns the {@link DisplayInfo} for this device state, indicated by the given display id. The
+ * DisplayInfo represents the attributes of the indicated display in the layout associated with
+ * this state. This is used to get display information for various displays in various states;
+ * e.g. to help apps preload resources for the possible display states.
*
* @param deviceState the state to query possible layouts for
- * @param displayId the display id to apply to all displays within the group
- * @param groupId the display group to filter display info for. Must be the same group as
- * the display with the provided display id.
+ * @param displayId the display id to retrieve
+ * @return {@code null} if no corresponding {@link DisplayInfo} could be found, or the
+ * {@link DisplayInfo} with a matching display id.
*/
- public Set<DisplayInfo> getDisplayInfoForStateLocked(int deviceState, int displayId,
- int groupId) {
- Set<DisplayInfo> displayInfos = new ArraySet<>();
+ @Nullable
+ public DisplayInfo getDisplayInfoForStateLocked(int deviceState, int displayId) {
+ // Retrieve the layout for this particular state.
final Layout layout = mDeviceStateToLayoutMap.get(deviceState);
- final int layoutSize = layout.size();
- for (int i = 0; i < layoutSize; i++) {
- Layout.Display displayLayout = layout.getAt(i);
- if (displayLayout == null) {
- continue;
- }
-
- // If the underlying display-device we want to use for this display
- // doesn't exist, then skip it. This can happen at startup as display-devices
- // trickle in one at a time. When the new display finally shows up, the layout is
- // recalculated so that the display is properly added to the current layout.
- final DisplayAddress address = displayLayout.getAddress();
- final DisplayDevice device = mDisplayDeviceRepo.getByAddressLocked(address);
- if (device == null) {
- Slog.w(TAG, "The display device (" + address + "), is not available"
- + " for the display state " + deviceState);
- continue;
- }
-
- // Find or create the LogicalDisplay to map the DisplayDevice to.
- final int logicalDisplayId = displayLayout.getLogicalDisplayId();
- final LogicalDisplay logicalDisplay = getDisplayLocked(logicalDisplayId);
- if (logicalDisplay == null) {
- Slog.w(TAG, "The logical display (" + address + "), is not available"
- + " for the display state " + deviceState);
- continue;
- }
- final DisplayInfo temp = logicalDisplay.getDisplayInfoLocked();
- DisplayInfo displayInfo = new DisplayInfo(temp);
- if (displayInfo.displayGroupId != groupId) {
- // Ignore any displays not in the provided group.
- continue;
- }
- // A display in the same group can be swapped out at any point, so set the display id
- // for all results to the provided display id.
- displayInfo.displayId = displayId;
- displayInfos.add(displayInfo);
+ if (layout == null) {
+ return null;
}
- return displayInfos;
+ // Retrieve the details of the given display within this layout.
+ Layout.Display display = layout.getById(displayId);
+ if (display == null) {
+ return null;
+ }
+ // Retrieve the display info for the display that matches the display id.
+ final DisplayDevice device = mDisplayDeviceRepo.getByAddressLocked(display.getAddress());
+ if (device == null) {
+ Slog.w(TAG, "The display device (" + display.getAddress() + "), is not available"
+ + " for the display state " + mDeviceState);
+ return null;
+ }
+ LogicalDisplay logicalDisplay = getDisplayLocked(device, /* includeDisabled= */ true);
+ if (logicalDisplay == null) {
+ Slog.w(TAG, "The logical display associated with address (" + display.getAddress()
+ + "), is not available for the display state " + mDeviceState);
+ return null;
+ }
+ DisplayInfo displayInfo = new DisplayInfo(logicalDisplay.getDisplayInfoLocked());
+ displayInfo.displayId = displayId;
+ return displayInfo;
}
public void dumpLocked(PrintWriter pw) {
@@ -426,7 +411,8 @@
// Send the device to sleep when required.
mHandler.post(() -> {
mPowerManager.goToSleep(SystemClock.uptimeMillis(),
- PowerManager.GO_TO_SLEEP_REASON_DEVICE_FOLD, 0);
+ PowerManager.GO_TO_SLEEP_REASON_DEVICE_FOLD,
+ PowerManager.GO_TO_SLEEP_FLAG_SOFT_SLEEP);
});
}
}
diff --git a/services/core/java/com/android/server/net/NetworkPolicyManagerService.java b/services/core/java/com/android/server/net/NetworkPolicyManagerService.java
index 1ea949e..2f818fa 100644
--- a/services/core/java/com/android/server/net/NetworkPolicyManagerService.java
+++ b/services/core/java/com/android/server/net/NetworkPolicyManagerService.java
@@ -123,6 +123,7 @@
import static android.telephony.CarrierConfigManager.KEY_DATA_WARNING_NOTIFICATION_BOOL;
import static android.telephony.SubscriptionManager.INVALID_SUBSCRIPTION_ID;
+import static com.android.internal.annotations.VisibleForTesting.Visibility.PRIVATE;
import static com.android.internal.util.ArrayUtils.appendInt;
import static com.android.internal.util.XmlUtils.readBooleanAttribute;
import static com.android.internal.util.XmlUtils.readIntAttribute;
@@ -3148,7 +3149,8 @@
* active merge set [A,B], we'd return a new template that primarily matches
* A, but also matches B.
*/
- private static NetworkTemplate normalizeTemplate(@NonNull NetworkTemplate template,
+ @VisibleForTesting(visibility = PRIVATE)
+ static NetworkTemplate normalizeTemplate(@NonNull NetworkTemplate template,
@NonNull List<String[]> mergedList) {
// Now there are several types of network which uses Subscriber Id to store network
// information. For instance:
@@ -3158,6 +3160,12 @@
if (template.getSubscriberIds().isEmpty()) return template;
for (final String[] merged : mergedList) {
+ // In some rare cases (e.g. b/243015487), merged subscriberId list might contain
+ // duplicated items. Deduplication for better error handling.
+ final ArraySet mergedSet = new ArraySet(merged);
+ if (mergedSet.size() != merged.length) {
+ Log.wtf(TAG, "Duplicated merged list detected: " + Arrays.toString(merged));
+ }
// TODO: Handle incompatible subscriberIds if that happens in practice.
for (final String subscriberId : template.getSubscriberIds()) {
if (com.android.net.module.util.CollectionUtils.contains(merged, subscriberId)) {
@@ -3165,7 +3173,7 @@
// a template that matches all merged subscribers.
return new NetworkTemplate.Builder(template.getMatchRule())
.setWifiNetworkKeys(template.getWifiNetworkKeys())
- .setSubscriberIds(Set.of(merged))
+ .setSubscriberIds(mergedSet)
.setMeteredness(template.getMeteredness())
.build();
}
diff --git a/services/core/java/com/android/server/policy/PhoneWindowManager.java b/services/core/java/com/android/server/policy/PhoneWindowManager.java
index 85a2a5d..dbe049a 100644
--- a/services/core/java/com/android/server/policy/PhoneWindowManager.java
+++ b/services/core/java/com/android/server/policy/PhoneWindowManager.java
@@ -1049,12 +1049,6 @@
return;
}
- // Make sure the device locks. Unfortunately, this has the side-effect of briefly revealing
- // the lock screen before the dream appears. Note that this locking behavior needs to
- // happen regardless of whether we end up dreaming (below) or not.
- // TODO(b/261662912): Find a better way to lock the device that doesn't result in jank.
- lockNow(null);
-
// Don't dream if the user isn't user zero.
// TODO(b/261907079): Move this check to DreamManagerService#canStartDreamingInternal().
if (ActivityManager.getCurrentUser() != UserHandle.USER_SYSTEM) {
@@ -1068,6 +1062,12 @@
return;
}
+ // Make sure the device locks. Unfortunately, this has the side-effect of briefly revealing
+ // the lock screen before the dream appears. Note that locking is a side-effect of the no
+ // dream action that is executed if we early return above.
+ // TODO(b/261662912): Find a better way to lock the device that doesn't result in jank.
+ lockNow(null);
+
dreamManagerInternal.requestDream();
}
diff --git a/services/core/java/com/android/server/power/PowerGroup.java b/services/core/java/com/android/server/power/PowerGroup.java
index 431cf38..3a3f6b2 100644
--- a/services/core/java/com/android/server/power/PowerGroup.java
+++ b/services/core/java/com/android/server/power/PowerGroup.java
@@ -28,6 +28,8 @@
import static com.android.server.power.PowerManagerService.WAKE_LOCK_DOZE;
import static com.android.server.power.PowerManagerService.WAKE_LOCK_DRAW;
import static com.android.server.power.PowerManagerService.WAKE_LOCK_SCREEN_BRIGHT;
+import static com.android.server.power.PowerManagerService.WAKE_LOCK_SCREEN_DIM;
+import static com.android.server.power.PowerManagerService.WAKE_LOCK_STAY_AWAKE;
import android.hardware.display.DisplayManagerInternal;
import android.hardware.display.DisplayManagerInternal.DisplayPowerRequest;
@@ -341,6 +343,22 @@
return mWakeLockSummary;
}
+ /**
+ * Query whether a wake lock is at least partially responsible for keeping the device awake.
+ *
+ * This does not necessarily mean the wake lock is the sole reason the device is awake; there
+ * could also be user activity keeping the device awake, for example. It just means a wake lock
+ * is being held that would keep the device awake even if nothing else was.
+ *
+ * @return whether the PowerGroup is being kept awake at least in part because a wake lock is
+ * being held.
+ */
+ public boolean hasWakeLockKeepingScreenOnLocked() {
+ final int screenOnWakeLockMask =
+ WAKE_LOCK_SCREEN_BRIGHT | WAKE_LOCK_SCREEN_DIM | WAKE_LOCK_STAY_AWAKE;
+ return (mWakeLockSummary & (screenOnWakeLockMask)) != 0;
+ }
+
public void setWakeLockSummaryLocked(int summary) {
mWakeLockSummary = summary;
}
@@ -419,16 +437,14 @@
return mDisplayPowerRequest.policy;
}
- boolean updateLocked(float screenBrightnessOverride, boolean autoBrightness,
- boolean useProximitySensor, boolean boostScreenBrightness, int dozeScreenState,
- float dozeScreenBrightness, boolean overrideDrawWakeLock,
- PowerSaveState powerSaverState, boolean quiescent, boolean dozeAfterScreenOff,
- boolean vrModeEnabled, boolean bootCompleted, boolean screenBrightnessBoostInProgress,
- boolean waitForNegativeProximity) {
+ boolean updateLocked(float screenBrightnessOverride, boolean useProximitySensor,
+ boolean boostScreenBrightness, int dozeScreenState, float dozeScreenBrightness,
+ boolean overrideDrawWakeLock, PowerSaveState powerSaverState, boolean quiescent,
+ boolean dozeAfterScreenOff, boolean vrModeEnabled, boolean bootCompleted,
+ boolean screenBrightnessBoostInProgress, boolean waitForNegativeProximity) {
mDisplayPowerRequest.policy = getDesiredScreenPolicyLocked(quiescent, dozeAfterScreenOff,
vrModeEnabled, bootCompleted, screenBrightnessBoostInProgress);
mDisplayPowerRequest.screenBrightnessOverride = screenBrightnessOverride;
- mDisplayPowerRequest.useAutoBrightness = autoBrightness;
mDisplayPowerRequest.useProximitySensor = useProximitySensor;
mDisplayPowerRequest.boostScreenBrightness = boostScreenBrightness;
diff --git a/services/core/java/com/android/server/power/PowerManagerService.java b/services/core/java/com/android/server/power/PowerManagerService.java
index b1c986e..651bb93 100644
--- a/services/core/java/com/android/server/power/PowerManagerService.java
+++ b/services/core/java/com/android/server/power/PowerManagerService.java
@@ -584,10 +584,6 @@
private boolean mIsFaceDown = false;
private long mLastFlipTime = 0L;
- // The screen brightness mode.
- // One of the Settings.System.SCREEN_BRIGHTNESS_MODE_* constants.
- private int mScreenBrightnessModeSetting;
-
// The screen brightness setting override from the window manager
// to allow the current foreground activity to override the brightness.
private float mScreenBrightnessOverrideFromWindowManager =
@@ -1481,10 +1477,6 @@
mSystemProperties.set(SYSTEM_PROPERTY_RETAIL_DEMO_ENABLED, retailDemoValue);
}
- mScreenBrightnessModeSetting = Settings.System.getIntForUser(resolver,
- Settings.System.SCREEN_BRIGHTNESS_MODE,
- Settings.System.SCREEN_BRIGHTNESS_MODE_MANUAL, UserHandle.USER_CURRENT);
-
mDirty |= DIRTY_SETTINGS;
}
@@ -3466,23 +3458,18 @@
final PowerGroup powerGroup = mPowerGroups.valueAt(idx);
final int groupId = powerGroup.getGroupId();
- // Determine appropriate screen brightness and auto-brightness adjustments.
- final boolean autoBrightness;
+ // Determine appropriate screen brightness.
final float screenBrightnessOverride;
if (!mBootCompleted) {
// Keep the brightness steady during boot. This requires the
// bootloader brightness and the default brightness to be identical.
- autoBrightness = false;
screenBrightnessOverride = mScreenBrightnessDefault;
} else if (isValidBrightness(mScreenBrightnessOverrideFromWindowManager)) {
- autoBrightness = false;
screenBrightnessOverride = mScreenBrightnessOverrideFromWindowManager;
} else {
- autoBrightness = (mScreenBrightnessModeSetting
- == Settings.System.SCREEN_BRIGHTNESS_MODE_AUTOMATIC);
screenBrightnessOverride = PowerManager.BRIGHTNESS_INVALID_FLOAT;
}
- boolean ready = powerGroup.updateLocked(screenBrightnessOverride, autoBrightness,
+ boolean ready = powerGroup.updateLocked(screenBrightnessOverride,
shouldUseProximitySensorLocked(), shouldBoostScreenBrightness(),
mDozeScreenStateOverrideFromDreamManager,
mDozeScreenBrightnessOverrideFromDreamManagerFloat,
@@ -3503,7 +3490,6 @@
powerGroup.getUserActivitySummaryLocked())
+ ", mBootCompleted=" + mBootCompleted
+ ", screenBrightnessOverride=" + screenBrightnessOverride
- + ", useAutoBrightness=" + autoBrightness
+ ", mScreenBrightnessBoostInProgress="
+ mScreenBrightnessBoostInProgress
+ ", mIsVrModeEnabled= " + mIsVrModeEnabled
@@ -4525,7 +4511,6 @@
+ mMaximumScreenOffTimeoutFromDeviceAdmin + " (enforced="
+ isMaximumScreenOffTimeoutFromDeviceAdminEnforcedLocked() + ")");
pw.println(" mStayOnWhilePluggedInSetting=" + mStayOnWhilePluggedInSetting);
- pw.println(" mScreenBrightnessModeSetting=" + mScreenBrightnessModeSetting);
pw.println(" mScreenBrightnessOverrideFromWindowManager="
+ mScreenBrightnessOverrideFromWindowManager);
pw.println(" mUserActivityTimeoutOverrideFromWindowManager="
@@ -4904,9 +4889,6 @@
proto.end(stayOnWhilePluggedInToken);
proto.write(
- PowerServiceSettingsAndConfigurationDumpProto.SCREEN_BRIGHTNESS_MODE_SETTING,
- mScreenBrightnessModeSetting);
- proto.write(
PowerServiceSettingsAndConfigurationDumpProto
.SCREEN_BRIGHTNESS_OVERRIDE_FROM_WINDOW_MANAGER,
mScreenBrightnessOverrideFromWindowManager);
@@ -5778,6 +5760,11 @@
try {
synchronized (mLock) {
PowerGroup defaultPowerGroup = mPowerGroups.get(Display.DEFAULT_DISPLAY_GROUP);
+ if ((flags & PowerManager.GO_TO_SLEEP_FLAG_SOFT_SLEEP) != 0) {
+ if (defaultPowerGroup.hasWakeLockKeepingScreenOnLocked()) {
+ return;
+ }
+ }
if ((flags & PowerManager.GO_TO_SLEEP_FLAG_NO_DOZE) != 0) {
sleepPowerGroupLocked(defaultPowerGroup, eventTime, reason, uid);
} else {
diff --git a/services/core/java/com/android/server/trust/TrustManagerService.java b/services/core/java/com/android/server/trust/TrustManagerService.java
index 2888b9a..afe8d3e 100644
--- a/services/core/java/com/android/server/trust/TrustManagerService.java
+++ b/services/core/java/com/android/server/trust/TrustManagerService.java
@@ -563,7 +563,12 @@
changed = mUserIsTrusted.get(userId) != trusted;
mUserIsTrusted.put(userId, trusted);
}
- dispatchOnTrustChanged(trusted, userId, flags, getTrustGrantedMessages(userId));
+ dispatchOnTrustChanged(
+ trusted,
+ false /* newlyUnlocked */,
+ userId,
+ flags,
+ getTrustGrantedMessages(userId));
if (changed) {
refreshDeviceLockedForUser(userId);
if (!trusted) {
@@ -628,7 +633,9 @@
if (DEBUG) Slog.d(TAG, "pendingTrustState: " + pendingTrustState);
boolean isNowTrusted = pendingTrustState == TrustState.TRUSTED;
- dispatchOnTrustChanged(isNowTrusted, userId, flags, getTrustGrantedMessages(userId));
+ boolean newlyUnlocked = !alreadyUnlocked && isNowTrusted;
+ dispatchOnTrustChanged(
+ isNowTrusted, newlyUnlocked, userId, flags, getTrustGrantedMessages(userId));
if (isNowTrusted != wasTrusted) {
refreshDeviceLockedForUser(userId);
if (!isNowTrusted) {
@@ -643,8 +650,7 @@
}
}
- boolean wasLocked = !alreadyUnlocked;
- boolean shouldSendCallback = wasLocked && pendingTrustState == TrustState.TRUSTED;
+ boolean shouldSendCallback = newlyUnlocked;
if (shouldSendCallback) {
if (resultCallback != null) {
if (DEBUG) Slog.d(TAG, "calling back with UNLOCKED_BY_GRANT");
@@ -690,7 +696,7 @@
*/
public void lockUser(int userId) {
mLockPatternUtils.requireStrongAuth(
- StrongAuthTracker.SOME_AUTH_REQUIRED_AFTER_USER_REQUEST, userId);
+ StrongAuthTracker.SOME_AUTH_REQUIRED_AFTER_TRUSTAGENT_EXPIRED, userId);
try {
WindowManagerGlobal.getWindowManagerService().lockNow(null);
} catch (RemoteException e) {
@@ -1387,16 +1393,17 @@
}
}
- private void dispatchOnTrustChanged(boolean enabled, int userId, int flags,
- @NonNull List<String> trustGrantedMessages) {
+ private void dispatchOnTrustChanged(boolean enabled, boolean newlyUnlocked, int userId,
+ int flags, @NonNull List<String> trustGrantedMessages) {
if (DEBUG) {
- Log.i(TAG, "onTrustChanged(" + enabled + ", " + userId + ", 0x"
+ Log.i(TAG, "onTrustChanged(" + enabled + ", " + newlyUnlocked + ", " + userId + ", 0x"
+ Integer.toHexString(flags) + ")");
}
if (!enabled) flags = 0;
for (int i = 0; i < mTrustListeners.size(); i++) {
try {
- mTrustListeners.get(i).onTrustChanged(enabled, userId, flags, trustGrantedMessages);
+ mTrustListeners.get(i).onTrustChanged(
+ enabled, newlyUnlocked, userId, flags, trustGrantedMessages);
} catch (DeadObjectException e) {
Slog.d(TAG, "Removing dead TrustListener.");
mTrustListeners.remove(i);
@@ -2087,7 +2094,7 @@
if (mStrongAuthTracker.isTrustAllowedForUser(mUserId)) {
if (DEBUG) Slog.d(TAG, "Revoking all trust because of trust timeout");
mLockPatternUtils.requireStrongAuth(
- mStrongAuthTracker.SOME_AUTH_REQUIRED_AFTER_USER_REQUEST, mUserId);
+ mStrongAuthTracker.SOME_AUTH_REQUIRED_AFTER_TRUSTAGENT_EXPIRED, mUserId);
}
maybeLockScreen(mUserId);
}
diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java
index 292ed95..4cc15c9 100644
--- a/services/core/java/com/android/server/wm/Task.java
+++ b/services/core/java/com/android/server/wm/Task.java
@@ -2772,6 +2772,13 @@
@Override
void getAnimationFrames(Rect outFrame, Rect outInsets, Rect outStableInsets,
Rect outSurfaceInsets) {
+ // If this task has its adjacent task, it means they should animate together. Use display
+ // bounds for them could move same as full screen task.
+ if (getAdjacentTaskFragment() != null && getAdjacentTaskFragment().asTask() != null) {
+ super.getAnimationFrames(outFrame, outInsets, outStableInsets, outSurfaceInsets);
+ return;
+ }
+
final WindowState windowState = getTopVisibleAppMainWindow();
if (windowState != null) {
windowState.getAnimationFrames(outFrame, outInsets, outStableInsets, outSurfaceInsets);
@@ -4920,6 +4927,11 @@
}
if (child.getVisibility(null /* starting */)
!= TASK_FRAGMENT_VISIBILITY_VISIBLE) {
+ if (child.topRunningActivity() == null) {
+ // Skip the task if no running activity and continue resuming next task.
+ continue;
+ }
+ // Otherwise, assuming everything behind this task should also be invisible.
break;
}
diff --git a/services/core/java/com/android/server/wm/TaskFragment.java b/services/core/java/com/android/server/wm/TaskFragment.java
index ae4f894..f6db3f7 100644
--- a/services/core/java/com/android/server/wm/TaskFragment.java
+++ b/services/core/java/com/android/server/wm/TaskFragment.java
@@ -227,11 +227,16 @@
private TaskFragment mCompanionTaskFragment;
/**
- * Prevents duplicate calls to onTaskAppeared.
+ * Prevents duplicate calls to onTaskFragmentAppeared.
*/
boolean mTaskFragmentAppearedSent;
/**
+ * Prevents unnecessary callbacks after onTaskFragmentVanished.
+ */
+ boolean mTaskFragmentVanishedSent;
+
+ /**
* The last running activity of the TaskFragment was finished due to clear task while launching
* an activity in the Task.
*/
@@ -2223,7 +2228,8 @@
// task, because they should not be affected by insets.
inOutConfig.smallestScreenWidthDp = (int) (0.5f
+ Math.min(mTmpFullBounds.width(), mTmpFullBounds.height()) / density);
- } else if (isEmbedded()) {
+ } else if (windowingMode == WINDOWING_MODE_MULTI_WINDOW
+ && isEmbeddedWithBoundsOverride()) {
// For embedded TFs, the smallest width should be updated. Otherwise, inherit
// from the parent task would result in applications loaded wrong resource.
inOutConfig.smallestScreenWidthDp =
@@ -2552,7 +2558,7 @@
mRemoteToken.toWindowContainerToken(),
getConfiguration(),
getNonFinishingActivityCount(),
- isVisibleRequested(),
+ shouldBeVisible(null /* starting */),
childActivities,
positionInParent,
mClearedTaskForReuse,
@@ -2848,6 +2854,8 @@
if (parentTf != null) {
parentTf.onActivityVisibleRequestedChanged();
}
+ // Send the info changed to update the TaskFragment visibility.
+ sendTaskFragmentInfoChanged();
}
@Nullable
diff --git a/services/core/java/com/android/server/wm/TaskFragmentOrganizerController.java b/services/core/java/com/android/server/wm/TaskFragmentOrganizerController.java
index 6e4df79..90a0dff 100644
--- a/services/core/java/com/android/server/wm/TaskFragmentOrganizerController.java
+++ b/services/core/java/com/android/server/wm/TaskFragmentOrganizerController.java
@@ -553,6 +553,9 @@
void onTaskFragmentAppeared(@NonNull ITaskFragmentOrganizer organizer,
@NonNull TaskFragment taskFragment) {
+ if (taskFragment.mTaskFragmentVanishedSent) {
+ return;
+ }
if (taskFragment.getTask() == null) {
Slog.w(TAG, "onTaskFragmentAppeared failed because it is not attached tf="
+ taskFragment);
@@ -574,6 +577,9 @@
void onTaskFragmentInfoChanged(@NonNull ITaskFragmentOrganizer organizer,
@NonNull TaskFragment taskFragment) {
+ if (taskFragment.mTaskFragmentVanishedSent) {
+ return;
+ }
validateAndGetState(organizer);
if (!taskFragment.mTaskFragmentAppearedSent) {
// Skip if TaskFragment still not appeared.
@@ -586,10 +592,6 @@
.setTaskFragment(taskFragment)
.build();
} else {
- if (pendingEvent.mEventType == PendingTaskFragmentEvent.EVENT_VANISHED) {
- // Skipped the info changed event if vanished event is pending.
- return;
- }
// Remove and add for re-ordering.
removePendingEvent(pendingEvent);
// Reset the defer time when TaskFragment is changed, so that it can check again if
@@ -602,6 +604,10 @@
void onTaskFragmentVanished(@NonNull ITaskFragmentOrganizer organizer,
@NonNull TaskFragment taskFragment) {
+ if (taskFragment.mTaskFragmentVanishedSent) {
+ return;
+ }
+ taskFragment.mTaskFragmentVanishedSent = true;
final TaskFragmentOrganizerState state = validateAndGetState(organizer);
final List<PendingTaskFragmentEvent> pendingEvents = mPendingTaskFragmentEvents
.get(organizer.asBinder());
@@ -617,20 +623,18 @@
.setTaskFragment(taskFragment)
.build());
state.removeTaskFragment(taskFragment);
+ // Make sure the vanished event will be dispatched if there are no other changes.
+ mAtmService.mWindowManager.mWindowPlacerLocked.requestTraversal();
}
void onTaskFragmentError(@NonNull ITaskFragmentOrganizer organizer,
@Nullable IBinder errorCallbackToken, @Nullable TaskFragment taskFragment,
int opType, @NonNull Throwable exception) {
- validateAndGetState(organizer);
- Slog.w(TAG, "onTaskFragmentError ", exception);
- final PendingTaskFragmentEvent vanishedEvent = taskFragment != null
- ? getPendingTaskFragmentEvent(taskFragment, PendingTaskFragmentEvent.EVENT_VANISHED)
- : null;
- if (vanishedEvent != null) {
- // No need to notify if the TaskFragment has been removed.
+ if (taskFragment != null && taskFragment.mTaskFragmentVanishedSent) {
return;
}
+ validateAndGetState(organizer);
+ Slog.w(TAG, "onTaskFragmentError ", exception);
addPendingEvent(new PendingTaskFragmentEvent.Builder(
PendingTaskFragmentEvent.EVENT_ERROR, organizer)
.setErrorCallbackToken(errorCallbackToken)
diff --git a/services/core/java/com/android/server/wm/WindowContainer.java b/services/core/java/com/android/server/wm/WindowContainer.java
index 5c893de..2f81f93 100644
--- a/services/core/java/com/android/server/wm/WindowContainer.java
+++ b/services/core/java/com/android/server/wm/WindowContainer.java
@@ -18,6 +18,7 @@
import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
+import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW;
import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_BEHIND;
import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSET;
import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED;
@@ -3186,7 +3187,8 @@
if (isOrganized()
// TODO(b/161711458): Clean-up when moved to shell.
&& getWindowingMode() != WINDOWING_MODE_FULLSCREEN
- && getWindowingMode() != WINDOWING_MODE_FREEFORM) {
+ && getWindowingMode() != WINDOWING_MODE_FREEFORM
+ && getWindowingMode() != WINDOWING_MODE_MULTI_WINDOW) {
return null;
}
diff --git a/services/core/xsd/display-device-config/display-device-config.xsd b/services/core/xsd/display-device-config/display-device-config.xsd
index f53a1cf..7bc8931 100644
--- a/services/core/xsd/display-device-config/display-device-config.xsd
+++ b/services/core/xsd/display-device-config/display-device-config.xsd
@@ -44,9 +44,11 @@
</xs:element>
<xs:element type="highBrightnessMode" name="highBrightnessMode" minOccurs="0"
maxOccurs="1"/>
- <xs:element type="displayQuirks" name="quirks" minOccurs="0" maxOccurs="1" />
+ <xs:element type="displayQuirks" name="quirks" minOccurs="0" maxOccurs="1"/>
<xs:element type="autoBrightness" name="autoBrightness" minOccurs="0"
- maxOccurs="1" />
+ maxOccurs="1"/>
+ <xs:element type="refreshRateConfigs" name="refreshRate" minOccurs="0"
+ maxOccurs="1"/>
<xs:element type="nonNegativeDecimal" name="screenBrightnessRampFastDecrease">
<xs:annotation name="final"/>
</xs:element>
@@ -324,7 +326,7 @@
<xs:annotation name="final"/>
</xs:element>
</xs:sequence>
- </xs:complexType>
+ </xs:complexType>
<!-- Thresholds for brightness changes. -->
<xs:complexType name="thresholds">
@@ -452,4 +454,35 @@
</xs:element>
</xs:sequence>
</xs:complexType>
+
+ <xs:complexType name="refreshRateConfigs">
+ <xs:element name="lowerBlockingZoneConfigs" type="blockingZoneConfig"
+ minOccurs="0" maxOccurs="1">
+ <xs:annotation name="final"/>
+ </xs:element>
+ <xs:element name="higherBlockingZoneConfigs" type="blockingZoneConfig"
+ minOccurs="0" maxOccurs="1">
+ <xs:annotation name="final"/>
+ </xs:element>
+ </xs:complexType>
+
+ <xs:complexType name="blockingZoneConfig">
+ <xs:element name="defaultRefreshRate" type="xs:nonNegativeInteger"
+ minOccurs="1" maxOccurs="1">
+ <xs:annotation name="final"/>
+ </xs:element>
+ <xs:element name="blockingZoneThreshold" type="blockingZoneThreshold"
+ minOccurs="1" maxOccurs="1">
+ <xs:annotation name="final"/>
+ </xs:element>
+ </xs:complexType>
+
+ <xs:complexType name="blockingZoneThreshold">
+ <xs:sequence>
+ <xs:element name="displayBrightnessPoint" type="displayBrightnessPoint"
+ minOccurs="1" maxOccurs="unbounded">
+ <xs:annotation name="final"/>
+ </xs:element>
+ </xs:sequence>
+ </xs:complexType>
</xs:schema>
diff --git a/services/core/xsd/display-device-config/schema/current.txt b/services/core/xsd/display-device-config/schema/current.txt
index d89bd7c..6276eda 100644
--- a/services/core/xsd/display-device-config/schema/current.txt
+++ b/services/core/xsd/display-device-config/schema/current.txt
@@ -13,6 +13,19 @@
method public void setEnabled(boolean);
}
+ public class BlockingZoneConfig {
+ ctor public BlockingZoneConfig();
+ method public final com.android.server.display.config.BlockingZoneThreshold getBlockingZoneThreshold();
+ method public final java.math.BigInteger getDefaultRefreshRate();
+ method public final void setBlockingZoneThreshold(com.android.server.display.config.BlockingZoneThreshold);
+ method public final void setDefaultRefreshRate(java.math.BigInteger);
+ }
+
+ public class BlockingZoneThreshold {
+ ctor public BlockingZoneThreshold();
+ method public final java.util.List<com.android.server.display.config.DisplayBrightnessPoint> getDisplayBrightnessPoint();
+ }
+
public class BrightnessThresholds {
ctor public BrightnessThresholds();
method public final com.android.server.display.config.ThresholdPoints getBrightnessThresholdPoints();
@@ -76,6 +89,7 @@
method public final com.android.server.display.config.SensorDetails getLightSensor();
method public final com.android.server.display.config.SensorDetails getProxSensor();
method public com.android.server.display.config.DisplayQuirks getQuirks();
+ method public com.android.server.display.config.RefreshRateConfigs getRefreshRate();
method @NonNull public final java.math.BigDecimal getScreenBrightnessDefault();
method @NonNull public final com.android.server.display.config.NitsMap getScreenBrightnessMap();
method public final java.math.BigInteger getScreenBrightnessRampDecreaseMaxMillis();
@@ -97,6 +111,7 @@
method public final void setLightSensor(com.android.server.display.config.SensorDetails);
method public final void setProxSensor(com.android.server.display.config.SensorDetails);
method public void setQuirks(com.android.server.display.config.DisplayQuirks);
+ method public void setRefreshRate(com.android.server.display.config.RefreshRateConfigs);
method public final void setScreenBrightnessDefault(@NonNull java.math.BigDecimal);
method public final void setScreenBrightnessMap(@NonNull com.android.server.display.config.NitsMap);
method public final void setScreenBrightnessRampDecreaseMaxMillis(java.math.BigInteger);
@@ -160,6 +175,14 @@
method public final void setValue(@NonNull java.math.BigDecimal);
}
+ public class RefreshRateConfigs {
+ ctor public RefreshRateConfigs();
+ method public final com.android.server.display.config.BlockingZoneConfig getHigherBlockingZoneConfigs();
+ method public final com.android.server.display.config.BlockingZoneConfig getLowerBlockingZoneConfigs();
+ method public final void setHigherBlockingZoneConfigs(com.android.server.display.config.BlockingZoneConfig);
+ method public final void setLowerBlockingZoneConfigs(com.android.server.display.config.BlockingZoneConfig);
+ }
+
public class RefreshRateRange {
ctor public RefreshRateRange();
method public final java.math.BigInteger getMaximum();
diff --git a/services/tests/mockingservicestests/src/com/android/server/display/LocalDisplayAdapterTest.java b/services/tests/mockingservicestests/src/com/android/server/display/LocalDisplayAdapterTest.java
index ed369c0..82236bf 100644
--- a/services/tests/mockingservicestests/src/com/android/server/display/LocalDisplayAdapterTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/display/LocalDisplayAdapterTest.java
@@ -155,6 +155,18 @@
when(mMockedResources.getIntArray(
com.android.internal.R.array.config_autoBrightnessLevels))
.thenReturn(new int[]{});
+ when(mMockedResources.getIntArray(
+ com.android.internal.R.array.config_brightnessThresholdsOfPeakRefreshRate))
+ .thenReturn(new int[]{});
+ when(mMockedResources.getIntArray(
+ com.android.internal.R.array.config_ambientThresholdsOfPeakRefreshRate))
+ .thenReturn(new int[]{});
+ when(mMockedResources.getIntArray(
+ com.android.internal.R.array.config_highDisplayBrightnessThresholdsOfFixedRefreshRate))
+ .thenReturn(new int[]{});
+ when(mMockedResources.getIntArray(
+ com.android.internal.R.array.config_highAmbientBrightnessThresholdsOfFixedRefreshRate))
+ .thenReturn(new int[]{});
}
@After
diff --git a/services/tests/servicestests/src/com/android/server/display/DisplayDeviceConfigTest.java b/services/tests/servicestests/src/com/android/server/display/DisplayDeviceConfigTest.java
index 7f341ff..6b705aa 100644
--- a/services/tests/servicestests/src/com/android/server/display/DisplayDeviceConfigTest.java
+++ b/services/tests/servicestests/src/com/android/server/display/DisplayDeviceConfigTest.java
@@ -150,6 +150,16 @@
assertEquals("ProximitySensor123", mDisplayDeviceConfig.getProximitySensor().name);
assertEquals("prox_type_1", mDisplayDeviceConfig.getProximitySensor().type);
+ assertEquals(75, mDisplayDeviceConfig.getDefaultLowRefreshRate());
+ assertEquals(90, mDisplayDeviceConfig.getDefaultHighRefreshRate());
+ assertArrayEquals(new int[]{45, 55},
+ mDisplayDeviceConfig.getLowDisplayBrightnessThresholds());
+ assertArrayEquals(new int[]{50, 60},
+ mDisplayDeviceConfig.getLowAmbientBrightnessThresholds());
+ assertArrayEquals(new int[]{65, 75},
+ mDisplayDeviceConfig.getHighDisplayBrightnessThresholds());
+ assertArrayEquals(new int[]{70, 80},
+ mDisplayDeviceConfig.getHighAmbientBrightnessThresholds());
// Todo(brup): Add asserts for BrightnessThrottlingData, DensityMapping,
// HighBrightnessModeData AmbientLightSensor, RefreshRateLimitations and ProximitySensor.
@@ -212,9 +222,8 @@
mDisplayDeviceConfig.getAmbientDarkeningLevelsIdle(), ZERO_DELTA);
assertArrayEquals(new float[]{29, 30, 31},
mDisplayDeviceConfig.getAmbientDarkeningPercentagesIdle(), ZERO_DELTA);
-
- assertEquals(mDisplayDeviceConfig.getDefaultRefreshRate(), DEFAULT_REFRESH_RATE);
- assertEquals(mDisplayDeviceConfig.getDefaultPeakRefreshRate(), DEFAULT_PEAK_REFRESH_RATE);
+ assertEquals(mDisplayDeviceConfig.getDefaultLowRefreshRate(), DEFAULT_REFRESH_RATE);
+ assertEquals(mDisplayDeviceConfig.getDefaultHighRefreshRate(), DEFAULT_PEAK_REFRESH_RATE);
assertArrayEquals(mDisplayDeviceConfig.getLowDisplayBrightnessThresholds(),
LOW_BRIGHTNESS_THRESHOLD_OF_PEAK_REFRESH_RATE);
assertArrayEquals(mDisplayDeviceConfig.getLowAmbientBrightnessThresholds(),
@@ -426,6 +435,38 @@
+ "<name>ProximitySensor123</name>\n"
+ "<type>prox_type_1</type>\n"
+ "</proxSensor>\n"
+ + "<refreshRate>\n"
+ + "<lowerBlockingZoneConfigs>\n"
+ + "<defaultRefreshRate>75</defaultRefreshRate>\n"
+ + "<blockingZoneThreshold>\n"
+ + "<displayBrightnessPoint>\n"
+ + "<lux>50</lux>\n"
+ // This number will be rounded to integer when read by the system
+ + "<nits>45.3</nits>\n"
+ + "</displayBrightnessPoint>\n"
+ + "<displayBrightnessPoint>\n"
+ + "<lux>60</lux>\n"
+ // This number will be rounded to integer when read by the system
+ + "<nits>55.2</nits>\n"
+ + "</displayBrightnessPoint>\n"
+ + "</blockingZoneThreshold>\n"
+ + "</lowerBlockingZoneConfigs>\n"
+ + "<higherBlockingZoneConfigs>\n"
+ + "<defaultRefreshRate>90</defaultRefreshRate>\n"
+ + "<blockingZoneThreshold>\n"
+ + "<displayBrightnessPoint>\n"
+ + "<lux>70</lux>\n"
+ // This number will be rounded to integer when read by the system
+ + "<nits>65.6</nits>\n"
+ + "</displayBrightnessPoint>\n"
+ + "<displayBrightnessPoint>\n"
+ + "<lux>80</lux>\n"
+ // This number will be rounded to integer when read by the system
+ + "<nits>75</nits>\n"
+ + "</displayBrightnessPoint>\n"
+ + "</blockingZoneThreshold>\n"
+ + "</higherBlockingZoneConfigs>\n"
+ + "</refreshRate>\n"
+ "</displayConfiguration>\n";
}
diff --git a/services/tests/servicestests/src/com/android/server/display/DisplayModeDirectorTest.java b/services/tests/servicestests/src/com/android/server/display/DisplayModeDirectorTest.java
index fb0cdfa..cfea63b 100644
--- a/services/tests/servicestests/src/com/android/server/display/DisplayModeDirectorTest.java
+++ b/services/tests/servicestests/src/com/android/server/display/DisplayModeDirectorTest.java
@@ -1896,8 +1896,8 @@
// Notify that the default display is updated, such that DisplayDeviceConfig has new values
DisplayDeviceConfig displayDeviceConfig = mock(DisplayDeviceConfig.class);
- when(displayDeviceConfig.getDefaultRefreshRate()).thenReturn(50);
- when(displayDeviceConfig.getDefaultPeakRefreshRate()).thenReturn(55);
+ when(displayDeviceConfig.getDefaultLowRefreshRate()).thenReturn(50);
+ when(displayDeviceConfig.getDefaultHighRefreshRate()).thenReturn(55);
when(displayDeviceConfig.getLowDisplayBrightnessThresholds()).thenReturn(new int[]{25});
when(displayDeviceConfig.getLowAmbientBrightnessThresholds()).thenReturn(new int[]{30});
when(displayDeviceConfig.getHighDisplayBrightnessThresholds()).thenReturn(new int[]{210});
diff --git a/services/tests/servicestests/src/com/android/server/display/LogicalDisplayMapperTest.java b/services/tests/servicestests/src/com/android/server/display/LogicalDisplayMapperTest.java
index d515fae..638637d 100644
--- a/services/tests/servicestests/src/com/android/server/display/LogicalDisplayMapperTest.java
+++ b/services/tests/servicestests/src/com/android/server/display/LogicalDisplayMapperTest.java
@@ -18,10 +18,14 @@
import static android.view.Display.DEFAULT_DISPLAY;
import static android.view.Display.DEFAULT_DISPLAY_GROUP;
+import static android.view.Display.TYPE_INTERNAL;
+import static android.view.Display.TYPE_VIRTUAL;
+import static com.android.server.display.DeviceStateToLayoutMap.STATE_DEFAULT;
import static com.android.server.display.DisplayAdapter.DISPLAY_DEVICE_EVENT_ADDED;
import static com.android.server.display.DisplayAdapter.DISPLAY_DEVICE_EVENT_CHANGED;
import static com.android.server.display.DisplayAdapter.DISPLAY_DEVICE_EVENT_REMOVED;
+import static com.android.server.display.DisplayDeviceInfo.FLAG_ALLOWED_TO_BE_DEFAULT_DISPLAY;
import static com.android.server.display.LogicalDisplayMapper.LOGICAL_DISPLAY_EVENT_ADDED;
import static com.android.server.display.LogicalDisplayMapper.LOGICAL_DISPLAY_EVENT_REMOVED;
@@ -69,7 +73,6 @@
import java.io.InputStream;
import java.io.OutputStream;
import java.util.Arrays;
-import java.util.Set;
@SmallTest
@Presubmit
@@ -151,8 +154,8 @@
@Test
public void testDisplayDeviceAddAndRemove_Internal() {
- DisplayDevice device = createDisplayDevice(Display.TYPE_INTERNAL, 600, 800,
- DisplayDeviceInfo.FLAG_ALLOWED_TO_BE_DEFAULT_DISPLAY);
+ DisplayDevice device = createDisplayDevice(TYPE_INTERNAL, 600, 800,
+ FLAG_ALLOWED_TO_BE_DEFAULT_DISPLAY);
// add
LogicalDisplay displayAdded = add(device);
@@ -173,7 +176,7 @@
testDisplayDeviceAddAndRemove_NonInternal(Display.TYPE_EXTERNAL);
testDisplayDeviceAddAndRemove_NonInternal(Display.TYPE_WIFI);
testDisplayDeviceAddAndRemove_NonInternal(Display.TYPE_OVERLAY);
- testDisplayDeviceAddAndRemove_NonInternal(Display.TYPE_VIRTUAL);
+ testDisplayDeviceAddAndRemove_NonInternal(TYPE_VIRTUAL);
testDisplayDeviceAddAndRemove_NonInternal(Display.TYPE_UNKNOWN);
// Call the internal test again, just to verify that adding non-internal displays
@@ -183,9 +186,9 @@
@Test
public void testDisplayDeviceAdd_TwoInternalOneDefault() {
- DisplayDevice device1 = createDisplayDevice(Display.TYPE_INTERNAL, 600, 800, 0);
- DisplayDevice device2 = createDisplayDevice(Display.TYPE_INTERNAL, 600, 800,
- DisplayDeviceInfo.FLAG_ALLOWED_TO_BE_DEFAULT_DISPLAY);
+ DisplayDevice device1 = createDisplayDevice(TYPE_INTERNAL, 600, 800, 0);
+ DisplayDevice device2 = createDisplayDevice(TYPE_INTERNAL, 600, 800,
+ FLAG_ALLOWED_TO_BE_DEFAULT_DISPLAY);
LogicalDisplay display1 = add(device1);
assertEquals(info(display1).address, info(device1).address);
@@ -198,10 +201,10 @@
@Test
public void testDisplayDeviceAdd_TwoInternalBothDefault() {
- DisplayDevice device1 = createDisplayDevice(Display.TYPE_INTERNAL, 600, 800,
- DisplayDeviceInfo.FLAG_ALLOWED_TO_BE_DEFAULT_DISPLAY);
- DisplayDevice device2 = createDisplayDevice(Display.TYPE_INTERNAL, 600, 800,
- DisplayDeviceInfo.FLAG_ALLOWED_TO_BE_DEFAULT_DISPLAY);
+ DisplayDevice device1 = createDisplayDevice(TYPE_INTERNAL, 600, 800,
+ FLAG_ALLOWED_TO_BE_DEFAULT_DISPLAY);
+ DisplayDevice device2 = createDisplayDevice(TYPE_INTERNAL, 600, 800,
+ FLAG_ALLOWED_TO_BE_DEFAULT_DISPLAY);
LogicalDisplay display1 = add(device1);
assertEquals(info(display1).address, info(device1).address);
@@ -216,7 +219,7 @@
@Test
public void testDisplayDeviceAddAndRemove_OneExternalDefault() {
DisplayDevice device = createDisplayDevice(Display.TYPE_EXTERNAL, 600, 800,
- DisplayDeviceInfo.FLAG_ALLOWED_TO_BE_DEFAULT_DISPLAY);
+ FLAG_ALLOWED_TO_BE_DEFAULT_DISPLAY);
// add
LogicalDisplay displayAdded = add(device);
@@ -234,10 +237,10 @@
@Test
public void testDisplayDeviceAddAndRemove_SwitchDefault() {
- DisplayDevice device1 = createDisplayDevice(Display.TYPE_INTERNAL, 600, 800,
- DisplayDeviceInfo.FLAG_ALLOWED_TO_BE_DEFAULT_DISPLAY);
- DisplayDevice device2 = createDisplayDevice(Display.TYPE_INTERNAL, 600, 800,
- DisplayDeviceInfo.FLAG_ALLOWED_TO_BE_DEFAULT_DISPLAY);
+ DisplayDevice device1 = createDisplayDevice(TYPE_INTERNAL, 600, 800,
+ FLAG_ALLOWED_TO_BE_DEFAULT_DISPLAY);
+ DisplayDevice device2 = createDisplayDevice(TYPE_INTERNAL, 600, 800,
+ FLAG_ALLOWED_TO_BE_DEFAULT_DISPLAY);
LogicalDisplay display1 = add(device1);
assertEquals(info(display1).address, info(device1).address);
@@ -263,10 +266,10 @@
@Test
public void testGetDisplayIdsLocked() {
- add(createDisplayDevice(Display.TYPE_INTERNAL, 600, 800,
- DisplayDeviceInfo.FLAG_ALLOWED_TO_BE_DEFAULT_DISPLAY));
+ add(createDisplayDevice(TYPE_INTERNAL, 600, 800,
+ FLAG_ALLOWED_TO_BE_DEFAULT_DISPLAY));
add(createDisplayDevice(Display.TYPE_EXTERNAL, 600, 800, 0));
- add(createDisplayDevice(Display.TYPE_VIRTUAL, 600, 800, 0));
+ add(createDisplayDevice(TYPE_VIRTUAL, 600, 800, 0));
int [] ids = mLogicalDisplayMapper.getDisplayIdsLocked(Process.SYSTEM_UID,
/* includeDisabled= */ true);
@@ -276,71 +279,98 @@
}
@Test
- public void testGetDisplayInfoForStateLocked_oneDisplayGroup_internalType() {
- add(createDisplayDevice(Display.TYPE_INTERNAL, 600, 800,
- DisplayDeviceInfo.FLAG_ALLOWED_TO_BE_DEFAULT_DISPLAY));
- add(createDisplayDevice(Display.TYPE_INTERNAL, 200, 800,
- DisplayDeviceInfo.FLAG_ALLOWED_TO_BE_DEFAULT_DISPLAY));
- add(createDisplayDevice(Display.TYPE_INTERNAL, 700, 800,
- DisplayDeviceInfo.FLAG_ALLOWED_TO_BE_DEFAULT_DISPLAY));
+ public void testGetDisplayInfoForStateLocked_defaultLayout() {
+ final DisplayDevice device1 = createDisplayDevice(TYPE_INTERNAL, 600, 800,
+ FLAG_ALLOWED_TO_BE_DEFAULT_DISPLAY);
+ final DisplayDevice device2 = createDisplayDevice(TYPE_INTERNAL, 200, 800,
+ FLAG_ALLOWED_TO_BE_DEFAULT_DISPLAY);
- Set<DisplayInfo> displayInfos = mLogicalDisplayMapper.getDisplayInfoForStateLocked(
- DeviceStateToLayoutMap.STATE_DEFAULT, DEFAULT_DISPLAY, DEFAULT_DISPLAY_GROUP);
- assertThat(displayInfos.size()).isEqualTo(1);
- for (DisplayInfo displayInfo : displayInfos) {
- assertThat(displayInfo.displayId).isEqualTo(DEFAULT_DISPLAY);
- assertThat(displayInfo.displayGroupId).isEqualTo(DEFAULT_DISPLAY_GROUP);
- assertThat(displayInfo.logicalWidth).isEqualTo(600);
- assertThat(displayInfo.logicalHeight).isEqualTo(800);
- }
+ add(device1);
+ add(device2);
+
+ Layout layout1 = new Layout();
+ layout1.createDisplayLocked(info(device1).address, /* isDefault= */ true,
+ /* isEnabled= */ true);
+ layout1.createDisplayLocked(info(device2).address, /* isDefault= */ false,
+ /* isEnabled= */ true);
+ when(mDeviceStateToLayoutMapSpy.get(STATE_DEFAULT)).thenReturn(layout1);
+ assertThat(layout1.size()).isEqualTo(2);
+ final int logicalId2 = layout1.getByAddress(info(device2).address).getLogicalDisplayId();
+
+ final DisplayInfo displayInfoDefault = mLogicalDisplayMapper.getDisplayInfoForStateLocked(
+ STATE_DEFAULT, DEFAULT_DISPLAY);
+ assertThat(displayInfoDefault.displayId).isEqualTo(DEFAULT_DISPLAY);
+ assertThat(displayInfoDefault.logicalWidth).isEqualTo(width(device1));
+ assertThat(displayInfoDefault.logicalHeight).isEqualTo(height(device1));
+
+ final DisplayInfo displayInfoOther = mLogicalDisplayMapper.getDisplayInfoForStateLocked(
+ STATE_DEFAULT, logicalId2);
+ assertThat(displayInfoOther).isNotNull();
+ assertThat(displayInfoOther.displayId).isEqualTo(logicalId2);
+ assertThat(displayInfoOther.logicalWidth).isEqualTo(width(device2));
+ assertThat(displayInfoOther.logicalHeight).isEqualTo(height(device2));
}
@Test
- public void testGetDisplayInfoForStateLocked_oneDisplayGroup_differentTypes() {
- add(createDisplayDevice(Display.TYPE_INTERNAL, 600, 800,
- DisplayDeviceInfo.FLAG_ALLOWED_TO_BE_DEFAULT_DISPLAY));
- add(createDisplayDevice(Display.TYPE_INTERNAL, 200, 800,
- DisplayDeviceInfo.FLAG_ALLOWED_TO_BE_DEFAULT_DISPLAY));
- add(createDisplayDevice(Display.TYPE_EXTERNAL, 700, 800,
- DisplayDeviceInfo.FLAG_ALLOWED_TO_BE_DEFAULT_DISPLAY));
+ public void testGetDisplayInfoForStateLocked_multipleLayouts() {
+ final DisplayDevice device1 = createDisplayDevice(TYPE_INTERNAL, 600, 800,
+ FLAG_ALLOWED_TO_BE_DEFAULT_DISPLAY);
+ final DisplayDevice device2 = createDisplayDevice(TYPE_INTERNAL, 200, 800,
+ FLAG_ALLOWED_TO_BE_DEFAULT_DISPLAY);
+ final DisplayDevice device3 = createDisplayDevice(TYPE_VIRTUAL, 700, 800,
+ DisplayDeviceInfo.FLAG_OWN_DISPLAY_GROUP);
- Set<DisplayInfo> displayInfos = mLogicalDisplayMapper.getDisplayInfoForStateLocked(
- DeviceStateToLayoutMap.STATE_DEFAULT, DEFAULT_DISPLAY, DEFAULT_DISPLAY_GROUP);
- assertThat(displayInfos.size()).isEqualTo(1);
- for (DisplayInfo displayInfo : displayInfos) {
- assertThat(displayInfo.displayId).isEqualTo(DEFAULT_DISPLAY);
- assertThat(displayInfo.displayGroupId).isEqualTo(DEFAULT_DISPLAY_GROUP);
- assertThat(displayInfo.logicalWidth).isEqualTo(600);
- assertThat(displayInfo.logicalHeight).isEqualTo(800);
- }
- }
+ add(device1);
+ add(device2);
+ add(device3);
- @Test
- public void testGetDisplayInfoForStateLocked_multipleDisplayGroups_defaultGroup() {
- add(createDisplayDevice(Display.TYPE_INTERNAL, 600, 800,
- DisplayDeviceInfo.FLAG_ALLOWED_TO_BE_DEFAULT_DISPLAY));
- add(createDisplayDevice(Display.TYPE_INTERNAL, 200, 800,
- DisplayDeviceInfo.FLAG_ALLOWED_TO_BE_DEFAULT_DISPLAY));
- add(createDisplayDevice(Display.TYPE_VIRTUAL, 700, 800,
- DisplayDeviceInfo.FLAG_OWN_DISPLAY_GROUP));
+ Layout layout1 = new Layout();
+ layout1.createDisplayLocked(info(device1).address,
+ /* isDefault= */ true, /* isEnabled= */ true);
+ when(mDeviceStateToLayoutMapSpy.get(STATE_DEFAULT)).thenReturn(layout1);
- Set<DisplayInfo> displayInfos = mLogicalDisplayMapper.getDisplayInfoForStateLocked(
- DeviceStateToLayoutMap.STATE_DEFAULT, DEFAULT_DISPLAY, DEFAULT_DISPLAY_GROUP);
- assertThat(displayInfos.size()).isEqualTo(1);
- for (DisplayInfo displayInfo : displayInfos) {
- assertThat(displayInfo.displayId).isEqualTo(DEFAULT_DISPLAY);
- assertThat(displayInfo.displayGroupId).isEqualTo(DEFAULT_DISPLAY_GROUP);
- assertThat(displayInfo.logicalWidth).isEqualTo(600);
- assertThat(displayInfo.logicalHeight).isEqualTo(800);
- }
+ final int layoutState2 = 2;
+ Layout layout2 = new Layout();
+ layout2.createDisplayLocked(info(device2).address,
+ /* isDefault= */ false, /* isEnabled= */ true);
+ // Device3 is the default display.
+ layout2.createDisplayLocked(info(device3).address,
+ /* isDefault= */ true, /* isEnabled= */ true);
+ when(mDeviceStateToLayoutMapSpy.get(layoutState2)).thenReturn(layout2);
+ assertThat(layout2.size()).isEqualTo(2);
+ final int logicalId2 = layout2.getByAddress(info(device2).address).getLogicalDisplayId();
+
+ // Default layout.
+ final DisplayInfo displayInfoLayout1Default =
+ mLogicalDisplayMapper.getDisplayInfoForStateLocked(
+ STATE_DEFAULT, DEFAULT_DISPLAY);
+ assertThat(displayInfoLayout1Default.displayId).isEqualTo(DEFAULT_DISPLAY);
+ assertThat(displayInfoLayout1Default.logicalWidth).isEqualTo(width(device1));
+ assertThat(displayInfoLayout1Default.logicalHeight).isEqualTo(height(device1));
+
+ // Second layout, where device3 is the default display.
+ final DisplayInfo displayInfoLayout2Default =
+ mLogicalDisplayMapper.getDisplayInfoForStateLocked(
+ layoutState2, DEFAULT_DISPLAY);
+ assertThat(displayInfoLayout2Default.displayId).isEqualTo(DEFAULT_DISPLAY);
+ assertThat(displayInfoLayout2Default.logicalWidth).isEqualTo(width(device3));
+ assertThat(displayInfoLayout2Default.logicalHeight).isEqualTo(height(device3));
+
+ final DisplayInfo displayInfoLayout2Other =
+ mLogicalDisplayMapper.getDisplayInfoForStateLocked(
+ layoutState2, logicalId2);
+ assertThat(displayInfoLayout2Other).isNotNull();
+ assertThat(displayInfoLayout2Other.displayId).isEqualTo(logicalId2);
+ assertThat(displayInfoLayout2Other.logicalWidth).isEqualTo(width(device2));
+ assertThat(displayInfoLayout2Other.logicalHeight).isEqualTo(height(device2));
}
@Test
public void testSingleDisplayGroup() {
- LogicalDisplay display1 = add(createDisplayDevice(Display.TYPE_INTERNAL, 600, 800,
- DisplayDeviceInfo.FLAG_ALLOWED_TO_BE_DEFAULT_DISPLAY));
- LogicalDisplay display2 = add(createDisplayDevice(Display.TYPE_INTERNAL, 600, 800, 0));
- LogicalDisplay display3 = add(createDisplayDevice(Display.TYPE_VIRTUAL, 600, 800, 0));
+ LogicalDisplay display1 = add(createDisplayDevice(TYPE_INTERNAL, 600, 800,
+ FLAG_ALLOWED_TO_BE_DEFAULT_DISPLAY));
+ LogicalDisplay display2 = add(createDisplayDevice(TYPE_INTERNAL, 600, 800, 0));
+ LogicalDisplay display3 = add(createDisplayDevice(TYPE_VIRTUAL, 600, 800, 0));
assertEquals(DEFAULT_DISPLAY_GROUP,
mLogicalDisplayMapper.getDisplayGroupIdFromDisplayIdLocked(id(display1)));
@@ -352,12 +382,12 @@
@Test
public void testMultipleDisplayGroups() {
- LogicalDisplay display1 = add(createDisplayDevice(Display.TYPE_INTERNAL, 600, 800,
- DisplayDeviceInfo.FLAG_ALLOWED_TO_BE_DEFAULT_DISPLAY));
- LogicalDisplay display2 = add(createDisplayDevice(Display.TYPE_INTERNAL, 600, 800, 0));
+ LogicalDisplay display1 = add(createDisplayDevice(TYPE_INTERNAL, 600, 800,
+ FLAG_ALLOWED_TO_BE_DEFAULT_DISPLAY));
+ LogicalDisplay display2 = add(createDisplayDevice(TYPE_INTERNAL, 600, 800, 0));
- TestDisplayDevice device3 = createDisplayDevice(Display.TYPE_VIRTUAL, 600, 800,
+ TestDisplayDevice device3 = createDisplayDevice(TYPE_VIRTUAL, 600, 800,
DisplayDeviceInfo.FLAG_OWN_DISPLAY_GROUP);
LogicalDisplay display3 = add(device3);
@@ -423,10 +453,10 @@
@Test
public void testDeviceStateLocked() {
- DisplayDevice device1 = createDisplayDevice(Display.TYPE_INTERNAL, 600, 800,
- DisplayDeviceInfo.FLAG_ALLOWED_TO_BE_DEFAULT_DISPLAY);
- DisplayDevice device2 = createDisplayDevice(Display.TYPE_INTERNAL, 600, 800,
- DisplayDeviceInfo.FLAG_ALLOWED_TO_BE_DEFAULT_DISPLAY);
+ DisplayDevice device1 = createDisplayDevice(TYPE_INTERNAL, 600, 800,
+ FLAG_ALLOWED_TO_BE_DEFAULT_DISPLAY);
+ DisplayDevice device2 = createDisplayDevice(TYPE_INTERNAL, 600, 800,
+ FLAG_ALLOWED_TO_BE_DEFAULT_DISPLAY);
Layout layout = new Layout();
layout.createDisplayLocked(device1.getDisplayDeviceInfoLocked().address, true, true);
@@ -479,13 +509,13 @@
DisplayAddress displayAddressTwo = new TestUtils.TestDisplayAddress();
DisplayAddress displayAddressThree = new TestUtils.TestDisplayAddress();
- TestDisplayDevice device1 = createDisplayDevice(displayAddressOne, Display.TYPE_INTERNAL,
+ TestDisplayDevice device1 = createDisplayDevice(displayAddressOne, TYPE_INTERNAL,
600, 800,
- DisplayDeviceInfo.FLAG_ALLOWED_TO_BE_DEFAULT_DISPLAY);
- TestDisplayDevice device2 = createDisplayDevice(displayAddressTwo, Display.TYPE_INTERNAL,
+ FLAG_ALLOWED_TO_BE_DEFAULT_DISPLAY);
+ TestDisplayDevice device2 = createDisplayDevice(displayAddressTwo, TYPE_INTERNAL,
200, 800,
DisplayDeviceInfo.FLAG_OWN_DISPLAY_GROUP);
- TestDisplayDevice device3 = createDisplayDevice(displayAddressThree, Display.TYPE_INTERNAL,
+ TestDisplayDevice device3 = createDisplayDevice(displayAddressThree, TYPE_INTERNAL,
600, 900, DisplayDeviceInfo.FLAG_OWN_DISPLAY_GROUP);
Layout threeDevicesEnabledLayout = new Layout();
@@ -502,7 +532,7 @@
/* isDefault= */ false,
/* isEnabled= */ true);
- when(mDeviceStateToLayoutMapSpy.get(DeviceStateToLayoutMap.STATE_DEFAULT))
+ when(mDeviceStateToLayoutMapSpy.get(STATE_DEFAULT))
.thenReturn(threeDevicesEnabledLayout);
LogicalDisplay display1 = add(device1);
@@ -620,6 +650,14 @@
return device.getDisplayDeviceInfoLocked();
}
+ private int width(DisplayDevice device) {
+ return info(device).width;
+ }
+
+ private int height(DisplayDevice device) {
+ return info(device).height;
+ }
+
private DisplayInfo info(LogicalDisplay display) {
return display.getDisplayInfoLocked();
}
diff --git a/services/tests/servicestests/src/com/android/server/net/NetworkPolicyManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/net/NetworkPolicyManagerServiceTest.java
index 07009cb..7c7e2ee 100644
--- a/services/tests/servicestests/src/com/android/server/net/NetworkPolicyManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/net/NetworkPolicyManagerServiceTest.java
@@ -75,6 +75,7 @@
import static com.android.server.net.NetworkPolicyManagerService.TYPE_RAPID;
import static com.android.server.net.NetworkPolicyManagerService.TYPE_WARNING;
import static com.android.server.net.NetworkPolicyManagerService.UidBlockedState.getEffectiveBlockedReasons;
+import static com.android.server.net.NetworkPolicyManagerService.normalizeTemplate;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
@@ -2059,6 +2060,18 @@
METERED_NO, actualPolicy.template.getMeteredness());
}
+ @Test
+ public void testNormalizeTemplate_duplicatedMergedImsiList() {
+ final NetworkTemplate template = new NetworkTemplate.Builder(MATCH_CARRIER)
+ .setSubscriberIds(Set.of(TEST_IMSI)).build();
+ final String[] mergedImsiGroup = new String[] {TEST_IMSI, TEST_IMSI};
+ final ArrayList<String[]> mergedList = new ArrayList<>();
+ mergedList.add(mergedImsiGroup);
+ // Verify the duplicated items in the merged IMSI list won't crash the system.
+ final NetworkTemplate result = normalizeTemplate(template, mergedList);
+ assertEquals(template, result);
+ }
+
private String formatBlockedStateError(int uid, int rule, boolean metered,
boolean backgroundRestricted) {
return String.format(
diff --git a/services/tests/servicestests/src/com/android/server/power/PowerGroupTest.java b/services/tests/servicestests/src/com/android/server/power/PowerGroupTest.java
index e3ca170..5bc750f 100644
--- a/services/tests/servicestests/src/com/android/server/power/PowerGroupTest.java
+++ b/services/tests/servicestests/src/com/android/server/power/PowerGroupTest.java
@@ -258,7 +258,6 @@
.build();
mPowerGroup.updateLocked(/* screenBrightnessOverride= */ BRIGHTNESS,
- /* autoBrightness = */ false,
/* useProximitySensor= */ false,
/* boostScreenBrightness= */ false,
/* dozeScreenStateOverride= */ Display.STATE_ON,
@@ -275,7 +274,6 @@
mPowerGroup.mDisplayPowerRequest;
assertThat(displayPowerRequest.policy).isEqualTo(POLICY_DIM);
assertThat(displayPowerRequest.screenBrightnessOverride).isWithin(PRECISION).of(BRIGHTNESS);
- assertThat(displayPowerRequest.useAutoBrightness).isEqualTo(false);
assertThat(displayPowerRequest.useProximitySensor).isEqualTo(false);
assertThat(displayPowerRequest.boostScreenBrightness).isEqualTo(false);
assertThat(displayPowerRequest.dozeScreenState).isEqualTo(Display.STATE_UNKNOWN);
@@ -299,7 +297,6 @@
mPowerGroup.setWakeLockSummaryLocked(WAKE_LOCK_DOZE);
mPowerGroup.updateLocked(/* screenBrightnessOverride= */ BRIGHTNESS,
- /* autoBrightness = */ true,
/* useProximitySensor= */ true,
/* boostScreenBrightness= */ true,
/* dozeScreenStateOverride= */ Display.STATE_ON,
@@ -316,7 +313,6 @@
mPowerGroup.mDisplayPowerRequest;
assertThat(displayPowerRequest.policy).isEqualTo(POLICY_DOZE);
assertThat(displayPowerRequest.screenBrightnessOverride).isWithin(PRECISION).of(BRIGHTNESS);
- assertThat(displayPowerRequest.useAutoBrightness).isEqualTo(true);
assertThat(displayPowerRequest.useProximitySensor).isEqualTo(true);
assertThat(displayPowerRequest.boostScreenBrightness).isEqualTo(true);
assertThat(displayPowerRequest.dozeScreenState).isEqualTo(Display.STATE_ON);
@@ -339,7 +335,6 @@
assertThat(mPowerGroup.getWakefulnessLocked()).isEqualTo(WAKEFULNESS_DOZING);
mPowerGroup.updateLocked(/* screenBrightnessOverride= */ BRIGHTNESS,
- /* autoBrightness = */ true,
/* useProximitySensor= */ true,
/* boostScreenBrightness= */ true,
/* dozeScreenStateOverride= */ Display.STATE_ON,
@@ -356,7 +351,6 @@
mPowerGroup.mDisplayPowerRequest;
assertThat(displayPowerRequest.policy).isEqualTo(POLICY_OFF);
assertThat(displayPowerRequest.screenBrightnessOverride).isWithin(PRECISION).of(BRIGHTNESS);
- assertThat(displayPowerRequest.useAutoBrightness).isEqualTo(true);
assertThat(displayPowerRequest.useProximitySensor).isEqualTo(true);
assertThat(displayPowerRequest.boostScreenBrightness).isEqualTo(true);
assertThat(displayPowerRequest.dozeScreenState).isEqualTo(Display.STATE_UNKNOWN);
@@ -378,7 +372,6 @@
assertThat(mPowerGroup.getWakefulnessLocked()).isEqualTo(WAKEFULNESS_AWAKE);
mPowerGroup.updateLocked(/* screenBrightnessOverride= */ BRIGHTNESS,
- /* autoBrightness = */ true,
/* useProximitySensor= */ true,
/* boostScreenBrightness= */ true,
/* dozeScreenStateOverride= */ Display.STATE_ON,
@@ -395,7 +388,6 @@
mPowerGroup.mDisplayPowerRequest;
assertThat(displayPowerRequest.policy).isEqualTo(POLICY_OFF);
assertThat(displayPowerRequest.screenBrightnessOverride).isWithin(PRECISION).of(BRIGHTNESS);
- assertThat(displayPowerRequest.useAutoBrightness).isEqualTo(true);
assertThat(displayPowerRequest.useProximitySensor).isEqualTo(true);
assertThat(displayPowerRequest.boostScreenBrightness).isEqualTo(true);
assertThat(displayPowerRequest.dozeScreenState).isEqualTo(Display.STATE_UNKNOWN);
@@ -417,7 +409,6 @@
mPowerGroup.sleepLocked(TIMESTAMP1, UID, GO_TO_SLEEP_REASON_TIMEOUT);
assertThat(mPowerGroup.getWakefulnessLocked()).isEqualTo(WAKEFULNESS_ASLEEP);
mPowerGroup.updateLocked(/* screenBrightnessOverride= */ BRIGHTNESS,
- /* autoBrightness = */ true,
/* useProximitySensor= */ true,
/* boostScreenBrightness= */ true,
/* dozeScreenStateOverride= */ Display.STATE_ON,
@@ -434,7 +425,6 @@
mPowerGroup.mDisplayPowerRequest;
assertThat(displayPowerRequest.policy).isEqualTo(POLICY_OFF);
assertThat(displayPowerRequest.screenBrightnessOverride).isWithin(PRECISION).of(BRIGHTNESS);
- assertThat(displayPowerRequest.useAutoBrightness).isEqualTo(true);
assertThat(displayPowerRequest.useProximitySensor).isEqualTo(true);
assertThat(displayPowerRequest.boostScreenBrightness).isEqualTo(true);
assertThat(displayPowerRequest.dozeScreenState).isEqualTo(Display.STATE_UNKNOWN);
@@ -455,7 +445,6 @@
.build();
assertThat(mPowerGroup.getWakefulnessLocked()).isEqualTo(WAKEFULNESS_AWAKE);
mPowerGroup.updateLocked(/* screenBrightnessOverride= */ BRIGHTNESS,
- /* autoBrightness = */ true,
/* useProximitySensor= */ true,
/* boostScreenBrightness= */ true,
/* dozeScreenStateOverride= */ Display.STATE_ON,
@@ -472,7 +461,6 @@
mPowerGroup.mDisplayPowerRequest;
assertThat(displayPowerRequest.policy).isEqualTo(POLICY_VR);
assertThat(displayPowerRequest.screenBrightnessOverride).isWithin(PRECISION).of(BRIGHTNESS);
- assertThat(displayPowerRequest.useAutoBrightness).isEqualTo(true);
assertThat(displayPowerRequest.useProximitySensor).isEqualTo(true);
assertThat(displayPowerRequest.boostScreenBrightness).isEqualTo(true);
assertThat(displayPowerRequest.dozeScreenState).isEqualTo(Display.STATE_UNKNOWN);
@@ -494,7 +482,6 @@
mPowerGroup.sleepLocked(TIMESTAMP1, UID, GO_TO_SLEEP_REASON_TIMEOUT);
assertThat(mPowerGroup.getWakefulnessLocked()).isEqualTo(WAKEFULNESS_ASLEEP);
mPowerGroup.updateLocked(/* screenBrightnessOverride= */ BRIGHTNESS,
- /* autoBrightness = */ true,
/* useProximitySensor= */ true,
/* boostScreenBrightness= */ true,
/* dozeScreenStateOverride= */ Display.STATE_ON,
@@ -511,7 +498,6 @@
mPowerGroup.mDisplayPowerRequest;
assertThat(displayPowerRequest.policy).isEqualTo(POLICY_OFF);
assertThat(displayPowerRequest.screenBrightnessOverride).isWithin(PRECISION).of(BRIGHTNESS);
- assertThat(displayPowerRequest.useAutoBrightness).isEqualTo(true);
assertThat(displayPowerRequest.useProximitySensor).isEqualTo(true);
assertThat(displayPowerRequest.boostScreenBrightness).isEqualTo(true);
assertThat(displayPowerRequest.dozeScreenState).isEqualTo(Display.STATE_UNKNOWN);
@@ -534,7 +520,6 @@
assertThat(mPowerGroup.getWakefulnessLocked()).isEqualTo(WAKEFULNESS_DREAMING);
mPowerGroup.setWakeLockSummaryLocked(WAKE_LOCK_SCREEN_BRIGHT);
mPowerGroup.updateLocked(/* screenBrightnessOverride= */ BRIGHTNESS,
- /* autoBrightness = */ true,
/* useProximitySensor= */ true,
/* boostScreenBrightness= */ true,
/* dozeScreenStateOverride= */ Display.STATE_ON,
@@ -551,7 +536,6 @@
mPowerGroup.mDisplayPowerRequest;
assertThat(displayPowerRequest.policy).isEqualTo(POLICY_BRIGHT);
assertThat(displayPowerRequest.screenBrightnessOverride).isWithin(PRECISION).of(BRIGHTNESS);
- assertThat(displayPowerRequest.useAutoBrightness).isEqualTo(true);
assertThat(displayPowerRequest.useProximitySensor).isEqualTo(true);
assertThat(displayPowerRequest.boostScreenBrightness).isEqualTo(true);
assertThat(displayPowerRequest.dozeScreenState).isEqualTo(Display.STATE_UNKNOWN);
@@ -572,7 +556,6 @@
.build();
assertThat(mPowerGroup.getWakefulnessLocked()).isEqualTo(WAKEFULNESS_AWAKE);
mPowerGroup.updateLocked(/* screenBrightnessOverride= */ BRIGHTNESS,
- /* autoBrightness = */ true,
/* useProximitySensor= */ true,
/* boostScreenBrightness= */ true,
/* dozeScreenStateOverride= */ Display.STATE_ON,
@@ -589,7 +572,6 @@
mPowerGroup.mDisplayPowerRequest;
assertThat(displayPowerRequest.policy).isEqualTo(POLICY_BRIGHT);
assertThat(displayPowerRequest.screenBrightnessOverride).isWithin(PRECISION).of(BRIGHTNESS);
- assertThat(displayPowerRequest.useAutoBrightness).isEqualTo(true);
assertThat(displayPowerRequest.useProximitySensor).isEqualTo(true);
assertThat(displayPowerRequest.boostScreenBrightness).isEqualTo(true);
assertThat(displayPowerRequest.dozeScreenState).isEqualTo(Display.STATE_UNKNOWN);
@@ -611,7 +593,6 @@
assertThat(mPowerGroup.getWakefulnessLocked()).isEqualTo(WAKEFULNESS_AWAKE);
mPowerGroup.setUserActivitySummaryLocked(USER_ACTIVITY_SCREEN_BRIGHT);
mPowerGroup.updateLocked(/* screenBrightnessOverride= */ BRIGHTNESS,
- /* autoBrightness = */ true,
/* useProximitySensor= */ true,
/* boostScreenBrightness= */ true,
/* dozeScreenStateOverride= */ Display.STATE_ON,
@@ -628,7 +609,6 @@
mPowerGroup.mDisplayPowerRequest;
assertThat(displayPowerRequest.policy).isEqualTo(POLICY_BRIGHT);
assertThat(displayPowerRequest.screenBrightnessOverride).isWithin(PRECISION).of(BRIGHTNESS);
- assertThat(displayPowerRequest.useAutoBrightness).isEqualTo(true);
assertThat(displayPowerRequest.useProximitySensor).isEqualTo(true);
assertThat(displayPowerRequest.boostScreenBrightness).isEqualTo(true);
assertThat(displayPowerRequest.dozeScreenState).isEqualTo(Display.STATE_UNKNOWN);
@@ -649,7 +629,6 @@
.build();
assertThat(mPowerGroup.getWakefulnessLocked()).isEqualTo(WAKEFULNESS_AWAKE);
mPowerGroup.updateLocked(/* screenBrightnessOverride= */ BRIGHTNESS,
- /* autoBrightness = */ true,
/* useProximitySensor= */ true,
/* boostScreenBrightness= */ true,
/* dozeScreenStateOverride= */ Display.STATE_ON,
@@ -666,7 +645,6 @@
mPowerGroup.mDisplayPowerRequest;
assertThat(displayPowerRequest.policy).isEqualTo(POLICY_BRIGHT);
assertThat(displayPowerRequest.screenBrightnessOverride).isWithin(PRECISION).of(BRIGHTNESS);
- assertThat(displayPowerRequest.useAutoBrightness).isEqualTo(true);
assertThat(displayPowerRequest.useProximitySensor).isEqualTo(true);
assertThat(displayPowerRequest.boostScreenBrightness).isEqualTo(true);
assertThat(displayPowerRequest.dozeScreenState).isEqualTo(Display.STATE_UNKNOWN);
diff --git a/services/tests/servicestests/src/com/android/server/power/PowerManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/power/PowerManagerServiceTest.java
index 6325008..cbe8e66 100644
--- a/services/tests/servicestests/src/com/android/server/power/PowerManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/power/PowerManagerServiceTest.java
@@ -492,6 +492,111 @@
}
@Test
+ public void testWakefulnessSleep_SoftSleepFlag_NoWakelocks() {
+ createService();
+ // Start with AWAKE state
+ startSystem();
+ assertThat(mService.getGlobalWakefulnessLocked()).isEqualTo(WAKEFULNESS_AWAKE);
+
+ // Take a nap and verify we go to sleep.
+ mService.getBinderServiceInstance().goToSleep(mClock.now(),
+ PowerManager.GO_TO_SLEEP_REASON_APPLICATION,
+ PowerManager.GO_TO_SLEEP_FLAG_SOFT_SLEEP);
+ assertThat(mService.getGlobalWakefulnessLocked()).isEqualTo(WAKEFULNESS_DOZING);
+ }
+
+ @Test
+ public void testWakefulnessSleep_SoftSleepFlag_WithPartialWakelock() {
+ createService();
+ // Start with AWAKE state
+ startSystem();
+ assertThat(mService.getGlobalWakefulnessLocked()).isEqualTo(WAKEFULNESS_AWAKE);
+
+ // Grab a wakelock
+ final String tag = "wakelock1";
+ final String packageName = "pkg.name";
+ final IBinder token = new Binder();
+ final int flags = PowerManager.PARTIAL_WAKE_LOCK;
+ mService.getBinderServiceInstance().acquireWakeLock(token, flags, tag, packageName,
+ null /* workSource */, null /* historyTag */, Display.INVALID_DISPLAY,
+ null /* callback */);
+
+ // Take a nap and verify we stay awake.
+ mService.getBinderServiceInstance().goToSleep(mClock.now(),
+ PowerManager.GO_TO_SLEEP_REASON_APPLICATION,
+ PowerManager.GO_TO_SLEEP_FLAG_SOFT_SLEEP);
+ assertThat(mService.getGlobalWakefulnessLocked()).isEqualTo(WAKEFULNESS_DOZING);
+ }
+
+ @Test
+ public void testWakefulnessSleep_SoftSleepFlag_WithFullWakelock() {
+ createService();
+ // Start with AWAKE state
+ startSystem();
+ assertThat(mService.getGlobalWakefulnessLocked()).isEqualTo(WAKEFULNESS_AWAKE);
+
+ // Grab a wakelock
+ final String tag = "wakelock1";
+ final String packageName = "pkg.name";
+ final IBinder token = new Binder();
+ final int flags = PowerManager.FULL_WAKE_LOCK;
+ mService.getBinderServiceInstance().acquireWakeLock(token, flags, tag, packageName,
+ null /* workSource */, null /* historyTag */, Display.INVALID_DISPLAY,
+ null /* callback */);
+
+ // Take a nap and verify we stay awake.
+ mService.getBinderServiceInstance().goToSleep(mClock.now(),
+ PowerManager.GO_TO_SLEEP_REASON_APPLICATION,
+ PowerManager.GO_TO_SLEEP_FLAG_SOFT_SLEEP);
+ assertThat(mService.getGlobalWakefulnessLocked()).isEqualTo(WAKEFULNESS_AWAKE);
+ }
+
+ @Test
+ public void testWakefulnessSleep_SoftSleepFlag_WithScreenBrightWakelock() {
+ createService();
+ // Start with AWAKE state
+ startSystem();
+ assertThat(mService.getGlobalWakefulnessLocked()).isEqualTo(WAKEFULNESS_AWAKE);
+
+ // Grab a wakelock
+ final String tag = "wakelock1";
+ final String packageName = "pkg.name";
+ final IBinder token = new Binder();
+ final int flags = PowerManager.SCREEN_BRIGHT_WAKE_LOCK;
+ mService.getBinderServiceInstance().acquireWakeLock(token, flags, tag, packageName,
+ null /* workSource */, null /* historyTag */, Display.INVALID_DISPLAY,
+ null /* callback */);
+
+ // Take a nap and verify we stay awake.
+ mService.getBinderServiceInstance().goToSleep(mClock.now(),
+ PowerManager.GO_TO_SLEEP_REASON_APPLICATION,
+ PowerManager.GO_TO_SLEEP_FLAG_SOFT_SLEEP);
+ assertThat(mService.getGlobalWakefulnessLocked()).isEqualTo(WAKEFULNESS_AWAKE);
+ }
+ @Test
+ public void testWakefulnessSleep_SoftSleepFlag_WithScreenDimWakelock() {
+ createService();
+ // Start with AWAKE state
+ startSystem();
+ assertThat(mService.getGlobalWakefulnessLocked()).isEqualTo(WAKEFULNESS_AWAKE);
+
+ // Grab a wakelock
+ final String tag = "wakelock1";
+ final String packageName = "pkg.name";
+ final IBinder token = new Binder();
+ final int flags = PowerManager.SCREEN_DIM_WAKE_LOCK;
+ mService.getBinderServiceInstance().acquireWakeLock(token, flags, tag, packageName,
+ null /* workSource */, null /* historyTag */, Display.INVALID_DISPLAY,
+ null /* callback */);
+
+ // Take a nap and verify we stay awake.
+ mService.getBinderServiceInstance().goToSleep(mClock.now(),
+ PowerManager.GO_TO_SLEEP_REASON_APPLICATION,
+ PowerManager.GO_TO_SLEEP_FLAG_SOFT_SLEEP);
+ assertThat(mService.getGlobalWakefulnessLocked()).isEqualTo(WAKEFULNESS_AWAKE);
+ }
+
+ @Test
public void testWakefulnessAwake_AcquireCausesWakeup_turnScreenOnAllowed() {
createService();
startSystem();
diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskFragmentOrganizerControllerTest.java b/services/tests/wmtests/src/com/android/server/wm/TaskFragmentOrganizerControllerTest.java
index db65f49..140051d 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TaskFragmentOrganizerControllerTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TaskFragmentOrganizerControllerTest.java
@@ -247,6 +247,7 @@
mController.onTaskFragmentVanished(mTaskFragment.getTaskFragmentOrganizer(), mTaskFragment);
mController.dispatchPendingEvents();
+ assertTrue(mTaskFragment.mTaskFragmentVanishedSent);
assertTaskFragmentVanishedTransaction();
}
@@ -259,10 +260,12 @@
mController.onTaskFragmentVanished(mTaskFragment.getTaskFragmentOrganizer(), mTaskFragment);
mController.dispatchPendingEvents();
+ assertTrue(mTaskFragment.mTaskFragmentVanishedSent);
assertTaskFragmentVanishedTransaction();
// Not trigger onTaskFragmentInfoChanged.
// Call onTaskFragmentAppeared before calling onTaskFragmentInfoChanged.
+ mTaskFragment.mTaskFragmentVanishedSent = false;
mController.onTaskFragmentAppeared(mTaskFragment.getTaskFragmentOrganizer(), mTaskFragment);
mController.dispatchPendingEvents();
clearInvocations(mOrganizer);
diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskFragmentTest.java b/services/tests/wmtests/src/com/android/server/wm/TaskFragmentTest.java
index 11ac929..c893255 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TaskFragmentTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TaskFragmentTest.java
@@ -38,6 +38,7 @@
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotEquals;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.anyInt;
@@ -98,12 +99,25 @@
}
@Test
- public void testOnConfigurationChanged_updateSurface() {
- final Rect bounds = new Rect(100, 100, 1100, 1100);
+ public void testOnConfigurationChanged() {
+ final Configuration parentConfig = mTaskFragment.getParent().getConfiguration();
+ final Rect parentBounds = parentConfig.windowConfiguration.getBounds();
+ parentConfig.smallestScreenWidthDp += 10;
+ final int parentSw = parentConfig.smallestScreenWidthDp;
+ final Rect bounds = new Rect(parentBounds);
+ bounds.inset(100, 100);
mTaskFragment.setBounds(bounds);
+ mTaskFragment.setWindowingMode(WINDOWING_MODE_MULTI_WINDOW);
+ // Calculate its own sw with smaller bounds in multi-window mode.
+ assertNotEquals(parentSw, mTaskFragment.getConfiguration().smallestScreenWidthDp);
- verify(mTransaction).setPosition(mLeash, 100, 100);
- verify(mTransaction).setWindowCrop(mLeash, 1000, 1000);
+ verify(mTransaction).setPosition(mLeash, bounds.left, bounds.top);
+ verify(mTransaction).setWindowCrop(mLeash, bounds.width(), bounds.height());
+
+ mTaskFragment.setBounds(parentBounds);
+ mTaskFragment.setWindowingMode(WINDOWING_MODE_FULLSCREEN);
+ // Inherit parent's sw in fullscreen mode.
+ assertEquals(parentSw, mTaskFragment.getConfiguration().smallestScreenWidthDp);
}
@Test
diff --git a/services/voiceinteraction/java/com/android/server/voiceinteraction/HotwordAudioStreamCopier.java b/services/voiceinteraction/java/com/android/server/voiceinteraction/HotwordAudioStreamCopier.java
new file mode 100644
index 0000000..244b3a0
--- /dev/null
+++ b/services/voiceinteraction/java/com/android/server/voiceinteraction/HotwordAudioStreamCopier.java
@@ -0,0 +1,342 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.voiceinteraction;
+
+import static android.app.AppOpsManager.MODE_ALLOWED;
+import static android.service.voice.HotwordAudioStream.KEY_AUDIO_STREAM_COPY_BUFFER_LENGTH_BYTES;
+
+import static com.android.internal.util.FrameworkStatsLog.HOTWORD_AUDIO_EGRESS_EVENT_REPORTED__EVENT__CLOSE_ERROR_FROM_SYSTEM;
+import static com.android.internal.util.FrameworkStatsLog.HOTWORD_AUDIO_EGRESS_EVENT_REPORTED__EVENT__EMPTY_AUDIO_STREAM_LIST;
+import static com.android.internal.util.FrameworkStatsLog.HOTWORD_AUDIO_EGRESS_EVENT_REPORTED__EVENT__ENDED;
+import static com.android.internal.util.FrameworkStatsLog.HOTWORD_AUDIO_EGRESS_EVENT_REPORTED__EVENT__ILLEGAL_COPY_BUFFER_SIZE;
+import static com.android.internal.util.FrameworkStatsLog.HOTWORD_AUDIO_EGRESS_EVENT_REPORTED__EVENT__INTERRUPTED_EXCEPTION;
+import static com.android.internal.util.FrameworkStatsLog.HOTWORD_AUDIO_EGRESS_EVENT_REPORTED__EVENT__NO_PERMISSION;
+import static com.android.internal.util.FrameworkStatsLog.HOTWORD_AUDIO_EGRESS_EVENT_REPORTED__EVENT__STARTED;
+import static com.android.server.voiceinteraction.HotwordDetectionConnection.DEBUG;
+
+import android.annotation.NonNull;
+import android.app.AppOpsManager;
+import android.os.ParcelFileDescriptor;
+import android.os.PersistableBundle;
+import android.service.voice.HotwordAudioStream;
+import android.service.voice.HotwordDetectedResult;
+import android.util.Slog;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.concurrent.Callable;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+
+/**
+ * Copies the audio streams in {@link HotwordDetectedResult}s. This allows the system to manage the
+ * lifetime of the {@link ParcelFileDescriptor}s and ensures that the flow of data is in the right
+ * direction from the {@link android.service.voice.HotwordDetectionService} to the client (i.e., the
+ * voice interactor).
+ *
+ * @hide
+ */
+final class HotwordAudioStreamCopier {
+
+ private static final String TAG = "HotwordAudioStreamCopier";
+ private static final String OP_MESSAGE = "Streaming hotword audio to VoiceInteractionService";
+ private static final String TASK_ID_PREFIX = "HotwordDetectedResult@";
+ private static final String THREAD_NAME_PREFIX = "Copy-";
+
+ // Corresponds to the OS pipe capacity in bytes
+ private static final int MAX_COPY_BUFFER_LENGTH_BYTES = 65_536;
+ private static final int DEFAULT_COPY_BUFFER_LENGTH_BYTES = 32_768;
+
+ private final AppOpsManager mAppOpsManager;
+ private final int mDetectorType;
+ private final int mVoiceInteractorUid;
+ private final String mVoiceInteractorPackageName;
+ private final String mVoiceInteractorAttributionTag;
+ private final ExecutorService mExecutorService = Executors.newCachedThreadPool();
+
+ HotwordAudioStreamCopier(@NonNull AppOpsManager appOpsManager, int detectorType,
+ int voiceInteractorUid, @NonNull String voiceInteractorPackageName,
+ @NonNull String voiceInteractorAttributionTag) {
+ mAppOpsManager = appOpsManager;
+ mDetectorType = detectorType;
+ mVoiceInteractorUid = voiceInteractorUid;
+ mVoiceInteractorPackageName = voiceInteractorPackageName;
+ mVoiceInteractorAttributionTag = voiceInteractorAttributionTag;
+ }
+
+ /**
+ * Starts copying the audio streams in the given {@link HotwordDetectedResult}.
+ * <p>
+ * The returned {@link HotwordDetectedResult} is identical the one that was passed in, except
+ * that the {@link ParcelFileDescriptor}s within {@link HotwordDetectedResult#getAudioStreams()}
+ * are replaced with descriptors from pipes managed by {@link HotwordAudioStreamCopier}. The
+ * returned value should be passed on to the client (i.e., the voice interactor).
+ * </p>
+ *
+ * @throws IOException If there was an error creating the managed pipe.
+ */
+ @NonNull
+ public HotwordDetectedResult startCopyingAudioStreams(@NonNull HotwordDetectedResult result)
+ throws IOException {
+ List<HotwordAudioStream> audioStreams = result.getAudioStreams();
+ if (audioStreams.isEmpty()) {
+ HotwordMetricsLogger.writeAudioEgressEvent(mDetectorType,
+ HOTWORD_AUDIO_EGRESS_EVENT_REPORTED__EVENT__EMPTY_AUDIO_STREAM_LIST,
+ mVoiceInteractorUid, /* streamSizeBytes= */ 0, /* bundleSizeBytes= */ 0,
+ /* streamCount= */ 0);
+ return result;
+ }
+
+ final int audioStreamCount = audioStreams.size();
+ List<HotwordAudioStream> newAudioStreams = new ArrayList<>(audioStreams.size());
+ List<CopyTaskInfo> copyTaskInfos = new ArrayList<>(audioStreams.size());
+ int totalMetadataBundleSizeBytes = 0;
+ for (HotwordAudioStream audioStream : audioStreams) {
+ ParcelFileDescriptor[] clientPipe = ParcelFileDescriptor.createReliablePipe();
+ ParcelFileDescriptor clientAudioSource = clientPipe[0];
+ ParcelFileDescriptor clientAudioSink = clientPipe[1];
+ HotwordAudioStream newAudioStream =
+ audioStream.buildUpon().setAudioStreamParcelFileDescriptor(
+ clientAudioSource).build();
+ newAudioStreams.add(newAudioStream);
+
+ int copyBufferLength = DEFAULT_COPY_BUFFER_LENGTH_BYTES;
+ PersistableBundle metadata = audioStream.getMetadata();
+ totalMetadataBundleSizeBytes += HotwordDetectedResult.getParcelableSize(metadata);
+ if (metadata.containsKey(KEY_AUDIO_STREAM_COPY_BUFFER_LENGTH_BYTES)) {
+ copyBufferLength = metadata.getInt(KEY_AUDIO_STREAM_COPY_BUFFER_LENGTH_BYTES, -1);
+ if (copyBufferLength < 1 || copyBufferLength > MAX_COPY_BUFFER_LENGTH_BYTES) {
+ HotwordMetricsLogger.writeAudioEgressEvent(mDetectorType,
+ HOTWORD_AUDIO_EGRESS_EVENT_REPORTED__EVENT__ILLEGAL_COPY_BUFFER_SIZE,
+ mVoiceInteractorUid, /* streamSizeBytes= */ 0, /* bundleSizeBytes= */ 0,
+ audioStreamCount);
+ Slog.w(TAG, "Attempted to set an invalid copy buffer length ("
+ + copyBufferLength + ") for: " + audioStream);
+ copyBufferLength = DEFAULT_COPY_BUFFER_LENGTH_BYTES;
+ } else if (DEBUG) {
+ Slog.i(TAG, "Copy buffer length set to " + copyBufferLength + " for: "
+ + audioStream);
+ }
+ }
+
+ ParcelFileDescriptor serviceAudioSource =
+ audioStream.getAudioStreamParcelFileDescriptor();
+ copyTaskInfos.add(new CopyTaskInfo(serviceAudioSource, clientAudioSink,
+ copyBufferLength));
+ }
+
+ String resultTaskId = TASK_ID_PREFIX + System.identityHashCode(result);
+ mExecutorService.execute(
+ new HotwordDetectedResultCopyTask(resultTaskId, copyTaskInfos,
+ totalMetadataBundleSizeBytes));
+
+ return result.buildUpon().setAudioStreams(newAudioStreams).build();
+ }
+
+ private static class CopyTaskInfo {
+ private final ParcelFileDescriptor mSource;
+ private final ParcelFileDescriptor mSink;
+ private final int mCopyBufferLength;
+
+ CopyTaskInfo(ParcelFileDescriptor source, ParcelFileDescriptor sink, int copyBufferLength) {
+ mSource = source;
+ mSink = sink;
+ mCopyBufferLength = copyBufferLength;
+ }
+ }
+
+ private class HotwordDetectedResultCopyTask implements Runnable {
+ private final String mResultTaskId;
+ private final List<CopyTaskInfo> mCopyTaskInfos;
+ private final int mTotalMetadataSizeBytes;
+ private final ExecutorService mExecutorService = Executors.newCachedThreadPool();
+
+ HotwordDetectedResultCopyTask(String resultTaskId, List<CopyTaskInfo> copyTaskInfos,
+ int totalMetadataSizeBytes) {
+ mResultTaskId = resultTaskId;
+ mCopyTaskInfos = copyTaskInfos;
+ mTotalMetadataSizeBytes = totalMetadataSizeBytes;
+ }
+
+ @Override
+ public void run() {
+ Thread.currentThread().setName(THREAD_NAME_PREFIX + mResultTaskId);
+ int size = mCopyTaskInfos.size();
+ List<SingleAudioStreamCopyTask> tasks = new ArrayList<>(size);
+ for (int i = 0; i < size; i++) {
+ CopyTaskInfo copyTaskInfo = mCopyTaskInfos.get(i);
+ String streamTaskId = mResultTaskId + "@" + i;
+ tasks.add(new SingleAudioStreamCopyTask(streamTaskId, copyTaskInfo.mSource,
+ copyTaskInfo.mSink, copyTaskInfo.mCopyBufferLength, mDetectorType,
+ mVoiceInteractorUid));
+ }
+
+ if (mAppOpsManager.startOpNoThrow(AppOpsManager.OPSTR_RECORD_AUDIO_HOTWORD,
+ mVoiceInteractorUid, mVoiceInteractorPackageName,
+ mVoiceInteractorAttributionTag, OP_MESSAGE) == MODE_ALLOWED) {
+ try {
+ HotwordMetricsLogger.writeAudioEgressEvent(mDetectorType,
+ HOTWORD_AUDIO_EGRESS_EVENT_REPORTED__EVENT__STARTED,
+ mVoiceInteractorUid, /* streamSizeBytes= */ 0, mTotalMetadataSizeBytes,
+ size);
+ // TODO(b/244599891): Set timeout, close after inactivity
+ mExecutorService.invokeAll(tasks);
+
+ int totalStreamSizeBytes = 0;
+ for (SingleAudioStreamCopyTask task : tasks) {
+ totalStreamSizeBytes += task.mTotalCopiedBytes;
+ }
+
+ Slog.i(TAG, mResultTaskId + ": Task was completed. Total bytes streamed: "
+ + totalStreamSizeBytes + ", total metadata bundle size bytes: "
+ + mTotalMetadataSizeBytes);
+ HotwordMetricsLogger.writeAudioEgressEvent(mDetectorType,
+ HOTWORD_AUDIO_EGRESS_EVENT_REPORTED__EVENT__ENDED,
+ mVoiceInteractorUid, totalStreamSizeBytes, mTotalMetadataSizeBytes,
+ size);
+ } catch (InterruptedException e) {
+ int totalStreamSizeBytes = 0;
+ for (SingleAudioStreamCopyTask task : tasks) {
+ totalStreamSizeBytes += task.mTotalCopiedBytes;
+ }
+
+ HotwordMetricsLogger.writeAudioEgressEvent(mDetectorType,
+ HOTWORD_AUDIO_EGRESS_EVENT_REPORTED__EVENT__INTERRUPTED_EXCEPTION,
+ mVoiceInteractorUid, totalStreamSizeBytes, mTotalMetadataSizeBytes,
+ size);
+ Slog.e(TAG, mResultTaskId + ": Task was interrupted. Total bytes streamed: "
+ + totalStreamSizeBytes + ", total metadata bundle size bytes: "
+ + mTotalMetadataSizeBytes);
+ bestEffortPropagateError(e.getMessage());
+ } finally {
+ mAppOpsManager.finishOp(AppOpsManager.OPSTR_RECORD_AUDIO_HOTWORD,
+ mVoiceInteractorUid, mVoiceInteractorPackageName,
+ mVoiceInteractorAttributionTag);
+ }
+ } else {
+ HotwordMetricsLogger.writeAudioEgressEvent(mDetectorType,
+ HOTWORD_AUDIO_EGRESS_EVENT_REPORTED__EVENT__NO_PERMISSION,
+ mVoiceInteractorUid, /* streamSizeBytes= */ 0, /* bundleSizeBytes= */ 0,
+ size);
+ bestEffortPropagateError(
+ "Failed to obtain RECORD_AUDIO_HOTWORD permission for voice interactor with"
+ + " uid=" + mVoiceInteractorUid
+ + " packageName=" + mVoiceInteractorPackageName
+ + " attributionTag=" + mVoiceInteractorAttributionTag);
+ }
+ }
+
+ private void bestEffortPropagateError(@NonNull String errorMessage) {
+ try {
+ for (CopyTaskInfo copyTaskInfo : mCopyTaskInfos) {
+ copyTaskInfo.mSource.closeWithError(errorMessage);
+ copyTaskInfo.mSink.closeWithError(errorMessage);
+ }
+ HotwordMetricsLogger.writeAudioEgressEvent(mDetectorType,
+ HOTWORD_AUDIO_EGRESS_EVENT_REPORTED__EVENT__CLOSE_ERROR_FROM_SYSTEM,
+ mVoiceInteractorUid, /* streamSizeBytes= */ 0, /* bundleSizeBytes= */ 0,
+ mCopyTaskInfos.size());
+ } catch (IOException e) {
+ Slog.e(TAG, mResultTaskId + ": Failed to propagate error", e);
+ }
+ }
+ }
+
+ private static class SingleAudioStreamCopyTask implements Callable<Void> {
+ private final String mStreamTaskId;
+ private final ParcelFileDescriptor mAudioSource;
+ private final ParcelFileDescriptor mAudioSink;
+ private final int mCopyBufferLength;
+ private final int mDetectorType;
+ private final int mUid;
+
+ private volatile int mTotalCopiedBytes = 0;
+
+ SingleAudioStreamCopyTask(String streamTaskId, ParcelFileDescriptor audioSource,
+ ParcelFileDescriptor audioSink, int copyBufferLength, int detectorType, int uid) {
+ mStreamTaskId = streamTaskId;
+ mAudioSource = audioSource;
+ mAudioSink = audioSink;
+ mCopyBufferLength = copyBufferLength;
+ mDetectorType = detectorType;
+ mUid = uid;
+ }
+
+ @Override
+ public Void call() throws Exception {
+ Thread.currentThread().setName(THREAD_NAME_PREFIX + mStreamTaskId);
+
+ // Note: We are intentionally NOT using try-with-resources here. If we did,
+ // the ParcelFileDescriptors will be automatically closed WITHOUT errors before we go
+ // into the IOException-catch block. We want to propagate the error while closing the
+ // PFDs.
+ InputStream fis = null;
+ OutputStream fos = null;
+ try {
+ fis = new ParcelFileDescriptor.AutoCloseInputStream(mAudioSource);
+ fos = new ParcelFileDescriptor.AutoCloseOutputStream(mAudioSink);
+ byte[] buffer = new byte[mCopyBufferLength];
+ while (true) {
+ if (Thread.interrupted()) {
+ Slog.e(TAG,
+ mStreamTaskId + ": SingleAudioStreamCopyTask task was interrupted");
+ break;
+ }
+
+ int bytesRead = fis.read(buffer);
+ if (bytesRead < 0) {
+ Slog.i(TAG, mStreamTaskId + ": Reached end of audio stream");
+ break;
+ }
+ if (bytesRead > 0) {
+ if (DEBUG) {
+ // TODO(b/244599440): Add proper logging
+ Slog.d(TAG, mStreamTaskId + ": Copied " + bytesRead
+ + " bytes from audio stream. First 20 bytes=" + Arrays.toString(
+ Arrays.copyOfRange(buffer, 0, 20)));
+ }
+ fos.write(buffer, 0, bytesRead);
+ mTotalCopiedBytes += bytesRead;
+ }
+ // TODO(b/244599891): Close PFDs after inactivity
+ }
+ } catch (IOException e) {
+ mAudioSource.closeWithError(e.getMessage());
+ mAudioSink.closeWithError(e.getMessage());
+ Slog.e(TAG, mStreamTaskId + ": Failed to copy audio stream", e);
+ HotwordMetricsLogger.writeAudioEgressEvent(mDetectorType,
+ HOTWORD_AUDIO_EGRESS_EVENT_REPORTED__EVENT__CLOSE_ERROR_FROM_SYSTEM,
+ mUid, /* streamSizeBytes= */ 0, /* bundleSizeBytes= */ 0,
+ /* streamCount= */ 0);
+ } finally {
+ if (fis != null) {
+ fis.close();
+ }
+ if (fos != null) {
+ fos.close();
+ }
+ }
+
+ return null;
+ }
+ }
+
+}
diff --git a/services/voiceinteraction/java/com/android/server/voiceinteraction/HotwordAudioStreamManager.java b/services/voiceinteraction/java/com/android/server/voiceinteraction/HotwordAudioStreamManager.java
deleted file mode 100644
index d5eea1f..0000000
--- a/services/voiceinteraction/java/com/android/server/voiceinteraction/HotwordAudioStreamManager.java
+++ /dev/null
@@ -1,233 +0,0 @@
-/*
- * Copyright (C) 2022 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.server.voiceinteraction;
-
-import static android.app.AppOpsManager.MODE_ALLOWED;
-
-import static com.android.server.voiceinteraction.HotwordDetectionConnection.DEBUG;
-
-import android.annotation.NonNull;
-import android.app.AppOpsManager;
-import android.media.permission.Identity;
-import android.os.ParcelFileDescriptor;
-import android.service.voice.HotwordAudioStream;
-import android.service.voice.HotwordDetectedResult;
-import android.util.Pair;
-import android.util.Slog;
-
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.OutputStream;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.List;
-import java.util.concurrent.Callable;
-import java.util.concurrent.ExecutorService;
-import java.util.concurrent.Executors;
-
-final class HotwordAudioStreamManager {
-
- private static final String TAG = "HotwordAudioStreamManager";
- private static final String OP_MESSAGE = "Streaming hotword audio to VoiceInteractionService";
- private static final String TASK_ID_PREFIX = "HotwordDetectedResult@";
- private static final String THREAD_NAME_PREFIX = "Copy-";
-
- private final AppOpsManager mAppOpsManager;
- private final Identity mVoiceInteractorIdentity;
- private final ExecutorService mExecutorService = Executors.newCachedThreadPool();
-
- HotwordAudioStreamManager(@NonNull AppOpsManager appOpsManager,
- @NonNull Identity voiceInteractorIdentity) {
- mAppOpsManager = appOpsManager;
- mVoiceInteractorIdentity = voiceInteractorIdentity;
- }
-
- /**
- * Starts copying the audio streams in the given {@link HotwordDetectedResult}.
- * <p>
- * The returned {@link HotwordDetectedResult} is identical the one that was passed in, except
- * that the {@link ParcelFileDescriptor}s within {@link HotwordDetectedResult#getAudioStreams()}
- * are replaced with descriptors from pipes managed by {@link HotwordAudioStreamManager}. The
- * returned value should be passed on to the client (i.e., the voice interactor).
- * </p>
- *
- * @throws IOException If there was an error creating the managed pipe.
- */
- @NonNull
- public HotwordDetectedResult startCopyingAudioStreams(@NonNull HotwordDetectedResult result)
- throws IOException {
- List<HotwordAudioStream> audioStreams = result.getAudioStreams();
- if (audioStreams.isEmpty()) {
- return result;
- }
-
- List<HotwordAudioStream> newAudioStreams = new ArrayList<>(audioStreams.size());
- List<Pair<ParcelFileDescriptor, ParcelFileDescriptor>> sourcesAndSinks = new ArrayList<>(
- audioStreams.size());
- for (HotwordAudioStream audioStream : audioStreams) {
- ParcelFileDescriptor[] clientPipe = ParcelFileDescriptor.createReliablePipe();
- ParcelFileDescriptor clientAudioSource = clientPipe[0];
- ParcelFileDescriptor clientAudioSink = clientPipe[1];
- HotwordAudioStream newAudioStream =
- audioStream.buildUpon().setAudioStreamParcelFileDescriptor(
- clientAudioSource).build();
- newAudioStreams.add(newAudioStream);
-
- ParcelFileDescriptor serviceAudioSource =
- audioStream.getAudioStreamParcelFileDescriptor();
- sourcesAndSinks.add(new Pair<>(serviceAudioSource, clientAudioSink));
- }
-
- String resultTaskId = TASK_ID_PREFIX + System.identityHashCode(result);
- mExecutorService.execute(new HotwordDetectedResultCopyTask(resultTaskId, sourcesAndSinks));
-
- return result.buildUpon().setAudioStreams(newAudioStreams).build();
- }
-
- private class HotwordDetectedResultCopyTask implements Runnable {
- private final String mResultTaskId;
- private final List<Pair<ParcelFileDescriptor, ParcelFileDescriptor>> mSourcesAndSinks;
- private final ExecutorService mExecutorService = Executors.newCachedThreadPool();
-
- HotwordDetectedResultCopyTask(String resultTaskId,
- List<Pair<ParcelFileDescriptor, ParcelFileDescriptor>> sourcesAndSinks) {
- mResultTaskId = resultTaskId;
- mSourcesAndSinks = sourcesAndSinks;
- }
-
- @Override
- public void run() {
- Thread.currentThread().setName(THREAD_NAME_PREFIX + mResultTaskId);
- int size = mSourcesAndSinks.size();
- List<SingleAudioStreamCopyTask> tasks = new ArrayList<>(size);
- for (int i = 0; i < size; i++) {
- Pair<ParcelFileDescriptor, ParcelFileDescriptor> sourceAndSink =
- mSourcesAndSinks.get(i);
- ParcelFileDescriptor serviceAudioSource = sourceAndSink.first;
- ParcelFileDescriptor clientAudioSink = sourceAndSink.second;
- String streamTaskId = mResultTaskId + "@" + i;
- tasks.add(new SingleAudioStreamCopyTask(streamTaskId, serviceAudioSource,
- clientAudioSink));
- }
-
- if (mAppOpsManager.startOpNoThrow(AppOpsManager.OPSTR_RECORD_AUDIO_HOTWORD,
- mVoiceInteractorIdentity.uid, mVoiceInteractorIdentity.packageName,
- mVoiceInteractorIdentity.attributionTag, OP_MESSAGE) == MODE_ALLOWED) {
- try {
- // TODO(b/244599891): Set timeout, close after inactivity
- mExecutorService.invokeAll(tasks);
- } catch (InterruptedException e) {
- Slog.e(TAG, mResultTaskId + ": Task was interrupted", e);
- bestEffortPropagateError(e.getMessage());
- } finally {
- mAppOpsManager.finishOp(AppOpsManager.OPSTR_RECORD_AUDIO_HOTWORD,
- mVoiceInteractorIdentity.uid, mVoiceInteractorIdentity.packageName,
- mVoiceInteractorIdentity.attributionTag);
- }
- } else {
- bestEffortPropagateError(
- "Failed to obtain RECORD_AUDIO_HOTWORD permission for "
- + SoundTriggerSessionPermissionsDecorator.toString(
- mVoiceInteractorIdentity));
- }
- }
-
- private void bestEffortPropagateError(@NonNull String errorMessage) {
- try {
- for (Pair<ParcelFileDescriptor, ParcelFileDescriptor> sourceAndSink :
- mSourcesAndSinks) {
- ParcelFileDescriptor serviceAudioSource = sourceAndSink.first;
- ParcelFileDescriptor clientAudioSink = sourceAndSink.second;
- serviceAudioSource.closeWithError(errorMessage);
- clientAudioSink.closeWithError(errorMessage);
- }
- } catch (IOException e) {
- Slog.e(TAG, mResultTaskId + ": Failed to propagate error", e);
- }
- }
- }
-
- private static class SingleAudioStreamCopyTask implements Callable<Void> {
- // TODO: Make this buffer size customizable from updateState()
- private static final int COPY_BUFFER_LENGTH = 2_560;
-
- private final String mStreamTaskId;
- private final ParcelFileDescriptor mAudioSource;
- private final ParcelFileDescriptor mAudioSink;
-
- SingleAudioStreamCopyTask(String streamTaskId, ParcelFileDescriptor audioSource,
- ParcelFileDescriptor audioSink) {
- mStreamTaskId = streamTaskId;
- mAudioSource = audioSource;
- mAudioSink = audioSink;
- }
-
- @Override
- public Void call() throws Exception {
- Thread.currentThread().setName(THREAD_NAME_PREFIX + mStreamTaskId);
-
- // Note: We are intentionally NOT using try-with-resources here. If we did,
- // the ParcelFileDescriptors will be automatically closed WITHOUT errors before we go
- // into the IOException-catch block. We want to propagate the error while closing the
- // PFDs.
- InputStream fis = null;
- OutputStream fos = null;
- try {
- fis = new ParcelFileDescriptor.AutoCloseInputStream(mAudioSource);
- fos = new ParcelFileDescriptor.AutoCloseOutputStream(mAudioSink);
- byte[] buffer = new byte[COPY_BUFFER_LENGTH];
- while (true) {
- if (Thread.interrupted()) {
- Slog.e(TAG,
- mStreamTaskId + ": SingleAudioStreamCopyTask task was interrupted");
- break;
- }
-
- int bytesRead = fis.read(buffer);
- if (bytesRead < 0) {
- Slog.i(TAG, mStreamTaskId + ": Reached end of audio stream");
- break;
- }
- if (bytesRead > 0) {
- if (DEBUG) {
- // TODO(b/244599440): Add proper logging
- Slog.d(TAG, mStreamTaskId + ": Copied " + bytesRead
- + " bytes from audio stream. First 20 bytes=" + Arrays.toString(
- Arrays.copyOfRange(buffer, 0, 20)));
- }
- fos.write(buffer, 0, bytesRead);
- }
- // TODO(b/244599891): Close PFDs after inactivity
- }
- } catch (IOException e) {
- mAudioSource.closeWithError(e.getMessage());
- mAudioSink.closeWithError(e.getMessage());
- Slog.e(TAG, mStreamTaskId + ": Failed to copy audio stream", e);
- } finally {
- if (fis != null) {
- fis.close();
- }
- if (fos != null) {
- fos.close();
- }
- }
-
- return null;
- }
- }
-
-}
diff --git a/services/voiceinteraction/java/com/android/server/voiceinteraction/HotwordDetectionConnection.java b/services/voiceinteraction/java/com/android/server/voiceinteraction/HotwordDetectionConnection.java
index 6f7d80c..55bf2ab 100644
--- a/services/voiceinteraction/java/com/android/server/voiceinteraction/HotwordDetectionConnection.java
+++ b/services/voiceinteraction/java/com/android/server/voiceinteraction/HotwordDetectionConnection.java
@@ -170,7 +170,7 @@
private final ScheduledExecutorService mScheduledExecutorService =
Executors.newSingleThreadScheduledExecutor();
private final AppOpsManager mAppOpsManager;
- private final HotwordAudioStreamManager mHotwordAudioStreamManager;
+ private final HotwordAudioStreamCopier mHotwordAudioStreamCopier;
@Nullable private final ScheduledFuture<?> mCancellationTaskFuture;
private final AtomicBoolean mUpdateStateAfterStartFinished = new AtomicBoolean(false);
private final IBinder.DeathRecipient mAudioServerDeathRecipient = this::audioServerDied;
@@ -232,8 +232,9 @@
mVoiceInteractionServiceUid = voiceInteractionServiceUid;
mVoiceInteractorIdentity = voiceInteractorIdentity;
mAppOpsManager = mContext.getSystemService(AppOpsManager.class);
- mHotwordAudioStreamManager = new HotwordAudioStreamManager(mAppOpsManager,
- mVoiceInteractorIdentity);
+ mHotwordAudioStreamCopier = new HotwordAudioStreamCopier(mAppOpsManager, detectorType,
+ mVoiceInteractorIdentity.uid, mVoiceInteractorIdentity.packageName,
+ mVoiceInteractorIdentity.attributionTag);
mDetectionComponentName = serviceName;
mUser = userId;
mCallback = callback;
@@ -267,7 +268,8 @@
synchronized (mLock) {
restartProcessLocked();
HotwordMetricsLogger.writeServiceRestartEvent(mDetectorType,
- HOTWORD_DETECTION_SERVICE_RESTARTED__REASON__SCHEDULE);
+ HOTWORD_DETECTION_SERVICE_RESTARTED__REASON__SCHEDULE,
+ mVoiceInteractionServiceUid);
}
}, mReStartPeriodSeconds, mReStartPeriodSeconds, TimeUnit.SECONDS);
}
@@ -302,7 +304,8 @@
// conditions with audio reading in the service.
restartProcessLocked();
HotwordMetricsLogger.writeServiceRestartEvent(mDetectorType,
- HOTWORD_DETECTION_SERVICE_RESTARTED__REASON__AUDIO_SERVICE_DIED);
+ HOTWORD_DETECTION_SERVICE_RESTARTED__REASON__AUDIO_SERVICE_DIED,
+ mVoiceInteractionServiceUid);
}
}
@@ -333,13 +336,14 @@
try {
mCallback.onStatusReported(status);
HotwordMetricsLogger.writeServiceInitResultEvent(mDetectorType,
- initResultMetricsResult);
+ initResultMetricsResult, mVoiceInteractionServiceUid);
} catch (RemoteException e) {
// TODO: Add a new atom for RemoteException case, the error doesn't very
// correct here
Slog.w(TAG, "Failed to report initialization status: " + e);
HotwordMetricsLogger.writeServiceInitResultEvent(mDetectorType,
- METRICS_INIT_CALLBACK_STATE_ERROR);
+ METRICS_INIT_CALLBACK_STATE_ERROR,
+ mVoiceInteractionServiceUid);
}
}
};
@@ -362,11 +366,12 @@
try {
mCallback.onStatusReported(INITIALIZATION_STATUS_UNKNOWN);
HotwordMetricsLogger.writeServiceInitResultEvent(mDetectorType,
- METRICS_INIT_UNKNOWN_TIMEOUT);
+ METRICS_INIT_UNKNOWN_TIMEOUT, mVoiceInteractionServiceUid);
} catch (RemoteException e) {
Slog.w(TAG, "Failed to report initialization status UNKNOWN", e);
HotwordMetricsLogger.writeServiceInitResultEvent(mDetectorType,
- METRICS_INIT_CALLBACK_STATE_ERROR);
+ METRICS_INIT_CALLBACK_STATE_ERROR,
+ mVoiceInteractionServiceUid);
}
} else if (err != null) {
Slog.w(TAG, "Failed to update state: " + err);
@@ -469,12 +474,14 @@
synchronized (mLock) {
HotwordMetricsLogger.writeKeyphraseTriggerEvent(
mDetectorType,
- HOTWORD_DETECTOR_KEYPHRASE_TRIGGERED__RESULT__DETECTED);
+ HOTWORD_DETECTOR_KEYPHRASE_TRIGGERED__RESULT__DETECTED,
+ mVoiceInteractionServiceUid);
if (!mPerformingSoftwareHotwordDetection) {
Slog.i(TAG, "Hotword detection has already completed");
HotwordMetricsLogger.writeKeyphraseTriggerEvent(
mDetectorType,
- METRICS_KEYPHRASE_TRIGGERED_DETECT_UNEXPECTED_CALLBACK);
+ METRICS_KEYPHRASE_TRIGGERED_DETECT_UNEXPECTED_CALLBACK,
+ mVoiceInteractionServiceUid);
return;
}
mPerformingSoftwareHotwordDetection = false;
@@ -483,14 +490,15 @@
} catch (SecurityException e) {
HotwordMetricsLogger.writeKeyphraseTriggerEvent(
mDetectorType,
- METRICS_KEYPHRASE_TRIGGERED_DETECT_SECURITY_EXCEPTION);
+ METRICS_KEYPHRASE_TRIGGERED_DETECT_SECURITY_EXCEPTION,
+ mVoiceInteractionServiceUid);
mSoftwareCallback.onError();
return;
}
saveProximityValueToBundle(result);
HotwordDetectedResult newResult;
try {
- newResult = mHotwordAudioStreamManager.startCopyingAudioStreams(result);
+ newResult = mHotwordAudioStreamCopier.startCopyingAudioStreams(result);
} catch (IOException e) {
// TODO: Write event
mSoftwareCallback.onError();
@@ -512,7 +520,8 @@
}
HotwordMetricsLogger.writeKeyphraseTriggerEvent(
mDetectorType,
- HOTWORD_DETECTOR_KEYPHRASE_TRIGGERED__RESULT__REJECTED);
+ HOTWORD_DETECTOR_KEYPHRASE_TRIGGERED__RESULT__REJECTED,
+ mVoiceInteractionServiceUid);
// onRejected isn't allowed here, and we are not expecting it.
}
};
@@ -660,12 +669,14 @@
}
HotwordMetricsLogger.writeKeyphraseTriggerEvent(
mDetectorType,
- HOTWORD_DETECTOR_KEYPHRASE_TRIGGERED__RESULT__DETECTED);
+ HOTWORD_DETECTOR_KEYPHRASE_TRIGGERED__RESULT__DETECTED,
+ mVoiceInteractionServiceUid);
if (!mValidatingDspTrigger) {
Slog.i(TAG, "Ignoring #onDetected due to a process restart");
HotwordMetricsLogger.writeKeyphraseTriggerEvent(
mDetectorType,
- METRICS_KEYPHRASE_TRIGGERED_DETECT_UNEXPECTED_CALLBACK);
+ METRICS_KEYPHRASE_TRIGGERED_DETECT_UNEXPECTED_CALLBACK,
+ mVoiceInteractionServiceUid);
return;
}
mValidatingDspTrigger = false;
@@ -675,14 +686,15 @@
Slog.i(TAG, "Ignoring #onDetected due to a SecurityException", e);
HotwordMetricsLogger.writeKeyphraseTriggerEvent(
mDetectorType,
- METRICS_KEYPHRASE_TRIGGERED_DETECT_SECURITY_EXCEPTION);
+ METRICS_KEYPHRASE_TRIGGERED_DETECT_SECURITY_EXCEPTION,
+ mVoiceInteractionServiceUid);
externalCallback.onError(CALLBACK_ONDETECTED_GOT_SECURITY_EXCEPTION);
return;
}
saveProximityValueToBundle(result);
HotwordDetectedResult newResult;
try {
- newResult = mHotwordAudioStreamManager.startCopyingAudioStreams(result);
+ newResult = mHotwordAudioStreamCopier.startCopyingAudioStreams(result);
} catch (IOException e) {
// TODO: Write event
externalCallback.onError(CALLBACK_ONDETECTED_STREAM_COPY_ERROR);
@@ -708,12 +720,14 @@
}
HotwordMetricsLogger.writeKeyphraseTriggerEvent(
mDetectorType,
- HOTWORD_DETECTOR_KEYPHRASE_TRIGGERED__RESULT__REJECTED);
+ HOTWORD_DETECTOR_KEYPHRASE_TRIGGERED__RESULT__REJECTED,
+ mVoiceInteractionServiceUid);
if (!mValidatingDspTrigger) {
Slog.i(TAG, "Ignoring #onRejected due to a process restart");
HotwordMetricsLogger.writeKeyphraseTriggerEvent(
mDetectorType,
- METRICS_KEYPHRASE_TRIGGERED_REJECT_UNEXPECTED_CALLBACK);
+ METRICS_KEYPHRASE_TRIGGERED_REJECT_UNEXPECTED_CALLBACK,
+ mVoiceInteractionServiceUid);
return;
}
mValidatingDspTrigger = false;
@@ -727,21 +741,20 @@
synchronized (mLock) {
mValidatingDspTrigger = true;
- mRemoteHotwordDetectionService.run(
- service -> {
- // TODO: avoid allocate every time
- mCancellationKeyPhraseDetectionFuture = mScheduledExecutorService.schedule(
- () -> HotwordMetricsLogger
- .writeKeyphraseTriggerEvent(mDetectorType,
- HOTWORD_DETECTOR_KEYPHRASE_TRIGGERED__RESULT__DETECT_TIMEOUT),
- VALIDATION_TIMEOUT_MILLIS,
- TimeUnit.MILLISECONDS);
- service.detectFromDspSource(
- recognitionEvent,
- recognitionEvent.getCaptureFormat(),
- VALIDATION_TIMEOUT_MILLIS,
- internalCallback);
- });
+ mRemoteHotwordDetectionService.run(service -> {
+ // TODO: avoid allocate every time
+ mCancellationKeyPhraseDetectionFuture = mScheduledExecutorService.schedule(
+ () -> HotwordMetricsLogger.writeKeyphraseTriggerEvent(mDetectorType,
+ HOTWORD_DETECTOR_KEYPHRASE_TRIGGERED__RESULT__DETECT_TIMEOUT,
+ mVoiceInteractionServiceUid),
+ VALIDATION_TIMEOUT_MILLIS,
+ TimeUnit.MILLISECONDS);
+ service.detectFromDspSource(
+ recognitionEvent,
+ recognitionEvent.getCaptureFormat(),
+ VALIDATION_TIMEOUT_MILLIS,
+ internalCallback);
+ });
}
}
@@ -789,7 +802,8 @@
mCallback.onRejected(new HotwordRejectedResult.Builder().build());
HotwordMetricsLogger.writeKeyphraseTriggerEvent(
mDetectorType,
- HOTWORD_DETECTOR_KEYPHRASE_TRIGGERED__RESULT__REJECTED_FROM_RESTART);
+ HOTWORD_DETECTOR_KEYPHRASE_TRIGGERED__RESULT__REJECTED_FROM_RESTART,
+ mVoiceInteractionServiceUid);
} catch (RemoteException e) {
Slog.w(TAG, "Failed to call #rejected");
}
@@ -835,11 +849,13 @@
private SoundTrigger.KeyphraseRecognitionEvent mRecognitionEvent;
private final HotwordDetectionConnection mHotwordDetectionConnection;
private final IHotwordRecognitionStatusCallback mExternalCallback;
+ private final int mVoiceInteractionServiceUid;
SoundTriggerCallback(IHotwordRecognitionStatusCallback callback,
- HotwordDetectionConnection connection) {
+ HotwordDetectionConnection connection, int uid) {
mHotwordDetectionConnection = connection;
mExternalCallback = callback;
+ mVoiceInteractionServiceUid = uid;
}
@Override
@@ -852,14 +868,16 @@
if (useHotwordDetectionService) {
HotwordMetricsLogger.writeKeyphraseTriggerEvent(
HOTWORD_DETECTOR_KEYPHRASE_TRIGGERED__DETECTOR_TYPE__TRUSTED_DETECTOR_DSP,
- HOTWORD_DETECTOR_KEYPHRASE_TRIGGERED__RESULT__KEYPHRASE_TRIGGER);
+ HOTWORD_DETECTOR_KEYPHRASE_TRIGGERED__RESULT__KEYPHRASE_TRIGGER,
+ mVoiceInteractionServiceUid);
mRecognitionEvent = recognitionEvent;
mHotwordDetectionConnection.detectFromDspSource(
recognitionEvent, mExternalCallback);
} else {
HotwordMetricsLogger.writeKeyphraseTriggerEvent(
HOTWORD_DETECTOR_KEYPHRASE_TRIGGERED__DETECTOR_TYPE__NORMAL_DETECTOR,
- HOTWORD_DETECTOR_KEYPHRASE_TRIGGERED__RESULT__KEYPHRASE_TRIGGER);
+ HOTWORD_DETECTOR_KEYPHRASE_TRIGGERED__RESULT__KEYPHRASE_TRIGGER,
+ mVoiceInteractionServiceUid);
mExternalCallback.onKeyphraseDetected(recognitionEvent, null);
}
}
@@ -1014,7 +1032,7 @@
HotwordDetectedResult newResult;
try {
newResult =
- mHotwordAudioStreamManager.startCopyingAudioStreams(
+ mHotwordAudioStreamCopier.startCopyingAudioStreams(
triggerResult);
} catch (IOException e) {
// TODO: Write event
diff --git a/services/voiceinteraction/java/com/android/server/voiceinteraction/HotwordMetricsLogger.java b/services/voiceinteraction/java/com/android/server/voiceinteraction/HotwordMetricsLogger.java
index 940aed3..c35d90f 100644
--- a/services/voiceinteraction/java/com/android/server/voiceinteraction/HotwordMetricsLogger.java
+++ b/services/voiceinteraction/java/com/android/server/voiceinteraction/HotwordMetricsLogger.java
@@ -16,6 +16,9 @@
package com.android.server.voiceinteraction;
+import static com.android.internal.util.FrameworkStatsLog.HOTWORD_AUDIO_EGRESS_EVENT_REPORTED__DETECTOR_TYPE__NORMAL_DETECTOR;
+import static com.android.internal.util.FrameworkStatsLog.HOTWORD_AUDIO_EGRESS_EVENT_REPORTED__DETECTOR_TYPE__TRUSTED_DETECTOR_DSP;
+import static com.android.internal.util.FrameworkStatsLog.HOTWORD_AUDIO_EGRESS_EVENT_REPORTED__DETECTOR_TYPE__TRUSTED_DETECTOR_SOFTWARE;
import static com.android.internal.util.FrameworkStatsLog.HOTWORD_DETECTION_SERVICE_INIT_RESULT_REPORTED__DETECTOR_TYPE__NORMAL_DETECTOR;
import static com.android.internal.util.FrameworkStatsLog.HOTWORD_DETECTION_SERVICE_INIT_RESULT_REPORTED__DETECTOR_TYPE__TRUSTED_DETECTOR_DSP;
import static com.android.internal.util.FrameworkStatsLog.HOTWORD_DETECTION_SERVICE_INIT_RESULT_REPORTED__DETECTOR_TYPE__TRUSTED_DETECTOR_SOFTWARE;
@@ -47,6 +50,12 @@
HOTWORD_DETECTION_SERVICE_INIT_RESULT_REPORTED__DETECTOR_TYPE__TRUSTED_DETECTOR_DSP;
private static final int METRICS_INIT_NORMAL_DETECTOR =
HOTWORD_DETECTION_SERVICE_INIT_RESULT_REPORTED__DETECTOR_TYPE__NORMAL_DETECTOR;
+ private static final int AUDIO_EGRESS_DSP_DETECTOR =
+ HOTWORD_AUDIO_EGRESS_EVENT_REPORTED__DETECTOR_TYPE__TRUSTED_DETECTOR_DSP;
+ private static final int AUDIO_EGRESS_SOFTWARE_DETECTOR =
+ HOTWORD_AUDIO_EGRESS_EVENT_REPORTED__DETECTOR_TYPE__TRUSTED_DETECTOR_SOFTWARE;
+ private static final int AUDIO_EGRESS_NORMAL_DETECTOR =
+ HOTWORD_AUDIO_EGRESS_EVENT_REPORTED__DETECTOR_TYPE__NORMAL_DETECTOR;
private HotwordMetricsLogger() {
// Class only contains static utility functions, and should not be instantiated
@@ -64,28 +73,28 @@
/**
* Logs information related to hotword detection service init result.
*/
- public static void writeServiceInitResultEvent(int detectorType, int result) {
+ public static void writeServiceInitResultEvent(int detectorType, int result, int uid) {
int metricsDetectorType = getInitMetricsDetectorType(detectorType);
FrameworkStatsLog.write(FrameworkStatsLog.HOTWORD_DETECTION_SERVICE_INIT_RESULT_REPORTED,
- metricsDetectorType, result);
+ metricsDetectorType, result, uid);
}
/**
* Logs information related to hotword detection service restarting.
*/
- public static void writeServiceRestartEvent(int detectorType, int reason) {
+ public static void writeServiceRestartEvent(int detectorType, int reason, int uid) {
int metricsDetectorType = getRestartMetricsDetectorType(detectorType);
FrameworkStatsLog.write(FrameworkStatsLog.HOTWORD_DETECTION_SERVICE_RESTARTED,
- metricsDetectorType, reason);
+ metricsDetectorType, reason, uid);
}
/**
* Logs information related to keyphrase trigger.
*/
- public static void writeKeyphraseTriggerEvent(int detectorType, int result) {
+ public static void writeKeyphraseTriggerEvent(int detectorType, int result, int uid) {
int metricsDetectorType = getKeyphraseMetricsDetectorType(detectorType);
FrameworkStatsLog.write(FrameworkStatsLog.HOTWORD_DETECTOR_KEYPHRASE_TRIGGERED,
- metricsDetectorType, result);
+ metricsDetectorType, result, uid);
}
/**
@@ -97,6 +106,16 @@
metricsDetectorType, event, uid);
}
+ /**
+ * Logs information related to hotword audio egress events.
+ */
+ public static void writeAudioEgressEvent(int detectorType, int event, int uid,
+ int streamSizeBytes, int bundleSizeBytes, int streamCount) {
+ int metricsDetectorType = getAudioEgressDetectorType(detectorType);
+ FrameworkStatsLog.write(FrameworkStatsLog.HOTWORD_AUDIO_EGRESS_EVENT_REPORTED,
+ metricsDetectorType, event, uid, streamSizeBytes, bundleSizeBytes, streamCount);
+ }
+
private static int getCreateMetricsDetectorType(int detectorType) {
switch (detectorType) {
case HotwordDetector.DETECTOR_TYPE_TRUSTED_HOTWORD_SOFTWARE:
@@ -151,4 +170,15 @@
return HOTWORD_DETECTOR_EVENTS__DETECTOR_TYPE__NORMAL_DETECTOR;
}
}
+
+ private static int getAudioEgressDetectorType(int detectorType) {
+ switch (detectorType) {
+ case HotwordDetector.DETECTOR_TYPE_TRUSTED_HOTWORD_SOFTWARE:
+ return AUDIO_EGRESS_SOFTWARE_DETECTOR;
+ case HotwordDetector.DETECTOR_TYPE_TRUSTED_HOTWORD_DSP:
+ return AUDIO_EGRESS_DSP_DETECTOR;
+ default:
+ return AUDIO_EGRESS_NORMAL_DETECTOR;
+ }
+ }
}
diff --git a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerServiceImpl.java b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerServiceImpl.java
index f90fbb2..c4f341e 100644
--- a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerServiceImpl.java
+++ b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerServiceImpl.java
@@ -711,7 +711,7 @@
Slog.d(TAG, "createSoundTriggerCallbackLocked");
}
return new HotwordDetectionConnection.SoundTriggerCallback(callback,
- mHotwordDetectionConnection);
+ mHotwordDetectionConnection, mInfo.getServiceInfo().applicationInfo.uid);
}
private static ServiceInfo getServiceInfoLocked(@NonNull ComponentName componentName,
@@ -880,5 +880,8 @@
@Override
public void onSessionHidden(VoiceInteractionSessionConnection connection) {
mServiceStub.onSessionHidden();
+ // Notifies visibility change here can cause duplicate events, it is added to make sure
+ // client always get the callback even if session is unexpectedly closed.
+ mServiceStub.setSessionWindowVisible(connection.mToken, false);
}
}
diff --git a/tests/TrustTests/src/android/trust/test/lib/LockStateTrackingRule.kt b/tests/TrustTests/src/android/trust/test/lib/LockStateTrackingRule.kt
index 2031af2..1930a1c 100644
--- a/tests/TrustTests/src/android/trust/test/lib/LockStateTrackingRule.kt
+++ b/tests/TrustTests/src/android/trust/test/lib/LockStateTrackingRule.kt
@@ -63,6 +63,7 @@
inner class Listener : TrustListener {
override fun onTrustChanged(
enabled: Boolean,
+ newlyUnlocked: Boolean,
userId: Int,
flags: Int,
trustGrantedMessages: MutableList<String>