Merge "Update PermissionManager#checkPermission API based on API council feedback" into main
diff --git a/apex/jobscheduler/service/Android.bp b/apex/jobscheduler/service/Android.bp
index 8b55e07..5586295 100644
--- a/apex/jobscheduler/service/Android.bp
+++ b/apex/jobscheduler/service/Android.bp
@@ -28,6 +28,7 @@
static_libs: [
"modules-utils-fastxmlserializer",
+ "service-jobscheduler-alarm.flags-aconfig-java",
"service-jobscheduler-job.flags-aconfig-java",
],
diff --git a/apex/jobscheduler/service/aconfig/Android.bp b/apex/jobscheduler/service/aconfig/Android.bp
index 3ba7aa2..4db39dc 100644
--- a/apex/jobscheduler/service/aconfig/Android.bp
+++ b/apex/jobscheduler/service/aconfig/Android.bp
@@ -27,3 +27,15 @@
aconfig_declarations: "service-job.flags-aconfig",
visibility: ["//frameworks/base:__subpackages__"],
}
+
+// Alarm
+aconfig_declarations {
+ name: "alarm_flags",
+ package: "com.android.server.alarm",
+ srcs: ["alarm.aconfig"],
+}
+
+java_aconfig_library {
+ name: "service-jobscheduler-alarm.flags-aconfig-java",
+ aconfig_declarations: "alarm_flags",
+}
diff --git a/apex/jobscheduler/service/aconfig/alarm.aconfig b/apex/jobscheduler/service/aconfig/alarm.aconfig
new file mode 100644
index 0000000..3b9b4e7
--- /dev/null
+++ b/apex/jobscheduler/service/aconfig/alarm.aconfig
@@ -0,0 +1,11 @@
+package: "com.android.server.alarm"
+
+flag {
+ name: "use_frozen_state_to_drop_listener_alarms"
+ namespace: "backstage_power"
+ description: "Use frozen state callback to drop listener alarms for cached apps"
+ bug: "324470945"
+ metadata {
+ purpose: PURPOSE_BUGFIX
+ }
+}
diff --git a/apex/jobscheduler/service/java/com/android/server/DeviceIdleController.java b/apex/jobscheduler/service/java/com/android/server/DeviceIdleController.java
index 696c317..4832ea6 100644
--- a/apex/jobscheduler/service/java/com/android/server/DeviceIdleController.java
+++ b/apex/jobscheduler/service/java/com/android/server/DeviceIdleController.java
@@ -893,8 +893,9 @@
}
// Fall through when quick doze is not requested.
- if (!mIsOffBody) {
- // Quick doze was not requested and device is on body so turn the device active.
+ if (!mIsOffBody && !mForceIdle) {
+ // Quick doze wasn't requested, doze wasn't forced and device is on body
+ // so turn the device active.
mActiveReason = ACTIVE_REASON_ONBODY;
becomeActiveLocked("on_body", Process.myUid());
}
diff --git a/apex/jobscheduler/service/java/com/android/server/alarm/AlarmManagerService.java b/apex/jobscheduler/service/java/com/android/server/alarm/AlarmManagerService.java
index 39de0af..e728a2c 100644
--- a/apex/jobscheduler/service/java/com/android/server/alarm/AlarmManagerService.java
+++ b/apex/jobscheduler/service/java/com/android/server/alarm/AlarmManagerService.java
@@ -16,6 +16,7 @@
package com.android.server.alarm;
+import static android.app.ActivityManager.UidFrozenStateChangedCallback.UID_FROZEN_STATE_FROZEN;
import static android.app.ActivityManagerInternal.ALLOW_NON_FULL;
import static android.app.AlarmManager.ELAPSED_REALTIME;
import static android.app.AlarmManager.ELAPSED_REALTIME_WAKEUP;
@@ -75,6 +76,7 @@
import android.annotation.SuppressLint;
import android.annotation.UserIdInt;
import android.app.Activity;
+import android.app.ActivityManager;
import android.app.ActivityManagerInternal;
import android.app.ActivityOptions;
import android.app.AlarmManager;
@@ -103,6 +105,7 @@
import android.os.Build;
import android.os.Bundle;
import android.os.Handler;
+import android.os.HandlerExecutor;
import android.os.IBinder;
import android.os.Looper;
import android.os.Message;
@@ -145,6 +148,7 @@
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.app.IAppOpsCallback;
import com.android.internal.app.IAppOpsService;
+import com.android.internal.util.ArrayUtils;
import com.android.internal.util.DumpUtils;
import com.android.internal.util.FrameworkStatsLog;
import com.android.internal.util.LocalLog;
@@ -293,6 +297,7 @@
private final Injector mInjector;
int mBroadcastRefCount = 0;
+ boolean mUseFrozenStateToDropListenerAlarms;
MetricsHelper mMetricsHelper;
PowerManager.WakeLock mWakeLock;
SparseIntArray mAlarmsPerUid = new SparseIntArray();
@@ -1856,15 +1861,47 @@
@Override
public void onStart() {
mInjector.init();
+ mHandler = new AlarmHandler();
+
mOptsWithFgs.setPendingIntentBackgroundActivityLaunchAllowed(false);
mOptsWithFgsForAlarmClock.setPendingIntentBackgroundActivityLaunchAllowed(false);
mOptsWithoutFgs.setPendingIntentBackgroundActivityLaunchAllowed(false);
mOptsTimeBroadcast.setPendingIntentBackgroundActivityLaunchAllowed(false);
mActivityOptsRestrictBal.setPendingIntentBackgroundActivityLaunchAllowed(false);
mBroadcastOptsRestrictBal.setPendingIntentBackgroundActivityLaunchAllowed(false);
+
mMetricsHelper = new MetricsHelper(getContext(), mLock);
mActivityManagerInternal = LocalServices.getService(ActivityManagerInternal.class);
+ mUseFrozenStateToDropListenerAlarms = Flags.useFrozenStateToDropListenerAlarms();
+ if (mUseFrozenStateToDropListenerAlarms) {
+ final ActivityManager.UidFrozenStateChangedCallback callback = (uids, frozenStates) -> {
+ final int size = frozenStates.length;
+ if (uids.length != size) {
+ Slog.wtf(TAG, "Got different length arrays in frozen state callback!"
+ + " uids.length: " + uids.length + " frozenStates.length: " + size);
+ // Cannot process received data in any meaningful way.
+ return;
+ }
+ final IntArray affectedUids = new IntArray();
+ for (int i = 0; i < size; i++) {
+ if (frozenStates[i] != UID_FROZEN_STATE_FROZEN) {
+ continue;
+ }
+ if (!CompatChanges.isChangeEnabled(EXACT_LISTENER_ALARMS_DROPPED_ON_CACHED,
+ uids[i])) {
+ continue;
+ }
+ affectedUids.add(uids[i]);
+ }
+ if (affectedUids.size() > 0) {
+ removeExactListenerAlarms(affectedUids.toArray());
+ }
+ };
+ final ActivityManager am = getContext().getSystemService(ActivityManager.class);
+ am.registerUidFrozenStateChangedCallback(new HandlerExecutor(mHandler), callback);
+ }
+
mListenerDeathRecipient = new IBinder.DeathRecipient() {
@Override
public void binderDied() {
@@ -1880,7 +1917,6 @@
};
synchronized (mLock) {
- mHandler = new AlarmHandler();
mConstants = new Constants(mHandler);
mAlarmStore = new LazyAlarmStore();
@@ -1960,6 +1996,21 @@
publishBinderService(Context.ALARM_SERVICE, mService);
}
+ private void removeExactListenerAlarms(int... whichUids) {
+ synchronized (mLock) {
+ removeAlarmsInternalLocked(a -> {
+ if (!ArrayUtils.contains(whichUids, a.uid) || a.listener == null
+ || a.windowLength != 0) {
+ return false;
+ }
+ Slog.w(TAG, "Alarm " + a.listenerTag + " being removed for "
+ + UserHandle.formatUid(a.uid) + ":" + a.packageName
+ + " because the app got frozen");
+ return true;
+ }, REMOVE_REASON_LISTENER_CACHED);
+ }
+ }
+
void refreshExactAlarmCandidates() {
final String[] candidates = mLocalPermissionManager.getAppOpPermissionPackages(
Manifest.permission.SCHEDULE_EXACT_ALARM);
@@ -3074,6 +3125,14 @@
mConstants.dump(pw);
pw.println();
+ pw.println("Feature Flags:");
+ pw.increaseIndent();
+ pw.print(Flags.FLAG_USE_FROZEN_STATE_TO_DROP_LISTENER_ALARMS,
+ mUseFrozenStateToDropListenerAlarms);
+ pw.decreaseIndent();
+ pw.println();
+ pw.println();
+
if (mConstants.USE_TARE_POLICY == EconomyManager.ENABLED_MODE_ON) {
pw.println("TARE details:");
pw.increaseIndent();
@@ -4959,18 +5018,7 @@
break;
case REMOVE_EXACT_LISTENER_ALARMS_ON_CACHED:
uid = (Integer) msg.obj;
- synchronized (mLock) {
- removeAlarmsInternalLocked(a -> {
- if (a.uid != uid || a.listener == null || a.windowLength != 0) {
- return false;
- }
- // TODO (b/265195908): Change to .w once we have some data on breakages.
- Slog.wtf(TAG, "Alarm " + a.listenerTag + " being removed for "
- + UserHandle.formatUid(a.uid) + ":" + a.packageName
- + " because the app went into cached state");
- return true;
- }, REMOVE_REASON_LISTENER_CACHED);
- }
+ removeExactListenerAlarms(uid);
break;
default:
// nope, just ignore it
@@ -5322,6 +5370,10 @@
@Override
public void handleUidCachedChanged(int uid, boolean cached) {
+ if (mUseFrozenStateToDropListenerAlarms) {
+ // Use ActivityManager#UidFrozenStateChangedCallback instead.
+ return;
+ }
if (!CompatChanges.isChangeEnabled(EXACT_LISTENER_ALARMS_DROPPED_ON_CACHED, uid)) {
return;
}
diff --git a/core/api/current.txt b/core/api/current.txt
index e8eace1..386396c 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -10733,6 +10733,7 @@
field public static final String DROPBOX_SERVICE = "dropbox";
field public static final String EUICC_SERVICE = "euicc";
field public static final String FILE_INTEGRITY_SERVICE = "file_integrity";
+ field public static final String FINGERPRINT_SERVICE = "fingerprint";
field public static final String GAME_SERVICE = "game";
field public static final String GRAMMATICAL_INFLECTION_SERVICE = "grammatical_inflection";
field public static final String HARDWARE_PROPERTIES_SERVICE = "hardware_properties";
@@ -18053,6 +18054,24 @@
method public android.graphics.pdf.PdfDocument.PageInfo.Builder setContentRect(android.graphics.Rect);
}
+ public final class PdfRenderer implements java.lang.AutoCloseable {
+ ctor public PdfRenderer(@NonNull android.os.ParcelFileDescriptor) throws java.io.IOException;
+ method public void close();
+ method public int getPageCount();
+ method public android.graphics.pdf.PdfRenderer.Page openPage(int);
+ method public boolean shouldScaleForPrinting();
+ }
+
+ public final class PdfRenderer.Page implements java.lang.AutoCloseable {
+ method public void close();
+ method public int getHeight();
+ method public int getIndex();
+ method public int getWidth();
+ method public void render(@NonNull android.graphics.Bitmap, @Nullable android.graphics.Rect, @Nullable android.graphics.Matrix, int);
+ field public static final int RENDER_MODE_FOR_DISPLAY = 1; // 0x1
+ field public static final int RENDER_MODE_FOR_PRINT = 2; // 0x2
+ }
+
}
package android.graphics.text {
@@ -20363,6 +20382,54 @@
}
+package android.hardware.fingerprint {
+
+ @Deprecated public class FingerprintManager {
+ method @Deprecated @RequiresPermission(anyOf={android.Manifest.permission.USE_BIOMETRIC, android.Manifest.permission.USE_FINGERPRINT}) public void authenticate(@Nullable android.hardware.fingerprint.FingerprintManager.CryptoObject, @Nullable android.os.CancellationSignal, int, @NonNull android.hardware.fingerprint.FingerprintManager.AuthenticationCallback, @Nullable android.os.Handler);
+ method @Deprecated @RequiresPermission(android.Manifest.permission.USE_FINGERPRINT) public boolean hasEnrolledFingerprints();
+ method @Deprecated @RequiresPermission(android.Manifest.permission.USE_FINGERPRINT) public boolean isHardwareDetected();
+ field public static final int FINGERPRINT_ACQUIRED_GOOD = 0; // 0x0
+ field public static final int FINGERPRINT_ACQUIRED_IMAGER_DIRTY = 3; // 0x3
+ field public static final int FINGERPRINT_ACQUIRED_INSUFFICIENT = 2; // 0x2
+ field public static final int FINGERPRINT_ACQUIRED_PARTIAL = 1; // 0x1
+ field public static final int FINGERPRINT_ACQUIRED_TOO_FAST = 5; // 0x5
+ field public static final int FINGERPRINT_ACQUIRED_TOO_SLOW = 4; // 0x4
+ field public static final int FINGERPRINT_ERROR_CANCELED = 5; // 0x5
+ field public static final int FINGERPRINT_ERROR_HW_NOT_PRESENT = 12; // 0xc
+ field public static final int FINGERPRINT_ERROR_HW_UNAVAILABLE = 1; // 0x1
+ field public static final int FINGERPRINT_ERROR_LOCKOUT = 7; // 0x7
+ field public static final int FINGERPRINT_ERROR_LOCKOUT_PERMANENT = 9; // 0x9
+ field public static final int FINGERPRINT_ERROR_NO_FINGERPRINTS = 11; // 0xb
+ field public static final int FINGERPRINT_ERROR_NO_SPACE = 4; // 0x4
+ field public static final int FINGERPRINT_ERROR_TIMEOUT = 3; // 0x3
+ field public static final int FINGERPRINT_ERROR_UNABLE_TO_PROCESS = 2; // 0x2
+ field public static final int FINGERPRINT_ERROR_USER_CANCELED = 10; // 0xa
+ field public static final int FINGERPRINT_ERROR_VENDOR = 8; // 0x8
+ }
+
+ @Deprecated public abstract static class FingerprintManager.AuthenticationCallback {
+ ctor @Deprecated public FingerprintManager.AuthenticationCallback();
+ method @Deprecated public void onAuthenticationError(int, CharSequence);
+ method @Deprecated public void onAuthenticationFailed();
+ method @Deprecated public void onAuthenticationHelp(int, CharSequence);
+ method @Deprecated public void onAuthenticationSucceeded(android.hardware.fingerprint.FingerprintManager.AuthenticationResult);
+ }
+
+ @Deprecated public static class FingerprintManager.AuthenticationResult {
+ method @Deprecated public android.hardware.fingerprint.FingerprintManager.CryptoObject getCryptoObject();
+ }
+
+ @Deprecated public static final class FingerprintManager.CryptoObject {
+ ctor @Deprecated public FingerprintManager.CryptoObject(@NonNull java.security.Signature);
+ ctor @Deprecated public FingerprintManager.CryptoObject(@NonNull javax.crypto.Cipher);
+ ctor @Deprecated public FingerprintManager.CryptoObject(@NonNull javax.crypto.Mac);
+ method @Deprecated public javax.crypto.Cipher getCipher();
+ method @Deprecated public javax.crypto.Mac getMac();
+ method @Deprecated public java.security.Signature getSignature();
+ }
+
+}
+
package android.hardware.input {
public final class HostUsiVersion implements android.os.Parcelable {
@@ -56279,7 +56346,7 @@
method public boolean acceptStylusHandwritingDelegation(@NonNull android.view.View);
method public boolean acceptStylusHandwritingDelegation(@NonNull android.view.View, @NonNull String);
method @FlaggedApi("android.view.inputmethod.use_zero_jank_proxy") public void acceptStylusHandwritingDelegation(@NonNull android.view.View, @NonNull String, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<java.lang.Boolean>);
- method @FlaggedApi("android.view.inputmethod.home_screen_handwriting_delegator") public boolean acceptStylusHandwritingDelegation(@NonNull android.view.View, @NonNull String, int);
+ method @FlaggedApi("android.view.inputmethod.home_screen_handwriting_delegator") public void acceptStylusHandwritingDelegation(@NonNull android.view.View, @NonNull String, int, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<java.lang.Boolean>);
method public void dispatchKeyEventFromInputMethod(@Nullable android.view.View, @NonNull android.view.KeyEvent);
method public void displayCompletions(android.view.View, android.view.inputmethod.CompletionInfo[]);
method @Nullable public android.view.inputmethod.InputMethodInfo getCurrentInputMethodInfo();
diff --git a/core/api/removed.txt b/core/api/removed.txt
index c61f163..3c7c0d6 100644
--- a/core/api/removed.txt
+++ b/core/api/removed.txt
@@ -35,7 +35,6 @@
method @Deprecated @Nullable public String getFeatureId();
method public abstract android.content.SharedPreferences getSharedPreferences(java.io.File, int);
method public abstract java.io.File getSharedPreferencesPath(String);
- field public static final String FINGERPRINT_SERVICE = "fingerprint";
}
public class ContextWrapper extends android.content.Context {
@@ -146,54 +145,6 @@
}
-package android.hardware.fingerprint {
-
- @Deprecated public class FingerprintManager {
- method @Deprecated @RequiresPermission(anyOf={android.Manifest.permission.USE_BIOMETRIC, android.Manifest.permission.USE_FINGERPRINT}) public void authenticate(@Nullable android.hardware.fingerprint.FingerprintManager.CryptoObject, @Nullable android.os.CancellationSignal, int, @NonNull android.hardware.fingerprint.FingerprintManager.AuthenticationCallback, @Nullable android.os.Handler);
- method @Deprecated @RequiresPermission(android.Manifest.permission.USE_FINGERPRINT) public boolean hasEnrolledFingerprints();
- method @Deprecated @RequiresPermission(android.Manifest.permission.USE_FINGERPRINT) public boolean isHardwareDetected();
- field public static final int FINGERPRINT_ACQUIRED_GOOD = 0; // 0x0
- field public static final int FINGERPRINT_ACQUIRED_IMAGER_DIRTY = 3; // 0x3
- field public static final int FINGERPRINT_ACQUIRED_INSUFFICIENT = 2; // 0x2
- field public static final int FINGERPRINT_ACQUIRED_PARTIAL = 1; // 0x1
- field public static final int FINGERPRINT_ACQUIRED_TOO_FAST = 5; // 0x5
- field public static final int FINGERPRINT_ACQUIRED_TOO_SLOW = 4; // 0x4
- field public static final int FINGERPRINT_ERROR_CANCELED = 5; // 0x5
- field public static final int FINGERPRINT_ERROR_HW_NOT_PRESENT = 12; // 0xc
- field public static final int FINGERPRINT_ERROR_HW_UNAVAILABLE = 1; // 0x1
- field public static final int FINGERPRINT_ERROR_LOCKOUT = 7; // 0x7
- field public static final int FINGERPRINT_ERROR_LOCKOUT_PERMANENT = 9; // 0x9
- field public static final int FINGERPRINT_ERROR_NO_FINGERPRINTS = 11; // 0xb
- field public static final int FINGERPRINT_ERROR_NO_SPACE = 4; // 0x4
- field public static final int FINGERPRINT_ERROR_TIMEOUT = 3; // 0x3
- field public static final int FINGERPRINT_ERROR_UNABLE_TO_PROCESS = 2; // 0x2
- field public static final int FINGERPRINT_ERROR_USER_CANCELED = 10; // 0xa
- field public static final int FINGERPRINT_ERROR_VENDOR = 8; // 0x8
- }
-
- @Deprecated public abstract static class FingerprintManager.AuthenticationCallback {
- ctor public FingerprintManager.AuthenticationCallback();
- method public void onAuthenticationError(int, CharSequence);
- method public void onAuthenticationFailed();
- method public void onAuthenticationHelp(int, CharSequence);
- method public void onAuthenticationSucceeded(android.hardware.fingerprint.FingerprintManager.AuthenticationResult);
- }
-
- @Deprecated public static class FingerprintManager.AuthenticationResult {
- method public android.hardware.fingerprint.FingerprintManager.CryptoObject getCryptoObject();
- }
-
- @Deprecated public static final class FingerprintManager.CryptoObject {
- ctor public FingerprintManager.CryptoObject(@NonNull java.security.Signature);
- ctor public FingerprintManager.CryptoObject(@NonNull javax.crypto.Cipher);
- ctor public FingerprintManager.CryptoObject(@NonNull javax.crypto.Mac);
- method public javax.crypto.Cipher getCipher();
- method public javax.crypto.Mac getMac();
- method public java.security.Signature getSignature();
- }
-
-}
-
package android.media {
public final class AudioFormat implements android.os.Parcelable {
diff --git a/core/api/system-current.txt b/core/api/system-current.txt
index 31f44ac..062bdaa 100644
--- a/core/api/system-current.txt
+++ b/core/api/system-current.txt
@@ -1146,7 +1146,6 @@
public static final class StatusBarManager.DisableInfo implements android.os.Parcelable {
method public boolean areAllComponentsEnabled();
method public int describeContents();
- method public boolean isBackDisabled();
method public boolean isNavigateToHomeDisabled();
method public boolean isNotificationPeekingDisabled();
method public boolean isRecentsDisabled();
@@ -14204,7 +14203,8 @@
method @Deprecated public java.util.List<android.telecom.PhoneAccountHandle> getPhoneAccountsForPackage();
method @RequiresPermission(anyOf={android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE, android.Manifest.permission.READ_PHONE_STATE}) public java.util.List<android.telecom.PhoneAccountHandle> getPhoneAccountsSupportingScheme(String);
method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public boolean isInEmergencyCall();
- method @FlaggedApi("com.android.server.telecom.flags.telecom_resolve_hidden_dependencies") @RequiresPermission(allOf={android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE, android.Manifest.permission.INTERACT_ACROSS_USERS}, conditional=true) public boolean isInSelfManagedCall(@NonNull String, @NonNull android.os.UserHandle, boolean);
+ method @FlaggedApi("com.android.server.telecom.flags.telecom_resolve_hidden_dependencies") @RequiresPermission(allOf={android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE, android.Manifest.permission.INTERACT_ACROSS_USERS}, conditional=true) public boolean isInSelfManagedCall(@NonNull String, @NonNull android.os.UserHandle);
+ method @FlaggedApi("com.android.server.telecom.flags.telecom_resolve_hidden_dependencies") @RequiresPermission(allOf={android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE, android.Manifest.permission.INTERACT_ACROSS_USERS}, conditional=true) public boolean isInSelfManagedCall(@NonNull String, boolean);
method @RequiresPermission(anyOf={android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE, android.Manifest.permission.READ_PHONE_STATE}) public boolean isRinging();
method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void setUserSelectedOutgoingPhoneAccount(@Nullable android.telecom.PhoneAccountHandle);
field public static final String ACTION_CURRENT_TTY_MODE_CHANGED = "android.telecom.action.CURRENT_TTY_MODE_CHANGED";
diff --git a/core/api/test-current.txt b/core/api/test-current.txt
index e1e9d09..cdf232c 100644
--- a/core/api/test-current.txt
+++ b/core/api/test-current.txt
@@ -1711,6 +1711,15 @@
}
+package android.hardware.fingerprint {
+
+ @Deprecated public class FingerprintManager {
+ method @Deprecated @NonNull @RequiresPermission(android.Manifest.permission.TEST_BIOMETRIC) public android.hardware.biometrics.BiometricTestSession createTestSession(int);
+ method @Deprecated @NonNull @RequiresPermission(android.Manifest.permission.TEST_BIOMETRIC) public java.util.List<android.hardware.biometrics.SensorProperties> getSensorProperties();
+ }
+
+}
+
package android.hardware.hdmi {
public final class HdmiControlServiceWrapper {
diff --git a/core/api/test-removed.txt b/core/api/test-removed.txt
index 2e44176..d802177 100644
--- a/core/api/test-removed.txt
+++ b/core/api/test-removed.txt
@@ -1,10 +1 @@
// Signature format: 2.0
-package android.hardware.fingerprint {
-
- @Deprecated public class FingerprintManager {
- method @NonNull @RequiresPermission(android.Manifest.permission.TEST_BIOMETRIC) public android.hardware.biometrics.BiometricTestSession createTestSession(int);
- method @NonNull @RequiresPermission(android.Manifest.permission.TEST_BIOMETRIC) public java.util.List<android.hardware.biometrics.SensorProperties> getSensorProperties();
- }
-
-}
-
diff --git a/core/java/Android.bp b/core/java/Android.bp
index eba500d..34b8a87 100644
--- a/core/java/Android.bp
+++ b/core/java/Android.bp
@@ -126,6 +126,7 @@
srcs: [
"android/os/IExternalVibrationController.aidl",
"android/os/IExternalVibratorService.aidl",
+ "android/os/ExternalVibrationScale.aidl",
],
}
diff --git a/core/java/android/app/AppOpsManager.java b/core/java/android/app/AppOpsManager.java
index 16c7753..39900a0 100644
--- a/core/java/android/app/AppOpsManager.java
+++ b/core/java/android/app/AppOpsManager.java
@@ -74,6 +74,7 @@
import android.os.UserManager;
import android.permission.PermissionGroupUsage;
import android.permission.PermissionUsageHelper;
+import android.permission.flags.Flags;
import android.provider.DeviceConfig;
import android.util.ArrayMap;
import android.util.ArraySet;
@@ -99,7 +100,6 @@
import com.android.internal.util.FrameworkStatsLog;
import com.android.internal.util.Parcelling;
import com.android.internal.util.Preconditions;
-import com.android.media.flags.Flags;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
@@ -2077,7 +2077,8 @@
* @hide
*/
@SystemApi
- @FlaggedApi(Flags.FLAG_ENABLE_PRIVILEGED_ROUTING_FOR_MEDIA_ROUTING_CONTROL)
+ @FlaggedApi(com.android.media.flags.Flags
+ .FLAG_ENABLE_PRIVILEGED_ROUTING_FOR_MEDIA_ROUTING_CONTROL)
public static final String OPSTR_MEDIA_ROUTING_CONTROL = "android:media_routing_control";
/**
@@ -2243,7 +2244,7 @@
*
* @hide
*/
- @FlaggedApi(android.permission.flags.Flags.FLAG_ENHANCED_CONFIRMATION_MODE_APIS_ENABLED)
+ @FlaggedApi(Flags.FLAG_ENHANCED_CONFIRMATION_MODE_APIS_ENABLED)
@SystemApi
public static final String OPSTR_ACCESS_RESTRICTED_SETTINGS =
"android:access_restricted_settings";
@@ -3432,7 +3433,8 @@
}
}
- public static final @android.annotation.NonNull Creator<PackageOps> CREATOR = new Creator<PackageOps>() {
+ public static final @android.annotation.NonNull Creator<PackageOps> CREATOR =
+ new Creator<PackageOps>() {
@Override public PackageOps createFromParcel(Parcel source) {
return new PackageOps(source);
}
@@ -7410,7 +7412,7 @@
* @param userId User id of the app whose Op changed.
* @param persistentDeviceId persistent device id whose Op changed.
*/
- @FlaggedApi(android.permission.flags.Flags.FLAG_DEVICE_AWARE_PERMISSION_APIS_ENABLED)
+ @FlaggedApi(Flags.FLAG_DEVICE_AWARE_PERMISSION_APIS_ENABLED)
default void onOpChanged(@NonNull String op, @NonNull String packageName, int userId,
@NonNull String persistentDeviceId) {
if (Objects.equals(persistentDeviceId,
@@ -7480,7 +7482,7 @@
* @param attributionFlags the attribution flags for this operation.
* @param attributionChainId the unique id of the attribution chain this op is a part of.
*/
- @FlaggedApi(android.permission.flags.Flags.FLAG_DEVICE_AWARE_PERMISSION_APIS_ENABLED)
+ @FlaggedApi(Flags.FLAG_DEVICE_AWARE_PERMISSION_APIS_ENABLED)
default void onOpActiveChanged(@NonNull String op, int uid, @NonNull String packageName,
@Nullable String attributionTag, int virtualDeviceId, boolean active,
@AttributionFlags int attributionFlags, int attributionChainId) {
@@ -7534,7 +7536,7 @@
* @param flags The flags of this op
* @param result The result of the note.
*/
- @FlaggedApi(android.permission.flags.Flags.FLAG_DEVICE_AWARE_PERMISSION_APIS_ENABLED)
+ @FlaggedApi(Flags.FLAG_DEVICE_AWARE_PERMISSION_APIS_ENABLED)
default void onOpNoted(@NonNull String op, int uid, @NonNull String packageName,
@Nullable String attributionTag, int virtualDeviceId, @OpFlags int flags,
@Mode int result) {
@@ -8361,14 +8363,26 @@
String attributionTag, int virtualDeviceId, boolean active,
@AttributionFlags int attributionFlags, int attributionChainId) {
executor.execute(() -> {
- if (callback instanceof OnOpActiveChangedInternalListener) {
- ((OnOpActiveChangedInternalListener) callback).onOpActiveChanged(op,
- uid, packageName, virtualDeviceId, active);
- }
- if (sAppOpInfos[op].name != null) {
- callback.onOpActiveChanged(sAppOpInfos[op].name, uid, packageName,
- attributionTag, virtualDeviceId, active, attributionFlags,
- attributionChainId);
+ if (Flags.deviceAwarePermissionApisEnabled()) {
+ if (callback instanceof OnOpActiveChangedInternalListener) {
+ ((OnOpActiveChangedInternalListener) callback).onOpActiveChanged(op,
+ uid, packageName, virtualDeviceId, active);
+ }
+ if (sAppOpInfos[op].name != null) {
+ callback.onOpActiveChanged(sAppOpInfos[op].name, uid, packageName,
+ attributionTag, virtualDeviceId, active, attributionFlags,
+ attributionChainId);
+ }
+ } else {
+ if (callback instanceof OnOpActiveChangedInternalListener) {
+ ((OnOpActiveChangedInternalListener) callback).onOpActiveChanged(op,
+ uid, packageName, active);
+ }
+ if (sAppOpInfos[op].name != null) {
+ callback.onOpActiveChanged(sAppOpInfos[op].name, uid, packageName,
+ attributionTag, active, attributionFlags,
+ attributionChainId);
+ }
}
});
}
@@ -8613,9 +8627,13 @@
try {
executor.execute(() -> {
if (sAppOpInfos[op].name != null) {
- listener.onOpNoted(sAppOpInfos[op].name, uid, packageName,
- attributionTag, virtualDeviceId,
- flags, mode);
+ if (Flags.deviceAwarePermissionApisEnabled()) {
+ listener.onOpNoted(sAppOpInfos[op].name, uid, packageName,
+ attributionTag, virtualDeviceId, flags, mode);
+ } else {
+ listener.onOpNoted(sAppOpInfos[op].name, uid, packageName,
+ attributionTag, flags, mode);
+ }
}
});
} finally {
diff --git a/core/java/android/app/StatusBarManager.java b/core/java/android/app/StatusBarManager.java
index e6e46dd..301fef8 100644
--- a/core/java/android/app/StatusBarManager.java
+++ b/core/java/android/app/StatusBarManager.java
@@ -1448,7 +1448,6 @@
*
* @hide
*/
- @SystemApi
public boolean isBackDisabled() {
return mBack;
}
@@ -1862,38 +1861,38 @@
};
@DataClass.Generated(
- time = 1707345957771L,
+ time = 1708625947132L,
codegenVersion = "1.0.23",
sourceFile = "frameworks/base/core/java/android/app/StatusBarManager.java",
- inputSignatures = "private boolean mStatusBarExpansion\nprivate "
- + "boolean mNavigateHome\nprivate boolean mNotificationPeeking\nprivate "
- + "boolean mRecents\nprivate boolean mBack\nprivate boolean "
- + "mSearch\nprivate boolean mSystemIcons\nprivate boolean mClock\nprivate"
- + " boolean mNotificationIcons\nprivate boolean mRotationSuggestion\n"
+ inputSignatures = "private boolean mStatusBarExpansion\nprivate boolean "
+ + "mNavigateHome\nprivate boolean mNotificationPeeking\nprivate "
+ + "boolean mRecents\nprivate boolean mBack\nprivate boolean mSearch\n"
+ + "private boolean mSystemIcons\nprivate boolean mClock\nprivate "
+ + "boolean mNotificationIcons\nprivate boolean mRotationSuggestion\n"
+ "private boolean mNotificationTicker\npublic "
+ "@android.annotation.SystemApi boolean isStatusBarExpansionDisabled()\n"
+ "public void setStatusBarExpansionDisabled(boolean)\npublic "
- + "@android.annotation.SystemApi boolean isNavigateToHomeDisabled()\n"
- + "public void setNavigationHomeDisabled(boolean)\npublic "
- + "@android.annotation.SystemApi boolean isNotificationPeekingDisabled()\n"
- + "public void setNotificationPeekingDisabled(boolean)\npublic "
+ + "@android.annotation.SystemApi boolean isNavigateToHomeDisabled()\npublic"
+ + " void setNavigationHomeDisabled(boolean)\npublic "
+ + "@android.annotation.SystemApi boolean isNotificationPeekingDisabled()"
+ + "\npublic void setNotificationPeekingDisabled(boolean)\npublic "
+ "@android.annotation.SystemApi boolean isRecentsDisabled()\npublic "
- + "void setRecentsDisabled(boolean)\npublic @android.annotation.SystemApi "
- + "boolean isBackDisabled()\npublic void setBackDisabled(boolean)\npublic "
+ + "void setRecentsDisabled(boolean)\npublic boolean isBackDisabled()"
+ + "\npublic void setBackDisabled(boolean)\npublic "
+ "@android.annotation.SystemApi boolean isSearchDisabled()\npublic "
+ "void setSearchDisabled(boolean)\npublic boolean "
- + "areSystemIconsDisabled()\npublic void setSystemIconsDisabled(boolean)"
- + "\npublic boolean isClockDisabled()\npublic "
- + "void setClockDisabled(boolean)\npublic "
- + "boolean areNotificationIconsDisabled()\npublic "
- + "void setNotificationIconsDisabled(boolean)\npublic boolean "
+ + "areSystemIconsDisabled()\npublic void setSystemIconsDisabled(boolean)\n"
+ + "public boolean isClockDisabled()\npublic "
+ + "void setClockDisabled(boolean)\npublic boolean "
+ + "areNotificationIconsDisabled()\npublic void "
+ + "setNotificationIconsDisabled(boolean)\npublic boolean "
+ "isNotificationTickerDisabled()\npublic void "
+ "setNotificationTickerDisabled(boolean)\npublic "
+ "@android.annotation.TestApi boolean isRotationSuggestionDisabled()\n"
+ "public void setRotationSuggestionDisabled(boolean)\npublic "
- + "@android.annotation.SystemApi boolean areAllComponentsEnabled()\n"
- + "public void setEnableAll()\npublic boolean areAllComponentsDisabled()"
- + "\npublic void setDisableAll()\npublic @android.annotation.NonNull "
+ + "@android.annotation.SystemApi boolean areAllComponentsEnabled()\npublic"
+ + " void setEnableAll()\npublic boolean areAllComponentsDisabled()\n"
+ + "public void setDisableAll()\npublic @android.annotation.NonNull "
+ "@java.lang.Override java.lang.String toString()\npublic "
+ "android.util.Pair<java.lang.Integer,java.lang.Integer> toFlags()\n"
+ "class DisableInfo extends java.lang.Object implements "
diff --git a/core/java/android/app/admin/DevicePolicyCache.java b/core/java/android/app/admin/DevicePolicyCache.java
index 29f657e..16cb4ec 100644
--- a/core/java/android/app/admin/DevicePolicyCache.java
+++ b/core/java/android/app/admin/DevicePolicyCache.java
@@ -15,6 +15,9 @@
*/
package android.app.admin;
+import static android.app.admin.DevicePolicyManager.CONTENT_PROTECTION_DISABLED;
+import static android.app.admin.DevicePolicyManager.ContentProtectionPolicy;
+
import android.annotation.UserIdInt;
import com.android.server.LocalServices;
@@ -59,6 +62,12 @@
public abstract int getPermissionPolicy(@UserIdInt int userHandle);
/**
+ * Caches {@link DevicePolicyManager#getContentProtectionPolicy(android.content.ComponentName)}
+ * of the given user.
+ */
+ public abstract @ContentProtectionPolicy int getContentProtectionPolicy(@UserIdInt int userId);
+
+ /**
* True if there is an admin on the device who can grant sensor permissions.
*/
public abstract boolean canAdminGrantSensorsPermissions();
@@ -92,6 +101,11 @@
}
@Override
+ public @ContentProtectionPolicy int getContentProtectionPolicy(@UserIdInt int userId) {
+ return CONTENT_PROTECTION_DISABLED;
+ }
+
+ @Override
public boolean canAdminGrantSensorsPermissions() {
return false;
}
diff --git a/core/java/android/content/Context.java b/core/java/android/content/Context.java
index 7505372..f77ebc1 100644
--- a/core/java/android/content/Context.java
+++ b/core/java/android/content/Context.java
@@ -5068,7 +5068,6 @@
* {@link android.hardware.fingerprint.FingerprintManager} for handling management
* of fingerprints.
*
- * @removed See {@link android.hardware.biometrics.BiometricPrompt}
* @see #getSystemService(String)
* @see android.hardware.fingerprint.FingerprintManager
*/
diff --git a/core/java/android/hardware/fingerprint/FingerprintManager.java b/core/java/android/hardware/fingerprint/FingerprintManager.java
index b0f69f5..81e321d 100644
--- a/core/java/android/hardware/fingerprint/FingerprintManager.java
+++ b/core/java/android/hardware/fingerprint/FingerprintManager.java
@@ -83,8 +83,7 @@
/**
* A class that coordinates access to the fingerprint hardware.
- *
- * @removed See {@link BiometricPrompt} which shows a system-provided dialog upon starting
+ * @deprecated See {@link BiometricPrompt} which shows a system-provided dialog upon starting
* authentication. In a world where devices may have different types of biometric authentication,
* it's much more realistic to have a system-provided authentication dialog since the method may
* vary by vendor/device.
@@ -95,6 +94,7 @@
@RequiresFeature(PackageManager.FEATURE_FINGERPRINT)
public class FingerprintManager implements BiometricAuthenticator, BiometricFingerprintConstants {
private static final String TAG = "FingerprintManager";
+ private static final boolean DEBUG = true;
private static final int MSG_ENROLL_RESULT = 100;
private static final int MSG_ACQUIRED = 101;
private static final int MSG_AUTHENTICATION_SUCCEEDED = 102;
@@ -196,7 +196,6 @@
/**
* Retrieves a test session for FingerprintManager.
- *
* @hide
*/
@TestApi
@@ -255,10 +254,9 @@
}
/**
- * A wrapper class for the crypto objects supported by FingerprintManager. Currently, the
+ * A wrapper class for the crypto objects supported by FingerprintManager. Currently the
* framework supports {@link Signature}, {@link Cipher} and {@link Mac} objects.
- *
- * @removed See {@link android.hardware.biometrics.BiometricPrompt.CryptoObject}
+ * @deprecated See {@link android.hardware.biometrics.BiometricPrompt.CryptoObject}
*/
@Deprecated
public static final class CryptoObject extends android.hardware.biometrics.CryptoObject {
@@ -332,8 +330,7 @@
/**
* Container for callback data from {@link FingerprintManager#authenticate(CryptoObject,
* CancellationSignal, int, AuthenticationCallback, Handler)}.
- *
- * @removed See {@link android.hardware.biometrics.BiometricPrompt.AuthenticationResult}
+ * @deprecated See {@link android.hardware.biometrics.BiometricPrompt.AuthenticationResult}
*/
@Deprecated
public static class AuthenticationResult {
@@ -395,8 +392,7 @@
* FingerprintManager#authenticate(CryptoObject, CancellationSignal,
* int, AuthenticationCallback, Handler) } must provide an implementation of this for listening to
* fingerprint events.
- *
- * @removed See {@link android.hardware.biometrics.BiometricPrompt.AuthenticationCallback}
+ * @deprecated See {@link android.hardware.biometrics.BiometricPrompt.AuthenticationCallback}
*/
@Deprecated
public static abstract class AuthenticationCallback
@@ -459,7 +455,6 @@
/**
* Callback structure provided for {@link #detectFingerprint(CancellationSignal,
* FingerprintDetectionCallback, int, Surface)}.
- *
* @hide
*/
public interface FingerprintDetectionCallback {
@@ -613,8 +608,7 @@
* by <a href="{@docRoot}training/articles/keystore.html">Android Keystore
* facility</a>.
* @throws IllegalStateException if the crypto primitive is not initialized.
- *
- * @removed See {@link BiometricPrompt#authenticate(CancellationSignal, Executor,
+ * @deprecated See {@link BiometricPrompt#authenticate(CancellationSignal, Executor,
* BiometricPrompt.AuthenticationCallback)} and {@link BiometricPrompt#authenticate(
* BiometricPrompt.CryptoObject, CancellationSignal, Executor,
* BiometricPrompt.AuthenticationCallback)}
@@ -629,7 +623,6 @@
/**
* Per-user version of authenticate.
* @deprecated use {@link #authenticate(CryptoObject, CancellationSignal, AuthenticationCallback, Handler, FingerprintAuthenticateOptions)}.
- *
* @hide
*/
@Deprecated
@@ -642,7 +635,6 @@
/**
* Per-user and per-sensor version of authenticate.
* @deprecated use {@link #authenticate(CryptoObject, CancellationSignal, AuthenticationCallback, Handler, FingerprintAuthenticateOptions)}.
- *
* @hide
*/
@Deprecated
@@ -659,7 +651,6 @@
/**
* Version of authenticate with additional options.
- *
* @hide
*/
@RequiresPermission(anyOf = {USE_BIOMETRIC, USE_FINGERPRINT})
@@ -707,7 +698,6 @@
/**
* Uses the fingerprint hardware to detect for the presence of a finger, without giving details
* about accept/reject/lockout.
- *
* @hide
*/
@RequiresPermission(USE_BIOMETRIC_INTERNAL)
@@ -750,7 +740,6 @@
* @param callback an object to receive enrollment events
* @param shouldLogMetrics a flag that indicates if enrollment failure/success metrics
* should be logged.
- *
* @hide
*/
@RequiresPermission(MANAGE_FINGERPRINT)
@@ -821,7 +810,6 @@
/**
* Same as {@link #generateChallenge(int, GenerateChallengeCallback)}, but assumes the first
* enumerated sensor.
- *
* @hide
*/
@RequiresPermission(MANAGE_FINGERPRINT)
@@ -836,7 +824,6 @@
/**
* Revokes the specified challenge.
- *
* @hide
*/
@RequiresPermission(MANAGE_FINGERPRINT)
@@ -862,7 +849,6 @@
* @param sensorId Sensor ID that this operation takes effect for
* @param userId User ID that this operation takes effect for.
* @param hardwareAuthToken An opaque token returned by password confirmation.
- *
* @hide
*/
@RequiresPermission(RESET_FINGERPRINT_LOCKOUT)
@@ -900,7 +886,6 @@
/**
* Removes all fingerprint templates for the given user.
- *
* @hide
*/
@RequiresPermission(MANAGE_FINGERPRINT)
@@ -1020,7 +1005,6 @@
/**
* Forwards BiometricStateListener to FingerprintService
* @param listener new BiometricStateListener being added
- *
* @hide
*/
public void registerBiometricStateListener(@NonNull BiometricStateListener listener) {
@@ -1172,8 +1156,7 @@
}
/**
- * This is triggered by SideFpsEventHandler.
- *
+ * This is triggered by SideFpsEventHandler
* @hide
*/
@RequiresPermission(USE_BIOMETRIC_INTERNAL)
@@ -1186,8 +1169,7 @@
* Determine if there is at least one fingerprint enrolled.
*
* @return true if at least one fingerprint is enrolled, false otherwise
- *
- * @removed See {@link BiometricPrompt} and
+ * @deprecated See {@link BiometricPrompt} and
* {@link FingerprintManager#FINGERPRINT_ERROR_NO_FINGERPRINTS}
*/
@Deprecated
@@ -1221,8 +1203,7 @@
* Determine if fingerprint hardware is present and functional.
*
* @return true if hardware is present and functional, false otherwise.
- *
- * @removed See {@link BiometricPrompt} and
+ * @deprecated See {@link BiometricPrompt} and
* {@link FingerprintManager#FINGERPRINT_ERROR_HW_UNAVAILABLE}
*/
@Deprecated
@@ -1248,7 +1229,6 @@
/**
* Get statically configured sensor properties.
- *
* @hide
*/
@RequiresPermission(USE_BIOMETRIC_INTERNAL)
@@ -1267,7 +1247,6 @@
/**
* Returns whether the device has a power button fingerprint sensor.
* @return boolean indicating whether power button is fingerprint sensor
- *
* @hide
*/
public boolean isPowerbuttonFps() {
diff --git a/core/java/android/os/ExternalVibrationScale.aidl b/core/java/android/os/ExternalVibrationScale.aidl
new file mode 100644
index 0000000..cf6f8ed
--- /dev/null
+++ b/core/java/android/os/ExternalVibrationScale.aidl
@@ -0,0 +1,45 @@
+/**
+ * Copyright (c) 2018, 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 android.os;
+
+/**
+ * ExternalVibrationScale holds the vibration scale level and adaptive haptics scale. These
+ * can be used to scale external vibrations.
+ *
+ * @hide
+ */
+parcelable ExternalVibrationScale {
+ @Backing(type="int")
+ enum ScaleLevel {
+ SCALE_MUTE = -100,
+ SCALE_VERY_LOW = -2,
+ SCALE_LOW = -1,
+ SCALE_NONE = 0,
+ SCALE_HIGH = 1,
+ SCALE_VERY_HIGH = 2
+ }
+
+ /**
+ * The scale level that will be applied to external vibrations.
+ */
+ ScaleLevel scaleLevel = ScaleLevel.SCALE_NONE;
+
+ /**
+ * The adaptive haptics scale that will be applied to external vibrations.
+ */
+ float adaptiveHapticsScale = 1f;
+}
diff --git a/core/java/android/os/IExternalVibratorService.aidl b/core/java/android/os/IExternalVibratorService.aidl
index 666171f..a9df15a 100644
--- a/core/java/android/os/IExternalVibratorService.aidl
+++ b/core/java/android/os/IExternalVibratorService.aidl
@@ -17,6 +17,7 @@
package android.os;
import android.os.ExternalVibration;
+import android.os.ExternalVibrationScale;
/**
* The communication channel by which an external system that wants to control the system
@@ -32,29 +33,24 @@
* {@hide}
*/
interface IExternalVibratorService {
- const int SCALE_MUTE = -100;
- const int SCALE_VERY_LOW = -2;
- const int SCALE_LOW = -1;
- const int SCALE_NONE = 0;
- const int SCALE_HIGH = 1;
- const int SCALE_VERY_HIGH = 2;
-
/**
* A method called by the external system to start a vibration.
*
- * If this returns {@code SCALE_MUTE}, then the vibration should <em>not</em> play. If this
- * returns any other scale level, then any currently playing vibration controlled by the
- * requesting system must be muted and this vibration can begin playback.
+ * This returns an {@link ExternalVibrationScale} which includes the vibration scale level and
+ * the adaptive haptics scale.
+ *
+ * If the returned scale level is {@link ExternalVibrationScale.ScaleLevel#SCALE_MUTE}, then
+ * the vibration should <em>not</em> play. If it returns any other scale level, then
+ * any currently playing vibration controlled by the requesting system must be muted and this
+ * vibration can begin playback.
*
* Note that the IExternalVibratorService implementation will not call mute on any currently
* playing external vibrations in order to avoid re-entrancy with the system on the other side.
*
- * @param vibration An ExternalVibration
- *
- * @return {@code SCALE_MUTE} if the external vibration should not play, and any other scale
- * level if it should.
+ * @param vib The external vibration starting.
+ * @return {@link ExternalVibrationScale} including scale level and adaptive haptics scale.
*/
- int onExternalVibrationStart(in ExternalVibration vib);
+ ExternalVibrationScale onExternalVibrationStart(in ExternalVibration vib);
/**
* A method called by the external system when a vibration no longer wants to play.
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index ec4d587..50adc40 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -12481,6 +12481,24 @@
public static void setLocationProviderEnabled(ContentResolver cr,
String provider, boolean enabled) {
}
+
+ /**
+ * List of system components that support restore in a V-> U OS downgrade but do not have
+ * RestoreAnyVersion set to true. Value set before system restore.
+ * This setting is not B&Rd
+ * List is stored as a comma-separated string of package names e.g. "a,b,c"
+ * @hide
+ */
+ public static final String V_TO_U_RESTORE_ALLOWLIST = "v_to_u_restore_allowlist";
+
+ /**
+ * List of system components that have RestoreAnyVersion set to true but do not support
+ * restore in a V-> U OS downgrade. Value set before system restore.
+ * This setting is not B&Rd
+ * List is stored as a comma-separated string of package names e.g. "a,b,c"
+ * @hide
+ */
+ public static final String V_TO_U_RESTORE_DENYLIST = "v_to_u_restore_denylist";
}
/**
diff --git a/core/java/android/view/BatchedInputEventReceiver.java b/core/java/android/view/BatchedInputEventReceiver.java
index e679f29..ca2e56d 100644
--- a/core/java/android/view/BatchedInputEventReceiver.java
+++ b/core/java/android/view/BatchedInputEventReceiver.java
@@ -19,6 +19,7 @@
import android.compat.annotation.UnsupportedAppUsage;
import android.os.Handler;
import android.os.Looper;
+import android.os.Trace;
/**
* Similar to {@link InputEventReceiver}, but batches events to vsync boundaries when possible.
@@ -42,6 +43,8 @@
super(inputChannel, looper);
mChoreographer = choreographer;
mBatchingEnabled = true;
+ traceBoolVariable("mBatchingEnabled", mBatchingEnabled);
+ traceBoolVariable("mBatchedInputScheduled", mBatchedInputScheduled);
mHandler = new Handler(looper);
}
@@ -71,6 +74,7 @@
}
mBatchingEnabled = batchingEnabled;
+ traceBoolVariable("mBatchingEnabled", mBatchingEnabled);
mHandler.removeCallbacks(mConsumeBatchedInputEvents);
if (!batchingEnabled) {
unscheduleBatchedInput();
@@ -81,6 +85,7 @@
protected void doConsumeBatchedInput(long frameTimeNanos) {
if (mBatchedInputScheduled) {
mBatchedInputScheduled = false;
+ traceBoolVariable("mBatchedInputScheduled", mBatchedInputScheduled);
if (consumeBatchedInputEvents(frameTimeNanos) && frameTimeNanos != -1) {
// If we consumed a batch here, we want to go ahead and schedule the
// consumption of batched input events on the next frame. Otherwise, we would
@@ -95,6 +100,7 @@
private void scheduleBatchedInput() {
if (!mBatchedInputScheduled) {
mBatchedInputScheduled = true;
+ traceBoolVariable("mBatchedInputScheduled", mBatchedInputScheduled);
mChoreographer.postCallback(Choreographer.CALLBACK_INPUT, mBatchedInputRunnable, null);
}
}
@@ -102,11 +108,18 @@
private void unscheduleBatchedInput() {
if (mBatchedInputScheduled) {
mBatchedInputScheduled = false;
+ traceBoolVariable("mBatchedInputScheduled", mBatchedInputScheduled);
mChoreographer.removeCallbacks(
Choreographer.CALLBACK_INPUT, mBatchedInputRunnable, null);
}
}
+ // @TODO(b/311142655): Delete this temporary tracing. It's only used here to debug a very
+ // specific issue.
+ private void traceBoolVariable(String name, boolean value) {
+ Trace.traceCounter(Trace.TRACE_TAG_INPUT, name, value ? 1 : 0);
+ }
+
private final class BatchedInputRunnable implements Runnable {
@Override
public void run() {
diff --git a/core/java/android/view/InputEventReceiver.java b/core/java/android/view/InputEventReceiver.java
index 9c430cd..2cc05b0 100644
--- a/core/java/android/view/InputEventReceiver.java
+++ b/core/java/android/view/InputEventReceiver.java
@@ -271,12 +271,29 @@
return mInputChannel.getToken();
}
+ private String getShortDescription(InputEvent event) {
+ if (event instanceof MotionEvent motion) {
+ return "MotionEvent " + MotionEvent.actionToString(motion.getAction()) + " deviceId="
+ + motion.getDeviceId() + " source=0x"
+ + Integer.toHexString(motion.getSource()) + " historySize="
+ + motion.getHistorySize();
+ } else if (event instanceof KeyEvent key) {
+ return "KeyEvent " + KeyEvent.actionToString(key.getAction())
+ + " deviceId=" + key.getDeviceId();
+ } else {
+ Log.wtf(TAG, "Illegal InputEvent type: " + event);
+ return "InputEvent";
+ }
+ }
+
// Called from native code.
@SuppressWarnings("unused")
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
private void dispatchInputEvent(int seq, InputEvent event) {
+ Trace.traceBegin(Trace.TRACE_TAG_INPUT, "dispatchInputEvent " + getShortDescription(event));
mSeqMap.put(event.getSequenceNumber(), seq);
onInputEvent(event);
+ Trace.traceEnd(Trace.TRACE_TAG_INPUT);
}
/**
diff --git a/core/java/android/view/TextureView.java b/core/java/android/view/TextureView.java
index 021bbf7..896b3f4 100644
--- a/core/java/android/view/TextureView.java
+++ b/core/java/android/view/TextureView.java
@@ -196,8 +196,6 @@
private Canvas mCanvas;
private int mSaveCount;
- @Surface.FrameRateCompatibility int mFrameRateCompatibility;
-
private final Object[] mNativeWindowLock = new Object[0];
// Set by native code, do not write!
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java
index 06dc275..596c52d 100644
--- a/core/java/android/view/View.java
+++ b/core/java/android/view/View.java
@@ -24,6 +24,7 @@
import static android.view.Surface.FRAME_RATE_CATEGORY_LOW;
import static android.view.Surface.FRAME_RATE_CATEGORY_NORMAL;
import static android.view.Surface.FRAME_RATE_CATEGORY_NO_PREFERENCE;
+import static android.view.Surface.FRAME_RATE_COMPATIBILITY_FIXED_SOURCE;
import static android.view.accessibility.AccessibilityEvent.CONTENT_CHANGE_TYPE_UNDEFINED;
import static android.view.displayhash.DisplayHashResultCallback.DISPLAY_HASH_ERROR_INVALID_BOUNDS;
import static android.view.displayhash.DisplayHashResultCallback.DISPLAY_HASH_ERROR_MISSING_WINDOW;
@@ -2368,6 +2369,9 @@
private static boolean sToolkitSetFrameRateReadOnlyFlagValue;
private static boolean sToolkitMetricsForFrameRateDecisionFlagValue;
+ // Used to set frame rate compatibility.
+ @Surface.FrameRateCompatibility int mFrameRateCompatibility =
+ FRAME_RATE_COMPATIBILITY_FIXED_SOURCE;
static {
EMPTY_STATE_SET = StateSet.get(0);
@@ -10889,8 +10893,11 @@
structure.setAutofillId(new AutofillId(getAutofillId(),
AccessibilityNodeInfo.getVirtualDescendantId(info.getSourceNodeId())));
}
- structure.setCredentialManagerRequest(getCredentialManagerRequest(),
- getCredentialManagerCallback());
+ if (getViewCredentialHandler() != null) {
+ structure.setCredentialManagerRequest(
+ getViewCredentialHandler().getRequest(),
+ getViewCredentialHandler().getCallback());
+ }
CharSequence cname = info.getClassName();
structure.setClassName(cname != null ? cname.toString() : null);
structure.setContentDescription(info.getContentDescription());
@@ -33538,7 +33545,8 @@
frameRateCateogry = FRAME_RATE_CATEGORY_HIGH;
}
} else {
- viewRootImpl.votePreferredFrameRate(mPreferredFrameRate);
+ viewRootImpl.votePreferredFrameRate(mPreferredFrameRate,
+ mFrameRateCompatibility);
return;
}
}
diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java
index 1e79786..94260b2 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -30,6 +30,7 @@
import static android.view.Surface.FRAME_RATE_CATEGORY_LOW;
import static android.view.Surface.FRAME_RATE_CATEGORY_NORMAL;
import static android.view.Surface.FRAME_RATE_CATEGORY_NO_PREFERENCE;
+import static android.view.Surface.FRAME_RATE_COMPATIBILITY_FIXED_SOURCE;
import static android.view.View.PFLAG_DRAW_ANIMATION;
import static android.view.View.SYSTEM_UI_FLAG_FULLSCREEN;
import static android.view.View.SYSTEM_UI_FLAG_HIDE_NAVIGATION;
@@ -1027,6 +1028,13 @@
// as needed and can be useful for power saving.
// Should not enable the dVRR feature if the value is false.
private boolean mIsFrameRatePowerSavingsBalanced = true;
+ // Used to check if there is a conflict between different frame rate voting.
+ // Take 24 and 30 as an example, 24 is not a divisor of 30.
+ // We consider there is a conflict.
+ private boolean mIsFrameRateConflicted = false;
+ // Used to set frame rate compatibility.
+ @Surface.FrameRateCompatibility int mFrameRateCompatibility =
+ FRAME_RATE_COMPATIBILITY_FIXED_SOURCE;
// time for touch boost period.
private static final int FRAME_RATE_TOUCH_BOOST_TIME = 3000;
// time for checking idle status periodically.
@@ -4110,6 +4118,7 @@
? mFrameRateCategoryLowCount - 1 : mFrameRateCategoryLowCount;
mPreferredFrameRateCategory = FRAME_RATE_CATEGORY_NO_PREFERENCE;
mPreferredFrameRate = -1;
+ mIsFrameRateConflicted = false;
}
private void createSyncIfNeeded() {
@@ -6508,6 +6517,7 @@
break;
case MSG_FRAME_RATE_SETTING:
mPreferredFrameRate = 0;
+ mFrameRateCompatibility = FRAME_RATE_COMPATIBILITY_FIXED_SOURCE;
setPreferredFrameRate(mPreferredFrameRate);
break;
}
@@ -12286,9 +12296,15 @@
if (!shouldSetFrameRateCategory()) {
return;
}
+ int categoryFromConflictedFrameRates = FRAME_RATE_CATEGORY_NO_PREFERENCE;
+ if (mIsFrameRateConflicted) {
+ categoryFromConflictedFrameRates = mPreferredFrameRate > 60
+ ? FRAME_RATE_CATEGORY_HIGH : FRAME_RATE_CATEGORY_NORMAL;
+ }
int frameRateCategory = mIsTouchBoosting
- ? FRAME_RATE_CATEGORY_HIGH_HINT : preferredFrameRateCategory;
+ ? FRAME_RATE_CATEGORY_HIGH_HINT
+ : Math.max(preferredFrameRateCategory, categoryFromConflictedFrameRates);
// FRAME_RATE_CATEGORY_HIGH has a higher precedence than FRAME_RATE_CATEGORY_HIGH_HINT
// For now, FRAME_RATE_CATEGORY_HIGH_HINT is used for boosting with user interaction.
@@ -12338,7 +12354,7 @@
+ preferredFrameRate);
}
mFrameRateTransaction.setFrameRate(mSurfaceControl, preferredFrameRate,
- Surface.FRAME_RATE_COMPATIBILITY_DEFAULT).applyAsyncUnsafe();
+ mFrameRateCompatibility).applyAsyncUnsafe();
mLastPreferredFrameRate = preferredFrameRate;
}
} catch (Exception e) {
@@ -12361,7 +12377,8 @@
private boolean shouldSetFrameRate() {
// use toolkitSetFrameRate flag to gate the change
- return mSurface.isValid() && mPreferredFrameRate > 0 && shouldEnableDvrr();
+ return mSurface.isValid() && mPreferredFrameRate > 0
+ && shouldEnableDvrr() && !mIsFrameRateConflicted;
}
private boolean shouldTouchBoost(int motionEventAction, int windowType) {
@@ -12404,29 +12421,45 @@
}
/**
- * Allow Views to vote for the preferred frame rate
+ * Allow Views to vote for the preferred frame rate and compatibility.
* When determining the preferred frame rate value,
* we follow this logic: If no preferred frame rate has been set yet,
* we assign the value of frameRate as the preferred frame rate.
- * If either the current or the new preferred frame rate exceeds 60 Hz,
- * we select the higher value between them.
- * However, if both values are 60 Hz or lower, we set the preferred frame rate
- * to 60 Hz to maintain optimal performance.
+ * IF there are multiple frame rates are voted:
+ * 1. There is a frame rate is a multiple of all other frame rates.
+ * We choose this frmae rate to be the one to be set.
+ * 2. There is no frame rate can be a multiple of others
+ * We set category to HIGH if the maximum frame rate is greater than 60.
+ * Otherwise, we set category to NORMAL.
+ *
+ * Use FRAME_RATE_COMPATIBILITY_GTE for velocity and FRAME_RATE_COMPATIBILITY_FIXED_SOURCE
+ * for TextureView video play and user requested frame rate.
*
* @param frameRate the preferred frame rate of a View
+ * @param frameRateCompatibility the preferred frame rate compatibility of a View
*/
@VisibleForTesting(visibility = VisibleForTesting.Visibility.PROTECTED)
- public void votePreferredFrameRate(float frameRate) {
+ public void votePreferredFrameRate(float frameRate, int frameRateCompatibility) {
if (frameRate <= 0) {
return;
}
+ if (mPreferredFrameRate > 0 && mPreferredFrameRate % frameRate != 0
+ && frameRate % mPreferredFrameRate != 0) {
+ mIsFrameRateConflicted = true;
+ mFrameRateCompatibility = FRAME_RATE_COMPATIBILITY_FIXED_SOURCE;
+ }
+ if (frameRate > mPreferredFrameRate) {
+ mFrameRateCompatibility = frameRateCompatibility;
+ }
mPreferredFrameRate = Math.max(mPreferredFrameRate, frameRate);
-
mHasInvalidation = true;
- mHandler.removeMessages(MSG_FRAME_RATE_SETTING);
- mHandler.sendEmptyMessageDelayed(MSG_FRAME_RATE_SETTING,
- FRAME_RATE_SETTING_REEVALUATE_TIME);
+
+ if (!mIsFrameRateConflicted) {
+ mHandler.removeMessages(MSG_FRAME_RATE_SETTING);
+ mHandler.sendEmptyMessageDelayed(MSG_FRAME_RATE_SETTING,
+ FRAME_RATE_SETTING_REEVALUATE_TIME);
+ }
}
/**
@@ -12454,6 +12487,14 @@
}
/**
+ * Get the value of mFrameRateCompatibility
+ */
+ @VisibleForTesting
+ public int getFrameRateCompatibility() {
+ return mFrameRateCompatibility;
+ }
+
+ /**
* Get the value of mIsFrameRateBoosting
*/
@VisibleForTesting
@@ -12503,6 +12544,15 @@
}
/**
+ * Get the value of mIsFrameRateConflicted
+ * Can be used to checked if there is a conflict of frame rate votes.
+ */
+ @VisibleForTesting
+ public boolean isFrameRateConflicted() {
+ return mIsFrameRateConflicted;
+ }
+
+ /**
* Set the value of mIsFrameRatePowerSavingsBalanced
* Can be used to checked if toolkit dVRR feature is enabled.
*/
diff --git a/core/java/android/view/inputmethod/InputBinding.java b/core/java/android/view/inputmethod/InputBinding.java
index 2bfeb5a..fedee9d 100644
--- a/core/java/android/view/inputmethod/InputBinding.java
+++ b/core/java/android/view/inputmethod/InputBinding.java
@@ -19,11 +19,13 @@
import android.os.IBinder;
import android.os.Parcel;
import android.os.Parcelable;
+import android.ravenwood.annotation.RavenwoodKeepWholeClass;
/**
* Information given to an {@link InputMethod} about a client connecting
* to it.
*/
+@RavenwoodKeepWholeClass
public final class InputBinding implements Parcelable {
static final String TAG = "InputBinding";
diff --git a/core/java/android/view/inputmethod/InputMethodManager.java b/core/java/android/view/inputmethod/InputMethodManager.java
index 5238479..3bce155 100644
--- a/core/java/android/view/inputmethod/InputMethodManager.java
+++ b/core/java/android/view/inputmethod/InputMethodManager.java
@@ -462,8 +462,8 @@
* Flag indicating that views from the default home screen ({@link Intent#CATEGORY_HOME}) may
* act as a handwriting delegator for the delegate editor view. If set, views from the home
* screen package will be trusted for handwriting delegation, in addition to views in the {@code
- * delegatorPackageName} passed to {@link #acceptStylusHandwritingDelegation(View, String,
- * int)}.
+ * delegatorPackageName} passed to
+ * {@link #acceptStylusHandwritingDelegation(View, String, int, Executor, Consumer)} .
*/
@FlaggedApi(FLAG_HOME_SCREEN_HANDWRITING_DELEGATOR)
public static final int HANDWRITING_DELEGATE_FLAG_HOME_DELEGATOR_ALLOWED = 0x0001;
@@ -2896,6 +2896,8 @@
* @param delegateView delegate view capable of receiving input via {@link InputConnection}
* @param delegatorPackageName package name of the delegator that handled initial stylus stroke.
* @param flags {@link #HANDWRITING_DELEGATE_FLAG_HOME_DELEGATOR_ALLOWED} or {@code 0}
+ * @param executor The executor to run the callback on.
+ * @param callback {@code true>} would be received if delegation was accepted.
* @return {@code true} if view belongs to allowed delegate package declared in {@link
* #prepareStylusHandwritingDelegation(View, String)} and delegation is accepted
* @see #prepareStylusHandwritingDelegation(View, String)
@@ -2908,13 +2910,16 @@
// session to the delegate view.
// @see #startConnectionlessStylusHandwritingForDelegation(View, ResultReceiver,
// CursorAnchorInfo, String)
+ //
@FlaggedApi(FLAG_HOME_SCREEN_HANDWRITING_DELEGATOR)
- public boolean acceptStylusHandwritingDelegation(
+ public void acceptStylusHandwritingDelegation(
@NonNull View delegateView, @NonNull String delegatorPackageName,
- @HandwritingDelegateFlags int flags) {
+ @HandwritingDelegateFlags int flags, @NonNull @CallbackExecutor Executor executor,
+ @NonNull Consumer<Boolean> callback) {
Objects.requireNonNull(delegatorPackageName);
- return startStylusHandwritingInternal(delegateView, delegatorPackageName, flags);
+ startStylusHandwritingInternal(
+ delegateView, delegatorPackageName, flags, executor, callback);
}
/**
diff --git a/core/java/android/view/inputmethod/flags.aconfig b/core/java/android/view/inputmethod/flags.aconfig
index 55986e7..8d3920f 100644
--- a/core/java/android/view/inputmethod/flags.aconfig
+++ b/core/java/android/view/inputmethod/flags.aconfig
@@ -78,3 +78,11 @@
bug: "300979854"
is_fixed_read_only: true
}
+
+flag {
+ name: "predictive_back_ime"
+ namespace: "input_method"
+ description: "Predictive back animation for IMEs"
+ bug: "322836622"
+ is_fixed_read_only: true
+}
diff --git a/core/java/android/window/flags/lse_desktop_experience.aconfig b/core/java/android/window/flags/lse_desktop_experience.aconfig
new file mode 100644
index 0000000..4e86616
--- /dev/null
+++ b/core/java/android/window/flags/lse_desktop_experience.aconfig
@@ -0,0 +1,9 @@
+package: "com.android.window.flags"
+
+flag {
+ name: "enable_scaled_resizing"
+ namespace: "lse_desktop_experience"
+ description: "Enable the resizing of un-resizable apps through scaling their bounds up/down"
+ bug: "320350734"
+ is_fixed_read_only: true
+}
diff --git a/core/java/com/android/internal/accessibility/dialog/AccessibilityTargetHelper.java b/core/java/com/android/internal/accessibility/dialog/AccessibilityTargetHelper.java
index 51a5ddf..3d3db47 100644
--- a/core/java/com/android/internal/accessibility/dialog/AccessibilityTargetHelper.java
+++ b/core/java/com/android/internal/accessibility/dialog/AccessibilityTargetHelper.java
@@ -111,8 +111,9 @@
* @param context The context of the application.
* @param shortcutType The shortcut type.
* @return The list of {@link AccessibilityTarget}.
+ * @hide
*/
- static List<AccessibilityTarget> getInstalledTargets(Context context,
+ public static List<AccessibilityTarget> getInstalledTargets(Context context,
@ShortcutType int shortcutType) {
final List<AccessibilityTarget> targets = new ArrayList<>();
targets.addAll(getAccessibilityFilteredTargets(context, shortcutType));
diff --git a/core/java/com/android/internal/os/BatteryStatsHistory.java b/core/java/com/android/internal/os/BatteryStatsHistory.java
index c5c17cf..2a522645 100644
--- a/core/java/com/android/internal/os/BatteryStatsHistory.java
+++ b/core/java/com/android/internal/os/BatteryStatsHistory.java
@@ -136,10 +136,8 @@
private final Parcel mHistoryBuffer;
private final File mSystemDir;
private final HistoryStepDetailsCalculator mStepDetailsCalculator;
- private final File mHistoryDir;
private final Clock mClock;
- private int mMaxHistoryFiles;
private int mMaxHistoryBufferSize;
/**
@@ -150,7 +148,7 @@
/**
* A list of history files with increasing timestamps.
*/
- private final List<BatteryHistoryFile> mHistoryFiles = new ArrayList<>();
+ private final BatteryHistoryDirectory mHistoryDir;
/**
* A list of small history parcels, used when BatteryStatsImpl object is created from
@@ -161,7 +159,8 @@
/**
* When iterating history files, the current file index.
*/
- private int mCurrentFileIndex;
+ private BatteryHistoryFile mCurrentFile;
+
/**
* When iterating history files, the current file parcel.
*/
@@ -203,7 +202,6 @@
private byte mLastHistoryStepLevel = 0;
private boolean mMutable = true;
private final BatteryStatsHistory mWritableHistory;
- private boolean mCleanupEnabled = true;
private static class BatteryHistoryFile implements Comparable<BatteryHistoryFile> {
public final long monotonicTimeMs;
@@ -235,6 +233,271 @@
}
}
+ private static class BatteryHistoryDirectory {
+ private final File mDirectory;
+ private final MonotonicClock mMonotonicClock;
+ private int mMaxHistoryFiles;
+ private final List<BatteryHistoryFile> mHistoryFiles = new ArrayList<>();
+ private final ReentrantLock mLock = new ReentrantLock();
+ private boolean mCleanupNeeded;
+
+ BatteryHistoryDirectory(File directory, MonotonicClock monotonicClock,
+ int maxHistoryFiles) {
+ mDirectory = directory;
+ mMonotonicClock = monotonicClock;
+ mMaxHistoryFiles = maxHistoryFiles;
+ if (mMaxHistoryFiles == 0) {
+ Slog.wtf(TAG, "mMaxHistoryFiles should not be zero when writing history");
+ }
+ }
+
+ void setMaxHistoryFiles(int maxHistoryFiles) {
+ mMaxHistoryFiles = maxHistoryFiles;
+ cleanup();
+ }
+
+ void lock() {
+ mLock.lock();
+ }
+
+ void unlock() {
+ mLock.unlock();
+ if (mCleanupNeeded) {
+ cleanup();
+ }
+ }
+
+ boolean isLocked() {
+ return mLock.isLocked();
+ }
+
+ void load() {
+ mDirectory.mkdirs();
+ if (!mDirectory.exists()) {
+ Slog.wtf(TAG, "HistoryDir does not exist:" + mDirectory.getPath());
+ }
+
+ final List<File> toRemove = new ArrayList<>();
+ final Set<BatteryHistoryFile> dedup = new ArraySet<>();
+ mDirectory.listFiles((dir, name) -> {
+ final int b = name.lastIndexOf(FILE_SUFFIX);
+ if (b <= 0) {
+ toRemove.add(new File(dir, name));
+ return false;
+ }
+ try {
+ long monotonicTime = Long.parseLong(name.substring(0, b));
+ dedup.add(new BatteryHistoryFile(mDirectory, monotonicTime));
+ } catch (NumberFormatException e) {
+ toRemove.add(new File(dir, name));
+ return false;
+ }
+ return true;
+ });
+ if (!dedup.isEmpty()) {
+ mHistoryFiles.addAll(dedup);
+ Collections.sort(mHistoryFiles);
+ }
+ if (!toRemove.isEmpty()) {
+ // Clear out legacy history files, which did not follow the X-Y.bin naming format.
+ BackgroundThread.getHandler().post(() -> {
+ lock();
+ try {
+ for (File file : toRemove) {
+ file.delete();
+ }
+ } finally {
+ unlock();
+ }
+ });
+ }
+ }
+
+ List<String> getFileNames() {
+ lock();
+ try {
+ List<String> names = new ArrayList<>();
+ for (BatteryHistoryFile historyFile : mHistoryFiles) {
+ names.add(historyFile.atomicFile.getBaseFile().getName());
+ }
+ return names;
+ } finally {
+ unlock();
+ }
+ }
+
+ @Nullable
+ BatteryHistoryFile getFirstFile() {
+ lock();
+ try {
+ if (!mHistoryFiles.isEmpty()) {
+ return mHistoryFiles.get(0);
+ }
+ return null;
+ } finally {
+ unlock();
+ }
+ }
+
+ @Nullable
+ BatteryHistoryFile getLastFile() {
+ lock();
+ try {
+ if (!mHistoryFiles.isEmpty()) {
+ return mHistoryFiles.get(mHistoryFiles.size() - 1);
+ }
+ return null;
+ } finally {
+ unlock();
+ }
+ }
+
+ @Nullable
+ BatteryHistoryFile getNextFile(BatteryHistoryFile current, long startTimeMs,
+ long endTimeMs) {
+ if (!mLock.isHeldByCurrentThread()) {
+ throw new IllegalStateException("Iterating battery history without a lock");
+ }
+
+ int nextFileIndex = 0;
+ int firstFileIndex = 0;
+ // skip the last file because its data is in history buffer.
+ int lastFileIndex = mHistoryFiles.size() - 2;
+ for (int i = lastFileIndex; i >= 0; i--) {
+ BatteryHistoryFile file = mHistoryFiles.get(i);
+ if (current != null && file.monotonicTimeMs == current.monotonicTimeMs) {
+ nextFileIndex = i + 1;
+ }
+ if (file.monotonicTimeMs > endTimeMs) {
+ lastFileIndex = i - 1;
+ }
+ if (file.monotonicTimeMs <= startTimeMs) {
+ firstFileIndex = i;
+ break;
+ }
+ }
+
+ if (nextFileIndex < firstFileIndex) {
+ nextFileIndex = firstFileIndex;
+ }
+
+ if (nextFileIndex <= lastFileIndex) {
+ return mHistoryFiles.get(nextFileIndex);
+ }
+
+ return null;
+ }
+
+ BatteryHistoryFile makeBatteryHistoryFile() {
+ BatteryHistoryFile file = new BatteryHistoryFile(mDirectory,
+ mMonotonicClock.monotonicTime());
+ lock();
+ try {
+ mHistoryFiles.add(file);
+ } finally {
+ unlock();
+ }
+ return file;
+ }
+
+ void writeToParcel(Parcel out, boolean useBlobs) {
+ lock();
+ try {
+ final long start = SystemClock.uptimeMillis();
+ out.writeInt(mHistoryFiles.size() - 1);
+ for (int i = 0; i < mHistoryFiles.size() - 1; i++) {
+ AtomicFile file = mHistoryFiles.get(i).atomicFile;
+ byte[] raw = new byte[0];
+ try {
+ raw = file.readFully();
+ } catch (Exception e) {
+ Slog.e(TAG, "Error reading file " + file.getBaseFile().getPath(), e);
+ }
+ if (useBlobs) {
+ out.writeBlob(raw);
+ } else {
+ // Avoiding blobs in the check-in file for compatibility
+ out.writeByteArray(raw);
+ }
+ }
+ if (DEBUG) {
+ Slog.d(TAG,
+ "writeToParcel duration ms:" + (SystemClock.uptimeMillis() - start));
+ }
+ } finally {
+ unlock();
+ }
+ }
+
+ int getFileCount() {
+ lock();
+ try {
+ return mHistoryFiles.size();
+ } finally {
+ unlock();
+ }
+ }
+
+ int getSize() {
+ lock();
+ try {
+ int ret = 0;
+ for (int i = 0; i < mHistoryFiles.size() - 1; i++) {
+ ret += (int) mHistoryFiles.get(i).atomicFile.getBaseFile().length();
+ }
+ return ret;
+ } finally {
+ unlock();
+ }
+ }
+
+ void reset() {
+ lock();
+ try {
+ if (DEBUG) Slog.i(TAG, "********** CLEARING HISTORY!");
+ for (BatteryHistoryFile file : mHistoryFiles) {
+ file.atomicFile.delete();
+ }
+ mHistoryFiles.clear();
+ } finally {
+ unlock();
+ }
+ }
+
+ private void cleanup() {
+ if (mDirectory == null) {
+ return;
+ }
+
+ if (isLocked()) {
+ mCleanupNeeded = true;
+ return;
+ }
+
+ mCleanupNeeded = false;
+
+ lock();
+ try {
+ // if free disk space is less than 100MB, delete oldest history file.
+ if (!hasFreeDiskSpace(mDirectory)) {
+ BatteryHistoryFile oldest = mHistoryFiles.remove(0);
+ oldest.atomicFile.delete();
+ }
+
+ // if there are more history files than allowed, delete oldest history files.
+ // mMaxHistoryFiles comes from Constants.MAX_HISTORY_FILES and
+ // can be updated by DeviceConfig at run time.
+ while (mHistoryFiles.size() > mMaxHistoryFiles) {
+ BatteryHistoryFile oldest = mHistoryFiles.get(0);
+ oldest.atomicFile.delete();
+ mHistoryFiles.remove(0);
+ }
+ } finally {
+ unlock();
+ }
+ }
+ }
+
/**
* A delegate responsible for computing additional details for a step in battery history.
*/
@@ -351,7 +614,6 @@
BatteryStatsHistory writableHistory) {
mHistoryBuffer = historyBuffer;
mSystemDir = systemDir;
- mMaxHistoryFiles = maxHistoryFiles;
mMaxHistoryBufferSize = maxHistoryBufferSize;
mStepDetailsCalculator = stepDetailsCalculator;
mTracer = tracer;
@@ -363,66 +625,32 @@
mMutable = false;
}
- mHistoryDir = new File(systemDir, HISTORY_DIR);
- mHistoryDir.mkdirs();
- if (!mHistoryDir.exists()) {
- Slog.wtf(TAG, "HistoryDir does not exist:" + mHistoryDir.getPath());
- }
-
- final List<File> toRemove = new ArrayList<>();
- final Set<BatteryHistoryFile> dedup = new ArraySet<>();
- mHistoryDir.listFiles((dir, name) -> {
- final int b = name.lastIndexOf(FILE_SUFFIX);
- if (b <= 0) {
- toRemove.add(new File(dir, name));
- return false;
+ if (writableHistory != null) {
+ mHistoryDir = writableHistory.mHistoryDir;
+ } else {
+ mHistoryDir = new BatteryHistoryDirectory(new File(systemDir, HISTORY_DIR),
+ monotonicClock, maxHistoryFiles);
+ mHistoryDir.load();
+ BatteryHistoryFile activeFile = mHistoryDir.getLastFile();
+ if (activeFile == null) {
+ activeFile = mHistoryDir.makeBatteryHistoryFile();
}
- try {
- long monotonicTime = Long.parseLong(name.substring(0, b));
- dedup.add(new BatteryHistoryFile(mHistoryDir, monotonicTime));
- } catch (NumberFormatException e) {
- toRemove.add(new File(dir, name));
- return false;
- }
- return true;
- });
- if (!dedup.isEmpty()) {
- mHistoryFiles.addAll(dedup);
- Collections.sort(mHistoryFiles);
- setActiveFile(mHistoryFiles.get(mHistoryFiles.size() - 1));
- } else if (mMutable) {
- // No file found, default to have the initial file.
- BatteryHistoryFile name = makeBatteryHistoryFile();
- mHistoryFiles.add(name);
- setActiveFile(name);
- }
- if (!toRemove.isEmpty()) {
- // Clear out legacy history files, which did not follow the X-Y.bin naming format.
- BackgroundThread.getHandler().post(() -> {
- for (File file : toRemove) {
- file.delete();
- }
- });
+ setActiveFile(activeFile);
}
}
- private BatteryHistoryFile makeBatteryHistoryFile() {
- return new BatteryHistoryFile(mHistoryDir, mMonotonicClock.monotonicTime());
- }
-
- public BatteryStatsHistory(int maxHistoryFiles, int maxHistoryBufferSize,
+ public BatteryStatsHistory(int maxHistoryBufferSize,
HistoryStepDetailsCalculator stepDetailsCalculator, Clock clock,
MonotonicClock monotonicClock) {
- this(maxHistoryFiles, maxHistoryBufferSize, stepDetailsCalculator, clock, monotonicClock,
+ this(maxHistoryBufferSize, stepDetailsCalculator, clock, monotonicClock,
new TraceDelegate(), new EventLogger());
}
@VisibleForTesting
- public BatteryStatsHistory(int maxHistoryFiles, int maxHistoryBufferSize,
+ public BatteryStatsHistory(int maxHistoryBufferSize,
HistoryStepDetailsCalculator stepDetailsCalculator, Clock clock,
MonotonicClock monotonicClock, TraceDelegate traceDelegate,
EventLogger eventLogger) {
- mMaxHistoryFiles = maxHistoryFiles;
mMaxHistoryBufferSize = maxHistoryBufferSize;
mStepDetailsCalculator = stepDetailsCalculator;
mTracer = traceDelegate;
@@ -484,7 +712,9 @@
* Changes the maximum number of history files to be kept.
*/
public void setMaxHistoryFiles(int maxHistoryFiles) {
- mMaxHistoryFiles = maxHistoryFiles;
+ if (mHistoryDir != null) {
+ mHistoryDir.setMaxHistoryFiles(maxHistoryFiles);
+ }
}
/**
@@ -513,7 +743,7 @@
* Returns true if this instance only supports reading history.
*/
public boolean isReadOnly() {
- return !mMutable || mActiveFile == null || mHistoryDir == null;
+ return !mMutable || mActiveFile == null/* || mHistoryDir == null*/;
}
/**
@@ -538,25 +768,13 @@
@GuardedBy("this")
private void startNextFileLocked(long elapsedRealtimeMs) {
- if (mMaxHistoryFiles == 0) {
- Slog.wtf(TAG, "mMaxHistoryFiles should not be zero when writing history");
- return;
- }
-
- if (mHistoryFiles.isEmpty()) {
- Slog.wtf(TAG, "mFileNumbers should never be empty");
- return;
- }
-
final long start = SystemClock.uptimeMillis();
writeHistory();
if (DEBUG) {
Slog.d(TAG, "writeHistory took ms:" + (SystemClock.uptimeMillis() - start));
}
- final BatteryHistoryFile next = makeBatteryHistoryFile();
- mHistoryFiles.add(next);
- setActiveFile(next);
+ setActiveFile(mHistoryDir.makeBatteryHistoryFile());
try {
mActiveFile.getBaseFile().createNewFile();
} catch (IOException e) {
@@ -578,37 +796,7 @@
}
mWrittenPowerStatsDescriptors.clear();
- cleanupLocked();
- }
-
- @GuardedBy("this")
- private void setCleanupEnabledLocked(boolean enabled) {
- mCleanupEnabled = enabled;
- if (mCleanupEnabled) {
- cleanupLocked();
- }
- }
-
- @GuardedBy("this")
- private void cleanupLocked() {
- if (!mCleanupEnabled || mHistoryDir == null) {
- return;
- }
-
- // if free disk space is less than 100MB, delete oldest history file.
- if (!hasFreeDiskSpace()) {
- BatteryHistoryFile oldest = mHistoryFiles.remove(0);
- oldest.atomicFile.delete();
- }
-
- // if there are more history files than allowed, delete oldest history files.
- // mMaxHistoryFiles comes from Constants.MAX_HISTORY_FILES and can be updated by GService
- // config at run time.
- while (mHistoryFiles.size() > mMaxHistoryFiles) {
- BatteryHistoryFile oldest = mHistoryFiles.get(0);
- oldest.atomicFile.delete();
- mHistoryFiles.remove(0);
- }
+ mHistoryDir.cleanup();
}
/**
@@ -616,9 +804,7 @@
* currently being read.
*/
public boolean isResetEnabled() {
- synchronized (this) {
- return mCleanupEnabled;
- }
+ return mHistoryDir == null || !mHistoryDir.isLocked();
}
/**
@@ -627,16 +813,10 @@
*/
public void reset() {
synchronized (this) {
- if (DEBUG) Slog.i(TAG, "********** CLEARING HISTORY!");
- for (BatteryHistoryFile file : mHistoryFiles) {
- file.atomicFile.delete();
+ if (mHistoryDir != null) {
+ mHistoryDir.reset();
+ setActiveFile(mHistoryDir.makeBatteryHistoryFile());
}
- mHistoryFiles.clear();
-
- BatteryHistoryFile name = makeBatteryHistoryFile();
- mHistoryFiles.add(name);
- setActiveFile(name);
-
initHistoryBuffer();
}
}
@@ -646,8 +826,9 @@
*/
public long getStartTime() {
synchronized (this) {
- if (!mHistoryFiles.isEmpty()) {
- return mHistoryFiles.get(0).monotonicTimeMs;
+ BatteryHistoryFile file = mHistoryDir.getFirstFile();
+ if (file != null) {
+ return file.monotonicTimeMs;
} else {
return mHistoryBufferStartTime;
}
@@ -668,15 +849,13 @@
return copy().iterate(startTimeMs, endTimeMs);
}
- mCurrentFileIndex = 0;
+ if (mHistoryDir != null) {
+ mHistoryDir.lock();
+ }
+ mCurrentFile = null;
mCurrentParcel = null;
mCurrentParcelEnd = 0;
mParcelIndex = 0;
- if (mWritableHistory != null) {
- synchronized (mWritableHistory) {
- mWritableHistory.setCleanupEnabledLocked(false);
- }
- }
return new BatteryStatsHistoryIterator(this, startTimeMs, endTimeMs);
}
@@ -685,10 +864,8 @@
*/
void iteratorFinished() {
mHistoryBuffer.setDataPosition(mHistoryBuffer.dataSize());
- if (mWritableHistory != null) {
- synchronized (mWritableHistory) {
- mWritableHistory.setCleanupEnabledLocked(true);
- }
+ if (mHistoryDir != null) {
+ mHistoryDir.unlock();
}
}
@@ -719,39 +896,27 @@
}
}
- int firstFileIndex = 0;
- // skip the last file because its data is in history buffer.
- int lastFileIndex = mHistoryFiles.size() - 1;
- for (int i = mHistoryFiles.size() - 1; i >= 0; i--) {
- BatteryHistoryFile file = mHistoryFiles.get(i);
- if (file.monotonicTimeMs >= endTimeMs) {
- lastFileIndex = i;
- }
- if (file.monotonicTimeMs <= startTimeMs) {
- firstFileIndex = i;
- break;
- }
- }
-
- if (mCurrentFileIndex < firstFileIndex) {
- mCurrentFileIndex = firstFileIndex;
- }
-
- while (mCurrentFileIndex < lastFileIndex) {
- mCurrentParcel = null;
- mCurrentParcelEnd = 0;
- final Parcel p = Parcel.obtain();
- AtomicFile file = mHistoryFiles.get(mCurrentFileIndex++).atomicFile;
- if (readFileToParcel(p, file)) {
- int bufSize = p.readInt();
- int curPos = p.dataPosition();
- mCurrentParcelEnd = curPos + bufSize;
- mCurrentParcel = p;
- if (curPos < mCurrentParcelEnd) {
- return mCurrentParcel;
+ if (mHistoryDir != null) {
+ BatteryHistoryFile nextFile = mHistoryDir.getNextFile(mCurrentFile, startTimeMs,
+ endTimeMs);
+ while (nextFile != null) {
+ mCurrentParcel = null;
+ mCurrentParcelEnd = 0;
+ final Parcel p = Parcel.obtain();
+ AtomicFile file = nextFile.atomicFile;
+ if (readFileToParcel(p, file)) {
+ int bufSize = p.readInt();
+ int curPos = p.dataPosition();
+ mCurrentParcelEnd = curPos + bufSize;
+ mCurrentParcel = p;
+ if (curPos < mCurrentParcelEnd) {
+ mCurrentFile = nextFile;
+ return mCurrentParcel;
+ }
+ } else {
+ p.recycle();
}
- } else {
- p.recycle();
+ nextFile = mHistoryDir.getNextFile(nextFile, startTimeMs, endTimeMs);
}
}
@@ -922,25 +1087,8 @@
}
private void writeToParcel(Parcel out, boolean useBlobs) {
- final long start = SystemClock.uptimeMillis();
- out.writeInt(mHistoryFiles.size() - 1);
- for (int i = 0; i < mHistoryFiles.size() - 1; i++) {
- AtomicFile file = mHistoryFiles.get(i).atomicFile;
- byte[] raw = new byte[0];
- try {
- raw = file.readFully();
- } catch (Exception e) {
- Slog.e(TAG, "Error reading file " + file.getBaseFile().getPath(), e);
- }
- if (useBlobs) {
- out.writeBlob(raw);
- } else {
- // Avoiding blobs in the check-in file for compatibility
- out.writeByteArray(raw);
- }
- }
- if (DEBUG) {
- Slog.d(TAG, "writeToParcel duration ms:" + (SystemClock.uptimeMillis() - start));
+ if (mHistoryDir != null) {
+ mHistoryDir.writeToParcel(out, useBlobs);
}
}
@@ -1021,22 +1169,18 @@
* @return true if there is more than 100MB free disk space left.
*/
@android.ravenwood.annotation.RavenwoodReplace
- private boolean hasFreeDiskSpace() {
- final StatFs stats = new StatFs(mHistoryDir.getAbsolutePath());
+ private static boolean hasFreeDiskSpace(File systemDir) {
+ final StatFs stats = new StatFs(systemDir.getAbsolutePath());
return stats.getAvailableBytes() > MIN_FREE_SPACE;
}
- private boolean hasFreeDiskSpace$ravenwood() {
+ private static boolean hasFreeDiskSpace$ravenwood(File systemDir) {
return true;
}
@VisibleForTesting
public List<String> getFilesNames() {
- List<String> names = new ArrayList<>();
- for (BatteryHistoryFile historyFile : mHistoryFiles) {
- names.add(historyFile.atomicFile.getBaseFile().getName());
- }
- return names;
+ return mHistoryDir.getFileNames();
}
@VisibleForTesting
@@ -1048,10 +1192,7 @@
* @return the total size of all history files and history buffer.
*/
public int getHistoryUsedSize() {
- int ret = 0;
- for (int i = 0; i < mHistoryFiles.size() - 1; i++) {
- ret += mHistoryFiles.get(i).atomicFile.getBaseFile().length();
- }
+ int ret = mHistoryDir.getSize();
ret += mHistoryBuffer.dataSize();
if (mHistoryParcels != null) {
for (int i = 0; i < mHistoryParcels.size(); i++) {
@@ -1109,7 +1250,7 @@
*/
public void continueRecordingHistory() {
synchronized (this) {
- if (mHistoryBuffer.dataPosition() <= 0 && mHistoryFiles.size() <= 1) {
+ if (mHistoryBuffer.dataPosition() <= 0 && mHistoryDir.getFileCount() <= 1) {
return;
}
diff --git a/core/java/com/android/internal/os/BatteryStatsHistoryIterator.java b/core/java/com/android/internal/os/BatteryStatsHistoryIterator.java
index b2a6a93..83e9407 100644
--- a/core/java/com/android/internal/os/BatteryStatsHistoryIterator.java
+++ b/core/java/com/android/internal/os/BatteryStatsHistoryIterator.java
@@ -44,6 +44,7 @@
private BatteryStats.HistoryItem mHistoryItem = new BatteryStats.HistoryItem();
private boolean mNextItemReady;
private boolean mTimeInitialized;
+ private boolean mClosed;
public BatteryStatsHistoryIterator(@NonNull BatteryStatsHistory history, long startTimeMs,
long endTimeMs) {
@@ -322,6 +323,9 @@
*/
@Override
public void close() {
- mBatteryStatsHistory.iteratorFinished();
+ if (!mClosed) {
+ mClosed = true;
+ mBatteryStatsHistory.iteratorFinished();
+ }
}
}
diff --git a/core/jni/android_view_InputEventReceiver.cpp b/core/jni/android_view_InputEventReceiver.cpp
index f1b93db..07cbaad 100644
--- a/core/jni/android_view_InputEventReceiver.cpp
+++ b/core/jni/android_view_InputEventReceiver.cpp
@@ -15,6 +15,7 @@
*/
#define LOG_TAG "InputEventReceiver"
+#define ATRACE_TAG ATRACE_TAG_INPUT
//#define LOG_NDEBUG 0
@@ -46,6 +47,16 @@
return value ? "true" : "false";
}
+/**
+ * Trace a bool variable, writing "1" if the value is "true" and "0" otherwise.
+ * TODO(b/311142655): delete this tracing. It's only useful for debugging very specific issues.
+ * @param var the name of the variable
+ * @param value the value of the variable
+ */
+static void traceBoolVariable(const char* var, bool value) {
+ ATRACE_INT(var, value ? 1 : 0);
+}
+
static struct {
jclass clazz;
@@ -130,6 +141,7 @@
mMessageQueue(messageQueue),
mBatchedInputEventPending(false),
mFdEvents(0) {
+ traceBoolVariable("mBatchedInputEventPending", mBatchedInputEventPending);
if (kDebugDispatchCycle) {
ALOGD("channel '%s' ~ Initializing input event receiver.", getInputChannelName().c_str());
}
@@ -311,6 +323,7 @@
if (consumeBatches) {
mBatchedInputEventPending = false;
+ traceBoolVariable("mBatchedInputEventPending", mBatchedInputEventPending);
}
if (outConsumedBatch) {
*outConsumedBatch = false;
@@ -344,6 +357,7 @@
}
mBatchedInputEventPending = true;
+ traceBoolVariable("mBatchedInputEventPending", mBatchedInputEventPending);
if (kDebugDispatchCycle) {
ALOGD("channel '%s' ~ Dispatching batched input event pending notification.",
getInputChannelName().c_str());
@@ -355,6 +369,7 @@
if (env->ExceptionCheck()) {
ALOGE("Exception dispatching batched input events.");
mBatchedInputEventPending = false; // try again later
+ traceBoolVariable("mBatchedInputEventPending", mBatchedInputEventPending);
}
}
return OK;
diff --git a/core/tests/coretests/src/android/view/ViewRootImplTest.java b/core/tests/coretests/src/android/view/ViewRootImplTest.java
index 038c00e..64cbe7f 100644
--- a/core/tests/coretests/src/android/view/ViewRootImplTest.java
+++ b/core/tests/coretests/src/android/view/ViewRootImplTest.java
@@ -24,6 +24,8 @@
import static android.view.Surface.FRAME_RATE_CATEGORY_LOW;
import static android.view.Surface.FRAME_RATE_CATEGORY_NORMAL;
import static android.view.Surface.FRAME_RATE_CATEGORY_NO_PREFERENCE;
+import static android.view.Surface.FRAME_RATE_COMPATIBILITY_FIXED_SOURCE;
+import static android.view.Surface.FRAME_RATE_COMPATIBILITY_GTE;
import static android.view.View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY;
import static android.view.View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN;
import static android.view.View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION;
@@ -705,17 +707,51 @@
public void votePreferredFrameRate_voteFrameRate_aggregate() {
View view = new View(sContext);
attachViewToWindow(view);
+ ViewRootImpl viewRootImpl = view.getViewRootImpl();
sInstrumentation.runOnMainSync(() -> {
- ViewRootImpl viewRootImpl = view.getViewRootImpl();
assertEquals(viewRootImpl.getPreferredFrameRate(), 0, 0.1);
- viewRootImpl.votePreferredFrameRate(24);
+ assertEquals(viewRootImpl.getFrameRateCompatibility(),
+ FRAME_RATE_COMPATIBILITY_FIXED_SOURCE);
+ assertEquals(viewRootImpl.isFrameRateConflicted(), false);
+ viewRootImpl.votePreferredFrameRate(24, FRAME_RATE_COMPATIBILITY_GTE);
assertEquals(viewRootImpl.getPreferredFrameRate(), 24, 0.1);
- viewRootImpl.votePreferredFrameRate(30);
+ assertEquals(viewRootImpl.getFrameRateCompatibility(),
+ FRAME_RATE_COMPATIBILITY_GTE);
+ assertEquals(viewRootImpl.isFrameRateConflicted(), false);
+ viewRootImpl.votePreferredFrameRate(30, FRAME_RATE_COMPATIBILITY_FIXED_SOURCE);
assertEquals(viewRootImpl.getPreferredFrameRate(), 30, 0.1);
- viewRootImpl.votePreferredFrameRate(60);
+ // If there is a conflict, then set compatibility to
+ // FRAME_RATE_COMPATIBILITY_FIXED_SOURCE
+ assertEquals(viewRootImpl.getFrameRateCompatibility(),
+ FRAME_RATE_COMPATIBILITY_FIXED_SOURCE);
+ // Should be true since there is a conflict between 24 and 30.
+ assertEquals(viewRootImpl.isFrameRateConflicted(), true);
+ view.invalidate();
+ });
+ sInstrumentation.waitForIdleSync();
+
+ sInstrumentation.runOnMainSync(() -> {
+ assertEquals(viewRootImpl.isFrameRateConflicted(), false);
+ viewRootImpl.votePreferredFrameRate(60, FRAME_RATE_COMPATIBILITY_GTE);
assertEquals(viewRootImpl.getPreferredFrameRate(), 60, 0.1);
- viewRootImpl.votePreferredFrameRate(120);
+ assertEquals(viewRootImpl.getFrameRateCompatibility(),
+ FRAME_RATE_COMPATIBILITY_GTE);
+ assertEquals(viewRootImpl.isFrameRateConflicted(), false);
+ viewRootImpl.votePreferredFrameRate(120, FRAME_RATE_COMPATIBILITY_FIXED_SOURCE);
assertEquals(viewRootImpl.getPreferredFrameRate(), 120, 0.1);
+ assertEquals(viewRootImpl.getFrameRateCompatibility(),
+ FRAME_RATE_COMPATIBILITY_FIXED_SOURCE);
+ // Should be false since 60 is a divisor of 120.
+ assertEquals(viewRootImpl.isFrameRateConflicted(), false);
+ viewRootImpl.votePreferredFrameRate(60, FRAME_RATE_COMPATIBILITY_GTE);
+ assertEquals(viewRootImpl.getPreferredFrameRate(), 120, 0.1);
+ // compatibility should be remained the same (FRAME_RATE_COMPATIBILITY_FIXED_SOURCE)
+ // since the frame rate 60 is smaller than 120.
+ assertEquals(viewRootImpl.getFrameRateCompatibility(),
+ FRAME_RATE_COMPATIBILITY_FIXED_SOURCE);
+ // Should be false since 60 is a divisor of 120.
+ assertEquals(viewRootImpl.isFrameRateConflicted(), false);
+
});
}
@@ -842,14 +878,26 @@
sInstrumentation.runOnMainSync(() -> {
assertEquals(viewRootImpl.getPreferredFrameRate(), 0, 0.1);
- viewRootImpl.votePreferredFrameRate(24);
+ assertEquals(viewRootImpl.getFrameRateCompatibility(),
+ FRAME_RATE_COMPATIBILITY_FIXED_SOURCE);
+ assertEquals(viewRootImpl.isFrameRateConflicted(), false);
+ viewRootImpl.votePreferredFrameRate(24, FRAME_RATE_COMPATIBILITY_FIXED_SOURCE);
assertEquals(viewRootImpl.getPreferredFrameRate(), 24, 0.1);
+ assertEquals(viewRootImpl.getFrameRateCompatibility(),
+ FRAME_RATE_COMPATIBILITY_FIXED_SOURCE);
+ assertEquals(viewRootImpl.isFrameRateConflicted(), false);
view.invalidate();
assertEquals(viewRootImpl.getPreferredFrameRate(), 24, 0.1);
+ assertEquals(viewRootImpl.getFrameRateCompatibility(),
+ FRAME_RATE_COMPATIBILITY_FIXED_SOURCE);
+ assertEquals(viewRootImpl.isFrameRateConflicted(), false);
});
Thread.sleep(delay);
assertEquals(viewRootImpl.getPreferredFrameRate(), 0, 0.1);
+ assertEquals(viewRootImpl.getFrameRateCompatibility(),
+ FRAME_RATE_COMPATIBILITY_FIXED_SOURCE);
+ assertEquals(viewRootImpl.isFrameRateConflicted(), false);
}
/**
diff --git a/graphics/java/android/graphics/pdf/PdfEditor.java b/graphics/java/android/graphics/pdf/PdfEditor.java
index 69e1982..3cd709e 100644
--- a/graphics/java/android/graphics/pdf/PdfEditor.java
+++ b/graphics/java/android/graphics/pdf/PdfEditor.java
@@ -25,9 +25,7 @@
import android.system.ErrnoException;
import android.system.Os;
import android.system.OsConstants;
-
import dalvik.system.CloseGuard;
-
import libcore.io.IoUtils;
import java.io.IOException;
@@ -39,12 +37,6 @@
*/
public final class PdfEditor {
- /**
- * Any call the native pdfium code has to be single threaded as the library does not support
- * parallel use.
- */
- private static final Object sPdfiumLock = new Object();
-
private final CloseGuard mCloseGuard = CloseGuard.get();
private long mNativeDocument;
@@ -87,7 +79,7 @@
}
mInput = input;
- synchronized (sPdfiumLock) {
+ synchronized (PdfRenderer.sPdfiumLock) {
mNativeDocument = nativeOpen(mInput.getFd(), size);
try {
mPageCount = nativeGetPageCount(mNativeDocument);
@@ -120,7 +112,7 @@
throwIfClosed();
throwIfPageNotInDocument(pageIndex);
- synchronized (sPdfiumLock) {
+ synchronized (PdfRenderer.sPdfiumLock) {
mPageCount = nativeRemovePage(mNativeDocument, pageIndex);
}
}
@@ -146,12 +138,12 @@
Point size = new Point();
getPageSize(pageIndex, size);
- synchronized (sPdfiumLock) {
+ synchronized (PdfRenderer.sPdfiumLock) {
nativeSetTransformAndClip(mNativeDocument, pageIndex, transform.ni(),
0, 0, size.x, size.y);
}
} else {
- synchronized (sPdfiumLock) {
+ synchronized (PdfRenderer.sPdfiumLock) {
nativeSetTransformAndClip(mNativeDocument, pageIndex, transform.ni(),
clip.left, clip.top, clip.right, clip.bottom);
}
@@ -169,7 +161,7 @@
throwIfOutSizeNull(outSize);
throwIfPageNotInDocument(pageIndex);
- synchronized (sPdfiumLock) {
+ synchronized (PdfRenderer.sPdfiumLock) {
nativeGetPageSize(mNativeDocument, pageIndex, outSize);
}
}
@@ -185,7 +177,7 @@
throwIfOutMediaBoxNull(outMediaBox);
throwIfPageNotInDocument(pageIndex);
- synchronized (sPdfiumLock) {
+ synchronized (PdfRenderer.sPdfiumLock) {
return nativeGetPageMediaBox(mNativeDocument, pageIndex, outMediaBox);
}
}
@@ -201,7 +193,7 @@
throwIfMediaBoxNull(mediaBox);
throwIfPageNotInDocument(pageIndex);
- synchronized (sPdfiumLock) {
+ synchronized (PdfRenderer.sPdfiumLock) {
nativeSetPageMediaBox(mNativeDocument, pageIndex, mediaBox);
}
}
@@ -217,7 +209,7 @@
throwIfOutCropBoxNull(outCropBox);
throwIfPageNotInDocument(pageIndex);
- synchronized (sPdfiumLock) {
+ synchronized (PdfRenderer.sPdfiumLock) {
return nativeGetPageCropBox(mNativeDocument, pageIndex, outCropBox);
}
}
@@ -233,7 +225,7 @@
throwIfCropBoxNull(cropBox);
throwIfPageNotInDocument(pageIndex);
- synchronized (sPdfiumLock) {
+ synchronized (PdfRenderer.sPdfiumLock) {
nativeSetPageCropBox(mNativeDocument, pageIndex, cropBox);
}
}
@@ -246,7 +238,7 @@
public boolean shouldScaleForPrinting() {
throwIfClosed();
- synchronized (sPdfiumLock) {
+ synchronized (PdfRenderer.sPdfiumLock) {
return nativeScaleForPrinting(mNativeDocument);
}
}
@@ -263,7 +255,7 @@
try {
throwIfClosed();
- synchronized (sPdfiumLock) {
+ synchronized (PdfRenderer.sPdfiumLock) {
nativeWrite(mNativeDocument, output.getFd());
}
} finally {
@@ -295,7 +287,7 @@
private void doClose() {
if (mNativeDocument != 0) {
- synchronized (sPdfiumLock) {
+ synchronized (PdfRenderer.sPdfiumLock) {
nativeClose(mNativeDocument);
}
mNativeDocument = 0;
diff --git a/graphics/java/android/graphics/pdf/PdfRenderer.java b/graphics/java/android/graphics/pdf/PdfRenderer.java
new file mode 100644
index 0000000..4666963
--- /dev/null
+++ b/graphics/java/android/graphics/pdf/PdfRenderer.java
@@ -0,0 +1,502 @@
+/*
+ * Copyright (C) 2014 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 android.graphics.pdf;
+
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.compat.annotation.UnsupportedAppUsage;
+import android.graphics.Bitmap;
+import android.graphics.Bitmap.Config;
+import android.graphics.Matrix;
+import android.graphics.Point;
+import android.graphics.Rect;
+import android.os.Build;
+import android.os.ParcelFileDescriptor;
+import android.system.ErrnoException;
+import android.system.Os;
+import android.system.OsConstants;
+
+import com.android.internal.util.Preconditions;
+
+import dalvik.system.CloseGuard;
+
+import libcore.io.IoUtils;
+
+import java.io.IOException;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/**
+ * <p>
+ * This class enables rendering a PDF document. This class is not thread safe.
+ * </p>
+ * <p>
+ * If you want to render a PDF, you create a renderer and for every page you want
+ * to render, you open the page, render it, and close the page. After you are done
+ * with rendering, you close the renderer. After the renderer is closed it should not
+ * be used anymore. Note that the pages are rendered one by one, i.e. you can have
+ * only a single page opened at any given time.
+ * </p>
+ * <p>
+ * A typical use of the APIs to render a PDF looks like this:
+ * </p>
+ * <pre>
+ * // create a new renderer
+ * PdfRenderer renderer = new PdfRenderer(getSeekableFileDescriptor());
+ *
+ * // let us just render all pages
+ * final int pageCount = renderer.getPageCount();
+ * for (int i = 0; i < pageCount; i++) {
+ * Page page = renderer.openPage(i);
+ *
+ * // say we render for showing on the screen
+ * page.render(mBitmap, null, null, Page.RENDER_MODE_FOR_DISPLAY);
+ *
+ * // do stuff with the bitmap
+ *
+ * // close the page
+ * page.close();
+ * }
+ *
+ * // close the renderer
+ * renderer.close();
+ * </pre>
+ *
+ * <h3>Print preview and print output</h3>
+ * <p>
+ * If you are using this class to rasterize a PDF for printing or show a print
+ * preview, it is recommended that you respect the following contract in order
+ * to provide a consistent user experience when seeing a preview and printing,
+ * i.e. the user sees a preview that is the same as the printout.
+ * </p>
+ * <ul>
+ * <li>
+ * Respect the property whether the document would like to be scaled for printing
+ * as per {@link #shouldScaleForPrinting()}.
+ * </li>
+ * <li>
+ * When scaling a document for printing the aspect ratio should be preserved.
+ * </li>
+ * <li>
+ * Do not inset the content with any margins from the {@link android.print.PrintAttributes}
+ * as the application is responsible to render it such that the margins are respected.
+ * </li>
+ * <li>
+ * If document page size is greater than the printed media size the content should
+ * be anchored to the upper left corner of the page for left-to-right locales and
+ * top right corner for right-to-left locales.
+ * </li>
+ * </ul>
+ *
+ * @see #close()
+ */
+public final class PdfRenderer implements AutoCloseable {
+ /**
+ * Any call the native pdfium code has to be single threaded as the library does not support
+ * parallel use.
+ */
+ final static Object sPdfiumLock = new Object();
+
+ private final CloseGuard mCloseGuard = CloseGuard.get();
+
+ private final Point mTempPoint = new Point();
+
+ private long mNativeDocument;
+
+ private final int mPageCount;
+
+ private ParcelFileDescriptor mInput;
+
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
+ private Page mCurrentPage;
+
+ /** @hide */
+ @IntDef({
+ Page.RENDER_MODE_FOR_DISPLAY,
+ Page.RENDER_MODE_FOR_PRINT
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface RenderMode {}
+
+ /**
+ * Creates a new instance.
+ * <p>
+ * <strong>Note:</strong> The provided file descriptor must be <strong>seekable</strong>,
+ * i.e. its data being randomly accessed, e.g. pointing to a file.
+ * </p>
+ * <p>
+ * <strong>Note:</strong> This class takes ownership of the passed in file descriptor
+ * and is responsible for closing it when the renderer is closed.
+ * </p>
+ * <p>
+ * If the file is from an untrusted source it is recommended to run the renderer in a separate,
+ * isolated process with minimal permissions to limit the impact of security exploits.
+ * </p>
+ *
+ * @param input Seekable file descriptor to read from.
+ *
+ * @throws java.io.IOException If an error occurs while reading the file.
+ * @throws java.lang.SecurityException If the file requires a password or
+ * the security scheme is not supported.
+ */
+ public PdfRenderer(@NonNull ParcelFileDescriptor input) throws IOException {
+ if (input == null) {
+ throw new NullPointerException("input cannot be null");
+ }
+
+ final long size;
+ try {
+ Os.lseek(input.getFileDescriptor(), 0, OsConstants.SEEK_SET);
+ size = Os.fstat(input.getFileDescriptor()).st_size;
+ } catch (ErrnoException ee) {
+ throw new IllegalArgumentException("file descriptor not seekable");
+ }
+ mInput = input;
+
+ synchronized (sPdfiumLock) {
+ mNativeDocument = nativeCreate(mInput.getFd(), size);
+ try {
+ mPageCount = nativeGetPageCount(mNativeDocument);
+ } catch (Throwable t) {
+ nativeClose(mNativeDocument);
+ mNativeDocument = 0;
+ throw t;
+ }
+ }
+
+ mCloseGuard.open("close");
+ }
+
+ /**
+ * Closes this renderer. You should not use this instance
+ * after this method is called.
+ */
+ public void close() {
+ throwIfClosed();
+ throwIfPageOpened();
+ doClose();
+ }
+
+ /**
+ * Gets the number of pages in the document.
+ *
+ * @return The page count.
+ */
+ public int getPageCount() {
+ throwIfClosed();
+ return mPageCount;
+ }
+
+ /**
+ * Gets whether the document prefers to be scaled for printing.
+ * You should take this info account if the document is rendered
+ * for printing and the target media size differs from the page
+ * size.
+ *
+ * @return If to scale the document.
+ */
+ public boolean shouldScaleForPrinting() {
+ throwIfClosed();
+
+ synchronized (sPdfiumLock) {
+ return nativeScaleForPrinting(mNativeDocument);
+ }
+ }
+
+ /**
+ * Opens a page for rendering.
+ *
+ * @param index The page index.
+ * @return A page that can be rendered.
+ *
+ * @see android.graphics.pdf.PdfRenderer.Page#close() PdfRenderer.Page.close()
+ */
+ public Page openPage(int index) {
+ throwIfClosed();
+ throwIfPageOpened();
+ throwIfPageNotInDocument(index);
+ mCurrentPage = new Page(index);
+ return mCurrentPage;
+ }
+
+ @Override
+ protected void finalize() throws Throwable {
+ try {
+ if (mCloseGuard != null) {
+ mCloseGuard.warnIfOpen();
+ }
+
+ doClose();
+ } finally {
+ super.finalize();
+ }
+ }
+
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
+ private void doClose() {
+ if (mCurrentPage != null) {
+ mCurrentPage.close();
+ mCurrentPage = null;
+ }
+
+ if (mNativeDocument != 0) {
+ synchronized (sPdfiumLock) {
+ nativeClose(mNativeDocument);
+ }
+ mNativeDocument = 0;
+ }
+
+ if (mInput != null) {
+ IoUtils.closeQuietly(mInput);
+ mInput = null;
+ }
+ mCloseGuard.close();
+ }
+
+ private void throwIfClosed() {
+ if (mInput == null) {
+ throw new IllegalStateException("Already closed");
+ }
+ }
+
+ private void throwIfPageOpened() {
+ if (mCurrentPage != null) {
+ throw new IllegalStateException("Current page not closed");
+ }
+ }
+
+ private void throwIfPageNotInDocument(int pageIndex) {
+ if (pageIndex < 0 || pageIndex >= mPageCount) {
+ throw new IllegalArgumentException("Invalid page index");
+ }
+ }
+
+ /**
+ * This class represents a PDF document page for rendering.
+ */
+ public final class Page implements AutoCloseable {
+
+ private final CloseGuard mCloseGuard = CloseGuard.get();
+
+ /**
+ * Mode to render the content for display on a screen.
+ */
+ public static final int RENDER_MODE_FOR_DISPLAY = 1;
+
+ /**
+ * Mode to render the content for printing.
+ */
+ public static final int RENDER_MODE_FOR_PRINT = 2;
+
+ private final int mIndex;
+ private final int mWidth;
+ private final int mHeight;
+
+ private long mNativePage;
+
+ private Page(int index) {
+ Point size = mTempPoint;
+ synchronized (sPdfiumLock) {
+ mNativePage = nativeOpenPageAndGetSize(mNativeDocument, index, size);
+ }
+ mIndex = index;
+ mWidth = size.x;
+ mHeight = size.y;
+ mCloseGuard.open("close");
+ }
+
+ /**
+ * Gets the page index.
+ *
+ * @return The index.
+ */
+ public int getIndex() {
+ return mIndex;
+ }
+
+ /**
+ * Gets the page width in points (1/72").
+ *
+ * @return The width in points.
+ */
+ public int getWidth() {
+ return mWidth;
+ }
+
+ /**
+ * Gets the page height in points (1/72").
+ *
+ * @return The height in points.
+ */
+ public int getHeight() {
+ return mHeight;
+ }
+
+ /**
+ * Renders a page to a bitmap.
+ * <p>
+ * You may optionally specify a rectangular clip in the bitmap bounds. No rendering
+ * outside the clip will be performed, hence it is your responsibility to initialize
+ * the bitmap outside the clip.
+ * </p>
+ * <p>
+ * You may optionally specify a matrix to transform the content from page coordinates
+ * which are in points (1/72") to bitmap coordinates which are in pixels. If this
+ * matrix is not provided this method will apply a transformation that will fit the
+ * whole page to the destination clip if provided or the destination bitmap if no
+ * clip is provided.
+ * </p>
+ * <p>
+ * The clip and transformation are useful for implementing tile rendering where the
+ * destination bitmap contains a portion of the image, for example when zooming.
+ * Another useful application is for printing where the size of the bitmap holding
+ * the page is too large and a client can render the page in stripes.
+ * </p>
+ * <p>
+ * <strong>Note: </strong> The destination bitmap format must be
+ * {@link Config#ARGB_8888 ARGB}.
+ * </p>
+ * <p>
+ * <strong>Note: </strong> The optional transformation matrix must be affine as per
+ * {@link android.graphics.Matrix#isAffine() Matrix.isAffine()}. Hence, you can specify
+ * rotation, scaling, translation but not a perspective transformation.
+ * </p>
+ *
+ * @param destination Destination bitmap to which to render.
+ * @param destClip Optional clip in the bitmap bounds.
+ * @param transform Optional transformation to apply when rendering.
+ * @param renderMode The render mode.
+ *
+ * @see #RENDER_MODE_FOR_DISPLAY
+ * @see #RENDER_MODE_FOR_PRINT
+ */
+ public void render(@NonNull Bitmap destination, @Nullable Rect destClip,
+ @Nullable Matrix transform, @RenderMode int renderMode) {
+ if (mNativePage == 0) {
+ throw new NullPointerException();
+ }
+
+ destination = Preconditions.checkNotNull(destination, "bitmap null");
+
+ if (destination.getConfig() != Config.ARGB_8888) {
+ throw new IllegalArgumentException("Unsupported pixel format");
+ }
+
+ if (destClip != null) {
+ if (destClip.left < 0 || destClip.top < 0
+ || destClip.right > destination.getWidth()
+ || destClip.bottom > destination.getHeight()) {
+ throw new IllegalArgumentException("destBounds not in destination");
+ }
+ }
+
+ if (transform != null && !transform.isAffine()) {
+ throw new IllegalArgumentException("transform not affine");
+ }
+
+ if (renderMode != RENDER_MODE_FOR_PRINT && renderMode != RENDER_MODE_FOR_DISPLAY) {
+ throw new IllegalArgumentException("Unsupported render mode");
+ }
+
+ if (renderMode == RENDER_MODE_FOR_PRINT && renderMode == RENDER_MODE_FOR_DISPLAY) {
+ throw new IllegalArgumentException("Only single render mode supported");
+ }
+
+ final int contentLeft = (destClip != null) ? destClip.left : 0;
+ final int contentTop = (destClip != null) ? destClip.top : 0;
+ final int contentRight = (destClip != null) ? destClip.right
+ : destination.getWidth();
+ final int contentBottom = (destClip != null) ? destClip.bottom
+ : destination.getHeight();
+
+ // If transform is not set, stretch page to whole clipped area
+ if (transform == null) {
+ int clipWidth = contentRight - contentLeft;
+ int clipHeight = contentBottom - contentTop;
+
+ transform = new Matrix();
+ transform.postScale((float)clipWidth / getWidth(),
+ (float)clipHeight / getHeight());
+ transform.postTranslate(contentLeft, contentTop);
+ }
+
+ // FIXME: This code is planned to be outside the UI rendering module, so it should not
+ // be able to access native instances from Bitmap, Matrix, etc.
+ final long transformPtr = transform.ni();
+
+ synchronized (sPdfiumLock) {
+ nativeRenderPage(mNativeDocument, mNativePage, destination.getNativeInstance(),
+ contentLeft, contentTop, contentRight, contentBottom, transformPtr,
+ renderMode);
+ }
+ }
+
+ /**
+ * Closes this page.
+ *
+ * @see android.graphics.pdf.PdfRenderer#openPage(int)
+ */
+ @Override
+ public void close() {
+ throwIfClosed();
+ doClose();
+ }
+
+ @Override
+ protected void finalize() throws Throwable {
+ try {
+ if (mCloseGuard != null) {
+ mCloseGuard.warnIfOpen();
+ }
+
+ doClose();
+ } finally {
+ super.finalize();
+ }
+ }
+
+ private void doClose() {
+ if (mNativePage != 0) {
+ synchronized (sPdfiumLock) {
+ nativeClosePage(mNativePage);
+ }
+ mNativePage = 0;
+ }
+
+ mCloseGuard.close();
+ mCurrentPage = null;
+ }
+
+ private void throwIfClosed() {
+ if (mNativePage == 0) {
+ throw new IllegalStateException("Already closed");
+ }
+ }
+ }
+
+ private static native long nativeCreate(int fd, long size);
+ private static native void nativeClose(long documentPtr);
+ private static native int nativeGetPageCount(long documentPtr);
+ private static native boolean nativeScaleForPrinting(long documentPtr);
+ private static native void nativeRenderPage(long documentPtr, long pagePtr, long bitmapHandle,
+ int clipLeft, int clipTop, int clipRight, int clipBottom, long transformPtr,
+ int renderMode);
+ private static native long nativeOpenPageAndGetSize(long documentPtr, int pageIndex,
+ Point outSize);
+ private static native void nativeClosePage(long pagePtr);
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java
index 34be9b0..2606fb6 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java
@@ -114,6 +114,7 @@
/** Tracks if we should start the back gesture on the next motion move event */
private boolean mShouldStartOnNextMoveEvent = false;
private boolean mOnBackStartDispatched = false;
+ private boolean mPointerPilfered = false;
private final FlingAnimationUtils mFlingAnimationUtils;
@@ -404,11 +405,12 @@
@VisibleForTesting
void onPilferPointers() {
+ mPointerPilfered = true;
// Dispatch onBackStarted, only to app callbacks.
// System callbacks will receive onBackStarted when the remote animation starts.
if (!shouldDispatchToAnimator() && mActiveCallback != null) {
mCurrentTracker.updateStartLocation();
- tryDispatchAppOnBackStarted(mActiveCallback, mCurrentTracker.createStartEvent(null));
+ tryDispatchOnBackStarted(mActiveCallback, mCurrentTracker.createStartEvent(null));
}
}
@@ -511,7 +513,7 @@
mActiveCallback = mBackNavigationInfo.getOnBackInvokedCallback();
// App is handling back animation. Cancel system animation latency tracking.
cancelLatencyTracking();
- tryDispatchAppOnBackStarted(mActiveCallback, touchTracker.createStartEvent(null));
+ tryDispatchOnBackStarted(mActiveCallback, touchTracker.createStartEvent(null));
}
}
@@ -555,14 +557,13 @@
&& mBackNavigationInfo.isPrepareRemoteAnimation();
}
- private void tryDispatchAppOnBackStarted(
+ private void tryDispatchOnBackStarted(
IOnBackInvokedCallback callback,
BackMotionEvent backEvent) {
- if (mOnBackStartDispatched && callback != null) {
+ if (mOnBackStartDispatched || callback == null || !mPointerPilfered) {
return;
}
dispatchOnBackStarted(callback, backEvent);
- mOnBackStartDispatched = true;
}
private void dispatchOnBackStarted(
@@ -573,6 +574,7 @@
}
try {
callback.onBackStarted(backEvent);
+ mOnBackStartDispatched = true;
} catch (RemoteException e) {
Log.e(TAG, "dispatchOnBackStarted error: ", e);
}
@@ -872,6 +874,7 @@
mActiveCallback = null;
mShouldStartOnNextMoveEvent = false;
mOnBackStartDispatched = false;
+ mPointerPilfered = false;
mShellBackAnimationRegistry.resetDefaultCrossActivity();
cancelLatencyTracking();
if (mBackNavigationInfo != null) {
@@ -957,15 +960,7 @@
mCurrentTracker.updateStartLocation();
BackMotionEvent startEvent =
mCurrentTracker.createStartEvent(apps[0]);
- // {@code mActiveCallback} is the callback from
- // the BackAnimationRunners and not a real app-side
- // callback. We also dispatch to the app-side callback
- // (which should be a system callback with PRIORITY_SYSTEM)
- // to keep consistent with app registered callbacks.
dispatchOnBackStarted(mActiveCallback, startEvent);
- tryDispatchAppOnBackStarted(
- mBackNavigationInfo.getOnBackInvokedCallback(),
- startEvent);
}
// Dispatch the first progress after animation start for
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java
index e0f0556..15350fb 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java
@@ -122,6 +122,7 @@
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
+import java.util.Locale;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
@@ -247,6 +248,9 @@
/** Saved font scale, used to detect font size changes in {@link #onConfigurationChanged}. */
private float mFontScale = 0;
+ /** Saved locale, used to detect local changes in {@link #onConfigurationChanged}. */
+ private Locale mLocale = null;
+
/** Saved direction, used to detect layout direction changes @link #onConfigChanged}. */
private int mLayoutDirection = View.LAYOUT_DIRECTION_UNDEFINED;
@@ -683,6 +687,17 @@
mDataRepository.removeBubblesForUser(removedUserId, parentUserId);
}
+ /** Called when sensitive notification state has changed */
+ public void onSensitiveNotificationProtectionStateChanged(
+ boolean sensitiveNotificationProtectionActive) {
+ if (mStackView != null) {
+ mStackView.onSensitiveNotificationProtectionStateChanged(
+ sensitiveNotificationProtectionActive);
+ ProtoLog.d(WM_SHELL_BUBBLES, "onSensitiveNotificationProtectionStateChanged=%b",
+ sensitiveNotificationProtectionActive);
+ }
+ }
+
/** Whether bubbles are showing in the bubble bar. */
public boolean isShowingAsBubbleBar() {
return canShowAsBubbleBar() && mBubbleStateListener != null;
@@ -1057,6 +1072,11 @@
mLayoutDirection = newConfig.getLayoutDirection();
mStackView.onLayoutDirectionChanged(mLayoutDirection);
}
+ Locale newLocale = newConfig.locale;
+ if (newLocale != null && !newLocale.equals(mLocale)) {
+ mLocale = newLocale;
+ mStackView.updateLocale();
+ }
}
}
@@ -2583,6 +2603,14 @@
mMainExecutor.execute(
() -> BubbleController.this.onNotificationPanelExpandedChanged(expanded));
}
+
+ @Override
+ public void onSensitiveNotificationProtectionStateChanged(
+ boolean sensitiveNotificationProtectionActive) {
+ mMainExecutor.execute(
+ () -> BubbleController.this.onSensitiveNotificationProtectionStateChanged(
+ sensitiveNotificationProtectionActive));
+ }
}
/**
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleExpandedView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleExpandedView.java
index 123693d..74f087b 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleExpandedView.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleExpandedView.java
@@ -517,6 +517,15 @@
}
}
+ void updateLocale() {
+ if (mManageButton != null) {
+ mManageButton.setText(mContext.getString(R.string.manage_bubbles_text));
+ }
+ if (mOverflowView != null) {
+ mOverflowView.updateLocale();
+ }
+ }
+
void applyThemeAttrs() {
final TypedArray ta = mContext.obtainStyledAttributes(new int[]{
android.R.attr.dialogCornerRadius,
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleOverflowContainerView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleOverflowContainerView.java
index b06de4f..633b01b 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleOverflowContainerView.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleOverflowContainerView.java
@@ -242,6 +242,11 @@
mEmptyStateSubtitle.setTextSize(TypedValue.COMPLEX_UNIT_PX, fontSize);
}
+ public void updateLocale() {
+ mEmptyStateTitle.setText(mContext.getString(R.string.bubble_overflow_empty_title));
+ mEmptyStateSubtitle.setText(mContext.getString(R.string.bubble_overflow_empty_subtitle));
+ }
+
private final BubbleData.Listener mDataListener = new BubbleData.Listener() {
@Override
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java
index b23fd52..8fd6ffe 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java
@@ -291,6 +291,11 @@
*/
private boolean mRemovingLastBubbleWhileExpanded = false;
+ /**
+ * Whether sensitive notification protection should disable flyout
+ */
+ private boolean mSensitiveNotificationProtectionActive = false;
+
/** Animator for animating the expanded view's alpha (including the TaskView inside it). */
private final ValueAnimator mExpandedViewAlphaAnimator = ValueAnimator.ofFloat(0f, 1f);
@@ -1447,6 +1452,12 @@
}
}
+ void updateLocale() {
+ if (mBubbleOverflow != null && mBubbleOverflow.getExpandedView() != null) {
+ mBubbleOverflow.getExpandedView().updateLocale();
+ }
+ }
+
private void updateOverflow() {
mBubbleOverflow.update();
mBubbleContainer.reorderView(mBubbleOverflow.getIconView(),
@@ -2199,6 +2210,11 @@
}
}
+ void onSensitiveNotificationProtectionStateChanged(
+ boolean sensitiveNotificationProtectionActive) {
+ mSensitiveNotificationProtectionActive = sensitiveNotificationProtectionActive;
+ }
+
/**
* Asks the BubbleController to hide the IME from anywhere, whether it's focused on Bubbles or
* not.
@@ -2842,6 +2858,7 @@
|| isExpanded()
|| mIsExpansionAnimating
|| mIsGestureInProgress
+ || mSensitiveNotificationProtectionActive
|| mBubbleToExpandAfterFlyoutCollapse != null
|| bubbleView == null) {
if (bubbleView != null && mFlyout.getVisibility() != VISIBLE) {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubbles.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubbles.java
index 28af0ca..26077cf 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubbles.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubbles.java
@@ -286,6 +286,16 @@
void onUserRemoved(int removedUserId);
/**
+ * Called when the Sensitive notification protection state has changed, such as when media
+ * projection starts and stops.
+ *
+ * @param sensitiveNotificationProtectionActive {@code true} if notifications should be
+ * protected
+ */
+ void onSensitiveNotificationProtectionStateChanged(
+ boolean sensitiveNotificationProtectionActive);
+
+ /**
* A listener to be notified of bubble state changes, used by launcher to render bubbles in
* its process.
*/
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/OWNERS b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/OWNERS
index 8271014..08c7031 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/OWNERS
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/OWNERS
@@ -1,2 +1,6 @@
# WM shell sub-module bubble owner
madym@google.com
+atsjenk@google.com
+liranb@google.com
+sukeshram@google.com
+mpodolian@google.com
diff --git a/libs/hwui/Android.bp b/libs/hwui/Android.bp
index e4f3e2d..40239b8 100644
--- a/libs/hwui/Android.bp
+++ b/libs/hwui/Android.bp
@@ -155,6 +155,7 @@
host: {
static_libs: [
"libandroidfw",
+ "libhostgraphics",
"libutils",
],
},
@@ -427,6 +428,7 @@
"jni/MovieImpl.cpp",
"jni/pdf/PdfDocument.cpp",
"jni/pdf/PdfEditor.cpp",
+ "jni/pdf/PdfRenderer.cpp",
"jni/pdf/PdfUtils.cpp",
],
shared_libs: [
@@ -501,6 +503,17 @@
],
header_libs: ["android_graphics_jni_headers"],
export_header_lib_headers: ["android_graphics_jni_headers"],
+ target: {
+ android: {
+ export_include_dirs: ["platform/android"],
+ },
+ host: {
+ export_include_dirs: ["platform/host"],
+ },
+ windows: {
+ enabled: true,
+ },
+ },
}
cc_defaults {
@@ -538,6 +551,7 @@
"utils/Blur.cpp",
"utils/Color.cpp",
"utils/LinearAllocator.cpp",
+ "utils/StringUtils.cpp",
"utils/TypefaceUtils.cpp",
"utils/VectorDrawableUtils.cpp",
"AnimationContext.cpp",
@@ -552,6 +566,7 @@
"Mesh.cpp",
"MemoryPolicy.cpp",
"PathParser.cpp",
+ "ProfileData.cpp",
"Properties.cpp",
"PropertyValuesAnimatorSet.cpp",
"PropertyValuesHolder.cpp",
@@ -569,12 +584,13 @@
export_proto_headers: true,
},
+ header_libs: ["libandroid_headers_private"],
+
target: {
android: {
- header_libs: [
- "libandroid_headers_private",
- "libtonemap_headers",
- ],
+ header_libs: ["libtonemap_headers"],
+
+ local_include_dirs: ["platform/android"],
srcs: [
"hwui/AnimatedImageThread.cpp",
@@ -605,7 +621,6 @@
"thread/CommonPool.cpp",
"utils/GLUtils.cpp",
"utils/NdkUtils.cpp",
- "utils/StringUtils.cpp",
"AutoBackendTextureRelease.cpp",
"DeferredLayerUpdater.cpp",
"DeviceInfo.cpp",
@@ -617,7 +632,6 @@
"FrameMetricsReporter.cpp",
"Layer.cpp",
"LayerUpdateQueue.cpp",
- "ProfileData.cpp",
"ProfileDataContainer.cpp",
"Readback.cpp",
"TreeInfo.cpp",
@@ -628,6 +642,21 @@
// Allow implicit fallthroughs in HardwareBitmapUploader.cpp until they are fixed.
cflags: ["-Wno-implicit-fallthrough"],
},
+ host: {
+ header_libs: ["libnativebase_headers"],
+
+ local_include_dirs: ["platform/host"],
+
+ srcs: [
+ "platform/host/renderthread/CacheManager.cpp",
+ "platform/host/renderthread/RenderThread.cpp",
+ "platform/host/ProfileDataContainer.cpp",
+ "platform/host/Readback.cpp",
+ "platform/host/WebViewFunctorManager.cpp",
+ ],
+
+ cflags: ["-Wno-unused-private-field"],
+ },
},
}
@@ -663,6 +692,7 @@
header_libs: ["libandroid_headers_private"],
target: {
android: {
+ local_include_dirs: ["platform/android"],
shared_libs: [
"libgui",
"libui",
diff --git a/libs/hwui/ProfileData.cpp b/libs/hwui/ProfileData.cpp
index 3d0ca0a..7be9541 100644
--- a/libs/hwui/ProfileData.cpp
+++ b/libs/hwui/ProfileData.cpp
@@ -110,6 +110,7 @@
}
void ProfileData::dump(int fd) const {
+#ifdef __ANDROID__
dprintf(fd, "\nStats since: %" PRIu64 "ns", mStatStartTime);
dprintf(fd, "\nTotal frames rendered: %u", mTotalFrameCount);
dprintf(fd, "\nJanky frames: %u (%.2f%%)", mJankFrameCount,
@@ -138,6 +139,7 @@
dprintf(fd, " %ums=%u", entry.renderTimeMs, entry.frameCount);
});
dprintf(fd, "\n");
+#endif
}
uint32_t ProfileData::findPercentile(int percentile) const {
diff --git a/libs/hwui/apex/jni_runtime.cpp b/libs/hwui/apex/jni_runtime.cpp
index fb0cdb0..883f273 100644
--- a/libs/hwui/apex/jni_runtime.cpp
+++ b/libs/hwui/apex/jni_runtime.cpp
@@ -70,6 +70,7 @@
extern int register_android_graphics_fonts_FontFamily(JNIEnv* env);
extern int register_android_graphics_pdf_PdfDocument(JNIEnv* env);
extern int register_android_graphics_pdf_PdfEditor(JNIEnv* env);
+extern int register_android_graphics_pdf_PdfRenderer(JNIEnv* env);
extern int register_android_graphics_text_MeasuredText(JNIEnv* env);
extern int register_android_graphics_text_LineBreaker(JNIEnv *env);
extern int register_android_graphics_text_TextShaper(JNIEnv *env);
@@ -141,6 +142,7 @@
REG_JNI(register_android_graphics_fonts_FontFamily),
REG_JNI(register_android_graphics_pdf_PdfDocument),
REG_JNI(register_android_graphics_pdf_PdfEditor),
+ REG_JNI(register_android_graphics_pdf_PdfRenderer),
REG_JNI(register_android_graphics_text_MeasuredText),
REG_JNI(register_android_graphics_text_LineBreaker),
REG_JNI(register_android_graphics_text_TextShaper),
diff --git a/libs/hwui/jni/pdf/PdfRenderer.cpp b/libs/hwui/jni/pdf/PdfRenderer.cpp
new file mode 100644
index 0000000..cc1f961
--- /dev/null
+++ b/libs/hwui/jni/pdf/PdfRenderer.cpp
@@ -0,0 +1,134 @@
+/*
+ * Copyright (C) 2014 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.
+ */
+
+#include "PdfUtils.h"
+
+#include "GraphicsJNI.h"
+#include "SkBitmap.h"
+#include "SkMatrix.h"
+#include "fpdfview.h"
+
+#include <vector>
+#include <utils/Log.h>
+#include <unistd.h>
+#include <sys/types.h>
+#include <unistd.h>
+
+namespace android {
+
+static const int RENDER_MODE_FOR_DISPLAY = 1;
+static const int RENDER_MODE_FOR_PRINT = 2;
+
+static struct {
+ jfieldID x;
+ jfieldID y;
+} gPointClassInfo;
+
+static jlong nativeOpenPageAndGetSize(JNIEnv* env, jclass thiz, jlong documentPtr,
+ jint pageIndex, jobject outSize) {
+ FPDF_DOCUMENT document = reinterpret_cast<FPDF_DOCUMENT>(documentPtr);
+
+ FPDF_PAGE page = FPDF_LoadPage(document, pageIndex);
+ if (!page) {
+ jniThrowException(env, "java/lang/IllegalStateException",
+ "cannot load page");
+ return -1;
+ }
+
+ double width = 0;
+ double height = 0;
+
+ int result = FPDF_GetPageSizeByIndex(document, pageIndex, &width, &height);
+ if (!result) {
+ jniThrowException(env, "java/lang/IllegalStateException",
+ "cannot get page size");
+ return -1;
+ }
+
+ env->SetIntField(outSize, gPointClassInfo.x, width);
+ env->SetIntField(outSize, gPointClassInfo.y, height);
+
+ return reinterpret_cast<jlong>(page);
+}
+
+static void nativeClosePage(JNIEnv* env, jclass thiz, jlong pagePtr) {
+ FPDF_PAGE page = reinterpret_cast<FPDF_PAGE>(pagePtr);
+ FPDF_ClosePage(page);
+}
+
+static void nativeRenderPage(JNIEnv* env, jclass thiz, jlong documentPtr, jlong pagePtr,
+ jlong bitmapPtr, jint clipLeft, jint clipTop, jint clipRight, jint clipBottom,
+ jlong transformPtr, jint renderMode) {
+ FPDF_PAGE page = reinterpret_cast<FPDF_PAGE>(pagePtr);
+
+ SkBitmap skBitmap;
+ bitmap::toBitmap(bitmapPtr).getSkBitmap(&skBitmap);
+
+ const int stride = skBitmap.width() * 4;
+
+ FPDF_BITMAP bitmap = FPDFBitmap_CreateEx(skBitmap.width(), skBitmap.height(),
+ FPDFBitmap_BGRA, skBitmap.getPixels(), stride);
+
+ int renderFlags = FPDF_REVERSE_BYTE_ORDER;
+ if (renderMode == RENDER_MODE_FOR_DISPLAY) {
+ renderFlags |= FPDF_LCD_TEXT;
+ } else if (renderMode == RENDER_MODE_FOR_PRINT) {
+ renderFlags |= FPDF_PRINTING;
+ }
+
+ SkMatrix matrix = *reinterpret_cast<SkMatrix*>(transformPtr);
+ SkScalar transformValues[6];
+ if (!matrix.asAffine(transformValues)) {
+ jniThrowException(env, "java/lang/IllegalArgumentException",
+ "transform matrix has perspective. Only affine matrices are allowed.");
+ return;
+ }
+
+ FS_MATRIX transform = {transformValues[SkMatrix::kAScaleX], transformValues[SkMatrix::kASkewY],
+ transformValues[SkMatrix::kASkewX], transformValues[SkMatrix::kAScaleY],
+ transformValues[SkMatrix::kATransX],
+ transformValues[SkMatrix::kATransY]};
+
+ FS_RECTF clip = {(float) clipLeft, (float) clipTop, (float) clipRight, (float) clipBottom};
+
+ FPDF_RenderPageBitmapWithMatrix(bitmap, page, &transform, &clip, renderFlags);
+
+ skBitmap.notifyPixelsChanged();
+}
+
+static const JNINativeMethod gPdfRenderer_Methods[] = {
+ {"nativeCreate", "(IJ)J", (void*) nativeOpen},
+ {"nativeClose", "(J)V", (void*) nativeClose},
+ {"nativeGetPageCount", "(J)I", (void*) nativeGetPageCount},
+ {"nativeScaleForPrinting", "(J)Z", (void*) nativeScaleForPrinting},
+ {"nativeRenderPage", "(JJJIIIIJI)V", (void*) nativeRenderPage},
+ {"nativeOpenPageAndGetSize", "(JILandroid/graphics/Point;)J", (void*) nativeOpenPageAndGetSize},
+ {"nativeClosePage", "(J)V", (void*) nativeClosePage}
+};
+
+int register_android_graphics_pdf_PdfRenderer(JNIEnv* env) {
+ int result = RegisterMethodsOrDie(
+ env, "android/graphics/pdf/PdfRenderer", gPdfRenderer_Methods,
+ NELEM(gPdfRenderer_Methods));
+
+ jclass clazz = FindClassOrDie(env, "android/graphics/Point");
+ gPointClassInfo.x = GetFieldIDOrDie(env, clazz, "x", "I");
+ gPointClassInfo.y = GetFieldIDOrDie(env, clazz, "y", "I");
+
+ return result;
+};
+
+};
diff --git a/libs/hwui/thread/ThreadBase.h b/libs/hwui/platform/android/thread/ThreadBase.h
similarity index 98%
rename from libs/hwui/thread/ThreadBase.h
rename to libs/hwui/platform/android/thread/ThreadBase.h
index 0289d3f..2f3581f 100644
--- a/libs/hwui/thread/ThreadBase.h
+++ b/libs/hwui/platform/android/thread/ThreadBase.h
@@ -17,14 +17,14 @@
#ifndef HWUI_THREADBASE_H
#define HWUI_THREADBASE_H
-#include "WorkQueue.h"
-#include "utils/Macros.h"
-
#include <utils/Looper.h>
#include <utils/Thread.h>
#include <algorithm>
+#include "thread/WorkQueue.h"
+#include "utils/Macros.h"
+
namespace android::uirenderer {
class ThreadBase : public Thread {
diff --git a/libs/hwui/platform/host/ProfileDataContainer.cpp b/libs/hwui/platform/host/ProfileDataContainer.cpp
new file mode 100644
index 0000000..9ed1b02
--- /dev/null
+++ b/libs/hwui/platform/host/ProfileDataContainer.cpp
@@ -0,0 +1,40 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "ProfileDataContainer.h"
+
+#include <log/log.h>
+
+namespace android {
+namespace uirenderer {
+
+void ProfileDataContainer::freeData() REQUIRES(mJankDataMutex) {
+ delete mData;
+ mIsMapped = false;
+ mData = nullptr;
+}
+
+void ProfileDataContainer::rotateStorage() {
+ std::lock_guard lock(mJankDataMutex);
+ mData->reset();
+}
+
+void ProfileDataContainer::switchStorageToAshmem(int ashmemfd) {
+ ALOGE("Ashmem is not supported for non-Android configurations");
+}
+
+} /* namespace uirenderer */
+} /* namespace android */
diff --git a/libs/hwui/platform/host/Readback.cpp b/libs/hwui/platform/host/Readback.cpp
new file mode 100644
index 0000000..b024ec0
--- /dev/null
+++ b/libs/hwui/platform/host/Readback.cpp
@@ -0,0 +1,50 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "Readback.h"
+
+using namespace android::uirenderer::renderthread;
+
+namespace android {
+namespace uirenderer {
+
+void Readback::copySurfaceInto(ANativeWindow* window, const std::shared_ptr<CopyRequest>& request) {
+}
+
+CopyResult Readback::copyHWBitmapInto(Bitmap* hwBitmap, SkBitmap* bitmap) {
+ return CopyResult::UnknownError;
+}
+
+CopyResult Readback::copyLayerInto(DeferredLayerUpdater* deferredLayer, SkBitmap* bitmap) {
+ return CopyResult::UnknownError;
+}
+
+CopyResult Readback::copyImageInto(const sk_sp<SkImage>& image, SkBitmap* bitmap) {
+ return CopyResult::UnknownError;
+}
+
+CopyResult Readback::copyImageInto(const sk_sp<SkImage>& image, const Rect& srcRect,
+ SkBitmap* bitmap) {
+ return CopyResult::UnknownError;
+}
+
+bool Readback::copyLayerInto(Layer* layer, const SkRect* srcRect, const SkRect* dstRect,
+ SkBitmap* bitmap) {
+ return false;
+}
+
+} /* namespace uirenderer */
+} /* namespace android */
diff --git a/libs/hwui/platform/host/WebViewFunctorManager.cpp b/libs/hwui/platform/host/WebViewFunctorManager.cpp
new file mode 100644
index 0000000..1d16655
--- /dev/null
+++ b/libs/hwui/platform/host/WebViewFunctorManager.cpp
@@ -0,0 +1,75 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "WebViewFunctorManager.h"
+
+namespace android::uirenderer {
+
+WebViewFunctor::WebViewFunctor(void* data, const WebViewFunctorCallbacks& callbacks,
+ RenderMode functorMode)
+ : mData(data) {}
+
+WebViewFunctor::~WebViewFunctor() {}
+
+void WebViewFunctor::sync(const WebViewSyncData& syncData) const {}
+
+void WebViewFunctor::onRemovedFromTree() {}
+
+bool WebViewFunctor::prepareRootSurfaceControl() {
+ return true;
+}
+
+void WebViewFunctor::drawGl(const DrawGlInfo& drawInfo) {}
+
+void WebViewFunctor::initVk(const VkFunctorInitParams& params) {}
+
+void WebViewFunctor::drawVk(const VkFunctorDrawParams& params) {}
+
+void WebViewFunctor::postDrawVk() {}
+
+void WebViewFunctor::destroyContext() {}
+
+void WebViewFunctor::removeOverlays() {}
+
+ASurfaceControl* WebViewFunctor::getSurfaceControl() {
+ return mSurfaceControl;
+}
+
+void WebViewFunctor::mergeTransaction(ASurfaceTransaction* transaction) {}
+
+void WebViewFunctor::reparentSurfaceControl(ASurfaceControl* parent) {}
+
+WebViewFunctorManager& WebViewFunctorManager::instance() {
+ static WebViewFunctorManager sInstance;
+ return sInstance;
+}
+
+int WebViewFunctorManager::createFunctor(void* data, const WebViewFunctorCallbacks& callbacks,
+ RenderMode functorMode) {
+ return 0;
+}
+
+void WebViewFunctorManager::releaseFunctor(int functor) {}
+
+void WebViewFunctorManager::onContextDestroyed() {}
+
+void WebViewFunctorManager::destroyFunctor(int functor) {}
+
+sp<WebViewFunctor::Handle> WebViewFunctorManager::handleFor(int functor) {
+ return nullptr;
+}
+
+} // namespace android::uirenderer
diff --git a/libs/hwui/platform/host/renderthread/CacheManager.cpp b/libs/hwui/platform/host/renderthread/CacheManager.cpp
new file mode 100644
index 0000000..b03f400
--- /dev/null
+++ b/libs/hwui/platform/host/renderthread/CacheManager.cpp
@@ -0,0 +1,64 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "renderthread/CacheManager.h"
+
+namespace android {
+namespace uirenderer {
+namespace renderthread {
+
+CacheManager::CacheManager(RenderThread& thread)
+ : mRenderThread(thread), mMemoryPolicy(loadMemoryPolicy()) {}
+
+void CacheManager::setupCacheLimits() {}
+
+void CacheManager::destroy() {}
+
+void CacheManager::trimMemory(TrimLevel mode) {}
+
+void CacheManager::trimCaches(CacheTrimLevel mode) {}
+
+void CacheManager::trimStaleResources() {}
+
+void CacheManager::getMemoryUsage(size_t* cpuUsage, size_t* gpuUsage) {}
+
+void CacheManager::dumpMemoryUsage(String8& log, const RenderState* renderState) {}
+
+void CacheManager::onFrameCompleted() {}
+
+void CacheManager::onThreadIdle() {}
+
+void CacheManager::scheduleDestroyContext() {}
+
+void CacheManager::cancelDestroyContext() {}
+
+bool CacheManager::areAllContextsStopped() {
+ return false;
+}
+
+void CacheManager::checkUiHidden() {}
+
+void CacheManager::registerCanvasContext(CanvasContext* context) {}
+
+void CacheManager::unregisterCanvasContext(CanvasContext* context) {}
+
+void CacheManager::onContextStopped(CanvasContext* context) {}
+
+void CacheManager::notifyNextFrameSize(int width, int height) {}
+
+} /* namespace renderthread */
+} /* namespace uirenderer */
+} /* namespace android */
diff --git a/libs/hwui/platform/host/renderthread/RenderThread.cpp b/libs/hwui/platform/host/renderthread/RenderThread.cpp
new file mode 100644
index 0000000..6f08b59
--- /dev/null
+++ b/libs/hwui/platform/host/renderthread/RenderThread.cpp
@@ -0,0 +1,141 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "renderthread/RenderThread.h"
+
+#include "Readback.h"
+#include "renderthread/VulkanManager.h"
+
+namespace android {
+namespace uirenderer {
+namespace renderthread {
+
+static bool gHasRenderThreadInstance = false;
+static JVMAttachHook gOnStartHook = nullptr;
+
+ASurfaceControlFunctions::ASurfaceControlFunctions() {}
+
+bool RenderThread::hasInstance() {
+ return gHasRenderThreadInstance;
+}
+
+void RenderThread::setOnStartHook(JVMAttachHook onStartHook) {
+ LOG_ALWAYS_FATAL_IF(hasInstance(), "can't set an onStartHook after we've started...");
+ gOnStartHook = onStartHook;
+}
+
+JVMAttachHook RenderThread::getOnStartHook() {
+ return gOnStartHook;
+}
+
+RenderThread& RenderThread::getInstance() {
+ [[clang::no_destroy]] static sp<RenderThread> sInstance = []() {
+ sp<RenderThread> thread = sp<RenderThread>::make();
+ thread->start("RenderThread");
+ return thread;
+ }();
+ gHasRenderThreadInstance = true;
+ return *sInstance;
+}
+
+RenderThread::RenderThread()
+ : ThreadBase()
+ , mVsyncSource(nullptr)
+ , mVsyncRequested(false)
+ , mFrameCallbackTaskPending(false)
+ , mRenderState(nullptr)
+ , mEglManager(nullptr)
+ , mFunctorManager(WebViewFunctorManager::instance())
+ , mGlobalProfileData(mJankDataMutex) {
+ Properties::load();
+}
+
+RenderThread::~RenderThread() {}
+
+void RenderThread::initThreadLocals() {
+ mCacheManager = new CacheManager(*this);
+}
+
+void RenderThread::requireGlContext() {}
+
+void RenderThread::requireVkContext() {}
+
+void RenderThread::initGrContextOptions(GrContextOptions& options) {}
+
+void RenderThread::destroyRenderingContext() {}
+
+VulkanManager& RenderThread::vulkanManager() {
+ return *mVkManager;
+}
+
+void RenderThread::dumpGraphicsMemory(int fd, bool includeProfileData) {}
+
+void RenderThread::getMemoryUsage(size_t* cpuUsage, size_t* gpuUsage) {}
+
+Readback& RenderThread::readback() {
+ if (!mReadback) {
+ mReadback = new Readback(*this);
+ }
+
+ return *mReadback;
+}
+
+void RenderThread::setGrContext(sk_sp<GrDirectContext> context) {}
+
+sk_sp<GrDirectContext> RenderThread::requireGrContext() {
+ return mGrContext;
+}
+
+bool RenderThread::threadLoop() {
+ if (gOnStartHook) {
+ gOnStartHook("RenderThread");
+ }
+ initThreadLocals();
+
+ while (true) {
+ waitForWork();
+ processQueue();
+ mCacheManager->onThreadIdle();
+ }
+
+ return false;
+}
+
+void RenderThread::postFrameCallback(IFrameCallback* callback) {}
+
+bool RenderThread::removeFrameCallback(IFrameCallback* callback) {
+ return false;
+}
+
+void RenderThread::pushBackFrameCallback(IFrameCallback* callback) {}
+
+sk_sp<Bitmap> RenderThread::allocateHardwareBitmap(SkBitmap& skBitmap) {
+ return nullptr;
+}
+
+bool RenderThread::isCurrent() {
+ return true;
+}
+
+void RenderThread::preload() {}
+
+void RenderThread::trimMemory(TrimLevel level) {}
+
+void RenderThread::trimCaches(CacheTrimLevel level) {}
+
+} /* namespace renderthread */
+} /* namespace uirenderer */
+} /* namespace android */
diff --git a/libs/hwui/thread/ThreadBase.h b/libs/hwui/platform/host/thread/ThreadBase.h
similarity index 65%
copy from libs/hwui/thread/ThreadBase.h
copy to libs/hwui/platform/host/thread/ThreadBase.h
index 0289d3f..d709430 100644
--- a/libs/hwui/thread/ThreadBase.h
+++ b/libs/hwui/platform/host/thread/ThreadBase.h
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2017 The Android Open Source Project
+ * Copyright (C) 2024 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -17,31 +17,24 @@
#ifndef HWUI_THREADBASE_H
#define HWUI_THREADBASE_H
-#include "WorkQueue.h"
-#include "utils/Macros.h"
-
-#include <utils/Looper.h>
#include <utils/Thread.h>
#include <algorithm>
+#include "thread/WorkQueue.h"
+#include "utils/Macros.h"
+
namespace android::uirenderer {
class ThreadBase : public Thread {
PREVENT_COPY_AND_ASSIGN(ThreadBase);
public:
- ThreadBase()
- : Thread(false)
- , mLooper(new Looper(false))
- , mQueue([this]() { mLooper->wake(); }, mLock) {}
+ ThreadBase() : Thread(false), mQueue([this]() { mCondition.notify_all(); }, mLock) {}
WorkQueue& queue() { return mQueue; }
- void requestExit() {
- Thread::requestExit();
- mLooper->wake();
- }
+ void requestExit() { Thread::requestExit(); }
void start(const char* name = "ThreadBase") { Thread::run(name); }
@@ -51,37 +44,31 @@
protected:
void waitForWork() {
- nsecs_t nextWakeup;
- {
- std::unique_lock lock{mLock};
- nextWakeup = mQueue.nextWakeup(lock);
- }
- int timeout = -1;
+ std::unique_lock lock{mLock};
+ nsecs_t nextWakeup = mQueue.nextWakeup(lock);
+ std::chrono::nanoseconds duration = std::chrono::nanoseconds::max();
if (nextWakeup < std::numeric_limits<nsecs_t>::max()) {
- timeout = ns2ms(nextWakeup - WorkQueue::clock::now());
+ int timeout = nextWakeup - WorkQueue::clock::now();
if (timeout < 0) timeout = 0;
+ duration = std::chrono::nanoseconds(timeout);
}
- int result = mLooper->pollOnce(timeout);
- LOG_ALWAYS_FATAL_IF(result == Looper::POLL_ERROR, "RenderThread Looper POLL_ERROR!");
+ mCondition.wait_for(lock, duration);
}
void processQueue() { mQueue.process(); }
virtual bool threadLoop() override {
- Looper::setForThread(mLooper);
while (!exitPending()) {
waitForWork();
processQueue();
}
- Looper::setForThread(nullptr);
return false;
}
- sp<Looper> mLooper;
-
private:
WorkQueue mQueue;
std::mutex mLock;
+ std::condition_variable mCondition;
};
} // namespace android::uirenderer
diff --git a/libs/hwui/private/hwui/WebViewFunctor.h b/libs/hwui/private/hwui/WebViewFunctor.h
index 22ae59e..493c943 100644
--- a/libs/hwui/private/hwui/WebViewFunctor.h
+++ b/libs/hwui/private/hwui/WebViewFunctor.h
@@ -17,15 +17,7 @@
#ifndef FRAMEWORKS_BASE_WEBVIEWFUNCTOR_H
#define FRAMEWORKS_BASE_WEBVIEWFUNCTOR_H
-#ifdef __ANDROID__ // Layoutlib does not support surface control
#include <android/surface_control.h>
-#else
-// To avoid ifdefs around overlay implementation all over the place we typedef these to void *. They
-// won't be used.
-typedef void* ASurfaceControl;
-typedef void* ASurfaceTransaction;
-#endif
-
#include <cutils/compiler.h>
#include <private/hwui/DrawGlInfo.h>
#include <private/hwui/DrawVkInfo.h>
diff --git a/libs/hwui/renderthread/RenderThread.cpp b/libs/hwui/renderthread/RenderThread.cpp
index 623ee4e..a024aeb 100644
--- a/libs/hwui/renderthread/RenderThread.cpp
+++ b/libs/hwui/renderthread/RenderThread.cpp
@@ -17,11 +17,12 @@
#include "RenderThread.h"
#include <GrContextOptions.h>
-#include <include/gpu/ganesh/gl/GrGLDirectContext.h>
#include <android-base/properties.h>
#include <dlfcn.h>
#include <gl/GrGLInterface.h>
#include <gui/TraceUtils.h>
+#include <include/gpu/ganesh/gl/GrGLDirectContext.h>
+#include <private/android/choreographer.h>
#include <sys/resource.h>
#include <ui/FatVector.h>
#include <utils/Condition.h>
diff --git a/libs/hwui/renderthread/RenderThread.h b/libs/hwui/renderthread/RenderThread.h
index 79e57de..045d26f 100644
--- a/libs/hwui/renderthread/RenderThread.h
+++ b/libs/hwui/renderthread/RenderThread.h
@@ -20,10 +20,7 @@
#include <GrDirectContext.h>
#include <SkBitmap.h>
#include <cutils/compiler.h>
-#include <private/android/choreographer.h>
#include <surface_control_private.h>
-#include <thread/ThreadBase.h>
-#include <utils/Looper.h>
#include <utils/Thread.h>
#include <memory>
diff --git a/nfc/java/android/nfc/cardemulation/ApduServiceInfo.java b/nfc/java/android/nfc/cardemulation/ApduServiceInfo.java
index 5242a7d..c81b95b 100644
--- a/nfc/java/android/nfc/cardemulation/ApduServiceInfo.java
+++ b/nfc/java/android/nfc/cardemulation/ApduServiceInfo.java
@@ -176,11 +176,25 @@
List<AidGroup> staticAidGroups, List<AidGroup> dynamicAidGroups,
boolean requiresUnlock, boolean requiresScreenOn, int bannerResource, int uid,
String settingsActivityName, String offHost, String staticOffHost, boolean isEnabled) {
+ this(info, onHost, description, staticAidGroups, dynamicAidGroups,
+ requiresUnlock, requiresScreenOn, bannerResource, uid,
+ settingsActivityName, offHost, staticOffHost, isEnabled,
+ new HashMap<String, Boolean>());
+ }
+
+ /**
+ * @hide
+ */
+ public ApduServiceInfo(ResolveInfo info, boolean onHost, String description,
+ List<AidGroup> staticAidGroups, List<AidGroup> dynamicAidGroups,
+ boolean requiresUnlock, boolean requiresScreenOn, int bannerResource, int uid,
+ String settingsActivityName, String offHost, String staticOffHost, boolean isEnabled,
+ HashMap<String, Boolean> autoTransact) {
this.mService = info;
this.mDescription = description;
this.mStaticAidGroups = new HashMap<String, AidGroup>();
this.mDynamicAidGroups = new HashMap<String, AidGroup>();
- this.mAutoTransact = new HashMap<String, Boolean>();
+ this.mAutoTransact = autoTransact;
this.mOffHostName = offHost;
this.mStaticOffHostName = staticOffHost;
this.mOnHost = onHost;
@@ -196,7 +210,6 @@
this.mUid = uid;
this.mSettingsActivityName = settingsActivityName;
this.mCategoryOtherServiceEnabled = isEnabled;
-
}
/**
@@ -857,6 +870,8 @@
dest.writeString(mSettingsActivityName);
dest.writeInt(mCategoryOtherServiceEnabled ? 1 : 0);
+ dest.writeInt(mAutoTransact.size());
+ dest.writeMap(mAutoTransact);
};
@FlaggedApi(Flags.FLAG_ENABLE_NFC_MAINLINE)
@@ -885,10 +900,15 @@
int uid = source.readInt();
String settingsActivityName = source.readString();
boolean isEnabled = source.readInt() != 0;
+ int autoTransactSize = source.readInt();
+ HashMap<String, Boolean> autoTransact =
+ new HashMap<String, Boolean>(autoTransactSize);
+ source.readMap(autoTransact, getClass().getClassLoader(),
+ String.class, Boolean.class);
return new ApduServiceInfo(info, onHost, description, staticAidGroups,
dynamicAidGroups, requiresUnlock, requiresScreenOn, bannerResource, uid,
settingsActivityName, offHostName, staticOffHostName,
- isEnabled);
+ isEnabled, autoTransact);
}
@Override
diff --git a/packages/PrintRecommendationService/res/values/strings.xml b/packages/PrintRecommendationService/res/values/strings.xml
index 2bab1b6..b6c45b7 100644
--- a/packages/PrintRecommendationService/res/values/strings.xml
+++ b/packages/PrintRecommendationService/res/values/strings.xml
@@ -18,7 +18,6 @@
-->
<resources>
- <string name="plugin_vendor_google_cloud_print">Cloud Print</string>
<string name="plugin_vendor_hp">HP</string>
<string name="plugin_vendor_lexmark">Lexmark</string>
<string name="plugin_vendor_brother">Brother</string>
diff --git a/packages/PrintRecommendationService/src/com/android/printservice/recommendation/RecommendationServiceImpl.java b/packages/PrintRecommendationService/src/com/android/printservice/recommendation/RecommendationServiceImpl.java
index 5a756fe..4ec8883 100644
--- a/packages/PrintRecommendationService/src/com/android/printservice/recommendation/RecommendationServiceImpl.java
+++ b/packages/PrintRecommendationService/src/com/android/printservice/recommendation/RecommendationServiceImpl.java
@@ -23,7 +23,6 @@
import android.printservice.recommendation.RecommendationService;
import android.util.Log;
-import com.android.printservice.recommendation.plugin.google.CloudPrintPlugin;
import com.android.printservice.recommendation.plugin.hp.HPRecommendationPlugin;
import com.android.printservice.recommendation.plugin.mdnsFilter.MDNSFilterPlugin;
import com.android.printservice.recommendation.plugin.mdnsFilter.VendorConfig;
@@ -77,14 +76,6 @@
}
try {
- mPlugins.add(new RemotePrintServicePlugin(new CloudPrintPlugin(this), this,
- true));
- } catch (Exception e) {
- Log.e(LOG_TAG, "Could not initiate "
- + getString(R.string.plugin_vendor_google_cloud_print) + " plugin", e);
- }
-
- try {
mPlugins.add(new RemotePrintServicePlugin(new HPRecommendationPlugin(this), this,
false));
} catch (Exception e) {
diff --git a/packages/PrintRecommendationService/src/com/android/printservice/recommendation/plugin/google/CloudPrintPlugin.java b/packages/PrintRecommendationService/src/com/android/printservice/recommendation/plugin/google/CloudPrintPlugin.java
deleted file mode 100644
index 3029d10..0000000
--- a/packages/PrintRecommendationService/src/com/android/printservice/recommendation/plugin/google/CloudPrintPlugin.java
+++ /dev/null
@@ -1,166 +0,0 @@
-/*
- * Copyright (C) 2017 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.printservice.recommendation.plugin.google;
-
-import static com.android.printservice.recommendation.util.MDNSUtils.ATTRIBUTE_TY;
-
-import android.content.Context;
-import android.util.ArrayMap;
-import android.util.Log;
-
-import androidx.annotation.NonNull;
-import androidx.annotation.StringRes;
-
-import com.android.printservice.recommendation.PrintServicePlugin;
-import com.android.printservice.recommendation.R;
-import com.android.printservice.recommendation.util.MDNSFilteredDiscovery;
-
-import java.net.Inet4Address;
-import java.net.InetAddress;
-import java.nio.charset.StandardCharsets;
-import java.util.HashSet;
-import java.util.Map;
-import java.util.Set;
-
-/**
- * Plugin detecting <a href="https://developers.google.com/cloud-print/docs/privet">Google Cloud
- * Print</a> printers.
- */
-public class CloudPrintPlugin implements PrintServicePlugin {
- private static final String LOG_TAG = CloudPrintPlugin.class.getSimpleName();
- private static final boolean DEBUG = false;
-
- private static final String ATTRIBUTE_TXTVERS = "txtvers";
- private static final String ATTRIBUTE_URL = "url";
- private static final String ATTRIBUTE_TYPE = "type";
- private static final String ATTRIBUTE_ID = "id";
- private static final String ATTRIBUTE_CS = "cs";
-
- private static final String TYPE = "printer";
-
- private static final String PRIVET_SERVICE = "_privet._tcp";
-
- /** The required mDNS service types */
- private static final Set<String> PRINTER_SERVICE_TYPE = Set.of(
- PRIVET_SERVICE); // Not checking _printer_._sub
-
- /** All possible connection states */
- private static final Set<String> POSSIBLE_CONNECTION_STATES = Set.of(
- "online",
- "offline",
- "connecting",
- "not-configured");
-
- private static final byte SUPPORTED_TXTVERS = '1';
-
- /** The mDNS filtered discovery */
- private final MDNSFilteredDiscovery mMDNSFilteredDiscovery;
-
- /**
- * Create a plugin detecting Google Cloud Print printers.
- *
- * @param context The context the plugin runs in
- */
- public CloudPrintPlugin(@NonNull Context context) {
- mMDNSFilteredDiscovery = new MDNSFilteredDiscovery(context, PRINTER_SERVICE_TYPE,
- nsdServiceInfo -> {
- // The attributes are case insensitive. For faster searching create a clone of
- // the map with the attribute-keys all in lower case.
- ArrayMap<String, byte[]> caseInsensitiveAttributes =
- new ArrayMap<>(nsdServiceInfo.getAttributes().size());
- for (Map.Entry<String, byte[]> entry : nsdServiceInfo.getAttributes()
- .entrySet()) {
- caseInsensitiveAttributes.put(entry.getKey().toLowerCase(),
- entry.getValue());
- }
-
- if (DEBUG) {
- Log.i(LOG_TAG, nsdServiceInfo.getServiceName() + ":");
- Log.i(LOG_TAG, "type: " + nsdServiceInfo.getServiceType());
- Log.i(LOG_TAG, "host: " + nsdServiceInfo.getHost());
- for (Map.Entry<String, byte[]> entry : caseInsensitiveAttributes.entrySet()) {
- if (entry.getValue() == null) {
- Log.i(LOG_TAG, entry.getKey() + "= null");
- } else {
- Log.i(LOG_TAG, entry.getKey() + "=" + new String(entry.getValue(),
- StandardCharsets.UTF_8));
- }
- }
- }
-
- byte[] txtvers = caseInsensitiveAttributes.get(ATTRIBUTE_TXTVERS);
- if (txtvers == null || txtvers.length != 1 || txtvers[0] != SUPPORTED_TXTVERS) {
- // The spec requires this to be the first attribute, but at this time we
- // lost the order of the attributes
- return false;
- }
-
- if (caseInsensitiveAttributes.get(ATTRIBUTE_TY) == null) {
- return false;
- }
-
- byte[] url = caseInsensitiveAttributes.get(ATTRIBUTE_URL);
- if (url == null || url.length == 0) {
- return false;
- }
-
- byte[] type = caseInsensitiveAttributes.get(ATTRIBUTE_TYPE);
- if (type == null || !TYPE.equals(
- new String(type, StandardCharsets.UTF_8).toLowerCase())) {
- return false;
- }
-
- if (caseInsensitiveAttributes.get(ATTRIBUTE_ID) == null) {
- return false;
- }
-
- byte[] cs = caseInsensitiveAttributes.get(ATTRIBUTE_CS);
- if (cs == null || !POSSIBLE_CONNECTION_STATES.contains(
- new String(cs, StandardCharsets.UTF_8).toLowerCase())) {
- return false;
- }
-
- InetAddress address = nsdServiceInfo.getHost();
- if (!(address instanceof Inet4Address)) {
- // Not checking for link local address
- return false;
- }
-
- return true;
- });
- }
-
- @Override
- @NonNull public CharSequence getPackageName() {
- return "com.google.android.apps.cloudprint";
- }
-
- @Override
- public void start(@NonNull PrinterDiscoveryCallback callback) throws Exception {
- mMDNSFilteredDiscovery.start(callback);
- }
-
- @Override
- @StringRes public int getName() {
- return R.string.plugin_vendor_google_cloud_print;
- }
-
- @Override
- public void stop() throws Exception {
- mMDNSFilteredDiscovery.stop();
- }
-}
diff --git a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/GallerySpaEnvironment.kt b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/GallerySpaEnvironment.kt
index e185367..761bb79 100644
--- a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/GallerySpaEnvironment.kt
+++ b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/GallerySpaEnvironment.kt
@@ -27,7 +27,7 @@
import com.android.settingslib.spa.gallery.dialog.DialogMainPageProvider
import com.android.settingslib.spa.gallery.dialog.NavDialogProvider
import com.android.settingslib.spa.gallery.editor.EditorMainPageProvider
-import com.android.settingslib.spa.gallery.editor.SettingsExposedDropdownMenuBoxPageProvider
+import com.android.settingslib.spa.gallery.editor.SettingsDropdownBoxPageProvider
import com.android.settingslib.spa.gallery.editor.SettingsDropdownCheckBoxProvider
import com.android.settingslib.spa.gallery.home.HomePageProvider
import com.android.settingslib.spa.gallery.itemList.ItemListPageProvider
@@ -99,7 +99,7 @@
OperateListPageProvider,
EditorMainPageProvider,
SettingsOutlinedTextFieldPageProvider,
- SettingsExposedDropdownMenuBoxPageProvider,
+ SettingsDropdownBoxPageProvider,
SettingsDropdownCheckBoxProvider,
SettingsTextFieldPasswordPageProvider,
SearchScaffoldPageProvider,
diff --git a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/editor/EditorMainPageProvider.kt b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/editor/EditorMainPageProvider.kt
index 9f2158a..c511542 100644
--- a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/editor/EditorMainPageProvider.kt
+++ b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/editor/EditorMainPageProvider.kt
@@ -35,7 +35,7 @@
return listOf(
SettingsOutlinedTextFieldPageProvider.buildInjectEntry().setLink(fromPage = owner)
.build(),
- SettingsExposedDropdownMenuBoxPageProvider.buildInjectEntry().setLink(fromPage = owner)
+ SettingsDropdownBoxPageProvider.buildInjectEntry().setLink(fromPage = owner)
.build(),
SettingsDropdownCheckBoxProvider.buildInjectEntry().setLink(fromPage = owner)
.build(),
diff --git a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/editor/SettingsExposedDropdownMenuBoxPageProvider.kt b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/editor/SettingsDropdownBoxPageProvider.kt
similarity index 63%
rename from packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/editor/SettingsExposedDropdownMenuBoxPageProvider.kt
rename to packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/editor/SettingsDropdownBoxPageProvider.kt
index 5ffbe8ba..2ebb5f5 100644
--- a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/editor/SettingsExposedDropdownMenuBoxPageProvider.kt
+++ b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/editor/SettingsDropdownBoxPageProvider.kt
@@ -28,16 +28,15 @@
import com.android.settingslib.spa.framework.common.createSettingsPage
import com.android.settingslib.spa.framework.compose.navigator
import com.android.settingslib.spa.framework.theme.SettingsTheme
-import com.android.settingslib.spa.widget.editor.SettingsExposedDropdownMenuBox
+import com.android.settingslib.spa.widget.editor.SettingsDropdownBox
import com.android.settingslib.spa.widget.preference.Preference
import com.android.settingslib.spa.widget.preference.PreferenceModel
import com.android.settingslib.spa.widget.scaffold.RegularScaffold
-private const val TITLE = "Sample SettingsExposedDropdownMenuBox"
+private const val TITLE = "Sample SettingsDropdownBox"
-object SettingsExposedDropdownMenuBoxPageProvider : SettingsPageProvider {
- override val name = "SettingsExposedDropdownMenuBox"
- private const val exposedDropdownMenuBoxLabel = "ExposedDropdownMenuBoxLabel"
+object SettingsDropdownBoxPageProvider : SettingsPageProvider {
+ override val name = "SettingsDropdownBox"
override fun getTitle(arguments: Bundle?): String {
return TITLE
@@ -45,18 +44,44 @@
@Composable
override fun Page(arguments: Bundle?) {
- var selectedItem by remember { mutableIntStateOf(-1) }
- val options = listOf("item1", "item2", "item3")
RegularScaffold(title = TITLE) {
- SettingsExposedDropdownMenuBox(
- label = exposedDropdownMenuBoxLabel,
- options = options,
- selectedOptionIndex = selectedItem,
- enabled = true,
- onselectedOptionTextChange = { selectedItem = it })
+ Regular()
+ NotEnabled()
+ Empty()
}
}
+ @Composable
+ private fun Regular() {
+ var selectedItem by remember { mutableIntStateOf(-1) }
+ SettingsDropdownBox(
+ label = "SettingsDropdownBox",
+ options = listOf("item1", "item2", "item3"),
+ selectedOptionIndex = selectedItem,
+ ) { selectedItem = it }
+ }
+
+ @Composable
+ private fun NotEnabled() {
+ var selectedItem by remember { mutableIntStateOf(0) }
+ SettingsDropdownBox(
+ label = "Not enabled",
+ options = listOf("item1", "item2", "item3"),
+ enabled = false,
+ selectedOptionIndex = selectedItem,
+ ) { selectedItem = it }
+ }
+
+ @Composable
+ private fun Empty() {
+ var selectedItem by remember { mutableIntStateOf(-1) }
+ SettingsDropdownBox(
+ label = "Empty",
+ options = emptyList(),
+ selectedOptionIndex = selectedItem,
+ ) { selectedItem = it }
+ }
+
fun buildInjectEntry(): SettingsEntryBuilder {
return SettingsEntryBuilder.createInject(owner = createSettingsPage())
.setUiLayoutFn {
@@ -70,8 +95,8 @@
@Preview(showBackground = true)
@Composable
-private fun SettingsExposedDropdownMenuBoxPagePreview() {
+private fun SettingsDropdownBoxPagePreview() {
SettingsTheme {
- SettingsExposedDropdownMenuBoxPageProvider.Page(null)
+ SettingsDropdownBoxPageProvider.Page(null)
}
}
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/editor/DropdownTextBox.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/editor/DropdownTextBox.kt
new file mode 100644
index 0000000..679c562
--- /dev/null
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/editor/DropdownTextBox.kt
@@ -0,0 +1,87 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settingslib.spa.widget.editor
+
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.layout.width
+import androidx.compose.material3.ExperimentalMaterial3Api
+import androidx.compose.material3.ExposedDropdownMenuBox
+import androidx.compose.material3.ExposedDropdownMenuDefaults
+import androidx.compose.material3.OutlinedTextField
+import androidx.compose.material3.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.remember
+import androidx.compose.runtime.setValue
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.unit.dp
+import com.android.settingslib.spa.framework.theme.SettingsDimension
+
+internal interface DropdownTextBoxScope {
+ fun dismiss()
+}
+
+@OptIn(ExperimentalMaterial3Api::class)
+@Composable
+internal fun DropdownTextBox(
+ label: String,
+ text: String,
+ enabled: Boolean = true,
+ errorMessage: String? = null,
+ content: @Composable DropdownTextBoxScope.() -> Unit,
+) {
+ var expanded by remember { mutableStateOf(false) }
+ val scope = remember {
+ object : DropdownTextBoxScope {
+ override fun dismiss() {
+ expanded = false
+ }
+ }
+ }
+ ExposedDropdownMenuBox(
+ expanded = expanded,
+ onExpandedChange = { expanded = enabled && it },
+ modifier = Modifier
+ .padding(SettingsDimension.menuFieldPadding)
+ .width(Width),
+ ) {
+ OutlinedTextField(
+ // The `menuAnchor` modifier must be passed to the text field for correctness.
+ modifier = Modifier
+ .menuAnchor()
+ .fillMaxWidth(),
+ value = text,
+ onValueChange = { },
+ label = { Text(text = label) },
+ trailingIcon = { ExposedDropdownMenuDefaults.TrailingIcon(expanded = expanded) },
+ singleLine = true,
+ readOnly = true,
+ enabled = enabled,
+ isError = errorMessage != null,
+ supportingText = errorMessage?.let { { Text(text = it) } },
+ )
+ ExposedDropdownMenu(
+ expanded = expanded,
+ modifier = Modifier.width(Width),
+ onDismissRequest = { expanded = false },
+ ) { scope.content() }
+ }
+}
+
+private val Width = 310.dp
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/editor/SettingsDropdownBox.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/editor/SettingsDropdownBox.kt
new file mode 100644
index 0000000..ff141c2
--- /dev/null
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/editor/SettingsDropdownBox.kt
@@ -0,0 +1,69 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settingslib.spa.widget.editor
+
+import androidx.compose.material3.DropdownMenuItem
+import androidx.compose.material3.ExperimentalMaterial3Api
+import androidx.compose.material3.ExposedDropdownMenuDefaults
+import androidx.compose.material3.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.tooling.preview.Preview
+import com.android.settingslib.spa.framework.theme.SettingsTheme
+
+@Composable
+@OptIn(ExperimentalMaterial3Api::class)
+fun SettingsDropdownBox(
+ label: String,
+ options: List<String>,
+ selectedOptionIndex: Int,
+ enabled: Boolean = true,
+ onSelectedOptionChange: (Int) -> Unit,
+) {
+ DropdownTextBox(
+ label = label,
+ text = options.getOrElse(selectedOptionIndex) { "" },
+ enabled = enabled && options.isNotEmpty(),
+ ) {
+ options.forEachIndexed { index, option ->
+ DropdownMenuItem(
+ text = { Text(option) },
+ onClick = {
+ dismiss()
+ onSelectedOptionChange(index)
+ },
+ contentPadding = ExposedDropdownMenuDefaults.ItemContentPadding,
+ )
+ }
+ }
+}
+
+@Preview
+@Composable
+private fun SettingsDropdownBoxPreview() {
+ val item1 = "item1"
+ val item2 = "item2"
+ val item3 = "item3"
+ val options = listOf(item1, item2, item3)
+ SettingsTheme {
+ SettingsDropdownBox(
+ label = "ExposedDropdownMenuBoxLabel",
+ options = options,
+ selectedOptionIndex = 0,
+ enabled = true,
+ ) {}
+ }
+}
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/editor/SettingsDropdownCheckBox.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/editor/SettingsDropdownCheckBox.kt
index 57963e6..0e7e499 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/editor/SettingsDropdownCheckBox.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/editor/SettingsDropdownCheckBox.kt
@@ -19,28 +19,15 @@
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxWidth
-import androidx.compose.foundation.layout.padding
-import androidx.compose.foundation.layout.width
import androidx.compose.material3.Checkbox
-import androidx.compose.material3.ExperimentalMaterial3Api
-import androidx.compose.material3.ExposedDropdownMenuBox
-import androidx.compose.material3.ExposedDropdownMenuDefaults
-import androidx.compose.material3.OutlinedTextField
import androidx.compose.material3.Text
import androidx.compose.material3.TextButton
import androidx.compose.runtime.Composable
import androidx.compose.runtime.MutableState
-import androidx.compose.runtime.getValue
-import androidx.compose.runtime.mutableIntStateOf
import androidx.compose.runtime.mutableStateOf
-import androidx.compose.runtime.remember
-import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
-import androidx.compose.ui.layout.onSizeChanged
-import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.tooling.preview.Preview
-import androidx.compose.ui.unit.dp
import com.android.settingslib.spa.framework.theme.SettingsDimension
import com.android.settingslib.spa.framework.theme.SettingsOpacity.alphaForEnabled
import com.android.settingslib.spa.framework.theme.SettingsTheme
@@ -68,7 +55,6 @@
}
}
-@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun SettingsDropdownCheckBox(
label: String,
@@ -78,43 +64,18 @@
errorMessage: String? = null,
onSelectedStateChange: () -> Unit = {},
) {
- var dropDownWidth by remember { mutableIntStateOf(0) }
- var expanded by remember { mutableStateOf(false) }
- val changeable = enabled && options.changeable
- ExposedDropdownMenuBox(
- expanded = expanded,
- onExpandedChange = { expanded = changeable && it },
- modifier = Modifier
- .width(350.dp)
- .padding(SettingsDimension.textFieldPadding)
- .onSizeChanged { dropDownWidth = it.width },
+ DropdownTextBox(
+ label = label,
+ text = getDisplayText(options) ?: emptyText,
+ enabled = enabled && options.changeable,
+ errorMessage = errorMessage,
) {
- OutlinedTextField(
- // The `menuAnchor` modifier must be passed to the text field for correctness.
- modifier = Modifier
- .menuAnchor()
- .fillMaxWidth(),
- value = getDisplayText(options) ?: emptyText,
- onValueChange = {},
- label = { Text(text = label) },
- trailingIcon = { ExposedDropdownMenuDefaults.TrailingIcon(expanded) },
- readOnly = true,
- enabled = changeable,
- isError = errorMessage != null,
- supportingText = errorMessage?.let { { Text(text = it) } },
- )
- ExposedDropdownMenu(
- expanded = expanded,
- modifier = Modifier.width(with(LocalDensity.current) { dropDownWidth.toDp() }),
- onDismissRequest = { expanded = false },
- ) {
- for (option in options) {
- CheckboxItem(option) {
- option.onClick()
- if (option.changeable) {
- checkboxItemOnClick(options, option)
- onSelectedStateChange()
- }
+ for (option in options) {
+ CheckboxItem(option) {
+ option.onClick()
+ if (option.changeable) {
+ checkboxItemOnClick(options, option)
+ onSelectedStateChange()
}
}
}
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/editor/SettingsExposedDropdownMenuBox.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/editor/SettingsExposedDropdownMenuBox.kt
deleted file mode 100644
index f6692a3..0000000
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/editor/SettingsExposedDropdownMenuBox.kt
+++ /dev/null
@@ -1,110 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.settingslib.spa.widget.editor
-
-import androidx.compose.foundation.layout.fillMaxWidth
-import androidx.compose.foundation.layout.padding
-import androidx.compose.foundation.layout.width
-import androidx.compose.material3.DropdownMenuItem
-import androidx.compose.material3.ExperimentalMaterial3Api
-import androidx.compose.material3.ExposedDropdownMenuBox
-import androidx.compose.material3.ExposedDropdownMenuDefaults
-import androidx.compose.material3.OutlinedTextField
-import androidx.compose.material3.Text
-import androidx.compose.runtime.Composable
-import androidx.compose.runtime.getValue
-import androidx.compose.runtime.mutableStateOf
-import androidx.compose.runtime.remember
-import androidx.compose.runtime.setValue
-import androidx.compose.ui.Modifier
-import androidx.compose.ui.tooling.preview.Preview
-import androidx.compose.ui.unit.dp
-import com.android.settingslib.spa.framework.theme.SettingsDimension
-import com.android.settingslib.spa.framework.theme.SettingsTheme
-
-@Composable
-@OptIn(ExperimentalMaterial3Api::class)
-fun SettingsExposedDropdownMenuBox(
- label: String,
- options: List<String>,
- selectedOptionIndex: Int,
- enabled: Boolean,
- onselectedOptionTextChange: (Int) -> Unit,
-) {
- var expanded by remember { mutableStateOf(false) }
- ExposedDropdownMenuBox(
- expanded = expanded,
- onExpandedChange = { expanded = it },
- modifier = Modifier
- .width(350.dp)
- .padding(SettingsDimension.menuFieldPadding),
- ) {
- OutlinedTextField(
- // The `menuAnchor` modifier must be passed to the text field for correctness.
- modifier = Modifier
- .menuAnchor()
- .fillMaxWidth(),
- value = options.getOrElse(selectedOptionIndex) { "" },
- onValueChange = { },
- label = { Text(text = label) },
- trailingIcon = {
- ExposedDropdownMenuDefaults.TrailingIcon(
- expanded = expanded
- )
- },
- singleLine = true,
- readOnly = true,
- enabled = enabled
- )
- if (options.isNotEmpty()) {
- ExposedDropdownMenu(
- expanded = expanded,
- modifier = Modifier
- .fillMaxWidth(),
- onDismissRequest = { expanded = false },
- ) {
- options.forEach { option ->
- DropdownMenuItem(
- text = { Text(option) },
- onClick = {
- onselectedOptionTextChange(options.indexOf(option))
- expanded = false
- },
- contentPadding = ExposedDropdownMenuDefaults.ItemContentPadding,
- )
- }
- }
- }
- }
-}
-
-@Preview
-@Composable
-private fun SettingsExposedDropdownMenuBoxsPreview() {
- val item1 = "item1"
- val item2 = "item2"
- val item3 = "item3"
- val options = listOf(item1, item2, item3)
- SettingsTheme {
- SettingsExposedDropdownMenuBox(
- label = "ExposedDropdownMenuBoxLabel",
- options = options,
- selectedOptionIndex = 0,
- enabled = true,
- onselectedOptionTextChange = {})
- }
-}
\ No newline at end of file
diff --git a/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/editor/SettingsDropdownBoxTest.kt b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/editor/SettingsDropdownBoxTest.kt
new file mode 100644
index 0000000..c347424
--- /dev/null
+++ b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/editor/SettingsDropdownBoxTest.kt
@@ -0,0 +1,109 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settingslib.spa.widget.editor
+
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableIntStateOf
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.remember
+import androidx.compose.runtime.setValue
+import androidx.compose.ui.test.assertIsDisplayed
+import androidx.compose.ui.test.junit4.createComposeRule
+import androidx.compose.ui.test.onNodeWithText
+import androidx.compose.ui.test.performClick
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(AndroidJUnit4::class)
+class SettingsDropdownBoxTest {
+ @get:Rule
+ val composeTestRule = createComposeRule()
+
+ @Test
+ fun dropdownMenuBox_displayed() {
+ composeTestRule.setContent {
+ var selectedItem by remember { mutableStateOf(0) }
+ SettingsDropdownBox(
+ label = LABEL,
+ options = options,
+ selectedOptionIndex = selectedItem,
+ ) { selectedItem = it }
+ }
+
+ composeTestRule.onNodeWithText(LABEL).assertIsDisplayed()
+ }
+
+ @Test
+ fun dropdownMenuBox_enabled_expanded() {
+ composeTestRule.setContent {
+ var selectedItem by remember { mutableIntStateOf(0) }
+ SettingsDropdownBox(
+ label = LABEL,
+ options = options,
+ selectedOptionIndex = selectedItem
+ ) { selectedItem = it }
+ }
+ composeTestRule.onNodeWithText(ITEM2).assertDoesNotExist()
+
+ composeTestRule.onNodeWithText(LABEL).performClick()
+
+ composeTestRule.onNodeWithText(ITEM2).assertIsDisplayed()
+ }
+
+ @Test
+ fun dropdownMenuBox_notEnabled_notExpanded() {
+ composeTestRule.setContent {
+ var selectedItem by remember { mutableIntStateOf(0) }
+ SettingsDropdownBox(
+ label = LABEL,
+ options = options,
+ enabled = false,
+ selectedOptionIndex = selectedItem
+ ) { selectedItem = it }
+ }
+ composeTestRule.onNodeWithText(ITEM2).assertDoesNotExist()
+
+ composeTestRule.onNodeWithText(LABEL).performClick()
+
+ composeTestRule.onNodeWithText(ITEM2).assertDoesNotExist()
+ }
+
+ @Test
+ fun dropdownMenuBox_valueChanged() {
+ composeTestRule.setContent {
+ var selectedItem by remember { mutableIntStateOf(0) }
+ SettingsDropdownBox(
+ label = LABEL,
+ options = options,
+ selectedOptionIndex = selectedItem
+ ) { selectedItem = it }
+ }
+ composeTestRule.onNodeWithText(ITEM2).assertDoesNotExist()
+
+ composeTestRule.onNodeWithText(LABEL).performClick()
+ composeTestRule.onNodeWithText(ITEM2).performClick()
+
+ composeTestRule.onNodeWithText(ITEM2).assertIsDisplayed()
+ }
+ private companion object {
+ const val LABEL = "Label"
+ const val ITEM2 = "item2"
+ val options = listOf("item1", ITEM2, "item3")
+ }
+}
diff --git a/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/editor/SettingsExposedDropdownMenuBoxTest.kt b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/editor/SettingsExposedDropdownMenuBoxTest.kt
deleted file mode 100644
index bc67e4c..0000000
--- a/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/editor/SettingsExposedDropdownMenuBoxTest.kt
+++ /dev/null
@@ -1,95 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.settingslib.spa.widget.editor
-
-import androidx.compose.runtime.getValue
-import androidx.compose.runtime.mutableIntStateOf
-import androidx.compose.runtime.mutableStateOf
-import androidx.compose.runtime.remember
-import androidx.compose.runtime.setValue
-import androidx.compose.ui.test.assertIsDisplayed
-import androidx.compose.ui.test.junit4.createComposeRule
-import androidx.compose.ui.test.onNodeWithText
-import androidx.compose.ui.test.performClick
-import androidx.test.ext.junit.runners.AndroidJUnit4
-import org.junit.Rule
-import org.junit.Test
-import org.junit.runner.RunWith
-
-@RunWith(AndroidJUnit4::class)
-class SettingsExposedDropdownMenuBoxTest {
- @get:Rule
- val composeTestRule = createComposeRule()
- private val options = listOf("item1", "item2", "item3")
- private val item2 = "item2"
- private val exposedDropdownMenuBoxLabel = "ExposedDropdownMenuBoxLabel"
-
- @Test
- fun exposedDropdownMenuBoxs_displayed() {
- composeTestRule.setContent {
- var selectedItem by remember { mutableStateOf(0) }
- SettingsExposedDropdownMenuBox(
- label = exposedDropdownMenuBoxLabel,
- options = options,
- selectedOptionIndex = selectedItem,
- enabled = true,
- onselectedOptionTextChange = { selectedItem = it })
- }
- composeTestRule.onNodeWithText(exposedDropdownMenuBoxLabel, substring = true)
- .assertIsDisplayed()
- }
-
- @Test
- fun exposedDropdownMenuBoxs_expanded() {
- composeTestRule.setContent {
- var selectedItem by remember { mutableIntStateOf(0) }
- SettingsExposedDropdownMenuBox(
- label = exposedDropdownMenuBoxLabel,
- options = options,
- selectedOptionIndex = selectedItem,
- enabled = true,
- onselectedOptionTextChange = { selectedItem = it })
- }
- composeTestRule.onNodeWithText(item2, substring = true)
- .assertDoesNotExist()
- composeTestRule.onNodeWithText(exposedDropdownMenuBoxLabel, substring = true)
- .performClick()
- composeTestRule.onNodeWithText(item2, substring = true)
- .assertIsDisplayed()
- }
-
- @Test
- fun exposedDropdownMenuBoxs_valueChanged() {
- composeTestRule.setContent {
- var selectedItem by remember { mutableIntStateOf(0) }
- SettingsExposedDropdownMenuBox(
- label = exposedDropdownMenuBoxLabel,
- options = options,
- selectedOptionIndex = selectedItem,
- enabled = true,
- onselectedOptionTextChange = { selectedItem = it })
- }
- composeTestRule.onNodeWithText(item2, substring = true)
- .assertDoesNotExist()
- composeTestRule.onNodeWithText(exposedDropdownMenuBoxLabel, substring = true)
- .performClick()
- composeTestRule.onNodeWithText(item2, substring = true)
- .performClick()
- composeTestRule.onNodeWithText(item2, substring = true)
- .assertIsDisplayed()
- }
-}
\ No newline at end of file
diff --git a/packages/SettingsLib/res/drawable/ic_bt_le_audio_sharing.xml b/packages/SettingsLib/res/drawable/ic_bt_le_audio_sharing.xml
new file mode 100644
index 0000000..6186773
--- /dev/null
+++ b/packages/SettingsLib/res/drawable/ic_bt_le_audio_sharing.xml
@@ -0,0 +1,87 @@
+<!--
+ ~ Copyright (C) 2023 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:autoMirrored="true"
+ android:height="24dp"
+ android:width="24dp"
+ android:viewportHeight="24"
+ android:viewportWidth="24"
+ android:tint="?android:attr/colorControlNormal">
+ <path
+ android:fillColor="#000000"
+ android:pathData="M16.984,24H7.279L12.131,15.508L16.984,24ZM10.481,22.144H13.781L12.131,19.257L10.481,22.144Z"/>
+ <path
+ android:fillColor="#000000"
+ android:pathData="M12.131,14.295C13.471,14.295 14.558,13.209 14.558,11.869C14.558,10.529 13.471,9.442 12.131,9.442C10.791,9.442 9.705,10.529 9.705,11.869C9.705,13.209 10.791,14.295 12.131,14.295Z"/>
+ <path
+ android:fillColor="#000000"
+ android:pathData="M4.573,21.368C4.052,20.943 3.967,20.179 4.379,19.657C4.804,19.136 5.568,19.051 6.09,19.463C6.611,19.876 6.696,20.64 6.284,21.174C6.041,21.465 5.689,21.623 5.338,21.623C5.071,21.623 4.804,21.538 4.573,21.368Z"/>
+ <path
+ android:fillColor="#000000"
+ android:pathData="M17.991,21.162C17.579,20.628 17.663,19.876 18.185,19.451C18.707,19.039 19.471,19.124 19.896,19.646C20.308,20.167 20.223,20.931 19.702,21.344C19.471,21.526 19.204,21.611 18.949,21.611C18.586,21.611 18.234,21.453 17.991,21.162Z"/>
+ <path
+ android:fillColor="#000000"
+ android:pathData="M1.213,17.145C0.91,16.551 1.165,15.823 1.771,15.532C2.378,15.241 3.093,15.495 3.397,16.09C3.688,16.697 3.433,17.424 2.827,17.715C2.657,17.8 2.475,17.837 2.305,17.837C1.844,17.837 1.419,17.582 1.213,17.145Z"/>
+ <path
+ android:fillColor="#000000"
+ android:pathData="M21.449,17.691C20.842,17.4 20.588,16.684 20.879,16.077C21.17,15.471 21.898,15.216 22.504,15.507C23.099,15.798 23.354,16.526 23.062,17.133C22.856,17.557 22.419,17.812 21.971,17.812C21.789,17.812 21.619,17.776 21.449,17.691Z"/>
+ <path
+ android:fillColor="#000000"
+ android:pathData="M0,11.892C0,11.225 0.546,10.679 1.213,10.679C1.88,10.679 2.426,11.212 2.426,11.892C2.426,12.559 1.88,13.105 1.213,13.105C0.546,13.105 0,12.559 0,11.892Z"/>
+ <path
+ android:fillColor="#000000"
+ android:pathData="M21.837,11.869C21.837,11.857 21.837,11.845 21.837,11.833C21.824,11.153 22.37,10.62 23.05,10.607C23.717,10.607 24.251,11.153 24.263,11.821C24.263,11.833 24.263,11.845 24.263,11.845C24.263,11.857 24.263,11.869 24.263,11.869C24.263,12.536 23.717,13.082 23.05,13.082C22.382,13.082 21.837,12.536 21.837,11.869Z"/>
+ <path
+ android:fillColor="#000000"
+ android:pathData="M1.759,8.242C1.152,7.963 0.898,7.235 1.189,6.628C1.48,6.022 2.196,5.767 2.802,6.058C3.409,6.349 3.664,7.077 3.372,7.684C3.166,8.108 2.729,8.363 2.281,8.363C2.099,8.363 1.929,8.327 1.759,8.242Z"/>
+ <path
+ android:fillColor="#000000"
+ android:pathData="M20.866,7.622C20.563,7.028 20.818,6.3 21.424,6.009C22.019,5.706 22.747,5.96 23.038,6.567C23.038,6.567 23.038,6.567 23.05,6.567C23.341,7.161 23.087,7.889 22.48,8.181C22.31,8.265 22.128,8.302 21.958,8.302C21.509,8.302 21.073,8.059 20.866,7.622Z"/>
+ <path
+ android:fillColor="#000000"
+ android:pathData="M4.355,4.104C3.931,3.582 4.016,2.818 4.537,2.406C5.071,1.981 5.823,2.066 6.248,2.588C6.672,3.109 6.588,3.874 6.066,4.298C5.835,4.48 5.569,4.565 5.302,4.565C4.95,4.565 4.598,4.407 4.355,4.104Z"/>
+ <path
+ android:fillColor="#000000"
+ android:pathData="M18.161,4.262C17.627,3.838 17.542,3.073 17.955,2.552C18.379,2.03 19.132,1.945 19.666,2.358C20.187,2.77 20.272,3.534 19.86,4.068C19.617,4.359 19.265,4.517 18.913,4.517C18.646,4.517 18.379,4.432 18.161,4.262Z"/>
+ <path
+ android:fillColor="#000000"
+ android:pathData="M8.492,1.497C8.334,0.854 8.747,0.199 9.402,0.041C10.057,-0.105 10.7,0.308 10.858,0.963C11.003,1.606 10.591,2.261 9.948,2.407C9.851,2.431 9.754,2.443 9.669,2.443C9.123,2.443 8.613,2.067 8.492,1.497Z"/>
+ <path
+ android:fillColor="#000000"
+ android:pathData="M14.267,2.395C13.599,2.249 13.199,1.606 13.345,0.951C13.49,0.296 14.133,-0.116 14.788,0.029C15.443,0.175 15.856,0.83 15.71,1.485C15.589,2.043 15.08,2.431 14.534,2.431C14.437,2.431 14.352,2.419 14.267,2.395Z"/>
+ <path
+ android:fillColor="#000000"
+ android:pathData="M7,17.037C6.527,16.564 6.527,15.8 7,15.326C7.473,14.841 8.237,14.841 8.71,15.314C9.196,15.787 9.196,16.552 8.723,17.025C8.48,17.267 8.177,17.389 7.861,17.389C7.546,17.389 7.242,17.267 7,17.037Z"/>
+ <path
+ android:fillColor="#000000"
+ android:pathData="M15.565,17.012C15.092,16.539 15.092,15.762 15.565,15.289C16.038,14.816 16.814,14.816 17.288,15.289C17.761,15.762 17.761,16.539 17.288,17.012C17.045,17.243 16.742,17.364 16.426,17.364C16.111,17.364 15.807,17.243 15.565,17.012Z"/>
+ <path
+ android:fillColor="#000000"
+ android:pathData="M4.853,11.917C4.853,11.237 5.386,10.691 6.054,10.691C6.721,10.691 7.279,11.225 7.279,11.892C7.279,12.56 6.745,13.106 6.078,13.118C5.398,13.118 4.853,12.584 4.853,11.917Z"/>
+ <path
+ android:fillColor="#000000"
+ android:pathData="M16.984,11.868C16.984,11.856 16.984,11.844 16.984,11.832C16.984,11.832 16.984,11.82 16.984,11.807C16.972,11.14 17.506,10.582 18.185,10.582C18.852,10.57 19.398,11.116 19.41,11.783C19.41,11.795 19.41,11.82 19.41,11.832C19.41,11.844 19.41,11.856 19.41,11.868C19.41,12.535 18.865,13.081 18.197,13.081C17.53,13.081 16.984,12.535 16.984,11.868Z"/>
+ <path
+ android:fillColor="#000000"
+ android:pathData="M6.952,8.471C6.478,7.997 6.478,7.233 6.952,6.76C6.952,6.76 6.952,6.76 6.939,6.76C7.413,6.275 8.189,6.275 8.662,6.748C9.135,7.221 9.147,7.985 8.674,8.458C8.432,8.701 8.116,8.822 7.813,8.822C7.497,8.822 7.194,8.701 6.952,8.471Z"/>
+ <path
+ android:fillColor="#000000"
+ android:pathData="M15.529,8.399C15.043,7.938 15.043,7.161 15.504,6.688C15.977,6.203 16.742,6.203 17.227,6.664C17.7,7.137 17.712,7.901 17.239,8.387C17.009,8.629 16.693,8.751 16.378,8.751C16.075,8.751 15.759,8.629 15.529,8.399Z"/>
+ <path
+ android:fillColor="#000000"
+ android:pathData="M10.87,5.815C10.858,5.148 11.392,4.59 12.071,4.59C12.738,4.578 13.284,5.124 13.284,5.791C13.296,6.458 12.762,7.016 12.083,7.016C11.416,7.016 10.87,6.483 10.87,5.815Z"/>
+</vector>
diff --git a/packages/SettingsLib/src/com/android/settingslib/statusbar/notification/data/repository/FakeNotificationsSoundPolicyRepository.kt b/packages/SettingsLib/src/com/android/settingslib/statusbar/notification/data/repository/FakeNotificationsSoundPolicyRepository.kt
index 6098307..2a44511 100644
--- a/packages/SettingsLib/src/com/android/settingslib/statusbar/notification/data/repository/FakeNotificationsSoundPolicyRepository.kt
+++ b/packages/SettingsLib/src/com/android/settingslib/statusbar/notification/data/repository/FakeNotificationsSoundPolicyRepository.kt
@@ -40,3 +40,21 @@
mutableZenMode.value = zenMode
}
}
+
+fun FakeNotificationsSoundPolicyRepository.updateNotificationPolicy(
+ priorityCategories: Int = 0,
+ priorityCallSenders: Int = NotificationManager.Policy.PRIORITY_SENDERS_ANY,
+ priorityMessageSenders: Int = NotificationManager.Policy.CONVERSATION_SENDERS_NONE,
+ suppressedVisualEffects: Int = NotificationManager.Policy.SUPPRESSED_EFFECTS_UNSET,
+ state: Int = NotificationManager.Policy.STATE_UNSET,
+ priorityConversationSenders: Int = NotificationManager.Policy.CONVERSATION_SENDERS_NONE,
+) = updateNotificationPolicy(
+ NotificationManager.Policy(
+ priorityCategories,
+ priorityCallSenders,
+ priorityMessageSenders,
+ suppressedVisualEffects,
+ state,
+ priorityConversationSenders,
+ )
+)
\ No newline at end of file
diff --git a/packages/SettingsLib/src/com/android/settingslib/statusbar/notification/domain/interactor/NotificationsSoundPolicyInteractor.kt b/packages/SettingsLib/src/com/android/settingslib/statusbar/notification/domain/interactor/NotificationsSoundPolicyInteractor.kt
new file mode 100644
index 0000000..794cf83
--- /dev/null
+++ b/packages/SettingsLib/src/com/android/settingslib/statusbar/notification/domain/interactor/NotificationsSoundPolicyInteractor.kt
@@ -0,0 +1,93 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settingslib.statusbar.notification.domain.interactor
+
+import android.app.NotificationManager
+import android.media.AudioManager
+import android.provider.Settings
+import android.service.notification.ZenModeConfig
+import com.android.settingslib.statusbar.notification.data.model.ZenMode
+import com.android.settingslib.statusbar.notification.data.repository.NotificationsSoundPolicyRepository
+import com.android.settingslib.volume.shared.model.AudioStream
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.flow.filterNotNull
+import kotlinx.coroutines.flow.map
+
+/** Determines notification sounds state and limitations. */
+class NotificationsSoundPolicyInteractor(
+ private val repository: NotificationsSoundPolicyRepository
+) {
+
+ /** @see NotificationManager.getNotificationPolicy */
+ val notificationPolicy: StateFlow<NotificationManager.Policy?>
+ get() = repository.notificationPolicy
+
+ /** @see NotificationManager.getZenMode */
+ val zenMode: StateFlow<ZenMode?>
+ get() = repository.zenMode
+
+ /** Checks if [notificationPolicy] allows alarms. */
+ val areAlarmsAllowed: Flow<Boolean?> = notificationPolicy.map { it?.allowAlarms() }
+
+ /** Checks if [notificationPolicy] allows media. */
+ val isMediaAllowed: Flow<Boolean?> = notificationPolicy.map { it?.allowMedia() }
+
+ /** Checks if [notificationPolicy] allows ringer. */
+ val isRingerAllowed: Flow<Boolean?> =
+ notificationPolicy.map { policy ->
+ policy ?: return@map null
+ !ZenModeConfig.areAllPriorityOnlyRingerSoundsMuted(policy)
+ }
+
+ /** Checks if the [stream] is muted by either [zenMode] or [notificationPolicy]. */
+ fun isZenMuted(stream: AudioStream): Flow<Boolean> {
+ return combine(
+ zenMode.filterNotNull(),
+ areAlarmsAllowed.filterNotNull(),
+ isMediaAllowed.filterNotNull(),
+ isRingerAllowed.filterNotNull(),
+ ) { zenMode, areAlarmsAllowed, isMediaAllowed, isRingerAllowed ->
+ if (zenMode.zenMode == Settings.Global.ZEN_MODE_NO_INTERRUPTIONS) {
+ return@combine true
+ }
+
+ val isNotificationOrRing =
+ stream.value == AudioManager.STREAM_RING ||
+ stream.value == AudioManager.STREAM_NOTIFICATION
+ if (isNotificationOrRing && zenMode.zenMode == Settings.Global.ZEN_MODE_ALARMS) {
+ return@combine true
+ }
+ if (zenMode.zenMode != Settings.Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS) {
+ return@combine false
+ }
+
+ if (stream.value == AudioManager.STREAM_ALARM && !areAlarmsAllowed) {
+ return@combine true
+ }
+ if (stream.value == AudioManager.STREAM_MUSIC && !isMediaAllowed) {
+ return@combine true
+ }
+ if (isNotificationOrRing && !isRingerAllowed) {
+ return@combine true
+ }
+
+ return@combine false
+ }
+ }
+}
diff --git a/packages/SettingsLib/src/com/android/settingslib/volume/data/repository/AudioRepository.kt b/packages/SettingsLib/src/com/android/settingslib/volume/data/repository/AudioRepository.kt
index 6851997..0df4615 100644
--- a/packages/SettingsLib/src/com/android/settingslib/volume/data/repository/AudioRepository.kt
+++ b/packages/SettingsLib/src/com/android/settingslib/volume/data/repository/AudioRepository.kt
@@ -39,6 +39,7 @@
import kotlinx.coroutines.flow.filterNotNull
import kotlinx.coroutines.flow.flowOn
import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.onStart
import kotlinx.coroutines.flow.stateIn
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
@@ -64,10 +65,10 @@
val communicationDevice: StateFlow<AudioDeviceInfo?>
/** State of the [AudioStream]. */
- suspend fun getAudioStream(audioStream: AudioStream): Flow<AudioStreamModel>
+ fun getAudioStream(audioStream: AudioStream): Flow<AudioStreamModel>
- /** Current state of the [AudioStream]. */
- suspend fun getCurrentAudioStream(audioStream: AudioStream): AudioStreamModel
+ /** Returns the last audible volume before stream was muted. */
+ suspend fun getLastAudibleVolume(audioStream: AudioStream): Int
suspend fun setVolume(audioStream: AudioStream, volume: Int)
@@ -122,7 +123,7 @@
audioManager.communicationDevice,
)
- override suspend fun getAudioStream(audioStream: AudioStream): Flow<AudioStreamModel> {
+ override fun getAudioStream(audioStream: AudioStream): Flow<AudioStreamModel> {
return audioManagerEventsReceiver.events
.filter {
if (it is StreamAudioManagerEvent) {
@@ -132,20 +133,24 @@
}
}
.map { getCurrentAudioStream(audioStream) }
+ .onStart { emit(getCurrentAudioStream(audioStream)) }
.flowOn(backgroundCoroutineContext)
}
- override suspend fun getCurrentAudioStream(audioStream: AudioStream): AudioStreamModel {
+ private fun getCurrentAudioStream(audioStream: AudioStream): AudioStreamModel {
+ return AudioStreamModel(
+ audioStream = audioStream,
+ minVolume = getMinVolume(audioStream),
+ maxVolume = audioManager.getStreamMaxVolume(audioStream.value),
+ volume = audioManager.getStreamVolume(audioStream.value),
+ isAffectedByRingerMode = audioManager.isStreamAffectedByRingerMode(audioStream.value),
+ isMuted = audioManager.isStreamMute(audioStream.value),
+ )
+ }
+
+ override suspend fun getLastAudibleVolume(audioStream: AudioStream): Int {
return withContext(backgroundCoroutineContext) {
- AudioStreamModel(
- audioStream = audioStream,
- minVolume = getMinVolume(audioStream),
- maxVolume = audioManager.getStreamMaxVolume(audioStream.value),
- volume = audioManager.getStreamVolume(audioStream.value),
- isAffectedByRingerMode =
- audioManager.isStreamAffectedByRingerMode(audioStream.value),
- isMuted = audioManager.isStreamMute(audioStream.value)
- )
+ audioManager.getLastAudibleStreamVolume(audioStream.value)
}
}
diff --git a/packages/SettingsLib/src/com/android/settingslib/volume/domain/interactor/AudioVolumeInteractor.kt b/packages/SettingsLib/src/com/android/settingslib/volume/domain/interactor/AudioVolumeInteractor.kt
new file mode 100644
index 0000000..56b0bf7
--- /dev/null
+++ b/packages/SettingsLib/src/com/android/settingslib/volume/domain/interactor/AudioVolumeInteractor.kt
@@ -0,0 +1,86 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settingslib.volume.domain.interactor
+
+import android.media.AudioManager
+import com.android.settingslib.statusbar.notification.domain.interactor.NotificationsSoundPolicyInteractor
+import com.android.settingslib.volume.data.repository.AudioRepository
+import com.android.settingslib.volume.shared.model.AudioStream
+import com.android.settingslib.volume.shared.model.AudioStreamModel
+import com.android.settingslib.volume.shared.model.RingerMode
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.flow.flowOf
+import kotlinx.coroutines.flow.map
+
+/** Provides audio stream state and an ability to change it */
+class AudioVolumeInteractor(
+ private val audioRepository: AudioRepository,
+ private val notificationsSoundPolicyInteractor: NotificationsSoundPolicyInteractor,
+) {
+
+ /** State of the [AudioStream]. */
+ fun getAudioStream(audioStream: AudioStream): Flow<AudioStreamModel> =
+ combine(
+ audioRepository.getAudioStream(audioStream),
+ audioRepository.ringerMode,
+ notificationsSoundPolicyInteractor.isZenMuted(audioStream)
+ ) { streamModel: AudioStreamModel, ringerMode: RingerMode, isZenMuted: Boolean ->
+ streamModel.copy(volume = processVolume(streamModel, ringerMode, isZenMuted))
+ }
+
+ suspend fun setVolume(audioStream: AudioStream, volume: Int) =
+ audioRepository.setVolume(audioStream, volume)
+
+ suspend fun setMuted(audioStream: AudioStream, isMuted: Boolean) =
+ audioRepository.setMuted(audioStream, isMuted)
+
+ /** Checks if the volume can be changed via the UI. */
+ fun canChangeVolume(audioStream: AudioStream): Flow<Boolean> {
+ return if (audioStream.value == AudioManager.STREAM_NOTIFICATION) {
+ getAudioStream(AudioStream(AudioManager.STREAM_RING)).map { !it.isMuted }
+ } else {
+ flowOf(true)
+ }
+ }
+
+ private suspend fun processVolume(
+ audioStreamModel: AudioStreamModel,
+ ringerMode: RingerMode,
+ isZenMuted: Boolean,
+ ): Int {
+ if (isZenMuted) {
+ return audioRepository.getLastAudibleVolume(audioStreamModel.audioStream)
+ }
+ val isNotificationOrRing =
+ audioStreamModel.audioStream.value == AudioManager.STREAM_RING ||
+ audioStreamModel.audioStream.value == AudioManager.STREAM_NOTIFICATION
+ if (isNotificationOrRing && ringerMode.value == AudioManager.RINGER_MODE_VIBRATE) {
+ // For ringer-mode affected streams, show volume as zero when ringer mode is vibrate
+ if (
+ audioStreamModel.audioStream.value == AudioManager.STREAM_RING ||
+ (audioStreamModel.audioStream.value == AudioManager.STREAM_NOTIFICATION &&
+ audioStreamModel.isMuted)
+ ) {
+ return 0
+ }
+ } else if (audioStreamModel.isMuted) {
+ return 0
+ }
+ return audioStreamModel.volume
+ }
+}
diff --git a/packages/SettingsLib/src/com/android/settingslib/volume/shared/AudioManagerEventsReceiver.kt b/packages/SettingsLib/src/com/android/settingslib/volume/shared/AudioManagerEventsReceiver.kt
index 13ed9a8..c3b1a7c 100644
--- a/packages/SettingsLib/src/com/android/settingslib/volume/shared/AudioManagerEventsReceiver.kt
+++ b/packages/SettingsLib/src/com/android/settingslib/volume/shared/AudioManagerEventsReceiver.kt
@@ -54,6 +54,7 @@
AudioManager.VOLUME_CHANGED_ACTION,
AudioManager.INTERNAL_RINGER_MODE_CHANGED_ACTION,
AudioManager.STREAM_DEVICES_CHANGED_ACTION,
+ AudioManager.ACTION_VOLUME_CHANGED,
)
override val events: SharedFlow<AudioManagerEvent> =
diff --git a/packages/SettingsLib/src/com/android/settingslib/volume/shared/model/AudioStream.kt b/packages/SettingsLib/src/com/android/settingslib/volume/shared/model/AudioStream.kt
index 58f3c2d..9c48299 100644
--- a/packages/SettingsLib/src/com/android/settingslib/volume/shared/model/AudioStream.kt
+++ b/packages/SettingsLib/src/com/android/settingslib/volume/shared/model/AudioStream.kt
@@ -25,7 +25,7 @@
require(value in supportedStreamTypes) { "Unsupported stream=$value" }
}
- private companion object {
+ companion object {
val supportedStreamTypes =
setOf(
AudioManager.STREAM_VOICE_CALL,
diff --git a/packages/SettingsLib/tests/integ/src/com/android/settingslib/volume/data/repository/AudioRepositoryTest.kt b/packages/SettingsLib/tests/integ/src/com/android/settingslib/volume/data/repository/AudioRepositoryTest.kt
index 9ddf876..1728a80 100644
--- a/packages/SettingsLib/tests/integ/src/com/android/settingslib/volume/data/repository/AudioRepositoryTest.kt
+++ b/packages/SettingsLib/tests/integ/src/com/android/settingslib/volume/data/repository/AudioRepositoryTest.kt
@@ -181,24 +181,6 @@
}
@Test
- fun adjustingVolume_currentModeIsUpToDate() {
- testScope.runTest {
- val audioStream = AudioStream(AudioManager.STREAM_SYSTEM)
- var streamModel: AudioStreamModel? = null
- underTest
- .getAudioStream(audioStream)
- .onEach { streamModel = it }
- .launchIn(backgroundScope)
- runCurrent()
-
- underTest.setVolume(audioStream, 50)
- runCurrent()
-
- assertThat(underTest.getCurrentAudioStream(audioStream)).isEqualTo(streamModel)
- }
- }
-
- @Test
fun muteStream_mutesTheStream() {
testScope.runTest {
val audioStream = AudioStream(AudioManager.STREAM_SYSTEM)
diff --git a/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java b/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java
index b58187d..28cdc6d 100644
--- a/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java
+++ b/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java
@@ -807,7 +807,9 @@
Settings.Secure.UI_TRANSLATION_ENABLED,
Settings.Secure.ACCESSIBILITY_DISPLAY_MAGNIFICATION_EDGE_HAPTIC_ENABLED,
Settings.Secure.DND_CONFIGS_MIGRATED,
- Settings.Secure.NAVIGATION_MODE_RESTORE);
+ Settings.Secure.NAVIGATION_MODE_RESTORE,
+ Settings.Secure.V_TO_U_RESTORE_ALLOWLIST,
+ Settings.Secure.V_TO_U_RESTORE_DENYLIST);
@Test
public void systemSettingsBackedUpOrDenied() {
diff --git a/packages/SystemUI/aconfig/systemui.aconfig b/packages/SystemUI/aconfig/systemui.aconfig
index 71f9ba27..a69a2a6 100644
--- a/packages/SystemUI/aconfig/systemui.aconfig
+++ b/packages/SystemUI/aconfig/systemui.aconfig
@@ -377,6 +377,13 @@
}
flag {
+ name: "screenshot_action_dismiss_system_windows"
+ namespace: "systemui"
+ description: "Dismiss existing system windows when starting action from screenshot UI"
+ bug: "309933761"
+}
+
+flag {
name: "run_fingerprint_detect_on_dismissible_keyguard"
namespace: "systemui"
description: "Run fingerprint detect instead of authenticate if the keyguard is dismissible."
@@ -529,3 +536,13 @@
purpose: PURPOSE_BUGFIX
}
}
+
+flag {
+ name: "bind_keyguard_media_visibility"
+ namespace: "systemui"
+ description: "Binds Keyguard Media Controller Visibility to MediaContainerView"
+ bug: "298213983"
+ metadata {
+ purpose: PURPOSE_BUGFIX
+ }
+}
diff --git a/packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/turbulencenoise/TurbulenceNoiseController.kt b/packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/turbulencenoise/TurbulenceNoiseController.kt
index 535c2d3..e862f0c 100644
--- a/packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/turbulencenoise/TurbulenceNoiseController.kt
+++ b/packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/turbulencenoise/TurbulenceNoiseController.kt
@@ -17,7 +17,6 @@
import android.view.View
import androidx.annotation.VisibleForTesting
-import java.util.Random
/** Plays [TurbulenceNoiseView] in ease-in, main (no easing), and ease-out order. */
class TurbulenceNoiseController(private val turbulenceNoiseView: TurbulenceNoiseView) {
@@ -37,8 +36,6 @@
}
}
- private val random = Random()
-
/** Current state of the animation. */
@VisibleForTesting
var state: AnimationState = AnimationState.NOT_PLAYING
@@ -95,12 +92,7 @@
}
state = AnimationState.EASE_IN
- // Add offset to avoid repetitive noise.
- turbulenceNoiseView.playEaseIn(
- offsetX = random.nextFloat(),
- offsetY = random.nextFloat(),
- this::playMainAnimation
- )
+ turbulenceNoiseView.playEaseIn(this::playMainAnimation)
}
private fun playMainAnimation() {
diff --git a/packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/turbulencenoise/TurbulenceNoiseView.kt b/packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/turbulencenoise/TurbulenceNoiseView.kt
index c59bc10..5e72e3b 100644
--- a/packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/turbulencenoise/TurbulenceNoiseView.kt
+++ b/packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/turbulencenoise/TurbulenceNoiseView.kt
@@ -109,7 +109,7 @@
/** Plays the turbulence noise with linear ease-in. */
@VisibleForTesting(otherwise = VisibleForTesting.PACKAGE_PRIVATE)
- fun playEaseIn(offsetX: Float = 0f, offsetY: Float = 0f, onAnimationEnd: Runnable? = null) {
+ fun playEaseIn(onAnimationEnd: Runnable? = null) {
if (noiseConfig == null) {
return
}
@@ -129,8 +129,8 @@
val progress = updateListener.animatedValue as Float
shader.setNoiseMove(
- offsetX + initialX + timeInSec * config.noiseMoveSpeedX,
- offsetY + initialY + timeInSec * config.noiseMoveSpeedY,
+ initialX + timeInSec * config.noiseMoveSpeedX,
+ initialY + timeInSec * config.noiseMoveSpeedY,
initialZ + timeInSec * config.noiseMoveSpeedZ
)
diff --git a/packages/SystemUI/compose/core/src/com/android/compose/PlatformSlider.kt b/packages/SystemUI/compose/core/src/com/android/compose/PlatformSlider.kt
index b8c4fae..62dd4ac 100644
--- a/packages/SystemUI/compose/core/src/com/android/compose/PlatformSlider.kt
+++ b/packages/SystemUI/compose/core/src/com/android/compose/PlatformSlider.kt
@@ -57,9 +57,6 @@
import com.android.compose.modifiers.padding
import com.android.compose.theme.LocalAndroidColorScheme
-/** Indicator corner radius used when the user drags the [PlatformSlider]. */
-private val DefaultPlatformSliderDraggingCornerRadius = 8.dp
-
/**
* Platform slider implementation that displays a slider with an [icon] and a [label] at the start.
*
@@ -83,10 +80,8 @@
valueRange: ClosedFloatingPointRange<Float> = 0f..1f,
enabled: Boolean = true,
interactionSource: MutableInteractionSource = remember { MutableInteractionSource() },
- colors: PlatformSliderColors =
- if (isSystemInDarkTheme()) darkThemePlatformSliderColors()
- else lightThemePlatformSliderColors(),
- draggingCornersRadius: Dp = DefaultPlatformSliderDraggingCornerRadius,
+ colors: PlatformSliderColors = PlatformSliderDefaults.defaultPlatformSliderColors(),
+ draggingCornersRadius: Dp = PlatformSliderDefaults.DefaultPlatformSliderDraggingCornerRadius,
icon: (@Composable (isDragging: Boolean) -> Unit)? = null,
label: (@Composable (isDragging: Boolean) -> Unit)? = null,
) {
@@ -109,7 +104,7 @@
val paddingStart by
animateDpAsState(
targetValue =
- if ((!isDragging && value == 0f) || icon == null) {
+ if ((!isDragging && value == valueRange.start) || icon == null) {
16.dp
} else {
0.dp
@@ -125,6 +120,7 @@
valueRange = valueRange,
onValueChangeFinished = onValueChangeFinished,
interactionSource = interactionSource,
+ enabled = enabled,
track = {
Track(
sliderState = it,
@@ -286,6 +282,17 @@
val disabledLabelColor: Color,
)
+object PlatformSliderDefaults {
+
+ /** Indicator corner radius used when the user drags the [PlatformSlider]. */
+ val DefaultPlatformSliderDraggingCornerRadius = 8.dp
+
+ @Composable
+ fun defaultPlatformSliderColors(): PlatformSliderColors =
+ if (isSystemInDarkTheme()) darkThemePlatformSliderColors()
+ else lightThemePlatformSliderColors()
+}
+
/** [PlatformSliderColors] for the light theme */
@Composable
private fun lightThemePlatformSliderColors() =
diff --git a/packages/SystemUI/compose/facade/disabled/src/com/android/systemui/volume/panel/component/volume/VolumeSlidersModule.kt b/packages/SystemUI/compose/facade/disabled/src/com/android/systemui/volume/panel/component/volume/VolumeSlidersModule.kt
new file mode 100644
index 0000000..b4cb098
--- /dev/null
+++ b/packages/SystemUI/compose/facade/disabled/src/com/android/systemui/volume/panel/component/volume/VolumeSlidersModule.kt
@@ -0,0 +1,21 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.volume.panel.component.volume
+
+import dagger.Module
+
+@Module interface VolumeSlidersModule
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/LockscreenSceneBlueprintModule.kt b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/LockscreenSceneBlueprintModule.kt
index 3677cab..53f400f 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/LockscreenSceneBlueprintModule.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/LockscreenSceneBlueprintModule.kt
@@ -20,6 +20,8 @@
import com.android.systemui.keyguard.ui.composable.blueprint.DefaultBlueprintModule
import com.android.systemui.keyguard.ui.composable.blueprint.ShortcutsBesideUdfpsBlueprintModule
import com.android.systemui.keyguard.ui.composable.blueprint.SplitShadeBlueprintModule
+import com.android.systemui.keyguard.ui.composable.blueprint.SplitShadeWeatherClockBlueprintModule
+import com.android.systemui.keyguard.ui.composable.blueprint.WeatherClockBlueprintModule
import com.android.systemui.keyguard.ui.composable.section.OptionalSectionModule
import dagger.Module
@@ -31,6 +33,8 @@
OptionalSectionModule::class,
ShortcutsBesideUdfpsBlueprintModule::class,
SplitShadeBlueprintModule::class,
+ SplitShadeWeatherClockBlueprintModule::class,
+ WeatherClockBlueprintModule::class,
],
)
interface LockscreenSceneBlueprintModule
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/DefaultBlueprint.kt b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/DefaultBlueprint.kt
index a07ab4a..452dc03 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/DefaultBlueprint.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/DefaultBlueprint.kt
@@ -33,7 +33,7 @@
import com.android.systemui.keyguard.ui.composable.LockscreenLongPress
import com.android.systemui.keyguard.ui.composable.section.AmbientIndicationSection
import com.android.systemui.keyguard.ui.composable.section.BottomAreaSection
-import com.android.systemui.keyguard.ui.composable.section.ClockSection
+import com.android.systemui.keyguard.ui.composable.section.DefaultClockSection
import com.android.systemui.keyguard.ui.composable.section.LockSection
import com.android.systemui.keyguard.ui.composable.section.NotificationSection
import com.android.systemui.keyguard.ui.composable.section.SettingsMenuSection
@@ -56,7 +56,7 @@
constructor(
private val viewModel: LockscreenContentViewModel,
private val statusBarSection: StatusBarSection,
- private val clockSection: ClockSection,
+ private val clockSection: DefaultClockSection,
private val smartSpaceSection: SmartSpaceSection,
private val notificationSection: NotificationSection,
private val lockSection: LockSection,
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/ShortcutsBesideUdfpsBlueprint.kt b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/ShortcutsBesideUdfpsBlueprint.kt
index b035e42..71c60c7 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/ShortcutsBesideUdfpsBlueprint.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/ShortcutsBesideUdfpsBlueprint.kt
@@ -33,7 +33,7 @@
import com.android.systemui.keyguard.ui.composable.LockscreenLongPress
import com.android.systemui.keyguard.ui.composable.section.AmbientIndicationSection
import com.android.systemui.keyguard.ui.composable.section.BottomAreaSection
-import com.android.systemui.keyguard.ui.composable.section.ClockSection
+import com.android.systemui.keyguard.ui.composable.section.DefaultClockSection
import com.android.systemui.keyguard.ui.composable.section.LockSection
import com.android.systemui.keyguard.ui.composable.section.NotificationSection
import com.android.systemui.keyguard.ui.composable.section.SettingsMenuSection
@@ -56,7 +56,7 @@
constructor(
private val viewModel: LockscreenContentViewModel,
private val statusBarSection: StatusBarSection,
- private val clockSection: ClockSection,
+ private val clockSection: DefaultClockSection,
private val smartSpaceSection: SmartSpaceSection,
private val notificationSection: NotificationSection,
private val lockSection: LockSection,
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/SplitShadeBlueprint.kt b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/SplitShadeBlueprint.kt
index 44fe883..af836b6 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/SplitShadeBlueprint.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/SplitShadeBlueprint.kt
@@ -39,7 +39,7 @@
import com.android.systemui.keyguard.ui.composable.LockscreenLongPress
import com.android.systemui.keyguard.ui.composable.section.AmbientIndicationSection
import com.android.systemui.keyguard.ui.composable.section.BottomAreaSection
-import com.android.systemui.keyguard.ui.composable.section.ClockSection
+import com.android.systemui.keyguard.ui.composable.section.DefaultClockSection
import com.android.systemui.keyguard.ui.composable.section.LockSection
import com.android.systemui.keyguard.ui.composable.section.NotificationSection
import com.android.systemui.keyguard.ui.composable.section.SettingsMenuSection
@@ -63,7 +63,7 @@
constructor(
private val viewModel: LockscreenContentViewModel,
private val statusBarSection: StatusBarSection,
- private val clockSection: ClockSection,
+ private val clockSection: DefaultClockSection,
private val smartSpaceSection: SmartSpaceSection,
private val notificationSection: NotificationSection,
private val lockSection: LockSection,
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/WeatherClockBlueprint.kt b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/WeatherClockBlueprint.kt
new file mode 100644
index 0000000..e2e7a95
--- /dev/null
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/WeatherClockBlueprint.kt
@@ -0,0 +1,409 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.keyguard.ui.composable.blueprint
+
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.Row
+import androidx.compose.foundation.layout.fillMaxHeight
+import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.padding
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.layout.Layout
+import androidx.compose.ui.platform.LocalContext
+import androidx.compose.ui.res.dimensionResource
+import androidx.compose.ui.unit.Dp
+import androidx.compose.ui.unit.IntRect
+import androidx.compose.ui.unit.dp
+import com.android.compose.animation.scene.SceneScope
+import com.android.compose.modifiers.padding
+import com.android.systemui.Flags
+import com.android.systemui.keyguard.domain.interactor.KeyguardBlueprintInteractor.Companion.SPLIT_SHADE_WEATHER_CLOCK_BLUEPRINT_ID
+import com.android.systemui.keyguard.domain.interactor.KeyguardBlueprintInteractor.Companion.WEATHER_CLOCK_BLUEPRINT_ID
+import com.android.systemui.keyguard.domain.interactor.KeyguardClockInteractor
+import com.android.systemui.keyguard.ui.composable.LockscreenLongPress
+import com.android.systemui.keyguard.ui.composable.section.AmbientIndicationSection
+import com.android.systemui.keyguard.ui.composable.section.BottomAreaSection
+import com.android.systemui.keyguard.ui.composable.section.LockSection
+import com.android.systemui.keyguard.ui.composable.section.NotificationSection
+import com.android.systemui.keyguard.ui.composable.section.SettingsMenuSection
+import com.android.systemui.keyguard.ui.composable.section.SmartSpaceSection
+import com.android.systemui.keyguard.ui.composable.section.StatusBarSection
+import com.android.systemui.keyguard.ui.composable.section.WeatherClockSection
+import com.android.systemui.keyguard.ui.viewmodel.LockscreenContentViewModel
+import com.android.systemui.res.R
+import com.android.systemui.shade.LargeScreenHeaderHelper
+import dagger.Binds
+import dagger.Module
+import dagger.multibindings.IntoSet
+import java.util.Optional
+import javax.inject.Inject
+
+class WeatherClockBlueprint
+@Inject
+constructor(
+ private val viewModel: LockscreenContentViewModel,
+ private val statusBarSection: StatusBarSection,
+ private val weatherClockSection: WeatherClockSection,
+ private val smartSpaceSection: SmartSpaceSection,
+ private val notificationSection: NotificationSection,
+ private val lockSection: LockSection,
+ private val ambientIndicationSectionOptional: Optional<AmbientIndicationSection>,
+ private val bottomAreaSection: BottomAreaSection,
+ private val settingsMenuSection: SettingsMenuSection,
+ private val clockInteractor: KeyguardClockInteractor,
+) : ComposableLockscreenSceneBlueprint {
+
+ override val id: String = WEATHER_CLOCK_BLUEPRINT_ID
+ @Composable
+ override fun SceneScope.Content(modifier: Modifier) {
+ val isUdfpsVisible = viewModel.isUdfpsVisible
+ val burnIn = rememberBurnIn(clockInteractor)
+ val resources = LocalContext.current.resources
+
+ LockscreenLongPress(
+ viewModel = viewModel.longPress,
+ modifier = modifier,
+ ) { onSettingsMenuPlaced ->
+ Layout(
+ content = {
+ // Constrained to above the lock icon.
+ Column(
+ modifier = Modifier.fillMaxWidth(),
+ ) {
+ with(statusBarSection) { StatusBar(modifier = Modifier.fillMaxWidth()) }
+ // TODO: Add weather clock for small and large clock
+ with(smartSpaceSection) {
+ SmartSpace(
+ burnInParams = burnIn.parameters,
+ onTopChanged = burnIn.onSmartspaceTopChanged,
+ modifier =
+ Modifier.fillMaxWidth()
+ .padding(
+ top = { viewModel.getSmartSpacePaddingTop(resources) },
+ )
+ .padding(
+ bottom =
+ dimensionResource(
+ R.dimen.keyguard_status_view_bottom_margin
+ ),
+ ),
+ )
+ }
+
+ if (viewModel.areNotificationsVisible) {
+ with(notificationSection) {
+ Notifications(
+ modifier = Modifier.fillMaxWidth().weight(weight = 1f)
+ )
+ }
+ }
+
+ if (!isUdfpsVisible && ambientIndicationSectionOptional.isPresent) {
+ with(ambientIndicationSectionOptional.get()) {
+ AmbientIndication(modifier = Modifier.fillMaxWidth())
+ }
+ }
+ }
+
+ with(lockSection) { LockIcon() }
+
+ // Aligned to bottom and constrained to below the lock icon.
+ Column(modifier = Modifier.fillMaxWidth()) {
+ if (isUdfpsVisible && ambientIndicationSectionOptional.isPresent) {
+ with(ambientIndicationSectionOptional.get()) {
+ AmbientIndication(modifier = Modifier.fillMaxWidth())
+ }
+ }
+
+ with(bottomAreaSection) {
+ IndicationArea(modifier = Modifier.fillMaxWidth())
+ }
+ }
+
+ // Aligned to bottom and NOT constrained by the lock icon.
+ with(bottomAreaSection) {
+ Shortcut(isStart = true, applyPadding = true)
+ Shortcut(isStart = false, applyPadding = true)
+ }
+ with(settingsMenuSection) { SettingsMenu(onSettingsMenuPlaced) }
+ },
+ modifier = Modifier.fillMaxSize(),
+ ) { measurables, constraints ->
+ check(measurables.size == 6)
+ val aboveLockIconMeasurable = measurables[0]
+ val lockIconMeasurable = measurables[1]
+ val belowLockIconMeasurable = measurables[2]
+ val startShortcutMeasurable = measurables[3]
+ val endShortcutMeasurable = measurables[4]
+ val settingsMenuMeasurable = measurables[5]
+
+ val noMinConstraints =
+ constraints.copy(
+ minWidth = 0,
+ minHeight = 0,
+ )
+ val lockIconPlaceable = lockIconMeasurable.measure(noMinConstraints)
+ val lockIconBounds =
+ IntRect(
+ left = lockIconPlaceable[BlueprintAlignmentLines.LockIcon.Left],
+ top = lockIconPlaceable[BlueprintAlignmentLines.LockIcon.Top],
+ right = lockIconPlaceable[BlueprintAlignmentLines.LockIcon.Right],
+ bottom = lockIconPlaceable[BlueprintAlignmentLines.LockIcon.Bottom],
+ )
+
+ val aboveLockIconPlaceable =
+ aboveLockIconMeasurable.measure(
+ noMinConstraints.copy(maxHeight = lockIconBounds.top)
+ )
+ val belowLockIconPlaceable =
+ belowLockIconMeasurable.measure(
+ noMinConstraints.copy(
+ maxHeight =
+ (constraints.maxHeight - lockIconBounds.bottom).coerceAtLeast(0)
+ )
+ )
+ val startShortcutPleaceable = startShortcutMeasurable.measure(noMinConstraints)
+ val endShortcutPleaceable = endShortcutMeasurable.measure(noMinConstraints)
+ val settingsMenuPlaceable = settingsMenuMeasurable.measure(noMinConstraints)
+
+ layout(constraints.maxWidth, constraints.maxHeight) {
+ aboveLockIconPlaceable.place(
+ x = 0,
+ y = 0,
+ )
+ lockIconPlaceable.place(
+ x = lockIconBounds.left,
+ y = lockIconBounds.top,
+ )
+ belowLockIconPlaceable.place(
+ x = 0,
+ y = constraints.maxHeight - belowLockIconPlaceable.height,
+ )
+ startShortcutPleaceable.place(
+ x = 0,
+ y = constraints.maxHeight - startShortcutPleaceable.height,
+ )
+ endShortcutPleaceable.place(
+ x = constraints.maxWidth - endShortcutPleaceable.width,
+ y = constraints.maxHeight - endShortcutPleaceable.height,
+ )
+ settingsMenuPlaceable.place(
+ x = (constraints.maxWidth - settingsMenuPlaceable.width) / 2,
+ y = constraints.maxHeight - settingsMenuPlaceable.height,
+ )
+ }
+ }
+ }
+ }
+}
+
+class SplitShadeWeatherClockBlueprint
+@Inject
+constructor(
+ private val viewModel: LockscreenContentViewModel,
+ private val statusBarSection: StatusBarSection,
+ private val smartSpaceSection: SmartSpaceSection,
+ private val notificationSection: NotificationSection,
+ private val lockSection: LockSection,
+ private val ambientIndicationSectionOptional: Optional<AmbientIndicationSection>,
+ private val bottomAreaSection: BottomAreaSection,
+ private val settingsMenuSection: SettingsMenuSection,
+ private val clockInteractor: KeyguardClockInteractor,
+ private val largeScreenHeaderHelper: LargeScreenHeaderHelper,
+ private val weatherClockSection: WeatherClockSection,
+) : ComposableLockscreenSceneBlueprint {
+ override val id: String = SPLIT_SHADE_WEATHER_CLOCK_BLUEPRINT_ID
+
+ @Composable
+ override fun SceneScope.Content(modifier: Modifier) {
+ val isUdfpsVisible = viewModel.isUdfpsVisible
+ val burnIn = rememberBurnIn(clockInteractor)
+ val resources = LocalContext.current.resources
+
+ LockscreenLongPress(
+ viewModel = viewModel.longPress,
+ modifier = modifier,
+ ) { onSettingsMenuPlaced ->
+ Layout(
+ content = {
+ // Constrained to above the lock icon.
+ Column(
+ modifier = Modifier.fillMaxSize(),
+ ) {
+ with(statusBarSection) { StatusBar(modifier = Modifier.fillMaxWidth()) }
+ Row(
+ modifier = Modifier.fillMaxSize(),
+ ) {
+ // TODO: Add weather clock for small and large clock
+ Column(
+ modifier = Modifier.fillMaxHeight().weight(weight = 1f),
+ horizontalAlignment = Alignment.CenterHorizontally,
+ ) {
+ with(smartSpaceSection) {
+ SmartSpace(
+ burnInParams = burnIn.parameters,
+ onTopChanged = burnIn.onSmartspaceTopChanged,
+ modifier =
+ Modifier.fillMaxWidth()
+ .padding(
+ top = {
+ viewModel.getSmartSpacePaddingTop(resources)
+ },
+ )
+ .padding(
+ bottom =
+ dimensionResource(
+ R.dimen
+ .keyguard_status_view_bottom_margin
+ )
+ ),
+ )
+ }
+ }
+ with(notificationSection) {
+ val splitShadeTopMargin: Dp =
+ if (Flags.centralizedStatusBarHeightFix()) {
+ largeScreenHeaderHelper.getLargeScreenHeaderHeight().dp
+ } else {
+ dimensionResource(
+ id = R.dimen.large_screen_shade_header_height
+ )
+ }
+ Notifications(
+ modifier =
+ Modifier.fillMaxHeight()
+ .weight(weight = 1f)
+ .padding(top = splitShadeTopMargin)
+ )
+ }
+ }
+
+ if (!isUdfpsVisible && ambientIndicationSectionOptional.isPresent) {
+ with(ambientIndicationSectionOptional.get()) {
+ AmbientIndication(modifier = Modifier.fillMaxWidth())
+ }
+ }
+ }
+
+ with(lockSection) { LockIcon() }
+
+ // Aligned to bottom and constrained to below the lock icon.
+ Column(modifier = Modifier.fillMaxWidth()) {
+ if (isUdfpsVisible && ambientIndicationSectionOptional.isPresent) {
+ with(ambientIndicationSectionOptional.get()) {
+ AmbientIndication(modifier = Modifier.fillMaxWidth())
+ }
+ }
+
+ with(bottomAreaSection) {
+ IndicationArea(modifier = Modifier.fillMaxWidth())
+ }
+ }
+
+ // Aligned to bottom and NOT constrained by the lock icon.
+ with(bottomAreaSection) {
+ Shortcut(isStart = true, applyPadding = true)
+ Shortcut(isStart = false, applyPadding = true)
+ }
+ with(settingsMenuSection) { SettingsMenu(onSettingsMenuPlaced) }
+ },
+ modifier = Modifier.fillMaxSize(),
+ ) { measurables, constraints ->
+ check(measurables.size == 6)
+ val aboveLockIconMeasurable = measurables[0]
+ val lockIconMeasurable = measurables[1]
+ val belowLockIconMeasurable = measurables[2]
+ val startShortcutMeasurable = measurables[3]
+ val endShortcutMeasurable = measurables[4]
+ val settingsMenuMeasurable = measurables[5]
+
+ val noMinConstraints =
+ constraints.copy(
+ minWidth = 0,
+ minHeight = 0,
+ )
+ val lockIconPlaceable = lockIconMeasurable.measure(noMinConstraints)
+ val lockIconBounds =
+ IntRect(
+ left = lockIconPlaceable[BlueprintAlignmentLines.LockIcon.Left],
+ top = lockIconPlaceable[BlueprintAlignmentLines.LockIcon.Top],
+ right = lockIconPlaceable[BlueprintAlignmentLines.LockIcon.Right],
+ bottom = lockIconPlaceable[BlueprintAlignmentLines.LockIcon.Bottom],
+ )
+
+ val aboveLockIconPlaceable =
+ aboveLockIconMeasurable.measure(
+ noMinConstraints.copy(maxHeight = lockIconBounds.top)
+ )
+ val belowLockIconPlaceable =
+ belowLockIconMeasurable.measure(
+ noMinConstraints.copy(
+ maxHeight =
+ (constraints.maxHeight - lockIconBounds.bottom).coerceAtLeast(0)
+ )
+ )
+ val startShortcutPleaceable = startShortcutMeasurable.measure(noMinConstraints)
+ val endShortcutPleaceable = endShortcutMeasurable.measure(noMinConstraints)
+ val settingsMenuPlaceable = settingsMenuMeasurable.measure(noMinConstraints)
+
+ layout(constraints.maxWidth, constraints.maxHeight) {
+ aboveLockIconPlaceable.place(
+ x = 0,
+ y = 0,
+ )
+ lockIconPlaceable.place(
+ x = lockIconBounds.left,
+ y = lockIconBounds.top,
+ )
+ belowLockIconPlaceable.place(
+ x = 0,
+ y = constraints.maxHeight - belowLockIconPlaceable.height,
+ )
+ startShortcutPleaceable.place(
+ x = 0,
+ y = constraints.maxHeight - startShortcutPleaceable.height,
+ )
+ endShortcutPleaceable.place(
+ x = constraints.maxWidth - endShortcutPleaceable.width,
+ y = constraints.maxHeight - endShortcutPleaceable.height,
+ )
+ settingsMenuPlaceable.place(
+ x = (constraints.maxWidth - settingsMenuPlaceable.width) / 2,
+ y = constraints.maxHeight - settingsMenuPlaceable.height,
+ )
+ }
+ }
+ }
+ }
+}
+
+@Module
+interface WeatherClockBlueprintModule {
+ @Binds
+ @IntoSet
+ fun blueprint(blueprint: WeatherClockBlueprint): ComposableLockscreenSceneBlueprint
+}
+
+@Module
+interface SplitShadeWeatherClockBlueprintModule {
+ @Binds
+ @IntoSet
+ fun blueprint(blueprint: SplitShadeWeatherClockBlueprint): ComposableLockscreenSceneBlueprint
+}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/BottomAreaSection.kt b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/BottomAreaSection.kt
index 8bd0d45..97d5b41 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/BottomAreaSection.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/BottomAreaSection.kt
@@ -35,7 +35,6 @@
import com.android.systemui.keyguard.ui.binder.KeyguardIndicationAreaBinder
import com.android.systemui.keyguard.ui.binder.KeyguardQuickAffordanceViewBinder
import com.android.systemui.keyguard.ui.view.KeyguardIndicationArea
-import com.android.systemui.keyguard.ui.viewmodel.AodAlphaViewModel
import com.android.systemui.keyguard.ui.viewmodel.KeyguardIndicationAreaViewModel
import com.android.systemui.keyguard.ui.viewmodel.KeyguardQuickAffordanceViewModel
import com.android.systemui.keyguard.ui.viewmodel.KeyguardQuickAffordancesCombinedViewModel
@@ -55,7 +54,6 @@
private val vibratorHelper: VibratorHelper,
private val indicationController: KeyguardIndicationController,
private val indicationAreaViewModel: KeyguardIndicationAreaViewModel,
- private val alphaViewModel: AodAlphaViewModel,
) {
/**
* Renders a single lockscreen shortcut.
@@ -104,7 +102,6 @@
content {
IndicationArea(
indicationAreaViewModel = indicationAreaViewModel,
- alphaViewModel = alphaViewModel,
indicationController = indicationController,
)
}
@@ -183,7 +180,6 @@
@Composable
private fun IndicationArea(
indicationAreaViewModel: KeyguardIndicationAreaViewModel,
- alphaViewModel: AodAlphaViewModel,
indicationController: KeyguardIndicationController,
modifier: Modifier = Modifier,
) {
@@ -196,7 +192,6 @@
KeyguardIndicationAreaBinder.bind(
view = view,
viewModel = indicationAreaViewModel,
- aodAlphaViewModel = alphaViewModel,
indicationController = indicationController,
)
)
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/ClockSection.kt b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/DefaultClockSection.kt
similarity index 93%
rename from packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/ClockSection.kt
rename to packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/DefaultClockSection.kt
index fa07baf..335c915 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/ClockSection.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/DefaultClockSection.kt
@@ -18,6 +18,7 @@
import android.view.ViewGroup
import android.widget.FrameLayout
+import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.padding
import androidx.compose.runtime.Composable
import androidx.compose.runtime.DisposableEffect
@@ -38,14 +39,17 @@
import com.android.systemui.keyguard.ui.viewmodel.AodBurnInViewModel
import com.android.systemui.keyguard.ui.viewmodel.BurnInParameters
import com.android.systemui.keyguard.ui.viewmodel.KeyguardClockViewModel
+import com.android.systemui.statusbar.lockscreen.LockscreenSmartspaceController
import javax.inject.Inject
-class ClockSection
+/** Provides small clock and large clock composables for the default clock face. */
+class DefaultClockSection
@Inject
constructor(
private val viewModel: KeyguardClockViewModel,
private val clockInteractor: KeyguardClockInteractor,
private val aodBurnInViewModel: AodBurnInViewModel,
+ private val lockscreenSmartspaceController: LockscreenSmartspaceController,
) {
@Composable
@@ -151,6 +155,7 @@
(newClockView.parent as? ViewGroup)?.removeView(newClockView)
it.addView(newClockView)
},
+ modifier = Modifier.fillMaxSize()
)
}
}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/WeatherClockSection.kt b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/WeatherClockSection.kt
new file mode 100644
index 0000000..2e7bc2a
--- /dev/null
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/WeatherClockSection.kt
@@ -0,0 +1,60 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.keyguard.ui.composable.section
+
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Modifier
+import com.android.compose.animation.scene.SceneScope
+import javax.inject.Inject
+
+/** Provides small clock and large clock composables for the weather clock layout. */
+class WeatherClockSection @Inject constructor() {
+ @Composable
+ fun SceneScope.Time(
+ modifier: Modifier = Modifier,
+ ) {
+ // TODO: compose view
+ }
+
+ @Composable
+ fun SceneScope.Date(
+ modifier: Modifier = Modifier,
+ ) {
+ // TODO: compose view
+ }
+
+ @Composable
+ fun SceneScope.Weather(
+ modifier: Modifier = Modifier,
+ ) {
+ // TODO: compose view
+ }
+
+ @Composable
+ fun SceneScope.DndAlarmStatus(
+ modifier: Modifier = Modifier,
+ ) {
+ // TODO: compose view
+ }
+
+ @Composable
+ fun SceneScope.Temperature(
+ modifier: Modifier = Modifier,
+ ) {
+ // TODO: compose view
+ }
+}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/volume/VolumeSlidersModule.kt b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/volume/VolumeSlidersModule.kt
new file mode 100644
index 0000000..453ff02
--- /dev/null
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/volume/VolumeSlidersModule.kt
@@ -0,0 +1,43 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.volume.panel.component.volume
+
+import com.android.systemui.volume.panel.component.shared.model.VolumePanelComponents
+import com.android.systemui.volume.panel.component.volume.ui.composable.VolumeSlidersComponent
+import com.android.systemui.volume.panel.domain.AlwaysAvailableCriteria
+import com.android.systemui.volume.panel.domain.ComponentAvailabilityCriteria
+import com.android.systemui.volume.panel.shared.model.VolumePanelUiComponent
+import dagger.Binds
+import dagger.Module
+import dagger.multibindings.IntoMap
+import dagger.multibindings.StringKey
+
+@Module
+interface VolumeSlidersModule {
+
+ @Binds
+ @IntoMap
+ @StringKey(VolumePanelComponents.VOLUME_SLIDERS)
+ fun bindVolumePanelUiComponent(component: VolumeSlidersComponent): VolumePanelUiComponent
+
+ @Binds
+ @IntoMap
+ @StringKey(VolumePanelComponents.VOLUME_SLIDERS)
+ fun bindComponentAvailabilityCriteria(
+ criteria: AlwaysAvailableCriteria
+ ): ComponentAvailabilityCriteria
+}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/volume/ui/composable/ColumnVolumeSliders.kt b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/volume/ui/composable/ColumnVolumeSliders.kt
new file mode 100644
index 0000000..a197a4b
--- /dev/null
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/volume/ui/composable/ColumnVolumeSliders.kt
@@ -0,0 +1,200 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.volume.panel.component.volume.ui.composable
+
+import androidx.compose.animation.AnimatedVisibility
+import androidx.compose.animation.EnterTransition
+import androidx.compose.animation.ExitTransition
+import androidx.compose.animation.ExperimentalAnimationApi
+import androidx.compose.animation.core.tween
+import androidx.compose.animation.core.updateTransition
+import androidx.compose.animation.expandVertically
+import androidx.compose.animation.fadeIn
+import androidx.compose.animation.fadeOut
+import androidx.compose.animation.scaleIn
+import androidx.compose.animation.scaleOut
+import androidx.compose.animation.shrinkVertically
+import androidx.compose.animation.slideInVertically
+import androidx.compose.animation.slideOutVertically
+import androidx.compose.foundation.layout.Arrangement
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.Row
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.layout.size
+import androidx.compose.material3.Icon
+import androidx.compose.material3.IconButton
+import androidx.compose.material3.IconButtonDefaults
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.collectAsState
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.remember
+import androidx.compose.runtime.setValue
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.res.painterResource
+import androidx.compose.ui.unit.dp
+import com.android.compose.PlatformSliderColors
+import com.android.systemui.res.R
+import com.android.systemui.volume.panel.component.volume.slider.ui.viewmodel.SliderViewModel
+
+private const val EXPAND_DURATION_MILLIS = 500
+private const val COLLAPSE_DURATION_MILLIS = 300
+
+/** Volume sliders laid out in a collapsable column */
+@OptIn(ExperimentalAnimationApi::class)
+@Composable
+fun ColumnVolumeSliders(
+ viewModels: List<SliderViewModel>,
+ sliderColors: PlatformSliderColors,
+ isExpandable: Boolean,
+ modifier: Modifier = Modifier,
+) {
+ require(viewModels.isNotEmpty())
+ var isExpanded: Boolean by remember { mutableStateOf(false) }
+ val transition = updateTransition(isExpanded, label = "CollapsableSliders")
+ Column(modifier = modifier) {
+ Row(
+ modifier = Modifier.fillMaxWidth(),
+ horizontalArrangement = Arrangement.spacedBy(8.dp),
+ ) {
+ val sliderViewModel: SliderViewModel = viewModels.first()
+ val sliderState by viewModels.first().slider.collectAsState()
+ VolumeSlider(
+ modifier = Modifier.weight(1f),
+ state = sliderState,
+ onValueChangeFinished = { sliderViewModel.onValueChangeFinished(sliderState, it) },
+ sliderColors = sliderColors,
+ )
+
+ if (isExpandable) {
+ ExpandButton(
+ isExpanded = isExpanded,
+ onExpandedChanged = { isExpanded = it },
+ sliderColors,
+ Modifier,
+ )
+ }
+ }
+ transition.AnimatedVisibility(
+ visible = { it },
+ enter =
+ expandVertically(
+ animationSpec = tween(durationMillis = EXPAND_DURATION_MILLIS),
+ expandFrom = Alignment.CenterVertically,
+ ),
+ exit =
+ shrinkVertically(
+ animationSpec = tween(durationMillis = COLLAPSE_DURATION_MILLIS),
+ shrinkTowards = Alignment.CenterVertically,
+ ),
+ ) {
+ Column(modifier = Modifier.fillMaxWidth()) {
+ for (index in 1..viewModels.lastIndex) {
+ val sliderViewModel: SliderViewModel = viewModels[index]
+ val sliderState by sliderViewModel.slider.collectAsState()
+ transition.AnimatedVisibility(
+ visible = { it },
+ enter = enterTransition(index = index, totalCount = viewModels.size),
+ exit = exitTransition(index = index, totalCount = viewModels.size)
+ ) {
+ VolumeSlider(
+ modifier = Modifier.fillMaxWidth().padding(top = 16.dp),
+ state = sliderState,
+ onValueChangeFinished = {
+ sliderViewModel.onValueChangeFinished(sliderState, it)
+ },
+ sliderColors = sliderColors,
+ )
+ }
+ }
+ }
+ }
+ }
+}
+
+@Composable
+private fun ExpandButton(
+ isExpanded: Boolean,
+ onExpandedChanged: (Boolean) -> Unit,
+ sliderColors: PlatformSliderColors,
+ modifier: Modifier = Modifier,
+) {
+ IconButton(
+ modifier = modifier.size(64.dp),
+ onClick = { onExpandedChanged(!isExpanded) },
+ colors =
+ IconButtonDefaults.filledIconButtonColors(
+ containerColor = sliderColors.indicatorColor,
+ contentColor = sliderColors.iconColor
+ ),
+ ) {
+ Icon(
+ painter =
+ painterResource(
+ if (isExpanded) {
+ R.drawable.ic_filled_arrow_down
+ } else {
+ R.drawable.ic_filled_arrow_up
+ }
+ ),
+ contentDescription = null,
+ )
+ }
+}
+
+private fun enterTransition(index: Int, totalCount: Int): EnterTransition {
+ val enterDelay = ((totalCount - index + 1) * 10).coerceAtLeast(0)
+ val enterDuration = (EXPAND_DURATION_MILLIS - enterDelay).coerceAtLeast(100)
+ return slideInVertically(
+ initialOffsetY = { (it * 0.25).toInt() },
+ animationSpec = tween(durationMillis = enterDuration, delayMillis = enterDelay),
+ ) +
+ scaleIn(
+ initialScale = 0.9f,
+ animationSpec = tween(durationMillis = enterDuration, delayMillis = enterDelay),
+ ) +
+ expandVertically(
+ initialHeight = { (it * 0.65).toInt() },
+ animationSpec = tween(durationMillis = enterDuration, delayMillis = enterDelay),
+ clip = false,
+ expandFrom = Alignment.CenterVertically,
+ ) +
+ fadeIn(
+ animationSpec = tween(durationMillis = enterDuration, delayMillis = enterDelay),
+ )
+}
+
+private fun exitTransition(index: Int, totalCount: Int): ExitTransition {
+ val exitDuration = (COLLAPSE_DURATION_MILLIS - (totalCount - index + 1) * 10).coerceAtLeast(100)
+ return slideOutVertically(
+ targetOffsetY = { (it * 0.25).toInt() },
+ animationSpec = tween(durationMillis = exitDuration),
+ ) +
+ scaleOut(
+ targetScale = 0.9f,
+ animationSpec = tween(durationMillis = exitDuration),
+ ) +
+ shrinkVertically(
+ targetHeight = { (it * 0.65).toInt() },
+ animationSpec = tween(durationMillis = exitDuration),
+ clip = false,
+ shrinkTowards = Alignment.CenterVertically,
+ ) +
+ fadeOut(animationSpec = tween(durationMillis = exitDuration))
+}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/volume/ui/composable/GridVolumeSliders.kt b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/volume/ui/composable/GridVolumeSliders.kt
new file mode 100644
index 0000000..910ee72
--- /dev/null
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/volume/ui/composable/GridVolumeSliders.kt
@@ -0,0 +1,51 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.volume.panel.component.volume.ui.composable
+
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.collectAsState
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.unit.dp
+import com.android.compose.PlatformSliderColors
+import com.android.compose.grid.VerticalGrid
+import com.android.systemui.volume.panel.component.volume.slider.ui.viewmodel.SliderViewModel
+
+@Composable
+fun GridVolumeSliders(
+ viewModels: List<SliderViewModel>,
+ sliderColors: PlatformSliderColors,
+ modifier: Modifier = Modifier,
+) {
+ require(viewModels.isNotEmpty())
+ VerticalGrid(
+ modifier = modifier,
+ columns = 2,
+ verticalSpacing = 16.dp,
+ horizontalSpacing = 24.dp,
+ ) {
+ for (sliderViewModel in viewModels) {
+ val sliderState = sliderViewModel.slider.collectAsState().value
+ VolumeSlider(
+ modifier = Modifier.fillMaxWidth(),
+ state = sliderState,
+ onValueChangeFinished = { sliderViewModel.onValueChangeFinished(sliderState, it) },
+ sliderColors = sliderColors,
+ )
+ }
+ }
+}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/volume/ui/composable/VolumeSlider.kt b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/volume/ui/composable/VolumeSlider.kt
new file mode 100644
index 0000000..5925b14
--- /dev/null
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/volume/ui/composable/VolumeSlider.kt
@@ -0,0 +1,76 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.volume.panel.component.volume.ui.composable
+
+import androidx.compose.animation.AnimatedVisibility
+import androidx.compose.animation.animateContentSize
+import androidx.compose.animation.expandVertically
+import androidx.compose.animation.shrinkVertically
+import androidx.compose.foundation.layout.Column
+import androidx.compose.material3.MaterialTheme
+import androidx.compose.material3.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableFloatStateOf
+import androidx.compose.runtime.remember
+import androidx.compose.runtime.setValue
+import androidx.compose.ui.Modifier
+import com.android.compose.PlatformSlider
+import com.android.compose.PlatformSliderColors
+import com.android.systemui.common.ui.compose.Icon
+import com.android.systemui.volume.panel.component.volume.slider.ui.viewmodel.SliderState
+
+@Composable
+fun VolumeSlider(
+ state: SliderState,
+ onValueChangeFinished: (Float) -> Unit,
+ modifier: Modifier = Modifier,
+ sliderColors: PlatformSliderColors,
+) {
+ var value by remember { mutableFloatStateOf(state.value) }
+ PlatformSlider(
+ modifier = modifier,
+ value = value,
+ valueRange = state.valueRange,
+ onValueChange = { value = it },
+ onValueChangeFinished = { onValueChangeFinished(value) },
+ enabled = state.isEnabled,
+ icon = { isDragging ->
+ if (isDragging) {
+ Text(text = value.toInt().toString())
+ } else {
+ state.icon?.let { Icon(icon = it) }
+ }
+ },
+ colors = sliderColors,
+ label = {
+ Column(modifier = Modifier.animateContentSize()) {
+ Text(state.label, style = MaterialTheme.typography.titleMedium)
+
+ state.disabledMessage?.let { message ->
+ AnimatedVisibility(
+ !state.isEnabled,
+ enter = expandVertically { it },
+ exit = shrinkVertically { it },
+ ) {
+ Text(text = message, style = MaterialTheme.typography.bodySmall)
+ }
+ }
+ }
+ }
+ )
+}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/volume/ui/composable/VolumeSlidersComponent.kt b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/volume/ui/composable/VolumeSlidersComponent.kt
new file mode 100644
index 0000000..6213dc5
--- /dev/null
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/volume/ui/composable/VolumeSlidersComponent.kt
@@ -0,0 +1,58 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.volume.panel.component.volume.ui.composable
+
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.collectAsState
+import androidx.compose.runtime.getValue
+import androidx.compose.ui.Modifier
+import com.android.compose.PlatformSliderDefaults
+import com.android.systemui.volume.panel.component.volume.slider.ui.viewmodel.SliderViewModel
+import com.android.systemui.volume.panel.component.volume.ui.viewmodel.AudioVolumeComponentViewModel
+import com.android.systemui.volume.panel.ui.composable.ComposeVolumePanelUiComponent
+import com.android.systemui.volume.panel.ui.composable.VolumePanelComposeScope
+import javax.inject.Inject
+
+class VolumeSlidersComponent
+@Inject
+constructor(
+ private val viewModel: AudioVolumeComponentViewModel,
+) : ComposeVolumePanelUiComponent {
+
+ @Composable
+ override fun VolumePanelComposeScope.Content(modifier: Modifier) {
+ val sliderViewModels: List<SliderViewModel> by viewModel.sliderViewModels.collectAsState()
+ if (sliderViewModels.isEmpty()) {
+ return
+ }
+ if (isLargeScreen) {
+ GridVolumeSliders(
+ viewModels = sliderViewModels,
+ sliderColors = PlatformSliderDefaults.defaultPlatformSliderColors(),
+ modifier = modifier.fillMaxWidth(),
+ )
+ } else {
+ ColumnVolumeSliders(
+ viewModels = sliderViewModels,
+ sliderColors = PlatformSliderDefaults.defaultPlatformSliderColors(),
+ isExpandable = true,
+ modifier = modifier.fillMaxWidth(),
+ )
+ }
+ }
+}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/ui/composable/HorizontalVolumePanelContent.kt b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/ui/composable/HorizontalVolumePanelContent.kt
index 98ef067..0a651c8 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/ui/composable/HorizontalVolumePanelContent.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/ui/composable/HorizontalVolumePanelContent.kt
@@ -51,10 +51,8 @@
verticalArrangement = Arrangement.spacedBy(space = spacing, alignment = Alignment.Top)
) {
for (component in layout.headerComponents) {
- AnimatedVisibility(component.isVisible) {
- with(component.component as ComposeVolumePanelUiComponent) {
- Content(Modifier.weight(1f))
- }
+ AnimatedVisibility(visible = component.isVisible) {
+ with(component.component as ComposeVolumePanelUiComponent) { Content(Modifier) }
}
}
Row(
@@ -62,9 +60,12 @@
horizontalArrangement = Arrangement.spacedBy(spacing),
) {
for (component in layout.footerComponents) {
- AnimatedVisibility(component.isVisible) {
+ AnimatedVisibility(
+ visible = component.isVisible,
+ modifier = Modifier.weight(1f),
+ ) {
with(component.component as ComposeVolumePanelUiComponent) {
- Content(Modifier.weight(1f))
+ Content(Modifier)
}
}
}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/ui/composable/VerticalVolumePanelContent.kt b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/ui/composable/VerticalVolumePanelContent.kt
index 2285128..4d07379 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/ui/composable/VerticalVolumePanelContent.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/ui/composable/VerticalVolumePanelContent.kt
@@ -17,7 +17,6 @@
package com.android.systemui.volume.panel.ui.composable
import androidx.compose.animation.AnimatedVisibility
-import androidx.compose.animation.animateContentSize
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
@@ -34,14 +33,12 @@
modifier: Modifier = Modifier,
) {
Column(
- modifier = modifier.animateContentSize(),
+ modifier = modifier,
verticalArrangement = Arrangement.spacedBy(20.dp),
) {
for (component in layout.headerComponents) {
AnimatedVisibility(component.isVisible) {
- with(component.component as ComposeVolumePanelUiComponent) {
- Content(Modifier.weight(1f))
- }
+ with(component.component as ComposeVolumePanelUiComponent) { Content(Modifier) }
}
}
for (component in layout.contentComponents) {
@@ -55,9 +52,12 @@
horizontalArrangement = Arrangement.spacedBy(if (isLargeScreen) 28.dp else 20.dp),
) {
for (component in layout.footerComponents) {
- AnimatedVisibility(component.isVisible) {
+ AnimatedVisibility(
+ visible = component.isVisible,
+ modifier = Modifier.weight(1f),
+ ) {
with(component.component as ComposeVolumePanelUiComponent) {
- Content(Modifier.weight(1f))
+ Content(Modifier)
}
}
}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/ui/composable/VolumePanelComposeScope.kt b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/ui/composable/VolumePanelComposeScope.kt
index 8df8d2e..af69091 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/ui/composable/VolumePanelComposeScope.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/ui/composable/VolumePanelComposeScope.kt
@@ -29,7 +29,7 @@
/** Is true when Volume Panel is using large-screen layout and false the otherwise. */
val isLargeScreen: Boolean
- get() = state.isWideScreen
+ get() = state.isLargeScreen
}
val VolumePanelComposeScope.isPortrait: Boolean
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/UdfpsControllerOverlayTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/UdfpsControllerOverlayTest.kt
index 259f349..5034631 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/UdfpsControllerOverlayTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/UdfpsControllerOverlayTest.kt
@@ -44,12 +44,18 @@
import com.android.systemui.biometrics.ui.viewmodel.DeviceEntryUdfpsTouchOverlayViewModel
import com.android.systemui.bouncer.domain.interactor.AlternateBouncerInteractor
import com.android.systemui.bouncer.domain.interactor.PrimaryBouncerInteractor
+import com.android.systemui.classifier.FalsingCollector
import com.android.systemui.dump.DumpManager
import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
import com.android.systemui.plugins.statusbar.StatusBarStateController
+import com.android.systemui.power.data.repository.FakePowerRepository
+import com.android.systemui.power.domain.interactor.PowerInteractor
+import com.android.systemui.power.shared.model.WakeSleepReason
+import com.android.systemui.power.shared.model.WakefulnessState
import com.android.systemui.res.R
import com.android.systemui.shade.domain.interactor.ShadeInteractor
import com.android.systemui.statusbar.LockscreenShadeTransitionController
+import com.android.systemui.statusbar.phone.ScreenOffAnimationController
import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager
import com.android.systemui.statusbar.phone.SystemUIDialogManager
import com.android.systemui.statusbar.phone.UnlockedScreenOffAnimationController
@@ -58,6 +64,10 @@
import com.android.systemui.user.domain.interactor.SelectedUserInteractor
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.StandardTestDispatcher
+import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.runCurrent
+import kotlinx.coroutines.test.runTest
import org.junit.Before
import org.junit.Rule
import org.junit.Test
@@ -68,6 +78,7 @@
import org.mockito.Captor
import org.mockito.Mock
import org.mockito.Mockito.mock
+import org.mockito.Mockito.never
import org.mockito.Mockito.verify
import org.mockito.Mockito.`when` as whenever
import org.mockito.junit.MockitoJUnit
@@ -120,6 +131,9 @@
@Mock private lateinit var shadeInteractor: ShadeInteractor
@Captor private lateinit var layoutParamsCaptor: ArgumentCaptor<WindowManager.LayoutParams>
@Mock private lateinit var udfpsOverlayInteractor: UdfpsOverlayInteractor
+ private lateinit var powerRepository: FakePowerRepository
+ private lateinit var powerInteractor: PowerInteractor
+ private lateinit var testScope: TestScope
private val onTouch = { _: View, _: MotionEvent, _: Boolean -> true }
private var overlayParams: UdfpsOverlayParams = UdfpsOverlayParams()
@@ -127,6 +141,15 @@
@Before
fun setup() {
+ testScope = TestScope(StandardTestDispatcher())
+ powerRepository = FakePowerRepository()
+ powerInteractor =
+ PowerInteractor(
+ powerRepository,
+ mock(FalsingCollector::class.java),
+ mock(ScreenOffAnimationController::class.java),
+ statusBarStateController,
+ )
whenever(inflater.inflate(R.layout.udfps_view, null, false)).thenReturn(udfpsView)
whenever(inflater.inflate(R.layout.udfps_bp_view, null))
.thenReturn(mock(UdfpsBpView::class.java))
@@ -178,6 +201,8 @@
{ defaultUdfpsTouchOverlayViewModel },
shadeInteractor,
udfpsOverlayInteractor,
+ powerInteractor,
+ testScope,
)
block()
}
@@ -277,6 +302,66 @@
}
}
+ @Test
+ fun showUdfpsOverlay_awake() =
+ testScope.runTest {
+ withReason(REASON_AUTH_KEYGUARD) {
+ mSetFlagsRule.enableFlags(Flags.FLAG_UDFPS_VIEW_PERFORMANCE)
+ powerRepository.updateWakefulness(
+ rawState = WakefulnessState.AWAKE,
+ lastWakeReason = WakeSleepReason.POWER_BUTTON,
+ lastSleepReason = WakeSleepReason.OTHER,
+ )
+ controllerOverlay.show(udfpsController, overlayParams)
+ runCurrent()
+ verify(windowManager).addView(any(), any())
+ }
+ }
+
+ @Test
+ fun showUdfpsOverlay_whileGoingToSleep() =
+ testScope.runTest {
+ withReason(REASON_AUTH_KEYGUARD) {
+ mSetFlagsRule.enableFlags(Flags.FLAG_UDFPS_VIEW_PERFORMANCE)
+ powerRepository.updateWakefulness(
+ rawState = WakefulnessState.STARTING_TO_SLEEP,
+ lastWakeReason = WakeSleepReason.POWER_BUTTON,
+ lastSleepReason = WakeSleepReason.OTHER,
+ )
+ controllerOverlay.show(udfpsController, overlayParams)
+ runCurrent()
+ verify(windowManager, never()).addView(any(), any())
+
+ // we hide to end the job that listens for the finishedGoingToSleep signal
+ controllerOverlay.hide()
+ }
+ }
+
+ @Test
+ fun showUdfpsOverlay_afterFinishedGoingToSleep() =
+ testScope.runTest {
+ withReason(REASON_AUTH_KEYGUARD) {
+ mSetFlagsRule.enableFlags(Flags.FLAG_UDFPS_VIEW_PERFORMANCE)
+ powerRepository.updateWakefulness(
+ rawState = WakefulnessState.STARTING_TO_SLEEP,
+ lastWakeReason = WakeSleepReason.POWER_BUTTON,
+ lastSleepReason = WakeSleepReason.OTHER,
+ )
+ controllerOverlay.show(udfpsController, overlayParams)
+ runCurrent()
+ verify(windowManager, never()).addView(any(), any())
+
+ powerRepository.updateWakefulness(
+ rawState = WakefulnessState.ASLEEP,
+ lastWakeReason = WakeSleepReason.POWER_BUTTON,
+ lastSleepReason = WakeSleepReason.OTHER,
+ )
+ runCurrent()
+ verify(windowManager)
+ .addView(eq(controllerOverlay.getTouchOverlay()), layoutParamsCaptor.capture())
+ }
+ }
+
private fun showUdfpsOverlay() {
val didShow = controllerOverlay.show(udfpsController, overlayParams)
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/UdfpsControllerTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/UdfpsControllerTest.java
index 529403a..561cdbb 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/UdfpsControllerTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/UdfpsControllerTest.java
@@ -86,6 +86,7 @@
import com.android.systemui.biometrics.ui.viewmodel.DeviceEntryUdfpsTouchOverlayViewModel;
import com.android.systemui.bouncer.domain.interactor.AlternateBouncerInteractor;
import com.android.systemui.bouncer.domain.interactor.PrimaryBouncerInteractor;
+import com.android.systemui.classifier.FalsingCollector;
import com.android.systemui.deviceentry.domain.interactor.DeviceEntryFaceAuthInteractor;
import com.android.systemui.dump.DumpManager;
import com.android.systemui.flags.FeatureFlags;
@@ -94,10 +95,15 @@
import com.android.systemui.log.SessionTracker;
import com.android.systemui.plugins.FalsingManager;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
+import com.android.systemui.power.data.repository.FakePowerRepository;
+import com.android.systemui.power.domain.interactor.PowerInteractor;
+import com.android.systemui.power.shared.model.WakeSleepReason;
+import com.android.systemui.power.shared.model.WakefulnessState;
import com.android.systemui.res.R;
import com.android.systemui.shade.domain.interactor.ShadeInteractor;
import com.android.systemui.statusbar.LockscreenShadeTransitionController;
import com.android.systemui.statusbar.VibratorHelper;
+import com.android.systemui.statusbar.phone.ScreenOffAnimationController;
import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager;
import com.android.systemui.statusbar.phone.SystemUIDialogManager;
import com.android.systemui.statusbar.phone.UnlockedScreenOffAnimationController;
@@ -125,6 +131,8 @@
import java.util.ArrayList;
import java.util.List;
+import kotlinx.coroutines.CoroutineScope;
+
@SmallTest
@RunWith(AndroidJUnit4.class)
@RunWithLooper(setAsMainLooper = true)
@@ -238,6 +246,8 @@
private ScreenLifecycle.Observer mScreenObserver;
private FingerprintSensorPropertiesInternal mOpticalProps;
private FingerprintSensorPropertiesInternal mUltrasonicProps;
+ private PowerInteractor mPowerInteractor;
+ private FakePowerRepository mPowerRepository;
@Mock
private InputManager mInputManager;
@Mock
@@ -253,6 +263,19 @@
@Before
public void setUp() {
+ mPowerRepository = new FakePowerRepository();
+ mPowerInteractor = new PowerInteractor(
+ mPowerRepository,
+ mock(FalsingCollector.class),
+ mock(ScreenOffAnimationController.class),
+ mStatusBarStateController
+ );
+ mPowerRepository.updateWakefulness(
+ WakefulnessState.AWAKE,
+ WakeSleepReason.POWER_BUTTON,
+ WakeSleepReason.OTHER,
+ /* powerButtonLaunchGestureTriggered */ false
+ );
mContext.getOrCreateTestableResources()
.addOverride(com.android.internal.R.bool.config_ignoreUdfpsVote, false);
@@ -346,7 +369,9 @@
mKeyguardTransitionInteractor,
mDeviceEntryUdfpsTouchOverlayViewModel,
mDefaultUdfpsTouchOverlayViewModel,
- mUdfpsOverlayInteractor
+ mUdfpsOverlayInteractor,
+ mPowerInteractor,
+ mock(CoroutineScope.class)
);
verify(mFingerprintManager).setUdfpsOverlayController(mOverlayCaptor.capture());
mOverlayController = mOverlayCaptor.getValue();
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/CommunalSceneStartableTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/CommunalSceneStartableTest.kt
index a8fe16b..d86b35d 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/CommunalSceneStartableTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/CommunalSceneStartableTest.kt
@@ -20,6 +20,7 @@
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.communal.domain.interactor.communalInteractor
+import com.android.systemui.communal.domain.interactor.setCommunalAvailable
import com.android.systemui.communal.shared.model.CommunalSceneKey
import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.dock.DockManager
@@ -33,6 +34,7 @@
import com.android.systemui.testKosmos
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.launch
import kotlinx.coroutines.test.TestScope
import kotlinx.coroutines.test.advanceTimeBy
import kotlinx.coroutines.test.runCurrent
@@ -50,7 +52,7 @@
private lateinit var underTest: CommunalSceneStartable
@Before
- fun setUp() =
+ fun setUp() {
with(kosmos) {
underTest =
CommunalSceneStartable(
@@ -61,7 +63,15 @@
bgScope = applicationCoroutineScope,
)
.apply { start() }
+
+ // Make communal available so that communalInteractor.desiredScene accurately reflects
+ // scene changes instead of just returning Blank.
+ with(kosmos.testScope) {
+ launch { setCommunalAvailable(true) }
+ testScheduler.runCurrent()
+ }
}
+ }
@Test
fun keyguardGoesAway_forceBlankScene() =
@@ -249,4 +259,10 @@
fakeDockManager.setDockEvent(DockManager.STATE_DOCKED)
runCurrent()
}
+
+ private suspend fun TestScope.enableCommunal() =
+ with(kosmos) {
+ setCommunalAvailable(true)
+ runCurrent()
+ }
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalInteractorTest.kt
index 4156d83..cd29652 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalInteractorTest.kt
@@ -18,7 +18,9 @@
package com.android.systemui.communal.domain.interactor
import android.app.smartspace.SmartspaceTarget
+import android.appwidget.AppWidgetProviderInfo
import android.content.pm.UserInfo
+import android.os.UserHandle
import android.provider.Settings.Secure.HUB_MODE_TUTORIAL_COMPLETED
import android.widget.RemoteViews
import androidx.test.ext.junit.runners.AndroidJUnit4
@@ -51,6 +53,8 @@
import com.android.systemui.scene.domain.interactor.sceneInteractor
import com.android.systemui.scene.shared.flag.fakeSceneContainerFlags
import com.android.systemui.scene.shared.model.SceneKey
+import com.android.systemui.settings.FakeUserTracker
+import com.android.systemui.settings.fakeUserTracker
import com.android.systemui.smartspace.data.repository.FakeSmartspaceRepository
import com.android.systemui.smartspace.data.repository.fakeSmartspaceRepository
import com.android.systemui.testKosmos
@@ -96,6 +100,7 @@
private lateinit var communalPrefsRepository: FakeCommunalPrefsRepository
private lateinit var editWidgetsActivityStarter: EditWidgetsActivityStarter
private lateinit var sceneInteractor: SceneInteractor
+ private lateinit var userTracker: FakeUserTracker
private lateinit var underTest: CommunalInteractor
@@ -113,6 +118,7 @@
editWidgetsActivityStarter = kosmos.editWidgetsActivityStarter
communalPrefsRepository = kosmos.fakeCommunalPrefsRepository
sceneInteractor = kosmos.sceneInteractor
+ userTracker = kosmos.fakeUserTracker
whenever(mainUser.isMain).thenReturn(true)
whenever(secondaryUser.isMain).thenReturn(false)
@@ -207,25 +213,19 @@
keyguardRepository.setKeyguardOccluded(false)
tutorialRepository.setTutorialSettingState(HUB_MODE_TUTORIAL_COMPLETED)
- // Widgets are available.
- val widgets =
- listOf(
- CommunalWidgetContentModel(
- appWidgetId = 0,
- priority = 30,
- providerInfo = mock(),
- ),
- CommunalWidgetContentModel(
- appWidgetId = 1,
- priority = 20,
- providerInfo = mock(),
- ),
- CommunalWidgetContentModel(
- appWidgetId = 2,
- priority = 10,
- providerInfo = mock(),
- ),
- )
+ val userInfos = listOf(MAIN_USER_INFO, USER_INFO_WORK)
+ userRepository.setUserInfos(userInfos)
+ userTracker.set(
+ userInfos = userInfos,
+ selectedUserIndex = 0,
+ )
+ runCurrent()
+
+ // Widgets available.
+ val widget1 = createWidgetForUser(1, USER_INFO_WORK.id)
+ val widget2 = createWidgetForUser(2, MAIN_USER_INFO.id)
+ val widget3 = createWidgetForUser(3, MAIN_USER_INFO.id)
+ val widgets = listOf(widget1, widget2, widget3)
widgetRepository.setCommunalWidgets(widgets)
val widgetContent by collectLastValue(underTest.widgetContent)
@@ -455,6 +455,9 @@
@Test
fun listensToSceneChange() =
testScope.runTest {
+ kosmos.setCommunalAvailable(true)
+ runCurrent()
+
var desiredScene = collectLastValue(underTest.desiredScene)
runCurrent()
assertThat(desiredScene()).isEqualTo(CommunalSceneKey.Blank)
@@ -479,6 +482,30 @@
}
@Test
+ fun desiredScene_communalNotAvailable_returnsBlank() =
+ testScope.runTest {
+ kosmos.setCommunalAvailable(true)
+ runCurrent()
+
+ val desiredScene by collectLastValue(underTest.desiredScene)
+
+ underTest.onSceneChanged(CommunalSceneKey.Communal)
+ assertThat(desiredScene).isEqualTo(CommunalSceneKey.Communal)
+
+ kosmos.setCommunalAvailable(false)
+ runCurrent()
+
+ // Scene returns blank when communal is not available.
+ assertThat(desiredScene).isEqualTo(CommunalSceneKey.Blank)
+
+ kosmos.setCommunalAvailable(true)
+ runCurrent()
+
+ // After re-enabling, scene goes back to Communal.
+ assertThat(desiredScene).isEqualTo(CommunalSceneKey.Communal)
+ }
+
+ @Test
fun transitionProgress_onTargetScene_fullProgress() =
testScope.runTest {
val targetScene = CommunalSceneKey.Blank
@@ -604,8 +631,28 @@
}
@Test
+ fun isCommunalShowing() =
+ testScope.runTest {
+ kosmos.setCommunalAvailable(true)
+ runCurrent()
+
+ var isCommunalShowing = collectLastValue(underTest.isCommunalShowing)
+ runCurrent()
+ assertThat(isCommunalShowing()).isEqualTo(false)
+
+ underTest.onSceneChanged(CommunalSceneKey.Communal)
+
+ isCommunalShowing = collectLastValue(underTest.isCommunalShowing)
+ runCurrent()
+ assertThat(isCommunalShowing()).isEqualTo(true)
+ }
+
+ @Test
fun isCommunalShowing_whenSceneContainerDisabled() =
testScope.runTest {
+ kosmos.setCommunalAvailable(true)
+ runCurrent()
+
// Verify default is false
val isCommunalShowing by collectLastValue(underTest.isCommunalShowing)
runCurrent()
@@ -752,6 +799,38 @@
verify(editWidgetsActivityStarter).startActivity(widgetKey)
}
+ @Test
+ fun filterWidgets_whenUserProfileRemoved() =
+ testScope.runTest {
+ // Keyguard showing, and tutorial completed.
+ keyguardRepository.setKeyguardShowing(true)
+ keyguardRepository.setKeyguardOccluded(false)
+ tutorialRepository.setTutorialSettingState(HUB_MODE_TUTORIAL_COMPLETED)
+
+ // Only main user exists.
+ val userInfos = listOf(MAIN_USER_INFO)
+ userRepository.setUserInfos(userInfos)
+ userTracker.set(
+ userInfos = userInfos,
+ selectedUserIndex = 0,
+ )
+ runCurrent()
+
+ val widgetContent by collectLastValue(underTest.widgetContent)
+ // Given three widgets, and one of them is associated with pre-existing work profile.
+ val widget1 = createWidgetForUser(1, USER_INFO_WORK.id)
+ val widget2 = createWidgetForUser(2, MAIN_USER_INFO.id)
+ val widget3 = createWidgetForUser(3, MAIN_USER_INFO.id)
+ val widgets = listOf(widget1, widget2, widget3)
+ widgetRepository.setCommunalWidgets(widgets)
+
+ // One widget is filtered out and the remaining two link to main user id.
+ assertThat(checkNotNull(widgetContent).size).isEqualTo(2)
+ widgetContent!!.forEachIndexed { _, model ->
+ assertThat(model.providerInfo.profile?.identifier).isEqualTo(MAIN_USER_INFO.id)
+ }
+ }
+
private fun smartspaceTimer(id: String, timestamp: Long = 0L): SmartspaceTarget {
val timer = mock(SmartspaceTarget::class.java)
whenever(timer.smartspaceTargetId).thenReturn(id)
@@ -760,4 +839,17 @@
whenever(timer.creationTimeMillis).thenReturn(timestamp)
return timer
}
+
+ private fun createWidgetForUser(appWidgetId: Int, userId: Int): CommunalWidgetContentModel =
+ mock<CommunalWidgetContentModel> {
+ whenever(this.appWidgetId).thenReturn(appWidgetId)
+ val providerInfo = mock<AppWidgetProviderInfo>()
+ whenever(providerInfo.profile).thenReturn(UserHandle(userId))
+ whenever(this.providerInfo).thenReturn(providerInfo)
+ }
+
+ private companion object {
+ val MAIN_USER_INFO = UserInfo(0, "primary", UserInfo.FLAG_MAIN)
+ val USER_INFO_WORK = UserInfo(10, "work", UserInfo.FLAG_PROFILE)
+ }
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalTutorialInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalTutorialInteractorTest.kt
index 5211c55..8b78592 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalTutorialInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalTutorialInteractorTest.kt
@@ -16,7 +16,6 @@
package com.android.systemui.communal.domain.interactor
-import android.content.pm.UserInfo
import android.provider.Settings.Secure.HUB_MODE_TUTORIAL_COMPLETED
import android.provider.Settings.Secure.HUB_MODE_TUTORIAL_NOT_STARTED
import android.provider.Settings.Secure.HUB_MODE_TUTORIAL_STARTED
@@ -61,7 +60,6 @@
communalInteractor = kosmos.communalInteractor
userRepository = kosmos.fakeUserRepository
- userRepository.setUserInfos(listOf(MAIN_USER_INFO))
kosmos.fakeFeatureFlagsClassic.set(Flags.COMMUNAL_SERVICE_ENABLED, true)
mSetFlagsRule.enableFlags(FLAG_COMMUNAL_HUB)
@@ -72,7 +70,7 @@
fun tutorialUnavailable_whenKeyguardNotVisible() =
testScope.runTest {
val isTutorialAvailable by collectLastValue(underTest.isTutorialAvailable)
- setCommunalAvailable(true)
+ kosmos.setCommunalAvailable(true)
communalTutorialRepository.setTutorialSettingState(HUB_MODE_TUTORIAL_NOT_STARTED)
keyguardRepository.setKeyguardShowing(false)
assertThat(isTutorialAvailable).isFalse()
@@ -82,10 +80,7 @@
fun tutorialUnavailable_whenTutorialIsCompleted() =
testScope.runTest {
val isTutorialAvailable by collectLastValue(underTest.isTutorialAvailable)
- setCommunalAvailable(true)
- keyguardRepository.setKeyguardShowing(true)
- keyguardRepository.setKeyguardOccluded(false)
- communalInteractor.onSceneChanged(CommunalSceneKey.Communal)
+ goToCommunal()
communalTutorialRepository.setTutorialSettingState(HUB_MODE_TUTORIAL_COMPLETED)
assertThat(isTutorialAvailable).isFalse()
}
@@ -94,7 +89,7 @@
fun tutorialUnavailable_whenCommunalNotAvailable() =
testScope.runTest {
val isTutorialAvailable by collectLastValue(underTest.isTutorialAvailable)
- setCommunalAvailable(false)
+ kosmos.setCommunalAvailable(false)
communalTutorialRepository.setTutorialSettingState(HUB_MODE_TUTORIAL_NOT_STARTED)
keyguardRepository.setKeyguardShowing(true)
assertThat(isTutorialAvailable).isFalse()
@@ -104,10 +99,7 @@
fun tutorialAvailable_whenTutorialNotStarted() =
testScope.runTest {
val isTutorialAvailable by collectLastValue(underTest.isTutorialAvailable)
- setCommunalAvailable(true)
- keyguardRepository.setKeyguardShowing(true)
- keyguardRepository.setKeyguardOccluded(false)
- communalInteractor.onSceneChanged(CommunalSceneKey.Blank)
+ kosmos.setCommunalAvailable(true)
communalTutorialRepository.setTutorialSettingState(HUB_MODE_TUTORIAL_NOT_STARTED)
assertThat(isTutorialAvailable).isTrue()
}
@@ -116,10 +108,7 @@
fun tutorialAvailable_whenTutorialIsStarted() =
testScope.runTest {
val isTutorialAvailable by collectLastValue(underTest.isTutorialAvailable)
- setCommunalAvailable(true)
- keyguardRepository.setKeyguardShowing(true)
- keyguardRepository.setKeyguardOccluded(false)
- communalInteractor.onSceneChanged(CommunalSceneKey.Communal)
+ goToCommunal()
communalTutorialRepository.setTutorialSettingState(HUB_MODE_TUTORIAL_STARTED)
assertThat(isTutorialAvailable).isTrue()
}
@@ -129,10 +118,9 @@
testScope.runTest {
val tutorialSettingState by
collectLastValue(communalTutorialRepository.tutorialSettingState)
- userRepository.setSelectedUserInfo(MAIN_USER_INFO)
communalTutorialRepository.setTutorialSettingState(HUB_MODE_TUTORIAL_NOT_STARTED)
- communalInteractor.onSceneChanged(CommunalSceneKey.Communal)
+ goToCommunal()
assertThat(tutorialSettingState).isEqualTo(HUB_MODE_TUTORIAL_STARTED)
}
@@ -142,10 +130,10 @@
testScope.runTest {
val tutorialSettingState by
collectLastValue(communalTutorialRepository.tutorialSettingState)
- userRepository.setSelectedUserInfo(MAIN_USER_INFO)
+
communalTutorialRepository.setTutorialSettingState(HUB_MODE_TUTORIAL_STARTED)
- communalInteractor.onSceneChanged(CommunalSceneKey.Communal)
+ goToCommunal()
assertThat(tutorialSettingState).isEqualTo(HUB_MODE_TUTORIAL_STARTED)
}
@@ -155,10 +143,9 @@
testScope.runTest {
val tutorialSettingState by
collectLastValue(communalTutorialRepository.tutorialSettingState)
- userRepository.setSelectedUserInfo(MAIN_USER_INFO)
communalTutorialRepository.setTutorialSettingState(HUB_MODE_TUTORIAL_COMPLETED)
- communalInteractor.onSceneChanged(CommunalSceneKey.Communal)
+ goToCommunal()
assertThat(tutorialSettingState).isEqualTo(HUB_MODE_TUTORIAL_COMPLETED)
}
@@ -168,7 +155,7 @@
testScope.runTest {
val tutorialSettingState by
collectLastValue(communalTutorialRepository.tutorialSettingState)
- userRepository.setSelectedUserInfo(MAIN_USER_INFO)
+ kosmos.setCommunalAvailable(true)
communalTutorialRepository.setTutorialSettingState(HUB_MODE_TUTORIAL_NOT_STARTED)
communalInteractor.onSceneChanged(CommunalSceneKey.Blank)
@@ -181,8 +168,7 @@
testScope.runTest {
val tutorialSettingState by
collectLastValue(communalTutorialRepository.tutorialSettingState)
- userRepository.setSelectedUserInfo(MAIN_USER_INFO)
- communalInteractor.onSceneChanged(CommunalSceneKey.Communal)
+ goToCommunal()
communalTutorialRepository.setTutorialSettingState(HUB_MODE_TUTORIAL_STARTED)
communalInteractor.onSceneChanged(CommunalSceneKey.Blank)
@@ -195,8 +181,7 @@
testScope.runTest {
val tutorialSettingState by
collectLastValue(communalTutorialRepository.tutorialSettingState)
- userRepository.setSelectedUserInfo(MAIN_USER_INFO)
- communalInteractor.onSceneChanged(CommunalSceneKey.Communal)
+ goToCommunal()
communalTutorialRepository.setTutorialSettingState(HUB_MODE_TUTORIAL_COMPLETED)
communalInteractor.onSceneChanged(CommunalSceneKey.Blank)
@@ -204,17 +189,8 @@
assertThat(tutorialSettingState).isEqualTo(HUB_MODE_TUTORIAL_COMPLETED)
}
- private suspend fun setCommunalAvailable(available: Boolean) {
- if (available) {
- keyguardRepository.setIsEncryptedOrLockdown(false)
- userRepository.setSelectedUserInfo(MAIN_USER_INFO)
- keyguardRepository.setKeyguardShowing(true)
- } else {
- keyguardRepository.setIsEncryptedOrLockdown(true)
- }
- }
-
- private companion object {
- val MAIN_USER_INFO = UserInfo(0, "primary", UserInfo.FLAG_MAIN)
+ private suspend fun goToCommunal() {
+ kosmos.setCommunalAvailable(true)
+ communalInteractor.onSceneChanged(CommunalSceneKey.Communal)
}
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalEditModeViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalEditModeViewModelTest.kt
index 352bacc..5ee88cb 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalEditModeViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalEditModeViewModelTest.kt
@@ -17,6 +17,9 @@
package com.android.systemui.communal.view.viewmodel
import android.app.smartspace.SmartspaceTarget
+import android.appwidget.AppWidgetProviderInfo
+import android.content.pm.UserInfo
+import android.os.UserHandle
import android.provider.Settings
import android.widget.RemoteViews
import androidx.test.ext.junit.runners.AndroidJUnit4
@@ -39,6 +42,7 @@
import com.android.systemui.kosmos.testScope
import com.android.systemui.log.logcatLogBuffer
import com.android.systemui.media.controls.ui.view.MediaHost
+import com.android.systemui.settings.fakeUserTracker
import com.android.systemui.smartspace.data.repository.FakeSmartspaceRepository
import com.android.systemui.smartspace.data.repository.fakeSmartspaceRepository
import com.android.systemui.testKosmos
@@ -59,6 +63,7 @@
class CommunalEditModeViewModelTest : SysuiTestCase() {
@Mock private lateinit var mediaHost: MediaHost
@Mock private lateinit var uiEventLogger: UiEventLogger
+ @Mock private lateinit var providerInfo: AppWidgetProviderInfo
private val kosmos = testKosmos()
private val testScope = kosmos.testScope
@@ -78,6 +83,11 @@
widgetRepository = kosmos.fakeCommunalWidgetRepository
smartspaceRepository = kosmos.fakeSmartspaceRepository
mediaRepository = kosmos.fakeCommunalMediaRepository
+ kosmos.fakeUserTracker.set(
+ userInfos = listOf(MAIN_USER_INFO),
+ selectedUserIndex = 0,
+ )
+ whenever(providerInfo.profile).thenReturn(UserHandle(MAIN_USER_INFO.id))
underTest =
CommunalEditModeViewModel(
@@ -100,12 +110,12 @@
CommunalWidgetContentModel(
appWidgetId = 0,
priority = 30,
- providerInfo = mock(),
+ providerInfo = providerInfo,
),
CommunalWidgetContentModel(
appWidgetId = 1,
priority = 20,
- providerInfo = mock(),
+ providerInfo = providerInfo,
),
)
widgetRepository.setCommunalWidgets(widgets)
@@ -156,12 +166,12 @@
CommunalWidgetContentModel(
appWidgetId = 0,
priority = 30,
- providerInfo = mock(),
+ providerInfo = providerInfo,
),
CommunalWidgetContentModel(
appWidgetId = 1,
priority = 20,
- providerInfo = mock(),
+ providerInfo = providerInfo,
),
)
widgetRepository.setCommunalWidgets(widgets)
@@ -205,4 +215,8 @@
underTest.onReorderWidgetCancel()
verify(uiEventLogger).log(CommunalUiEvent.COMMUNAL_HUB_REORDER_WIDGET_CANCEL)
}
+
+ private companion object {
+ val MAIN_USER_INFO = UserInfo(0, "primary", UserInfo.FLAG_MAIN)
+ }
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalViewModelTest.kt
index cc322d0..1e523dd 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalViewModelTest.kt
@@ -17,7 +17,9 @@
package com.android.systemui.communal.view.viewmodel
import android.app.smartspace.SmartspaceTarget
+import android.appwidget.AppWidgetProviderInfo
import android.content.pm.UserInfo
+import android.os.UserHandle
import android.provider.Settings
import android.widget.RemoteViews
import androidx.test.ext.junit.runners.AndroidJUnit4
@@ -45,13 +47,13 @@
import com.android.systemui.log.logcatLogBuffer
import com.android.systemui.media.controls.ui.controller.MediaHierarchyManager
import com.android.systemui.media.controls.ui.view.MediaHost
+import com.android.systemui.settings.fakeUserTracker
import com.android.systemui.shade.domain.interactor.shadeInteractor
import com.android.systemui.smartspace.data.repository.FakeSmartspaceRepository
import com.android.systemui.smartspace.data.repository.fakeSmartspaceRepository
import com.android.systemui.testKosmos
import com.android.systemui.user.data.repository.FakeUserRepository
import com.android.systemui.user.data.repository.fakeUserRepository
-import com.android.systemui.util.mockito.mock
import com.android.systemui.util.mockito.whenever
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.ExperimentalCoroutinesApi
@@ -71,6 +73,7 @@
class CommunalViewModelTest : SysuiTestCase() {
@Mock private lateinit var mediaHost: MediaHost
@Mock private lateinit var user: UserInfo
+ @Mock private lateinit var providerInfo: AppWidgetProviderInfo
private val kosmos = testKosmos()
private val testScope = kosmos.testScope
@@ -98,6 +101,12 @@
kosmos.fakeFeatureFlagsClassic.set(COMMUNAL_SERVICE_ENABLED, true)
mSetFlagsRule.enableFlags(FLAG_COMMUNAL_HUB)
+ kosmos.fakeUserTracker.set(
+ userInfos = listOf(MAIN_USER_INFO),
+ selectedUserIndex = 0,
+ )
+ whenever(providerInfo.profile).thenReturn(UserHandle(MAIN_USER_INFO.id))
+
underTest =
CommunalViewModel(
testScope,
@@ -147,12 +156,12 @@
CommunalWidgetContentModel(
appWidgetId = 0,
priority = 30,
- providerInfo = mock(),
+ providerInfo = providerInfo,
),
CommunalWidgetContentModel(
appWidgetId = 1,
priority = 20,
- providerInfo = mock(),
+ providerInfo = providerInfo,
),
)
widgetRepository.setCommunalWidgets(widgets)
@@ -225,4 +234,8 @@
userRepository.setUserInfos(listOf(user))
userRepository.setSelectedUserInfo(user)
}
+
+ private companion object {
+ val MAIN_USER_INFO = UserInfo(0, "primary", UserInfo.FLAG_MAIN)
+ }
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/widgets/CommunalAppWidgetHostStartableTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/widgets/CommunalAppWidgetHostStartableTest.kt
index 8488843..2c9d72c 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/widgets/CommunalAppWidgetHostStartableTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/widgets/CommunalAppWidgetHostStartableTest.kt
@@ -16,7 +16,9 @@
package com.android.systemui.communal.widgets
+import android.appwidget.AppWidgetProviderInfo
import android.content.pm.UserInfo
+import android.os.UserHandle
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.systemui.Flags.FLAG_COMMUNAL_HUB
@@ -32,6 +34,7 @@
import com.android.systemui.kosmos.applicationCoroutineScope
import com.android.systemui.kosmos.testDispatcher
import com.android.systemui.kosmos.testScope
+import com.android.systemui.settings.fakeUserTracker
import com.android.systemui.testKosmos
import com.android.systemui.user.data.repository.fakeUserRepository
import com.android.systemui.util.mockito.mock
@@ -65,7 +68,7 @@
@Before
fun setUp() {
MockitoAnnotations.initMocks(this)
- kosmos.fakeUserRepository.setUserInfos(listOf(MAIN_USER_INFO))
+ kosmos.fakeUserRepository.setUserInfos(listOf(MAIN_USER_INFO, USER_INFO_WORK))
kosmos.fakeFeatureFlagsClassic.set(Flags.COMMUNAL_SERVICE_ENABLED, true)
mSetFlagsRule.enableFlags(FLAG_COMMUNAL_HUB)
@@ -76,6 +79,7 @@
CommunalAppWidgetHostStartable(
appWidgetHost,
kosmos.communalInteractor,
+ kosmos.fakeUserTracker,
kosmos.applicationCoroutineScope,
kosmos.testDispatcher,
)
@@ -170,6 +174,46 @@
}
}
+ @Test
+ fun removeWidgetsForDeletedProfile_whenCommunalIsAvailable() =
+ with(kosmos) {
+ testScope.runTest {
+ // Communal is available and work profile is configured.
+ setCommunalAvailable(true)
+ kosmos.fakeUserTracker.set(
+ userInfos = listOf(MAIN_USER_INFO, USER_INFO_WORK),
+ selectedUserIndex = 0,
+ )
+ val widget1 = createWidgetForUser(1, USER_INFO_WORK.id)
+ val widget2 = createWidgetForUser(2, MAIN_USER_INFO.id)
+ val widget3 = createWidgetForUser(3, MAIN_USER_INFO.id)
+ val widgets = listOf(widget1, widget2, widget3)
+ fakeCommunalWidgetRepository.setCommunalWidgets(widgets)
+
+ underTest.start()
+ runCurrent()
+
+ val communalWidgets by
+ collectLastValue(fakeCommunalWidgetRepository.communalWidgets)
+ assertThat(communalWidgets).containsExactly(widget1, widget2, widget3)
+
+ // Unlock the device and remove work profile.
+ fakeKeyguardRepository.setKeyguardShowing(false)
+ kosmos.fakeUserTracker.set(
+ userInfos = listOf(MAIN_USER_INFO),
+ selectedUserIndex = 0,
+ )
+ runCurrent()
+
+ // Communal becomes available.
+ fakeKeyguardRepository.setKeyguardShowing(true)
+ runCurrent()
+
+ // Widget created for work profile is removed.
+ assertThat(communalWidgets).containsExactly(widget2, widget3)
+ }
+ }
+
private suspend fun setCommunalAvailable(available: Boolean) =
with(kosmos) {
fakeKeyguardRepository.setIsEncryptedOrLockdown(false)
@@ -179,7 +223,16 @@
fakeSettings.putIntForUser(GLANCEABLE_HUB_ENABLED, settingsValue, MAIN_USER_INFO.id)
}
+ private fun createWidgetForUser(appWidgetId: Int, userId: Int): CommunalWidgetContentModel =
+ mock<CommunalWidgetContentModel> {
+ whenever(this.appWidgetId).thenReturn(appWidgetId)
+ val providerInfo = mock<AppWidgetProviderInfo>()
+ whenever(providerInfo.profile).thenReturn(UserHandle(userId))
+ whenever(this.providerInfo).thenReturn(providerInfo)
+ }
+
private companion object {
val MAIN_USER_INFO = UserInfo(0, "primary", UserInfo.FLAG_MAIN)
+ val USER_INFO_WORK = UserInfo(10, "work", UserInfo.FLAG_PROFILE)
}
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/domain/interactor/NotificationsSoundPolicyInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/domain/interactor/NotificationsSoundPolicyInteractorTest.kt
new file mode 100644
index 0000000..e188f5b
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/domain/interactor/NotificationsSoundPolicyInteractorTest.kt
@@ -0,0 +1,306 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.notification.domain.interactor
+
+import android.app.NotificationManager
+import android.media.AudioManager
+import android.provider.Settings.Global
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.settingslib.statusbar.notification.data.model.ZenMode
+import com.android.settingslib.statusbar.notification.data.repository.updateNotificationPolicy
+import com.android.settingslib.statusbar.notification.domain.interactor.NotificationsSoundPolicyInteractor
+import com.android.settingslib.volume.shared.model.AudioStream
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.testKosmos
+import com.google.common.truth.Expect
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.runCurrent
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@OptIn(ExperimentalCoroutinesApi::class)
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class NotificationsSoundPolicyInteractorTest : SysuiTestCase() {
+
+ @JvmField @Rule val expect = Expect.create()
+
+ private val kosmos = testKosmos()
+
+ private lateinit var underTest: NotificationsSoundPolicyInteractor
+
+ @Before
+ fun setup() {
+ with(kosmos) {
+ underTest = NotificationsSoundPolicyInteractor(notificationsSoundPolicyRepository)
+ }
+ }
+
+ @Test
+ fun onlyAlarmsCategory_areAlarmsAllowed_isTrue() {
+ with(kosmos) {
+ testScope.runTest {
+ notificationsSoundPolicyRepository.updateZenMode(ZenMode(Global.ZEN_MODE_OFF))
+ val expectedByCategory =
+ NotificationManager.Policy.ALL_PRIORITY_CATEGORIES.associateWith {
+ it == NotificationManager.Policy.PRIORITY_CATEGORY_ALARMS
+ }
+ expectedByCategory.forEach { entry ->
+ notificationsSoundPolicyRepository.updateNotificationPolicy(
+ priorityCategories = entry.key
+ )
+
+ val areAlarmsAllowed by collectLastValue(underTest.areAlarmsAllowed)
+ runCurrent()
+
+ expect.that(areAlarmsAllowed).isEqualTo(entry.value)
+ }
+ }
+ }
+ }
+
+ @Test
+ fun onlyMediaCategory_areAlarmsAllowed_isTrue() {
+ with(kosmos) {
+ testScope.runTest {
+ notificationsSoundPolicyRepository.updateZenMode(ZenMode(Global.ZEN_MODE_OFF))
+ val expectedByCategory =
+ NotificationManager.Policy.ALL_PRIORITY_CATEGORIES.associateWith {
+ it == NotificationManager.Policy.PRIORITY_CATEGORY_MEDIA
+ }
+ expectedByCategory.forEach { entry ->
+ notificationsSoundPolicyRepository.updateNotificationPolicy(
+ priorityCategories = entry.key
+ )
+
+ val isMediaAllowed by collectLastValue(underTest.isMediaAllowed)
+ runCurrent()
+
+ expect.that(isMediaAllowed).isEqualTo(entry.value)
+ }
+ }
+ }
+ }
+
+ @Test
+ fun atLeastOneCategoryAllowed_isRingerAllowed_isTrue() {
+ with(kosmos) {
+ testScope.runTest {
+ for (category in NotificationManager.Policy.ALL_PRIORITY_CATEGORIES) {
+ notificationsSoundPolicyRepository.updateNotificationPolicy(
+ priorityCategories = category,
+ state = NotificationManager.Policy.STATE_UNSET,
+ )
+
+ val isRingerAllowed by collectLastValue(underTest.isRingerAllowed)
+ runCurrent()
+
+ expect.that(isRingerAllowed).isTrue()
+ }
+ }
+ }
+ }
+
+ @Test
+ fun allCategoriesAllowed_isRingerAllowed_isTrue() {
+ with(kosmos) {
+ testScope.runTest {
+ notificationsSoundPolicyRepository.updateNotificationPolicy(
+ priorityCategories =
+ NotificationManager.Policy.ALL_PRIORITY_CATEGORIES.reduce { acc, value ->
+ acc or value
+ },
+ state = NotificationManager.Policy.STATE_PRIORITY_CHANNELS_BLOCKED,
+ )
+
+ val isRingerAllowed by collectLastValue(underTest.isRingerAllowed)
+ runCurrent()
+
+ assertThat(isRingerAllowed).isTrue()
+ }
+ }
+ }
+
+ @Test
+ fun noCategoriesAndBlocked_isRingerAllowed_isFalse() {
+ with(kosmos) {
+ testScope.runTest {
+ notificationsSoundPolicyRepository.updateNotificationPolicy(
+ priorityCategories = 0,
+ state = NotificationManager.Policy.STATE_PRIORITY_CHANNELS_BLOCKED,
+ )
+
+ val isRingerAllowed by collectLastValue(underTest.isRingerAllowed)
+ runCurrent()
+
+ assertThat(isRingerAllowed).isFalse()
+ }
+ }
+ }
+
+ @Test
+ fun zenModeNoInterruptions_allStreams_muted() {
+ with(kosmos) {
+ testScope.runTest {
+ notificationsSoundPolicyRepository.updateNotificationPolicy()
+ notificationsSoundPolicyRepository.updateZenMode(
+ ZenMode(Global.ZEN_MODE_NO_INTERRUPTIONS)
+ )
+
+ for (stream in AudioStream.supportedStreamTypes) {
+ val isZenMuted by collectLastValue(underTest.isZenMuted(AudioStream(stream)))
+ runCurrent()
+
+ expect.that(isZenMuted).isTrue()
+ }
+ }
+ }
+ }
+
+ @Test
+ fun zenModeOff_allStreams_notMuted() {
+ with(kosmos) {
+ testScope.runTest {
+ notificationsSoundPolicyRepository.updateNotificationPolicy()
+ notificationsSoundPolicyRepository.updateZenMode(ZenMode(Global.ZEN_MODE_OFF))
+
+ for (stream in AudioStream.supportedStreamTypes) {
+ val isZenMuted by collectLastValue(underTest.isZenMuted(AudioStream(stream)))
+ runCurrent()
+
+ expect.that(isZenMuted).isFalse()
+ }
+ }
+ }
+ }
+
+ @Test
+ fun zenModeAlarms_ringAndNotifications_muted() {
+ with(kosmos) {
+ val expectedToBeMuted =
+ setOf(AudioManager.STREAM_RING, AudioManager.STREAM_NOTIFICATION)
+ testScope.runTest {
+ notificationsSoundPolicyRepository.updateNotificationPolicy()
+ notificationsSoundPolicyRepository.updateZenMode(ZenMode(Global.ZEN_MODE_ALARMS))
+
+ for (stream in AudioStream.supportedStreamTypes) {
+ val isZenMuted by collectLastValue(underTest.isZenMuted(AudioStream(stream)))
+ runCurrent()
+
+ expect.that(isZenMuted).isEqualTo(stream in expectedToBeMuted)
+ }
+ }
+ }
+ }
+
+ @Test
+ fun alarms_allowed_notMuted() {
+ with(kosmos) {
+ testScope.runTest {
+ notificationsSoundPolicyRepository.updateZenMode(
+ ZenMode(Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS)
+ )
+ notificationsSoundPolicyRepository.updateNotificationPolicy(
+ priorityCategories = NotificationManager.Policy.PRIORITY_CATEGORY_ALARMS
+ )
+
+ val isZenMuted by
+ collectLastValue(underTest.isZenMuted(AudioStream(AudioManager.STREAM_ALARM)))
+ runCurrent()
+
+ expect.that(isZenMuted).isFalse()
+ }
+ }
+ }
+
+ @Test
+ fun media_allowed_notMuted() {
+ with(kosmos) {
+ testScope.runTest {
+ notificationsSoundPolicyRepository.updateZenMode(
+ ZenMode(Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS)
+ )
+ notificationsSoundPolicyRepository.updateNotificationPolicy(
+ priorityCategories = NotificationManager.Policy.PRIORITY_CATEGORY_MEDIA
+ )
+
+ val isZenMuted by
+ collectLastValue(underTest.isZenMuted(AudioStream(AudioManager.STREAM_MUSIC)))
+ runCurrent()
+
+ expect.that(isZenMuted).isFalse()
+ }
+ }
+ }
+
+ @Test
+ fun ringer_allowed_notificationsNotMuted() {
+ with(kosmos) {
+ testScope.runTest {
+ notificationsSoundPolicyRepository.updateZenMode(
+ ZenMode(Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS)
+ )
+ notificationsSoundPolicyRepository.updateNotificationPolicy(
+ priorityCategories =
+ NotificationManager.Policy.ALL_PRIORITY_CATEGORIES.reduce { acc, value ->
+ acc or value
+ },
+ state = NotificationManager.Policy.STATE_PRIORITY_CHANNELS_BLOCKED,
+ )
+
+ val isZenMuted by
+ collectLastValue(
+ underTest.isZenMuted(AudioStream(AudioManager.STREAM_NOTIFICATION))
+ )
+ runCurrent()
+
+ expect.that(isZenMuted).isFalse()
+ }
+ }
+ }
+
+ @Test
+ fun ringer_allowed_ringNotMuted() {
+ with(kosmos) {
+ testScope.runTest {
+ notificationsSoundPolicyRepository.updateZenMode(
+ ZenMode(Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS)
+ )
+ notificationsSoundPolicyRepository.updateNotificationPolicy(
+ priorityCategories =
+ NotificationManager.Policy.ALL_PRIORITY_CATEGORIES.reduce { acc, value ->
+ acc or value
+ },
+ state = NotificationManager.Policy.STATE_PRIORITY_CHANNELS_BLOCKED,
+ )
+
+ val isZenMuted by
+ collectLastValue(underTest.isZenMuted(AudioStream(AudioManager.STREAM_RING)))
+ runCurrent()
+
+ expect.that(isZenMuted).isFalse()
+ }
+ }
+ }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/domain/interactor/AudioVolumeInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/domain/interactor/AudioVolumeInteractorTest.kt
new file mode 100644
index 0000000..a2f3ccb
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/domain/interactor/AudioVolumeInteractorTest.kt
@@ -0,0 +1,184 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.volume.domain.interactor
+
+import android.media.AudioManager
+import android.provider.Settings
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.settingslib.statusbar.notification.data.model.ZenMode
+import com.android.settingslib.statusbar.notification.data.repository.updateNotificationPolicy
+import com.android.settingslib.volume.domain.interactor.AudioVolumeInteractor
+import com.android.settingslib.volume.shared.model.AudioStream
+import com.android.settingslib.volume.shared.model.RingerMode
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.statusbar.notification.domain.interactor.notificationsSoundPolicyInteractor
+import com.android.systemui.statusbar.notification.domain.interactor.notificationsSoundPolicyRepository
+import com.android.systemui.testKosmos
+import com.android.systemui.volume.audioRepository
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.runCurrent
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@OptIn(ExperimentalCoroutinesApi::class)
+@RunWith(AndroidJUnit4::class)
+@SmallTest
+class AudioVolumeInteractorTest : SysuiTestCase() {
+
+ private val kosmos = testKosmos()
+
+ private lateinit var underTest: AudioVolumeInteractor
+
+ @Before
+ fun setup() {
+ with(kosmos) {
+ underTest = AudioVolumeInteractor(audioRepository, notificationsSoundPolicyInteractor)
+
+ audioRepository.setRingerMode(RingerMode(AudioManager.RINGER_MODE_NORMAL))
+
+ notificationsSoundPolicyRepository.updateNotificationPolicy()
+ notificationsSoundPolicyRepository.updateZenMode(ZenMode(Settings.Global.ZEN_MODE_OFF))
+ }
+ }
+
+ @Test
+ fun setMuted_mutesStream() {
+ with(kosmos) {
+ testScope.runTest {
+ val model by collectLastValue(underTest.getAudioStream(audioStream))
+
+ underTest.setMuted(audioStream, false)
+ runCurrent()
+ assertThat(model!!.isMuted).isFalse()
+
+ underTest.setMuted(audioStream, true)
+ runCurrent()
+ assertThat(model!!.isMuted).isTrue()
+ }
+ }
+ }
+
+ @Test
+ fun setVolume_changesVolume() {
+ with(kosmos) {
+ testScope.runTest {
+ val model by collectLastValue(underTest.getAudioStream(audioStream))
+
+ underTest.setVolume(audioStream, 10)
+ runCurrent()
+ assertThat(model!!.volume).isEqualTo(10)
+
+ underTest.setVolume(audioStream, 20)
+ runCurrent()
+ assertThat(model!!.volume).isEqualTo(20)
+ }
+ }
+ }
+
+ @Test
+ fun ringMuted_notificationVolume_cantChange() {
+ with(kosmos) {
+ testScope.runTest {
+ val canChangeVolume by
+ collectLastValue(
+ underTest.canChangeVolume(AudioStream(AudioManager.STREAM_NOTIFICATION))
+ )
+
+ underTest.setMuted(AudioStream(AudioManager.STREAM_RING), true)
+ runCurrent()
+
+ assertThat(canChangeVolume).isFalse()
+ }
+ }
+ }
+
+ @Test
+ fun streamIsMuted_getStream_volumeZero() {
+ with(kosmos) {
+ testScope.runTest {
+ val model by collectLastValue(underTest.getAudioStream(audioStream))
+
+ underTest.setMuted(audioStream, true)
+ runCurrent()
+
+ assertThat(model!!.volume).isEqualTo(0)
+ }
+ }
+ }
+
+ @Test
+ fun streamIsZenMuted_getStream_lastAudibleVolume() {
+ with(kosmos) {
+ testScope.runTest {
+ audioRepository.setLastAudibleVolume(audioStream, 30)
+ notificationsSoundPolicyRepository.updateZenMode(
+ ZenMode(Settings.Global.ZEN_MODE_NO_INTERRUPTIONS)
+ )
+
+ val model by collectLastValue(underTest.getAudioStream(audioStream))
+ runCurrent()
+
+ assertThat(model!!.volume).isEqualTo(30)
+ }
+ }
+ }
+
+ @Test
+ fun ringerModeVibrateAndMuted_getNotificationStream_volumeIsZero() {
+ with(kosmos) {
+ testScope.runTest {
+ audioRepository.setRingerMode(RingerMode(AudioManager.RINGER_MODE_VIBRATE))
+ underTest.setMuted(AudioStream(AudioManager.STREAM_NOTIFICATION), true)
+
+ val model by
+ collectLastValue(
+ underTest.getAudioStream(AudioStream(AudioManager.STREAM_NOTIFICATION))
+ )
+ runCurrent()
+
+ assertThat(model!!.volume).isEqualTo(0)
+ }
+ }
+ }
+
+ @Test
+ fun ringerModeVibrate_getRingerStream_volumeIsZero() {
+ with(kosmos) {
+ testScope.runTest {
+ audioRepository.setRingerMode(RingerMode(AudioManager.RINGER_MODE_VIBRATE))
+
+ val model by
+ collectLastValue(
+ underTest.getAudioStream(AudioStream(AudioManager.STREAM_RING))
+ )
+ runCurrent()
+
+ assertThat(model!!.volume).isEqualTo(0)
+ }
+ }
+ }
+
+ private companion object {
+ val audioStream = AudioStream(AudioManager.STREAM_SYSTEM)
+ }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/component/volume/domain/interactor/VolumeSliderInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/component/volume/domain/interactor/VolumeSliderInteractorTest.kt
new file mode 100644
index 0000000..a1e4fca
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/component/volume/domain/interactor/VolumeSliderInteractorTest.kt
@@ -0,0 +1,60 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.volume.panel.component.volume.domain.interactor
+
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.google.common.truth.Truth.assertThat
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(AndroidJUnit4::class)
+@SmallTest
+class VolumeSliderInteractorTest : SysuiTestCase() {
+
+ private val underTest = VolumeSliderInteractor()
+
+ @Test
+ fun translateValueToVolume() {
+ assertThat(underTest.translateValueToVolume(30f, volumeRange)).isEqualTo(3)
+ }
+
+ @Test
+ fun processVolumeToValue_muted_zero() {
+ assertThat(underTest.processVolumeToValue(3, volumeRange, null, true)).isEqualTo(0)
+ }
+
+ @Test
+ fun processVolumeToValue_currentValue_currentValue() {
+ assertThat(underTest.processVolumeToValue(3, volumeRange, 30f, false)).isEqualTo(30f)
+ }
+
+ @Test
+ fun processVolumeToValue_currentValueDiffersVolume_returnsTranslatedVolume() {
+ assertThat(underTest.processVolumeToValue(1, volumeRange, 60f, false)).isEqualTo(10f)
+ }
+
+ @Test
+ fun processVolumeToValue_currentValueDiffersNotEnoughVolume_returnsTranslatedVolume() {
+ assertThat(underTest.processVolumeToValue(1, volumeRange, 12f, false)).isEqualTo(12f)
+ }
+
+ private companion object {
+ val volumeRange = 0..10
+ }
+}
diff --git a/packages/SystemUI/res/drawable/ic_call.xml b/packages/SystemUI/res/drawable/ic_call.xml
new file mode 100644
index 0000000..859506a
--- /dev/null
+++ b/packages/SystemUI/res/drawable/ic_call.xml
@@ -0,0 +1,26 @@
+<!--
+ ~ Copyright (C) 2024 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="960"
+ android:viewportHeight="960"
+ android:tint="?attr/colorControlNormal">
+ <path
+ android:fillColor="@android:color/white"
+ android:pathData="M798,840Q673,840 551,785.5Q429,731 329,631Q229,531 174.5,409Q120,287 120,162Q120,144 132,132Q144,120 162,120L324,120Q338,120 349,129.5Q360,139 362,152L388,292Q390,308 387,319Q384,330 376,338L279,436Q299,473 326.5,507.5Q354,542 387,574Q418,605 452,631.5Q486,658 524,680L618,586Q627,577 641.5,572.5Q656,568 670,570L808,598Q822,602 831,612.5Q840,623 840,636L840,798Q840,816 828,828Q816,840 798,840ZM241,360L307,294Q307,294 307,294Q307,294 307,294L290,200Q290,200 290,200Q290,200 290,200L201,200Q201,200 201,200Q201,200 201,200Q206,241 215,281Q224,321 241,360ZM599,718Q638,735 678.5,745Q719,755 760,758Q760,758 760,758Q760,758 760,758L760,670Q760,670 760,670Q760,670 760,670L666,651Q666,651 666,651Q666,651 666,651L599,718ZM241,360Q241,360 241,360Q241,360 241,360Q241,360 241,360Q241,360 241,360L241,360Q241,360 241,360Q241,360 241,360L241,360Q241,360 241,360Q241,360 241,360L241,360ZM599,718L599,718Q599,718 599,718Q599,718 599,718L599,718Q599,718 599,718Q599,718 599,718L599,718Q599,718 599,718Q599,718 599,718Q599,718 599,718Q599,718 599,718Z"/>
+</vector>
diff --git a/packages/SystemUI/res/drawable/ic_filled_arrow_down.xml b/packages/SystemUI/res/drawable/ic_filled_arrow_down.xml
new file mode 100644
index 0000000..c85965f
--- /dev/null
+++ b/packages/SystemUI/res/drawable/ic_filled_arrow_down.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="utf-8"?><!--
+ ~ Copyright (C) 2024 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:viewportWidth="24"
+ android:viewportHeight="24"
+ android:width="24dp"
+ android:height="24dp">
+ <path
+ android:pathData="M7 10l5 5 5 -5z"
+ android:fillColor="#FF000000"/>
+</vector>
diff --git a/packages/SystemUI/res/drawable/ic_filled_arrow_up.xml b/packages/SystemUI/res/drawable/ic_filled_arrow_up.xml
new file mode 100644
index 0000000..8ee7e13
--- /dev/null
+++ b/packages/SystemUI/res/drawable/ic_filled_arrow_up.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="utf-8"?><!--
+ ~ Copyright (C) 2024 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:viewportWidth="24"
+ android:viewportHeight="24"
+ android:width="24dp"
+ android:height="24dp">
+ <path
+ android:pathData="M7 14l5-5 5 5z"
+ android:fillColor="#FF000000"/>
+</vector>
diff --git a/packages/SystemUI/res/drawable/ic_music_note_off.xml b/packages/SystemUI/res/drawable/ic_music_note_off.xml
new file mode 100644
index 0000000..d583576
--- /dev/null
+++ b/packages/SystemUI/res/drawable/ic_music_note_off.xml
@@ -0,0 +1,25 @@
+<!--
+ ~ Copyright (C) 2024 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportHeight="960"
+ android:viewportWidth="960">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M792,904L56,168L112,112L848,848L792,904ZM560,446L480,366L480,120L720,120L720,280L560,280L560,446ZM400,840Q334,840 287,793Q240,746 240,680Q240,614 287,567Q334,520 400,520Q423,520 442.5,525.5Q462,531 480,542L480,480L560,560L560,680Q560,746 513,793Q466,840 400,840Z" />
+</vector>
diff --git a/packages/SystemUI/res/drawable/ic_volume_off.xml b/packages/SystemUI/res/drawable/ic_volume_off.xml
new file mode 100644
index 0000000..209f684
--- /dev/null
+++ b/packages/SystemUI/res/drawable/ic_volume_off.xml
@@ -0,0 +1,27 @@
+<!--
+ ~ Copyright (C) 2024 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="960"
+ android:viewportHeight="960"
+ android:tint="?attr/colorControlNormal"
+ android:autoMirrored="true">
+ <path
+ android:fillColor="@android:color/white"
+ android:pathData="M792,904L671,783Q646,799 618,810.5Q590,822 560,829L560,747Q574,742 587.5,737Q601,732 613,725L480,592L480,800L280,600L120,600L120,360L248,360L56,168L112,112L848,848L792,904ZM784,672L726,614Q743,583 751.5,549Q760,515 760,479Q760,385 705,311Q650,237 560,211L560,129Q684,157 762,254.5Q840,352 840,479Q840,532 825.5,581Q811,630 784,672ZM650,538L560,448L560,318Q607,340 633.5,384Q660,428 660,480Q660,495 657.5,509.5Q655,524 650,538ZM480,368L376,264L480,160L480,368ZM400,606L400,512L328,440L328,440L200,440L200,520L314,520L400,606ZM364,476L364,476L364,476L364,476L364,476L364,476L364,476L364,476Z"/>
+</vector>
\ No newline at end of file
diff --git a/packages/SystemUI/res/layout/screen_share_dialog.xml b/packages/SystemUI/res/layout/screen_share_dialog.xml
index 3796415..2616e8a 100644
--- a/packages/SystemUI/res/layout/screen_share_dialog.xml
+++ b/packages/SystemUI/res/layout/screen_share_dialog.xml
@@ -67,12 +67,12 @@
android:gravity="start"/>
<!-- Buttons -->
- <LinearLayout
+ <com.android.internal.widget.ButtonBarLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:layout_marginTop="@dimen/screenrecord_buttons_margin_top">
- <TextView
+ <Button
android:id="@android:id/button2"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
@@ -83,13 +83,13 @@
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1"/>
- <TextView
+ <Button
android:id="@android:id/button1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_weight="0"
android:text="@string/screenrecord_continue"
style="@style/Widget.Dialog.Button" />
- </LinearLayout>
+ </com.android.internal.widget.ButtonBarLayout>
</LinearLayout>
</ScrollView>
\ No newline at end of file
diff --git a/packages/SystemUI/res/values-sw600dp/config.xml b/packages/SystemUI/res/values-sw600dp/config.xml
index f1017d8..b438315 100644
--- a/packages/SystemUI/res/values-sw600dp/config.xml
+++ b/packages/SystemUI/res/values-sw600dp/config.xml
@@ -53,4 +53,7 @@
<item>bottom_start:home</item>
<item>bottom_end:create_note</item>
</string-array>
+
+ <!-- Whether volume panel should use the large screen layout or not -->
+ <bool name="volume_panel_is_large_screen">true</bool>
</resources>
diff --git a/packages/SystemUI/res/values/config.xml b/packages/SystemUI/res/values/config.xml
index 65c69f7..beaa708 100644
--- a/packages/SystemUI/res/values/config.xml
+++ b/packages/SystemUI/res/values/config.xml
@@ -1010,4 +1010,7 @@
<!-- Whether to use a machine learning model for back gesture falsing. -->
<bool name="config_useBackGestureML">true</bool>
+
+ <!-- Whether volume panel should use the large screen layout or not -->
+ <bool name="volume_panel_is_large_screen">false</bool>
</resources>
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index 495f20f2..4263d94 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -1513,6 +1513,11 @@
<string name="volume_ringer_status_vibrate">Vibrate</string>
<string name="volume_ringer_status_silent">Mute</string>
+ <!-- Media device casting volume slider label [CHAR_LIMIT=20] -->
+ <string name="media_device_cast">Cast</string>
+ <!-- A message shown when the notification volume changing is disabled because of the muted ring stream [CHAR_LIMIT=40]-->
+ <string name="stream_notification_unavailable">Unavailable because ring is muted</string>
+
<!-- Shown in the header of quick settings to indicate to the user that their phone ringer is on vibrate. [CHAR_LIMIT=NONE] -->
<!-- Shown in the header of quick settings to indicate to the user that their phone ringer is on silent (muted). [CHAR_LIMIT=NONE] -->
@@ -3129,6 +3134,8 @@
<!-- [CHAR LIMIT=25] Long label used by Note Task Shortcut -->
<string name="note_task_shortcut_long_label">Note-taking, <xliff:g id="note_taking_app" example="Note-taking App">%1$s</xliff:g></string>
+ <!-- [CHAR LIMIT=NONE] Output switch chip text during broadcasting -->
+ <string name="audio_sharing_description">Sharing audio</string>
<!-- [CHAR LIMIT=NONE] Le audio broadcast dialog, media app is broadcasting -->
<string name="broadcasting_description_is_broadcasting">Broadcasting</string>
<!-- [CHAR LIMIT=NONE] Le audio broadcast dialog, title -->
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardPinBasedInputViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardPinBasedInputViewController.java
index 476497d..10d1891 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardPinBasedInputViewController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardPinBasedInputViewController.java
@@ -19,6 +19,7 @@
import static com.android.systemui.Flags.pinInputFieldStyledFocusState;
import static com.android.systemui.util.kotlin.JavaAdapterKt.collectFlow;
+import android.graphics.drawable.Drawable;
import android.graphics.drawable.GradientDrawable;
import android.graphics.drawable.StateListDrawable;
import android.util.TypedValue;
@@ -150,18 +151,22 @@
}
private void setKeyboardBasedFocusOutline(boolean isAnyKeyboardConnected) {
- StateListDrawable background = (StateListDrawable) mPasswordEntry.getBackground();
- GradientDrawable stateDrawable = (GradientDrawable) background.getStateDrawable(0);
+ Drawable background = mPasswordEntry.getBackground();
+ if (!(background instanceof StateListDrawable)) return;
+ Drawable stateDrawable = ((StateListDrawable) background).getStateDrawable(0);
+ if (!(stateDrawable instanceof GradientDrawable gradientDrawable)) return;
+
int color = getResources().getColor(R.color.bouncer_password_focus_color);
if (!isAnyKeyboardConnected) {
- stateDrawable.setStroke(0, color);
+ gradientDrawable.setStroke(0, color);
} else {
int strokeWidthInDP = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 3,
getResources().getDisplayMetrics());
- stateDrawable.setStroke(strokeWidthInDP, color);
+ gradientDrawable.setStroke(strokeWidthInDP, color);
}
}
+
@Override
protected void onViewDetached() {
super.onViewDetached();
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuView.java b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuView.java
index 27f9106fd..6299739 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuView.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuView.java
@@ -464,9 +464,6 @@
Bundle fragmentArgs = new Bundle();
fragmentArgs.putStringArray("targets", targets.toArray(new String[0]));
args.putBundle(":settings:show_fragment_args", fragmentArgs);
- // TODO: b/318748373 - The fragment should set its own title using the targets
- args.putString(
- ":settings:show_fragment_title", "Accessibility Shortcut");
intent.replaceExtras(args);
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TOP);
return intent;
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java
index 716209d..2c3ebe9 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java
@@ -80,6 +80,7 @@
import com.android.systemui.bouncer.domain.interactor.AlternateBouncerInteractor;
import com.android.systemui.bouncer.domain.interactor.PrimaryBouncerInteractor;
import com.android.systemui.dagger.SysUISingleton;
+import com.android.systemui.dagger.qualifiers.Application;
import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.deviceentry.domain.interactor.DeviceEntryFaceAuthInteractor;
import com.android.systemui.deviceentry.shared.DeviceEntryUdfpsRefactor;
@@ -90,6 +91,7 @@
import com.android.systemui.log.SessionTracker;
import com.android.systemui.plugins.FalsingManager;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
+import com.android.systemui.power.domain.interactor.PowerInteractor;
import com.android.systemui.shade.domain.interactor.ShadeInteractor;
import com.android.systemui.shared.system.SysUiStatsLog;
import com.android.systemui.statusbar.LockscreenShadeTransitionController;
@@ -116,6 +118,7 @@
import javax.inject.Inject;
+import kotlinx.coroutines.CoroutineScope;
import kotlinx.coroutines.ExperimentalCoroutinesApi;
/**
@@ -173,6 +176,8 @@
mDefaultUdfpsTouchOverlayViewModel;
@NonNull private final AlternateBouncerInteractor mAlternateBouncerInteractor;
@NonNull private final UdfpsOverlayInteractor mUdfpsOverlayInteractor;
+ @NonNull private final PowerInteractor mPowerInteractor;
+ @NonNull private final CoroutineScope mScope;
@NonNull private final InputManager mInputManager;
@NonNull private final UdfpsKeyguardAccessibilityDelegate mUdfpsKeyguardAccessibilityDelegate;
@NonNull private final SelectedUserInteractor mSelectedUserInteractor;
@@ -296,7 +301,9 @@
mDeviceEntryUdfpsTouchOverlayViewModel,
mDefaultUdfpsTouchOverlayViewModel,
mShadeInteractor,
- mUdfpsOverlayInteractor
+ mUdfpsOverlayInteractor,
+ mPowerInteractor,
+ mScope
)));
}
@@ -678,7 +685,9 @@
@NonNull KeyguardTransitionInteractor keyguardTransitionInteractor,
Lazy<DeviceEntryUdfpsTouchOverlayViewModel> deviceEntryUdfpsTouchOverlayViewModel,
Lazy<DefaultUdfpsTouchOverlayViewModel> defaultUdfpsTouchOverlayViewModel,
- @NonNull UdfpsOverlayInteractor udfpsOverlayInteractor) {
+ @NonNull UdfpsOverlayInteractor udfpsOverlayInteractor,
+ @NonNull PowerInteractor powerInteractor,
+ @Application CoroutineScope scope) {
mContext = context;
mExecution = execution;
mVibrator = vibrator;
@@ -720,6 +729,8 @@
mShadeInteractor = shadeInteractor;
mAlternateBouncerInteractor = alternateBouncerInteractor;
mUdfpsOverlayInteractor = udfpsOverlayInteractor;
+ mPowerInteractor = powerInteractor;
+ mScope = scope;
mInputManager = inputManager;
mUdfpsKeyguardAccessibilityDelegate = udfpsKeyguardAccessibilityDelegate;
mSelectedUserInteractor = selectedUserInteractor;
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsControllerOverlay.kt b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsControllerOverlay.kt
index a209eae..921e395 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsControllerOverlay.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsControllerOverlay.kt
@@ -44,6 +44,7 @@
import androidx.annotation.LayoutRes
import androidx.annotation.VisibleForTesting
import com.android.keyguard.KeyguardUpdateMonitor
+import com.android.systemui.Flags.udfpsViewPerformance
import com.android.systemui.animation.ActivityTransitionAnimator
import com.android.systemui.biometrics.domain.interactor.UdfpsOverlayInteractor
import com.android.systemui.biometrics.shared.model.UdfpsOverlayParams
@@ -53,10 +54,13 @@
import com.android.systemui.biometrics.ui.viewmodel.DeviceEntryUdfpsTouchOverlayViewModel
import com.android.systemui.bouncer.domain.interactor.AlternateBouncerInteractor
import com.android.systemui.bouncer.domain.interactor.PrimaryBouncerInteractor
+import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.deviceentry.shared.DeviceEntryUdfpsRefactor
import com.android.systemui.dump.DumpManager
import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
import com.android.systemui.plugins.statusbar.StatusBarStateController
+import com.android.systemui.power.domain.interactor.PowerInteractor
+import com.android.systemui.power.shared.model.WakefulnessState
import com.android.systemui.res.R
import com.android.systemui.shade.domain.interactor.ShadeInteractor
import com.android.systemui.statusbar.LockscreenShadeTransitionController
@@ -67,7 +71,13 @@
import com.android.systemui.statusbar.policy.KeyguardStateController
import com.android.systemui.user.domain.interactor.SelectedUserInteractor
import dagger.Lazy
+import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.Job
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.filter
+import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.launch
private const val TAG = "UdfpsControllerOverlay"
@@ -82,36 +92,45 @@
@ExperimentalCoroutinesApi
@UiThread
class UdfpsControllerOverlay @JvmOverloads constructor(
- private val context: Context,
- private val inflater: LayoutInflater,
- private val windowManager: WindowManager,
- private val accessibilityManager: AccessibilityManager,
- private val statusBarStateController: StatusBarStateController,
- private val statusBarKeyguardViewManager: StatusBarKeyguardViewManager,
- private val keyguardUpdateMonitor: KeyguardUpdateMonitor,
- private val dialogManager: SystemUIDialogManager,
- private val dumpManager: DumpManager,
- private val transitionController: LockscreenShadeTransitionController,
- private val configurationController: ConfigurationController,
- private val keyguardStateController: KeyguardStateController,
- private val unlockedScreenOffAnimationController: UnlockedScreenOffAnimationController,
- private var udfpsDisplayModeProvider: UdfpsDisplayModeProvider,
- val requestId: Long,
- @RequestReason val requestReason: Int,
- private val controllerCallback: IUdfpsOverlayControllerCallback,
- private val onTouch: (View, MotionEvent, Boolean) -> Boolean,
- private val activityTransitionAnimator: ActivityTransitionAnimator,
- private val primaryBouncerInteractor: PrimaryBouncerInteractor,
- private val alternateBouncerInteractor: AlternateBouncerInteractor,
- private val isDebuggable: Boolean = Build.IS_DEBUGGABLE,
- private val udfpsKeyguardAccessibilityDelegate: UdfpsKeyguardAccessibilityDelegate,
- private val transitionInteractor: KeyguardTransitionInteractor,
- private val selectedUserInteractor: SelectedUserInteractor,
- private val deviceEntryUdfpsTouchOverlayViewModel: Lazy<DeviceEntryUdfpsTouchOverlayViewModel>,
- private val defaultUdfpsTouchOverlayViewModel: Lazy<DefaultUdfpsTouchOverlayViewModel>,
- private val shadeInteractor: ShadeInteractor,
- private val udfpsOverlayInteractor: UdfpsOverlayInteractor,
+ private val context: Context,
+ private val inflater: LayoutInflater,
+ private val windowManager: WindowManager,
+ private val accessibilityManager: AccessibilityManager,
+ private val statusBarStateController: StatusBarStateController,
+ private val statusBarKeyguardViewManager: StatusBarKeyguardViewManager,
+ private val keyguardUpdateMonitor: KeyguardUpdateMonitor,
+ private val dialogManager: SystemUIDialogManager,
+ private val dumpManager: DumpManager,
+ private val transitionController: LockscreenShadeTransitionController,
+ private val configurationController: ConfigurationController,
+ private val keyguardStateController: KeyguardStateController,
+ private val unlockedScreenOffAnimationController: UnlockedScreenOffAnimationController,
+ private var udfpsDisplayModeProvider: UdfpsDisplayModeProvider,
+ val requestId: Long,
+ @RequestReason val requestReason: Int,
+ private val controllerCallback: IUdfpsOverlayControllerCallback,
+ private val onTouch: (View, MotionEvent, Boolean) -> Boolean,
+ private val activityTransitionAnimator: ActivityTransitionAnimator,
+ private val primaryBouncerInteractor: PrimaryBouncerInteractor,
+ private val alternateBouncerInteractor: AlternateBouncerInteractor,
+ private val isDebuggable: Boolean = Build.IS_DEBUGGABLE,
+ private val udfpsKeyguardAccessibilityDelegate: UdfpsKeyguardAccessibilityDelegate,
+ private val transitionInteractor: KeyguardTransitionInteractor,
+ private val selectedUserInteractor: SelectedUserInteractor,
+ private val deviceEntryUdfpsTouchOverlayViewModel:
+ Lazy<DeviceEntryUdfpsTouchOverlayViewModel>,
+ private val defaultUdfpsTouchOverlayViewModel: Lazy<DefaultUdfpsTouchOverlayViewModel>,
+ private val shadeInteractor: ShadeInteractor,
+ private val udfpsOverlayInteractor: UdfpsOverlayInteractor,
+ private val powerInteractor: PowerInteractor,
+ @Application private val scope: CoroutineScope,
) {
+ private val isFinishedGoingToSleep: Flow<Unit> =
+ powerInteractor.detailedWakefulness
+ .filter { it.internalWakefulnessState == WakefulnessState.ASLEEP }
+ .map { } // map to Unit
+ private var listenForAsleepJob: Job? = null
+ private var addViewRunnable: Runnable? = null
private var overlayViewLegacy: UdfpsView? = null
private set
private var overlayTouchView: UdfpsTouchOverlay? = null
@@ -192,7 +211,8 @@
if (requestReason.isImportantForAccessibility()) {
importantForAccessibility = View.IMPORTANT_FOR_ACCESSIBILITY_NO
}
- windowManager.addView(this, coreLayoutParams.updateDimensions(null))
+
+ addViewNowOrLater(this, null)
when (requestReason) {
REASON_AUTH_KEYGUARD ->
UdfpsTouchOverlayBinder.bind(
@@ -225,7 +245,7 @@
importantForAccessibility = View.IMPORTANT_FOR_ACCESSIBILITY_NO
}
- windowManager.addView(this, coreLayoutParams.updateDimensions(animation))
+ addViewNowOrLater(this, animation)
sensorRect = sensorBounds
}
}
@@ -257,6 +277,41 @@
return false
}
+ private fun addViewNowOrLater(view: View, animation: UdfpsAnimationViewController<*>?) {
+ if (udfpsViewPerformance()) {
+ addViewRunnable = kotlinx.coroutines.Runnable {
+ windowManager.addView(
+ view,
+ coreLayoutParams.updateDimensions(animation)
+ )
+ }
+ if (powerInteractor.detailedWakefulness.value.internalWakefulnessState
+ != WakefulnessState.STARTING_TO_SLEEP) {
+ addViewIfPending()
+ } else {
+ listenForAsleepJob?.cancel()
+ listenForAsleepJob = scope.launch {
+ isFinishedGoingToSleep.collect {
+ addViewIfPending()
+ }
+ }
+ }
+ } else {
+ windowManager.addView(
+ view,
+ coreLayoutParams.updateDimensions(animation)
+ )
+ }
+ }
+
+ private fun addViewIfPending() {
+ addViewRunnable?.let {
+ listenForAsleepJob?.cancel()
+ it.run()
+ }
+ addViewRunnable = null
+ }
+
fun inflateUdfpsAnimation(
view: UdfpsView,
controller: UdfpsController
@@ -368,6 +423,7 @@
overlayViewLegacy = null
overlayTouchView = null
overlayTouchListener = null
+ listenForAsleepJob?.cancel()
return wasShowing
}
@@ -412,7 +468,8 @@
if (rot == Surface.ROTATION_90 || rot == Surface.ROTATION_270) {
if (!shouldRotate(animation)) {
Log.v(
- TAG, "Skip rotating UDFPS bounds " + Surface.rotationToString(rot) +
+ TAG,
+ "Skip rotating UDFPS bounds " + Surface.rotationToString(rot) +
" animation=$animation" +
" isGoingToSleep=${keyguardUpdateMonitor.isGoingToSleep}" +
" isOccluded=${keyguardStateController.isOccluded}"
diff --git a/packages/SystemUI/src/com/android/systemui/bluetooth/BroadcastDialogDelegate.java b/packages/SystemUI/src/com/android/systemui/bluetooth/BroadcastDialogDelegate.java
index 00bbb20..6af0fa0 100644
--- a/packages/SystemUI/src/com/android/systemui/bluetooth/BroadcastDialogDelegate.java
+++ b/packages/SystemUI/src/com/android/systemui/bluetooth/BroadcastDialogDelegate.java
@@ -40,6 +40,7 @@
import com.android.settingslib.bluetooth.LocalBluetoothManager;
import com.android.settingslib.media.MediaOutputConstants;
import com.android.systemui.broadcast.BroadcastSender;
+import com.android.systemui.dagger.qualifiers.Background;
import com.android.systemui.media.controls.util.MediaDataUtils;
import com.android.systemui.media.dialog.MediaOutputDialogFactory;
import com.android.systemui.res.R;
@@ -74,7 +75,7 @@
private final SystemUIDialog.Factory mSystemUIDialogFactory;
private final String mCurrentBroadcastApp;
private final String mOutputPackageName;
- private final Executor mExecutor;
+ private final Executor mBgExecutor;
private boolean mShouldLaunchLeBroadcastDialog;
private Button mSwitchBroadcast;
@@ -159,7 +160,7 @@
MediaOutputDialogFactory mediaOutputDialogFactory,
@Nullable LocalBluetoothManager localBluetoothManager,
UiEventLogger uiEventLogger,
- Executor executor,
+ @Background Executor bgExecutor,
BroadcastSender broadcastSender,
SystemUIDialog.Factory systemUIDialogFactory,
@Assisted(CURRENT_BROADCAST_APP) String currentBroadcastApp,
@@ -171,7 +172,7 @@
mCurrentBroadcastApp = currentBroadcastApp;
mOutputPackageName = outputPkgName;
mUiEventLogger = uiEventLogger;
- mExecutor = executor;
+ mBgExecutor = bgExecutor;
mBroadcastSender = broadcastSender;
if (DEBUG) {
@@ -187,7 +188,7 @@
@Override
public void onStart(SystemUIDialog dialog) {
mDialogs.add(dialog);
- registerBroadcastCallBack(mExecutor, mBroadcastCallback);
+ registerBroadcastCallBack(mBgExecutor, mBroadcastCallback);
}
@Override
diff --git a/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardOverlayView.java b/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardOverlayView.java
index 2af49cf..b269967 100644
--- a/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardOverlayView.java
+++ b/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardOverlayView.java
@@ -143,7 +143,7 @@
mTextPreview.getViewTreeObserver().addOnPreDrawListener(() -> {
int availableHeight = mTextPreview.getHeight()
- (mTextPreview.getPaddingTop() + mTextPreview.getPaddingBottom());
- mTextPreview.setMaxLines(availableHeight / mTextPreview.getLineHeight());
+ mTextPreview.setMaxLines(Math.max(availableHeight / mTextPreview.getLineHeight(), 1));
return true;
});
super.onFinishInflate();
diff --git a/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalInteractor.kt b/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalInteractor.kt
index d0044a4..5d52541 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalInteractor.kt
@@ -29,6 +29,7 @@
import com.android.systemui.communal.shared.model.CommunalContentSize.HALF
import com.android.systemui.communal.shared.model.CommunalContentSize.THIRD
import com.android.systemui.communal.shared.model.CommunalSceneKey
+import com.android.systemui.communal.shared.model.CommunalWidgetContentModel
import com.android.systemui.communal.shared.model.ObservableCommunalTransitionState
import com.android.systemui.communal.widgets.CommunalAppWidgetHost
import com.android.systemui.communal.widgets.EditWidgetsActivityStarter
@@ -45,6 +46,7 @@
import com.android.systemui.scene.domain.interactor.SceneInteractor
import com.android.systemui.scene.shared.flag.SceneContainerFlags
import com.android.systemui.scene.shared.model.SceneKey
+import com.android.systemui.settings.UserTracker
import com.android.systemui.smartspace.data.repository.SmartspaceRepository
import com.android.systemui.util.kotlin.BooleanFlowOperators.and
import com.android.systemui.util.kotlin.BooleanFlowOperators.not
@@ -59,6 +61,7 @@
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.distinctUntilChanged
+import kotlinx.coroutines.flow.emptyFlow
import kotlinx.coroutines.flow.flatMapLatest
import kotlinx.coroutines.flow.flow
import kotlinx.coroutines.flow.flowOf
@@ -82,6 +85,7 @@
communalSettingsInteractor: CommunalSettingsInteractor,
private val appWidgetHost: CommunalAppWidgetHost,
private val editWidgetsActivityStarter: EditWidgetsActivityStarter,
+ private val userTracker: UserTracker,
sceneInteractor: SceneInteractor,
sceneContainerFlags: SceneContainerFlags,
@CommunalLog logBuffer: LogBuffer,
@@ -125,8 +129,13 @@
/**
* Target scene as requested by the underlying [SceneTransitionLayout] or through
* [onSceneChanged].
+ *
+ * If [isCommunalAvailable] is false, will return [CommunalSceneKey.Blank]
*/
- val desiredScene: StateFlow<CommunalSceneKey> = communalRepository.desiredScene
+ val desiredScene: Flow<CommunalSceneKey> =
+ communalRepository.desiredScene.combine(isCommunalAvailable) { scene, available ->
+ if (available) scene else CommunalSceneKey.Blank
+ }
/** Transition state of the hub mode. */
val transitionState: StateFlow<ObservableCommunalTransitionState> =
@@ -262,10 +271,16 @@
fun updateWidgetOrder(widgetIdToPriorityMap: Map<Int, Int>) =
widgetRepository.updateWidgetOrder(widgetIdToPriorityMap)
+ /** All widgets present in db. */
+ val communalWidgets: Flow<List<CommunalWidgetContentModel>> =
+ isCommunalAvailable.flatMapLatest { available ->
+ if (!available) emptyFlow() else widgetRepository.communalWidgets
+ }
+
/** A list of widget content to be displayed in the communal hub. */
val widgetContent: Flow<List<CommunalContentModel.Widget>> =
widgetRepository.communalWidgets.map { widgets ->
- widgets.map Widget@{ widget ->
+ filterWidgetsByExistingUsers(widgets).map Widget@{ widget ->
return@Widget CommunalContentModel.Widget(
appWidgetId = widget.appWidgetId,
providerInfo = widget.providerInfo,
@@ -345,6 +360,19 @@
return@combine ongoingContent
}
+ /**
+ * Filter and retain widgets associated with an existing user, safeguarding against displaying
+ * stale data following user deletion.
+ */
+ private fun filterWidgetsByExistingUsers(
+ list: List<CommunalWidgetContentModel>,
+ ): List<CommunalWidgetContentModel> {
+ val currentUserIds = userTracker.userProfiles.map { it.id }.toSet()
+ return list.filter { widget ->
+ currentUserIds.contains(widget.providerInfo.profile?.identifier)
+ }
+ }
+
companion object {
/**
* The user activity timeout which should be used when the communal hub is opened. A value
diff --git a/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/BaseCommunalViewModel.kt b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/BaseCommunalViewModel.kt
index 8a7b5eb..3ec9a26 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/BaseCommunalViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/BaseCommunalViewModel.kt
@@ -34,7 +34,7 @@
private val communalInteractor: CommunalInteractor,
val mediaHost: MediaHost,
) {
- val currentScene: StateFlow<CommunalSceneKey> = communalInteractor.desiredScene
+ val currentScene: Flow<CommunalSceneKey> = communalInteractor.desiredScene
/** Whether widgets are currently being re-ordered. */
open val reorderingWidgets: StateFlow<Boolean> = MutableStateFlow(false)
diff --git a/packages/SystemUI/src/com/android/systemui/communal/widgets/CommunalAppWidgetHostStartable.kt b/packages/SystemUI/src/com/android/systemui/communal/widgets/CommunalAppWidgetHostStartable.kt
index 4ddd768..8390d62 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/widgets/CommunalAppWidgetHostStartable.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/widgets/CommunalAppWidgetHostStartable.kt
@@ -18,11 +18,14 @@
import com.android.systemui.CoreStartable
import com.android.systemui.communal.domain.interactor.CommunalInteractor
+import com.android.systemui.communal.shared.model.CommunalWidgetContentModel
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.dagger.qualifiers.Main
+import com.android.systemui.settings.UserTracker
import com.android.systemui.util.kotlin.BooleanFlowOperators.or
import com.android.systemui.util.kotlin.pairwise
+import com.android.systemui.util.kotlin.sample
import javax.inject.Inject
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.CoroutineScope
@@ -37,6 +40,7 @@
constructor(
private val appWidgetHost: CommunalAppWidgetHost,
private val communalInteractor: CommunalInteractor,
+ private val userTracker: UserTracker,
@Background private val bgScope: CoroutineScope,
@Main private val uiDispatcher: CoroutineDispatcher
) : CoreStartable {
@@ -47,6 +51,14 @@
.pairwise(false)
.filter { (previous, new) -> previous != new }
.onEach { (_, shouldListen) -> updateAppWidgetHostActive(shouldListen) }
+ .sample(communalInteractor.communalWidgets, ::Pair)
+ .onEach { (withPrev, widgets) ->
+ val (_, isActive) = withPrev
+ // The validation is performed once the hub becomes active.
+ if (isActive) {
+ validateWidgetsAndDeleteOrphaned(widgets)
+ }
+ }
.launchIn(bgScope)
appWidgetHost.appWidgetIdToRemove
@@ -63,4 +75,15 @@
appWidgetHost.stopListening()
}
}
+
+ /**
+ * Ensure the existence of all associated users for widgets, and remove widgets belonging to
+ * users who have been deleted.
+ */
+ private fun validateWidgetsAndDeleteOrphaned(widgets: List<CommunalWidgetContentModel>) {
+ val currentUserIds = userTracker.userProfiles.map { it.id }.toSet()
+ widgets
+ .filter { widget -> !currentUserIds.contains(widget.providerInfo.profile?.identifier) }
+ .onEach { widget -> communalInteractor.deleteWidget(id = widget.appWidgetId) }
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/controls/ui/ControlActionCoordinatorImpl.kt b/packages/SystemUI/src/com/android/systemui/controls/ui/ControlActionCoordinatorImpl.kt
index 0f038e1..bc07b95 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/ui/ControlActionCoordinatorImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/ui/ControlActionCoordinatorImpl.kt
@@ -36,6 +36,7 @@
import com.android.systemui.controls.ControlsMetricsLogger
import com.android.systemui.controls.settings.ControlsSettingsRepository
import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.dagger.qualifiers.Main
import com.android.systemui.plugins.ActivityStarter
import com.android.systemui.statusbar.VibratorHelper
@@ -47,16 +48,16 @@
@SysUISingleton
class ControlActionCoordinatorImpl @Inject constructor(
- private val context: Context,
- private val bgExecutor: DelayableExecutor,
- @Main private val uiExecutor: DelayableExecutor,
- private val activityStarter: ActivityStarter,
- private val broadcastSender: BroadcastSender,
- private val keyguardStateController: KeyguardStateController,
- private val taskViewFactory: Optional<TaskViewFactory>,
- private val controlsMetricsLogger: ControlsMetricsLogger,
- private val vibrator: VibratorHelper,
- private val controlsSettingsRepository: ControlsSettingsRepository,
+ private val context: Context,
+ @Background private val bgExecutor: DelayableExecutor,
+ @Main private val uiExecutor: DelayableExecutor,
+ private val activityStarter: ActivityStarter,
+ private val broadcastSender: BroadcastSender,
+ private val keyguardStateController: KeyguardStateController,
+ private val taskViewFactory: Optional<TaskViewFactory>,
+ private val controlsMetricsLogger: ControlsMetricsLogger,
+ private val vibrator: VibratorHelper,
+ private val controlsSettingsRepository: ControlsSettingsRepository,
) : ControlActionCoordinator {
private var dialog: Dialog? = null
private var pendingAction: Action? = null
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java
index e893177..1157d97 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java
@@ -126,6 +126,7 @@
import com.android.systemui.statusbar.policy.HeadsUpManager;
import com.android.systemui.statusbar.policy.KeyguardStateController;
import com.android.systemui.statusbar.policy.PolicyModule;
+import com.android.systemui.statusbar.policy.SensitiveNotificationProtectionController;
import com.android.systemui.statusbar.policy.ZenModeController;
import com.android.systemui.statusbar.policy.dagger.SmartRepliesInflationModule;
import com.android.systemui.statusbar.policy.dagger.StatusBarPolicyModule;
@@ -358,6 +359,7 @@
VisualInterruptionDecisionProvider visualInterruptionDecisionProvider,
ZenModeController zenModeController,
NotificationLockscreenUserManager notifUserManager,
+ SensitiveNotificationProtectionController sensitiveNotificationProtectionController,
CommonNotifCollection notifCollection,
NotifPipeline notifPipeline,
SysUiState sysUiState,
@@ -376,6 +378,7 @@
visualInterruptionDecisionProvider,
zenModeController,
notifUserManager,
+ sensitiveNotificationProtectionController,
notifCollection,
notifPipeline,
sysUiState,
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewConfigurator.kt b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewConfigurator.kt
index e35c5a6..301942f 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewConfigurator.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewConfigurator.kt
@@ -170,7 +170,6 @@
KeyguardIndicationAreaBinder.bind(
notificationShadeWindowView.requireViewById(R.id.keyguard_indication_area),
keyguardIndicationAreaViewModel,
- aodAlphaViewModel,
indicationController,
)
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardBlueprintInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardBlueprintInteractor.kt
index 56d64a2..bc3f0cc 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardBlueprintInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardBlueprintInteractor.kt
@@ -15,12 +15,15 @@
*
*/
+@file:OptIn(ExperimentalCoroutinesApi::class)
+
package com.android.systemui.keyguard.domain.interactor
import android.content.Context
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.keyguard.data.repository.KeyguardBlueprintRepository
+import com.android.systemui.keyguard.shared.ComposeLockscreen
import com.android.systemui.keyguard.shared.model.KeyguardBlueprint
import com.android.systemui.keyguard.ui.view.layout.blueprints.DefaultKeyguardBlueprint
import com.android.systemui.keyguard.ui.view.layout.blueprints.SplitShadeKeyguardBlueprint
@@ -29,7 +32,9 @@
import com.android.systemui.statusbar.policy.SplitShadeStateController
import javax.inject.Inject
import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.collect
import kotlinx.coroutines.flow.onStart
import kotlinx.coroutines.launch
@@ -41,6 +46,7 @@
@Application private val applicationScope: CoroutineScope,
private val context: Context,
private val splitShadeStateController: SplitShadeStateController,
+ private val clockInteractor: KeyguardClockInteractor,
) {
/** The current blueprint for the lockscreen. */
@@ -58,6 +64,7 @@
.onStart { emit(Unit) }
.collect { updateBlueprint() }
}
+ applicationScope.launch { clockInteractor.currentClock.collect { updateBlueprint() } }
}
/**
@@ -67,12 +74,17 @@
private fun updateBlueprint() {
val useSplitShade =
splitShadeStateController.shouldUseSplitNotificationShade(context.resources)
+ // TODO(b/326098079): Make ID a constant value.
+ val useWeatherClockLayout =
+ clockInteractor.currentClock.value?.config?.id == "DIGITAL_CLOCK_WEATHER" &&
+ ComposeLockscreen.isEnabled
val blueprintId =
- if (useSplitShade) {
- SplitShadeKeyguardBlueprint.ID
- } else {
- DefaultKeyguardBlueprint.DEFAULT
+ when {
+ useWeatherClockLayout && useSplitShade -> SPLIT_SHADE_WEATHER_CLOCK_BLUEPRINT_ID
+ useWeatherClockLayout -> WEATHER_CLOCK_BLUEPRINT_ID
+ useSplitShade -> SplitShadeKeyguardBlueprint.ID
+ else -> DefaultKeyguardBlueprint.DEFAULT
}
transitionToBlueprint(blueprintId)
@@ -107,4 +119,13 @@
fun getCurrentBlueprint(): KeyguardBlueprint {
return keyguardBlueprintRepository.blueprint.value
}
+
+ companion object {
+ /**
+ * These values live here because classes in the composable package do not exist in some
+ * systems.
+ */
+ const val WEATHER_CLOCK_BLUEPRINT_ID = "weather-clock"
+ const val SPLIT_SHADE_WEATHER_CLOCK_BLUEPRINT_ID = "split-shade-weather-clock"
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardIndicationAreaBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardIndicationAreaBinder.kt
index 7c1368a..841f52d 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardIndicationAreaBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardIndicationAreaBinder.kt
@@ -23,7 +23,7 @@
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.repeatOnLifecycle
import com.android.systemui.Flags.keyguardBottomAreaRefactor
-import com.android.systemui.keyguard.ui.viewmodel.AodAlphaViewModel
+import com.android.systemui.Flags.migrateClocksToBlueprint
import com.android.systemui.keyguard.ui.viewmodel.KeyguardIndicationAreaViewModel
import com.android.systemui.lifecycle.repeatWhenAttached
import com.android.systemui.res.R
@@ -51,7 +51,6 @@
fun bind(
view: ViewGroup,
viewModel: KeyguardIndicationAreaViewModel,
- aodAlphaViewModel: AodAlphaViewModel,
indicationController: KeyguardIndicationController,
): DisposableHandle {
indicationController.setIndicationArea(view)
@@ -68,30 +67,10 @@
view.repeatWhenAttached {
repeatOnLifecycle(Lifecycle.State.STARTED) {
launch {
- if (keyguardBottomAreaRefactor()) {
- aodAlphaViewModel.alpha.collect { alpha ->
- view.apply {
- this.importantForAccessibility =
- if (alpha == 0f) {
- View.IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS
- } else {
- View.IMPORTANT_FOR_ACCESSIBILITY_AUTO
- }
- this.alpha = alpha
- }
- }
- } else {
- viewModel.alpha.collect { alpha ->
- view.apply {
- this.importantForAccessibility =
- if (alpha == 0f) {
- View.IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS
- } else {
- View.IMPORTANT_FOR_ACCESSIBILITY_AUTO
- }
- this.alpha = alpha
- }
- }
+ // Do not independently apply alpha, as [KeyguardRootViewModel] should work
+ // for this and all its children
+ if (!(migrateClocksToBlueprint() || keyguardBottomAreaRefactor())) {
+ viewModel.alpha.collect { alpha -> view.alpha = alpha }
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/KeyguardIndicationArea.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/KeyguardIndicationArea.kt
index 78099d9..a53c6d7 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/KeyguardIndicationArea.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/KeyguardIndicationArea.kt
@@ -50,6 +50,15 @@
)
}
+ override fun setAlpha(alpha: Float) {
+ super.setAlpha(alpha)
+
+ if (alpha == 0f) {
+ importantForAccessibility = View.IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS
+ } else {
+ importantForAccessibility = View.IMPORTANT_FOR_ACCESSIBILITY_AUTO
+ }
+ }
private fun indicationTopRow(): KeyguardIndicationTextView {
return KeyguardIndicationTextView(context, attrs).apply {
id = R.id.keyguard_indication_text
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/blueprints/KeyguardBlueprintModule.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/blueprints/KeyguardBlueprintModule.kt
index 4f1a754..b4e57cc 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/blueprints/KeyguardBlueprintModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/blueprints/KeyguardBlueprintModule.kt
@@ -17,10 +17,13 @@
package com.android.systemui.keyguard.ui.view.layout.blueprints
-import com.android.systemui.communal.ui.view.layout.blueprints.DefaultCommunalBlueprint
+import com.android.systemui.keyguard.domain.interactor.KeyguardBlueprintInteractor.Companion.SPLIT_SHADE_WEATHER_CLOCK_BLUEPRINT_ID
+import com.android.systemui.keyguard.domain.interactor.KeyguardBlueprintInteractor.Companion.WEATHER_CLOCK_BLUEPRINT_ID
import com.android.systemui.keyguard.shared.model.KeyguardBlueprint
+import com.android.systemui.keyguard.shared.model.KeyguardSection
import dagger.Binds
import dagger.Module
+import dagger.Provides
import dagger.multibindings.IntoSet
@Module
@@ -43,9 +46,25 @@
shortcutsBesideUdfpsLockscreenBlueprint: ShortcutsBesideUdfpsKeyguardBlueprint
): KeyguardBlueprint
- @Binds
- @IntoSet
- abstract fun bindDefaultCommunalBlueprint(
- defaultCommunalBlueprint: DefaultCommunalBlueprint
- ): KeyguardBlueprint
+ companion object {
+ /** This is a place holder for weather clock in compose. */
+ @Provides
+ @IntoSet
+ fun bindWeatherClockBlueprintPlaceHolder(): KeyguardBlueprint {
+ return object : KeyguardBlueprint {
+ override val id: String = WEATHER_CLOCK_BLUEPRINT_ID
+ override val sections: List<KeyguardSection> = listOf()
+ }
+ }
+
+ /** This is a place holder for weather clock in compose. */
+ @Provides
+ @IntoSet
+ fun bindSplitShadeWeatherClockBlueprintPlaceHolder(): KeyguardBlueprint {
+ return object : KeyguardBlueprint {
+ override val id: String = SPLIT_SHADE_WEATHER_CLOCK_BLUEPRINT_ID
+ override val sections: List<KeyguardSection> = listOf()
+ }
+ }
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultIndicationAreaSection.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultIndicationAreaSection.kt
index ea05c1d..3361343 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultIndicationAreaSection.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultIndicationAreaSection.kt
@@ -25,7 +25,6 @@
import com.android.systemui.keyguard.shared.model.KeyguardSection
import com.android.systemui.keyguard.ui.binder.KeyguardIndicationAreaBinder
import com.android.systemui.keyguard.ui.view.KeyguardIndicationArea
-import com.android.systemui.keyguard.ui.viewmodel.AodAlphaViewModel
import com.android.systemui.keyguard.ui.viewmodel.KeyguardIndicationAreaViewModel
import com.android.systemui.res.R
import com.android.systemui.statusbar.KeyguardIndicationController
@@ -37,7 +36,6 @@
constructor(
private val context: Context,
private val keyguardIndicationAreaViewModel: KeyguardIndicationAreaViewModel,
- private val aodAlphaViewModel: AodAlphaViewModel,
private val indicationController: KeyguardIndicationController,
) : KeyguardSection() {
private val indicationAreaViewId = R.id.keyguard_indication_area
@@ -56,7 +54,6 @@
KeyguardIndicationAreaBinder.bind(
constraintLayout.requireViewById(R.id.keyguard_indication_area),
keyguardIndicationAreaViewModel,
- aodAlphaViewModel,
indicationController,
)
}
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/MediaDeviceManager.kt b/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/MediaDeviceManager.kt
index 42d68ba..f4d70a5 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/MediaDeviceManager.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/MediaDeviceManager.kt
@@ -30,6 +30,8 @@
import androidx.annotation.WorkerThread
import com.android.settingslib.bluetooth.LocalBluetoothLeBroadcast
import com.android.settingslib.bluetooth.LocalBluetoothManager
+import com.android.settingslib.flags.Flags.enableLeAudioSharing
+import com.android.settingslib.flags.Flags.legacyLeAudioSharing
import com.android.settingslib.media.LocalMediaManager
import com.android.settingslib.media.MediaDevice
import com.android.settingslib.media.PhoneMediaDevice
@@ -332,14 +334,28 @@
@WorkerThread
private fun updateCurrent() {
if (isLeAudioBroadcastEnabled()) {
- current =
- MediaDeviceData(
- /* enabled */ true,
- /* icon */ context.getDrawable(R.drawable.settings_input_antenna),
- /* name */ broadcastDescription,
- /* intent */ null,
- /* showBroadcastButton */ showBroadcastButton = true
- )
+ if (enableLeAudioSharing()) {
+ current =
+ MediaDeviceData(
+ enabled = false,
+ icon =
+ context.getDrawable(
+ com.android.settingslib.R.drawable.ic_bt_le_audio_sharing
+ ),
+ name = context.getString(R.string.audio_sharing_description),
+ intent = null,
+ showBroadcastButton = false
+ )
+ } else {
+ current =
+ MediaDeviceData(
+ /* enabled */ true,
+ /* icon */ context.getDrawable(R.drawable.settings_input_antenna),
+ /* name */ broadcastDescription,
+ /* intent */ null,
+ /* showBroadcastButton */ showBroadcastButton = true
+ )
+ }
} else {
val aboutToConnect = aboutToConnectDeviceOverride
if (
@@ -420,6 +436,7 @@
@WorkerThread
private fun isLeAudioBroadcastEnabled(): Boolean {
+ if (!enableLeAudioSharing() && !legacyLeAudioSharing()) return false
val localBluetoothManager = localBluetoothManager.get()
if (localBluetoothManager != null) {
val profileManager = localBluetoothManager.profileManager
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/KeyguardMediaController.kt b/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/KeyguardMediaController.kt
index 9206af2..ba7d410 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/KeyguardMediaController.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/KeyguardMediaController.kt
@@ -300,10 +300,17 @@
private fun setVisibility(view: ViewGroup?, newVisibility: Int) {
val currentMediaContainer = view ?: return
- val previousVisibility = currentMediaContainer.visibility
- currentMediaContainer.visibility = newVisibility
- if (previousVisibility != newVisibility && currentMediaContainer is MediaContainerView) {
- visibilityChangedListener?.invoke(newVisibility == View.VISIBLE)
+ val isVisible = newVisibility == View.VISIBLE
+
+ if (currentMediaContainer is MediaContainerView) {
+ val previousVisibility = currentMediaContainer.visibility
+
+ currentMediaContainer.setKeyguardVisibility(isVisible)
+ if (previousVisibility != newVisibility) {
+ visibilityChangedListener?.invoke(isVisible)
+ }
+ } else {
+ currentMediaContainer.visibility = newVisibility
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaControlPanel.java b/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaControlPanel.java
index e8ad4d3..4e940f1 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaControlPanel.java
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaControlPanel.java
@@ -260,7 +260,6 @@
private TurbulenceNoiseController mTurbulenceNoiseController;
private LoadingEffect mLoadingEffect;
private final GlobalSettings mGlobalSettings;
- private final Random mRandom = new Random();
private TurbulenceNoiseAnimationConfig mTurbulenceNoiseAnimationConfig;
private boolean mWasPlaying = false;
private boolean mButtonClicked = false;
@@ -1294,13 +1293,14 @@
mMediaViewHolder.getTurbulenceNoiseView();
int width = targetView.getWidth();
int height = targetView.getHeight();
+ Random random = new Random();
return new TurbulenceNoiseAnimationConfig(
/* gridCount= */ 2.14f,
TurbulenceNoiseAnimationConfig.DEFAULT_LUMINOSITY_MULTIPLIER,
- /* noiseOffsetX= */ mRandom.nextFloat(),
- /* noiseOffsetY= */ mRandom.nextFloat(),
- /* noiseOffsetZ= */ mRandom.nextFloat(),
+ /* noiseOffsetX= */ random.nextFloat(),
+ /* noiseOffsetY= */ random.nextFloat(),
+ /* noiseOffsetZ= */ random.nextFloat(),
/* noiseMoveSpeedX= */ 0.42f,
/* noiseMoveSpeedY= */ 0f,
TurbulenceNoiseAnimationConfig.DEFAULT_NOISE_SPEED_Z,
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaHierarchyManager.kt b/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaHierarchyManager.kt
index 3b989d9..dbd71f3 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaHierarchyManager.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaHierarchyManager.kt
@@ -58,6 +58,7 @@
import com.android.systemui.statusbar.policy.KeyguardStateController
import com.android.systemui.statusbar.policy.SplitShadeStateController
import com.android.systemui.util.animation.UniqueObjectHostView
+import com.android.systemui.util.kotlin.BooleanFlowOperators.and
import com.android.systemui.util.settings.SecureSettings
import javax.inject.Inject
import kotlinx.coroutines.CoroutineScope
@@ -583,12 +584,14 @@
UserHandle.USER_ALL
)
- // Listen to the communal UI state.
+ // Listen to the communal UI state. Make sure that communal UI is showing and hub itself is
+ // available, ie. not disabled and able to be shown.
coroutineScope.launch {
- communalInteractor.isCommunalShowing.collect { value ->
- isCommunalShowing = value
- updateDesiredLocation(forceNoAnimation = true)
- }
+ and(communalInteractor.isCommunalShowing, communalInteractor.isCommunalAvailable)
+ .collect { value ->
+ isCommunalShowing = value
+ updateDesiredLocation()
+ }
}
}
@@ -1150,12 +1153,16 @@
when {
mediaFlags.isSceneContainerEnabled() -> desiredLocation
dreamOverlayActive && dreamMediaComplicationActive -> LOCATION_DREAM_OVERLAY
+
+ // UMO should show in communal unless the shade is expanding or visible.
+ isCommunalShowing && qsExpansion == 0.0f -> LOCATION_COMMUNAL_HUB
(qsExpansion > 0.0f || inSplitShade) && !onLockscreen -> LOCATION_QS
qsExpansion > 0.4f && onLockscreen -> LOCATION_QS
onLockscreen && isSplitShadeExpanding() -> LOCATION_QS
onLockscreen && isTransformingToFullShadeAndInQQS() -> LOCATION_QQS
- // TODO(b/311234666): revisit logic once interactions between the hub and
- // shade/keyguard state are finalized
+
+ // Communal does not have its own StatusBarState so it should always have higher
+ // priority for the UMO over the lockscreen.
isCommunalShowing -> LOCATION_COMMUNAL_HUB
onLockscreen && allowMediaPlayerOnLockScreen -> LOCATION_LOCKSCREEN
else -> LOCATION_QQS
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 416eae1..4f062af 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
@@ -72,7 +72,7 @@
context: Context,
logger: MediaTttReceiverLogger,
windowManager: WindowManager,
- mainExecutor: DelayableExecutor,
+ @Main mainExecutor: DelayableExecutor,
accessibilityManager: AccessibilityManager,
configurationController: ConfigurationController,
dumpManager: DumpManager,
diff --git a/packages/SystemUI/src/com/android/systemui/qs/ui/adapter/QSSceneAdapter.kt b/packages/SystemUI/src/com/android/systemui/qs/ui/adapter/QSSceneAdapter.kt
index 72a5c46..c1b2037 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/ui/adapter/QSSceneAdapter.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/ui/adapter/QSSceneAdapter.kt
@@ -84,6 +84,10 @@
*/
val qsHeight: Int
+ /** Compatibility for use by LockscreenShadeTransitionController. Matches default from [QS] */
+ val isQsFullyCollapsed: Boolean
+ get() = true
+
sealed interface State {
val isVisible: Boolean
@@ -165,6 +169,10 @@
override val qsHeight: Int
get() = qsImpl.value?.qsHeight ?: 0
+ // If value is null, there's no QS and therefore it's fully collapsed.
+ override val isQsFullyCollapsed: Boolean
+ get() = qsImpl.value?.isFullyCollapsed ?: true
+
// Same config changes as in FragmentHostManager
private val interestingChanges =
InterestingConfigChanges(
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ActionIntentExecutor.kt b/packages/SystemUI/src/com/android/systemui/screenshot/ActionIntentExecutor.kt
index bee3152..fb5339d 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/ActionIntentExecutor.kt
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ActionIntentExecutor.kt
@@ -31,10 +31,13 @@
import android.view.WindowManagerGlobal
import com.android.app.tracing.coroutines.launch
import com.android.internal.infra.ServiceConnector
+import com.android.systemui.Flags.screenshotActionDismissSystemWindows
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.dagger.qualifiers.Main
import com.android.systemui.settings.DisplayTracker
+import com.android.systemui.shared.system.ActivityManagerWrapper
+import com.android.systemui.statusbar.phone.CentralSurfaces
import javax.inject.Inject
import kotlinx.coroutines.CompletableDeferred
import kotlinx.coroutines.CoroutineDispatcher
@@ -46,9 +49,11 @@
@Inject
constructor(
private val context: Context,
+ private val activityManagerWrapper: ActivityManagerWrapper,
@Application private val applicationScope: CoroutineScope,
@Main private val mainDispatcher: CoroutineDispatcher,
private val displayTracker: DisplayTracker,
+ private val keyguardController: ScreenshotKeyguardController,
) {
/**
* Execute the given intent with startActivity while performing operations for screenshot action
@@ -74,7 +79,14 @@
user: UserHandle,
overrideTransition: Boolean,
) {
- dismissKeyguard()
+ if (screenshotActionDismissSystemWindows()) {
+ keyguardController.dismiss()
+ activityManagerWrapper.closeSystemWindows(
+ CentralSurfaces.SYSTEM_DIALOG_REASON_SCREENSHOT
+ )
+ } else {
+ dismissKeyguard()
+ }
if (user == myUserHandle()) {
withContext(mainDispatcher) { context.startActivity(intent, options) }
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotKeyguardController.kt b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotKeyguardController.kt
new file mode 100644
index 0000000..7696bbe
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotKeyguardController.kt
@@ -0,0 +1,46 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.screenshot
+
+import android.content.Context
+import android.content.Intent
+import com.android.internal.infra.ServiceConnector
+import javax.inject.Inject
+import kotlinx.coroutines.CompletableDeferred
+
+open class ScreenshotKeyguardController @Inject constructor(context: Context) {
+ private val proxyConnector: ServiceConnector<IScreenshotProxy> =
+ ServiceConnector.Impl(
+ context,
+ Intent(context, ScreenshotProxyService::class.java),
+ Context.BIND_AUTO_CREATE or Context.BIND_WAIVE_PRIORITY or Context.BIND_NOT_VISIBLE,
+ context.userId,
+ IScreenshotProxy.Stub::asInterface
+ )
+
+ suspend fun dismiss() {
+ val completion = CompletableDeferred<Unit>()
+ val onDoneBinder =
+ object : IOnDoneCallback.Stub() {
+ override fun onDone(success: Boolean) {
+ completion.complete(Unit)
+ }
+ }
+ proxyConnector.post { it.dismissKeyguard(onDoneBinder) }
+ completion.await()
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotProxyService.kt b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotProxyService.kt
index 86f6523..d5ab306 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotProxyService.kt
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotProxyService.kt
@@ -17,15 +17,16 @@
import android.content.Intent
import android.os.IBinder
+import android.os.RemoteException
import android.util.Log
import androidx.lifecycle.LifecycleService
import androidx.lifecycle.lifecycleScope
+import com.android.app.tracing.coroutines.launch
import com.android.systemui.dagger.qualifiers.Main
import com.android.systemui.plugins.ActivityStarter
import com.android.systemui.shade.ShadeExpansionStateManager
import javax.inject.Inject
import kotlinx.coroutines.CoroutineDispatcher
-import com.android.app.tracing.coroutines.launch
import kotlinx.coroutines.withContext
/** Provides state from the main SystemUI process on behalf of the Screenshot process. */
@@ -56,7 +57,13 @@
private suspend fun executeAfterDismissing(callback: IOnDoneCallback) =
withContext(mMainDispatcher) {
activityStarter.executeRunnableDismissingKeyguard(
- Runnable { callback.onDone(true) },
+ {
+ try {
+ callback.onDone(true)
+ } catch (e: RemoteException) {
+ Log.w(TAG, "Failed to complete callback transaction", e)
+ }
+ },
null,
true /* dismissShade */,
true /* afterKeyguardGone */,
diff --git a/packages/SystemUI/src/com/android/systemui/settings/UserTrackerImpl.kt b/packages/SystemUI/src/com/android/systemui/settings/UserTrackerImpl.kt
index f2fa0ef..125f7fc 100644
--- a/packages/SystemUI/src/com/android/systemui/settings/UserTrackerImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/settings/UserTrackerImpl.kt
@@ -121,7 +121,6 @@
@GuardedBy("callbacks")
private val callbacks: MutableList<DataItem> = ArrayList()
- private var beforeUserSwitchingJob: Job? = null
private var userSwitchingJob: Job? = null
private var afterUserSwitchingJob: Job? = null
@@ -194,14 +193,7 @@
private fun registerUserSwitchObserver() {
iActivityManager.registerUserSwitchObserver(object : UserSwitchObserver() {
override fun onBeforeUserSwitching(newUserId: Int) {
- if (isBackgroundUserSwitchEnabled) {
- beforeUserSwitchingJob?.cancel()
- beforeUserSwitchingJob = appScope.launch(backgroundContext) {
- handleBeforeUserSwitching(newUserId)
- }
- } else {
- handleBeforeUserSwitching(newUserId)
- }
+ handleBeforeUserSwitching(newUserId)
}
override fun onUserSwitching(newUserId: Int, reply: IRemoteCallback?) {
@@ -233,15 +225,24 @@
@WorkerThread
protected open fun handleBeforeUserSwitching(newUserId: Int) {
- Assert.isNotMainThread()
setUserIdInternal(newUserId)
val list = synchronized(callbacks) {
callbacks.toList()
}
+ val latch = CountDownLatch(list.size)
list.forEach {
- it.callback.get()?.onBeforeUserSwitching(newUserId)
+ val callback = it.callback.get()
+ if (callback != null) {
+ it.executor.execute {
+ callback.onBeforeUserSwitching(newUserId)
+ latch.countDown()
+ }
+ } else {
+ latch.countDown()
+ }
}
+ latch.await()
}
@WorkerThread
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/LockscreenShadeQsTransitionController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/LockscreenShadeQsTransitionController.kt
index 4d0552e..adca3f2 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/LockscreenShadeQsTransitionController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/LockscreenShadeQsTransitionController.kt
@@ -21,9 +21,9 @@
import android.util.MathUtils
import androidx.annotation.FloatRange
import androidx.annotation.Px
-import com.android.systemui.res.R
import com.android.systemui.dump.DumpManager
import com.android.systemui.plugins.qs.QS
+import com.android.systemui.res.R
import com.android.systemui.statusbar.policy.ConfigurationController
import com.android.systemui.statusbar.policy.SplitShadeStateController
import dagger.assisted.Assisted
@@ -38,7 +38,7 @@
context: Context,
configurationController: ConfigurationController,
dumpManager: DumpManager,
- @Assisted private val qsProvider: () -> QS,
+ @Assisted private val qsProvider: () -> QS?,
splitShadeStateController: SplitShadeStateController
) :
AbstractLockscreenShadeTransitionController(
@@ -48,7 +48,7 @@
splitShadeStateController
) {
- private val qs: QS
+ private val qs: QS?
get() = qsProvider()
/**
@@ -135,7 +135,7 @@
/* amount= */ MathUtils.saturate(qsDragDownAmount / qsSquishTransitionDistance)
)
isTransitioningToFullShade = dragDownAmount > 0.0f
- qs.setTransitionToFullShadeProgress(
+ qs?.setTransitionToFullShadeProgress(
isTransitioningToFullShade,
qsTransitionFraction,
qsSquishTransitionFraction
@@ -163,6 +163,6 @@
@AssistedFactory
fun interface Factory {
- fun create(qsProvider: () -> QS): LockscreenShadeQsTransitionController
+ fun create(qsProvider: () -> QS?): LockscreenShadeQsTransitionController
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/LockscreenShadeTransitionController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/LockscreenShadeTransitionController.kt
index a59d753..4ee8349 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/LockscreenShadeTransitionController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/LockscreenShadeTransitionController.kt
@@ -31,6 +31,7 @@
import com.android.systemui.plugins.FalsingManager
import com.android.systemui.plugins.qs.QS
import com.android.systemui.plugins.statusbar.StatusBarStateController
+import com.android.systemui.qs.ui.adapter.QSSceneAdapter
import com.android.systemui.res.R
import com.android.systemui.shade.ShadeLockscreenInteractor
import com.android.systemui.shade.data.repository.ShadeRepository
@@ -84,6 +85,7 @@
private val splitShadeStateController: SplitShadeStateController,
private val shadeLockscreenInteractorLazy: Lazy<ShadeLockscreenInteractor>,
naturalScrollingSettingObserver: NaturalScrollingSettingObserver,
+ private val lazyQSSceneAdapter: Lazy<QSSceneAdapter>,
) : Dumpable {
private var pulseHeight: Float = 0f
@@ -93,7 +95,11 @@
private var useSplitShade: Boolean = false
private lateinit var nsslController: NotificationStackScrollLayoutController
lateinit var centralSurfaces: CentralSurfaces
- lateinit var qS: QS
+
+ // When in scene container mode, this will be null. In that case, we use the adapter if needed
+ var qS: QS? = null
+ private val isQsFullyCollapsed: Boolean
+ get() = qS?.isFullyCollapsed ?: lazyQSSceneAdapter.get().isQsFullyCollapsed
/** A handler that handles the next keyguard dismiss animation. */
private var animationHandlerOnKeyguardDismiss: ((Long) -> Unit)? = null
@@ -286,7 +292,8 @@
/** @return true if the interaction is accepted, false if it should be cancelled */
internal fun canDragDown(): Boolean {
return (statusBarStateController.state == StatusBarState.KEYGUARD ||
- nsslController.isInLockedDownShade()) && (qS.isFullyCollapsed || useSplitShade)
+ nsslController.isInLockedDownShade()) &&
+ (isQsFullyCollapsed || useSplitShade)
}
/** Called by the touch helper when when a gesture has completed all the way and released. */
@@ -410,7 +417,7 @@
get() =
(statusBarStateController.getState() == StatusBarState.KEYGUARD &&
!keyguardBypassController.bypassEnabled &&
- (qS.isFullyCollapsed || useSplitShade))
+ (isQsFullyCollapsed || useSplitShade))
/** The amount in pixels that the user has dragged down. */
internal var dragDownAmount = 0f
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/SplitShadeLockScreenOverScroller.kt b/packages/SystemUI/src/com/android/systemui/statusbar/SplitShadeLockScreenOverScroller.kt
index e47c914..612a365 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/SplitShadeLockScreenOverScroller.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/SplitShadeLockScreenOverScroller.kt
@@ -27,7 +27,7 @@
private val context: Context,
private val scrimController: ScrimController,
private val statusBarStateController: SysuiStatusBarStateController,
- @Assisted private val qSProvider: () -> QS,
+ @Assisted private val qSProvider: () -> QS?,
@Assisted private val nsslControllerProvider: () -> NotificationStackScrollLayoutController
) : LockScreenShadeOverScroller {
@@ -37,7 +37,7 @@
private var maxOverScrollAmount = 0
private var previousOverscrollAmount = 0
- private val qS: QS
+ private val qS: QS?
get() = qSProvider()
private val nsslController: NotificationStackScrollLayoutController
@@ -90,7 +90,7 @@
}
private fun applyOverscroll(overscrollAmount: Int) {
- qS.setOverScrollAmount(overscrollAmount)
+ qS?.setOverScrollAmount(overscrollAmount)
scrimController.setNotificationsOverScrollAmount(overscrollAmount)
nsslController.setOverScrollAmount(overscrollAmount)
}
@@ -109,7 +109,7 @@
val animator = ValueAnimator.ofInt(previousOverscrollAmount, 0)
animator.addUpdateListener {
val overScrollAmount = it.animatedValue as Int
- qS.setOverScrollAmount(overScrollAmount)
+ qS?.setOverScrollAmount(overScrollAmount)
scrimController.setNotificationsOverScrollAmount(overScrollAmount)
nsslController.setOverScrollAmount(overScrollAmount)
}
@@ -143,7 +143,7 @@
@AssistedFactory
fun interface Factory {
fun create(
- qSProvider: () -> QS,
+ qSProvider: () -> QS?,
nsslControllerProvider: () -> NotificationStackScrollLayoutController
): SplitShadeLockScreenOverScroller
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/NotifCoordinators.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/NotifCoordinators.kt
index bd659d2..b9d1dde 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/NotifCoordinators.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/NotifCoordinators.kt
@@ -53,6 +53,7 @@
mediaCoordinator: MediaCoordinator,
preparationCoordinator: PreparationCoordinator,
remoteInputCoordinator: RemoteInputCoordinator,
+ rowAlertTimeCoordinator: RowAlertTimeCoordinator,
rowAppearanceCoordinator: RowAppearanceCoordinator,
stackCoordinator: StackCoordinator,
shadeEventCoordinator: ShadeEventCoordinator,
@@ -69,9 +70,7 @@
private val mCoordinators: MutableList<Coordinator> = ArrayList()
private val mOrderedSections: MutableList<NotifSectioner> = ArrayList()
- /**
- * Creates all the coordinators.
- */
+ /** Creates all the coordinators. */
init {
// Attach core coordinators.
mCoreCoordinators.add(dataStoreCoordinator)
@@ -89,6 +88,7 @@
mCoordinators.add(groupCountCoordinator)
mCoordinators.add(groupWhenCoordinator)
mCoordinators.add(mediaCoordinator)
+ mCoordinators.add(rowAlertTimeCoordinator)
mCoordinators.add(rowAppearanceCoordinator)
mCoordinators.add(stackCoordinator)
mCoordinators.add(shadeEventCoordinator)
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/RowAlertTimeCoordinator.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/RowAlertTimeCoordinator.kt
new file mode 100644
index 0000000..12de339
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/RowAlertTimeCoordinator.kt
@@ -0,0 +1,61 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.systemui.statusbar.notification.collection.coordinator
+
+import android.util.ArrayMap
+import com.android.systemui.statusbar.notification.collection.GroupEntry
+import com.android.systemui.statusbar.notification.collection.ListEntry
+import com.android.systemui.statusbar.notification.collection.NotifPipeline
+import com.android.systemui.statusbar.notification.collection.NotificationEntry
+import com.android.systemui.statusbar.notification.collection.coordinator.dagger.CoordinatorScope
+import com.android.systemui.statusbar.notification.collection.render.NotifRowController
+import javax.inject.Inject
+import kotlin.math.max
+
+/**
+ * A small coordinator which ensures the "alerted" bell shows not just for recently alerted entries,
+ * but also on the summary for every such entry.
+ */
+@CoordinatorScope
+class RowAlertTimeCoordinator @Inject constructor() : Coordinator {
+
+ private val latestAlertTimeBySummary = ArrayMap<NotificationEntry, Long>()
+
+ override fun attach(pipeline: NotifPipeline) {
+ pipeline.addOnBeforeFinalizeFilterListener(::onBeforeFinalizeFilterListener)
+ pipeline.addOnAfterRenderEntryListener(::onAfterRenderEntry)
+ }
+
+ private fun onBeforeFinalizeFilterListener(entries: List<ListEntry>) {
+ latestAlertTimeBySummary.clear()
+ entries.asSequence().filterIsInstance<GroupEntry>().forEach { groupEntry ->
+ val summary = checkNotNull(groupEntry.summary)
+ latestAlertTimeBySummary[summary] = groupEntry.calculateLatestAlertTime()
+ }
+ }
+
+ private fun onAfterRenderEntry(entry: NotificationEntry, controller: NotifRowController) {
+ // Show the "alerted" bell icon based on the latest group member for summaries
+ val lastAudiblyAlerted = latestAlertTimeBySummary[entry] ?: entry.lastAudiblyAlertedMs
+ controller.setLastAudibleMs(lastAudiblyAlerted)
+ }
+
+ private fun GroupEntry.calculateLatestAlertTime(): Long {
+ val lastChildAlertedTime = children.maxOf { it.lastAudiblyAlertedMs }
+ val summaryAlertedTime = checkNotNull(summary).lastAudiblyAlertedMs
+ return max(lastChildAlertedTime, summaryAlertedTime)
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/RowAppearanceCoordinator.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/RowAppearanceCoordinator.kt
index f2b8482..df694bb 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/RowAppearanceCoordinator.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/RowAppearanceCoordinator.kt
@@ -75,7 +75,5 @@
(mAutoExpandFirstNotification && entry == entryToExpand))
// Show/hide the feedback icon
controller.setFeedbackIcon(mAssistantFeedbackController.getFeedbackIcon(entry))
- // Show the "alerted" bell icon
- controller.setLastAudibleMs(entry.lastAudiblyAlertedMs)
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/VisualStabilityCoordinator.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/VisualStabilityCoordinator.java
index 8189fe0..dfe6cd5 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/VisualStabilityCoordinator.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/VisualStabilityCoordinator.java
@@ -25,6 +25,7 @@
import com.android.systemui.Dumpable;
import com.android.systemui.dagger.SysUISingleton;
+import com.android.systemui.dagger.qualifiers.Background;
import com.android.systemui.dump.DumpManager;
import com.android.systemui.keyguard.WakefulnessLifecycle;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
@@ -92,7 +93,7 @@
@Inject
public VisualStabilityCoordinator(
- DelayableExecutor delayableExecutor,
+ @Background DelayableExecutor delayableExecutor,
DumpManager dumpManager,
HeadsUpManager headsUpManager,
ShadeAnimationInteractor shadeAnimationInteractor,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/dagger/NotificationsModule.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/dagger/NotificationsModule.java
index 2a1ec3e..6548967 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/dagger/NotificationsModule.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/dagger/NotificationsModule.java
@@ -16,12 +16,18 @@
package com.android.systemui.statusbar.notification.dagger;
+import android.app.NotificationManager;
import android.content.Context;
import android.service.notification.NotificationListenerService;
import com.android.internal.jank.InteractionJankMonitor;
+import com.android.settingslib.statusbar.notification.data.repository.NotificationsSoundPolicyRepository;
+import com.android.settingslib.statusbar.notification.data.repository.NotificationsSoundPolicyRepositoryImpl;
+import com.android.settingslib.statusbar.notification.domain.interactor.NotificationsSoundPolicyInteractor;
import com.android.systemui.CoreStartable;
import com.android.systemui.dagger.SysUISingleton;
+import com.android.systemui.dagger.qualifiers.Application;
+import com.android.systemui.dagger.qualifiers.Background;
import com.android.systemui.res.R;
import com.android.systemui.statusbar.NotificationListener;
import com.android.systemui.statusbar.notification.NotificationActivityStarter;
@@ -79,13 +85,15 @@
import com.android.systemui.statusbar.phone.StatusBarNotificationActivityStarter;
import com.android.systemui.statusbar.policy.HeadsUpManager;
+import javax.inject.Provider;
+
import dagger.Binds;
import dagger.Module;
import dagger.Provides;
import dagger.multibindings.ClassKey;
import dagger.multibindings.IntoMap;
-
-import javax.inject.Provider;
+import kotlin.coroutines.CoroutineContext;
+import kotlinx.coroutines.CoroutineScope;
/**
* Dagger Module for classes found within the com.android.systemui.statusbar.notification package.
@@ -259,4 +267,22 @@
@ClassKey(VisualInterruptionDecisionProvider.class)
CoreStartable startVisualInterruptionDecisionProvider(
VisualInterruptionDecisionProvider provider);
+
+ @Provides
+ @SysUISingleton
+ public static NotificationsSoundPolicyRepository provideNotificationsSoundPolicyRepository(
+ Context context,
+ NotificationManager notificationManager,
+ @Application CoroutineScope coroutineScope,
+ @Background CoroutineContext coroutineContext) {
+ return new NotificationsSoundPolicyRepositoryImpl(context, notificationManager,
+ coroutineScope, coroutineContext);
+ }
+
+ @Provides
+ @SysUISingleton
+ public static NotificationsSoundPolicyInteractor provideNotificationsSoundPolicyInteractror(
+ NotificationsSoundPolicyRepository repository) {
+ return new NotificationsSoundPolicyInteractor(repository);
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/IconManager.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/IconManager.kt
index 76e5fd3..a5f42bb 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/IconManager.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/IconManager.kt
@@ -45,10 +45,12 @@
* icons and keeping the icon assets themselves up to date as notifications change.
*
* TODO: Much of this code was copied whole-sale in order to get it out of NotificationEntry.
- * Long-term, it should probably live somewhere in the content inflation pipeline.
+ * Long-term, it should probably live somewhere in the content inflation pipeline.
*/
@SysUISingleton
-class IconManager @Inject constructor(
+class IconManager
+@Inject
+constructor(
private val notifCollection: CommonNotifCollection,
private val launcherApps: LauncherApps,
private val iconBuilder: IconBuilder
@@ -59,30 +61,30 @@
notifCollection.addCollectionListener(entryListener)
}
- private val entryListener = object : NotifCollectionListener {
- override fun onEntryInit(entry: NotificationEntry) {
- entry.addOnSensitivityChangedListener(sensitivityListener)
+ private val entryListener =
+ object : NotifCollectionListener {
+ override fun onEntryInit(entry: NotificationEntry) {
+ entry.addOnSensitivityChangedListener(sensitivityListener)
+ }
+
+ override fun onEntryCleanUp(entry: NotificationEntry) {
+ entry.removeOnSensitivityChangedListener(sensitivityListener)
+ }
+
+ override fun onRankingApplied() {
+ // rankings affect whether a conversation is important, which can change the icons
+ recalculateForImportantConversationChange()
+ }
}
- override fun onEntryCleanUp(entry: NotificationEntry) {
- entry.removeOnSensitivityChangedListener(sensitivityListener)
- }
-
- override fun onRankingApplied() {
- // rankings affect whether a conversation is important, which can change the icons
- recalculateForImportantConversationChange()
- }
- }
-
- private val sensitivityListener = NotificationEntry.OnSensitivityChangedListener {
- entry -> updateIconsSafe(entry)
- }
+ private val sensitivityListener =
+ NotificationEntry.OnSensitivityChangedListener { entry -> updateIconsSafe(entry) }
private fun recalculateForImportantConversationChange() {
for (entry in notifCollection.allNotifs) {
val isImportant = isImportantConversation(entry)
- if (entry.icons.areIconsAvailable &&
- isImportant != entry.icons.isImportantConversation
+ if (
+ entry.icons.areIconsAvailable && isImportant != entry.icons.isImportantConversation
) {
updateIconsSafe(entry)
}
@@ -97,34 +99,35 @@
* @throws InflationException Exception if required icons are not valid or specified
*/
@Throws(InflationException::class)
- fun createIcons(entry: NotificationEntry) = traceSection("IconManager.createIcons") {
- // Construct the status bar icon view.
- val sbIcon = iconBuilder.createIconView(entry)
- sbIcon.scaleType = ImageView.ScaleType.CENTER_INSIDE
+ fun createIcons(entry: NotificationEntry) =
+ traceSection("IconManager.createIcons") {
+ // Construct the status bar icon view.
+ val sbIcon = iconBuilder.createIconView(entry)
+ sbIcon.scaleType = ImageView.ScaleType.CENTER_INSIDE
- // Construct the shelf icon view.
- val shelfIcon = iconBuilder.createIconView(entry)
- shelfIcon.scaleType = ImageView.ScaleType.CENTER_INSIDE
- shelfIcon.visibility = View.INVISIBLE
+ // Construct the shelf icon view.
+ val shelfIcon = iconBuilder.createIconView(entry)
+ shelfIcon.scaleType = ImageView.ScaleType.CENTER_INSIDE
+ shelfIcon.visibility = View.INVISIBLE
- // Construct the aod icon view.
- val aodIcon = iconBuilder.createIconView(entry)
- aodIcon.scaleType = ImageView.ScaleType.CENTER_INSIDE
- aodIcon.setIncreasedSize(true)
+ // Construct the aod icon view.
+ val aodIcon = iconBuilder.createIconView(entry)
+ aodIcon.scaleType = ImageView.ScaleType.CENTER_INSIDE
+ aodIcon.setIncreasedSize(true)
- // Set the icon views' icons
- val (normalIconDescriptor, sensitiveIconDescriptor) = getIconDescriptors(entry)
+ // Set the icon views' icons
+ val (normalIconDescriptor, sensitiveIconDescriptor) = getIconDescriptors(entry)
- try {
- setIcon(entry, normalIconDescriptor, sbIcon)
- setIcon(entry, sensitiveIconDescriptor, shelfIcon)
- setIcon(entry, sensitiveIconDescriptor, aodIcon)
- entry.icons = IconPack.buildPack(sbIcon, shelfIcon, aodIcon, entry.icons)
- } catch (e: InflationException) {
- entry.icons = IconPack.buildEmptyPack(entry.icons)
- throw e
+ try {
+ setIcon(entry, normalIconDescriptor, sbIcon)
+ setIcon(entry, sensitiveIconDescriptor, shelfIcon)
+ setIcon(entry, sensitiveIconDescriptor, aodIcon)
+ entry.icons = IconPack.buildPack(sbIcon, shelfIcon, aodIcon, entry.icons)
+ } catch (e: InflationException) {
+ entry.icons = IconPack.buildEmptyPack(entry.icons)
+ throw e
+ }
}
- }
/**
* Update the notification icons.
@@ -133,33 +136,33 @@
* @throws InflationException Exception if required icons are not valid or specified
*/
@Throws(InflationException::class)
- fun updateIcons(entry: NotificationEntry) = traceSection("IconManager.updateIcons") {
- if (!entry.icons.areIconsAvailable) {
- return@traceSection
- }
- entry.icons.smallIconDescriptor = null
- entry.icons.peopleAvatarDescriptor = null
+ fun updateIcons(entry: NotificationEntry) =
+ traceSection("IconManager.updateIcons") {
+ if (!entry.icons.areIconsAvailable) {
+ return@traceSection
+ }
+ entry.icons.smallIconDescriptor = null
+ entry.icons.peopleAvatarDescriptor = null
- val (normalIconDescriptor, sensitiveIconDescriptor) = getIconDescriptors(entry)
- val notificationContentDescription = entry.sbn.notification?.let {
- iconBuilder.getIconContentDescription(it)
- }
+ val (normalIconDescriptor, sensitiveIconDescriptor) = getIconDescriptors(entry)
+ val notificationContentDescription =
+ entry.sbn.notification?.let { iconBuilder.getIconContentDescription(it) }
- entry.icons.statusBarIcon?.let {
- it.setNotification(entry.sbn, notificationContentDescription)
- setIcon(entry, normalIconDescriptor, it)
- }
+ entry.icons.statusBarIcon?.let {
+ it.setNotification(entry.sbn, notificationContentDescription)
+ setIcon(entry, normalIconDescriptor, it)
+ }
- entry.icons.shelfIcon?.let {
- it.setNotification(entry.sbn, notificationContentDescription)
- setIcon(entry, normalIconDescriptor, it)
- }
+ entry.icons.shelfIcon?.let {
+ it.setNotification(entry.sbn, notificationContentDescription)
+ setIcon(entry, sensitiveIconDescriptor, it)
+ }
- entry.icons.aodIcon?.let {
- it.setNotification(entry.sbn, notificationContentDescription)
- setIcon(entry, sensitiveIconDescriptor, it)
+ entry.icons.aodIcon?.let {
+ it.setNotification(entry.sbn, notificationContentDescription)
+ setIcon(entry, sensitiveIconDescriptor, it)
+ }
}
- }
private fun updateIconsSafe(entry: NotificationEntry) {
try {
@@ -173,11 +176,12 @@
@Throws(InflationException::class)
private fun getIconDescriptors(entry: NotificationEntry): Pair<StatusBarIcon, StatusBarIcon> {
val iconDescriptor = getIconDescriptor(entry, redact = false)
- val sensitiveDescriptor = if (entry.isSensitive) {
- getIconDescriptor(entry, redact = true)
- } else {
- iconDescriptor
- }
+ val sensitiveDescriptor =
+ if (entry.isSensitive) {
+ getIconDescriptor(entry, redact = true)
+ } else {
+ iconDescriptor
+ }
return Pair(iconDescriptor, sensitiveDescriptor)
}
@@ -197,14 +201,15 @@
}
val icon =
- (if (showPeopleAvatar) {
- createPeopleAvatar(entry)
- } else {
- n.smallIcon
- }) ?: throw InflationException(
- "No icon in notification from " + entry.sbn.packageName)
+ (if (showPeopleAvatar) {
+ createPeopleAvatar(entry)
+ } else {
+ n.smallIcon
+ })
+ ?: throw InflationException("No icon in notification from " + entry.sbn.packageName)
- val ic = StatusBarIcon(
+ val ic =
+ StatusBarIcon(
entry.sbn.user,
entry.sbn.packageName,
icon,
@@ -282,8 +287,8 @@
/**
* Determines if this icon shows a conversation based on the sensitivity of the icon, its
- * context and the user's indicated sensitivity preference. If we're using a fall back icon
- * of the small icon, we don't consider this to be showing a conversation
+ * context and the user's indicated sensitivity preference. If we're using a fall back icon of
+ * the small icon, we don't consider this to be showing a conversation
*
* @param iconView The icon that shows the conversation.
*/
@@ -293,19 +298,20 @@
iconDescriptor: StatusBarIcon
): Boolean {
val usedInSensitiveContext =
- iconView === entry.icons.shelfIcon || iconView === entry.icons.aodIcon
+ iconView === entry.icons.shelfIcon || iconView === entry.icons.aodIcon
val isSmallIcon = iconDescriptor.icon.equals(entry.sbn.notification.smallIcon)
- return isImportantConversation(entry) && !isSmallIcon &&
- (!usedInSensitiveContext || !entry.isSensitive)
+ return isImportantConversation(entry) &&
+ !isSmallIcon &&
+ (!usedInSensitiveContext || !entry.isSensitive)
}
private fun isImportantConversation(entry: NotificationEntry): Boolean {
// Also verify that the Notification is MessagingStyle, since we're going to access
// MessagingStyle-specific data (EXTRA_MESSAGES, EXTRA_MESSAGING_PERSON).
return entry.ranking.channel != null &&
- entry.ranking.channel.isImportantConversation &&
- entry.sbn.notification.isStyle(MessagingStyle::class.java) &&
- entry.key !in unimportantConversationKeys
+ entry.ranking.channel.isImportantConversation &&
+ entry.sbn.notification.isStyle(MessagingStyle::class.java) &&
+ entry.key !in unimportantConversationKeys
}
override fun setUnimportantConversations(keys: Collection<String>) {
@@ -323,8 +329,8 @@
interface ConversationIconManager {
/**
* Sets the complete current set of notification keys which should (for the purposes of icon
- * presentation) be considered unimportant. This tells the icon manager to remove the avatar
- * of a group from which the priority notification has been removed.
+ * presentation) be considered unimportant. This tells the icon manager to remove the avatar of
+ * a group from which the priority notification has been removed.
*/
fun setUnimportantConversations(keys: Collection<String>)
-}
\ No newline at end of file
+}
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 d828ad7..decb244 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
@@ -849,6 +849,7 @@
public void setNotificationGroupWhen(long whenMillis) {
if (mIsSummaryWithChildren) {
mChildrenContainer.setNotificationGroupWhen(whenMillis);
+ mPublicLayout.setNotificationWhen(whenMillis);
} else {
Log.w(TAG, "setNotificationGroupWhen( whenMillis: " + whenMillis + ")"
+ " mIsSummaryWithChildren: false"
@@ -2704,6 +2705,7 @@
}
private void onAttachedChildrenCountChanged() {
+ final boolean wasSummary = mIsSummaryWithChildren;
mIsSummaryWithChildren = mChildrenContainer != null
&& mChildrenContainer.getNotificationChildCount() > 0;
if (mIsSummaryWithChildren) {
@@ -2714,6 +2716,10 @@
isConversation());
}
}
+ if (!mIsSummaryWithChildren && wasSummary) {
+ // Reset the 'when' once the row stops being a summary
+ mPublicLayout.setNotificationWhen(mEntry.getSbn().getNotification().when);
+ }
getShowingLayout().updateBackgroundColor(false /* animate */);
mPrivateLayout.updateExpandButtons(isExpandable());
updateChildrenAppearance();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentView.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentView.java
index 402ea51..3742482 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentView.java
@@ -59,6 +59,7 @@
import com.android.systemui.statusbar.notification.people.PeopleNotificationIdentifier;
import com.android.systemui.statusbar.notification.row.shared.AsyncHybridViewInflation;
import com.android.systemui.statusbar.notification.row.wrapper.NotificationCustomViewWrapper;
+import com.android.systemui.statusbar.notification.row.wrapper.NotificationHeaderViewWrapper;
import com.android.systemui.statusbar.notification.row.wrapper.NotificationViewWrapper;
import com.android.systemui.statusbar.policy.InflatedSmartReplyState;
import com.android.systemui.statusbar.policy.InflatedSmartReplyViewHolder;
@@ -2314,6 +2315,13 @@
return false;
}
+ public void setNotificationWhen(long whenMillis) {
+ NotificationViewWrapper wrapper = getNotificationViewWrapper();
+ if (wrapper instanceof NotificationHeaderViewWrapper headerViewWrapper) {
+ headerViewWrapper.setNotificationWhen(whenMillis);
+ }
+ }
+
private static class RemoteInputViewData {
@Nullable RemoteInputView mView;
@Nullable RemoteInputViewController mController;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/MediaContainerView.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/MediaContainerView.kt
index bae5baa..5551ab4 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/MediaContainerView.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/MediaContainerView.kt
@@ -22,6 +22,8 @@
import android.graphics.Path
import android.graphics.RectF
import android.util.AttributeSet
+import android.util.Log
+import com.android.systemui.Flags
import com.android.systemui.res.R
import com.android.systemui.statusbar.notification.row.ExpandableView
@@ -87,4 +89,63 @@
) {
// No animation, it doesn't need it, this would be local
}
+
+ override fun setVisibility(visibility: Int) {
+ if (Flags.bindKeyguardMediaVisibility()) {
+ if (isVisibilityValid(visibility)) {
+ super.setVisibility(visibility)
+ }
+ } else {
+ super.setVisibility(visibility)
+ }
+
+ assertMediaContainerVisibility(visibility)
+ }
+
+ /**
+ * visibility should be aligned with MediaContainerView visibility on the keyguard.
+ */
+ private fun isVisibilityValid(visibility: Int): Boolean {
+ val currentViewState = viewState as? MediaContainerViewState ?: return true
+ val shouldBeGone = !currentViewState.shouldBeVisible
+ return if (shouldBeGone) visibility == GONE else visibility != GONE
+ }
+
+ /**
+ * b/298213983
+ * MediaContainerView's visibility is changed to VISIBLE when it should be GONE.
+ * This method check this state and logs.
+ */
+ private fun assertMediaContainerVisibility(visibility: Int) {
+ val currentViewState = viewState
+
+ if (currentViewState is MediaContainerViewState) {
+ if (!currentViewState.shouldBeVisible && visibility == VISIBLE) {
+ Log.wtf("MediaContainerView", "MediaContainerView should be GONE " +
+ "but its visibility changed to VISIBLE")
+ }
+ }
+ }
+
+ fun setKeyguardVisibility(isVisible: Boolean) {
+ val currentViewState = viewState
+ if (currentViewState is MediaContainerViewState) {
+ currentViewState.shouldBeVisible = isVisible
+ }
+
+ visibility = if (isVisible) VISIBLE else GONE
+ }
+
+ override fun createExpandableViewState(): ExpandableViewState = MediaContainerViewState()
+
+ class MediaContainerViewState : ExpandableViewState() {
+ var shouldBeVisible: Boolean = false
+
+ override fun copyFrom(viewState: ViewState) {
+ super.copyFrom(viewState)
+ if (viewState is MediaContainerViewState) {
+ shouldBeVisible = viewState.shouldBeVisible
+ }
+ }
+ }
}
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 7925a1c..e397a70 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
@@ -573,7 +573,7 @@
* Do notifications dismiss with normal transitioning
*/
private boolean mDismissUsingRowTranslationX = true;
- private NotificationEntry mTopHeadsUpEntry;
+ private ExpandableNotificationRow mTopHeadsUpRow;
private long mNumHeadsUp;
private NotificationStackScrollLayoutController.TouchHandler mTouchHandler;
private final ScreenOffAnimationController mScreenOffAnimationController;
@@ -1688,10 +1688,10 @@
* is mainly used when dragging down from a heads up notification.
*/
private int getTopHeadsUpPinnedHeight() {
- if (mTopHeadsUpEntry == null) {
+ if (mTopHeadsUpRow == null) {
return 0;
}
- ExpandableNotificationRow row = mTopHeadsUpEntry.getRow();
+ ExpandableNotificationRow row = mTopHeadsUpRow;
if (row.isChildInGroup()) {
final NotificationEntry groupSummary =
mGroupMembershipManager.getGroupSummary(row.getEntry());
@@ -1872,8 +1872,9 @@
if (slidingChild instanceof ExpandableNotificationRow row) {
NotificationEntry entry = row.getEntry();
if (!mIsExpanded && row.isHeadsUp() && row.isPinned()
- && mTopHeadsUpEntry.getRow() != row
- && mGroupMembershipManager.getGroupSummary(mTopHeadsUpEntry) != entry) {
+ && mTopHeadsUpRow != row
+ && mGroupMembershipManager.getGroupSummary(mTopHeadsUpRow.getEntry())
+ != entry) {
continue;
}
return row.getViewAtPosition(touchY - childTop);
@@ -5724,8 +5725,8 @@
mShelf.updateAppearance();
}
- void setTopHeadsUpEntry(NotificationEntry topEntry) {
- mTopHeadsUpEntry = topEntry;
+ void setTopHeadsUpRow(ExpandableNotificationRow topHeadsUpRow) {
+ mTopHeadsUpRow = topHeadsUpRow;
}
void setNumHeadsUp(long numHeadsUp) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java
index 8dfac86..7c13877 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java
@@ -692,7 +692,7 @@
long numEntries = mHeadsUpManager.getAllEntries().count();
NotificationEntry topEntry = mHeadsUpManager.getTopEntry();
mView.setNumHeadsUp(numEntries);
- mView.setTopHeadsUpEntry(topEntry);
+ mView.setTopHeadsUpRow(topEntry != null ? topEntry.getRow() : null);
generateHeadsUpAnimation(entry, isHeadsUp);
}
};
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 634de7a..1ef9a8f 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
@@ -853,7 +853,7 @@
}
}
if (row.isHeadsUpAnimatingAway()) {
- if (NotificationsImprovedHunAnimation.isEnabled()) {
+ if (NotificationsImprovedHunAnimation.isEnabled() && !ambientState.isDozing()) {
if (shouldHunAppearFromBottom(ambientState, childState)) {
// move to the bottom of the screen
childState.setYTranslation(
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 b772158..db15144 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
@@ -1375,7 +1375,10 @@
|| !mKeyguardStateController.canDismissLockScreen()
|| mKeyguardViewMediator.isAnySimPinSecure()
|| (mQsController.getExpanded() && trackingTouch)
- || mShadeSurface.getBarState() == StatusBarState.SHADE_LOCKED) {
+ || mShadeSurface.getBarState() == StatusBarState.SHADE_LOCKED
+ // This last one causes a race condition when the shade resets. Don't send a 0
+ // and let StatusBarStateController process a keyguard state change instead
+ || 1f - fraction == 0f) {
return;
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarter.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarter.java
index 4fd33ba..5610ed9 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarter.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarter.java
@@ -55,6 +55,7 @@
import com.android.systemui.animation.ActivityTransitionAnimator;
import com.android.systemui.assist.AssistManager;
import com.android.systemui.dagger.SysUISingleton;
+import com.android.systemui.dagger.qualifiers.Background;
import com.android.systemui.dagger.qualifiers.DisplayId;
import com.android.systemui.plugins.ActivityStarter;
import com.android.systemui.power.domain.interactor.PowerInteractor;
@@ -138,7 +139,7 @@
Context context,
@DisplayId int displayId,
Handler mainThreadHandler,
- Executor uiBgExecutor,
+ @Background Executor uiBgExecutor,
NotificationVisibilityProvider visibilityProvider,
HeadsUpManager headsUpManager,
ActivityStarter activityStarter,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/satellite/data/prod/DeviceBasedSatelliteRepositoryImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/satellite/data/prod/DeviceBasedSatelliteRepositoryImpl.kt
index f73d089..3e3ea85 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/satellite/data/prod/DeviceBasedSatelliteRepositoryImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/satellite/data/prod/DeviceBasedSatelliteRepositoryImpl.kt
@@ -315,8 +315,8 @@
// TTL for satellite polling is one hour
const val POLLING_INTERVAL_MS: Long = 1000 * 60 * 60
- // Let the system boot up (5s) and stabilize before we check for system support
- const val MIN_UPTIME: Long = 1000 * 5
+ // Let the system boot up and stabilize before we check for system support
+ const val MIN_UPTIME: Long = 1000 * 60
private const val TAG = "DeviceBasedSatelliteRepo"
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BatteryStateNotifier.kt b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BatteryStateNotifier.kt
index a078dd5..2ad4d36 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BatteryStateNotifier.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BatteryStateNotifier.kt
@@ -23,6 +23,7 @@
import android.content.Context
import android.content.Intent
import android.net.Uri
+import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.res.R
import com.android.systemui.util.concurrency.DelayableExecutor
import javax.inject.Inject
@@ -34,7 +35,7 @@
class BatteryStateNotifier @Inject constructor(
val controller: BatteryController,
val noMan: NotificationManager,
- val delayableExecutor: DelayableExecutor,
+ @Background val delayableExecutor: DelayableExecutor,
val context: Context
) : BatteryController.BatteryStateChangeCallback {
var stateUnknown = false
diff --git a/packages/SystemUI/src/com/android/systemui/util/concurrency/SysUIConcurrencyModule.java b/packages/SystemUI/src/com/android/systemui/util/concurrency/SysUIConcurrencyModule.java
index 6124f63..2cad844 100644
--- a/packages/SystemUI/src/com/android/systemui/util/concurrency/SysUIConcurrencyModule.java
+++ b/packages/SystemUI/src/com/android/systemui/util/concurrency/SysUIConcurrencyModule.java
@@ -124,15 +124,6 @@
}
/**
- * Provide a Background-Thread Executor by default.
- */
- @Provides
- @SysUISingleton
- public static Executor provideExecutor(@Background Looper looper) {
- return new ExecutorImpl(looper);
- }
-
- /**
* Provide a BroadcastRunning Executor (for sending and receiving broadcasts).
*/
@Provides
@@ -174,15 +165,6 @@
}
/**
- * Provide a Background-Thread Executor by default.
- */
- @Provides
- @SysUISingleton
- public static DelayableExecutor provideDelayableExecutor(@Background Looper looper) {
- return new ExecutorImpl(looper);
- }
-
- /**
* Provide a Background-Thread Executor.
*/
@Provides
@@ -193,15 +175,6 @@
}
/**
- * Provide a Background-Thread Executor by default.
- */
- @Provides
- @SysUISingleton
- public static RepeatableExecutor provideRepeatableExecutor(@Background DelayableExecutor exec) {
- return new RepeatableExecutorImpl(exec);
- }
-
- /**
* Provide a Background-Thread Executor.
*/
@Provides
diff --git a/packages/SystemUI/src/com/android/systemui/util/service/PersistentConnectionManager.java b/packages/SystemUI/src/com/android/systemui/util/service/PersistentConnectionManager.java
index 9b72eb7..5979f3e 100644
--- a/packages/SystemUI/src/com/android/systemui/util/service/PersistentConnectionManager.java
+++ b/packages/SystemUI/src/com/android/systemui/util/service/PersistentConnectionManager.java
@@ -28,6 +28,7 @@
import androidx.annotation.NonNull;
import com.android.systemui.Dumpable;
+import com.android.systemui.dagger.qualifiers.Background;
import com.android.systemui.dump.DumpManager;
import com.android.systemui.util.concurrency.DelayableExecutor;
import com.android.systemui.util.time.SystemClock;
@@ -94,10 +95,12 @@
}
};
+ // TODO: b/326449074 - Ensure the DelayableExecutor is on the correct thread, and update the
+ // qualifier (to @Main) or name (to bgExecutor) to be consistent with that.
@Inject
public PersistentConnectionManager(
SystemClock clock,
- DelayableExecutor mainExecutor,
+ @Background DelayableExecutor mainExecutor,
DumpManager dumpManager,
@Named(DUMPSYS_NAME) String dumpsysName,
@Named(SERVICE_CONNECTION) ObservableServiceConnection<T> serviceConnection,
diff --git a/packages/SystemUI/src/com/android/systemui/volume/dagger/AudioModule.kt b/packages/SystemUI/src/com/android/systemui/volume/dagger/AudioModule.kt
index f6fd519..8431fbc 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/dagger/AudioModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/dagger/AudioModule.kt
@@ -16,17 +16,16 @@
package com.android.systemui.volume.dagger
-import android.app.NotificationManager
import android.content.Context
import android.media.AudioManager
import com.android.settingslib.media.data.repository.SpatializerRepository
import com.android.settingslib.media.data.repository.SpatializerRepositoryImpl
import com.android.settingslib.media.domain.interactor.SpatializerInteractor
-import com.android.settingslib.statusbar.notification.data.repository.NotificationsSoundPolicyRepository
-import com.android.settingslib.statusbar.notification.data.repository.NotificationsSoundPolicyRepositoryImpl
+import com.android.settingslib.statusbar.notification.domain.interactor.NotificationsSoundPolicyInteractor
import com.android.settingslib.volume.data.repository.AudioRepository
import com.android.settingslib.volume.data.repository.AudioRepositoryImpl
import com.android.settingslib.volume.domain.interactor.AudioModeInteractor
+import com.android.settingslib.volume.domain.interactor.AudioVolumeInteractor
import com.android.settingslib.volume.shared.AudioManagerEventsReceiver
import com.android.settingslib.volume.shared.AudioManagerEventsReceiverImpl
import com.android.systemui.dagger.qualifiers.Application
@@ -62,6 +61,13 @@
AudioModeInteractor(repository)
@Provides
+ fun provideAudioVolumeInteractor(
+ audioRepository: AudioRepository,
+ notificationsSoundPolicyInteractor: NotificationsSoundPolicyInteractor,
+ ): AudioVolumeInteractor =
+ AudioVolumeInteractor(audioRepository, notificationsSoundPolicyInteractor)
+
+ @Provides
fun provdieSpatializerRepository(
audioManager: AudioManager,
@Background backgroundContext: CoroutineContext,
@@ -71,19 +77,5 @@
@Provides
fun provideSpatializerInetractor(repository: SpatializerRepository): SpatializerInteractor =
SpatializerInteractor(repository)
-
- @Provides
- fun provideNotificationsSoundPolicyRepository(
- context: Context,
- notificationManager: NotificationManager,
- @Background coroutineContext: CoroutineContext,
- @Application coroutineScope: CoroutineScope,
- ): NotificationsSoundPolicyRepository =
- NotificationsSoundPolicyRepositoryImpl(
- context,
- notificationManager,
- coroutineScope,
- coroutineContext,
- )
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/volume/dagger/MediaDevicesModule.kt b/packages/SystemUI/src/com/android/systemui/volume/dagger/MediaDevicesModule.kt
index bf9963d..d134e60 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/dagger/MediaDevicesModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/dagger/MediaDevicesModule.kt
@@ -18,8 +18,10 @@
import android.media.session.MediaSessionManager
import com.android.settingslib.bluetooth.LocalBluetoothManager
+import com.android.settingslib.volume.data.repository.LocalMediaRepository
import com.android.settingslib.volume.data.repository.MediaControllerRepository
import com.android.settingslib.volume.data.repository.MediaControllerRepositoryImpl
+import com.android.settingslib.volume.domain.interactor.LocalMediaInteractor
import com.android.settingslib.volume.shared.AudioManagerEventsReceiver
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
@@ -44,6 +46,19 @@
@Provides
@SysUISingleton
+ fun provideLocalMediaRepository(
+ factory: LocalMediaRepositoryFactory
+ ): LocalMediaRepository = factory.create(null)
+
+ @Provides
+ @SysUISingleton
+ fun provideLocalMediaInteractor(
+ repository: LocalMediaRepository,
+ @Application scope: CoroutineScope,
+ ): LocalMediaInteractor = LocalMediaInteractor(repository, scope)
+
+ @Provides
+ @SysUISingleton
fun provideMediaDeviceSessionRepository(
intentsReceiver: AudioManagerEventsReceiver,
mediaSessionManager: MediaSessionManager,
diff --git a/packages/SystemUI/src/com/android/systemui/volume/panel/component/shared/model/VolumePanelComponents.kt b/packages/SystemUI/src/com/android/systemui/volume/panel/component/shared/model/VolumePanelComponents.kt
index f11ac5e..9d801fc 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/panel/component/shared/model/VolumePanelComponents.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/panel/component/shared/model/VolumePanelComponents.kt
@@ -22,6 +22,7 @@
const val MEDIA_OUTPUT: VolumePanelComponentKey = "media_output"
const val BOTTOM_BAR: VolumePanelComponentKey = "bottom_bar"
+ const val VOLUME_SLIDERS: VolumePanelComponentKey = "volume_sliders"
const val CAPTIONING: VolumePanelComponentKey = "captioning"
const val ANC: VolumePanelComponentKey = "anc"
}
diff --git a/packages/SystemUI/src/com/android/systemui/volume/panel/component/volume/domain/interactor/CastVolumeInteractor.kt b/packages/SystemUI/src/com/android/systemui/volume/panel/component/volume/domain/interactor/CastVolumeInteractor.kt
new file mode 100644
index 0000000..6b62074
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/volume/panel/component/volume/domain/interactor/CastVolumeInteractor.kt
@@ -0,0 +1,48 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.volume.panel.component.volume.domain.interactor
+
+import com.android.settingslib.volume.domain.interactor.LocalMediaInteractor
+import com.android.settingslib.volume.domain.model.RoutingSession
+import com.android.systemui.volume.panel.dagger.scope.VolumePanelScope
+import javax.inject.Inject
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.flow.SharingStarted
+import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.stateIn
+
+/** Provides a remote media casting state. */
+@VolumePanelScope
+class CastVolumeInteractor
+@Inject
+constructor(
+ @VolumePanelScope private val coroutineScope: CoroutineScope,
+ private val localMediaInteractor: LocalMediaInteractor,
+) {
+
+ /** Returns a list of [RoutingSession] to show in the UI. */
+ val remoteRoutingSessions: StateFlow<List<RoutingSession>> =
+ localMediaInteractor.remoteRoutingSessions
+ .map { it.filter { routingSession -> routingSession.isVolumeSeekBarEnabled } }
+ .stateIn(coroutineScope, SharingStarted.Eagerly, emptyList())
+
+ /** Sets [routingSession] volume to [volume]. */
+ suspend fun setVolume(routingSession: RoutingSession, volume: Int) {
+ localMediaInteractor.adjustSessionVolume(routingSession.routingSessionInfo.id, volume)
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/volume/panel/component/volume/domain/interactor/VolumeSliderInteractor.kt b/packages/SystemUI/src/com/android/systemui/volume/panel/component/volume/domain/interactor/VolumeSliderInteractor.kt
new file mode 100644
index 0000000..0c91bbf
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/volume/panel/component/volume/domain/interactor/VolumeSliderInteractor.kt
@@ -0,0 +1,98 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.volume.panel.component.volume.domain.interactor
+
+import com.android.systemui.volume.panel.dagger.scope.VolumePanelScope
+import javax.inject.Inject
+
+/** Converts from slider value to volume and back. */
+@VolumePanelScope
+class VolumeSliderInteractor @Inject constructor() {
+
+ /** mimic percentage volume setting */
+ val displayValueRange: ClosedFloatingPointRange<Float> = 0f..100f
+
+ /**
+ * Translates [volume], that belongs to [volumeRange] to the value that belongs to
+ * [displayValueRange].
+ *
+ * [currentValue] is the raw value received from the slider. Returns [currentValue] when it
+ * translates to the same volume as [volume] parameter. This ensures smooth slider experience
+ * (avoids snapping when the user stops dragging).
+ */
+ fun processVolumeToValue(
+ volume: Int,
+ volumeRange: ClosedRange<Int>,
+ currentValue: Float?,
+ isMuted: Boolean,
+ ): Float {
+ if (isMuted) {
+ return 0f
+ }
+ val changedVolume: Int? = currentValue?.let { translateValueToVolume(it, volumeRange) }
+ return if (volume != volumeRange.start && volume == changedVolume) {
+ currentValue
+ } else {
+ translateToRange(
+ currentValue = volume.toFloat(),
+ currentRangeStart = volumeRange.start.toFloat(),
+ currentRangeEnd = volumeRange.endInclusive.toFloat(),
+ targetRangeStart = displayValueRange.start,
+ targetRangeEnd = displayValueRange.endInclusive,
+ )
+ }
+ }
+
+ /** Translates [value] from [displayValueRange] to volume that has [volumeRange]. */
+ fun translateValueToVolume(
+ value: Float,
+ volumeRange: ClosedRange<Int>,
+ ): Int {
+ return translateToRange(
+ currentValue = value,
+ currentRangeStart = displayValueRange.start,
+ currentRangeEnd = displayValueRange.endInclusive,
+ targetRangeStart = volumeRange.start.toFloat(),
+ targetRangeEnd = volumeRange.endInclusive.toFloat(),
+ )
+ .toInt()
+ }
+
+ /**
+ * Translates a value from one range to another.
+ *
+ * ```
+ * Given: currentValue=3, currentRange=[0, 8], targetRange=[0, 100]
+ * Result: 37.5
+ * ```
+ */
+ private fun translateToRange(
+ currentValue: Float,
+ currentRangeStart: Float,
+ currentRangeEnd: Float,
+ targetRangeStart: Float,
+ targetRangeEnd: Float,
+ ): Float {
+ val currentRangeLength: Float = (currentRangeEnd - currentRangeStart)
+ val targetRangeLength: Float = targetRangeEnd - targetRangeStart
+ if (currentRangeLength == 0f || targetRangeLength == 0f) {
+ return 0f
+ }
+ val volumeFraction: Float = (currentValue - currentRangeStart) / currentRangeLength
+ return targetRangeStart + volumeFraction * targetRangeLength
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/volume/panel/component/volume/domain/model/SliderType.kt b/packages/SystemUI/src/com/android/systemui/volume/panel/component/volume/domain/model/SliderType.kt
new file mode 100644
index 0000000..b97123b
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/volume/panel/component/volume/domain/model/SliderType.kt
@@ -0,0 +1,29 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.volume.panel.component.volume.domain.model
+
+import com.android.settingslib.volume.shared.model.AudioStream
+
+/** The type of volume slider that can be shown at the UI. */
+sealed interface SliderType {
+
+ /** The slider represents one of the device volume streams. */
+ data class Stream(val stream: AudioStream) : SliderType
+
+ /** The represents media device casting volume. */
+ data object MediaDeviceCast : SliderType
+}
diff --git a/packages/SystemUI/src/com/android/systemui/volume/panel/component/volume/slider/ui/viewmodel/AudioStreamSliderViewModel.kt b/packages/SystemUI/src/com/android/systemui/volume/panel/component/volume/slider/ui/viewmodel/AudioStreamSliderViewModel.kt
new file mode 100644
index 0000000..faf7434
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/volume/panel/component/volume/slider/ui/viewmodel/AudioStreamSliderViewModel.kt
@@ -0,0 +1,170 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.volume.panel.component.volume.slider.ui.viewmodel
+
+import android.content.Context
+import android.media.AudioManager
+import com.android.settingslib.volume.domain.interactor.AudioVolumeInteractor
+import com.android.settingslib.volume.shared.model.AudioStream
+import com.android.settingslib.volume.shared.model.AudioStreamModel
+import com.android.systemui.common.shared.model.Icon
+import com.android.systemui.res.R
+import com.android.systemui.volume.panel.component.volume.domain.interactor.VolumeSliderInteractor
+import dagger.assisted.Assisted
+import dagger.assisted.AssistedFactory
+import dagger.assisted.AssistedInject
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.flow.SharingStarted
+import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.flow.stateIn
+import kotlinx.coroutines.launch
+
+/** Models a particular slider state. */
+class AudioStreamSliderViewModel
+@AssistedInject
+constructor(
+ @Assisted private val audioStreamWrapper: FactoryAudioStreamWrapper,
+ @Assisted private val coroutineScope: CoroutineScope,
+ private val context: Context,
+ private val audioVolumeInteractor: AudioVolumeInteractor,
+ private val volumeSliderInteractor: VolumeSliderInteractor,
+) : SliderViewModel {
+
+ private val audioStream = audioStreamWrapper.audioStream
+ private val iconsByStream =
+ mapOf(
+ AudioStream(AudioManager.STREAM_MUSIC) to R.drawable.ic_music_note,
+ AudioStream(AudioManager.STREAM_VOICE_CALL) to R.drawable.ic_call,
+ AudioStream(AudioManager.STREAM_RING) to R.drawable.ic_ring_volume,
+ AudioStream(AudioManager.STREAM_NOTIFICATION) to R.drawable.ic_volume_ringer,
+ AudioStream(AudioManager.STREAM_ALARM) to R.drawable.ic_volume_alarm,
+ )
+ private val mutedIconsByStream =
+ mapOf(
+ AudioStream(AudioManager.STREAM_MUSIC) to R.drawable.ic_volume_off,
+ AudioStream(AudioManager.STREAM_VOICE_CALL) to R.drawable.ic_volume_off,
+ AudioStream(AudioManager.STREAM_RING) to R.drawable.ic_volume_off,
+ AudioStream(AudioManager.STREAM_NOTIFICATION) to R.drawable.ic_volume_off,
+ AudioStream(AudioManager.STREAM_ALARM) to R.drawable.ic_volume_off,
+ )
+ private val labelsByStream =
+ mapOf(
+ AudioStream(AudioManager.STREAM_MUSIC) to R.string.stream_music,
+ AudioStream(AudioManager.STREAM_VOICE_CALL) to R.string.stream_voice_call,
+ AudioStream(AudioManager.STREAM_RING) to R.string.stream_ring,
+ AudioStream(AudioManager.STREAM_NOTIFICATION) to R.string.stream_notification,
+ AudioStream(AudioManager.STREAM_ALARM) to R.string.stream_alarm,
+ )
+ private val disabledTextByStream =
+ mapOf(
+ AudioStream(AudioManager.STREAM_NOTIFICATION) to
+ R.string.stream_notification_unavailable,
+ )
+
+ private var value = 0f
+ override val slider: StateFlow<SliderState> =
+ combine(
+ audioVolumeInteractor.getAudioStream(audioStream),
+ audioVolumeInteractor.canChangeVolume(audioStream),
+ ) { model, isEnabled ->
+ model.toState(value, isEnabled)
+ }
+ .stateIn(coroutineScope, SharingStarted.Eagerly, EmptyState)
+
+ override fun onValueChangeFinished(state: SliderState, newValue: Float) {
+ val audioViewModel = state as? State
+ audioViewModel ?: return
+ coroutineScope.launch {
+ value = newValue
+ val volume =
+ volumeSliderInteractor.translateValueToVolume(
+ newValue,
+ audioViewModel.audioStreamModel.volumeRange
+ )
+ audioVolumeInteractor.setVolume(audioStream, volume)
+ }
+ }
+
+ private fun AudioStreamModel.toState(value: Float, isEnabled: Boolean): State {
+ return State(
+ value =
+ volumeSliderInteractor.processVolumeToValue(
+ volume,
+ volumeRange,
+ value,
+ isMuted,
+ ),
+ valueRange = volumeSliderInteractor.displayValueRange,
+ icon = getIcon(this),
+ label = labelsByStream[audioStream]?.let(context::getString)
+ ?: error("No label for the stream: $audioStream"),
+ disabledMessage = disabledTextByStream[audioStream]?.let(context::getString),
+ isEnabled = isEnabled,
+ audioStreamModel = this,
+ )
+ }
+
+ private fun getIcon(model: AudioStreamModel): Icon {
+ val isMutedOrNoVolume = model.isMuted || model.volume == model.minVolume
+ val iconRes =
+ if (isMutedOrNoVolume) {
+ mutedIconsByStream
+ } else {
+ iconsByStream
+ }[audioStream]
+ ?: error("No icon for the stream: $audioStream")
+ return Icon.Resource(iconRes, null)
+ }
+
+ private val AudioStreamModel.volumeRange: IntRange
+ get() = minVolume..maxVolume
+
+ private data class State(
+ override val value: Float,
+ override val valueRange: ClosedFloatingPointRange<Float>,
+ override val icon: Icon,
+ override val label: String,
+ override val disabledMessage: String?,
+ override val isEnabled: Boolean,
+ val audioStreamModel: AudioStreamModel,
+ ) : SliderState
+
+ private data object EmptyState : SliderState {
+ override val value: Float = 0f
+ override val valueRange: ClosedFloatingPointRange<Float> = 0f..1f
+ override val icon: Icon? = null
+ override val label: String = ""
+ override val disabledMessage: String? = null
+ override val isEnabled: Boolean = true
+ }
+
+ @AssistedFactory
+ interface Factory {
+
+ fun create(
+ audioStream: FactoryAudioStreamWrapper,
+ coroutineScope: CoroutineScope,
+ ): AudioStreamSliderViewModel
+ }
+
+ /**
+ * AudioStream is a value class that compiles into a primitive. This fail AssistedFactory build
+ * when using [AudioStream] directly because it expects another type.
+ */
+ class FactoryAudioStreamWrapper(val audioStream: AudioStream)
+}
diff --git a/packages/SystemUI/src/com/android/systemui/volume/panel/component/volume/slider/ui/viewmodel/CastVolumeSliderViewModel.kt b/packages/SystemUI/src/com/android/systemui/volume/panel/component/volume/slider/ui/viewmodel/CastVolumeSliderViewModel.kt
new file mode 100644
index 0000000..ae93826
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/volume/panel/component/volume/slider/ui/viewmodel/CastVolumeSliderViewModel.kt
@@ -0,0 +1,102 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.volume.panel.component.volume.slider.ui.viewmodel
+
+import android.content.Context
+import com.android.settingslib.volume.domain.model.RoutingSession
+import com.android.systemui.common.shared.model.Icon
+import com.android.systemui.res.R
+import com.android.systemui.volume.panel.component.mediaoutput.domain.interactor.MediaOutputInteractor
+import com.android.systemui.volume.panel.component.volume.domain.interactor.CastVolumeInteractor
+import com.android.systemui.volume.panel.component.volume.domain.interactor.VolumeSliderInteractor
+import dagger.assisted.Assisted
+import dagger.assisted.AssistedFactory
+import dagger.assisted.AssistedInject
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.SharingStarted
+import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.flow.stateIn
+import kotlinx.coroutines.launch
+
+class CastVolumeSliderViewModel
+@AssistedInject
+constructor(
+ @Assisted private val routingSession: RoutingSession,
+ @Assisted private val coroutineScope: CoroutineScope,
+ private val context: Context,
+ mediaOutputInteractor: MediaOutputInteractor,
+ private val volumeSliderInteractor: VolumeSliderInteractor,
+ private val castVolumeInteractor: CastVolumeInteractor,
+) : SliderViewModel {
+
+ private val volumeRange = 0..routingSession.routingSessionInfo.volumeMax
+ private val value = MutableStateFlow(0f)
+
+ override val slider: StateFlow<SliderState> =
+ combine(value, mediaOutputInteractor.currentConnectedDevice) { value, _ ->
+ getCurrentState(value)
+ }
+ .stateIn(coroutineScope, SharingStarted.Eagerly, getCurrentState(value.value))
+
+ override fun onValueChangeFinished(state: SliderState, newValue: Float) {
+ coroutineScope.launch {
+ value.value = newValue
+ castVolumeInteractor.setVolume(
+ routingSession,
+ volumeSliderInteractor.translateValueToVolume(newValue, volumeRange),
+ )
+ }
+ }
+
+ private fun getCurrentState(value: Float): State {
+ return State(
+ value =
+ volumeSliderInteractor.processVolumeToValue(
+ volume = routingSession.routingSessionInfo.volume,
+ volumeRange = volumeRange,
+ currentValue = value,
+ isMuted = false,
+ ),
+ valueRange = volumeSliderInteractor.displayValueRange,
+ icon = Icon.Resource(R.drawable.ic_cast, null),
+ label = context.getString(R.string.media_device_cast),
+ isEnabled = true,
+ )
+ }
+
+ private data class State(
+ override val value: Float,
+ override val valueRange: ClosedFloatingPointRange<Float>,
+ override val icon: Icon,
+ override val label: String,
+ override val isEnabled: Boolean,
+ ) : SliderState {
+ override val disabledMessage: String?
+ get() = null
+ }
+
+ @AssistedFactory
+ interface Factory {
+
+ fun create(
+ routingSession: RoutingSession,
+ coroutineScope: CoroutineScope,
+ ): CastVolumeSliderViewModel
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/volume/panel/component/volume/slider/ui/viewmodel/SliderState.kt b/packages/SystemUI/src/com/android/systemui/volume/panel/component/volume/slider/ui/viewmodel/SliderState.kt
new file mode 100644
index 0000000..6e9794b
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/volume/panel/component/volume/slider/ui/viewmodel/SliderState.kt
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.volume.panel.component.volume.slider.ui.viewmodel
+
+import com.android.systemui.common.shared.model.Icon
+
+/**
+ * Models a state of a volume slider.
+ *
+ * @property disabledMessage is shown when [isEnabled] is false
+ */
+sealed interface SliderState {
+ val value: Float
+ val valueRange: ClosedFloatingPointRange<Float>
+ val icon: Icon?
+ val label: String
+ val disabledMessage: String?
+ val isEnabled: Boolean
+}
diff --git a/packages/SystemUI/src/com/android/systemui/volume/panel/component/volume/slider/ui/viewmodel/SliderViewModel.kt b/packages/SystemUI/src/com/android/systemui/volume/panel/component/volume/slider/ui/viewmodel/SliderViewModel.kt
new file mode 100644
index 0000000..0c4b322
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/volume/panel/component/volume/slider/ui/viewmodel/SliderViewModel.kt
@@ -0,0 +1,27 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.volume.panel.component.volume.slider.ui.viewmodel
+
+import kotlinx.coroutines.flow.StateFlow
+
+/** Controls the behaviour of a volume slider. */
+interface SliderViewModel {
+
+ val slider: StateFlow<SliderState>
+
+ fun onValueChangeFinished(state: SliderState, newValue: Float)
+}
diff --git a/packages/SystemUI/src/com/android/systemui/volume/panel/component/volume/ui/viewmodel/AudioVolumeComponentViewModel.kt b/packages/SystemUI/src/com/android/systemui/volume/panel/component/volume/ui/viewmodel/AudioVolumeComponentViewModel.kt
new file mode 100644
index 0000000..2824323
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/volume/panel/component/volume/ui/viewmodel/AudioVolumeComponentViewModel.kt
@@ -0,0 +1,93 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.volume.panel.component.volume.ui.viewmodel
+
+import android.media.AudioManager
+import com.android.settingslib.volume.shared.model.AudioStream
+import com.android.systemui.volume.panel.component.volume.domain.interactor.CastVolumeInteractor
+import com.android.systemui.volume.panel.component.volume.slider.ui.viewmodel.AudioStreamSliderViewModel
+import com.android.systemui.volume.panel.component.volume.slider.ui.viewmodel.CastVolumeSliderViewModel
+import com.android.systemui.volume.panel.component.volume.slider.ui.viewmodel.SliderViewModel
+import com.android.systemui.volume.panel.dagger.scope.VolumePanelScope
+import javax.inject.Inject
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.coroutineScope
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.SharingStarted
+import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.flow.flowOf
+import kotlinx.coroutines.flow.stateIn
+import kotlinx.coroutines.flow.transformLatest
+
+/**
+ * Controls the behaviour of the whole audio
+ * [com.android.systemui.volume.panel.shared.model.VolumePanelUiComponent].
+ */
+@OptIn(ExperimentalCoroutinesApi::class)
+@VolumePanelScope
+class AudioVolumeComponentViewModel
+@Inject
+constructor(
+ @VolumePanelScope private val scope: CoroutineScope,
+ castVolumeInteractor: CastVolumeInteractor,
+ private val streamSliderViewModelFactory: AudioStreamSliderViewModel.Factory,
+ private val castVolumeSliderViewModelFactory: CastVolumeSliderViewModel.Factory,
+) {
+
+ private val remoteSessionsViewModels: Flow<List<SliderViewModel>> =
+ castVolumeInteractor.remoteRoutingSessions.transformLatest { routingSessions ->
+ coroutineScope {
+ emit(
+ routingSessions.map { routingSession ->
+ castVolumeSliderViewModelFactory.create(routingSession, this)
+ }
+ )
+ }
+ }
+ private val streamViewModels: Flow<List<SliderViewModel>> =
+ flowOf(
+ listOf(
+ AudioStream(AudioManager.STREAM_MUSIC),
+ AudioStream(AudioManager.STREAM_VOICE_CALL),
+ AudioStream(AudioManager.STREAM_RING),
+ AudioStream(AudioManager.STREAM_NOTIFICATION),
+ AudioStream(AudioManager.STREAM_ALARM),
+ )
+ )
+ .transformLatest { streams ->
+ coroutineScope {
+ emit(
+ streams.map { stream ->
+ streamSliderViewModelFactory.create(
+ AudioStreamSliderViewModel.FactoryAudioStreamWrapper(stream),
+ this,
+ )
+ }
+ )
+ }
+ }
+
+ val sliderViewModels: StateFlow<List<SliderViewModel>> =
+ combine(remoteSessionsViewModels, streamViewModels) {
+ remoteSessionsViewModels,
+ streamViewModels ->
+ remoteSessionsViewModels + streamViewModels
+ }
+ .stateIn(scope, SharingStarted.Eagerly, emptyList())
+}
diff --git a/packages/SystemUI/src/com/android/systemui/volume/panel/dagger/VolumePanelComponent.kt b/packages/SystemUI/src/com/android/systemui/volume/panel/dagger/VolumePanelComponent.kt
index df4972a..f31ee86 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/panel/dagger/VolumePanelComponent.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/panel/dagger/VolumePanelComponent.kt
@@ -20,6 +20,7 @@
import com.android.systemui.volume.panel.component.bottombar.BottomBarModule
import com.android.systemui.volume.panel.component.captioning.CaptioningModule
import com.android.systemui.volume.panel.component.mediaoutput.MediaOutputModule
+import com.android.systemui.volume.panel.component.volume.VolumeSlidersModule
import com.android.systemui.volume.panel.dagger.factory.VolumePanelComponentFactory
import com.android.systemui.volume.panel.dagger.scope.VolumePanelScope
import com.android.systemui.volume.panel.domain.DomainModule
@@ -48,6 +49,7 @@
// Components modules
BottomBarModule::class,
AncModule::class,
+ VolumeSlidersModule::class,
CaptioningModule::class,
MediaOutputModule::class,
]
diff --git a/packages/SystemUI/src/com/android/systemui/volume/panel/domain/DomainModule.kt b/packages/SystemUI/src/com/android/systemui/volume/panel/domain/DomainModule.kt
index 0d65c42..57ea997 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/panel/domain/DomainModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/panel/domain/DomainModule.kt
@@ -52,6 +52,7 @@
return setOf(
VolumePanelComponents.ANC,
VolumePanelComponents.CAPTIONING,
+ VolumePanelComponents.VOLUME_SLIDERS,
VolumePanelComponents.MEDIA_OUTPUT,
VolumePanelComponents.BOTTOM_BAR,
)
diff --git a/packages/SystemUI/src/com/android/systemui/volume/panel/ui/viewmodel/VolumePanelState.kt b/packages/SystemUI/src/com/android/systemui/volume/panel/ui/viewmodel/VolumePanelState.kt
index 7f33a6b..f57e293 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/panel/ui/viewmodel/VolumePanelState.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/panel/ui/viewmodel/VolumePanelState.kt
@@ -23,12 +23,12 @@
* State of the Volume Panel itself.
*
* @property orientation is current Volume Panel orientation
- * @property isWideScreen is true when Volume Panel should use wide-screen layout and false the
+ * @property isLargeScreen is true when Volume Panel should use wide-screen layout and false the
* otherwise
*/
data class VolumePanelState(
@Orientation val orientation: Int,
- val isWideScreen: Boolean,
+ val isLargeScreen: Boolean,
val isVisible: Boolean,
) {
init {
diff --git a/packages/SystemUI/src/com/android/systemui/volume/panel/ui/viewmodel/VolumePanelViewModel.kt b/packages/SystemUI/src/com/android/systemui/volume/panel/ui/viewmodel/VolumePanelViewModel.kt
index 3c5b75c..5ae827f 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/panel/ui/viewmodel/VolumePanelViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/panel/ui/viewmodel/VolumePanelViewModel.kt
@@ -76,7 +76,7 @@
VolumePanelState(
orientation = configuration.orientation,
isVisible = isVisible,
- isWideScreen = !resources.getBoolean(R.bool.config_edgeToEdgeBottomSheetDialog),
+ isLargeScreen = resources.getBoolean(R.bool.volume_panel_is_large_screen),
)
}
.stateIn(
@@ -85,7 +85,7 @@
VolumePanelState(
orientation = resources.configuration.orientation,
isVisible = mutablePanelVisibility.value,
- isWideScreen = !resources.getBoolean(R.bool.config_edgeToEdgeBottomSheetDialog)
+ isLargeScreen = resources.getBoolean(R.bool.volume_panel_is_large_screen)
),
)
val componentsLayout: Flow<ComponentsLayout> =
diff --git a/packages/SystemUI/src/com/android/systemui/wmshell/BubblesManager.java b/packages/SystemUI/src/com/android/systemui/wmshell/BubblesManager.java
index 139d190..65dede8 100644
--- a/packages/SystemUI/src/com/android/systemui/wmshell/BubblesManager.java
+++ b/packages/SystemUI/src/com/android/systemui/wmshell/BubblesManager.java
@@ -25,6 +25,7 @@
import static android.service.notification.NotificationStats.DISMISSAL_BUBBLE;
import static android.service.notification.NotificationStats.DISMISS_SENTIMENT_NEUTRAL;
+import static com.android.server.notification.Flags.screenshareNotificationHiding;
import static com.android.wm.shell.bubbles.BubbleDebugConfig.TAG_BUBBLES;
import static com.android.wm.shell.bubbles.BubbleDebugConfig.TAG_WITH_CLASS_NAME;
@@ -69,6 +70,7 @@
import com.android.systemui.statusbar.notification.interruption.VisualInterruptionDecisionProvider;
import com.android.systemui.statusbar.phone.StatusBarWindowCallback;
import com.android.systemui.statusbar.policy.KeyguardStateController;
+import com.android.systemui.statusbar.policy.SensitiveNotificationProtectionController;
import com.android.systemui.statusbar.policy.ZenModeController;
import com.android.wm.shell.bubbles.Bubble;
import com.android.wm.shell.bubbles.BubbleEntry;
@@ -102,6 +104,7 @@
private final NotificationVisibilityProvider mVisibilityProvider;
private final VisualInterruptionDecisionProvider mVisualInterruptionDecisionProvider;
private final NotificationLockscreenUserManager mNotifUserManager;
+ private final SensitiveNotificationProtectionController mSensitiveNotifProtectionController;
private final CommonNotifCollection mCommonNotifCollection;
private final NotifPipeline mNotifPipeline;
private final NotifPipelineFlags mNotifPipelineFlags;
@@ -111,6 +114,7 @@
// TODO (b/145659174): allow for multiple callbacks to support the "shadow" new notif pipeline
private final List<NotifCallback> mCallbacks = new ArrayList<>();
private final StatusBarWindowCallback mStatusBarWindowCallback;
+ private final Runnable mSensitiveStateChangedListener;
private boolean mPanelExpanded;
/**
@@ -130,6 +134,7 @@
VisualInterruptionDecisionProvider visualInterruptionDecisionProvider,
ZenModeController zenModeController,
NotificationLockscreenUserManager notifUserManager,
+ SensitiveNotificationProtectionController sensitiveNotificationProtectionController,
CommonNotifCollection notifCollection,
NotifPipeline notifPipeline,
SysUiState sysUiState,
@@ -149,6 +154,7 @@
visualInterruptionDecisionProvider,
zenModeController,
notifUserManager,
+ sensitiveNotificationProtectionController,
notifCollection,
notifPipeline,
sysUiState,
@@ -173,6 +179,7 @@
VisualInterruptionDecisionProvider visualInterruptionDecisionProvider,
ZenModeController zenModeController,
NotificationLockscreenUserManager notifUserManager,
+ SensitiveNotificationProtectionController sensitiveNotificationProtectionController,
CommonNotifCollection notifCollection,
NotifPipeline notifPipeline,
SysUiState sysUiState,
@@ -188,6 +195,7 @@
mVisibilityProvider = visibilityProvider;
mVisualInterruptionDecisionProvider = visualInterruptionDecisionProvider;
mNotifUserManager = notifUserManager;
+ mSensitiveNotifProtectionController = sensitiveNotificationProtectionController;
mCommonNotifCollection = notifCollection;
mNotifPipeline = notifPipeline;
mNotifPipelineFlags = notifPipelineFlags;
@@ -251,6 +259,22 @@
};
notificationShadeWindowController.registerCallback(mStatusBarWindowCallback);
+ mSensitiveStateChangedListener = new Runnable() {
+ @Override
+ public void run() {
+ if (!screenshareNotificationHiding()) {
+ return;
+ }
+ bubbles.onSensitiveNotificationProtectionStateChanged(
+ mSensitiveNotifProtectionController.isSensitiveStateActive());
+ }
+ };
+
+ if (screenshareNotificationHiding()) {
+ mSensitiveNotifProtectionController
+ .registerSensitiveStateListener(mSensitiveStateChangedListener);
+ }
+
mSysuiProxy = new Bubbles.SysuiProxy() {
@Override
public void isNotificationPanelExpand(Consumer<Boolean> callback) {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuItemAccessibilityDelegateTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuItemAccessibilityDelegateTest.java
index c2ed7d4..976cd5bd 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuItemAccessibilityDelegateTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuItemAccessibilityDelegateTest.java
@@ -21,6 +21,7 @@
import static com.google.common.truth.Truth.assertThat;
+import static org.mockito.Mockito.doNothing;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.spy;
@@ -89,6 +90,7 @@
mMenuView = spy(new MenuView(mContext, stubMenuViewModel, stubMenuViewAppearance,
mSecureSettings));
mMenuView.setTranslationY(halfScreenHeight);
+ doNothing().when(mMenuView).gotoEditScreen();
mMenuViewLayer = spy(new MenuViewLayer(
mContext, stubWindowManager, mAccessibilityManager,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayerTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayerTest.java
index ce4db8f..3862b0f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayerTest.java
@@ -160,6 +160,7 @@
new MenuView(mSpyContext, mMenuViewModel, menuViewAppearance, mSecureSettings));
// Ensure tests don't actually update metrics.
doNothing().when(mMenuView).incrementTexMetric(any(), anyInt());
+ doNothing().when(mMenuView).gotoEditScreen();
mMenuViewLayer = spy(new MenuViewLayer(mSpyContext, mStubWindowManager,
mStubAccessibilityManager, mMenuViewModel, menuViewAppearance, mMenuView,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuViewTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuViewTest.java
index 7c97f53..1ce6525 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuViewTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuViewTest.java
@@ -21,6 +21,7 @@
import static com.google.common.truth.Truth.assertThat;
import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.doNothing;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.verify;
@@ -80,6 +81,7 @@
mUiModeManager.setNightMode(MODE_NIGHT_YES);
mSpyContext = spy(mContext);
+ doNothing().when(mSpyContext).startActivity(any());
final SecureSettings secureSettings = TestUtils.mockSecureSettings();
final MenuViewModel stubMenuViewModel = new MenuViewModel(mContext, mAccessibilityManager,
secureSettings);
@@ -179,8 +181,6 @@
@Test
@EnableFlags(Flags.FLAG_FLOATING_MENU_DRAG_TO_EDIT)
public void gotoEditScreen_sendsIntent() {
- // Notably, this shouldn't crash the settings app,
- // because the button target args are configured.
mMenuView.gotoEditScreen();
verify(mSpyContext).startActivity(any());
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardBlueprintInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardBlueprintInteractorTest.kt
index 9df00d3..b0d8de3 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardBlueprintInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardBlueprintInteractorTest.kt
@@ -19,17 +19,23 @@
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
+import com.android.systemui.Flags
import com.android.systemui.SysuiTestCase
import com.android.systemui.keyguard.data.repository.KeyguardBlueprintRepository
+import com.android.systemui.keyguard.domain.interactor.KeyguardBlueprintInteractor.Companion.SPLIT_SHADE_WEATHER_CLOCK_BLUEPRINT_ID
+import com.android.systemui.keyguard.domain.interactor.KeyguardBlueprintInteractor.Companion.WEATHER_CLOCK_BLUEPRINT_ID
import com.android.systemui.keyguard.ui.view.layout.blueprints.DefaultKeyguardBlueprint
import com.android.systemui.keyguard.ui.view.layout.blueprints.SplitShadeKeyguardBlueprint
import com.android.systemui.keyguard.ui.view.layout.blueprints.transitions.IntraBlueprintTransition
import com.android.systemui.keyguard.ui.view.layout.blueprints.transitions.IntraBlueprintTransition.Config
import com.android.systemui.keyguard.ui.view.layout.blueprints.transitions.IntraBlueprintTransition.Type
+import com.android.systemui.plugins.clocks.ClockConfig
+import com.android.systemui.plugins.clocks.ClockController
import com.android.systemui.statusbar.policy.SplitShadeStateController
import com.android.systemui.util.mockito.any
import com.android.systemui.util.mockito.whenever
import kotlinx.coroutines.flow.MutableSharedFlow
+import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.test.StandardTestDispatcher
import kotlinx.coroutines.test.TestScope
import kotlinx.coroutines.test.runCurrent
@@ -38,6 +44,7 @@
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.Mock
+import org.mockito.Mockito.never
import org.mockito.Mockito.reset
import org.mockito.Mockito.verify
import org.mockito.MockitoAnnotations
@@ -54,6 +61,8 @@
@Mock private lateinit var splitShadeStateController: SplitShadeStateController
@Mock private lateinit var keyguardBlueprintRepository: KeyguardBlueprintRepository
+ @Mock private lateinit var clockInteractor: KeyguardClockInteractor
+ @Mock private lateinit var clockController: ClockController
@Before
fun setup() {
@@ -61,6 +70,8 @@
testScope = TestScope(StandardTestDispatcher())
whenever(keyguardBlueprintRepository.configurationChange).thenReturn(configurationFlow)
whenever(keyguardBlueprintRepository.refreshTransition).thenReturn(refreshTransition)
+ whenever(clockInteractor.currentClock).thenReturn(MutableStateFlow(clockController))
+ clockInteractor.currentClock
underTest =
KeyguardBlueprintInteractor(
@@ -68,6 +79,7 @@
testScope.backgroundScope,
mContext,
splitShadeStateController,
+ clockInteractor,
)
}
@@ -102,6 +114,77 @@
}
@Test
+ fun composeLockscreenOff_DoesAppliesSplitShadeWeatherClockBlueprint() {
+ testScope.runTest {
+ mSetFlagsRule.disableFlags(Flags.FLAG_COMPOSE_LOCKSCREEN)
+ whenever(clockController.config)
+ .thenReturn(
+ ClockConfig(
+ id = "DIGITAL_CLOCK_WEATHER",
+ name = "clock",
+ description = "clock",
+ )
+ )
+ whenever(splitShadeStateController.shouldUseSplitNotificationShade(any()))
+ .thenReturn(true)
+
+ reset(keyguardBlueprintRepository)
+ configurationFlow.tryEmit(Unit)
+ runCurrent()
+
+ verify(keyguardBlueprintRepository, never())
+ .applyBlueprint(SPLIT_SHADE_WEATHER_CLOCK_BLUEPRINT_ID)
+ }
+ }
+
+ @Test
+ fun testDoesAppliesSplitShadeWeatherClockBlueprint() {
+ testScope.runTest {
+ mSetFlagsRule.enableFlags(Flags.FLAG_COMPOSE_LOCKSCREEN)
+ whenever(clockController.config)
+ .thenReturn(
+ ClockConfig(
+ id = "DIGITAL_CLOCK_WEATHER",
+ name = "clock",
+ description = "clock",
+ )
+ )
+ whenever(splitShadeStateController.shouldUseSplitNotificationShade(any()))
+ .thenReturn(true)
+
+ reset(keyguardBlueprintRepository)
+ configurationFlow.tryEmit(Unit)
+ runCurrent()
+
+ verify(keyguardBlueprintRepository)
+ .applyBlueprint(SPLIT_SHADE_WEATHER_CLOCK_BLUEPRINT_ID)
+ }
+ }
+
+ @Test
+ fun testAppliesWeatherClockBlueprint() {
+ testScope.runTest {
+ mSetFlagsRule.enableFlags(Flags.FLAG_COMPOSE_LOCKSCREEN)
+ whenever(clockController.config)
+ .thenReturn(
+ ClockConfig(
+ id = "DIGITAL_CLOCK_WEATHER",
+ name = "clock",
+ description = "clock",
+ )
+ )
+ whenever(splitShadeStateController.shouldUseSplitNotificationShade(any()))
+ .thenReturn(false)
+
+ reset(keyguardBlueprintRepository)
+ configurationFlow.tryEmit(Unit)
+ runCurrent()
+
+ verify(keyguardBlueprintRepository).applyBlueprint(WEATHER_CLOCK_BLUEPRINT_ID)
+ }
+ }
+
+ @Test
fun testRefreshBlueprint() {
underTest.refreshBlueprint()
verify(keyguardBlueprintRepository).refreshBlueprint()
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultIndicationAreaSectionTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultIndicationAreaSectionTest.kt
index 1205dce..711f90f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultIndicationAreaSectionTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultIndicationAreaSectionTest.kt
@@ -23,7 +23,6 @@
import androidx.test.filters.SmallTest
import com.android.systemui.Flags as AConfigFlags
import com.android.systemui.SysuiTestCase
-import com.android.systemui.keyguard.ui.viewmodel.AodAlphaViewModel
import com.android.systemui.keyguard.ui.viewmodel.KeyguardIndicationAreaViewModel
import com.android.systemui.res.R
import com.android.systemui.statusbar.KeyguardIndicationController
@@ -40,7 +39,6 @@
class DefaultIndicationAreaSectionTest : SysuiTestCase() {
@Mock private lateinit var keyguardIndicationAreaViewModel: KeyguardIndicationAreaViewModel
- @Mock private lateinit var aodAlphaViewModel: AodAlphaViewModel
@Mock private lateinit var indicationController: KeyguardIndicationController
private lateinit var underTest: DefaultIndicationAreaSection
@@ -52,7 +50,6 @@
DefaultIndicationAreaSection(
context,
keyguardIndicationAreaViewModel,
- aodAlphaViewModel,
indicationController,
)
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/controls/domain/pipeline/MediaDeviceManagerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/controls/domain/pipeline/MediaDeviceManagerTest.kt
index 14fe182..7f3d79f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/controls/domain/pipeline/MediaDeviceManagerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/controls/domain/pipeline/MediaDeviceManagerTest.kt
@@ -27,12 +27,16 @@
import android.media.session.MediaController
import android.media.session.MediaController.PlaybackInfo
import android.media.session.MediaSession
+import android.platform.test.annotations.DisableFlags
+import android.platform.test.annotations.EnableFlags
+import android.platform.test.flag.junit.DeviceFlagsValueProvider
import android.testing.AndroidTestingRunner
import android.testing.TestableLooper
import androidx.test.filters.SmallTest
import com.android.settingslib.bluetooth.LocalBluetoothLeBroadcast
import com.android.settingslib.bluetooth.LocalBluetoothManager
import com.android.settingslib.bluetooth.LocalBluetoothProfileManager
+import com.android.settingslib.flags.Flags
import com.android.settingslib.media.LocalMediaManager
import com.android.settingslib.media.MediaDevice
import com.android.settingslib.media.PhoneMediaDevice
@@ -83,6 +87,7 @@
@RunWith(AndroidTestingRunner::class)
@TestableLooper.RunWithLooper
public class MediaDeviceManagerTest : SysuiTestCase() {
+ @get:Rule val checkFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule()
private lateinit var manager: MediaDeviceManager
@Mock private lateinit var controllerFactory: MediaControllerFactory
@@ -668,7 +673,28 @@
}
@Test
- fun onBroadcastStarted_currentMediaDeviceDataIsBroadcasting() {
+ fun onBroadcastStarted_flagOff_currentMediaDeviceDataIsBroadcasting() {
+ mSetFlagsRule.disableFlags(Flags.FLAG_LEGACY_LE_AUDIO_SHARING)
+ mSetFlagsRule.disableFlags(Flags.FLAG_ENABLE_LE_AUDIO_SHARING)
+ val broadcastCallback = setupBroadcastCallback()
+ setupLeAudioConfiguration(true)
+ setupBroadcastPackage(BROADCAST_APP_NAME)
+ broadcastCallback.onBroadcastStarted(1, 1)
+
+ manager.onMediaDataLoaded(KEY, null, mediaData)
+ fakeBgExecutor.runAllReady()
+ fakeFgExecutor.runAllReady()
+
+ val data = captureDeviceData(KEY)
+ assertThat(data.showBroadcastButton).isFalse()
+ assertThat(data.enabled).isTrue()
+ assertThat(data.name).isEqualTo(DEVICE_NAME)
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_LEGACY_LE_AUDIO_SHARING)
+ @DisableFlags(Flags.FLAG_ENABLE_LE_AUDIO_SHARING)
+ fun onBroadcastStarted_legacy_currentMediaDeviceDataIsBroadcasting() {
val broadcastCallback = setupBroadcastCallback()
setupLeAudioConfiguration(true)
setupBroadcastPackage(BROADCAST_APP_NAME)
@@ -686,7 +712,9 @@
}
@Test
- fun onBroadcastStarted_currentMediaDeviceDataIsNotBroadcasting() {
+ @EnableFlags(Flags.FLAG_LEGACY_LE_AUDIO_SHARING)
+ @DisableFlags(Flags.FLAG_ENABLE_LE_AUDIO_SHARING)
+ fun onBroadcastStarted_legacy_currentMediaDeviceDataIsNotBroadcasting() {
val broadcastCallback = setupBroadcastCallback()
setupLeAudioConfiguration(true)
setupBroadcastPackage(NORMAL_APP_NAME)
@@ -703,6 +731,62 @@
}
@Test
+ @EnableFlags(Flags.FLAG_LEGACY_LE_AUDIO_SHARING)
+ @DisableFlags(Flags.FLAG_ENABLE_LE_AUDIO_SHARING)
+ fun onBroadcastStopped_legacy_bluetoothLeBroadcastIsDisabledAndBroadcastingButtonIsGone() {
+ val broadcastCallback = setupBroadcastCallback()
+ setupLeAudioConfiguration(false)
+ broadcastCallback.onBroadcastStopped(1, 1)
+
+ manager.onMediaDataLoaded(KEY, null, mediaData)
+ fakeBgExecutor.runAllReady()
+ fakeFgExecutor.runAllReady()
+
+ val data = captureDeviceData(KEY)
+ assertThat(data.showBroadcastButton).isFalse()
+ }
+
+ @Test
+ @DisableFlags(Flags.FLAG_LEGACY_LE_AUDIO_SHARING)
+ @EnableFlags(Flags.FLAG_ENABLE_LE_AUDIO_SHARING)
+ fun onBroadcastStarted_currentMediaDeviceDataIsBroadcasting() {
+ val broadcastCallback = setupBroadcastCallback()
+ setupLeAudioConfiguration(true)
+ setupBroadcastPackage(BROADCAST_APP_NAME)
+ broadcastCallback.onBroadcastStarted(1, 1)
+
+ manager.onMediaDataLoaded(KEY, null, mediaData)
+ fakeBgExecutor.runAllReady()
+ fakeFgExecutor.runAllReady()
+
+ val data = captureDeviceData(KEY)
+ assertThat(data.showBroadcastButton).isFalse()
+ assertThat(data.enabled).isFalse()
+ assertThat(data.name).isEqualTo(context.getString(R.string.audio_sharing_description))
+ }
+
+ @Test
+ @DisableFlags(Flags.FLAG_LEGACY_LE_AUDIO_SHARING)
+ @EnableFlags(Flags.FLAG_ENABLE_LE_AUDIO_SHARING)
+ fun onBroadcastStarted_currentMediaDeviceDataIsNotBroadcasting() {
+ val broadcastCallback = setupBroadcastCallback()
+ setupLeAudioConfiguration(true)
+ setupBroadcastPackage(NORMAL_APP_NAME)
+ broadcastCallback.onBroadcastStarted(1, 1)
+
+ manager.onMediaDataLoaded(KEY, null, mediaData)
+ fakeBgExecutor.runAllReady()
+ fakeFgExecutor.runAllReady()
+
+ val data = captureDeviceData(KEY)
+ assertThat(data.showBroadcastButton).isFalse()
+ assertThat(data.enabled).isFalse()
+ assertThat(data.name).isEqualTo(context.getString(R.string.audio_sharing_description))
+ }
+
+ @Test
+ @DisableFlags(Flags.FLAG_LEGACY_LE_AUDIO_SHARING)
+ @EnableFlags(Flags.FLAG_ENABLE_LE_AUDIO_SHARING)
fun onBroadcastStopped_bluetoothLeBroadcastIsDisabledAndBroadcastingButtonIsGone() {
val broadcastCallback = setupBroadcastCallback()
setupLeAudioConfiguration(false)
@@ -714,6 +798,8 @@
val data = captureDeviceData(KEY)
assertThat(data.showBroadcastButton).isFalse()
+ assertThat(data.name?.equals(context.getString(R.string.audio_sharing_description)))
+ .isFalse()
}
private fun captureCallback(): LocalMediaManager.DeviceCallback {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/controller/MediaHierarchyManagerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/controller/MediaHierarchyManagerTest.kt
index 85291b8..45f49f0 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/controller/MediaHierarchyManagerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/controller/MediaHierarchyManagerTest.kt
@@ -24,8 +24,10 @@
import android.widget.FrameLayout
import androidx.test.filters.SmallTest
import com.android.keyguard.KeyguardViewController
+import com.android.systemui.Flags
import com.android.systemui.SysuiTestCase
import com.android.systemui.communal.domain.interactor.communalInteractor
+import com.android.systemui.communal.domain.interactor.setCommunalAvailable
import com.android.systemui.communal.shared.model.CommunalSceneKey
import com.android.systemui.controls.controller.ControlsControllerImplTest.Companion.eq
import com.android.systemui.dreams.DreamOverlayStateController
@@ -508,6 +510,10 @@
@Test
fun testCommunalLocation() =
testScope.runTest {
+ mSetFlagsRule.enableFlags(Flags.FLAG_COMMUNAL_HUB)
+ kosmos.setCommunalAvailable(true)
+ runCurrent()
+
communalInteractor.onSceneChanged(CommunalSceneKey.Communal)
runCurrent()
verify(mediaCarouselController)
@@ -533,6 +539,66 @@
}
@Test
+ fun testCommunalLocation_showsOverLockscreen() =
+ testScope.runTest {
+ mSetFlagsRule.enableFlags(Flags.FLAG_COMMUNAL_HUB)
+ kosmos.setCommunalAvailable(true)
+ runCurrent()
+
+ // Device is on lock screen.
+ whenever(statusBarStateController.state).thenReturn(StatusBarState.KEYGUARD)
+
+ // UMO goes to communal even over the lock screen.
+ communalInteractor.onSceneChanged(CommunalSceneKey.Communal)
+ runCurrent()
+ verify(mediaCarouselController)
+ .onDesiredLocationChanged(
+ eq(MediaHierarchyManager.LOCATION_COMMUNAL_HUB),
+ nullable(),
+ eq(false),
+ anyLong(),
+ anyLong()
+ )
+ }
+
+ @Test
+ fun testCommunalLocation_showsUntilQsExpands() =
+ testScope.runTest {
+ mSetFlagsRule.enableFlags(Flags.FLAG_COMMUNAL_HUB)
+ kosmos.setCommunalAvailable(true)
+ runCurrent()
+
+ // Device is on lock screen.
+ whenever(statusBarStateController.state).thenReturn(StatusBarState.KEYGUARD)
+
+ communalInteractor.onSceneChanged(CommunalSceneKey.Communal)
+ runCurrent()
+ verify(mediaCarouselController)
+ .onDesiredLocationChanged(
+ eq(MediaHierarchyManager.LOCATION_COMMUNAL_HUB),
+ nullable(),
+ eq(false),
+ anyLong(),
+ anyLong()
+ )
+ clearInvocations(mediaCarouselController)
+
+ // Start opening the shade.
+ mediaHierarchyManager.qsExpansion = 0.1f
+ runCurrent()
+
+ // UMO goes to the shade instead.
+ verify(mediaCarouselController)
+ .onDesiredLocationChanged(
+ eq(MediaHierarchyManager.LOCATION_QS),
+ any(MediaHostState::class.java),
+ eq(false),
+ anyLong(),
+ anyLong()
+ )
+ }
+
+ @Test
fun testQsExpandedChanged_noQqsMedia() {
// When we are looking at QQS with active media
whenever(statusBarStateController.state).thenReturn(StatusBarState.SHADE)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/screenshot/ActionIntentExecutorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/screenshot/ActionIntentExecutorTest.kt
new file mode 100644
index 0000000..0c32470
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/screenshot/ActionIntentExecutorTest.kt
@@ -0,0 +1,73 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.screenshot
+
+import android.content.Intent
+import android.os.Process.myUserHandle
+import android.platform.test.annotations.EnableFlags
+import android.testing.AndroidTestingRunner
+import android.testing.TestableContext
+import com.android.systemui.Flags
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.settings.DisplayTracker
+import com.android.systemui.shared.system.ActivityManagerWrapper
+import com.android.systemui.statusbar.phone.CentralSurfaces
+import com.android.systemui.util.mockito.mock
+import kotlinx.coroutines.test.StandardTestDispatcher
+import kotlinx.coroutines.test.TestCoroutineScheduler
+import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.runTest
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mockito.verify
+
+@RunWith(AndroidTestingRunner::class)
+class ActionIntentExecutorTest : SysuiTestCase() {
+
+ private val scheduler = TestCoroutineScheduler()
+ private val mainDispatcher = StandardTestDispatcher(scheduler)
+ private val testScope = TestScope(mainDispatcher)
+ private val testableContext = TestableContext(mContext)
+
+ private val activityManagerWrapper = mock<ActivityManagerWrapper>()
+ private val displayTracker = mock<DisplayTracker>()
+ private val keyguardController = mock<ScreenshotKeyguardController>()
+
+ private val actionIntentExecutor =
+ ActionIntentExecutor(
+ testableContext,
+ activityManagerWrapper,
+ testScope,
+ mainDispatcher,
+ displayTracker,
+ keyguardController,
+ )
+
+ @Test
+ @EnableFlags(Flags.FLAG_SCREENSHOT_ACTION_DISMISS_SYSTEM_WINDOWS)
+ fun launchIntent_callsCloseSystemWindows() =
+ testScope.runTest {
+ val intent = Intent(Intent.ACTION_EDIT).apply { flags = Intent.FLAG_ACTIVITY_NEW_TASK }
+ val userHandle = myUserHandle()
+
+ actionIntentExecutor.launchIntent(intent, null, userHandle, false)
+ scheduler.advanceUntilIdle()
+
+ verify(activityManagerWrapper)
+ .closeSystemWindows(CentralSurfaces.SYSTEM_DIALOG_REASON_SCREENSHOT)
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/settings/UserTrackerImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/settings/UserTrackerImplTest.kt
index 032ec74..774aa51 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/settings/UserTrackerImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/settings/UserTrackerImplTest.kt
@@ -371,7 +371,6 @@
val captor = ArgumentCaptor.forClass(IUserSwitchObserver::class.java)
verify(iActivityManager).registerUserSwitchObserver(capture(captor), anyString())
- captor.value.onBeforeUserSwitching(newID)
captor.value.onUserSwitching(newID, userSwitchingReply)
assertThat(callback.calledOnUserChanging).isEqualTo(0)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/GlanceableHubContainerControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/GlanceableHubContainerControllerTest.kt
index 1dc5f7d..665fc75 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/GlanceableHubContainerControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/GlanceableHubContainerControllerTest.kt
@@ -26,17 +26,20 @@
import android.view.WindowManager
import android.widget.FrameLayout
import androidx.test.filters.SmallTest
+import com.android.systemui.Flags
import com.android.systemui.SysuiTestCase
import com.android.systemui.communal.data.repository.FakeCommunalRepository
import com.android.systemui.communal.data.repository.fakeCommunalRepository
import com.android.systemui.communal.domain.interactor.CommunalInteractor
import com.android.systemui.communal.domain.interactor.communalInteractor
+import com.android.systemui.communal.domain.interactor.setCommunalAvailable
import com.android.systemui.communal.shared.model.CommunalSceneKey
import com.android.systemui.communal.ui.viewmodel.CommunalViewModel
import com.android.systemui.compose.ComposeFacade
import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
import com.android.systemui.kosmos.Kosmos
import com.android.systemui.kosmos.testDispatcher
+import com.android.systemui.kosmos.testScope
import com.android.systemui.res.R
import com.android.systemui.shade.domain.interactor.ShadeInteractor
import com.android.systemui.testKosmos
@@ -45,6 +48,7 @@
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.launch
import kotlinx.coroutines.test.UnconfinedTestDispatcher
import org.junit.After
import org.junit.Assert.assertThrows
@@ -114,6 +118,14 @@
BOTTOM_SWIPE_REGION_WIDTH
)
+ // Make communal available so that communalInteractor.desiredScene accurately reflects
+ // scene changes instead of just returning Blank.
+ mSetFlagsRule.enableFlags(Flags.FLAG_COMMUNAL_HUB)
+ with(kosmos.testScope) {
+ launch { kosmos.setCommunalAvailable(true) }
+ testScheduler.runCurrent()
+ }
+
initAndAttachContainerView()
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/LockscreenShadeQsTransitionControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/LockscreenShadeQsTransitionControllerTest.kt
index 0b4de34..402d9aa 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/LockscreenShadeQsTransitionControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/LockscreenShadeQsTransitionControllerTest.kt
@@ -18,12 +18,13 @@
import android.testing.AndroidTestingRunner
import androidx.test.filters.SmallTest
-import com.android.systemui.res.R
import com.android.systemui.SysuiTestCase
import com.android.systemui.dump.DumpManager
import com.android.systemui.plugins.qs.QS
+import com.android.systemui.res.R
import com.android.systemui.statusbar.policy.FakeConfigurationController
import com.android.systemui.statusbar.policy.ResourcesSplitShadeStateController
+import com.android.systemui.util.mockito.mock
import com.google.common.truth.Expect
import com.google.common.truth.Truth.assertThat
import org.junit.Before
@@ -43,13 +44,15 @@
@get:Rule val expect: Expect = Expect.create()
@Mock private lateinit var dumpManager: DumpManager
- @Mock private lateinit var qS: QS
+ private var qS: QS? = null
private lateinit var controller: LockscreenShadeQsTransitionController
@Before
fun setUp() {
MockitoAnnotations.initMocks(this)
+ qS = mock()
+
setTransitionDistance(TRANSITION_DISTANCE)
setTransitionDelay(TRANSITION_DELAY)
setSquishTransitionDistance(SQUISH_TRANSITION_DISTANCE)
@@ -220,7 +223,7 @@
controller.dragDownAmount = rawDragAmount
- verify(qS)
+ verify(qS!!)
.setTransitionToFullShadeProgress(
/* isTransitioningToFullShade= */ true,
/* transitionFraction= */ controller.qsTransitionFraction,
@@ -228,6 +231,15 @@
)
}
+ @Test
+ fun nullQS_onDragAmountChanged_doesNotCrash() {
+ qS = null
+
+ val rawDragAmount = 200f
+
+ controller.dragDownAmount = rawDragAmount
+ }
+
private fun setTransitionDistance(value: Int) {
overrideResource(R.dimen.lockscreen_shade_qs_transition_distance, value)
configurationController.notifyConfigurationChanged()
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/LockscreenShadeTransitionControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/LockscreenShadeTransitionControllerTest.kt
index 91701b1..86116a0 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/LockscreenShadeTransitionControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/LockscreenShadeTransitionControllerTest.kt
@@ -18,6 +18,7 @@
import com.android.systemui.media.controls.ui.controller.MediaHierarchyManager
import com.android.systemui.plugins.qs.QS
import com.android.systemui.power.domain.interactor.PowerInteractor
+import com.android.systemui.qs.ui.adapter.FakeQSSceneAdapter
import com.android.systemui.res.R
import com.android.systemui.shade.ShadeLockscreenInteractor
import com.android.systemui.shade.data.repository.FakeShadeRepository
@@ -82,6 +83,8 @@
private val testScope
get() = testComponent.testScope
+ private val qsSceneAdapter = FakeQSSceneAdapter({ mock() })
+
lateinit var row: ExpandableNotificationRow
@Mock lateinit var centralSurfaces: CentralSurfaces
@@ -189,6 +192,7 @@
splitShadeStateController = ResourcesSplitShadeStateController(),
shadeLockscreenInteractorLazy = {shadeLockscreenInteractor},
naturalScrollingSettingObserver = naturalScrollingSettingObserver,
+ lazyQSSceneAdapter = { qsSceneAdapter }
)
transitionController.addCallback(transitionControllerCallback)
@@ -567,6 +571,16 @@
verify(shadeLockscreenInteractor).setKeyguardStatusBarAlpha(-1f)
}
+ @Test
+ fun nullQs_canDragDownFromAdapter() {
+ transitionController.qS = null
+
+ qsSceneAdapter.isQsFullyCollapsed = true
+ assertTrue("Can't drag down on keyguard", transitionController.canDragDown())
+ qsSceneAdapter.isQsFullyCollapsed = false
+ assertFalse("Can drag down when QS is expanded", transitionController.canDragDown())
+ }
+
private fun enableSplitShade() {
setSplitShadeEnabled(true)
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/SplitShadeLockScreenOverScrollerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/SplitShadeLockScreenOverScrollerTest.kt
index 81d5c4d..700fb1e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/SplitShadeLockScreenOverScrollerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/SplitShadeLockScreenOverScrollerTest.kt
@@ -9,6 +9,7 @@
import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController
import com.android.systemui.statusbar.phone.ScrimController
import com.android.systemui.statusbar.policy.FakeConfigurationController
+import com.android.systemui.util.mockito.mock
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
@@ -31,7 +32,7 @@
@Mock private lateinit var scrimController: ScrimController
@Mock private lateinit var statusBarStateController: SysuiStatusBarStateController
- @Mock private lateinit var qS: QS
+ private var qS: QS? = null
@Mock private lateinit var nsslController: NotificationStackScrollLayoutController
@Mock private lateinit var dumpManager: DumpManager
@@ -40,6 +41,7 @@
@Before
fun setUp() {
MockitoAnnotations.initMocks(this)
+ qS = mock()
whenever(nsslController.height).thenReturn(1800)
@@ -92,7 +94,7 @@
setDragAmount(1000f)
whenever(statusBarStateController.state).thenReturn(StatusBarState.SHADE)
setDragAmount(999f)
- reset(qS, scrimController, nsslController)
+ reset(qS!!, scrimController, nsslController)
setDragAmount(998f)
setDragAmount(997f)
@@ -100,8 +102,15 @@
verifyNoMoreOverScrollChanges()
}
+ @Test
+ fun qsNull_applyOverscroll_doesNotCrash() {
+ qS = null
+
+ setDragAmount(100f)
+ }
+
private fun verifyOverScrollPerformed() {
- verify(qS).setOverScrollAmount(intThat { it > 0 })
+ verify(qS!!).setOverScrollAmount(intThat { it > 0 })
verify(scrimController).setNotificationsOverScrollAmount(intThat { it > 0 })
verify(nsslController).setOverScrollAmount(intThat { it > 0 })
}
@@ -109,7 +118,7 @@
private fun verifyOverScrollResetToZero() {
// Might be more than once as the animator might have multiple values close to zero that
// round down to zero.
- verify(qS, atLeast(1)).setOverScrollAmount(0)
+ verify(qS!!, atLeast(1)).setOverScrollAmount(0)
verify(scrimController, atLeast(1)).setNotificationsOverScrollAmount(0)
verify(nsslController, atLeast(1)).setOverScrollAmount(0)
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/RowAlertTimeCoordinatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/RowAlertTimeCoordinatorTest.kt
new file mode 100644
index 0000000..7daadb0
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/RowAlertTimeCoordinatorTest.kt
@@ -0,0 +1,99 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.systemui.statusbar.notification.collection.coordinator
+
+import android.testing.AndroidTestingRunner
+import android.testing.TestableLooper.RunWithLooper
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.statusbar.notification.collection.GroupEntryBuilder
+import com.android.systemui.statusbar.notification.collection.NotifPipeline
+import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder
+import com.android.systemui.statusbar.notification.collection.listbuilder.OnAfterRenderEntryListener
+import com.android.systemui.statusbar.notification.collection.listbuilder.OnBeforeFinalizeFilterListener
+import com.android.systemui.statusbar.notification.collection.render.NotifRowController
+import com.android.systemui.util.mockito.mock
+import com.android.systemui.util.mockito.withArgCaptor
+import com.google.common.truth.Truth.assertThat
+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.Mockito.verifyNoMoreInteractions
+import org.mockito.MockitoAnnotations.initMocks
+
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+@RunWithLooper
+class RowAlertTimeCoordinatorTest : SysuiTestCase() {
+ private lateinit var coordinator: RowAlertTimeCoordinator
+ private lateinit var beforeFinalizeFilterListener: OnBeforeFinalizeFilterListener
+ private lateinit var afterRenderEntryListener: OnAfterRenderEntryListener
+
+ @Mock private lateinit var pipeline: NotifPipeline
+
+ @Before
+ fun setUp() {
+ initMocks(this)
+ coordinator = RowAlertTimeCoordinator()
+ coordinator.attach(pipeline)
+ beforeFinalizeFilterListener = withArgCaptor {
+ verify(pipeline).addOnBeforeFinalizeFilterListener(capture())
+ }
+ afterRenderEntryListener = withArgCaptor {
+ verify(pipeline).addOnAfterRenderEntryListener(capture())
+ }
+ }
+
+ @Test
+ fun testSetLastAudiblyAlerted() {
+ val entry1 = NotificationEntryBuilder().setLastAudiblyAlertedMs(10).build()
+ val entry2 = NotificationEntryBuilder().setLastAudiblyAlertedMs(20).build()
+ val summary = NotificationEntryBuilder().setLastAudiblyAlertedMs(5).build()
+ val child1 = NotificationEntryBuilder().setLastAudiblyAlertedMs(0).build()
+ val child2 = NotificationEntryBuilder().setLastAudiblyAlertedMs(8).build()
+ val group =
+ GroupEntryBuilder()
+ .setKey("group")
+ .setSummary(summary)
+ .addChild(child1)
+ .addChild(child2)
+ .build()
+
+ val entries = listOf(entry1, summary, child1, child2, entry2)
+
+ beforeFinalizeFilterListener.onBeforeFinalizeFilter(listOf(entry1, group, entry2))
+ val actualTimesSet =
+ entries.associateWith {
+ val rowController = mock<NotifRowController>()
+ afterRenderEntryListener.onAfterRenderEntry(it, rowController)
+ withArgCaptor<Long> {
+ verify(rowController).setLastAudibleMs(capture())
+ verifyNoMoreInteractions(rowController)
+ }
+ }
+ val expectedTimesSet =
+ mapOf(
+ entry1 to 10L,
+ entry2 to 20L,
+ summary to 8L,
+ child1 to 0L,
+ child2 to 8L,
+ )
+ assertThat(actualTimesSet).containsExactlyEntriesIn(expectedTimesSet)
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/RowAppearanceCoordinatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/RowAppearanceCoordinatorTest.kt
index fa669fc..a66f8ce 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/RowAppearanceCoordinatorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/RowAppearanceCoordinatorTest.kt
@@ -76,7 +76,7 @@
verify(pipeline).addOnAfterRenderEntryListener(capture())
}
whenever(assistantFeedbackController.getFeedbackIcon(any())).thenReturn(FeedbackIcon(1, 2))
- entry1 = NotificationEntryBuilder().setSection(section1).setLastAudiblyAlertedMs(17).build()
+ entry1 = NotificationEntryBuilder().setSection(section1).build()
entry2 = NotificationEntryBuilder().setSection(section2).build()
}
@@ -103,12 +103,6 @@
}
@Test
- fun testSetLastAudiblyAlerted() {
- afterRenderEntryListener.onAfterRenderEntry(entry1, controller1)
- verify(controller1).setLastAudibleMs(eq(17.toLong()))
- }
-
- @Test
fun testSetFeedbackIcon() {
afterRenderEntryListener.onAfterRenderEntry(entry1, controller1)
verify(controller1).setFeedbackIcon(eq(FeedbackIcon(1, 2)))
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/icon/IconManagerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/icon/IconManagerTest.kt
index 8b99811..a12806b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/icon/IconManagerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/icon/IconManagerTest.kt
@@ -167,6 +167,20 @@
}
@Test
+ fun testUpdateIcons_sensitiveImportantConversation() {
+ val entry =
+ notificationEntry(hasShortcut = true, hasMessageSenderIcon = true, hasLargeIcon = false)
+ entry?.setSensitive(true, true)
+ entry?.channel?.isImportantConversation = true
+ entry?.let { iconManager.createIcons(it) }
+ // Updating the icons after creation shouldn't break anything
+ entry?.let { iconManager.updateIcons(it) }
+ assertThat(entry?.icons?.statusBarIcon?.sourceIcon).isEqualTo(shortcutIc)
+ assertThat(entry?.icons?.shelfIcon?.sourceIcon).isEqualTo(smallIc)
+ assertThat(entry?.icons?.aodIcon?.sourceIcon).isEqualTo(smallIc)
+ }
+
+ @Test
fun testUpdateIcons_sensitivityChange() {
val entry =
notificationEntry(hasShortcut = true, hasMessageSenderIcon = true, hasLargeIcon = false)
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 91a9da3..995da81 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
@@ -222,6 +222,26 @@
}
@Test
+ @EnableFlags(NotificationsImprovedHunAnimation.FLAG_NAME)
+ fun resetViewStates_hunAnimatingAwayWhileDozing_yTranslationIsInset() {
+ whenever(notificationRow.isHeadsUpAnimatingAway).thenReturn(true)
+
+ ambientState.isDozing = true
+
+ resetViewStates_hunYTranslationIs(stackScrollAlgorithm.mHeadsUpInset)
+ }
+
+ @Test
+ @EnableFlags(NotificationsImprovedHunAnimation.FLAG_NAME)
+ fun resetViewStates_hunAnimatingAwayWhileDozing_hasStackMargin_changesHunYTranslation() {
+ whenever(notificationRow.isHeadsUpAnimatingAway).thenReturn(true)
+
+ ambientState.isDozing = true
+
+ resetViewStates_stackMargin_changesHunYTranslation()
+ }
+
+ @Test
fun resetViewStates_hunsOverlapping_bottomHunClipped() {
val topHun = mockExpandableNotificationRow()
val bottomHun = mockExpandableNotificationRow()
diff --git a/packages/SystemUI/tests/src/com/android/systemui/surfaceeffects/turbulencenoise/TurbulenceNoiseControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/surfaceeffects/turbulencenoise/TurbulenceNoiseControllerTest.kt
index 203096a..08b49f0 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/surfaceeffects/turbulencenoise/TurbulenceNoiseControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/surfaceeffects/turbulencenoise/TurbulenceNoiseControllerTest.kt
@@ -166,4 +166,28 @@
assertThat(config.color).isEqualTo(expectedColor)
}
}
+
+ @Test
+ fun play_initializesShader() {
+ val expectedNoiseOffset = floatArrayOf(0.1f, 0.2f, 0.3f)
+ val config =
+ TurbulenceNoiseAnimationConfig(
+ noiseOffsetX = expectedNoiseOffset[0],
+ noiseOffsetY = expectedNoiseOffset[1],
+ noiseOffsetZ = expectedNoiseOffset[2]
+ )
+ val turbulenceNoiseView = TurbulenceNoiseView(context, null)
+ val turbulenceNoiseController = TurbulenceNoiseController(turbulenceNoiseView)
+
+ fakeExecutor.execute {
+ turbulenceNoiseController.play(SIMPLEX_NOISE, config)
+
+ assertThat(turbulenceNoiseView.noiseConfig).isNotNull()
+ val shader = turbulenceNoiseView.turbulenceNoiseShader!!
+ assertThat(shader).isNotNull()
+ assertThat(shader.noiseOffsetX).isEqualTo(expectedNoiseOffset[0])
+ assertThat(shader.noiseOffsetY).isEqualTo(expectedNoiseOffset[1])
+ assertThat(shader.noiseOffsetZ).isEqualTo(expectedNoiseOffset[2])
+ }
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java b/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java
index b25ac24..a930860 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java
@@ -26,6 +26,7 @@
import static android.service.notification.NotificationListenerService.REASON_GROUP_SUMMARY_CANCELED;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
+import static com.android.server.notification.Flags.FLAG_SCREENSHARE_NOTIFICATION_HIDING;
import static com.google.common.truth.Truth.assertThat;
@@ -46,6 +47,7 @@
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyZeroInteractions;
import static org.mockito.Mockito.when;
import static kotlinx.coroutines.flow.FlowKt.emptyFlow;
@@ -73,6 +75,8 @@
import android.os.RemoteException;
import android.os.UserHandle;
import android.os.UserManager;
+import android.platform.test.annotations.DisableFlags;
+import android.platform.test.annotations.EnableFlags;
import android.service.dreams.IDreamManager;
import android.service.notification.NotificationListenerService;
import android.service.notification.ZenModeConfig;
@@ -161,6 +165,7 @@
import com.android.systemui.statusbar.policy.HeadsUpManager;
import com.android.systemui.statusbar.policy.KeyguardStateController;
import com.android.systemui.statusbar.policy.ResourcesSplitShadeStateController;
+import com.android.systemui.statusbar.policy.SensitiveNotificationProtectionController;
import com.android.systemui.statusbar.policy.ZenModeController;
import com.android.systemui.statusbar.policy.data.repository.FakeDeviceProvisioningRepository;
import com.android.systemui.statusbar.policy.data.repository.FakeUserSetupRepository;
@@ -257,6 +262,8 @@
private NotificationShadeWindowView mNotificationShadeWindowView;
@Mock
private AuthController mAuthController;
+ @Mock
+ private SensitiveNotificationProtectionController mSensitiveNotificationProtectionController;
private SysUiState mSysUiState;
private boolean mSysUiStateBubblesExpanded;
@@ -272,6 +279,8 @@
private ArgumentCaptor<BroadcastReceiver> mBroadcastReceiverArgumentCaptor;
@Captor
private ArgumentCaptor<KeyguardStateController.Callback> mKeyguardStateControllerCallbackCaptor;
+ @Captor
+ private ArgumentCaptor<Runnable> mSensitiveStateChangedListener;
private BubblesManager mBubblesManager;
private TestableBubbleController mBubbleController;
@@ -594,6 +603,7 @@
interruptionDecisionProvider,
mZenModeController,
mLockscreenUserManager,
+ mSensitiveNotificationProtectionController,
mCommonNotifCollection,
mNotifPipeline,
mSysUiState,
@@ -2203,6 +2213,33 @@
assertThat(mBubbleController.getLayerView().isExpanded()).isFalse();
}
+ @DisableFlags(FLAG_SCREENSHARE_NOTIFICATION_HIDING)
+ @Test
+ public void doesNotRegisterSensitiveStateListener() {
+ verifyZeroInteractions(mSensitiveNotificationProtectionController);
+ }
+
+ @EnableFlags(FLAG_SCREENSHARE_NOTIFICATION_HIDING)
+ @Test
+ public void registerSensitiveStateListener() {
+ verify(mSensitiveNotificationProtectionController).registerSensitiveStateListener(any());
+ }
+
+ @EnableFlags(FLAG_SCREENSHARE_NOTIFICATION_HIDING)
+ @Test
+ public void onSensitiveNotificationProtectionStateChanged() {
+ verify(mSensitiveNotificationProtectionController, atLeastOnce())
+ .registerSensitiveStateListener(mSensitiveStateChangedListener.capture());
+
+ when(mSensitiveNotificationProtectionController.isSensitiveStateActive()).thenReturn(true);
+ mSensitiveStateChangedListener.getValue().run();
+ verify(mBubbleController).onSensitiveNotificationProtectionStateChanged(true);
+
+ when(mSensitiveNotificationProtectionController.isSensitiveStateActive()).thenReturn(false);
+ mSensitiveStateChangedListener.getValue().run();
+ verify(mBubbleController).onSensitiveNotificationProtectionStateChanged(false);
+ }
+
/** Creates a bubble using the userId and package. */
private Bubble createBubble(int userId, String pkg) {
final UserHandle userHandle = new UserHandle(userId);
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/communal/domain/interactor/CommunalInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/domain/interactor/CommunalInteractorKosmos.kt
index 6af08d3..6ac702e 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/communal/domain/interactor/CommunalInteractorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/domain/interactor/CommunalInteractorKosmos.kt
@@ -31,6 +31,7 @@
import com.android.systemui.log.logcatLogBuffer
import com.android.systemui.scene.domain.interactor.sceneInteractor
import com.android.systemui.scene.shared.flag.fakeSceneContainerFlags
+import com.android.systemui.settings.userTracker
import com.android.systemui.smartspace.data.repository.smartspaceRepository
import com.android.systemui.user.data.repository.fakeUserRepository
import com.android.systemui.util.mockito.mock
@@ -46,6 +47,7 @@
appWidgetHost = mock(),
keyguardInteractor = keyguardInteractor,
editWidgetsActivityStarter = editWidgetsActivityStarter,
+ userTracker = userTracker,
logBuffer = logcatLogBuffer("CommunalInteractor"),
tableLogBuffer = mock(),
communalSettingsInteractor = communalSettingsInteractor,
@@ -60,9 +62,11 @@
fakeFeatureFlagsClassic.set(Flags.COMMUNAL_SERVICE_ENABLED, available)
if (available) {
fakeUserRepository.asMainUser()
- with(fakeKeyguardRepository) {
- setIsEncryptedOrLockdown(false)
- setKeyguardShowing(true)
- }
+ } else {
+ fakeUserRepository.asDefaultUser()
+ }
+ with(fakeKeyguardRepository) {
+ setIsEncryptedOrLockdown(!available)
+ setKeyguardShowing(available)
}
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardBlueprintInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardBlueprintInteractorKosmos.kt
index d9a3192..8b0bba1 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardBlueprintInteractorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardBlueprintInteractorKosmos.kt
@@ -29,5 +29,6 @@
applicationScope = applicationCoroutineScope,
context = applicationContext,
splitShadeStateController = splitShadeStateController,
+ clockInteractor = keyguardClockInteractor,
)
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/ui/adapter/FakeQSSceneAdapter.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/ui/adapter/FakeQSSceneAdapter.kt
index b1581d1..4d902fa 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/ui/adapter/FakeQSSceneAdapter.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/ui/adapter/FakeQSSceneAdapter.kt
@@ -41,6 +41,8 @@
private val _navBarPadding = MutableStateFlow<Int>(0)
val navBarPadding = _navBarPadding.asStateFlow()
+ override var isQsFullyCollapsed: Boolean = true
+
override suspend fun inflate(context: Context) {
_view.value = inflateDelegate(context)
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/ui/adapter/QSSceneAdapterKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/ui/adapter/QSSceneAdapterKosmos.kt
new file mode 100644
index 0000000..00ab0b5
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/ui/adapter/QSSceneAdapterKosmos.kt
@@ -0,0 +1,25 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.qs.ui.adapter
+
+import android.view.View
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.util.mockito.mock
+
+var Kosmos.fakeQSSceneAdapter by Kosmos.Fixture { FakeQSSceneAdapter({ mock<View>() }) }
+
+val Kosmos.qsSceneAdapter: QSSceneAdapter by Kosmos.Fixture { fakeQSSceneAdapter }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/LockscreenShadeTransitionControllerKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/LockscreenShadeTransitionControllerKosmos.kt
index e5072f1..e4a3896 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/LockscreenShadeTransitionControllerKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/LockscreenShadeTransitionControllerKosmos.kt
@@ -26,6 +26,7 @@
import com.android.systemui.kosmos.Kosmos.Fixture
import com.android.systemui.media.controls.ui.controller.mediaHierarchyManager
import com.android.systemui.plugins.activityStarter
+import com.android.systemui.qs.ui.adapter.qsSceneAdapter
import com.android.systemui.shade.data.repository.shadeRepository
import com.android.systemui.shade.domain.interactor.shadeInteractor
import com.android.systemui.shade.domain.interactor.shadeLockscreenInteractor
@@ -61,5 +62,6 @@
splitShadeStateController = splitShadeStateController,
shadeLockscreenInteractorLazy = { shadeLockscreenInteractor },
naturalScrollingSettingObserver = naturalScrollingSettingObserver,
+ lazyQSSceneAdapter = { qsSceneAdapter }
)
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/domain/interactor/NotificationsSoundPolicyInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/domain/interactor/NotificationsSoundPolicyInteractorKosmos.kt
new file mode 100644
index 0000000..0614309
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/domain/interactor/NotificationsSoundPolicyInteractorKosmos.kt
@@ -0,0 +1,27 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.notification.domain.interactor
+
+import com.android.settingslib.statusbar.notification.data.repository.FakeNotificationsSoundPolicyRepository
+import com.android.settingslib.statusbar.notification.domain.interactor.NotificationsSoundPolicyInteractor
+import com.android.systemui.kosmos.Kosmos
+
+var Kosmos.notificationsSoundPolicyRepository by
+ Kosmos.Fixture { FakeNotificationsSoundPolicyRepository() }
+
+val Kosmos.notificationsSoundPolicyInteractor: NotificationsSoundPolicyInteractor by
+ Kosmos.Fixture { NotificationsSoundPolicyInteractor(notificationsSoundPolicyRepository) }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/user/data/repository/FakeUserRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/user/data/repository/FakeUserRepository.kt
index 931a59d..3e9ae4d 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/user/data/repository/FakeUserRepository.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/user/data/repository/FakeUserRepository.kt
@@ -119,6 +119,13 @@
yield()
}
+ /** Resets the current user to the default of [DEFAULT_SELECTED_USER_INFO]. */
+ suspend fun asDefaultUser(): UserInfo {
+ setUserInfos(listOf(DEFAULT_SELECTED_USER_INFO))
+ setSelectedUserInfo(DEFAULT_SELECTED_USER_INFO)
+ return DEFAULT_SELECTED_USER_INFO
+ }
+
/** Makes the current user [MAIN_USER]. */
suspend fun asMainUser(): UserInfo {
setUserInfos(listOf(MAIN_USER))
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/volume/data/repository/FakeAudioRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/data/repository/FakeAudioRepository.kt
index fed3e17..a3ad2b8 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/volume/data/repository/FakeAudioRepository.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/data/repository/FakeAudioRepository.kt
@@ -42,6 +42,7 @@
get() = mutableCommunicationDevice.asStateFlow()
private val models: MutableMap<AudioStream, MutableStateFlow<AudioStreamModel>> = mutableMapOf()
+ private val lastAudibleVolumes: MutableMap<AudioStream, Int> = mutableMapOf()
private fun getAudioStreamModelState(
audioStream: AudioStream
@@ -59,12 +60,9 @@
)
}
- override suspend fun getAudioStream(audioStream: AudioStream): Flow<AudioStreamModel> =
+ override fun getAudioStream(audioStream: AudioStream): Flow<AudioStreamModel> =
getAudioStreamModelState(audioStream).asStateFlow()
- override suspend fun getCurrentAudioStream(audioStream: AudioStream): AudioStreamModel =
- getAudioStreamModelState(audioStream).value
-
override suspend fun setVolume(audioStream: AudioStream, volume: Int) {
getAudioStreamModelState(audioStream).update { it.copy(volume = volume) }
}
@@ -73,6 +71,9 @@
getAudioStreamModelState(audioStream).update { it.copy(isMuted = isMuted) }
}
+ override suspend fun getLastAudibleVolume(audioStream: AudioStream): Int =
+ lastAudibleVolumes.getOrDefault(audioStream, 0)
+
fun setMode(newMode: Int) {
mutableMode.value = newMode
}
@@ -88,4 +89,8 @@
fun setAudioStreamModel(model: AudioStreamModel) {
getAudioStreamModelState(model.audioStream).update { model }
}
+
+ fun setLastAudibleVolume(audioStream: AudioStream, volume: Int) {
+ lastAudibleVolumes[audioStream] = volume
+ }
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/volume/data/repository/FakeLocalMediaRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/data/repository/FakeLocalMediaRepository.kt
index 7835fc8..284bd55 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/volume/data/repository/FakeLocalMediaRepository.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/data/repository/FakeLocalMediaRepository.kt
@@ -27,20 +27,19 @@
private val volumeBySession: MutableMap<String?, Int> = mutableMapOf()
- private val mutableMediaDevices = MutableStateFlow<Collection<MediaDevice>>(emptyList())
- override val mediaDevices: StateFlow<Collection<MediaDevice>>
+ private val mutableMediaDevices = MutableStateFlow<List<MediaDevice>>(emptyList())
+ override val mediaDevices: StateFlow<List<MediaDevice>>
get() = mutableMediaDevices.asStateFlow()
private val mutableCurrentConnectedDevice = MutableStateFlow<MediaDevice?>(null)
override val currentConnectedDevice: StateFlow<MediaDevice?>
get() = mutableCurrentConnectedDevice.asStateFlow()
- private val mutableRemoteRoutingSessions =
- MutableStateFlow<Collection<RoutingSession>>(emptyList())
- override val remoteRoutingSessions: StateFlow<Collection<RoutingSession>>
+ private val mutableRemoteRoutingSessions = MutableStateFlow<List<RoutingSession>>(emptyList())
+ override val remoteRoutingSessions: StateFlow<List<RoutingSession>>
get() = mutableRemoteRoutingSessions.asStateFlow()
- fun updateMediaDevices(devices: Collection<MediaDevice>) {
+ fun updateMediaDevices(devices: List<MediaDevice>) {
mutableMediaDevices.value = devices
}
diff --git a/ravenwood/ravenwood-annotation-allowed-classes.txt b/ravenwood/ravenwood-annotation-allowed-classes.txt
index 4a4c290..eb3c55c 100644
--- a/ravenwood/ravenwood-annotation-allowed-classes.txt
+++ b/ravenwood/ravenwood-annotation-allowed-classes.txt
@@ -255,6 +255,7 @@
android.view.Display$HdrCapabilities
android.view.Display$Mode
android.view.DisplayInfo
+android.view.inputmethod.InputBinding
android.hardware.SerialManager
android.hardware.SerialManagerInternal
diff --git a/services/backup/flags.aconfig b/services/backup/flags.aconfig
index 71f2b9e..e9f959f 100644
--- a/services/backup/flags.aconfig
+++ b/services/backup/flags.aconfig
@@ -35,6 +35,15 @@
}
flag {
+ name: "enable_v_to_u_restore_for_system_components_in_allowlist"
+ namespace: "onboarding"
+ description: "Enables system components to opt in to support restore in V to U downgrade "
+ "scenario without opting in for restoreAnyVersion."
+ bug: "324233962"
+ is_fixed_read_only: true
+}
+
+flag {
name: "enable_increase_datatypes_for_agent_logging"
namespace: "onboarding"
description: "Increase the number of a supported datatypes that an agent can define for its "
diff --git a/services/backup/java/com/android/server/backup/PackageManagerBackupAgent.java b/services/backup/java/com/android/server/backup/PackageManagerBackupAgent.java
index 9f0deea..6e98e68 100644
--- a/services/backup/java/com/android/server/backup/PackageManagerBackupAgent.java
+++ b/services/backup/java/com/android/server/backup/PackageManagerBackupAgent.java
@@ -177,6 +177,10 @@
return mHasMetadata;
}
+ public int getSourceSdk() {
+ return mStoredSdkVersion;
+ }
+
public Metadata getRestoredMetadata(String packageName) {
if (mRestoredSignatures == null) {
Slog.w(TAG, "getRestoredMetadata() before metadata read!");
diff --git a/services/backup/java/com/android/server/backup/restore/PerformUnifiedRestoreTask.java b/services/backup/java/com/android/server/backup/restore/PerformUnifiedRestoreTask.java
index d85dd87..e666442 100644
--- a/services/backup/java/com/android/server/backup/restore/PerformUnifiedRestoreTask.java
+++ b/services/backup/java/com/android/server/backup/restore/PerformUnifiedRestoreTask.java
@@ -44,6 +44,7 @@
import android.content.pm.PackageManager;
import android.content.pm.PackageManager.NameNotFoundException;
import android.content.pm.PackageManagerInternal;
+import android.os.Build;
import android.os.Bundle;
import android.os.Message;
import android.os.ParcelFileDescriptor;
@@ -51,6 +52,7 @@
import android.os.RemoteException;
import android.os.SystemClock;
import android.os.UserHandle;
+import android.provider.Settings;
import android.util.EventLog;
import android.util.Slog;
@@ -82,6 +84,7 @@
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.ArrayList;
+import java.util.Arrays;
import java.util.List;
import java.util.Objects;
import java.util.Set;
@@ -158,6 +161,12 @@
// When finished call listener
private final OnTaskFinishedListener mListener;
+ // List of packages that support V-> U downgrade but do not have RestoreAnyVersion set to true.
+ private List<String> mVToUAllowlist;
+
+ // List of packages that have RestoreAnyVersion set to true but do not support V-> U downgrade.
+ private List<String> mVToUDenylist;
+
// Key/value: bookkeeping about staged data and files for agent access
private File mBackupDataName;
private File mStageName;
@@ -172,7 +181,8 @@
@VisibleForTesting
PerformUnifiedRestoreTask(
UserBackupManagerService backupManagerService,
- TransportConnection transportConnection) {
+ TransportConnection transportConnection,
+ String vToUAllowlist, String vToUDenyList) {
mListener = null;
mAgentTimeoutParameters = null;
mOperationStorage = null;
@@ -183,6 +193,8 @@
mBackupEligibilityRules = null;
this.backupManagerService = backupManagerService;
mBackupManagerMonitorEventSender = new BackupManagerMonitorEventSender(/* monitor= */ null);
+ mVToUAllowlist = createVToUList(vToUAllowlist);
+ mVToUDenylist = createVToUList(vToUDenyList);
}
// This task can assume that the wakelock is properly held for it and doesn't have to worry
@@ -223,6 +235,18 @@
backupManagerService.getAgentTimeoutParameters(),
"Timeout parameters cannot be null");
mBackupEligibilityRules = backupEligibilityRules;
+ mVToUAllowlist =
+ createVToUList(
+ Settings.Secure.getStringForUser(
+ backupManagerService.getContext().getContentResolver(),
+ Settings.Secure.V_TO_U_RESTORE_ALLOWLIST,
+ mUserId));
+ mVToUDenylist =
+ createVToUList(
+ Settings.Secure.getStringForUser(
+ backupManagerService.getContext().getContentResolver(),
+ Settings.Secure.V_TO_U_RESTORE_DENYLIST,
+ mUserId));
if (targetPackage != null) {
// Single package restore
@@ -636,60 +660,29 @@
// Data is from a "newer" version of the app than we have currently
// installed. If the app has not declared that it is prepared to
// handle this case, we do not attempt the restore.
- if ((mCurrentPackage.applicationInfo.flags
- & ApplicationInfo.FLAG_RESTORE_ANY_VERSION)
- == 0) {
- String message =
- "Source version "
- + metaInfo.versionCode
- + " > installed version "
- + mCurrentPackage.getLongVersionCode();
- Slog.w(TAG, "Package " + pkgName + ": " + message);
- Bundle monitoringExtras =
- mBackupManagerMonitorEventSender.putMonitoringExtra(
- null,
- BackupManagerMonitor.EXTRA_LOG_RESTORE_VERSION,
- metaInfo.versionCode);
- monitoringExtras =
- mBackupManagerMonitorEventSender.putMonitoringExtra(
- monitoringExtras,
- BackupManagerMonitor.EXTRA_LOG_RESTORE_ANYWAY,
- false);
- monitoringExtras = addRestoreOperationTypeToEvent(monitoringExtras);
- mBackupManagerMonitorEventSender.monitorEvent(
- BackupManagerMonitor.LOG_EVENT_ID_RESTORE_VERSION_HIGHER,
- mCurrentPackage,
- BackupManagerMonitor.LOG_EVENT_CATEGORY_BACKUP_MANAGER_POLICY,
- monitoringExtras);
- EventLog.writeEvent(EventLogTags.RESTORE_AGENT_FAILURE, pkgName, message);
- nextState = UnifiedRestoreState.RUNNING_QUEUE;
- return;
- } else {
- if (DEBUG) {
- Slog.v(
- TAG,
- "Source version "
- + metaInfo.versionCode
- + " > installed version "
- + mCurrentPackage.getLongVersionCode()
- + " but restoreAnyVersion");
+ if (mIsSystemRestore
+ && isVToUDowngrade(mPmAgent.getSourceSdk(), android.os.Build.VERSION.SDK_INT)) {
+ if (isPackageEligibleForVToURestore(mCurrentPackage)) {
+ Slog.i(TAG, "Package " + pkgName
+ + " is eligible for V to U downgrade scenario");
+ } else {
+ String message = "Package not eligible for V to U downgrade scenario";
+ Slog.i(TAG, pkgName + " : " + message);
+ EventLog.writeEvent(EventLogTags.RESTORE_AGENT_FAILURE, pkgName, message);
+ nextState = UnifiedRestoreState.RUNNING_QUEUE;
+ return;
}
- Bundle monitoringExtras =
- mBackupManagerMonitorEventSender.putMonitoringExtra(
- null,
- BackupManagerMonitor.EXTRA_LOG_RESTORE_VERSION,
- metaInfo.versionCode);
- monitoringExtras =
- mBackupManagerMonitorEventSender.putMonitoringExtra(
- monitoringExtras,
- BackupManagerMonitor.EXTRA_LOG_RESTORE_ANYWAY,
- true);
- monitoringExtras = addRestoreOperationTypeToEvent(monitoringExtras);
- mBackupManagerMonitorEventSender.monitorEvent(
- BackupManagerMonitor.LOG_EVENT_ID_RESTORE_VERSION_HIGHER,
- mCurrentPackage,
- BackupManagerMonitor.LOG_EVENT_CATEGORY_BACKUP_MANAGER_POLICY,
- monitoringExtras);
+ } else {
+ if ((mCurrentPackage.applicationInfo.flags
+ & ApplicationInfo.FLAG_RESTORE_ANY_VERSION)
+ == 0) {
+ // Downgrade scenario with RestoreAnyVersion flag off
+ logDowngradeScenario(/* isRestoreAnyVersion */ false, metaInfo);
+ nextState = UnifiedRestoreState.RUNNING_QUEUE;
+ return;
+ } else {
+ logDowngradeScenario(/* isRestoreAnyVersion */ true, metaInfo);
+ }
}
}
@@ -1673,4 +1666,86 @@
return mBackupManagerMonitorEventSender.putMonitoringExtra(
extras, BackupManagerMonitor.EXTRA_LOG_OPERATION_TYPE, RESTORE);
}
+
+ // checks the sdk of the target/source device for a B&R operation.
+ // system components can opt in/out of V->U restore via allowlists. All other apps are
+ // not impacted
+ @SuppressWarnings("AndroidFrameworkCompatChange")
+ @VisibleForTesting
+ protected boolean isVToUDowngrade(int sourceSdk, int targetSdk) {
+ // We assume that if the source sdk is greater than U then the source is V.
+ return Flags.enableVToURestoreForSystemComponentsInAllowlist()
+ && (sourceSdk > Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
+ && (targetSdk == Build.VERSION_CODES.UPSIDE_DOWN_CAKE);
+ }
+
+ @VisibleForTesting
+ protected List<String> createVToUList(@Nullable String listString) {
+ // The allowlist/denylist is stored as a comma-separated list of package names
+ List<String> list = new ArrayList<>();
+ if (listString != null) {
+ list = Arrays.asList(listString.split(","));
+ }
+ return list;
+ }
+
+ @VisibleForTesting
+ protected boolean isPackageEligibleForVToURestore(PackageInfo mCurrentPackage) {
+ // A package is eligible for V to U downgrade restore if either:
+ // - The package has restoreAnyVersion set to false and is part of the V to U allowlist
+ // (and not in the denylist)
+ // - The package has restoreAnyVersion set to true and is not part of the denylist
+ if (mVToUDenylist.contains(mCurrentPackage.packageName)){
+ return false;
+ } else if ((mCurrentPackage.applicationInfo.flags
+ & ApplicationInfo.FLAG_RESTORE_ANY_VERSION)
+ == 0) {
+ // package has restoreAnyVersion set to false
+ return mVToUAllowlist.contains(mCurrentPackage.packageName);
+ } else {
+ // package has restoreAnyVersion set to true and is nor in denylist
+ return true;
+ }
+ }
+
+ private void logDowngradeScenario(boolean isRestoreAnyVersion, Metadata metaInfo) {
+ Bundle monitoringExtras =
+ mBackupManagerMonitorEventSender.putMonitoringExtra(
+ null,
+ BackupManagerMonitor.EXTRA_LOG_RESTORE_VERSION,
+ metaInfo.versionCode);
+ String message;
+ if (isRestoreAnyVersion) {
+ monitoringExtras =
+ mBackupManagerMonitorEventSender.putMonitoringExtra(
+ monitoringExtras,
+ BackupManagerMonitor.EXTRA_LOG_RESTORE_ANYWAY,
+ true);
+ message = "Source version "
+ + metaInfo.versionCode
+ + " > installed version "
+ + mCurrentPackage.getLongVersionCode()
+ + " but restoreAnyVersion";
+ } else {
+ monitoringExtras =
+ mBackupManagerMonitorEventSender.putMonitoringExtra(
+ monitoringExtras,
+ BackupManagerMonitor.EXTRA_LOG_RESTORE_ANYWAY,
+ false);
+ message = "Source version "
+ + metaInfo.versionCode
+ + " > installed version "
+ + mCurrentPackage.getLongVersionCode();
+ EventLog.writeEvent(EventLogTags.RESTORE_AGENT_FAILURE, mCurrentPackage.packageName,
+ message);
+ }
+ Slog.i(TAG, "Package " + mCurrentPackage.packageName + ": " + message);
+ monitoringExtras = addRestoreOperationTypeToEvent(monitoringExtras);
+ mBackupManagerMonitorEventSender.monitorEvent(
+ BackupManagerMonitor.LOG_EVENT_ID_RESTORE_VERSION_HIGHER,
+ mCurrentPackage,
+ BackupManagerMonitor.LOG_EVENT_CATEGORY_BACKUP_MANAGER_POLICY,
+ monitoringExtras);
+ }
+
}
diff --git a/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java b/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java
index 767f54d..966fe5b 100644
--- a/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java
+++ b/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java
@@ -120,6 +120,7 @@
static final String[] sDeviceConfigAconfigScopes = new String[] {
"accessibility",
"android_core_networking",
+ "android_stylus",
"aoc",
"app_widgets",
"arc_next",
diff --git a/services/core/java/com/android/server/display/BrightnessThrottler.java b/services/core/java/com/android/server/display/BrightnessThrottler.java
index bba5ba3..631e751 100644
--- a/services/core/java/com/android/server/display/BrightnessThrottler.java
+++ b/services/core/java/com/android/server/display/BrightnessThrottler.java
@@ -37,9 +37,11 @@
import com.android.internal.annotations.VisibleForTesting;
import com.android.server.display.DisplayDeviceConfig.ThermalBrightnessThrottlingData;
import com.android.server.display.DisplayDeviceConfig.ThermalBrightnessThrottlingData.ThrottlingLevel;
+import com.android.server.display.config.SensorData;
import com.android.server.display.feature.DeviceConfigParameterProvider;
import com.android.server.display.utils.DebugUtils;
import com.android.server.display.utils.DeviceConfigParsingUtils;
+import com.android.server.display.utils.SensorUtils;
import java.io.PrintWriter;
import java.util.HashMap;
@@ -79,7 +81,7 @@
// Maps the throttling ID to the data. Sourced from DisplayDeviceConfig.
@NonNull
- private HashMap<String, ThermalBrightnessThrottlingData> mDdcThermalThrottlingDataMap;
+ private Map<String, ThermalBrightnessThrottlingData> mDdcThermalThrottlingDataMap;
// Current throttling data being used.
// Null if we do not support throttling.
@@ -97,6 +99,10 @@
// The brightness throttling configuration that should be used.
private String mThermalBrightnessThrottlingDataId;
+ // Temperature Sensor to be monitored for throttling.
+ @NonNull
+ private SensorData mTempSensor;
+
// This is a collection of brightness throttling data that has been written as overrides from
// the DeviceConfig. This will always take priority over the display device config data.
// We need to store the data for every display device, so we do not need to update this each
@@ -121,17 +127,19 @@
BrightnessThrottler(Handler handler, Runnable throttlingChangeCallback, String uniqueDisplayId,
String throttlingDataId,
- @NonNull HashMap<String, ThermalBrightnessThrottlingData>
- thermalBrightnessThrottlingDataMap) {
- this(new Injector(), handler, handler, throttlingChangeCallback,
- uniqueDisplayId, throttlingDataId, thermalBrightnessThrottlingDataMap);
+ @NonNull DisplayDeviceConfig displayDeviceConfig) {
+ this(new Injector(), handler, handler, throttlingChangeCallback, uniqueDisplayId,
+ throttlingDataId,
+ displayDeviceConfig.getThermalBrightnessThrottlingDataMapByThrottlingId(),
+ displayDeviceConfig.getTempSensor());
}
@VisibleForTesting
BrightnessThrottler(Injector injector, Handler handler, Handler deviceConfigHandler,
Runnable throttlingChangeCallback, String uniqueDisplayId, String throttlingDataId,
- @NonNull HashMap<String, ThermalBrightnessThrottlingData>
- thermalBrightnessThrottlingDataMap) {
+ @NonNull Map<String, ThermalBrightnessThrottlingData>
+ thermalBrightnessThrottlingDataMap,
+ @NonNull SensorData tempSensor) {
mInjector = injector;
mHandler = handler;
@@ -147,7 +155,7 @@
mDdcThermalThrottlingDataMap = thermalBrightnessThrottlingDataMap;
loadThermalBrightnessThrottlingDataFromDeviceConfig();
loadThermalBrightnessThrottlingDataFromDisplayDeviceConfig(mDdcThermalThrottlingDataMap,
- mThermalBrightnessThrottlingDataId, mUniqueDisplayId);
+ tempSensor, mThermalBrightnessThrottlingDataId, mUniqueDisplayId);
}
boolean deviceSupportsThrottling() {
@@ -180,12 +188,14 @@
}
void loadThermalBrightnessThrottlingDataFromDisplayDeviceConfig(
- HashMap<String, ThermalBrightnessThrottlingData> ddcThrottlingDataMap,
+ Map<String, ThermalBrightnessThrottlingData> ddcThrottlingDataMap,
+ SensorData tempSensor,
String brightnessThrottlingDataId,
String uniqueDisplayId) {
mDdcThermalThrottlingDataMap = ddcThrottlingDataMap;
mThermalBrightnessThrottlingDataId = brightnessThrottlingDataId;
mUniqueDisplayId = uniqueDisplayId;
+ mTempSensor = tempSensor;
resetThermalThrottlingData();
}
@@ -310,7 +320,7 @@
}
if (deviceSupportsThrottling()) {
- mSkinThermalStatusObserver.startObserving();
+ mSkinThermalStatusObserver.startObserving(mTempSensor);
}
}
@@ -357,6 +367,7 @@
private final class SkinThermalStatusObserver extends IThermalEventListener.Stub {
private final Injector mInjector;
private final Handler mHandler;
+ private SensorData mObserverTempSensor;
private IThermalService mThermalService;
private boolean mStarted;
@@ -371,28 +382,51 @@
if (DEBUG) {
Slog.d(TAG, "New thermal throttling status = " + temp.getStatus());
}
+
+ if (mObserverTempSensor.name != null
+ && !mObserverTempSensor.name.equals(temp.getName())) {
+ Slog.i(TAG, "Skipping thermal throttling notification as monitored sensor: "
+ + mObserverTempSensor.name
+ + " != notified sensor: "
+ + temp.getName());
+ return;
+ }
mHandler.post(() -> {
final @Temperature.ThrottlingStatus int status = temp.getStatus();
thermalStatusChanged(status);
});
}
- void startObserving() {
- if (mStarted) {
+ void startObserving(SensorData tempSensor) {
+ if (!mStarted || mObserverTempSensor == null) {
+ mObserverTempSensor = tempSensor;
+ registerThermalListener();
+ return;
+ }
+
+ String curType = mObserverTempSensor.type;
+ mObserverTempSensor = tempSensor;
+ if (curType.equals(tempSensor.type)) {
if (DEBUG) {
Slog.d(TAG, "Thermal status observer already started");
}
return;
}
+ stopObserving();
+ registerThermalListener();
+ }
+
+ void registerThermalListener() {
mThermalService = mInjector.getThermalService();
if (mThermalService == null) {
Slog.e(TAG, "Could not observe thermal status. Service not available");
return;
}
+ int temperatureType = SensorUtils.getSensorTemperatureType(mObserverTempSensor);
try {
// We get a callback immediately upon registering so there's no need to query
// for the current value.
- mThermalService.registerThermalEventListenerWithType(this, Temperature.TYPE_SKIN);
+ mThermalService.registerThermalEventListenerWithType(this, temperatureType);
mStarted = true;
} catch (RemoteException e) {
Slog.e(TAG, "Failed to register thermal status listener", e);
@@ -418,6 +452,7 @@
void dump(PrintWriter writer) {
writer.println(" SkinThermalStatusObserver:");
writer.println(" mStarted: " + mStarted);
+ writer.println(" mObserverTempSensor: " + mObserverTempSensor);
if (mThermalService != null) {
writer.println(" ThermalService available");
} else {
diff --git a/services/core/java/com/android/server/display/DisplayDeviceConfig.java b/services/core/java/com/android/server/display/DisplayDeviceConfig.java
index d1374a5..9b2dcc5 100644
--- a/services/core/java/com/android/server/display/DisplayDeviceConfig.java
+++ b/services/core/java/com/android/server/display/DisplayDeviceConfig.java
@@ -384,6 +384,10 @@
* </point>
* </supportedModes>
* </proxSensor>
+ * <tempSensor>
+ * <type>DISPLAY</type>
+ * <name>VIRTUAL-SKIN-DISPLAY</name>
+ * </tempSensor>
*
* <ambientLightHorizonLong>10001</ambientLightHorizonLong>
* <ambientLightHorizonShort>2001</ambientLightHorizonShort>
@@ -625,6 +629,12 @@
@Nullable
private SensorData mProximitySensor;
+ // The details of the temperature sensor associated with this display.
+ // Throttling will be based on thermal status of this sensor.
+ // For empty values default back to sensor of TYPE_SKIN.
+ @NonNull
+ private SensorData mTempSensor;
+
private final List<RefreshRateLimitation> mRefreshRateLimitations =
new ArrayList<>(2 /*initialCapacity*/);
@@ -821,10 +831,10 @@
private String mLowBlockingZoneThermalMapId = null;
private String mHighBlockingZoneThermalMapId = null;
- private final HashMap<String, ThermalBrightnessThrottlingData>
+ private final Map<String, ThermalBrightnessThrottlingData>
mThermalBrightnessThrottlingDataMapByThrottlingId = new HashMap<>();
- private final HashMap<String, PowerThrottlingData>
+ private final Map<String, PowerThrottlingData>
mPowerThrottlingDataMapByThrottlingId = new HashMap<>();
private final Map<String, SparseArray<SurfaceControl.RefreshRateRange>>
@@ -1489,6 +1499,13 @@
return mProximitySensor;
}
+ /**
+ * @return temperature sensor data associated with the display.
+ */
+ public SensorData getTempSensor() {
+ return mTempSensor;
+ }
+
boolean isAutoBrightnessAvailable() {
return mAutoBrightnessAvailable;
}
@@ -1539,7 +1556,7 @@
/**
* @return brightness throttling configuration data for this display, for each throttling id.
*/
- public HashMap<String, ThermalBrightnessThrottlingData>
+ public Map<String, ThermalBrightnessThrottlingData>
getThermalBrightnessThrottlingDataMapByThrottlingId() {
return mThermalBrightnessThrottlingDataMapByThrottlingId;
}
@@ -1558,7 +1575,7 @@
/**
* @return power throttling configuration data for this display, for each throttling id.
**/
- public HashMap<String, PowerThrottlingData>
+ public Map<String, PowerThrottlingData>
getPowerThrottlingDataMapByThrottlingId() {
return mPowerThrottlingDataMapByThrottlingId;
}
@@ -1871,6 +1888,7 @@
+ "mAmbientLightSensor=" + mAmbientLightSensor
+ ", mScreenOffBrightnessSensor=" + mScreenOffBrightnessSensor
+ ", mProximitySensor=" + mProximitySensor
+ + ", mTempSensor=" + mTempSensor
+ ", mRefreshRateLimitations= " + Arrays.toString(mRefreshRateLimitations.toArray())
+ ", mDensityMapping= " + mDensityMapping
+ ", mAutoBrightnessBrighteningLightDebounce= "
@@ -1972,6 +1990,7 @@
mContext.getResources());
mScreenOffBrightnessSensor = SensorData.loadScreenOffBrightnessSensorConfig(config);
mProximitySensor = SensorData.loadProxSensorConfig(config);
+ mTempSensor = SensorData.loadTempSensorConfig(mFlags, config);
loadAmbientHorizonFromDdc(config);
loadBrightnessChangeThresholds(config);
loadAutoBrightnessConfigValues(config);
@@ -1999,6 +2018,7 @@
loadBrightnessRampsFromConfigXml();
mAmbientLightSensor = SensorData.loadAmbientLightSensorConfig(mContext.getResources());
mProximitySensor = SensorData.loadSensorUnspecifiedConfig();
+ mTempSensor = SensorData.loadTempSensorUnspecifiedConfig();
loadBrightnessChangeThresholdsFromXml();
loadAutoBrightnessConfigsFromConfigXml();
loadAutoBrightnessAvailableFromConfigXml();
@@ -2026,6 +2046,7 @@
setSimpleMappingStrategyValues();
mAmbientLightSensor = SensorData.loadAmbientLightSensorConfig(mContext.getResources());
mProximitySensor = SensorData.loadSensorUnspecifiedConfig();
+ mTempSensor = SensorData.loadTempSensorUnspecifiedConfig();
loadAutoBrightnessAvailableFromConfigXml();
}
diff --git a/services/core/java/com/android/server/display/DisplayPowerController.java b/services/core/java/com/android/server/display/DisplayPowerController.java
index d5863a7..3965d55 100644
--- a/services/core/java/com/android/server/display/DisplayPowerController.java
+++ b/services/core/java/com/android/server/display/DisplayPowerController.java
@@ -861,6 +861,7 @@
mThermalBrightnessThrottlingDataId = thermalBrightnessThrottlingDataId;
mBrightnessThrottler.loadThermalBrightnessThrottlingDataFromDisplayDeviceConfig(
config.getThermalBrightnessThrottlingDataMapByThrottlingId(),
+ config.getTempSensor(),
mThermalBrightnessThrottlingDataId,
mUniqueDisplayId);
}
@@ -923,6 +924,7 @@
mBrightnessRangeController.loadFromConfig(hbmMetadata, token, info, mDisplayDeviceConfig);
mBrightnessThrottler.loadThermalBrightnessThrottlingDataFromDisplayDeviceConfig(
mDisplayDeviceConfig.getThermalBrightnessThrottlingDataMapByThrottlingId(),
+ mDisplayDeviceConfig.getTempSensor(),
mThermalBrightnessThrottlingDataId, mUniqueDisplayId);
}
@@ -1996,7 +1998,7 @@
postBrightnessChangeRunnable();
}, mUniqueDisplayId,
mLogicalDisplay.getDisplayInfoLocked().thermalBrightnessThrottlingDataId,
- ddConfig.getThermalBrightnessThrottlingDataMapByThrottlingId());
+ ddConfig);
}
private void blockScreenOn() {
diff --git a/services/core/java/com/android/server/display/brightness/clamper/BrightnessClamperController.java b/services/core/java/com/android/server/display/brightness/clamper/BrightnessClamperController.java
index bc5fcb4..18e8fab 100644
--- a/services/core/java/com/android/server/display/brightness/clamper/BrightnessClamperController.java
+++ b/services/core/java/com/android/server/display/brightness/clamper/BrightnessClamperController.java
@@ -38,6 +38,7 @@
import com.android.server.display.DisplayDeviceConfig.PowerThrottlingData;
import com.android.server.display.DisplayDeviceConfig.ThermalBrightnessThrottlingData;
import com.android.server.display.brightness.BrightnessReason;
+import com.android.server.display.config.SensorData;
import com.android.server.display.feature.DeviceConfigParameterProvider;
import com.android.server.display.feature.DisplayManagerFlags;
@@ -336,5 +337,10 @@
public float getBrightnessWearBedtimeModeCap() {
return mDisplayDeviceConfig.getBrightnessCapForWearBedtimeMode();
}
+
+ @NonNull
+ public SensorData getTempSensor() {
+ return mDisplayDeviceConfig.getTempSensor();
+ }
}
}
diff --git a/services/core/java/com/android/server/display/brightness/clamper/BrightnessThermalClamper.java b/services/core/java/com/android/server/display/brightness/clamper/BrightnessThermalClamper.java
index 944a8a6..4498258 100644
--- a/services/core/java/com/android/server/display/brightness/clamper/BrightnessThermalClamper.java
+++ b/services/core/java/com/android/server/display/brightness/clamper/BrightnessThermalClamper.java
@@ -35,8 +35,10 @@
import com.android.internal.annotations.VisibleForTesting;
import com.android.server.display.DisplayDeviceConfig.ThermalBrightnessThrottlingData;
import com.android.server.display.DisplayDeviceConfig.ThermalBrightnessThrottlingData.ThrottlingLevel;
+import com.android.server.display.config.SensorData;
import com.android.server.display.feature.DeviceConfigParameterProvider;
import com.android.server.display.utils.DeviceConfigParsingUtils;
+import com.android.server.display.utils.SensorUtils;
import java.io.PrintWriter;
import java.util.List;
@@ -49,9 +51,8 @@
BrightnessClamper<BrightnessThermalClamper.ThermalData> {
private static final String TAG = "BrightnessThermalClamper";
-
- @Nullable
- private final IThermalService mThermalService;
+ @NonNull
+ private final ThermalStatusObserver mThermalStatusObserver;
@NonNull
private final DeviceConfigParameterProvider mConfigParameterProvider;
// data from DeviceConfig, for all displays, for all dataSets
@@ -66,7 +67,6 @@
// otherwise mDataFromDeviceConfig
@Nullable
private ThermalBrightnessThrottlingData mThermalThrottlingDataActive = null;
- private boolean mStarted = false;
@Nullable
private String mUniqueDisplayId = null;
@Nullable
@@ -74,14 +74,6 @@
@Temperature.ThrottlingStatus
private int mThrottlingStatus = Temperature.THROTTLING_NONE;
- private final IThermalEventListener mThermalEventListener = new IThermalEventListener.Stub() {
- @Override
- public void notifyThrottling(Temperature temperature) {
- @Temperature.ThrottlingStatus int status = temperature.getStatus();
- mHandler.post(() -> thermalStatusChanged(status));
- }
- };
-
private final BiFunction<String, String, ThrottlingLevel> mDataPointMapper = (key, value) -> {
try {
int status = DeviceConfigParsingUtils.parseThermalStatus(key);
@@ -105,12 +97,11 @@
BrightnessThermalClamper(Injector injector, Handler handler,
ClamperChangeListener listener, ThermalData thermalData) {
super(handler, listener);
- mThermalService = injector.getThermalService();
mConfigParameterProvider = injector.getDeviceConfigParameterProvider();
+ mThermalStatusObserver = new ThermalStatusObserver(injector, handler);
mHandler.post(() -> {
setDisplayData(thermalData);
loadOverrideData();
- start();
});
}
@@ -139,32 +130,19 @@
@Override
void stop() {
- if (!mStarted) {
- return;
- }
- try {
- mThermalService.unregisterThermalEventListener(mThermalEventListener);
- } catch (RemoteException e) {
- Slog.e(TAG, "Failed to unregister thermal status listener", e);
- }
- mStarted = false;
+ mThermalStatusObserver.stopObserving();
}
@Override
void dump(PrintWriter writer) {
writer.println("BrightnessThermalClamper:");
- writer.println(" mStarted: " + mStarted);
- if (mThermalService != null) {
- writer.println(" ThermalService available");
- } else {
- writer.println(" ThermalService not available");
- }
writer.println(" mThrottlingStatus: " + mThrottlingStatus);
writer.println(" mUniqueDisplayId: " + mUniqueDisplayId);
writer.println(" mDataId: " + mDataId);
writer.println(" mDataOverride: " + mThermalThrottlingDataOverride);
writer.println(" mDataFromDeviceConfig: " + mThermalThrottlingDataFromDeviceConfig);
writer.println(" mDataActive: " + mThermalThrottlingDataActive);
+ mThermalStatusObserver.dump(writer);
super.dump(writer);
}
@@ -193,6 +171,7 @@
Slog.wtf(TAG,
"Thermal throttling data is missing for thermalThrottlingDataId=" + mDataId);
}
+ mThermalStatusObserver.registerSensor(data.getTempSensor());
}
private void recalculateBrightnessCap() {
@@ -226,19 +205,91 @@
}
}
- private void start() {
- if (mThermalService == null) {
- Slog.e(TAG, "Could not observe thermal status. Service not available");
- return;
+
+ private final class ThermalStatusObserver extends IThermalEventListener.Stub {
+ private final Injector mInjector;
+ private final Handler mHandler;
+ private IThermalService mThermalService;
+ private boolean mStarted;
+ private SensorData mObserverTempSensor;
+
+ ThermalStatusObserver(Injector injector, Handler handler) {
+ mInjector = injector;
+ mHandler = handler;
+ mStarted = false;
}
- try {
- // We get a callback immediately upon registering so there's no need to query
- // for the current value.
- mThermalService.registerThermalEventListenerWithType(mThermalEventListener,
- Temperature.TYPE_SKIN);
- mStarted = true;
- } catch (RemoteException e) {
- Slog.e(TAG, "Failed to register thermal status listener", e);
+
+ void registerSensor(SensorData tempSensor) {
+ if (!mStarted || mObserverTempSensor == null) {
+ mObserverTempSensor = tempSensor;
+ registerThermalListener();
+ return;
+ }
+
+ String curType = mObserverTempSensor.type;
+ mObserverTempSensor = tempSensor;
+ if (curType.equals(tempSensor.type)) {
+ Slog.d(TAG, "Thermal status observer already started");
+ return;
+ }
+ stopObserving();
+ registerThermalListener();
+ }
+
+ void registerThermalListener() {
+ mThermalService = mInjector.getThermalService();
+ if (mThermalService == null) {
+ Slog.e(TAG, "Could not observe thermal status. Service not available");
+ return;
+ }
+ int temperatureType = SensorUtils.getSensorTemperatureType(mObserverTempSensor);
+ try {
+ // We get a callback immediately upon registering so there's no need to query
+ // for the current value.
+ mThermalService.registerThermalEventListenerWithType(this, temperatureType);
+ mStarted = true;
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Failed to register thermal status listener", e);
+ }
+ }
+
+ @Override
+ public void notifyThrottling(Temperature temp) {
+ Slog.d(TAG, "New thermal throttling status = " + temp.getStatus());
+ if (mObserverTempSensor.name != null
+ && !mObserverTempSensor.name.equals(temp.getName())) {
+ Slog.i(TAG, "Skipping thermal throttling notification as monitored sensor: "
+ + mObserverTempSensor.name
+ + " != notified sensor: "
+ + temp.getName());
+ return;
+ }
+ @Temperature.ThrottlingStatus int status = temp.getStatus();
+ mHandler.post(() -> thermalStatusChanged(status));
+ }
+
+ void stopObserving() {
+ if (!mStarted) {
+ return;
+ }
+ try {
+ mThermalService.unregisterThermalEventListener(this);
+ mStarted = false;
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Failed to unregister thermal status listener", e);
+ }
+ mThermalService = null;
+ }
+
+ void dump(PrintWriter writer) {
+ writer.println(" ThermalStatusObserver:");
+ writer.println(" mStarted: " + mStarted);
+ writer.println(" mObserverTempSensor: " + mObserverTempSensor);
+ if (mThermalService != null) {
+ writer.println(" ThermalService available");
+ } else {
+ writer.println(" ThermalService not available");
+ }
}
}
@@ -251,6 +302,9 @@
@Nullable
ThermalBrightnessThrottlingData getThermalBrightnessThrottlingData();
+
+ @NonNull
+ SensorData getTempSensor();
}
@VisibleForTesting
diff --git a/services/core/java/com/android/server/display/config/SensorData.java b/services/core/java/com/android/server/display/config/SensorData.java
index 3bb35bf..8e716f8 100644
--- a/services/core/java/com/android/server/display/config/SensorData.java
+++ b/services/core/java/com/android/server/display/config/SensorData.java
@@ -22,6 +22,7 @@
import android.text.TextUtils;
import com.android.internal.annotations.VisibleForTesting;
+import com.android.server.display.feature.DisplayManagerFlags;
import java.util.ArrayList;
import java.util.Collections;
@@ -32,6 +33,9 @@
*/
public class SensorData {
+ public static final String TEMPERATURE_TYPE_DISPLAY = "DISPLAY";
+ public static final String TEMPERATURE_TYPE_SKIN = "SKIN";
+
@Nullable
public final String type;
@Nullable
@@ -143,6 +147,32 @@
}
/**
+ * Loads temperature sensor data for no config case. (Type: SKIN, Name: null)
+ */
+ public static SensorData loadTempSensorUnspecifiedConfig() {
+ return new SensorData(TEMPERATURE_TYPE_SKIN, null);
+ }
+
+ /**
+ * Loads temperature sensor data from given display config.
+ * If empty or null config given default to (Type: SKIN, Name: null)
+ */
+ public static SensorData loadTempSensorConfig(DisplayManagerFlags flags,
+ DisplayConfiguration config) {
+ SensorDetails sensorDetails = config.getTempSensor();
+ if (!flags.isSensorBasedBrightnessThrottlingEnabled() || sensorDetails == null) {
+ return new SensorData(TEMPERATURE_TYPE_SKIN, null);
+ }
+ String name = sensorDetails.getName();
+ String type = sensorDetails.getType();
+ if (TextUtils.isEmpty(type) || TextUtils.isEmpty(name)) {
+ type = TEMPERATURE_TYPE_SKIN;
+ name = null;
+ }
+ return new SensorData(type, name);
+ }
+
+ /**
* Loads sensor unspecified config, this means system should use default sensor.
* See also {@link com.android.server.display.utils.SensorUtils}
*/
diff --git a/services/core/java/com/android/server/display/feature/DisplayManagerFlags.java b/services/core/java/com/android/server/display/feature/DisplayManagerFlags.java
index 1ae2559..516d4b1 100644
--- a/services/core/java/com/android/server/display/feature/DisplayManagerFlags.java
+++ b/services/core/java/com/android/server/display/feature/DisplayManagerFlags.java
@@ -121,6 +121,11 @@
Flags::refreshRateVotingTelemetry
);
+ private final FlagState mSensorBasedBrightnessThrottling = new FlagState(
+ Flags.FLAG_SENSOR_BASED_BRIGHTNESS_THROTTLING,
+ Flags::sensorBasedBrightnessThrottling
+ );
+
/**
* @return {@code true} if 'port' is allowed in display layout configuration file.
*/
@@ -247,6 +252,10 @@
return mRefreshRateVotingTelemetry.isEnabled();
}
+ public boolean isSensorBasedBrightnessThrottlingEnabled() {
+ return mSensorBasedBrightnessThrottling.isEnabled();
+ }
+
/**
* dumps all flagstates
* @param pw printWriter
@@ -270,6 +279,7 @@
pw.println(" " + mAutoBrightnessModesFlagState);
pw.println(" " + mFastHdrTransitions);
pw.println(" " + mRefreshRateVotingTelemetry);
+ pw.println(" " + mSensorBasedBrightnessThrottling);
}
private static class FlagState {
diff --git a/services/core/java/com/android/server/display/feature/display_flags.aconfig b/services/core/java/com/android/server/display/feature/display_flags.aconfig
index c2f52b5..63ab3a9 100644
--- a/services/core/java/com/android/server/display/feature/display_flags.aconfig
+++ b/services/core/java/com/android/server/display/feature/display_flags.aconfig
@@ -184,3 +184,11 @@
bug: "310029108"
is_fixed_read_only: true
}
+
+flag {
+ name: "sensor_based_brightness_throttling"
+ namespace: "display_manager"
+ description: "Feature flag for enabling brightness throttling using sensor from config."
+ bug: "294900859"
+ is_fixed_read_only: true
+}
diff --git a/services/core/java/com/android/server/display/utils/SensorUtils.java b/services/core/java/com/android/server/display/utils/SensorUtils.java
index 8b9fe108..c63473a 100644
--- a/services/core/java/com/android/server/display/utils/SensorUtils.java
+++ b/services/core/java/com/android/server/display/utils/SensorUtils.java
@@ -16,9 +16,11 @@
package com.android.server.display.utils;
+import android.annotation.NonNull;
import android.annotation.Nullable;
import android.hardware.Sensor;
import android.hardware.SensorManager;
+import android.os.Temperature;
import android.text.TextUtils;
import com.android.server.display.config.SensorData;
@@ -70,4 +72,17 @@
return null;
}
+ /**
+ * Convert string temperature type to its corresponding integer value.
+ */
+ public static int getSensorTemperatureType(@NonNull SensorData tempSensor) {
+ if (tempSensor.type.equalsIgnoreCase(SensorData.TEMPERATURE_TYPE_DISPLAY)) {
+ return Temperature.TYPE_DISPLAY;
+ } else if (tempSensor.type.equalsIgnoreCase(SensorData.TEMPERATURE_TYPE_SKIN)) {
+ return Temperature.TYPE_SKIN;
+ }
+ throw new IllegalArgumentException(
+ "tempSensor doesn't support type: " + tempSensor.type);
+ }
+
}
diff --git a/services/core/java/com/android/server/input/debug/FocusEventDebugView.java b/services/core/java/com/android/server/input/debug/FocusEventDebugView.java
index 3ffd2e1..b30f5ec 100644
--- a/services/core/java/com/android/server/input/debug/FocusEventDebugView.java
+++ b/services/core/java/com/android/server/input/debug/FocusEventDebugView.java
@@ -313,7 +313,7 @@
case KeyEvent.KEYCODE_FORWARD_DEL:
return "\u2326";
case KeyEvent.KEYCODE_ESCAPE:
- return "ESC";
+ return "esc";
case KeyEvent.KEYCODE_DPAD_UP:
return "\u2191";
case KeyEvent.KEYCODE_DPAD_DOWN:
@@ -330,6 +330,14 @@
return "\u2198";
case KeyEvent.KEYCODE_DPAD_DOWN_LEFT:
return "\u2199";
+ case KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE:
+ return "\u23ef";
+ case KeyEvent.KEYCODE_HOME:
+ return "\u25ef";
+ case KeyEvent.KEYCODE_BACK:
+ return "\u25c1";
+ case KeyEvent.KEYCODE_RECENT_APPS:
+ return "\u25a1";
default:
break;
}
diff --git a/services/core/java/com/android/server/inputmethod/IInputMethodClientInvoker.java b/services/core/java/com/android/server/inputmethod/IInputMethodClientInvoker.java
index 84a59b4..7251ac4 100644
--- a/services/core/java/com/android/server/inputmethod/IInputMethodClientInvoker.java
+++ b/services/core/java/com/android/server/inputmethod/IInputMethodClientInvoker.java
@@ -43,6 +43,9 @@
* the given {@link Handler} thread if {@link IInputMethodClient} is not a proxy object. Be careful
* about its call ordering characteristics.</p>
*/
+// TODO(b/322895594) Mark this class to be host side test compatible once enabling fw/services in
+// Ravenwood (mark this class with @RavenwoodKeepWholeClass and #create with @RavenwoodReplace,
+// so Ravenwood can properly swap create method during test execution).
final class IInputMethodClientInvoker {
private static final String TAG = InputMethodManagerService.TAG;
private static final boolean DEBUG = InputMethodManagerService.DEBUG;
@@ -64,6 +67,16 @@
return new IInputMethodClientInvoker(inputMethodClient, isProxy, isProxy ? null : handler);
}
+ @AnyThread
+ @Nullable
+ static IInputMethodClientInvoker create$ravenwood(
+ @Nullable IInputMethodClient inputMethodClient, @NonNull Handler handler) {
+ if (inputMethodClient == null) {
+ return null;
+ }
+ return new IInputMethodClientInvoker(inputMethodClient, true, null);
+ }
+
private IInputMethodClientInvoker(@NonNull IInputMethodClient target,
boolean isProxy, @Nullable Handler handler) {
mTarget = target;
diff --git a/services/core/java/com/android/server/media/MediaSession2Record.java b/services/core/java/com/android/server/media/MediaSession2Record.java
index db70ce2..a110e56 100644
--- a/services/core/java/com/android/server/media/MediaSession2Record.java
+++ b/services/core/java/com/android/server/media/MediaSession2Record.java
@@ -40,6 +40,7 @@
private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
private final Object mLock = new Object();
+ private final int mUniqueId;
@GuardedBy("mLock")
private final Session2Token mSessionToken;
@GuardedBy("mLock")
@@ -63,11 +64,13 @@
MediaSessionService service,
Looper handlerLooper,
int pid,
- int policies) {
+ int policies,
+ int uniqueId) {
// The lock is required to prevent `Controller2Callback` from using partially initialized
// `MediaSession2Record.this`.
synchronized (mLock) {
mSessionToken = sessionToken;
+ mUniqueId = uniqueId;
mService = service;
mHandlerExecutor = new HandlerExecutor(new Handler(handlerLooper));
mController = new MediaController2.Builder(service.getContext(), sessionToken)
@@ -98,6 +101,13 @@
}
@Override
+ public int getUniqueId() {
+ synchronized (mLock) {
+ return mUniqueId;
+ }
+ }
+
+ @Override
public String getPackageName() {
return mSessionToken.getPackageName();
}
@@ -200,6 +210,7 @@
@Override
public void dump(PrintWriter pw, String prefix) {
+ pw.println(prefix + "uniqueId=" + mUniqueId);
pw.println(prefix + "token=" + mSessionToken);
pw.println(prefix + "controller=" + mController);
@@ -209,8 +220,7 @@
@Override
public String toString() {
- // TODO(jaewan): Also add getId().
- return getPackageName() + " (userId=" + getUserId() + ")";
+ return getPackageName() + "/" + mUniqueId + " (userId=" + getUserId() + ")";
}
private class Controller2Callback extends MediaController2.ControllerCallback {
diff --git a/services/core/java/com/android/server/media/MediaSessionRecord.java b/services/core/java/com/android/server/media/MediaSessionRecord.java
index 53f780e..1552704 100644
--- a/services/core/java/com/android/server/media/MediaSessionRecord.java
+++ b/services/core/java/com/android/server/media/MediaSessionRecord.java
@@ -173,6 +173,7 @@
private final int mUserId;
private final String mPackageName;
private final String mTag;
+ private final int mUniqueId;
private final Bundle mSessionInfo;
private final ControllerStub mController;
private final MediaSession.Token mSessionToken;
@@ -223,15 +224,25 @@
private int mPolicies;
- public MediaSessionRecord(int ownerPid, int ownerUid, int userId, String ownerPackageName,
- ISessionCallback cb, String tag, Bundle sessionInfo,
- MediaSessionService service, Looper handlerLooper, int policies)
+ public MediaSessionRecord(
+ int ownerPid,
+ int ownerUid,
+ int userId,
+ String ownerPackageName,
+ ISessionCallback cb,
+ String tag,
+ int uniqueId,
+ Bundle sessionInfo,
+ MediaSessionService service,
+ Looper handlerLooper,
+ int policies)
throws RemoteException {
mOwnerPid = ownerPid;
mOwnerUid = ownerUid;
mUserId = userId;
mPackageName = ownerPackageName;
mTag = tag;
+ mUniqueId = uniqueId;
mSessionInfo = sessionInfo;
mController = new ControllerStub();
mSessionToken = new MediaSession.Token(ownerUid, mController);
@@ -292,6 +303,16 @@
}
/**
+ * Get the unique id of this session record.
+ *
+ * @return a unique id of this session record.
+ */
+ @Override
+ public int getUniqueId() {
+ return mUniqueId;
+ }
+
+ /**
* Get the info for this session.
*
* @return Info that identifies this session.
@@ -703,7 +724,7 @@
@Override
public String toString() {
- return mPackageName + "/" + mTag + " (userId=" + mUserId + ")";
+ return mPackageName + "/" + mTag + "/" + mUniqueId + " (userId=" + mUserId + ")";
}
@Override
diff --git a/services/core/java/com/android/server/media/MediaSessionRecordImpl.java b/services/core/java/com/android/server/media/MediaSessionRecordImpl.java
index 99c8ea9..e53a2db 100644
--- a/services/core/java/com/android/server/media/MediaSessionRecordImpl.java
+++ b/services/core/java/com/android/server/media/MediaSessionRecordImpl.java
@@ -32,6 +32,13 @@
public interface MediaSessionRecordImpl extends AutoCloseable {
/**
+ * Get the unique id of this session record.
+ *
+ * @return a unique id of this session record.
+ */
+ int getUniqueId();
+
+ /**
* Get the info for this session.
*
* @return Info that identifies this session.
diff --git a/services/core/java/com/android/server/media/MediaSessionService.java b/services/core/java/com/android/server/media/MediaSessionService.java
index 757b26c..9e98a58 100644
--- a/services/core/java/com/android/server/media/MediaSessionService.java
+++ b/services/core/java/com/android/server/media/MediaSessionService.java
@@ -106,6 +106,7 @@
import java.util.HashSet;
import java.util.List;
import java.util.Set;
+import java.util.concurrent.atomic.AtomicInteger;
/**
* System implementation of MediaSessionManager
@@ -155,6 +156,8 @@
/* Maps uid with all user engaging session tokens associated to it */
private final SparseArray<Set<MediaSession.Token>> mUserEngagingSessions = new SparseArray<>();
+ private final AtomicInteger mNextMediaSessionRecordId = new AtomicInteger(1);
+
// The FullUserRecord of the current users. (i.e. The foreground user that isn't a profile)
// It's always not null after the MediaSessionService is started.
private FullUserRecord mCurrentFullUserRecord;
@@ -193,7 +196,8 @@
MediaSessionService.this,
mRecordThread.getLooper(),
pid,
- /* policies= */ 0);
+ /* policies= */ 0,
+ /* uniqueId= */ mNextMediaSessionRecordId.getAndIncrement());
synchronized (mLock) {
FullUserRecord user = getFullUserRecordLocked(record.getUserId());
if (user != null) {
@@ -794,9 +798,19 @@
final MediaSessionRecord session;
try {
- session = new MediaSessionRecord(callerPid, callerUid, userId,
- callerPackageName, cb, tag, sessionInfo, this,
- mRecordThread.getLooper(), policies);
+ session =
+ new MediaSessionRecord(
+ callerPid,
+ callerUid,
+ userId,
+ callerPackageName,
+ cb,
+ tag,
+ /* uniqueId= */ mNextMediaSessionRecordId.getAndIncrement(),
+ sessionInfo,
+ this,
+ mRecordThread.getLooper(),
+ policies);
} catch (RemoteException e) {
throw new RuntimeException("Media Session owner died prematurely.", e);
}
diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java
index fc7b873..3a7ac0b 100755
--- a/services/core/java/com/android/server/notification/NotificationManagerService.java
+++ b/services/core/java/com/android/server/notification/NotificationManagerService.java
@@ -8206,7 +8206,7 @@
try {
return mTelecomManager.isInManagedCall()
|| mTelecomManager.isInSelfManagedCall(pkg,
- UserHandle.getUserHandleForUid(uid), /* hasCrossUserAccess */ true);
+ /* hasCrossUserAccess */ true);
} catch (IllegalStateException ise) {
// Telecom is not ready (this is likely early boot), so there are no calls.
return false;
diff --git a/services/core/java/com/android/server/power/stats/BatteryStatsImpl.java b/services/core/java/com/android/server/power/stats/BatteryStatsImpl.java
index 9b347d5..ab27ac1 100644
--- a/services/core/java/com/android/server/power/stats/BatteryStatsImpl.java
+++ b/services/core/java/com/android/server/power/stats/BatteryStatsImpl.java
@@ -1817,9 +1817,8 @@
if (historyDirectory == null) {
mCheckinFile = null;
mStatsFile = null;
- mHistory = new BatteryStatsHistory(mConstants.MAX_HISTORY_FILES,
- mConstants.MAX_HISTORY_BUFFER, mStepDetailsCalculator, mClock, mMonotonicClock,
- traceDelegate, eventLogger);
+ mHistory = new BatteryStatsHistory(mConstants.MAX_HISTORY_BUFFER,
+ mStepDetailsCalculator, mClock, mMonotonicClock, traceDelegate, eventLogger);
} else {
mCheckinFile = new AtomicFile(new File(historyDirectory, "batterystats-checkin.bin"));
mStatsFile = new AtomicFile(new File(historyDirectory, "batterystats.bin"));
@@ -10962,8 +10961,8 @@
mStatsFile = null;
mCheckinFile = null;
mDailyFile = null;
- mHistory = new BatteryStatsHistory(mConstants.MAX_HISTORY_FILES,
- mConstants.MAX_HISTORY_BUFFER, mStepDetailsCalculator, mClock, mMonotonicClock);
+ mHistory = new BatteryStatsHistory(mConstants.MAX_HISTORY_BUFFER,
+ mStepDetailsCalculator, mClock, mMonotonicClock);
} else {
mStatsFile = new AtomicFile(new File(systemDir, "batterystats.bin"));
mCheckinFile = new AtomicFile(new File(systemDir, "batterystats-checkin.bin"));
@@ -15430,17 +15429,18 @@
mPerDisplayBatteryStats[i].screenStateAtLastEnergyMeasurement = screenState;
}
- final boolean compatibleConfig;
if (supportedStandardBuckets != null) {
final EnergyConsumerStats.Config config = new EnergyConsumerStats.Config(
supportedStandardBuckets, customBucketNames,
SUPPORTED_PER_PROCESS_STATE_STANDARD_ENERGY_BUCKETS,
getBatteryConsumerProcessStateNames());
- if (mEnergyConsumerStatsConfig == null) {
- compatibleConfig = true;
- } else {
- compatibleConfig = mEnergyConsumerStatsConfig.isCompatible(config);
+ if (mEnergyConsumerStatsConfig != null
+ && !mEnergyConsumerStatsConfig.isCompatible(config)) {
+ // Supported power buckets changed since last boot.
+ // Existing data is no longer reliable.
+ resetAllStatsLocked(SystemClock.uptimeMillis(), SystemClock.elapsedRealtime(),
+ RESET_REASON_ENERGY_CONSUMER_BUCKETS_CHANGE);
}
mEnergyConsumerStatsConfig = config;
@@ -15456,18 +15456,14 @@
mWifiPowerCalculator = new WifiPowerCalculator(mPowerProfile);
}
} else {
- compatibleConfig = (mEnergyConsumerStatsConfig == null);
- // EnergyConsumer no longer supported, wipe out the existing data.
+ if (mEnergyConsumerStatsConfig != null) {
+ // EnergyConsumer no longer supported, wipe out the existing data.
+ resetAllStatsLocked(SystemClock.uptimeMillis(), SystemClock.elapsedRealtime(),
+ RESET_REASON_ENERGY_CONSUMER_BUCKETS_CHANGE);
+ }
mEnergyConsumerStatsConfig = null;
mGlobalEnergyConsumerStats = null;
}
-
- if (!compatibleConfig) {
- // Supported power buckets changed since last boot.
- // Existing data is no longer reliable.
- resetAllStatsLocked(SystemClock.uptimeMillis(), SystemClock.elapsedRealtime(),
- RESET_REASON_ENERGY_CONSUMER_BUCKETS_CHANGE);
- }
}
@GuardedBy("this")
diff --git a/services/core/java/com/android/server/search/SearchManagerService.java b/services/core/java/com/android/server/search/SearchManagerService.java
index 7091c47..ecfc040 100644
--- a/services/core/java/com/android/server/search/SearchManagerService.java
+++ b/services/core/java/com/android/server/search/SearchManagerService.java
@@ -17,6 +17,7 @@
package com.android.server.search;
import android.annotation.NonNull;
+import android.annotation.UserIdInt;
import android.app.ISearchManager;
import android.app.SearchManager;
import android.app.SearchableInfo;
@@ -24,6 +25,7 @@
import android.content.ContentResolver;
import android.content.Context;
import android.content.Intent;
+import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
import android.database.ContentObserver;
import android.os.Binder;
@@ -32,6 +34,7 @@
import android.os.UserHandle;
import android.os.UserManager;
import android.provider.Settings;
+import android.util.ArraySet;
import android.util.Log;
import android.util.SparseArray;
@@ -47,6 +50,7 @@
import java.io.FileDescriptor;
import java.io.PrintWriter;
+import java.util.ArrayList;
import java.util.List;
/**
@@ -71,11 +75,6 @@
}
@Override
- public void onUserUnlocking(@NonNull TargetUser user) {
- mService.mHandler.post(() -> mService.onUnlockUser(user.getUserIdentifier()));
- }
-
- @Override
public void onUserStopped(@NonNull TargetUser user) {
mService.onCleanupUser(user.getUserIdentifier());
}
@@ -102,10 +101,6 @@
}
private Searchables getSearchables(int userId) {
- return getSearchables(userId, false);
- }
-
- private Searchables getSearchables(int userId, boolean forceUpdate) {
final long token = Binder.clearCallingIdentity();
try {
final UserManager um = mContext.getSystemService(UserManager.class);
@@ -122,21 +117,11 @@
Searchables searchables = mSearchables.get(userId);
if (searchables == null) {
searchables = new Searchables(mContext, userId);
- searchables.updateSearchableList();
- mSearchables.append(userId, searchables);
- } else if (forceUpdate) {
- searchables.updateSearchableList();
+ mSearchables.put(userId, searchables);
}
- return searchables;
- }
- }
- private void onUnlockUser(int userId) {
- try {
- getSearchables(userId, true);
- } catch (IllegalStateException ignored) {
- // We're just trying to warm a cache, so we don't mind if the user
- // was stopped or destroyed before we got here.
+ searchables.updateSearchableListIfNeeded();
+ return searchables;
}
}
@@ -150,28 +135,110 @@
* Refreshes the "searchables" list when packages are added/removed.
*/
class MyPackageMonitor extends PackageMonitor {
+ /**
+ * Packages that are appeared, disappeared, or modified for whatever reason.
+ */
+ private final ArrayList<String> mChangedPackages = new ArrayList<>();
+
+ /**
+ * {@code true} if one or more packages that contain {@link SearchableInfo} appeared.
+ */
+ private boolean mSearchablePackageAppeared = false;
@Override
- public void onSomePackagesChanged() {
- updateSearchables();
+ public void onBeginPackageChanges() {
+ clearPackageChangeState();
}
@Override
- public void onPackageModified(String pkg) {
- updateSearchables();
+ public void onPackageAppeared(String packageName, int reason) {
+ if (!mSearchablePackageAppeared) {
+ // Check if the new appeared package contains SearchableInfo.
+ mSearchablePackageAppeared =
+ hasSearchableForPackage(packageName, getChangingUserId());
+ }
+ mChangedPackages.add(packageName);
}
- private void updateSearchables() {
- final int changingUserId = getChangingUserId();
+ @Override
+ public void onPackageDisappeared(String packageName, int reason) {
+ mChangedPackages.add(packageName);
+ }
+
+ @Override
+ public void onPackageModified(String packageName) {
+ mChangedPackages.add(packageName);
+ }
+
+ @Override
+ public void onFinishPackageChanges() {
+ onFinishPackageChangesInternal();
+ clearPackageChangeState();
+ }
+
+ private void clearPackageChangeState() {
+ mChangedPackages.clear();
+ mSearchablePackageAppeared = false;
+ }
+
+ private boolean hasSearchableForPackage(String packageName, int userId) {
+ final List<ResolveInfo> searchList = querySearchableActivities(mContext,
+ new Intent(Intent.ACTION_SEARCH).setPackage(packageName), userId);
+ if (!searchList.isEmpty()) {
+ return true;
+ }
+
+ final List<ResolveInfo> webSearchList = querySearchableActivities(mContext,
+ new Intent(Intent.ACTION_WEB_SEARCH).setPackage(packageName), userId);
+ if (!webSearchList.isEmpty()) {
+ return true;
+ }
+
+ final List<ResolveInfo> globalSearchList = querySearchableActivities(mContext,
+ new Intent(SearchManager.INTENT_ACTION_GLOBAL_SEARCH).setPackage(packageName),
+ userId);
+ return !globalSearchList.isEmpty();
+ }
+
+ private boolean shouldRebuildSearchableList(@UserIdInt int changingUserId) {
+ // This method is guaranteed to be called only on getRegisteredHandler()
+ if (mSearchablePackageAppeared) {
+ return true;
+ }
+
+ ArraySet<String> knownSearchablePackageNames = new ArraySet<>();
synchronized (mSearchables) {
- // Update list of searchable activities
- for (int i = 0; i < mSearchables.size(); i++) {
- if (changingUserId == mSearchables.keyAt(i)) {
- mSearchables.valueAt(i).updateSearchableList();
- break;
- }
+ Searchables searchables = mSearchables.get(changingUserId);
+ if (searchables != null) {
+ knownSearchablePackageNames = searchables.getKnownSearchablePackageNames();
}
}
+
+ final int numOfPackages = mChangedPackages.size();
+ for (int i = 0; i < numOfPackages; i++) {
+ final String packageName = mChangedPackages.get(i);
+ if (knownSearchablePackageNames.contains(packageName)) {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ private void onFinishPackageChangesInternal() {
+ final int changingUserId = getChangingUserId();
+ if (!shouldRebuildSearchableList(changingUserId)) {
+ return;
+ }
+
+ synchronized (mSearchables) {
+ // Invalidate the searchable list.
+ Searchables searchables = mSearchables.get(changingUserId);
+ if (searchables != null) {
+ searchables.invalidateSearchableList();
+ }
+ }
+
// Inform all listeners that the list of searchables has been updated.
Intent intent = new Intent(SearchManager.INTENT_ACTION_SEARCHABLES_CHANGED);
intent.addFlags(Intent.FLAG_RECEIVER_REPLACE_PENDING
@@ -180,6 +247,17 @@
}
}
+ @NonNull
+ static List<ResolveInfo> querySearchableActivities(Context context, Intent searchIntent,
+ @UserIdInt int userId) {
+ final List<ResolveInfo> activities = context.getPackageManager()
+ .queryIntentActivitiesAsUser(searchIntent, PackageManager.GET_META_DATA
+ | PackageManager.MATCH_INSTANT
+ | PackageManager.MATCH_DEBUG_TRIAGED_MISSING, userId);
+ return activities;
+ }
+
+
class GlobalSearchProviderObserver extends ContentObserver {
private final ContentResolver mResolver;
@@ -196,7 +274,7 @@
public void onChange(boolean selfChange) {
synchronized (mSearchables) {
for (int i = 0; i < mSearchables.size(); i++) {
- mSearchables.valueAt(i).updateSearchableList();
+ mSearchables.valueAt(i).invalidateSearchableList();
}
}
Intent intent = new Intent(SearchManager.INTENT_GLOBAL_SEARCH_ACTIVITY_CHANGED);
diff --git a/services/core/java/com/android/server/search/Searchables.java b/services/core/java/com/android/server/search/Searchables.java
index 7b39775..dc67339 100644
--- a/services/core/java/com/android/server/search/Searchables.java
+++ b/services/core/java/com/android/server/search/Searchables.java
@@ -35,8 +35,10 @@
import android.os.UserHandle;
import android.provider.Settings;
import android.text.TextUtils;
+import android.util.ArraySet;
import android.util.Log;
+import com.android.internal.annotations.GuardedBy;
import com.android.server.LocalServices;
import java.io.FileDescriptor;
@@ -62,7 +64,6 @@
private static final String MD_SEARCHABLE_SYSTEM_SEARCH = "*";
private Context mContext;
-
private HashMap<ComponentName, SearchableInfo> mSearchablesMap = null;
private ArrayList<SearchableInfo> mSearchablesList = null;
private ArrayList<SearchableInfo> mSearchablesInGlobalSearchList = null;
@@ -81,6 +82,12 @@
final private IPackageManager mPm;
// User for which this Searchables caches information
private int mUserId;
+ @GuardedBy("this")
+ private boolean mRebuildSearchables = true;
+
+ // Package names that are known to contain {@link SearchableInfo}
+ @GuardedBy("this")
+ private ArraySet<String> mKnownSearchablePackageNames = new ArraySet<>();
/**
*
@@ -224,7 +231,14 @@
*
* TODO: sort the list somehow? UI choice.
*/
- public void updateSearchableList() {
+ public void updateSearchableListIfNeeded() {
+ synchronized (this) {
+ if (!mRebuildSearchables) {
+ // The searchable list is valid, no need to rebuild.
+ return;
+ }
+ }
+
// These will become the new values at the end of the method
HashMap<ComponentName, SearchableInfo> newSearchablesMap
= new HashMap<ComponentName, SearchableInfo>();
@@ -232,6 +246,7 @@
= new ArrayList<SearchableInfo>();
ArrayList<SearchableInfo> newSearchablesInGlobalSearchList
= new ArrayList<SearchableInfo>();
+ ArraySet<String> newKnownSearchablePackageNames = new ArraySet<>();
// Use intent resolver to generate list of ACTION_SEARCH & ACTION_WEB_SEARCH receivers.
List<ResolveInfo> searchList;
@@ -264,6 +279,7 @@
mUserId);
if (searchable != null) {
newSearchablesList.add(searchable);
+ newKnownSearchablePackageNames.add(ai.packageName);
newSearchablesMap.put(searchable.getSearchActivity(), searchable);
if (searchable.shouldIncludeInGlobalSearch()) {
newSearchablesInGlobalSearchList.add(searchable);
@@ -286,16 +302,41 @@
synchronized (this) {
mSearchablesMap = newSearchablesMap;
mSearchablesList = newSearchablesList;
+ mKnownSearchablePackageNames = newKnownSearchablePackageNames;
mSearchablesInGlobalSearchList = newSearchablesInGlobalSearchList;
mGlobalSearchActivities = newGlobalSearchActivities;
mCurrentGlobalSearchActivity = newGlobalSearchActivity;
mWebSearchActivity = newWebSearchActivity;
+ for (ResolveInfo globalSearchActivity: mGlobalSearchActivities) {
+ mKnownSearchablePackageNames.add(
+ globalSearchActivity.getComponentInfo().packageName);
+ }
+ if (mCurrentGlobalSearchActivity != null) {
+ mKnownSearchablePackageNames.add(
+ mCurrentGlobalSearchActivity.getPackageName());
+ }
+ if (mWebSearchActivity != null) {
+ mKnownSearchablePackageNames.add(mWebSearchActivity.getPackageName());
+ }
+
+ mRebuildSearchables = false;
}
} finally {
Binder.restoreCallingIdentity(ident);
}
}
+ synchronized ArraySet<String> getKnownSearchablePackageNames() {
+ return mKnownSearchablePackageNames;
+ }
+
+ synchronized void invalidateSearchableList() {
+ mRebuildSearchables = true;
+
+ // Don't rebuild the searchable list, it will be rebuilt
+ // when the next updateSearchableList gets called.
+ }
+
/**
* Returns a sorted list of installed search providers as per
* the following heuristics:
@@ -532,6 +573,8 @@
pw.print(" "); pw.println(info.getSuggestAuthority());
}
}
+
+ pw.println("mRebuildSearchables = " + mRebuildSearchables);
}
}
}
diff --git a/services/core/java/com/android/server/vibrator/VibrationScaler.java b/services/core/java/com/android/server/vibrator/VibrationScaler.java
index 7163319..5d17884 100644
--- a/services/core/java/com/android/server/vibrator/VibrationScaler.java
+++ b/services/core/java/com/android/server/vibrator/VibrationScaler.java
@@ -19,7 +19,7 @@
import android.annotation.NonNull;
import android.content.Context;
import android.hardware.vibrator.V1_0.EffectStrength;
-import android.os.IExternalVibratorService;
+import android.os.ExternalVibrationScale;
import android.os.VibrationAttributes;
import android.os.VibrationEffect;
import android.os.Vibrator;
@@ -37,11 +37,13 @@
// Scale levels. Each level, except MUTE, is defined as the delta between the current setting
// and the default intensity for that type of vibration (i.e. current - default).
- private static final int SCALE_VERY_LOW = IExternalVibratorService.SCALE_VERY_LOW; // -2
- private static final int SCALE_LOW = IExternalVibratorService.SCALE_LOW; // -1
- private static final int SCALE_NONE = IExternalVibratorService.SCALE_NONE; // 0
- private static final int SCALE_HIGH = IExternalVibratorService.SCALE_HIGH; // 1
- private static final int SCALE_VERY_HIGH = IExternalVibratorService.SCALE_VERY_HIGH; // 2
+ private static final int SCALE_VERY_LOW =
+ ExternalVibrationScale.ScaleLevel.SCALE_VERY_LOW; // -2
+ private static final int SCALE_LOW = ExternalVibrationScale.ScaleLevel.SCALE_LOW; // -1
+ private static final int SCALE_NONE = ExternalVibrationScale.ScaleLevel.SCALE_NONE; // 0
+ private static final int SCALE_HIGH = ExternalVibrationScale.ScaleLevel.SCALE_HIGH; // 1
+ private static final int SCALE_VERY_HIGH =
+ ExternalVibrationScale.ScaleLevel.SCALE_VERY_HIGH; // 2
// Scale factors for each level.
private static final float SCALE_FACTOR_VERY_LOW = 0.6f;
@@ -83,9 +85,9 @@
* Calculates the scale to be applied to external vibration with given usage.
*
* @param usageHint one of VibrationAttributes.USAGE_*
- * @return one of IExternalVibratorService.SCALE_*
+ * @return one of ExternalVibrationScale.ScaleLevel.SCALE_*
*/
- public int getExternalVibrationScale(int usageHint) {
+ public int getExternalVibrationScaleLevel(int usageHint) {
int defaultIntensity = mSettingsController.getDefaultIntensity(usageHint);
int currentIntensity = mSettingsController.getCurrentIntensity(usageHint);
@@ -107,6 +109,22 @@
}
/**
+ * Returns the adaptive haptics scale that should be applied to the vibrations with
+ * the given usage. When no adaptive scales are available for the usages, then returns 1
+ * indicating no scaling will be applied
+ *
+ * @param usageHint one of VibrationAttributes.USAGE_*
+ * @return The adaptive haptics scale.
+ */
+ public float getAdaptiveHapticsScale(int usageHint) {
+ if (shouldApplyAdaptiveHapticsScale(usageHint)) {
+ return mAdaptiveHapticsScales.get(usageHint);
+ }
+
+ return 1f; // no scaling
+ }
+
+ /**
* Scale a {@link VibrationEffect} based on the given usage hint for this vibration.
*
* @param effect the effect to be scaled
@@ -152,9 +170,7 @@
}
// If adaptive haptics scaling is available for this usage, apply it to the segment.
- if (Flags.adaptiveHapticsEnabled()
- && mAdaptiveHapticsScales.size() > 0
- && mAdaptiveHapticsScales.contains(usageHint)) {
+ if (shouldApplyAdaptiveHapticsScale(usageHint)) {
float adaptiveScale = mAdaptiveHapticsScales.get(usageHint);
segment = segment.scaleLinearly(adaptiveScale);
}
@@ -224,6 +240,10 @@
mAdaptiveHapticsScales.clear();
}
+ private boolean shouldApplyAdaptiveHapticsScale(int usageHint) {
+ return Flags.adaptiveHapticsEnabled() && mAdaptiveHapticsScales.contains(usageHint);
+ }
+
/** Mapping of Vibrator.VIBRATION_INTENSITY_* values to {@link EffectStrength}. */
private static int intensityToEffectStrength(int intensity) {
switch (intensity) {
diff --git a/services/core/java/com/android/server/vibrator/VibratorManagerService.java b/services/core/java/com/android/server/vibrator/VibratorManagerService.java
index be5d158..78e0ebb 100644
--- a/services/core/java/com/android/server/vibrator/VibratorManagerService.java
+++ b/services/core/java/com/android/server/vibrator/VibratorManagerService.java
@@ -16,6 +16,7 @@
package com.android.server.vibrator;
+import static android.os.ExternalVibrationScale.ScaleLevel.SCALE_MUTE;
import static android.os.VibrationEffect.VibrationParameter.targetAmplitude;
import static android.os.VibrationEffect.VibrationParameter.targetFrequency;
@@ -35,6 +36,7 @@
import android.os.Build;
import android.os.CombinedVibration;
import android.os.ExternalVibration;
+import android.os.ExternalVibrationScale;
import android.os.Handler;
import android.os.IBinder;
import android.os.IExternalVibratorService;
@@ -277,7 +279,7 @@
context.registerReceiver(mIntentReceiver, filter, Context.RECEIVER_NOT_EXPORTED);
injector.addService(EXTERNAL_VIBRATOR_SERVICE, new ExternalVibratorService());
- if (ServiceManager.isDeclared(VIBRATOR_CONTROL_SERVICE)) {
+ if (injector.isServiceDeclared(VIBRATOR_CONTROL_SERVICE)) {
injector.addService(VIBRATOR_CONTROL_SERVICE, mVibratorControlService);
}
@@ -1427,6 +1429,10 @@
VibratorControllerHolder createVibratorControllerHolder() {
return new VibratorControllerHolder();
}
+
+ boolean isServiceDeclared(String name) {
+ return ServiceManager.isDeclared(name);
+ }
}
/**
@@ -1594,7 +1600,7 @@
IBinder.DeathRecipient {
public final ExternalVibration externalVibration;
- public int scale;
+ public ExternalVibrationScale scale = new ExternalVibrationScale();
private Vibration.Status mStatus;
@@ -1605,7 +1611,6 @@
// instead of using DEVICE_ID_INVALID here and relying on the UID checks.
Context.DEVICE_ID_INVALID, externalVibration.getPackage(), null));
this.externalVibration = externalVibration;
- this.scale = IExternalVibratorService.SCALE_NONE;
mStatus = Vibration.Status.RUNNING;
}
@@ -1658,7 +1663,7 @@
public Vibration.DebugInfo getDebugInfo() {
return new Vibration.DebugInfo(mStatus, stats, /* playedEffect= */ null,
- /* originalEffect= */ null, scale, callerInfo);
+ /* originalEffect= */ null, scale.scaleLevel, callerInfo);
}
public VibrationStats.StatsInfo getStatsInfo(long completionUptimeMillis) {
@@ -1988,11 +1993,17 @@
/** Implementation of {@link IExternalVibratorService} to be triggered on external control. */
@VisibleForTesting
final class ExternalVibratorService extends IExternalVibratorService.Stub {
+ private static final ExternalVibrationScale SCALE_MUTE = new ExternalVibrationScale();
+
+ static {
+ SCALE_MUTE.scaleLevel = ExternalVibrationScale.ScaleLevel.SCALE_MUTE;
+ }
@Override
- public int onExternalVibrationStart(ExternalVibration vib) {
+ public ExternalVibrationScale onExternalVibrationStart(ExternalVibration vib) {
+
if (!hasExternalControlCapability()) {
- return IExternalVibratorService.SCALE_MUTE;
+ return SCALE_MUTE;
}
if (ActivityManager.checkComponentPermission(android.Manifest.permission.VIBRATE,
vib.getUid(), -1 /*owningUid*/, true /*exported*/)
@@ -2000,7 +2011,7 @@
Slog.w(TAG, "pkg=" + vib.getPackage() + ", uid=" + vib.getUid()
+ " tried to play externally controlled vibration"
+ " without VIBRATE permission, ignoring.");
- return IExternalVibratorService.SCALE_MUTE;
+ return SCALE_MUTE;
}
// Create Vibration.Stats as close to the received request as possible, for tracking.
@@ -2033,7 +2044,7 @@
}
if (vibrationEndInfo != null) {
- vibHolder.scale = IExternalVibratorService.SCALE_MUTE;
+ vibHolder.scale = SCALE_MUTE;
// Failed to start the vibration, end it and report metrics right away.
endVibrationAndWriteStatsLocked(vibHolder, vibrationEndInfo);
return vibHolder.scale;
@@ -2074,7 +2085,10 @@
}
mCurrentExternalVibration = vibHolder;
vibHolder.linkToDeath();
- vibHolder.scale = mVibrationScaler.getExternalVibrationScale(attrs.getUsage());
+ vibHolder.scale.scaleLevel = mVibrationScaler.getExternalVibrationScaleLevel(
+ attrs.getUsage());
+ vibHolder.scale.adaptiveHapticsScale = mVibrationScaler.getAdaptiveHapticsScale(
+ attrs.getUsage());
}
if (waitForCompletion) {
@@ -2086,7 +2100,7 @@
new Vibration.EndInfo(Vibration.Status.IGNORED_ERROR_CANCELLING),
/* continueExternalControl= */ false);
}
- return IExternalVibratorService.SCALE_MUTE;
+ return SCALE_MUTE;
}
}
if (!alreadyUnderExternalControl) {
diff --git a/services/core/java/com/android/server/webkit/SystemImpl.java b/services/core/java/com/android/server/webkit/SystemImpl.java
index 3b2e69a..c6e8eb8 100644
--- a/services/core/java/com/android/server/webkit/SystemImpl.java
+++ b/services/core/java/com/android/server/webkit/SystemImpl.java
@@ -23,6 +23,7 @@
import android.content.Context;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageInfo;
+import android.content.pm.PackageInstaller;
import android.content.pm.PackageManager;
import android.content.pm.PackageManager.NameNotFoundException;
import android.content.pm.UserInfo;
@@ -225,6 +226,21 @@
}
@Override
+ public void installExistingPackageForAllUsers(Context context, String packageName) {
+ UserManager userManager = context.getSystemService(UserManager.class);
+ for (UserInfo userInfo : userManager.getUsers()) {
+ installPackageForUser(packageName, userInfo.id);
+ }
+ }
+
+ private void installPackageForUser(String packageName, int userId) {
+ final Context context = AppGlobals.getInitialApplication();
+ final Context contextAsUser = context.createContextAsUser(UserHandle.of(userId), 0);
+ final PackageInstaller installer = contextAsUser.getPackageManager().getPackageInstaller();
+ installer.installExistingPackage(packageName, PackageManager.INSTALL_REASON_UNKNOWN, null);
+ }
+
+ @Override
public boolean systemIsDebuggable() {
return Build.IS_DEBUGGABLE;
}
diff --git a/services/core/java/com/android/server/webkit/SystemInterface.java b/services/core/java/com/android/server/webkit/SystemInterface.java
index 5ed2cfe..ad32f62 100644
--- a/services/core/java/com/android/server/webkit/SystemInterface.java
+++ b/services/core/java/com/android/server/webkit/SystemInterface.java
@@ -43,6 +43,7 @@
public void killPackageDependents(String packageName);
public void enablePackageForAllUsers(Context context, String packageName, boolean enable);
+ public void installExistingPackageForAllUsers(Context context, String packageName);
public boolean systemIsDebuggable();
public PackageInfo getPackageInfoForProvider(WebViewProviderInfo configInfo)
diff --git a/services/core/java/com/android/server/webkit/WebViewUpdateServiceImpl2.java b/services/core/java/com/android/server/webkit/WebViewUpdateServiceImpl2.java
index b3c8b0b..596de68 100644
--- a/services/core/java/com/android/server/webkit/WebViewUpdateServiceImpl2.java
+++ b/services/core/java/com/android/server/webkit/WebViewUpdateServiceImpl2.java
@@ -107,15 +107,20 @@
mContext = context;
mSystemInterface = systemInterface;
WebViewProviderInfo[] webviewProviders = getWebViewPackages();
+
+ WebViewProviderInfo defaultProvider = null;
for (WebViewProviderInfo provider : webviewProviders) {
if (provider.availableByDefault) {
- mDefaultProvider = provider;
+ defaultProvider = provider;
break;
}
}
- // This should be unreachable because the config parser enforces that there is at least one
- // availableByDefault provider.
- throw new AndroidRuntimeException("No available by default WebView Provider.");
+ if (defaultProvider == null) {
+ // This should be unreachable because the config parser enforces that there is at least
+ // one availableByDefault provider.
+ throw new AndroidRuntimeException("No available by default WebView Provider.");
+ }
+ mDefaultProvider = defaultProvider;
}
@Override
@@ -206,14 +211,16 @@
}
if (repairNeeded) {
- // We didn't find a valid WebView implementation. Try explicitly re-enabling the
- // default package for all users in case it was disabled, even if we already did the
- // one-time migration before. If this actually changes the state, we will see the
- // PackageManager broadcast shortly and try again.
+ // We didn't find a valid WebView implementation. Try explicitly re-installing and
+ // re-enabling the default package for all users in case it was disabled, even if we
+ // already did the one-time migration before. If this actually changes the state, we
+ // will see the PackageManager broadcast shortly and try again.
Slog.w(
TAG,
- "No provider available for all users, trying to enable "
+ "No provider available for all users, trying to install and enable "
+ mDefaultProvider.packageName);
+ mSystemInterface.installExistingPackageForAllUsers(
+ mContext, mDefaultProvider.packageName);
mSystemInterface.enablePackageForAllUsers(
mContext, mDefaultProvider.packageName, true);
}
diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java
index 56024f7..bc6f93f 100644
--- a/services/core/java/com/android/server/wm/ActivityRecord.java
+++ b/services/core/java/com/android/server/wm/ActivityRecord.java
@@ -1535,11 +1535,12 @@
// Picture-in-picture mode changes also trigger a multi-window mode change as well, so
// update that here in order. Set the last reported MW state to the same as the PiP
// state since we haven't yet actually resized the task (these callbacks need to
- // precede the configuration change from the resize.
+ // precede the configuration change from the resize.)
mLastReportedPictureInPictureMode = inPictureInPictureMode;
mLastReportedMultiWindowMode = inPictureInPictureMode;
ensureActivityConfiguration(true /* ignoreVisibility */);
- if (inPictureInPictureMode && findMainWindow() == null) {
+ if (inPictureInPictureMode && findMainWindow() == null
+ && task.topRunningActivity() == this) {
// Prevent malicious app entering PiP without valid WindowState, which can in turn
// result a non-touchable PiP window since the InputConsumer for PiP requires it.
EventLog.writeEvent(0x534e4554, "265293293", -1, "");
@@ -3549,7 +3550,7 @@
IBinder callerToken = new Binder();
if (android.security.Flags.contentUriPermissionApis()) {
try {
- resultTo.computeCallerInfo(callerToken, intent, this.getUid(),
+ resultTo.computeCallerInfo(callerToken, resultData, this.getUid(),
mAtmService.getPackageManager().getNameForUid(this.getUid()),
/* isShareIdentityEnabled */ false);
// Result callers cannot share their identity via
diff --git a/services/core/java/com/android/server/wm/TaskFragment.java b/services/core/java/com/android/server/wm/TaskFragment.java
index 10cbc66..85d81c4 100644
--- a/services/core/java/com/android/server/wm/TaskFragment.java
+++ b/services/core/java/com/android/server/wm/TaskFragment.java
@@ -104,6 +104,7 @@
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.protolog.common.ProtoLog;
+import com.android.internal.util.ToBooleanFunction;
import com.android.server.am.HostingRecord;
import com.android.server.pm.pkg.AndroidPackage;
import com.android.window.flags.Flags;
@@ -3025,11 +3026,17 @@
return false;
}
- // boost if there's an Activity window that has FLAG_DIM_BEHIND flag.
- return forAllWindows(
+ ToBooleanFunction<WindowState> getDimBehindWindow =
(w) -> (w.mAttrs.flags & FLAG_DIM_BEHIND) != 0 && w.mActivityRecord != null
&& w.mActivityRecord.isEmbedded() && (w.mActivityRecord.isVisibleRequested()
- || w.mActivityRecord.isVisible()), true);
+ || w.mActivityRecord.isVisible());
+ if (adjacentTf.forAllWindows(getDimBehindWindow, true)) {
+ // early return if the adjacent Tf has a dimming window.
+ return false;
+ }
+
+ // boost if there's an Activity window that has FLAG_DIM_BEHIND flag.
+ return forAllWindows(getDimBehindWindow, true);
}
@Override
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 a469165..b38a2f9 100644
--- a/services/core/xsd/display-device-config/display-device-config.xsd
+++ b/services/core/xsd/display-device-config/display-device-config.xsd
@@ -117,6 +117,9 @@
<xs:element type="sensorDetails" name="proxSensor">
<xs:annotation name="final"/>
</xs:element>
+ <xs:element type="sensorDetails" name="tempSensor">
+ <xs:annotation name="final"/>
+ </xs:element>
<!-- Length of the ambient light horizon used to calculate the long & short term
estimates of ambient light in milliseconds.-->
diff --git a/services/core/xsd/display-device-config/schema/current.txt b/services/core/xsd/display-device-config/schema/current.txt
index 79ea274..b329db4 100644
--- a/services/core/xsd/display-device-config/schema/current.txt
+++ b/services/core/xsd/display-device-config/schema/current.txt
@@ -133,6 +133,7 @@
method public final java.math.BigDecimal getScreenBrightnessRampSlowIncreaseIdle();
method public final com.android.server.display.config.SensorDetails getScreenOffBrightnessSensor();
method public final com.android.server.display.config.IntegerArray getScreenOffBrightnessSensorValueToLux();
+ method public final com.android.server.display.config.SensorDetails getTempSensor();
method @NonNull public final com.android.server.display.config.ThermalThrottling getThermalThrottling();
method public final com.android.server.display.config.UsiVersion getUsiVersion();
method public final void setAmbientBrightnessChangeThresholds(@NonNull com.android.server.display.config.Thresholds);
@@ -167,6 +168,7 @@
method public final void setScreenBrightnessRampSlowIncreaseIdle(java.math.BigDecimal);
method public final void setScreenOffBrightnessSensor(com.android.server.display.config.SensorDetails);
method public final void setScreenOffBrightnessSensorValueToLux(com.android.server.display.config.IntegerArray);
+ method public final void setTempSensor(com.android.server.display.config.SensorDetails);
method public final void setThermalThrottling(@NonNull com.android.server.display.config.ThermalThrottling);
method public final void setUsiVersion(com.android.server.display.config.UsiVersion);
}
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyCacheImpl.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyCacheImpl.java
index e7855bc..c4e2dc8 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyCacheImpl.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyCacheImpl.java
@@ -15,6 +15,10 @@
*/
package com.android.server.devicepolicy;
+import static android.app.admin.DevicePolicyManager.CONTENT_PROTECTION_DISABLED;
+import static android.app.admin.DevicePolicyManager.ContentProtectionPolicy;
+
+import android.annotation.Nullable;
import android.annotation.UserIdInt;
import android.app.admin.DevicePolicyCache;
import android.app.admin.DevicePolicyManager;
@@ -70,10 +74,14 @@
/** Maps to {@code ActiveAdmin.mAdminCanGrantSensorsPermissions}. */
private final AtomicBoolean mCanGrantSensorsPermissions = new AtomicBoolean(false);
+ @GuardedBy("mLock")
+ private final SparseIntArray mContentProtectionPolicy = new SparseIntArray();
+
public void onUserRemoved(int userHandle) {
synchronized (mLock) {
mPasswordQuality.delete(userHandle);
mPermissionPolicy.delete(userHandle);
+ mContentProtectionPolicy.delete(userHandle);
}
}
@@ -143,6 +151,24 @@
}
@Override
+ public @ContentProtectionPolicy int getContentProtectionPolicy(@UserIdInt int userId) {
+ synchronized (mLock) {
+ return mContentProtectionPolicy.get(userId, CONTENT_PROTECTION_DISABLED);
+ }
+ }
+
+ /** Update the content protection policy for the given user. */
+ public void setContentProtectionPolicy(@UserIdInt int userId, @Nullable Integer value) {
+ synchronized (mLock) {
+ if (value == null) {
+ mContentProtectionPolicy.delete(userId);
+ } else {
+ mContentProtectionPolicy.put(userId, value);
+ }
+ }
+ }
+
+ @Override
public boolean canAdminGrantSensorsPermissions() {
return mCanGrantSensorsPermissions.get();
}
@@ -178,6 +204,7 @@
pw.println("Screen capture disallowed users: " + mScreenCaptureDisallowedUsers);
pw.println("Password quality: " + mPasswordQuality);
pw.println("Permission policy: " + mPermissionPolicy);
+ pw.println("Content protection policy: " + mContentProtectionPolicy);
pw.println("Admin can grant sensors permission: " + mCanGrantSensorsPermissions.get());
pw.print("Shortcuts overrides: ");
pw.println(mLauncherShortcutOverrides);
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
index 9c48f29..0f97f4a 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
@@ -3633,6 +3633,7 @@
userId == UserHandle.USER_SYSTEM ? UserHandle.USER_ALL : userId);
updatePermissionPolicyCache(userId);
updateAdminCanGrantSensorsPermissionCache(userId);
+ updateContentProtectionPolicyCache(userId);
final List<PreferentialNetworkServiceConfig> preferentialNetworkServiceConfigs;
synchronized (getLockObject()) {
@@ -23534,6 +23535,12 @@
}
}
+ private void updateContentProtectionPolicyCache(@UserIdInt int userId) {
+ mPolicyCache.setContentProtectionPolicy(
+ userId,
+ mDevicePolicyEngine.getResolvedPolicy(PolicyDefinition.CONTENT_PROTECTION, userId));
+ }
+
@Override
public ManagedSubscriptionsPolicy getManagedSubscriptionsPolicy() {
synchronized (getLockObject()) {
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/PolicyDefinition.java b/services/devicepolicy/java/com/android/server/devicepolicy/PolicyDefinition.java
index 1247f9002..71facab 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/PolicyDefinition.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/PolicyDefinition.java
@@ -359,7 +359,7 @@
new NoArgsPolicyKey(DevicePolicyIdentifiers.CONTENT_PROTECTION_POLICY),
new MostRecent<>(),
POLICY_FLAG_LOCAL_ONLY_POLICY,
- (Integer value, Context context, Integer userId, PolicyKey policyKey) -> true,
+ PolicyEnforcerCallbacks::setContentProtectionPolicy,
new IntegerPolicySerializer());
private static final Map<String, PolicyDefinition<?>> POLICY_DEFINITIONS = new HashMap<>();
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/PolicyEnforcerCallbacks.java b/services/devicepolicy/java/com/android/server/devicepolicy/PolicyEnforcerCallbacks.java
index 54242ab..c108deaf 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/PolicyEnforcerCallbacks.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/PolicyEnforcerCallbacks.java
@@ -18,6 +18,7 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.annotation.UserIdInt;
import android.app.AppGlobals;
import android.app.admin.DevicePolicyCache;
import android.app.admin.DevicePolicyManager;
@@ -282,6 +283,21 @@
return true;
}
+ static boolean setContentProtectionPolicy(
+ @Nullable Integer value,
+ @NonNull Context context,
+ @UserIdInt Integer userId,
+ @NonNull PolicyKey policyKey) {
+ Binder.withCleanCallingIdentity(
+ () -> {
+ DevicePolicyCache cache = DevicePolicyCache.getInstance();
+ if (cache instanceof DevicePolicyCacheImpl cacheImpl) {
+ cacheImpl.setContentProtectionPolicy(userId, value);
+ }
+ });
+ return true;
+ }
+
private static void updateScreenCaptureDisabled() {
BackgroundThread.getHandler().post(() -> {
try {
diff --git a/services/tests/InputMethodSystemServerTests/Android.bp b/services/tests/InputMethodSystemServerTests/Android.bp
index afd6dbd..3bce9b5 100644
--- a/services/tests/InputMethodSystemServerTests/Android.bp
+++ b/services/tests/InputMethodSystemServerTests/Android.bp
@@ -69,7 +69,7 @@
}
android_ravenwood_test {
- name: "FrameworksInputMethodSystemServerTests_host",
+ name: "FrameworksInputMethodSystemServerTestsRavenwood",
static_libs: [
"androidx.annotation_annotation",
"androidx.test.rules",
@@ -85,7 +85,6 @@
srcs: [
"src/com/android/server/inputmethod/**/ClientControllerTest.java",
],
- sdk_version: "test_current",
auto_gen_config: true,
}
diff --git a/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/ClientControllerTest.java b/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/ClientControllerTest.java
index dc9631a..9e3d9ec 100644
--- a/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/ClientControllerTest.java
+++ b/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/ClientControllerTest.java
@@ -32,10 +32,8 @@
import android.os.Handler;
import android.os.IBinder;
import android.os.Looper;
-import android.platform.test.annotations.IgnoreUnderRavenwood;
import android.platform.test.ravenwood.RavenwoodRule;
import android.view.Display;
-import android.view.inputmethod.InputBinding;
import com.android.internal.inputmethod.IInputMethodClient;
import com.android.internal.inputmethod.IRemoteInputConnection;
@@ -53,7 +51,7 @@
public final class ClientControllerTest {
private static final int ANY_DISPLAY_ID = Display.DEFAULT_DISPLAY;
private static final int ANY_CALLER_UID = 1;
- private static final int ANY_CALLER_PID = 1;
+ private static final int ANY_CALLER_PID = 2;
private static final String SOME_PACKAGE_NAME = "some.package";
@Rule
@@ -82,13 +80,16 @@
mController = new ClientController(mMockPackageManagerInternal);
}
- @Test
- // TODO(b/314150112): Enable host side mode for this test once Ravenwood is enabled for
- // inputmethod server classes.
- @IgnoreUnderRavenwood(blockedBy = {InputBinding.class, IInputMethodClientInvoker.class})
- public void testAddClient_cannotAddTheSameClientTwice() {
- var invoker = IInputMethodClientInvoker.create(mClient, mHandler);
+ // TODO(b/322895594): No need to directly invoke create$ravenwood once b/322895594 is fixed.
+ private IInputMethodClientInvoker createInvoker(IInputMethodClient client, Handler handler) {
+ return RavenwoodRule.isOnRavenwood()
+ ? IInputMethodClientInvoker.create$ravenwood(client, handler) :
+ IInputMethodClientInvoker.create(client, handler);
+ }
+ @Test
+ public void testAddClient_cannotAddTheSameClientTwice() {
+ final var invoker = createInvoker(mClient, mHandler);
synchronized (ImfLock.class) {
mController.addClient(invoker, mConnection, ANY_DISPLAY_ID, ANY_CALLER_UID,
ANY_CALLER_PID);
@@ -101,18 +102,17 @@
}
});
assertThat(thrown.getMessage()).isEqualTo(
- "uid=1/pid=1/displayId=0 is already registered");
+ "uid=" + ANY_CALLER_UID + "/pid=" + ANY_CALLER_PID
+ + "/displayId=0 is already registered");
}
}
@Test
- // TODO(b/314150112): Enable host side mode for this test once Ravenwood is enabled for
- // inputmethod server classes.
- @IgnoreUnderRavenwood(blockedBy = {InputBinding.class, IInputMethodClientInvoker.class})
public void testAddClient() throws Exception {
+ final var invoker = createInvoker(mClient, mHandler);
synchronized (ImfLock.class) {
- var invoker = IInputMethodClientInvoker.create(mClient, mHandler);
- var added = mController.addClient(invoker, mConnection, ANY_DISPLAY_ID, ANY_CALLER_UID,
+ final var added = mController.addClient(invoker, mConnection, ANY_DISPLAY_ID,
+ ANY_CALLER_UID,
ANY_CALLER_PID);
verify(invoker.asBinder()).linkToDeath(any(IBinder.DeathRecipient.class), eq(0));
@@ -121,16 +121,12 @@
}
@Test
- // TODO(b/314150112): Enable host side mode for this test once Ravenwood is enabled for
- // inputmethod server classes.
- @IgnoreUnderRavenwood(blockedBy = {InputBinding.class, IInputMethodClientInvoker.class})
public void testRemoveClient() {
- var callback = new TestClientControllerCallback();
+ final var invoker = createInvoker(mClient, mHandler);
+ final var callback = new TestClientControllerCallback();
ClientState added;
synchronized (ImfLock.class) {
mController.addClientControllerCallback(callback);
-
- var invoker = IInputMethodClientInvoker.create(mClient, mHandler);
added = mController.addClient(invoker, mConnection, ANY_DISPLAY_ID, ANY_CALLER_UID,
ANY_CALLER_PID);
assertThat(mController.getClient(invoker.asBinder())).isSameInstanceAs(added);
@@ -138,21 +134,17 @@
}
// Test callback
- var removed = callback.waitForRemovedClient(5, TimeUnit.SECONDS);
+ final var removed = callback.waitForRemovedClient(5, TimeUnit.SECONDS);
assertThat(removed).isSameInstanceAs(added);
}
@Test
- // TODO(b/314150112): Enable host side mode for this test once Ravenwood is enabled for
- // inputmethod server classes and updated to newer Mockito with static mock support (mock
- // InputMethodUtils#checkIfPackageBelongsToUid instead of PackageManagerInternal#isSameApp)
- @IgnoreUnderRavenwood(blockedBy = {InputMethodUtils.class})
public void testVerifyClientAndPackageMatch() {
+ final var invoker = createInvoker(mClient, mHandler);
when(mMockPackageManagerInternal.isSameApp(eq(SOME_PACKAGE_NAME), /* flags= */
anyLong(), eq(ANY_CALLER_UID), /* userId= */ anyInt())).thenReturn(true);
synchronized (ImfLock.class) {
- var invoker = IInputMethodClientInvoker.create(mClient, mHandler);
mController.addClient(invoker, mConnection, ANY_DISPLAY_ID, ANY_CALLER_UID,
ANY_CALLER_PID);
assertThat(
diff --git a/services/tests/displayservicetests/src/com/android/server/display/BrightnessThrottlerTest.java b/services/tests/displayservicetests/src/com/android/server/display/BrightnessThrottlerTest.java
index 8faaf59..05c243f 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/BrightnessThrottlerTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/BrightnessThrottlerTest.java
@@ -43,6 +43,7 @@
import com.android.server.display.BrightnessThrottler.Injector;
import com.android.server.display.DisplayDeviceConfig.ThermalBrightnessThrottlingData;
import com.android.server.display.DisplayDeviceConfig.ThermalBrightnessThrottlingData.ThrottlingLevel;
+import com.android.server.display.config.SensorData;
import com.android.server.display.mode.DisplayModeDirectorTest;
import org.junit.Before;
@@ -56,6 +57,7 @@
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
+import java.util.Map;
@SmallTest
@RunWith(AndroidJUnit4.class)
@@ -292,6 +294,53 @@
assertEquals(BrightnessInfo.BRIGHTNESS_MAX_REASON_NONE, throttler.getBrightnessMaxReason());
}
+
+ @Test
+ public void testThermalThrottlingWithDisplaySensor() throws Exception {
+ final ThrottlingLevel level =
+ new ThrottlingLevel(PowerManager.THERMAL_STATUS_CRITICAL, 0.25f);
+ List<ThrottlingLevel> levels = new ArrayList<>(List.of(level));
+ final ThermalBrightnessThrottlingData data = ThermalBrightnessThrottlingData.create(levels);
+ final SensorData tempSensor = new SensorData("DISPLAY", "VIRTUAL-SKIN-DISPLAY");
+ final BrightnessThrottler throttler =
+ createThrottlerSupportedWithTempSensor(data, tempSensor);
+ assertTrue(throttler.deviceSupportsThrottling());
+
+ verify(mThermalServiceMock)
+ .registerThermalEventListenerWithType(
+ mThermalEventListenerCaptor.capture(), eq(Temperature.TYPE_DISPLAY));
+ final IThermalEventListener listener = mThermalEventListenerCaptor.getValue();
+
+ // Set VIRTUAL-SKIN-DISPLAY tatus too low to verify no throttling.
+ listener.notifyThrottling(getDisplayTempWithName(tempSensor.name, level.thermalStatus - 1));
+ mTestLooper.dispatchAll();
+ assertEquals(PowerManager.BRIGHTNESS_MAX, throttler.getBrightnessCap(), 0f);
+ assertFalse(throttler.isThrottled());
+ assertEquals(BrightnessInfo.BRIGHTNESS_MAX_REASON_NONE, throttler.getBrightnessMaxReason());
+
+ // Verify when skin sensor throttled, no brightness throttling triggered.
+ listener.notifyThrottling(getSkinTemp(level.thermalStatus + 1));
+ mTestLooper.dispatchAll();
+ assertEquals(PowerManager.BRIGHTNESS_MAX, throttler.getBrightnessCap(), 0f);
+ assertFalse(throttler.isThrottled());
+ assertEquals(BrightnessInfo.BRIGHTNESS_MAX_REASON_NONE, throttler.getBrightnessMaxReason());
+
+ // Verify when display sensor of another name throttled, no brightness throttling triggered.
+ listener.notifyThrottling(getDisplayTempWithName("ANOTHER-NAME", level.thermalStatus + 1));
+ mTestLooper.dispatchAll();
+ assertEquals(PowerManager.BRIGHTNESS_MAX, throttler.getBrightnessCap(), 0f);
+ assertFalse(throttler.isThrottled());
+ assertEquals(BrightnessInfo.BRIGHTNESS_MAX_REASON_NONE, throttler.getBrightnessMaxReason());
+
+ // Verify when display sensor of current name throttled, brightness throttling triggered.
+ listener.notifyThrottling(getDisplayTempWithName(tempSensor.name, level.thermalStatus + 1));
+ mTestLooper.dispatchAll();
+ assertEquals(level.brightness, throttler.getBrightnessCap(), 0f);
+ assertTrue(throttler.isThrottled());
+ assertEquals(BrightnessInfo.BRIGHTNESS_MAX_REASON_THERMAL,
+ throttler.getBrightnessMaxReason());
+ }
+
@Test public void testUpdateThermalThrottlingData() throws Exception {
// Initialise brightness throttling levels
// Ensure that they are overridden by setting the data through device config.
@@ -476,18 +525,30 @@
return new BrightnessThrottler(mInjectorMock, mHandler, mHandler,
/* throttlingChangeCallback= */ () -> {}, /* uniqueDisplayId= */ null,
/* thermalThrottlingDataId= */ null,
- /* thermalThrottlingDataMap= */ new HashMap<>(1));
+ /* thermalThrottlingDataMap= */ new HashMap<>(1),
+ /* tempSensor= */ null);
}
private BrightnessThrottler createThrottlerSupported(ThermalBrightnessThrottlingData data) {
+ SensorData tempSensor = SensorData.loadTempSensorUnspecifiedConfig();
+ return createThrottlerSupportedWithTempSensor(data, tempSensor);
+ }
+ private BrightnessThrottler createThrottlerSupportedWithTempSensor(
+ ThermalBrightnessThrottlingData data, SensorData tempSensor) {
assertNotNull(data);
- HashMap<String, ThermalBrightnessThrottlingData> throttlingDataMap = new HashMap<>(1);
+ Map<String, ThermalBrightnessThrottlingData> throttlingDataMap = new HashMap<>(1);
throttlingDataMap.put("default", data);
return new BrightnessThrottler(mInjectorMock, mHandler, BackgroundThread.getHandler(),
- () -> {}, "123", "default", throttlingDataMap);
+ () -> {}, "123", "default", throttlingDataMap, tempSensor);
}
private Temperature getSkinTemp(@ThrottlingStatus int status) {
return new Temperature(30.0f, Temperature.TYPE_SKIN, "test_skin_temp", status);
}
+
+ private Temperature getDisplayTempWithName(
+ String sensorName, @ThrottlingStatus int status) {
+ assertNotNull(sensorName);
+ return new Temperature(30.0f, Temperature.TYPE_DISPLAY, sensorName, status);
+ }
}
diff --git a/services/tests/displayservicetests/src/com/android/server/display/DisplayDeviceConfigTest.java b/services/tests/displayservicetests/src/com/android/server/display/DisplayDeviceConfigTest.java
index b29fc88..2867041 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/DisplayDeviceConfigTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/DisplayDeviceConfigTest.java
@@ -20,6 +20,7 @@
import static com.android.internal.display.BrightnessSynchronizer.brightnessIntToFloat;
import static com.android.server.display.AutomaticBrightnessController.AUTO_BRIGHTNESS_MODE_DEFAULT;
import static com.android.server.display.AutomaticBrightnessController.AUTO_BRIGHTNESS_MODE_DOZE;
+import static com.android.server.display.config.SensorData.TEMPERATURE_TYPE_SKIN;
import static com.android.server.display.config.SensorData.SupportedMode;
import static com.android.server.display.utils.DeviceConfigParsingUtils.ambientBrightnessThresholdsIntToFloat;
import static com.android.server.display.utils.DeviceConfigParsingUtils.displayBrightnessThresholdsIntToFloat;
@@ -106,6 +107,7 @@
MockitoAnnotations.initMocks(this);
when(mContext.getResources()).thenReturn(mResources);
when(mFlags.areAutoBrightnessModesEnabled()).thenReturn(true);
+ when(mFlags.isSensorBasedBrightnessThrottlingEnabled()).thenReturn(true);
mockDeviceConfigs();
}
@@ -143,6 +145,8 @@
assertEquals("", mDisplayDeviceConfig.getAmbientLightSensor().name);
assertNull(mDisplayDeviceConfig.getProximitySensor().type);
assertNull(mDisplayDeviceConfig.getProximitySensor().name);
+ assertEquals(TEMPERATURE_TYPE_SKIN, mDisplayDeviceConfig.getTempSensor().type);
+ assertNull(mDisplayDeviceConfig.getTempSensor().name);
assertTrue(mDisplayDeviceConfig.isAutoBrightnessAvailable());
}
@@ -592,6 +596,13 @@
}
@Test
+ public void testTempSensorFromDisplayConfig() throws IOException {
+ setupDisplayDeviceConfigFromDisplayConfigFile();
+ assertEquals("DISPLAY", mDisplayDeviceConfig.getTempSensor().type);
+ assertEquals("VIRTUAL-SKIN-DISPLAY", mDisplayDeviceConfig.getTempSensor().name);
+ }
+
+ @Test
public void testBlockingZoneThresholdsFromDisplayConfig() throws IOException {
setupDisplayDeviceConfigFromDisplayConfigFile();
@@ -1356,6 +1367,10 @@
+ "<name>Test Binned Brightness Sensor</name>\n"
+ "</screenOffBrightnessSensor>\n"
+ proxSensor
+ + "<tempSensor>\n"
+ + "<type>DISPLAY</type>\n"
+ + "<name>VIRTUAL-SKIN-DISPLAY</name>\n"
+ + "</tempSensor>\n"
+ "<ambientBrightnessChangeThresholds>\n"
+ "<brighteningThresholds>\n"
+ "<minimum>10</minimum>\n"
diff --git a/services/tests/displayservicetests/src/com/android/server/display/brightness/clamper/BrightnessThermalClamperTest.java b/services/tests/displayservicetests/src/com/android/server/display/brightness/clamper/BrightnessThermalClamperTest.java
index 37d0f62..34f352e 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/brightness/clamper/BrightnessThermalClamperTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/brightness/clamper/BrightnessThermalClamperTest.java
@@ -37,10 +37,14 @@
import com.android.server.display.DisplayDeviceConfig;
import com.android.server.display.DisplayDeviceConfig.ThermalBrightnessThrottlingData;
import com.android.server.display.DisplayDeviceConfig.ThermalBrightnessThrottlingData.ThrottlingLevel;
+import com.android.server.display.config.SensorData;
import com.android.server.display.feature.DeviceConfigParameterProvider;
import com.android.server.testutils.FakeDeviceConfigInterface;
import com.android.server.testutils.TestHandler;
+import junitparams.JUnitParamsRunner;
+import junitparams.Parameters;
+
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -50,9 +54,6 @@
import java.util.List;
-import junitparams.JUnitParamsRunner;
-import junitparams.Parameters;
-
@RunWith(JUnitParamsRunner.class)
public class BrightnessThermalClamperTest {
@@ -125,13 +126,13 @@
public void testNotifyThrottlingAfterOnDisplayChange(List<ThrottlingLevel> throttlingLevels,
@Temperature.ThrottlingStatus int throttlingStatus,
boolean expectedActive, float expectedBrightness) throws RemoteException {
- IThermalEventListener thermalEventListener = captureThermalEventListener();
+ IThermalEventListener thermalEventListener = captureSkinThermalEventListener();
mClamper.onDisplayChanged(new TestThermalData(throttlingLevels));
mTestHandler.flush();
assertFalse(mClamper.isActive());
assertEquals(PowerManager.BRIGHTNESS_MAX, mClamper.getBrightnessCap(), FLOAT_TOLERANCE);
- thermalEventListener.notifyThrottling(createTemperature(throttlingStatus));
+ thermalEventListener.notifyThrottling(createSkinTemperature(throttlingStatus));
mTestHandler.flush();
assertEquals(expectedActive, mClamper.isActive());
assertEquals(expectedBrightness, mClamper.getBrightnessCap(), FLOAT_TOLERANCE);
@@ -139,11 +140,11 @@
@Test
@Parameters(method = "testThrottlingData")
- public void testOnDisplayChangeAfterNotifyThrottlng(List<ThrottlingLevel> throttlingLevels,
+ public void testOnDisplayChangeAfterNotifyThrottling(List<ThrottlingLevel> throttlingLevels,
@Temperature.ThrottlingStatus int throttlingStatus,
boolean expectedActive, float expectedBrightness) throws RemoteException {
- IThermalEventListener thermalEventListener = captureThermalEventListener();
- thermalEventListener.notifyThrottling(createTemperature(throttlingStatus));
+ IThermalEventListener thermalEventListener = captureSkinThermalEventListener();
+ thermalEventListener.notifyThrottling(createSkinTemperature(throttlingStatus));
mTestHandler.flush();
assertFalse(mClamper.isActive());
assertEquals(PowerManager.BRIGHTNESS_MAX, mClamper.getBrightnessCap(), FLOAT_TOLERANCE);
@@ -156,8 +157,8 @@
@Test
public void testOverrideData() throws RemoteException {
- IThermalEventListener thermalEventListener = captureThermalEventListener();
- thermalEventListener.notifyThrottling(createTemperature(Temperature.THROTTLING_SEVERE));
+ IThermalEventListener thermalEventListener = captureSkinThermalEventListener();
+ thermalEventListener.notifyThrottling(createSkinTemperature(Temperature.THROTTLING_SEVERE));
mTestHandler.flush();
assertFalse(mClamper.isActive());
assertEquals(PowerManager.BRIGHTNESS_MAX, mClamper.getBrightnessCap(), FLOAT_TOLERANCE);
@@ -183,15 +184,60 @@
assertEquals(0.4f, mClamper.getBrightnessCap(), FLOAT_TOLERANCE);
}
- private IThermalEventListener captureThermalEventListener() throws RemoteException {
+ @Test
+ public void testDisplaySensorBasedThrottling() throws RemoteException {
+ final int severity = PowerManager.THERMAL_STATUS_SEVERE;
+ IThermalEventListener thermalEventListener = captureSkinThermalEventListener();
+ // Update config to listen to display type sensor.
+ final SensorData tempSensor = new SensorData("DISPLAY", "VIRTUAL-SKIN-DISPLAY");
+ final TestThermalData thermalData =
+ new TestThermalData(
+ DISPLAY_ID,
+ DisplayDeviceConfig.DEFAULT_ID,
+ List.of(new ThrottlingLevel(severity, 0.5f)),
+ tempSensor);
+ mClamper.onDisplayChanged(thermalData);
+ mTestHandler.flush();
+ verify(mMockThermalService).unregisterThermalEventListener(thermalEventListener);
+ thermalEventListener = captureThermalEventListener(Temperature.TYPE_DISPLAY);
+ assertFalse(mClamper.isActive());
+
+ // Verify no throttling triggered when any other sensor notification received.
+ thermalEventListener.notifyThrottling(createSkinTemperature(severity));
+ mTestHandler.flush();
+ assertFalse(mClamper.isActive());
+
+ thermalEventListener.notifyThrottling(createDisplayTemperature("OTHER-SENSOR", severity));
+ mTestHandler.flush();
+ assertFalse(mClamper.isActive());
+
+ assertEquals(PowerManager.BRIGHTNESS_MAX, mClamper.getBrightnessCap(), FLOAT_TOLERANCE);
+
+ // Verify throttling triggered when display sensor of given name throttled.
+ thermalEventListener.notifyThrottling(createDisplayTemperature(tempSensor.name, severity));
+ mTestHandler.flush();
+ assertTrue(mClamper.isActive());
+ assertEquals(0.5f, mClamper.getBrightnessCap(), FLOAT_TOLERANCE);
+ }
+
+ private IThermalEventListener captureSkinThermalEventListener() throws RemoteException {
+ return captureThermalEventListener(Temperature.TYPE_SKIN);
+ }
+
+ private IThermalEventListener captureThermalEventListener(int type) throws RemoteException {
ArgumentCaptor<IThermalEventListener> captor = ArgumentCaptor.forClass(
IThermalEventListener.class);
verify(mMockThermalService).registerThermalEventListenerWithType(captor.capture(), eq(
- Temperature.TYPE_SKIN));
+ type));
return captor.getValue();
}
- private Temperature createTemperature(@Temperature.ThrottlingStatus int status) {
+ private Temperature createDisplayTemperature(
+ @NonNull String sensorName, @Temperature.ThrottlingStatus int status) {
+ return new Temperature(100, Temperature.TYPE_DISPLAY, sensorName, status);
+ }
+
+ private Temperature createSkinTemperature(@Temperature.ThrottlingStatus int status) {
return new Temperature(100, Temperature.TYPE_SKIN, "test_temperature", status);
}
@@ -217,19 +263,26 @@
private final String mUniqueDisplayId;
private final String mDataId;
private final ThermalBrightnessThrottlingData mData;
+ private final SensorData mTempSensor;
private TestThermalData() {
- this(DISPLAY_ID, DisplayDeviceConfig.DEFAULT_ID, null);
+ this(DISPLAY_ID, DisplayDeviceConfig.DEFAULT_ID, null,
+ SensorData.loadTempSensorUnspecifiedConfig());
}
private TestThermalData(List<ThrottlingLevel> data) {
- this(DISPLAY_ID, DisplayDeviceConfig.DEFAULT_ID, data);
+ this(DISPLAY_ID, DisplayDeviceConfig.DEFAULT_ID, data,
+ SensorData.loadTempSensorUnspecifiedConfig());
}
- private TestThermalData(String uniqueDisplayId, String dataId, List<ThrottlingLevel> data) {
+
+ private TestThermalData(String uniqueDisplayId, String dataId, List<ThrottlingLevel> data,
+ SensorData tempSensor) {
mUniqueDisplayId = uniqueDisplayId;
mDataId = dataId;
mData = ThermalBrightnessThrottlingData.create(data);
+ mTempSensor = tempSensor;
}
+
@NonNull
@Override
public String getUniqueDisplayId() {
@@ -247,5 +300,11 @@
public ThermalBrightnessThrottlingData getThermalBrightnessThrottlingData() {
return mData;
}
+
+ @NonNull
+ @Override
+ public SensorData getTempSensor() {
+ return mTempSensor;
+ }
}
}
diff --git a/services/tests/media/mediarouterservicetest/src/com/android/server/media/AudioManagerRouteControllerTest.java b/services/tests/media/mediarouterservicetest/src/com/android/server/media/AudioManagerRouteControllerTest.java
index 8f5d125..a918be2 100644
--- a/services/tests/media/mediarouterservicetest/src/com/android/server/media/AudioManagerRouteControllerTest.java
+++ b/services/tests/media/mediarouterservicetest/src/com/android/server/media/AudioManagerRouteControllerTest.java
@@ -16,6 +16,8 @@
package com.android.server.media;
+import static android.Manifest.permission.MODIFY_AUDIO_ROUTING;
+
import static com.android.server.media.AudioRoutingUtils.ATTRIBUTES_MEDIA;
import static com.android.server.media.AudioRoutingUtils.getMediaAudioProductStrategy;
@@ -31,6 +33,7 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.app.Instrumentation;
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothManager;
import android.content.Context;
@@ -48,6 +51,7 @@
import androidx.test.platform.app.InstrumentationRegistry;
+import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -85,6 +89,7 @@
/* name= */ null,
/* address= */ null);
+ private Instrumentation mInstrumentation;
private AudioDeviceInfo mSelectedAudioDeviceInfo;
private Set<AudioDeviceInfo> mAvailableAudioDeviceInfos;
@Mock private AudioManager mMockAudioManager;
@@ -96,10 +101,11 @@
@Before
public void setUp() {
MockitoAnnotations.initMocks(this);
-
+ mInstrumentation = InstrumentationRegistry.getInstrumentation();
+ mInstrumentation.getUiAutomation().adoptShellPermissionIdentity(MODIFY_AUDIO_ROUTING);
Resources mockResources = Mockito.mock(Resources.class);
when(mockResources.getText(anyInt())).thenReturn(FAKE_ROUTE_NAME);
- Context realContext = InstrumentationRegistry.getInstrumentation().getContext();
+ Context realContext = mInstrumentation.getContext();
Context mockContext = Mockito.mock(Context.class);
when(mockContext.getResources()).thenReturn(mockResources);
// The bluetooth stack needs the application info, but we cannot use a spy because the
@@ -135,6 +141,11 @@
clearInvocations(mOnDeviceRouteChangedListener);
}
+ @After
+ public void tearDown() {
+ mInstrumentation.getUiAutomation().dropShellPermissionIdentity();
+ }
+
@Test
public void getSelectedRoute_afterDevicesConnect_returnsRightSelectedRoute() {
assertThat(mControllerUnderTest.getSelectedRoute().getType())
diff --git a/services/tests/mockingservicestests/src/com/android/server/alarm/AlarmManagerServiceTest.java b/services/tests/mockingservicestests/src/com/android/server/alarm/AlarmManagerServiceTest.java
index a476155..9975221 100644
--- a/services/tests/mockingservicestests/src/com/android/server/alarm/AlarmManagerServiceTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/alarm/AlarmManagerServiceTest.java
@@ -16,6 +16,8 @@
package com.android.server.alarm;
import static android.Manifest.permission.SCHEDULE_EXACT_ALARM;
+import static android.app.ActivityManager.UidFrozenStateChangedCallback.UID_FROZEN_STATE_FROZEN;
+import static android.app.ActivityManager.UidFrozenStateChangedCallback.UID_FROZEN_STATE_UNFROZEN;
import static android.app.AlarmManager.ELAPSED_REALTIME;
import static android.app.AlarmManager.ELAPSED_REALTIME_WAKEUP;
import static android.app.AlarmManager.EXACT_LISTENER_ALARMS_DROPPED_ON_CACHED;
@@ -42,6 +44,7 @@
import static android.app.usage.UsageStatsManager.STANDBY_BUCKET_WORKING_SET;
import static android.os.PowerWhitelistManager.TEMPORARY_ALLOWLIST_TYPE_FOREGROUND_SERVICE_ALLOWED;
import static android.os.PowerWhitelistManager.TEMPORARY_ALLOWLIST_TYPE_FOREGROUND_SERVICE_NOT_ALLOWED;
+import static android.platform.test.flag.junit.SetFlagsRule.DefaultInitValueType.NULL_DEFAULT;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.doAnswer;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.doCallRealMethod;
@@ -135,6 +138,7 @@
import android.os.BatteryManager;
import android.os.Bundle;
import android.os.Handler;
+import android.os.HandlerExecutor;
import android.os.IBinder;
import android.os.Looper;
import android.os.Message;
@@ -145,9 +149,11 @@
import android.os.ServiceManager;
import android.os.SystemProperties;
import android.os.UserHandle;
+import android.platform.test.annotations.EnableFlags;
import android.platform.test.annotations.Presubmit;
+import android.platform.test.flag.junit.SetFlagsRule;
+import android.platform.test.flag.util.FlagSetException;
import android.provider.DeviceConfig;
-import android.provider.Settings;
import android.text.format.DateFormat;
import android.util.ArraySet;
import android.util.Log;
@@ -215,6 +221,7 @@
private AppStateTrackerImpl.Listener mListener;
private AlarmManagerService.UninstallReceiver mPackageChangesReceiver;
private AlarmManagerService.ChargingReceiver mChargingReceiver;
+ private ActivityManager.UidFrozenStateChangedCallback mUidFrozenStateCallback;
private IAppOpsCallback mIAppOpsCallback;
private IAlarmManager mBinder;
@Mock
@@ -240,6 +247,8 @@
@Mock
private ActivityManagerInternal mActivityManagerInternal;
@Mock
+ private ActivityManager mActivityManager;
+ @Mock
private PackageManagerInternal mPackageManagerInternal;
@Mock
private AppStateTrackerImpl mAppStateTracker;
@@ -403,15 +412,31 @@
.mockStatic(PermissionChecker.class)
.mockStatic(PermissionManagerService.class)
.mockStatic(ServiceManager.class)
- .mockStatic(Settings.Global.class)
.mockStatic(SystemProperties.class)
.spyStatic(UserHandle.class)
.afterSessionFinished(
() -> LocalServices.removeServiceForTest(AlarmManagerInternal.class))
.build();
+ @Rule
+ public final SetFlagsRule mSetFlagsRule = new SetFlagsRule(NULL_DEFAULT);
+
+ /**
+ * Have to do this to switch the {@link Flags} implementation to {@link FakeFeatureFlagsImpl}.
+ * All methods that need any flag enabled should use the
+ * {@link android.platform.test.annotations.EnableFlags} annotation, in which case disabling
+ * the flag will fail with an exception that we will swallow here.
+ */
+ private void disableFlagsNotSetByAnnotation() {
+ try {
+ mSetFlagsRule.disableFlags(Flags.FLAG_USE_FROZEN_STATE_TO_DROP_LISTENER_ALARMS);
+ } catch (FlagSetException fse) {
+ // Expected if the test about to be run requires this enabled.
+ }
+ }
+
@Before
- public final void setUp() {
+ public void setUp() {
doReturn(mIActivityManager).when(ActivityManager::getService);
doReturn(mDeviceIdleInternal).when(
() -> LocalServices.getService(DeviceIdleInternal.class));
@@ -469,6 +494,7 @@
when(mMockContext.getSystemService(Context.APP_OPS_SERVICE)).thenReturn(mAppOpsManager);
when(mMockContext.getSystemService(BatteryManager.class)).thenReturn(mBatteryManager);
+ when(mMockContext.getSystemService(ActivityManager.class)).thenReturn(mActivityManager);
registerAppIds(new String[]{TEST_CALLING_PACKAGE},
new Integer[]{UserHandle.getAppId(TEST_CALLING_UID)});
@@ -479,7 +505,18 @@
mService = new AlarmManagerService(mMockContext, mInjector);
spyOn(mService);
+ disableFlagsNotSetByAnnotation();
+
mService.onStart();
+
+ if (Flags.useFrozenStateToDropListenerAlarms()) {
+ final ArgumentCaptor<ActivityManager.UidFrozenStateChangedCallback> frozenCaptor =
+ ArgumentCaptor.forClass(ActivityManager.UidFrozenStateChangedCallback.class);
+ verify(mActivityManager).registerUidFrozenStateChangedCallback(
+ any(HandlerExecutor.class), frozenCaptor.capture());
+ mUidFrozenStateCallback = frozenCaptor.getValue();
+ }
+
// Unable to mock mMockContext to return a mock stats manager.
// So just mocking the whole MetricsHelper instance.
mService.mMetricsHelper = mock(MetricsHelper.class);
@@ -3741,9 +3778,87 @@
mListener.handleUidCachedChanged(TEST_CALLING_UID, true);
assertAndHandleMessageSync(REMOVE_EXACT_LISTENER_ALARMS_ON_CACHED);
assertEquals(3, mService.mAlarmsPerUid.get(TEST_CALLING_UID));
+ assertEquals(numExactListenerUid2 + 2, mService.mAlarmsPerUid.get(TEST_CALLING_UID_2));
mListener.handleUidCachedChanged(TEST_CALLING_UID_2, true);
assertAndHandleMessageSync(REMOVE_EXACT_LISTENER_ALARMS_ON_CACHED);
+ assertEquals(3, mService.mAlarmsPerUid.get(TEST_CALLING_UID));
+ assertEquals(2, mService.mAlarmsPerUid.get(TEST_CALLING_UID_2));
+ }
+
+ private void executeUidFrozenStateCallback(int[] uids, int[] frozenStates) {
+ assertNotNull(mUidFrozenStateCallback);
+ mUidFrozenStateCallback.onUidFrozenStateChanged(uids, frozenStates);
+ }
+
+ @EnableFlags(Flags.FLAG_USE_FROZEN_STATE_TO_DROP_LISTENER_ALARMS)
+ @Test
+ public void exactListenerAlarmsRemovedOnFrozen() {
+ mockChangeEnabled(EXACT_LISTENER_ALARMS_DROPPED_ON_CACHED, true);
+
+ setTestAlarmWithListener(ELAPSED_REALTIME, 31, getNewListener(() -> {}), WINDOW_EXACT,
+ TEST_CALLING_UID);
+ setTestAlarmWithListener(RTC, 42, getNewListener(() -> {}), 56, TEST_CALLING_UID);
+ setTestAlarm(ELAPSED_REALTIME, 54, WINDOW_EXACT, getNewMockPendingIntent(), 0, 0,
+ TEST_CALLING_UID, null);
+ setTestAlarm(RTC, 49, 154, getNewMockPendingIntent(), 0, 0, TEST_CALLING_UID, null);
+
+ setTestAlarmWithListener(ELAPSED_REALTIME, 21, getNewListener(() -> {}), WINDOW_EXACT,
+ TEST_CALLING_UID_2);
+ setTestAlarmWithListener(RTC, 412, getNewListener(() -> {}), 561, TEST_CALLING_UID_2);
+ setTestAlarm(ELAPSED_REALTIME, 26, WINDOW_EXACT, getNewMockPendingIntent(), 0, 0,
+ TEST_CALLING_UID_2, null);
+ setTestAlarm(RTC, 549, 234, getNewMockPendingIntent(), 0, 0, TEST_CALLING_UID_2, null);
+
+ assertEquals(8, mService.mAlarmStore.size());
+
+ executeUidFrozenStateCallback(
+ new int[] {TEST_CALLING_UID, TEST_CALLING_UID_2},
+ new int[] {UID_FROZEN_STATE_FROZEN, UID_FROZEN_STATE_UNFROZEN});
+ assertEquals(7, mService.mAlarmStore.size());
+
+ executeUidFrozenStateCallback(
+ new int[] {TEST_CALLING_UID_2}, new int[] {UID_FROZEN_STATE_FROZEN});
+ assertEquals(6, mService.mAlarmStore.size());
+ }
+
+ @EnableFlags(Flags.FLAG_USE_FROZEN_STATE_TO_DROP_LISTENER_ALARMS)
+ @Test
+ public void alarmCountOnListenerFrozen() {
+ mockChangeEnabled(EXACT_LISTENER_ALARMS_DROPPED_ON_CACHED, true);
+
+ // Set some alarms for TEST_CALLING_UID.
+ final int numExactListenerUid1 = 17;
+ for (int i = 0; i < numExactListenerUid1; i++) {
+ setTestAlarmWithListener(ALARM_TYPES[i % 4], mNowElapsedTest + i,
+ getNewListener(() -> {}));
+ }
+ setTestAlarmWithListener(RTC, 42, getNewListener(() -> {}), 56, TEST_CALLING_UID);
+ setTestAlarm(ELAPSED_REALTIME, 54, getNewMockPendingIntent());
+ setTestAlarm(RTC, 49, 154, getNewMockPendingIntent(), 0, 0, TEST_CALLING_UID, null);
+
+ // Set some alarms for TEST_CALLING_UID_2.
+ final int numExactListenerUid2 = 11;
+ for (int i = 0; i < numExactListenerUid2; i++) {
+ setTestAlarmWithListener(ALARM_TYPES[i % 4], mNowElapsedTest + i,
+ getNewListener(() -> {}), WINDOW_EXACT, TEST_CALLING_UID_2);
+ }
+ setTestAlarmWithListener(RTC, 412, getNewListener(() -> {}), 561, TEST_CALLING_UID_2);
+ setTestAlarm(RTC_WAKEUP, 26, WINDOW_EXACT, getNewMockPendingIntent(), 0, 0,
+ TEST_CALLING_UID_2, null);
+
+ assertEquals(numExactListenerUid1 + 3, mService.mAlarmsPerUid.get(TEST_CALLING_UID));
+ assertEquals(numExactListenerUid2 + 2, mService.mAlarmsPerUid.get(TEST_CALLING_UID_2));
+
+ executeUidFrozenStateCallback(
+ new int[] {TEST_CALLING_UID, TEST_CALLING_UID_2},
+ new int[] {UID_FROZEN_STATE_FROZEN, UID_FROZEN_STATE_UNFROZEN});
+ assertEquals(3, mService.mAlarmsPerUid.get(TEST_CALLING_UID));
+ assertEquals(numExactListenerUid2 + 2, mService.mAlarmsPerUid.get(TEST_CALLING_UID_2));
+
+ executeUidFrozenStateCallback(
+ new int[] {TEST_CALLING_UID_2}, new int[] {UID_FROZEN_STATE_FROZEN});
+ assertEquals(3, mService.mAlarmsPerUid.get(TEST_CALLING_UID));
assertEquals(2, mService.mAlarmsPerUid.get(TEST_CALLING_UID_2));
}
diff --git a/services/tests/mockingservicestests/src/com/android/server/backup/restore/PerformUnifiedRestoreTaskTest.java b/services/tests/mockingservicestests/src/com/android/server/backup/restore/PerformUnifiedRestoreTaskTest.java
index 940469f..414532b 100644
--- a/services/tests/mockingservicestests/src/com/android/server/backup/restore/PerformUnifiedRestoreTaskTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/backup/restore/PerformUnifiedRestoreTaskTest.java
@@ -29,16 +29,20 @@
import android.app.backup.BackupDataOutput;
import android.app.backup.BackupTransport;
import android.content.Context;
+import android.content.pm.ApplicationInfo;
import android.content.pm.PackageInfo;
+import android.os.Build;
import android.os.Message;
import android.os.RemoteException;
import android.platform.test.annotations.Presubmit;
+import android.platform.test.flag.junit.SetFlagsRule;
import android.provider.DeviceConfig;
import androidx.test.InstrumentationRegistry;
import androidx.test.runner.AndroidJUnit4;
import com.android.modules.utils.testing.TestableDeviceConfig;
+import com.android.server.backup.Flags;
import com.android.server.backup.UserBackupManagerService;
import com.android.server.backup.internal.BackupHandler;
import com.android.server.backup.transport.BackupTransportClient;
@@ -56,10 +60,12 @@
import org.mockito.stubbing.Answer;
import java.util.ArrayDeque;
+import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
+import java.util.List;
import java.util.Map;
import java.util.Queue;
import java.util.Set;
@@ -74,10 +80,17 @@
private static final String SYSTEM_PACKAGE_NAME = "android";
private static final String NON_SYSTEM_PACKAGE_NAME = "package";
- @Mock private BackupDataInput mBackupDataInput;
- @Mock private BackupDataOutput mBackupDataOutput;
- @Mock private UserBackupManagerService mBackupManagerService;
- @Mock private TransportConnection mTransportConnection;
+ private static final String V_TO_U_ALLOWLIST = "pkg1";
+ private static final String V_TO_U_DENYLIST = "pkg2";
+
+ @Mock
+ private BackupDataInput mBackupDataInput;
+ @Mock
+ private BackupDataOutput mBackupDataOutput;
+ @Mock
+ private UserBackupManagerService mBackupManagerService;
+ @Mock
+ private TransportConnection mTransportConnection;
private Set<String> mExcludedkeys = new HashSet<>();
private Map<String, String> mBackupData = new HashMap<>();
@@ -91,6 +104,10 @@
public TestableDeviceConfig.TestableDeviceConfigRule mDeviceConfigRule =
new TestableDeviceConfig.TestableDeviceConfigRule();
+ @Rule
+ public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
+
+
private Context mContext;
@Before
@@ -118,7 +135,8 @@
return null;
});
- mRestoreTask = new PerformUnifiedRestoreTask(mBackupManagerService, mTransportConnection);
+ mRestoreTask = new PerformUnifiedRestoreTask(mBackupManagerService, mTransportConnection,
+ V_TO_U_ALLOWLIST, V_TO_U_DENYLIST);
}
private void populateTestData() {
@@ -235,6 +253,122 @@
== UnifiedRestoreState.FINAL);
}
+ @Test
+ public void testCreateVToUList_listSettingIsNull_returnEmptyList() {
+ List<String> expectedEmptyList = new ArrayList<>();
+
+ List<String> list = mRestoreTask.createVToUList(null);
+
+ assertEquals(list, expectedEmptyList);
+ }
+
+ @Test
+ public void testCreateVToUList_listIsNotNull_returnCorrectList() {
+ List<String> expectedList = Arrays.asList("a", "b", "c");
+ String listString = "a,b,c";
+
+ List<String> list = mRestoreTask.createVToUList(listString);
+
+ assertEquals(list, expectedList);
+ }
+
+ @Test
+ public void testIsVToUDowngrade_vToUFlagIsOffAndTargetIsUSourceIsV_returnFalse() {
+ mSetFlagsRule.disableFlags(
+ Flags.FLAG_ENABLE_V_TO_U_RESTORE_FOR_SYSTEM_COMPONENTS_IN_ALLOWLIST);
+
+ boolean isVToUDowngrade = mRestoreTask.isVToUDowngrade(
+ Build.VERSION_CODES.VANILLA_ICE_CREAM, Build.VERSION_CODES.UPSIDE_DOWN_CAKE);
+
+ assertFalse(isVToUDowngrade);
+ }
+
+ @Test
+ public void testIsVToUDowngrade_vToUFlagIsOnAndTargetIsUSourceIsV_returnTrue() {
+ mSetFlagsRule.enableFlags(
+ Flags.FLAG_ENABLE_V_TO_U_RESTORE_FOR_SYSTEM_COMPONENTS_IN_ALLOWLIST);
+
+ boolean isVToUDowngrade = mRestoreTask.isVToUDowngrade(
+ Build.VERSION_CODES.VANILLA_ICE_CREAM, Build.VERSION_CODES.UPSIDE_DOWN_CAKE);
+
+ assertTrue(isVToUDowngrade);
+ }
+
+ @Test
+ public void testIsVToUDowngrade_vToUFlagIsOnAndSourceIsNotV_returnFalse() {
+ mSetFlagsRule.enableFlags(
+ Flags.FLAG_ENABLE_V_TO_U_RESTORE_FOR_SYSTEM_COMPONENTS_IN_ALLOWLIST);
+
+ boolean isVToUDowngrade = mRestoreTask.isVToUDowngrade(Build.VERSION_CODES.UPSIDE_DOWN_CAKE,
+ Build.VERSION_CODES.UPSIDE_DOWN_CAKE);
+
+ assertFalse(isVToUDowngrade);
+ }
+
+ @Test
+ public void testIsVToUDowngrade_vToUFlagIsOnAndTargetIsNotU_returnFalse() {
+ mSetFlagsRule.enableFlags(
+ Flags.FLAG_ENABLE_V_TO_U_RESTORE_FOR_SYSTEM_COMPONENTS_IN_ALLOWLIST);
+
+ boolean isVToUDowngrade = mRestoreTask.isVToUDowngrade(
+ Build.VERSION_CODES.VANILLA_ICE_CREAM, Build.VERSION_CODES.VANILLA_ICE_CREAM);
+
+ assertFalse(isVToUDowngrade);
+ }
+
+
+ @Test
+ public void testIsEligibleForVToUDowngrade_pkgIsNotOnAllowlist_returnFalse() {
+ PackageInfo testPackageInfo = new PackageInfo();
+ testPackageInfo.packageName = "pkg";
+ testPackageInfo.applicationInfo = new ApplicationInfo();
+ // restoreAnyVersion flag is off
+ testPackageInfo.applicationInfo.flags = 0;
+
+ boolean eligibilityCriteria = mRestoreTask.isPackageEligibleForVToURestore(testPackageInfo);
+
+ assertFalse(eligibilityCriteria);
+ }
+
+ @Test
+ public void testIsEligibleForVToUDowngrade_pkgIsOnAllowlist_returnTrue() {
+ PackageInfo testPackageInfo = new PackageInfo();
+ testPackageInfo.packageName = "pkg1";
+ testPackageInfo.applicationInfo = new ApplicationInfo();
+ // restoreAnyVersion flag is off
+ testPackageInfo.applicationInfo.flags = 0;
+
+ boolean eligibilityCriteria = mRestoreTask.isPackageEligibleForVToURestore(testPackageInfo);
+
+ assertTrue(eligibilityCriteria);
+ }
+
+ @Test
+ public void testIsEligibleForVToUDowngrade_pkgIsNotOnDenyList_returnTrue() {
+ PackageInfo testPackageInfo = new PackageInfo();
+ testPackageInfo.packageName = "pkg";
+ testPackageInfo.applicationInfo = new ApplicationInfo();
+ // restoreAnyVersion flag is on
+ testPackageInfo.applicationInfo.flags = ApplicationInfo.FLAG_RESTORE_ANY_VERSION;
+
+ boolean eligibilityCriteria = mRestoreTask.isPackageEligibleForVToURestore(testPackageInfo);
+
+ assertTrue(eligibilityCriteria);
+ }
+
+ @Test
+ public void testIsEligibleForVToUDowngrade_pkgIsOnDenyList_returnFalse() {
+ PackageInfo testPackageInfo = new PackageInfo();
+ testPackageInfo.packageName = "pkg2";
+ testPackageInfo.applicationInfo = new ApplicationInfo();
+ // restoreAnyVersion flag is on
+ testPackageInfo.applicationInfo.flags = ApplicationInfo.FLAG_RESTORE_ANY_VERSION;
+
+ boolean eligibilityCriteria = mRestoreTask.isPackageEligibleForVToURestore(testPackageInfo);
+
+ assertFalse(eligibilityCriteria);
+ }
+
private void setupForRestoreKeyValueState(int transportStatus)
throws RemoteException, TransportNotAvailableException {
// Mock BackupHandler to do nothing when executeNextState() is called
diff --git a/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryUsageStatsProviderTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryUsageStatsProviderTest.java
index 6cd79bc..374426a 100644
--- a/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryUsageStatsProviderTest.java
+++ b/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryUsageStatsProviderTest.java
@@ -18,12 +18,17 @@
import static com.google.common.truth.Truth.assertThat;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyLong;
+import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import android.app.ActivityManager;
import android.content.Context;
import android.hardware.SensorManager;
+import android.os.AggregateBatteryConsumer;
import android.os.BatteryConsumer;
import android.os.BatteryManager;
import android.os.BatteryStats;
@@ -34,6 +39,7 @@
import android.os.Process;
import android.os.UidBatteryConsumer;
import android.platform.test.ravenwood.RavenwoodRule;
+import android.util.SparseLongArray;
import androidx.test.InstrumentationRegistry;
import androidx.test.filters.SmallTest;
@@ -49,7 +55,6 @@
import java.io.File;
import java.io.IOException;
-import java.nio.file.Files;
import java.util.List;
@SmallTest
@@ -64,11 +69,10 @@
private static final long MINUTE_IN_MS = 60 * 1000;
private static final double PRECISION = 0.00001;
- private File mHistoryDir;
-
@Rule(order = 1)
public final BatteryUsageStatsRule mStatsRule =
- new BatteryUsageStatsRule(12345, mHistoryDir)
+ new BatteryUsageStatsRule(12345)
+ .createTempDirectory()
.setAveragePower(PowerProfile.POWER_FLASHLIGHT, 360.0)
.setAveragePower(PowerProfile.POWER_AUDIO, 720.0);
@@ -77,9 +81,6 @@
@Before
public void setup() throws IOException {
- mHistoryDir = Files.createTempDirectory("BatteryUsageStatsProviderTest").toFile();
- clearDirectory(mHistoryDir);
-
if (RavenwoodRule.isUnderRavenwood()) {
mContext = mock(Context.class);
SensorManager sensorManager = mock(SensorManager.class);
@@ -89,17 +90,6 @@
}
}
- private void clearDirectory(File dir) {
- if (dir.exists()) {
- for (File child : dir.listFiles()) {
- if (child.isDirectory()) {
- clearDirectory(child);
- }
- child.delete();
- }
- }
- }
-
@Test
public void test_getBatteryUsageStats() {
BatteryStatsImpl batteryStats = prepareBatteryStats();
@@ -417,7 +407,7 @@
}
PowerStatsStore powerStatsStore = new PowerStatsStore(
- new File(mHistoryDir, "powerstatsstore"),
+ new File(mStatsRule.getHistoryDir(), "powerstatsstore"),
mStatsRule.getHandler(), null);
powerStatsStore.reset();
@@ -517,6 +507,57 @@
}
@Test
+ public void saveBatteryUsageStatsOnReset_incompatibleEnergyConsumers() throws Throwable {
+ MockBatteryStatsImpl batteryStats = mStatsRule.getBatteryStats();
+ batteryStats.initMeasuredEnergyStats(new String[]{"FOO", "BAR"});
+ int componentId0 = BatteryConsumer.FIRST_CUSTOM_POWER_COMPONENT_ID;
+ int componentId1 = BatteryConsumer.FIRST_CUSTOM_POWER_COMPONENT_ID + 1;
+
+ synchronized (batteryStats) {
+ batteryStats.getUidStatsLocked(APP_UID);
+
+ SparseLongArray uidEnergies = new SparseLongArray();
+ uidEnergies.put(APP_UID, 30_000_000);
+ batteryStats.updateCustomEnergyConsumerStatsLocked(0, 100_000_000, uidEnergies);
+ batteryStats.updateCustomEnergyConsumerStatsLocked(1, 200_000_000, uidEnergies);
+ }
+
+ BatteryUsageStatsProvider provider = new BatteryUsageStatsProvider(mContext, null,
+ mStatsRule.getPowerProfile(), mStatsRule.getCpuScalingPolicies(), null, mMockClock);
+
+ PowerStatsStore powerStatsStore = mock(PowerStatsStore.class);
+ doAnswer(invocation -> {
+ BatteryUsageStats stats = invocation.getArgument(1);
+ AggregateBatteryConsumer device = stats.getAggregateBatteryConsumer(
+ BatteryUsageStats.AGGREGATE_BATTERY_CONSUMER_SCOPE_DEVICE);
+ assertThat(device.getCustomPowerComponentName(componentId0)).isEqualTo("FOO");
+ assertThat(device.getCustomPowerComponentName(componentId1)).isEqualTo("BAR");
+ assertThat(device.getConsumedPowerForCustomComponent(componentId0))
+ .isWithin(PRECISION).of(27.77777);
+ assertThat(device.getConsumedPowerForCustomComponent(componentId1))
+ .isWithin(PRECISION).of(55.55555);
+
+ UidBatteryConsumer uid = stats.getUidBatteryConsumers().get(0);
+ assertThat(uid.getConsumedPowerForCustomComponent(componentId0))
+ .isWithin(PRECISION).of(8.33333);
+ assertThat(uid.getConsumedPowerForCustomComponent(componentId1))
+ .isWithin(PRECISION).of(8.33333);
+ return null;
+ }).when(powerStatsStore).storeBatteryUsageStats(anyLong(), any());
+
+ mStatsRule.getBatteryStats().saveBatteryUsageStatsOnReset(provider, powerStatsStore);
+
+ // Make an incompatible change of supported energy components. This will trigger
+ // a BatteryStats reset, which will generate a snapshot of battery stats.
+ mStatsRule.initMeasuredEnergyStatsLocked(
+ new String[]{"COMPONENT1"});
+
+ mStatsRule.waitForBackgroundThread();
+
+ verify(powerStatsStore).storeBatteryUsageStats(anyLong(), any());
+ }
+
+ @Test
public void testAggregateBatteryStats_incompatibleSnapshot() {
MockBatteryStatsImpl batteryStats = mStatsRule.getBatteryStats();
batteryStats.initMeasuredEnergyStats(new String[]{"FOO", "BAR"});
diff --git a/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryUsageStatsRule.java b/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryUsageStatsRule.java
index 8bdb029..296ad0e 100644
--- a/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryUsageStatsRule.java
+++ b/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryUsageStatsRule.java
@@ -16,6 +16,8 @@
package com.android.server.power.stats;
+import static com.google.common.truth.Truth.assertThat;
+
import static org.mockito.ArgumentMatchers.anyDouble;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.mock;
@@ -28,11 +30,11 @@
import android.os.BatteryStats;
import android.os.BatteryUsageStats;
import android.os.BatteryUsageStatsQuery;
+import android.os.ConditionVariable;
import android.os.Handler;
import android.os.HandlerThread;
import android.os.UidBatteryConsumer;
import android.os.UserBatteryConsumer;
-import android.platform.test.ravenwood.RavenwoodRule;
import android.util.SparseArray;
import androidx.test.InstrumentationRegistry;
@@ -47,6 +49,8 @@
import org.mockito.stubbing.Answer;
import java.io.File;
+import java.io.IOException;
+import java.nio.file.Files;
import java.util.Arrays;
@SuppressWarnings("SynchronizeOnNonFinalField")
@@ -59,7 +63,9 @@
private final PowerProfile mPowerProfile;
private final MockClock mMockClock = new MockClock();
- private final File mHistoryDir;
+ private String mTestName;
+ private boolean mCreateTempDirectory;
+ private File mHistoryDir;
private MockBatteryStatsImpl mBatteryStats;
private Handler mHandler;
@@ -74,34 +80,33 @@
private NetworkStats mNetworkStats;
private boolean[] mSupportedStandardBuckets;
private String[] mCustomPowerComponentNames;
+ private Throwable mThrowable;
public BatteryUsageStatsRule() {
- this(0, null);
+ this(0);
}
public BatteryUsageStatsRule(long currentTime) {
- this(currentTime, null);
- }
-
- public BatteryUsageStatsRule(long currentTime, File historyDir) {
mHandler = mock(Handler.class);
mPowerProfile = spy(new PowerProfile());
mMockClock.currentTime = currentTime;
- mHistoryDir = historyDir;
-
- if (!RavenwoodRule.isUnderRavenwood()) {
- lateInitBatteryStats();
- }
-
mCpusByPolicy.put(0, new int[]{0, 1, 2, 3});
mCpusByPolicy.put(4, new int[]{4, 5, 6, 7});
mFreqsByPolicy.put(0, new int[]{300000, 1000000, 2000000});
mFreqsByPolicy.put(4, new int[]{300000, 1000000, 2500000, 3000000});
}
- private void lateInitBatteryStats() {
+ private void initBatteryStats() {
if (mBatteryStats != null) return;
+ if (mCreateTempDirectory) {
+ try {
+ mHistoryDir = Files.createTempDirectory(mTestName).toFile();
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ }
+ clearDirectory();
+ }
mBatteryStats = new MockBatteryStatsImpl(mMockClock, mHistoryDir, mHandler);
mBatteryStats.setPowerProfile(mPowerProfile);
mBatteryStats.setCpuScalingPolicies(new CpuScalingPolicies(mCpusByPolicy, mFreqsByPolicy));
@@ -134,6 +139,15 @@
return mHandler;
}
+ public File getHistoryDir() {
+ return mHistoryDir;
+ }
+
+ public BatteryUsageStatsRule createTempDirectory() {
+ mCreateTempDirectory = true;
+ return this;
+ }
+
public BatteryUsageStatsRule setTestPowerProfile(@XmlRes int xmlId) {
mPowerProfile.forceInitForTesting(InstrumentationRegistry.getContext(), xmlId);
return this;
@@ -265,18 +279,23 @@
@Override
public Statement apply(Statement base, Description description) {
+ mTestName = description.getClassName() + "#" + description.getMethodName();
return new Statement() {
@Override
public void evaluate() throws Throwable {
before();
base.evaluate();
+ after();
}
};
}
private void before() {
- lateInitBatteryStats();
+ initBatteryStats();
HandlerThread bgThread = new HandlerThread("bg thread");
+ bgThread.setUncaughtExceptionHandler((thread, throwable)-> {
+ mThrowable = throwable;
+ });
bgThread.start();
mHandler = new Handler(bgThread.getLooper());
mBatteryStats.setHandler(mHandler);
@@ -285,6 +304,26 @@
mBatteryStats.getOnBatteryScreenOffTimeBase().setRunning(!mScreenOn, 0, 0);
}
+ private void after() throws Throwable {
+ if (mHandler != null) {
+ waitForBackgroundThread();
+ }
+ }
+
+ public void waitForBackgroundThread() throws Throwable {
+ if (mThrowable != null) {
+ throw mThrowable;
+ }
+
+ ConditionVariable done = new ConditionVariable();
+ mHandler.post(done::open);
+ assertThat(done.block(10000)).isTrue();
+
+ if (mThrowable != null) {
+ throw mThrowable;
+ }
+ }
+
public PowerProfile getPowerProfile() {
return mPowerProfile;
}
@@ -296,6 +335,9 @@
}
public MockBatteryStatsImpl getBatteryStats() {
+ if (mBatteryStats == null) {
+ initBatteryStats();
+ }
return mBatteryStats;
}
@@ -369,4 +411,19 @@
}
return null;
}
+
+ public void clearDirectory() {
+ clearDirectory(mHistoryDir);
+ }
+
+ private void clearDirectory(File dir) {
+ if (dir.exists()) {
+ for (File child : dir.listFiles()) {
+ if (child.isDirectory()) {
+ clearDirectory(child);
+ }
+ child.delete();
+ }
+ }
+ }
}
diff --git a/services/tests/powerstatstests/src/com/android/server/power/stats/PowerStatsAggregatorTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/PowerStatsAggregatorTest.java
index af5b462..2ea86a4 100644
--- a/services/tests/powerstatstests/src/com/android/server/power/stats/PowerStatsAggregatorTest.java
+++ b/services/tests/powerstatstests/src/com/android/server/power/stats/PowerStatsAggregatorTest.java
@@ -58,7 +58,7 @@
@Before
public void setup() throws ParseException {
- mHistory = new BatteryStatsHistory(32, 1024,
+ mHistory = new BatteryStatsHistory(1024,
mock(BatteryStatsHistory.HistoryStepDetailsCalculator.class), mClock,
mMonotonicClock, mock(BatteryStatsHistory.TraceDelegate.class), null);
diff --git a/services/tests/servicestests/src/com/android/server/pm/BaseShortcutManagerTest.java b/services/tests/servicestests/src/com/android/server/pm/BaseShortcutManagerTest.java
index 81df597..3e748ff 100644
--- a/services/tests/servicestests/src/com/android/server/pm/BaseShortcutManagerTest.java
+++ b/services/tests/servicestests/src/com/android/server/pm/BaseShortcutManagerTest.java
@@ -75,6 +75,7 @@
import android.content.pm.SigningInfo;
import android.content.pm.UserInfo;
import android.content.pm.UserPackage;
+import android.content.pm.UserProperties;
import android.content.res.Resources;
import android.content.res.XmlResourceParser;
import android.graphics.drawable.Icon;
@@ -776,6 +777,15 @@
new UserInfo(USER_P1, "userP1",
UserInfo.FLAG_INITIALIZED | UserInfo.FLAG_MANAGED_PROFILE), 0);
+ protected static final UserProperties USER_PROPERTIES_0 =
+ new UserProperties.Builder().setItemsRestrictedOnHomeScreen(false).build();
+
+ protected static final UserProperties USER_PROPERTIES_10 =
+ new UserProperties.Builder().setItemsRestrictedOnHomeScreen(false).build();
+
+ protected static final UserProperties USER_PROPERTIES_11 =
+ new UserProperties.Builder().setItemsRestrictedOnHomeScreen(true).build();
+
protected BiPredicate<String, Integer> mDefaultLauncherChecker =
(callingPackage, userId) ->
LAUNCHER_1.equals(callingPackage) || LAUNCHER_2.equals(callingPackage)
@@ -817,6 +827,7 @@
= new HashMap<>();
protected final Map<Integer, UserInfo> mUserInfos = new HashMap<>();
+ protected final Map<Integer, UserProperties> mUserProperties = new HashMap<>();
protected final Map<Integer, Boolean> mRunningUsers = new HashMap<>();
protected final Map<Integer, Boolean> mUnlockedUsers = new HashMap<>();
@@ -911,6 +922,9 @@
mUserInfos.put(USER_11, USER_INFO_11);
mUserInfos.put(USER_P0, USER_INFO_P0);
mUserInfos.put(USER_P1, USER_INFO_P1);
+ mUserProperties.put(USER_0, USER_PROPERTIES_0);
+ mUserProperties.put(USER_10, USER_PROPERTIES_10);
+ mUserProperties.put(USER_11, USER_PROPERTIES_11);
when(mMockUserManagerInternal.isUserUnlockingOrUnlocked(anyInt()))
.thenAnswer(inv -> {
@@ -959,6 +973,15 @@
inv -> mUserInfos.get((Integer) inv.getArguments()[0])));
when(mMockActivityManagerInternal.getUidProcessState(anyInt())).thenReturn(
ActivityManager.PROCESS_STATE_CACHED_EMPTY);
+ when(mMockUserManagerInternal.getUserProperties(anyInt()))
+ .thenAnswer(inv -> {
+ final int userId = (Integer) inv.getArguments()[0];
+ final UserProperties userProperties = mUserProperties.get(userId);
+ if (userProperties == null) {
+ return new UserProperties.Builder().build();
+ }
+ return userProperties;
+ });
// User 0 and P0 are always running
mRunningUsers.put(USER_0, true);
diff --git a/services/tests/servicestests/src/com/android/server/search/SearchablesTest.java b/services/tests/servicestests/src/com/android/server/search/SearchablesTest.java
index f5c6795..771a765 100644
--- a/services/tests/servicestests/src/com/android/server/search/SearchablesTest.java
+++ b/services/tests/servicestests/src/com/android/server/search/SearchablesTest.java
@@ -92,7 +92,7 @@
public void testNonSearchable() {
// test basic array & hashmap
Searchables searchables = new Searchables(mContext, 0);
- searchables.updateSearchableList();
+ searchables.updateSearchableListIfNeeded();
// confirm that we return null for non-searchy activities
ComponentName nonActivity = new ComponentName("com.android.frameworks.servicestests",
@@ -121,7 +121,7 @@
doReturn(true).when(mPackageManagerInternal).canAccessComponent(anyInt(), any(), anyInt());
Searchables searchables = new Searchables(mContext, 0);
- searchables.updateSearchableList();
+ searchables.updateSearchableListIfNeeded();
// tests with "real" searchables (deprecate, this should be a unit test)
ArrayList<SearchableInfo> searchablesList = searchables.getSearchablesList();
int count = searchablesList.size();
@@ -139,7 +139,7 @@
doReturn(false).when(mPackageManagerInternal).canAccessComponent(anyInt(), any(), anyInt());
Searchables searchables = new Searchables(mContext, 0);
- searchables.updateSearchableList();
+ searchables.updateSearchableListIfNeeded();
ArrayList<SearchableInfo> searchablesList = searchables.getSearchablesList();
assertNotNull(searchablesList);
MoreAsserts.assertEmpty(searchablesList);
diff --git a/services/tests/servicestests/src/com/android/server/webkit/TestSystemImpl.java b/services/tests/servicestests/src/com/android/server/webkit/TestSystemImpl.java
index 65662d6..2039f93 100644
--- a/services/tests/servicestests/src/com/android/server/webkit/TestSystemImpl.java
+++ b/services/tests/servicestests/src/com/android/server/webkit/TestSystemImpl.java
@@ -83,6 +83,13 @@
}
}
+ @Override
+ public void installExistingPackageForAllUsers(Context context, String packageName) {
+ for (int userId : mUsers) {
+ installPackageForUser(packageName, userId);
+ }
+ }
+
private void enablePackageForUser(String packageName, boolean enable, int userId) {
Map<Integer, PackageInfo> userPackages = mPackages.get(packageName);
if (userPackages == null) {
@@ -93,6 +100,17 @@
setPackageInfoForUser(userId, packageInfo);
}
+ private void installPackageForUser(String packageName, int userId) {
+ Map<Integer, PackageInfo> userPackages = mPackages.get(packageName);
+ if (userPackages == null) {
+ return;
+ }
+ PackageInfo packageInfo = userPackages.get(userId);
+ packageInfo.applicationInfo.flags |= ApplicationInfo.FLAG_INSTALLED;
+ packageInfo.applicationInfo.privateFlags &= (~ApplicationInfo.PRIVATE_FLAG_HIDDEN);
+ setPackageInfoForUser(userId, packageInfo);
+ }
+
@Override
public boolean systemIsDebuggable() { return mIsDebuggable; }
diff --git a/services/tests/servicestests/src/com/android/server/webkit/WebViewUpdateServiceTest.java b/services/tests/servicestests/src/com/android/server/webkit/WebViewUpdateServiceTest.java
index 5a06327..53c172a 100644
--- a/services/tests/servicestests/src/com/android/server/webkit/WebViewUpdateServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/webkit/WebViewUpdateServiceTest.java
@@ -1549,6 +1549,31 @@
Matchers.anyObject(), Mockito.eq(testPackage), Mockito.eq(true));
}
+ @Test
+ @RequiresFlagsEnabled("android.webkit.update_service_v2")
+ public void testDefaultWebViewPackageInstalling() {
+ String testPackage = "testDefault";
+ WebViewProviderInfo[] packages =
+ new WebViewProviderInfo[] {
+ new WebViewProviderInfo(
+ testPackage,
+ "",
+ true /* default available */,
+ false /* fallback */,
+ null)
+ };
+ setupWithPackages(packages);
+ mTestSystemImpl.setPackageInfo(
+ createPackageInfo(
+ testPackage, true /* enabled */, true /* valid */, false /* installed */));
+
+ // Check that the boot time logic tries to install the default package.
+ runWebViewBootPreparationOnMainSync();
+ Mockito.verify(mTestSystemImpl)
+ .installExistingPackageForAllUsers(
+ Matchers.anyObject(), Mockito.eq(testPackage));
+ }
+
private void testDefaultPackageChosen(PackageInfo packageInfo) {
WebViewProviderInfo[] packages =
new WebViewProviderInfo[] {
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
index cff7f46..715c9d4 100755
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
@@ -12008,7 +12008,7 @@
// style + self managed call - bypasses block
when(mTelecomManager.isInSelfManagedCall(
- r.getSbn().getPackageName(), r.getUser(), true)).thenReturn(true);
+ r.getSbn().getPackageName(), true)).thenReturn(true);
assertThat(mService.checkDisqualifyingFeatures(r.getUserId(), r.getUid(),
r.getSbn().getId(), r.getSbn().getTag(), r, false, false)).isTrue();
@@ -12091,7 +12091,7 @@
// style + self managed call - bypasses block
mService.clearNotifications();
reset(mUsageStats);
- when(mTelecomManager.isInSelfManagedCall(r.getSbn().getPackageName(), r.getUser(), true))
+ when(mTelecomManager.isInSelfManagedCall(r.getSbn().getPackageName(), true))
.thenReturn(true);
mService.addEnqueuedNotification(r);
diff --git a/services/tests/vibrator/src/com/android/server/vibrator/VibrationScalerTest.java b/services/tests/vibrator/src/com/android/server/vibrator/VibrationScalerTest.java
index b431888..3e59878 100644
--- a/services/tests/vibrator/src/com/android/server/vibrator/VibrationScalerTest.java
+++ b/services/tests/vibrator/src/com/android/server/vibrator/VibrationScalerTest.java
@@ -35,8 +35,8 @@
import android.content.ContentResolver;
import android.content.ContextWrapper;
import android.content.pm.PackageManagerInternal;
+import android.os.ExternalVibrationScale;
import android.os.Handler;
-import android.os.IExternalVibratorService;
import android.os.PowerManagerInternal;
import android.os.UserHandle;
import android.os.VibrationAttributes;
@@ -49,6 +49,7 @@
import android.os.vibrator.StepSegment;
import android.os.vibrator.VibrationConfig;
import android.os.vibrator.VibrationEffectSegment;
+import android.platform.test.annotations.RequiresFlagsDisabled;
import android.platform.test.annotations.RequiresFlagsEnabled;
import android.platform.test.flag.junit.CheckFlagsRule;
import android.platform.test.flag.junit.DeviceFlagsValueProvider;
@@ -119,29 +120,65 @@
public void testGetExternalVibrationScale() {
setDefaultIntensity(USAGE_TOUCH, Vibrator.VIBRATION_INTENSITY_LOW);
setUserSetting(Settings.System.HAPTIC_FEEDBACK_INTENSITY, VIBRATION_INTENSITY_HIGH);
- assertEquals(IExternalVibratorService.SCALE_VERY_HIGH,
- mVibrationScaler.getExternalVibrationScale(USAGE_TOUCH));
+ assertEquals(ExternalVibrationScale.ScaleLevel.SCALE_VERY_HIGH,
+ mVibrationScaler.getExternalVibrationScaleLevel(USAGE_TOUCH));
setUserSetting(Settings.System.HAPTIC_FEEDBACK_INTENSITY, VIBRATION_INTENSITY_MEDIUM);
- assertEquals(IExternalVibratorService.SCALE_HIGH,
- mVibrationScaler.getExternalVibrationScale(USAGE_TOUCH));
+ assertEquals(ExternalVibrationScale.ScaleLevel.SCALE_HIGH,
+ mVibrationScaler.getExternalVibrationScaleLevel(USAGE_TOUCH));
setUserSetting(Settings.System.HAPTIC_FEEDBACK_INTENSITY, VIBRATION_INTENSITY_LOW);
- assertEquals(IExternalVibratorService.SCALE_NONE,
- mVibrationScaler.getExternalVibrationScale(USAGE_TOUCH));
+ assertEquals(ExternalVibrationScale.ScaleLevel.SCALE_NONE,
+ mVibrationScaler.getExternalVibrationScaleLevel(USAGE_TOUCH));
setDefaultIntensity(USAGE_TOUCH, VIBRATION_INTENSITY_MEDIUM);
- assertEquals(IExternalVibratorService.SCALE_LOW,
- mVibrationScaler.getExternalVibrationScale(USAGE_TOUCH));
+ assertEquals(ExternalVibrationScale.ScaleLevel.SCALE_LOW,
+ mVibrationScaler.getExternalVibrationScaleLevel(USAGE_TOUCH));
setDefaultIntensity(USAGE_TOUCH, VIBRATION_INTENSITY_HIGH);
- assertEquals(IExternalVibratorService.SCALE_VERY_LOW,
- mVibrationScaler.getExternalVibrationScale(USAGE_TOUCH));
+ assertEquals(ExternalVibrationScale.ScaleLevel.SCALE_VERY_LOW,
+ mVibrationScaler.getExternalVibrationScaleLevel(USAGE_TOUCH));
setUserSetting(Settings.System.HAPTIC_FEEDBACK_INTENSITY, VIBRATION_INTENSITY_OFF);
// Vibration setting being bypassed will use default setting and not scale.
- assertEquals(IExternalVibratorService.SCALE_NONE,
- mVibrationScaler.getExternalVibrationScale(USAGE_TOUCH));
+ assertEquals(ExternalVibrationScale.ScaleLevel.SCALE_NONE,
+ mVibrationScaler.getExternalVibrationScaleLevel(USAGE_TOUCH));
+ }
+
+ @Test
+ @RequiresFlagsEnabled(Flags.FLAG_ADAPTIVE_HAPTICS_ENABLED)
+ public void testAdaptiveHapticsScale_withAdaptiveHapticsAvailable() {
+ setDefaultIntensity(USAGE_TOUCH, Vibrator.VIBRATION_INTENSITY_LOW);
+ setDefaultIntensity(USAGE_RINGTONE, Vibrator.VIBRATION_INTENSITY_LOW);
+ setUserSetting(Settings.System.HAPTIC_FEEDBACK_INTENSITY, VIBRATION_INTENSITY_HIGH);
+ setUserSetting(Settings.System.RING_VIBRATION_INTENSITY, VIBRATION_INTENSITY_HIGH);
+
+ mVibrationScaler.updateAdaptiveHapticsScale(USAGE_TOUCH, 0.5f);
+ mVibrationScaler.updateAdaptiveHapticsScale(USAGE_RINGTONE, 0.2f);
+
+ assertEquals(0.5f, mVibrationScaler.getAdaptiveHapticsScale(USAGE_TOUCH));
+ assertEquals(0.2f, mVibrationScaler.getAdaptiveHapticsScale(USAGE_RINGTONE));
+ assertEquals(1f, mVibrationScaler.getAdaptiveHapticsScale(USAGE_NOTIFICATION));
+
+ setUserSetting(Settings.System.HAPTIC_FEEDBACK_INTENSITY, VIBRATION_INTENSITY_OFF);
+ // Vibration setting being bypassed will apply adaptive haptics scales.
+ assertEquals(0.2f, mVibrationScaler.getAdaptiveHapticsScale(USAGE_RINGTONE));
+ }
+
+ @Test
+ @RequiresFlagsDisabled(Flags.FLAG_ADAPTIVE_HAPTICS_ENABLED)
+ public void testAdaptiveHapticsScale_flagDisabled_adaptiveHapticScaleAlwaysNone() {
+ setDefaultIntensity(USAGE_TOUCH, Vibrator.VIBRATION_INTENSITY_LOW);
+ setDefaultIntensity(USAGE_RINGTONE, Vibrator.VIBRATION_INTENSITY_LOW);
+ setUserSetting(Settings.System.HAPTIC_FEEDBACK_INTENSITY, VIBRATION_INTENSITY_HIGH);
+ setUserSetting(Settings.System.RING_VIBRATION_INTENSITY, VIBRATION_INTENSITY_HIGH);
+
+ mVibrationScaler.updateAdaptiveHapticsScale(USAGE_TOUCH, 0.5f);
+ mVibrationScaler.updateAdaptiveHapticsScale(USAGE_RINGTONE, 0.2f);
+
+ assertEquals(1f, mVibrationScaler.getAdaptiveHapticsScale(USAGE_TOUCH));
+ assertEquals(1f, mVibrationScaler.getAdaptiveHapticsScale(USAGE_RINGTONE));
+ assertEquals(1f, mVibrationScaler.getAdaptiveHapticsScale(USAGE_NOTIFICATION));
}
@Test
diff --git a/services/tests/vibrator/src/com/android/server/vibrator/VibratorControlServiceTest.java b/services/tests/vibrator/src/com/android/server/vibrator/VibratorControlServiceTest.java
index 2823223..417fbd0 100644
--- a/services/tests/vibrator/src/com/android/server/vibrator/VibratorControlServiceTest.java
+++ b/services/tests/vibrator/src/com/android/server/vibrator/VibratorControlServiceTest.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2021 The Android Open Source Project
+ * Copyright (C) 2023 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -35,7 +35,6 @@
import android.content.ComponentName;
import android.content.pm.PackageManagerInternal;
import android.frameworks.vibrator.ScaleParam;
-import android.frameworks.vibrator.VibrationParam;
import android.os.Binder;
import android.os.Handler;
import android.os.IBinder;
@@ -55,8 +54,6 @@
import org.mockito.junit.MockitoJUnit;
import org.mockito.junit.MockitoRule;
-import java.util.ArrayList;
-import java.util.List;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.TimeUnit;
@@ -135,7 +132,7 @@
vibrationScales.put(ScaleParam.TYPE_NOTIFICATION, 0.4f);
mVibratorControlService.onRequestVibrationParamsComplete(token,
- generateVibrationParams(vibrationScales));
+ VibrationParamGenerator.generateVibrationParams(vibrationScales));
verify(mMockVibrationScaler).updateAdaptiveHapticsScale(USAGE_ALARM, 0.7f);
verify(mMockVibrationScaler).updateAdaptiveHapticsScale(USAGE_NOTIFICATION, 0.4f);
@@ -162,7 +159,7 @@
vibrationScales.put(ScaleParam.TYPE_NOTIFICATION, 0.4f);
mVibratorControlService.onRequestVibrationParamsComplete(new Binder(),
- generateVibrationParams(vibrationScales));
+ VibrationParamGenerator.generateVibrationParams(vibrationScales));
verifyZeroInteractions(mMockVibrationScaler);
}
@@ -175,7 +172,8 @@
vibrationScales.put(ScaleParam.TYPE_ALARM, 0.7f);
vibrationScales.put(ScaleParam.TYPE_NOTIFICATION, 0.4f);
- mVibratorControlService.setVibrationParams(generateVibrationParams(vibrationScales),
+ mVibratorControlService.setVibrationParams(
+ VibrationParamGenerator.generateVibrationParams(vibrationScales),
mFakeVibratorController);
verify(mMockVibrationScaler).updateAdaptiveHapticsScale(USAGE_ALARM, 0.7f);
@@ -193,7 +191,8 @@
vibrationScales.put(ScaleParam.TYPE_ALARM, 0.7f);
vibrationScales.put(ScaleParam.TYPE_NOTIFICATION, 0.4f);
- mVibratorControlService.setVibrationParams(generateVibrationParams(vibrationScales),
+ mVibratorControlService.setVibrationParams(
+ VibrationParamGenerator.generateVibrationParams(vibrationScales),
mFakeVibratorController);
verifyZeroInteractions(mMockVibrationScaler);
@@ -268,28 +267,6 @@
}
}
- private VibrationParam[] generateVibrationParams(SparseArray<Float> vibrationScales) {
- List<VibrationParam> vibrationParamList = new ArrayList<>();
- for (int i = 0; i < vibrationScales.size(); i++) {
- int type = vibrationScales.keyAt(i);
- float scale = vibrationScales.valueAt(i);
-
- vibrationParamList.add(generateVibrationParam(type, scale));
- }
-
- return vibrationParamList.toArray(new VibrationParam[0]);
- }
-
- private VibrationParam generateVibrationParam(int type, float scale) {
- ScaleParam scaleParam = new ScaleParam();
- scaleParam.typesMask = type;
- scaleParam.scale = scale;
- VibrationParam vibrationParam = new VibrationParam();
- vibrationParam.setScale(scaleParam);
-
- return vibrationParam;
- }
-
private int buildVibrationTypesMask(int... types) {
int typesMask = 0;
for (int type : types) {
diff --git a/services/tests/vibrator/src/com/android/server/vibrator/VibratorManagerServiceTest.java b/services/tests/vibrator/src/com/android/server/vibrator/VibratorManagerServiceTest.java
index e7571ef..ed89ccf 100644
--- a/services/tests/vibrator/src/com/android/server/vibrator/VibratorManagerServiceTest.java
+++ b/services/tests/vibrator/src/com/android/server/vibrator/VibratorManagerServiceTest.java
@@ -50,6 +50,7 @@
import android.content.pm.PackageManager;
import android.content.pm.PackageManagerInternal;
import android.content.res.Resources;
+import android.frameworks.vibrator.ScaleParam;
import android.hardware.input.IInputManager;
import android.hardware.input.InputManager;
import android.hardware.input.InputManagerGlobal;
@@ -59,16 +60,17 @@
import android.media.AudioManager;
import android.os.CombinedVibration;
import android.os.ExternalVibration;
+import android.os.ExternalVibrationScale;
import android.os.Handler;
import android.os.IBinder;
import android.os.IExternalVibrationController;
-import android.os.IExternalVibratorService;
import android.os.IVibratorStateListener;
import android.os.Looper;
import android.os.PowerManager;
import android.os.PowerManagerInternal;
import android.os.PowerSaveState;
import android.os.Process;
+import android.os.RemoteException;
import android.os.SystemClock;
import android.os.UserHandle;
import android.os.VibrationAttributes;
@@ -82,6 +84,10 @@
import android.os.vibrator.StepSegment;
import android.os.vibrator.VibrationConfig;
import android.os.vibrator.VibrationEffectSegment;
+import android.platform.test.annotations.RequiresFlagsDisabled;
+import android.platform.test.annotations.RequiresFlagsEnabled;
+import android.platform.test.flag.junit.CheckFlagsRule;
+import android.platform.test.flag.junit.DeviceFlagsValueProvider;
import android.platform.test.flag.junit.SetFlagsRule;
import android.provider.Settings;
import android.util.SparseArray;
@@ -153,6 +159,8 @@
public MockitoRule rule = MockitoJUnit.rule();
@Rule
public FakeSettingsProviderRule mSettingsProviderRule = FakeSettingsProvider.rule();
+ @Rule
+ public final CheckFlagsRule mCheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule();
@Rule public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
@@ -185,8 +193,10 @@
private Context mContextSpy;
private TestLooper mTestLooper;
private FakeVibrator mVibrator;
+ private FakeVibratorController mFakeVibratorController;
private PowerManagerInternal.LowPowerModeListener mRegisteredPowerModeListener;
private VibratorManagerService.ExternalVibratorService mExternalVibratorService;
+ private VibratorControlService mVibratorControlService;
private VibrationConfig mVibrationConfig;
private InputManagerGlobal.TestSession mInputManagerGlobalSession;
private InputManager mInputManager;
@@ -197,6 +207,7 @@
mContextSpy = spy(new ContextWrapper(InstrumentationRegistry.getContext()));
mInputManagerGlobalSession = InputManagerGlobal.createTestSession(mIInputManagerMock);
mVibrationConfig = new VibrationConfig(mContextSpy.getResources());
+ mFakeVibratorController = new FakeVibratorController();
ContentResolver contentResolver = mSettingsProviderRule.mockContentResolver(mContextSpy);
when(mContextSpy.getContentResolver()).thenReturn(contentResolver);
@@ -310,6 +321,8 @@
if (service instanceof VibratorManagerService.ExternalVibratorService) {
mExternalVibratorService =
(VibratorManagerService.ExternalVibratorService) service;
+ } else if (service instanceof VibratorControlService) {
+ mVibratorControlService = (VibratorControlService) service;
}
}
@@ -321,9 +334,13 @@
VibratorControllerHolder createVibratorControllerHolder() {
VibratorControllerHolder holder = new VibratorControllerHolder();
- holder.setVibratorController(new FakeVibratorController());
+ holder.setVibratorController(mFakeVibratorController);
return holder;
}
+
+ boolean isServiceDeclared(String name) {
+ return true;
+ }
});
return mService;
}
@@ -1108,12 +1125,13 @@
ExternalVibration externalVibration = new ExternalVibration(UID, PACKAGE_NAME,
AUDIO_ALARM_ATTRS,
controller, firstToken);
- int scale = mExternalVibratorService.onExternalVibrationStart(externalVibration);
+ ExternalVibrationScale scale =
+ mExternalVibratorService.onExternalVibrationStart(externalVibration);
vibrateAndWaitUntilFinished(service, VibrationEffect.get(VibrationEffect.EFFECT_CLICK),
RINGTONE_ATTRS);
- assertNotEquals(IExternalVibratorService.SCALE_MUTE, scale);
+ assertNotEquals(ExternalVibrationScale.ScaleLevel.SCALE_MUTE, scale.scaleLevel);
// The external vibration should have been cancelled
verify(controller).mute();
assertEquals(Arrays.asList(false, true, false),
@@ -1708,13 +1726,14 @@
ExternalVibration externalVibration = new ExternalVibration(UID, PACKAGE_NAME,
AUDIO_ALARM_ATTRS,
mock(IExternalVibrationController.class), binderToken);
- int scale = mExternalVibratorService.onExternalVibrationStart(externalVibration);
- assertNotEquals(IExternalVibratorService.SCALE_MUTE, scale);
+ ExternalVibrationScale scale = mExternalVibratorService.onExternalVibrationStart(
+ externalVibration);
+ assertNotEquals(ExternalVibrationScale.ScaleLevel.SCALE_MUTE, scale.scaleLevel);
when(mVirtualDeviceManagerInternalMock.isAppRunningOnAnyVirtualDevice(UID))
.thenReturn(true);
scale = mExternalVibratorService.onExternalVibrationStart(externalVibration);
- assertEquals(IExternalVibratorService.SCALE_MUTE, scale);
+ assertEquals(ExternalVibrationScale.ScaleLevel.SCALE_MUTE, scale.scaleLevel);
}
@Test
@@ -1727,10 +1746,11 @@
ExternalVibration externalVibration = new ExternalVibration(UID, PACKAGE_NAME,
AUDIO_ALARM_ATTRS,
mock(IExternalVibrationController.class), binderToken);
- int scale = mExternalVibratorService.onExternalVibrationStart(externalVibration);
+ ExternalVibrationScale scale = mExternalVibratorService.onExternalVibrationStart(
+ externalVibration);
mExternalVibratorService.onExternalVibrationStop(externalVibration);
- assertNotEquals(IExternalVibratorService.SCALE_MUTE, scale);
+ assertNotEquals(ExternalVibrationScale.ScaleLevel.SCALE_MUTE, scale.scaleLevel);
assertEquals(Arrays.asList(false, true, false),
mVibratorProviders.get(1).getExternalControlStates());
@@ -1753,17 +1773,19 @@
ExternalVibration firstVibration = new ExternalVibration(UID, PACKAGE_NAME,
AUDIO_ALARM_ATTRS,
firstController, firstToken);
- int firstScale = mExternalVibratorService.onExternalVibrationStart(firstVibration);
+ ExternalVibrationScale firstScale =
+ mExternalVibratorService.onExternalVibrationStart(firstVibration);
AudioAttributes ringtoneAudioAttrs = new AudioAttributes.Builder()
.setUsage(AudioAttributes.USAGE_NOTIFICATION_RINGTONE)
.build();
ExternalVibration secondVibration = new ExternalVibration(UID, PACKAGE_NAME,
ringtoneAudioAttrs, secondController, secondToken);
- int secondScale = mExternalVibratorService.onExternalVibrationStart(secondVibration);
+ ExternalVibrationScale secondScale =
+ mExternalVibratorService.onExternalVibrationStart(secondVibration);
- assertNotEquals(IExternalVibratorService.SCALE_MUTE, firstScale);
- assertNotEquals(IExternalVibratorService.SCALE_MUTE, secondScale);
+ assertNotEquals(ExternalVibrationScale.ScaleLevel.SCALE_MUTE, firstScale.scaleLevel);
+ assertNotEquals(ExternalVibrationScale.ScaleLevel.SCALE_MUTE, secondScale.scaleLevel);
verify(firstController).mute();
verify(secondController, never()).mute();
// Set external control called only once.
@@ -1799,8 +1821,9 @@
ExternalVibration externalVibration = new ExternalVibration(UID, PACKAGE_NAME,
AUDIO_ALARM_ATTRS,
mock(IExternalVibrationController.class));
- int scale = mExternalVibratorService.onExternalVibrationStart(externalVibration);
- assertNotEquals(IExternalVibratorService.SCALE_MUTE, scale);
+ ExternalVibrationScale scale =
+ mExternalVibratorService.onExternalVibrationStart(externalVibration);
+ assertNotEquals(ExternalVibrationScale.ScaleLevel.SCALE_MUTE, scale.scaleLevel);
// Vibration is cancelled.
assertTrue(waitUntil(s -> !s.isVibrating(1), service, TEST_TIMEOUT_MILLIS));
@@ -1825,9 +1848,10 @@
ExternalVibration externalVibration = new ExternalVibration(UID, PACKAGE_NAME,
AUDIO_ALARM_ATTRS,
mock(IExternalVibrationController.class));
- int scale = mExternalVibratorService.onExternalVibrationStart(externalVibration);
+ ExternalVibrationScale scale =
+ mExternalVibratorService.onExternalVibrationStart(externalVibration);
// External vibration is ignored.
- assertEquals(IExternalVibratorService.SCALE_MUTE, scale);
+ assertEquals(ExternalVibrationScale.ScaleLevel.SCALE_MUTE, scale.scaleLevel);
// Vibration is not cancelled.
assertFalse(waitUntil(s -> !s.isVibrating(1), service, CLEANUP_TIMEOUT_MILLIS));
@@ -1852,8 +1876,9 @@
ExternalVibration externalVibration = new ExternalVibration(UID, PACKAGE_NAME,
AUDIO_ALARM_ATTRS, mock(IExternalVibrationController.class));
- int scale = mExternalVibratorService.onExternalVibrationStart(externalVibration);
- assertNotEquals(IExternalVibratorService.SCALE_MUTE, scale);
+ ExternalVibrationScale scale =
+ mExternalVibratorService.onExternalVibrationStart(externalVibration);
+ assertNotEquals(ExternalVibrationScale.ScaleLevel.SCALE_MUTE, scale.scaleLevel);
// Vibration is cancelled.
assertTrue(waitUntil(s -> !s.isVibrating(1), service, TEST_TIMEOUT_MILLIS));
@@ -1879,9 +1904,10 @@
ExternalVibration externalVibration = new ExternalVibration(UID, PACKAGE_NAME,
AUDIO_NOTIFICATION_ATTRS,
mock(IExternalVibrationController.class));
- int scale = mExternalVibratorService.onExternalVibrationStart(externalVibration);
+ ExternalVibrationScale scale =
+ mExternalVibratorService.onExternalVibrationStart(externalVibration);
// New vibration is ignored.
- assertEquals(IExternalVibratorService.SCALE_MUTE, scale);
+ assertEquals(ExternalVibrationScale.ScaleLevel.SCALE_MUTE, scale.scaleLevel);
// Vibration is not cancelled.
assertFalse(waitUntil(s -> !s.isVibrating(1), service, CLEANUP_TIMEOUT_MILLIS));
@@ -1901,18 +1927,19 @@
setRingerMode(AudioManager.RINGER_MODE_SILENT);
createSystemReadyService();
- int scale = mExternalVibratorService.onExternalVibrationStart(externalVibration);
- assertEquals(IExternalVibratorService.SCALE_MUTE, scale);
+ ExternalVibrationScale scale =
+ mExternalVibratorService.onExternalVibrationStart(externalVibration);
+ assertEquals(ExternalVibrationScale.ScaleLevel.SCALE_MUTE, scale.scaleLevel);
setRingerMode(AudioManager.RINGER_MODE_NORMAL);
createSystemReadyService();
scale = mExternalVibratorService.onExternalVibrationStart(externalVibration);
- assertNotEquals(IExternalVibratorService.SCALE_MUTE, scale);
+ assertNotEquals(ExternalVibrationScale.ScaleLevel.SCALE_MUTE, scale.scaleLevel);
setRingerMode(AudioManager.RINGER_MODE_VIBRATE);
createSystemReadyService();
scale = mExternalVibratorService.onExternalVibrationStart(externalVibration);
- assertNotEquals(IExternalVibratorService.SCALE_MUTE, scale);
+ assertNotEquals(ExternalVibrationScale.ScaleLevel.SCALE_MUTE, scale.scaleLevel);
}
@Test
@@ -1935,14 +1962,14 @@
ExternalVibration vib = new ExternalVibration(UID, PACKAGE_NAME, audioAttrs,
mock(IExternalVibrationController.class));
- int scale = mExternalVibratorService.onExternalVibrationStart(vib);
- assertEquals(IExternalVibratorService.SCALE_MUTE, scale);
+ ExternalVibrationScale scale = mExternalVibratorService.onExternalVibrationStart(vib);
+ assertEquals(ExternalVibrationScale.ScaleLevel.SCALE_MUTE, scale.scaleLevel);
mExternalVibratorService.onExternalVibrationStop(vib);
scale = mExternalVibratorService.onExternalVibrationStart(
new ExternalVibration(UID, PACKAGE_NAME, flaggedAudioAttrs,
mock(IExternalVibrationController.class)));
- assertNotEquals(IExternalVibratorService.SCALE_MUTE, scale);
+ assertNotEquals(ExternalVibrationScale.ScaleLevel.SCALE_MUTE, scale.scaleLevel);
}
@Test
@@ -1956,10 +1983,91 @@
.build();
createSystemReadyService();
- int scale = mExternalVibratorService.onExternalVibrationStart(
- new ExternalVibration(/* uid= */ 123, PACKAGE_NAME, flaggedAudioAttrs,
- mock(IExternalVibrationController.class)));
- assertEquals(IExternalVibratorService.SCALE_MUTE, scale);
+ ExternalVibrationScale scale =
+ mExternalVibratorService.onExternalVibrationStart(
+ new ExternalVibration(/* uid= */ 123, PACKAGE_NAME, flaggedAudioAttrs,
+ mock(IExternalVibrationController.class)));
+ assertEquals(ExternalVibrationScale.ScaleLevel.SCALE_MUTE, scale.scaleLevel);
+ }
+
+ @Test
+ @RequiresFlagsEnabled(android.os.vibrator.Flags.FLAG_ADAPTIVE_HAPTICS_ENABLED)
+ public void onExternalVibration_withAdaptiveHaptics_returnsCorrectAdaptiveScales()
+ throws RemoteException {
+ mockVibrators(1);
+ mVibratorProviders.get(1).setCapabilities(IVibrator.CAP_EXTERNAL_CONTROL,
+ IVibrator.CAP_AMPLITUDE_CONTROL);
+ createSystemReadyService();
+
+ SparseArray<Float> vibrationScales = new SparseArray<>();
+ vibrationScales.put(ScaleParam.TYPE_ALARM, 0.7f);
+ vibrationScales.put(ScaleParam.TYPE_NOTIFICATION, 0.4f);
+
+ mVibratorControlService.setVibrationParams(
+ VibrationParamGenerator.generateVibrationParams(vibrationScales),
+ mFakeVibratorController);
+ ExternalVibration externalVibration = new ExternalVibration(UID, PACKAGE_NAME,
+ AUDIO_ALARM_ATTRS,
+ mock(IExternalVibrationController.class));
+ ExternalVibrationScale scale =
+ mExternalVibratorService.onExternalVibrationStart(externalVibration);
+ mExternalVibratorService.onExternalVibrationStop(externalVibration);
+
+ assertEquals(scale.adaptiveHapticsScale, 0.7f, 0);
+
+ externalVibration = new ExternalVibration(UID, PACKAGE_NAME,
+ AUDIO_NOTIFICATION_ATTRS,
+ mock(IExternalVibrationController.class));
+ scale = mExternalVibratorService.onExternalVibrationStart(externalVibration);
+ mExternalVibratorService.onExternalVibrationStop(externalVibration);
+
+ assertEquals(scale.adaptiveHapticsScale, 0.4f, 0);
+
+ AudioAttributes ringtoneAudioAttrs = new AudioAttributes.Builder()
+ .setUsage(AudioAttributes.USAGE_NOTIFICATION_RINGTONE)
+ .build();
+ externalVibration = new ExternalVibration(UID, PACKAGE_NAME,
+ ringtoneAudioAttrs,
+ mock(IExternalVibrationController.class));
+ scale = mExternalVibratorService.onExternalVibrationStart(externalVibration);
+
+ assertEquals(scale.adaptiveHapticsScale, 1f, 0);
+ }
+
+ @Test
+ @RequiresFlagsDisabled(android.os.vibrator.Flags.FLAG_ADAPTIVE_HAPTICS_ENABLED)
+ public void onExternalVibration_withAdaptiveHapticsFlagDisabled_alwaysReturnScaleNone()
+ throws RemoteException {
+ mockVibrators(1);
+ mVibratorProviders.get(1).setCapabilities(IVibrator.CAP_EXTERNAL_CONTROL,
+ IVibrator.CAP_AMPLITUDE_CONTROL);
+ createSystemReadyService();
+
+ SparseArray<Float> vibrationScales = new SparseArray<>();
+ vibrationScales.put(ScaleParam.TYPE_ALARM, 0.7f);
+ vibrationScales.put(ScaleParam.TYPE_NOTIFICATION, 0.4f);
+
+ mVibratorControlService.setVibrationParams(
+ VibrationParamGenerator.generateVibrationParams(vibrationScales),
+ mFakeVibratorController);
+ ExternalVibration externalVibration = new ExternalVibration(UID, PACKAGE_NAME,
+ AUDIO_ALARM_ATTRS,
+ mock(IExternalVibrationController.class));
+ ExternalVibrationScale scale =
+ mExternalVibratorService.onExternalVibrationStart(externalVibration);
+ mExternalVibratorService.onExternalVibrationStop(externalVibration);
+
+ assertEquals(scale.adaptiveHapticsScale, 1f, 0);
+
+ mVibratorControlService.setVibrationParams(
+ VibrationParamGenerator.generateVibrationParams(vibrationScales),
+ mFakeVibratorController);
+ externalVibration = new ExternalVibration(UID, PACKAGE_NAME,
+ AUDIO_NOTIFICATION_ATTRS,
+ mock(IExternalVibrationController.class));
+ scale = mExternalVibratorService.onExternalVibrationStart(externalVibration);
+
+ assertEquals(scale.adaptiveHapticsScale, 1f, 0);
}
@Test
diff --git a/services/tests/vibrator/utils/com/android/server/vibrator/VibrationParamGenerator.java b/services/tests/vibrator/utils/com/android/server/vibrator/VibrationParamGenerator.java
new file mode 100644
index 0000000..a606388
--- /dev/null
+++ b/services/tests/vibrator/utils/com/android/server/vibrator/VibrationParamGenerator.java
@@ -0,0 +1,54 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.vibrator;
+
+import android.frameworks.vibrator.ScaleParam;
+import android.frameworks.vibrator.VibrationParam;
+import android.util.SparseArray;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * A helper class that can be used to generate arrays of {@link VibrationParam}.
+ */
+public final class VibrationParamGenerator {
+ /**
+ * Generates an array of {@link VibrationParam}.
+ */
+ public static VibrationParam[] generateVibrationParams(SparseArray<Float> vibrationScales) {
+ List<VibrationParam> vibrationParamList = new ArrayList<>();
+ for (int i = 0; i < vibrationScales.size(); i++) {
+ int type = vibrationScales.keyAt(i);
+ float scale = vibrationScales.valueAt(i);
+
+ vibrationParamList.add(generateVibrationParam(type, scale));
+ }
+
+ return vibrationParamList.toArray(new VibrationParam[0]);
+ }
+
+ private static VibrationParam generateVibrationParam(int type, float scale) {
+ ScaleParam scaleParam = new ScaleParam();
+ scaleParam.typesMask = type;
+ scaleParam.scale = scale;
+ VibrationParam vibrationParam = new VibrationParam();
+ vibrationParam.setScale(scaleParam);
+
+ return vibrationParam;
+ }
+}
diff --git a/services/tests/wmtests/src/com/android/server/wm/SurfaceSyncGroupTest.java b/services/tests/wmtests/src/com/android/server/wm/SurfaceSyncGroupTest.java
index d84620b..ead36f1 100644
--- a/services/tests/wmtests/src/com/android/server/wm/SurfaceSyncGroupTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/SurfaceSyncGroupTest.java
@@ -25,6 +25,7 @@
import static org.mockito.Mockito.verify;
import android.platform.test.annotations.Presubmit;
+import android.server.wm.BuildUtils;
import android.view.SurfaceControl;
import android.window.SurfaceSyncGroup;
@@ -370,6 +371,7 @@
assertEquals(0, finishedLatch.getCount());
}
+ @Test
public void testSurfaceSyncGroupTimeout() throws InterruptedException {
final CountDownLatch finishedLatch = new CountDownLatch(1);
SurfaceSyncGroup syncGroup = new SurfaceSyncGroup(TAG);
@@ -386,7 +388,7 @@
// Never finish syncTarget2 so it forces the timeout. Timeout is 1 second so wait a little
// over 1 second to make sure it completes.
- finishedLatch.await(1100, TimeUnit.MILLISECONDS);
+ finishedLatch.await(1100L * BuildUtils.HW_TIMEOUT_MULTIPLIER, TimeUnit.MILLISECONDS);
assertEquals(0, finishedLatch.getCount());
}
}
diff --git a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java
index 889f842..c217780 100644
--- a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java
+++ b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java
@@ -2582,7 +2582,6 @@
if (anyPackagesAppearing()) {
initRecognizer(userHandle);
}
- return;
}
if (curInteractor != null) {
@@ -2631,15 +2630,16 @@
}
}
- // There is no interactor, so just deal with a simple recognizer.
- int change = isPackageDisappearing(curRecognizer.getPackageName());
- if (change == PACKAGE_PERMANENT_CHANGE
- || change == PACKAGE_TEMPORARY_CHANGE) {
- setCurRecognizer(findAvailRecognizer(null, userHandle), userHandle);
+ if (curRecognizer != null) {
+ int change = isPackageDisappearing(curRecognizer.getPackageName());
+ if (change == PACKAGE_PERMANENT_CHANGE
+ || change == PACKAGE_TEMPORARY_CHANGE) {
+ setCurRecognizer(findAvailRecognizer(null, userHandle), userHandle);
- } else if (isPackageModified(curRecognizer.getPackageName())) {
- setCurRecognizer(findAvailRecognizer(curRecognizer.getPackageName(),
- userHandle), userHandle);
+ } else if (isPackageModified(curRecognizer.getPackageName())) {
+ setCurRecognizer(findAvailRecognizer(curRecognizer.getPackageName(),
+ userHandle), userHandle);
+ }
}
}
}
diff --git a/telecomm/java/android/telecom/TelecomManager.java b/telecomm/java/android/telecom/TelecomManager.java
index 08c76af..9792cdd 100644
--- a/telecomm/java/android/telecom/TelecomManager.java
+++ b/telecomm/java/android/telecom/TelecomManager.java
@@ -2797,13 +2797,10 @@
* calls for a given {@code packageName} and {@code userHandle}.
*
* @param packageName the package name of the app to check calls for.
- * @param userHandle the user handle on which to check for calls.
- * @param detectForAllUsers indicates if calls should be detected across all users. If it is
- * set to {@code true}, and the caller has the ability to interact
- * across users, the userHandle parameter is disregarded.
+ * @param userHandle the user handle to check calls for.
* @return {@code true} if there are ongoing calls, {@code false} otherwise.
- * @throws SecurityException if detectForAllUsers is true or userHandle is not the calling user
- * and the caller does not grant the ability to interact across users.
+ * @throws SecurityException if the userHandle is not the calling user and the caller does not
+ * grant the ability to interact across users.
* @hide
*/
@SystemApi
@@ -2811,11 +2808,45 @@
@RequiresPermission(allOf = {Manifest.permission.READ_PRIVILEGED_PHONE_STATE,
Manifest.permission.INTERACT_ACROSS_USERS}, conditional = true)
public boolean isInSelfManagedCall(@NonNull String packageName,
- @NonNull UserHandle userHandle, boolean detectForAllUsers) {
+ @NonNull UserHandle userHandle) {
ITelecomService service = getTelecomService();
if (service != null) {
try {
return service.isInSelfManagedCall(packageName, userHandle,
+ mContext.getOpPackageName(), false);
+ } catch (RemoteException e) {
+ Log.e(TAG, "RemoteException isInSelfManagedCall: " + e);
+ e.rethrowFromSystemServer();
+ return false;
+ }
+ } else {
+ throw new IllegalStateException("Telecom service is not present");
+ }
+ }
+
+ /**
+ * Determines whether there are any ongoing {@link PhoneAccount#CAPABILITY_SELF_MANAGED}
+ * calls for a given {@code packageName} amongst all users, given that detectForAllUsers is true
+ * and the caller has the ability to interact across users. If detectForAllUsers isn't enabled,
+ * the calls will be checked against the caller.
+ *
+ * @param packageName the package name of the app to check calls for.
+ * @param detectForAllUsers indicates if calls should be detected across all users.
+ * @return {@code true} if there are ongoing calls, {@code false} otherwise.
+ * @throws SecurityException if detectForAllUsers is true and the caller does not grant the
+ * ability to interact across users.
+ * @hide
+ */
+ @SystemApi
+ @FlaggedApi(Flags.FLAG_TELECOM_RESOLVE_HIDDEN_DEPENDENCIES)
+ @RequiresPermission(allOf = {Manifest.permission.READ_PRIVILEGED_PHONE_STATE,
+ Manifest.permission.INTERACT_ACROSS_USERS}, conditional = true)
+ public boolean isInSelfManagedCall(@NonNull String packageName,
+ boolean detectForAllUsers) {
+ ITelecomService service = getTelecomService();
+ if (service != null) {
+ try {
+ return service.isInSelfManagedCall(packageName, null,
mContext.getOpPackageName(), detectForAllUsers);
} catch (RemoteException e) {
Log.e(TAG, "RemoteException isInSelfManagedCall: " + e);
diff --git a/telephony/java/android/service/euicc/EuiccService.java b/telephony/java/android/service/euicc/EuiccService.java
index 5af2c34..5524541 100644
--- a/telephony/java/android/service/euicc/EuiccService.java
+++ b/telephony/java/android/service/euicc/EuiccService.java
@@ -856,10 +856,22 @@
int slotId, IGetAvailableMemoryInBytesCallback callback) {
mExecutor.execute(
() -> {
- long availableMemoryInBytes =
- EuiccService.this.onGetAvailableMemoryInBytes(slotId);
+ long availableMemoryInBytes = EuiccManager.EUICC_MEMORY_FIELD_UNAVAILABLE;
+ String unsupportedOperationMessage = "";
try {
- callback.onSuccess(availableMemoryInBytes);
+ availableMemoryInBytes =
+ EuiccService.this.onGetAvailableMemoryInBytes(slotId);
+ } catch (UnsupportedOperationException e) {
+ unsupportedOperationMessage = e.getMessage();
+ }
+
+ try {
+ if (!unsupportedOperationMessage.isEmpty()) {
+ callback.onUnsupportedOperationException(
+ unsupportedOperationMessage);
+ } else {
+ callback.onSuccess(availableMemoryInBytes);
+ }
} catch (RemoteException e) {
// Can't communicate with the phone process; ignore.
}
diff --git a/telephony/java/android/service/euicc/IGetAvailableMemoryInBytesCallback.aidl b/telephony/java/android/service/euicc/IGetAvailableMemoryInBytesCallback.aidl
index bd6d19b..e550e77 100644
--- a/telephony/java/android/service/euicc/IGetAvailableMemoryInBytesCallback.aidl
+++ b/telephony/java/android/service/euicc/IGetAvailableMemoryInBytesCallback.aidl
@@ -19,4 +19,5 @@
/** @hide */
oneway interface IGetAvailableMemoryInBytesCallback {
void onSuccess(long availableMemoryInBytes);
+ void onUnsupportedOperationException(String message);
}