summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--apex/jobscheduler/framework/java/android/app/job/JobInfo.java14
-rw-r--r--apex/jobscheduler/framework/java/android/app/job/JobService.java4
-rw-r--r--apex/jobscheduler/framework/java/com/android/server/DeviceIdleInternal.java9
-rw-r--r--apex/jobscheduler/service/java/com/android/server/DeviceIdleController.java39
-rw-r--r--cmds/sm/src/com/android/commands/sm/Sm.java2
-rw-r--r--core/api/current.txt7
-rw-r--r--core/api/system-current.txt6
-rw-r--r--core/api/test-current.txt2
-rw-r--r--core/java/android/app/ActivityManagerInternal.java5
-rw-r--r--core/java/android/app/UiModeManager.java2
-rw-r--r--core/java/android/app/admin/DevicePolicyManager.java34
-rw-r--r--core/java/android/app/admin/IDevicePolicyManager.aidl4
-rw-r--r--core/java/android/content/ClipboardManager.java4
-rw-r--r--core/java/android/content/pm/IPackageManager.aidl4
-rw-r--r--core/java/android/graphics/fonts/FontUpdateRequest.java7
-rw-r--r--core/java/android/hardware/display/BrightnessInfo.java24
-rw-r--r--core/java/android/view/OnReceiveContentListener.java18
-rw-r--r--core/java/com/android/internal/accessibility/dialog/AccessibilityTarget.java15
-rw-r--r--core/java/com/android/internal/accessibility/dialog/ToggleAccessibilityServiceTarget.java6
-rw-r--r--core/java/com/android/internal/accessibility/dialog/ToggleAllowListingFeatureTarget.java6
-rw-r--r--core/java/com/android/server/backup/PreferredActivityBackupHelper.java67
-rw-r--r--core/jni/com_android_internal_os_Zygote.cpp2
-rw-r--r--core/res/res/values/attrs.xml4
-rw-r--r--libs/WindowManager/Shell/res/layout/bubble_overflow_container.xml18
-rw-r--r--libs/WindowManager/Shell/res/layout/bubble_overflow_view.xml4
-rw-r--r--libs/WindowManager/Shell/res/values/dimen.xml4
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleExpandedView.java12
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleOverflowContainerView.java63
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubblePositioner.java67
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java56
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/animation/ExpandedAnimationController.java44
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/animation/StackAnimationController.java24
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PhonePipMenuController.java12
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipMenuView.java75
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipMotionHelper.java6
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipResizeGestureHandler.java3
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipTouchHandler.java3
-rw-r--r--media/java/android/media/MediaRouter2.java2
-rw-r--r--media/tests/MediaRouter/Android.bp2
-rw-r--r--media/tests/MediaRouter/AndroidManifest.xml1
-rw-r--r--media/tests/MediaRouter/src/com/android/mediaroutertest/MediaRouter2ManagerTest.java85
-rw-r--r--media/tests/MediaRouter/src/com/android/mediaroutertest/MediaRouter2ManagerTestActivity.java53
-rw-r--r--native/android/surface_control.cpp39
-rw-r--r--packages/SystemUI/animation/src/com/android/systemui/animation/ActivityLaunchAnimator.kt26
-rw-r--r--packages/SystemUI/res/layout/ongoing_privacy_chip.xml8
-rw-r--r--packages/SystemUI/res/layout/people_tile_emoji_background_large.xml58
-rw-r--r--packages/SystemUI/res/layout/people_tile_emoji_background_medium.xml58
-rw-r--r--packages/SystemUI/res/layout/people_tile_large_with_content.xml115
-rw-r--r--packages/SystemUI/res/layout/people_tile_medium_with_content.xml198
-rw-r--r--packages/SystemUI/res/layout/people_tile_punctuation_background_large.xml109
-rw-r--r--packages/SystemUI/res/layout/people_tile_punctuation_background_medium.xml95
-rw-r--r--packages/SystemUI/res/values/colors.xml3
-rw-r--r--packages/SystemUI/res/values/dimens.xml5
-rw-r--r--packages/SystemUI/res/values/strings.xml2
-rw-r--r--packages/SystemUI/res/values/styles.xml3
-rw-r--r--packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/AccessibilityFloatingMenu.java17
-rw-r--r--packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/AccessibilityFloatingMenuView.java23
-rw-r--r--packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/AccessibilityTargetAdapter.java18
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java28
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/KeyguardUnlockAnimationController.kt2
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java2
-rw-r--r--packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java10
-rw-r--r--packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarController.java3
-rw-r--r--packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarInflaterView.java1
-rw-r--r--packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarView.java46
-rw-r--r--packages/SystemUI/src/com/android/systemui/navigationbar/RotationButtonController.java15
-rw-r--r--packages/SystemUI/src/com/android/systemui/navigationbar/buttons/ContextualButtonGroup.java13
-rw-r--r--packages/SystemUI/src/com/android/systemui/people/PeopleTileViewHelper.java141
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/tiles/UserDetailView.java2
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/NotificationListener.java3
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java62
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationLaunchAnimatorController.kt26
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/legacy/NotificationGroupManagerLegacy.java438
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationBackgroundView.java3
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationRoundnessManager.java56
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java4
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpManagerPhone.java14
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationGroupAlertTransferHelper.java363
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java95
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarter.java78
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/policy/SmartReplyStateInflater.kt1
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/AccessibilityTargetAdapterTest.java27
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/animation/ActivityLaunchAnimatorTest.kt13
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarRotationContextTest.java4
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/people/PeopleTileViewHelperTest.java113
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/qs/tiles/UserDetailViewAdapterTest.kt1
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationRoundnessManagerTest.java12
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarterTest.java18
-rw-r--r--services/core/java/com/android/server/StorageManagerService.java2
-rw-r--r--services/core/java/com/android/server/UiModeManagerService.java4
-rw-r--r--services/core/java/com/android/server/am/ActivityManagerConstants.java41
-rw-r--r--services/core/java/com/android/server/am/ActivityManagerService.java21
-rw-r--r--services/core/java/com/android/server/am/ProcessList.java2
-rw-r--r--services/core/java/com/android/server/apphibernation/AppHibernationService.java5
-rw-r--r--services/core/java/com/android/server/backup/SystemBackupAgent.java2
-rw-r--r--services/core/java/com/android/server/biometrics/sensors/RevokeChallengeClient.java1
-rw-r--r--services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceGenerateChallengeClient.java1
-rw-r--r--services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceRevokeChallengeClient.java1
-rw-r--r--services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceGenerateChallengeClient.java1
-rw-r--r--services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceRevokeChallengeClient.java2
-rw-r--r--services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintGenerateChallengeClient.java7
-rw-r--r--services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintRevokeChallengeClient.java1
-rw-r--r--services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintGenerateChallengeClient.java1
-rw-r--r--services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintRevokeChallengeClient.java2
-rw-r--r--services/core/java/com/android/server/clipboard/ClipboardService.java64
-rw-r--r--services/core/java/com/android/server/display/AutomaticBrightnessController.java8
-rw-r--r--services/core/java/com/android/server/display/DisplayBlanker.java5
-rw-r--r--services/core/java/com/android/server/display/DisplayDevice.java4
-rw-r--r--services/core/java/com/android/server/display/DisplayDeviceConfig.java23
-rw-r--r--services/core/java/com/android/server/display/DisplayManagerService.java76
-rw-r--r--services/core/java/com/android/server/display/DisplayPowerController.java86
-rw-r--r--services/core/java/com/android/server/display/DisplayPowerState.java77
-rw-r--r--services/core/java/com/android/server/display/HighBrightnessModeController.java125
-rw-r--r--services/core/java/com/android/server/display/LocalDisplayAdapter.java60
-rw-r--r--services/core/java/com/android/server/display/RampAnimator.java50
-rw-r--r--services/core/java/com/android/server/display/VirtualDisplayAdapter.java3
-rw-r--r--services/core/java/com/android/server/graphics/fonts/UpdatableFontDir.java91
-rw-r--r--services/core/java/com/android/server/pm/PackageManagerService.java53
-rw-r--r--services/core/java/com/android/server/pm/Settings.java11
-rw-r--r--services/core/java/com/android/server/pm/verify/domain/DomainVerificationManagerInternal.java37
-rw-r--r--services/core/java/com/android/server/pm/verify/domain/DomainVerificationPersistence.java32
-rw-r--r--services/core/java/com/android/server/pm/verify/domain/DomainVerificationService.java69
-rw-r--r--services/core/java/com/android/server/pm/verify/domain/DomainVerificationSettings.java95
-rw-r--r--services/core/java/com/android/server/pm/verify/domain/proxy/DomainVerificationProxy.java16
-rw-r--r--services/core/java/com/android/server/pm/verify/domain/proxy/DomainVerificationProxyCombined.java7
-rw-r--r--services/core/java/com/android/server/pm/verify/domain/proxy/DomainVerificationProxyUnavailable.java26
-rw-r--r--services/core/java/com/android/server/pm/verify/domain/proxy/DomainVerificationProxyV1.java6
-rw-r--r--services/core/java/com/android/server/policy/PhoneWindowManager.java4
-rw-r--r--services/core/java/com/android/server/policy/keyguard/KeyguardServiceDelegate.java6
-rw-r--r--services/core/java/com/android/server/power/DisplayGroupPowerStateMapper.java9
-rw-r--r--services/core/java/com/android/server/power/PowerManagerService.java6
-rw-r--r--services/core/java/com/android/server/utils/OWNERS10
-rw-r--r--services/core/java/com/android/server/wm/WindowManagerService.java27
-rw-r--r--services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java38
-rw-r--r--services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationPackageTest.kt153
-rw-r--r--services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationPersistenceTest.kt12
-rw-r--r--services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationProxyTest.kt38
-rw-r--r--services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationTestUtils.kt5
-rw-r--r--services/tests/mockingservicestests/src/com/android/server/display/LocalDisplayAdapterTest.java12
-rw-r--r--services/tests/servicestests/src/com/android/server/display/HighBrightnessModeControllerTest.java11
-rw-r--r--services/tests/servicestests/src/com/android/server/graphics/fonts/UpdatableFontDirTest.java13
-rw-r--r--services/tests/uiservicestests/src/com/android/server/UiModeManagerServiceTest.java33
-rw-r--r--services/usb/java/com/android/server/usb/UsbService.java2
-rw-r--r--telecomm/java/android/telecom/DiagnosticCall.java27
-rw-r--r--telephony/java/android/telephony/CellIdentityNr.java2
145 files changed, 3560 insertions, 1156 deletions
diff --git a/apex/jobscheduler/framework/java/android/app/job/JobInfo.java b/apex/jobscheduler/framework/java/android/app/job/JobInfo.java
index 17682a5b655a..52442a66cf1d 100644
--- a/apex/jobscheduler/framework/java/android/app/job/JobInfo.java
+++ b/apex/jobscheduler/framework/java/android/app/job/JobInfo.java
@@ -1433,7 +1433,10 @@ public class JobInfo implements Parcelable {
}
/**
- * Specify that this job should be delayed by the provided amount of time.
+ * Specify that this job should be delayed by the provided amount of time. The job may not
+ * run the instant the delay has elapsed. JobScheduler will start the job at an
+ * indeterminate time after the delay has elapsed.
+ * <p>
* Because it doesn't make sense setting this property on a periodic job, doing so will
* throw an {@link java.lang.IllegalArgumentException} when
* {@link android.app.job.JobInfo.Builder#build()} is called.
@@ -1449,9 +1452,11 @@ public class JobInfo implements Parcelable {
/**
* Set deadline which is the maximum scheduling latency. The job will be run by this
- * deadline even if other requirements are not met. Because it doesn't make sense setting
- * this property on a periodic job, doing so will throw an
- * {@link java.lang.IllegalArgumentException} when
+ * deadline even if other requirements (including a delay set through
+ * {@link #setMinimumLatency(long)}) are not met.
+ * <p>
+ * Because it doesn't make sense setting this property on a periodic job, doing so will
+ * throw an {@link java.lang.IllegalArgumentException} when
* {@link android.app.job.JobInfo.Builder#build()} is called.
* @see JobInfo#getMaxExecutionDelayMillis()
*/
@@ -1465,6 +1470,7 @@ public class JobInfo implements Parcelable {
* Set up the back-off/retry policy.
* This defaults to some respectable values: {30 seconds, Exponential}. We cap back-off at
* 5hrs.
+ * <p>
* Note that trying to set a backoff criteria for a job with
* {@link #setRequiresDeviceIdle(boolean)} will throw an exception when you call build().
* This is because back-off typically does not make sense for these types of jobs. See
diff --git a/apex/jobscheduler/framework/java/android/app/job/JobService.java b/apex/jobscheduler/framework/java/android/app/job/JobService.java
index fa7a2d362ffa..c251529ae0b1 100644
--- a/apex/jobscheduler/framework/java/android/app/job/JobService.java
+++ b/apex/jobscheduler/framework/java/android/app/job/JobService.java
@@ -153,6 +153,10 @@ public abstract class JobService extends Service {
* Once this method returns (or times out), the system releases the wakelock that it is holding
* on behalf of the job.</p>
*
+ * <p class="caution"><strong>Note:</strong> When a job is stopped and rescheduled via this
+ * method call, the deadline constraint is excluded from the rescheduled job's constraint set.
+ * The rescheduled job will run again once all remaining constraints are satisfied.
+ *
* @param params The parameters identifying this job, similar to what was supplied to the job in
* the {@link #onStartJob(JobParameters)} callback, but with the stop reason
* included.
diff --git a/apex/jobscheduler/framework/java/com/android/server/DeviceIdleInternal.java b/apex/jobscheduler/framework/java/com/android/server/DeviceIdleInternal.java
index a9ca5cf5a26a..caf7e7f4a4ed 100644
--- a/apex/jobscheduler/framework/java/com/android/server/DeviceIdleInternal.java
+++ b/apex/jobscheduler/framework/java/com/android/server/DeviceIdleInternal.java
@@ -96,4 +96,13 @@ public interface DeviceIdleInternal {
* that the device is stationary or in motion.
*/
void unregisterStationaryListener(StationaryListener listener);
+
+ /**
+ * Apply some restrictions on temp allowlist type based on the reasonCode.
+ * @param reasonCode temp allowlist reason code.
+ * @param defaultType default temp allowlist type if reasonCode can not decide a type.
+ * @return temp allowlist type based on the reasonCode.
+ */
+ @TempAllowListType int getTempAllowListType(@ReasonCode int reasonCode,
+ @TempAllowListType int defaultType);
}
diff --git a/apex/jobscheduler/service/java/com/android/server/DeviceIdleController.java b/apex/jobscheduler/service/java/com/android/server/DeviceIdleController.java
index 57c8300b66f6..60f5769a46f7 100644
--- a/apex/jobscheduler/service/java/com/android/server/DeviceIdleController.java
+++ b/apex/jobscheduler/service/java/com/android/server/DeviceIdleController.java
@@ -19,6 +19,7 @@ package com.android.server;
import static android.os.PowerExemptionManager.REASON_SHELL;
import static android.os.PowerExemptionManager.REASON_UNKNOWN;
import static android.os.PowerExemptionManager.TEMPORARY_ALLOW_LIST_TYPE_FOREGROUND_SERVICE_ALLOWED;
+import static android.os.PowerExemptionManager.TEMPORARY_ALLOW_LIST_TYPE_NONE;
import static android.os.Process.INVALID_UID;
import android.Manifest;
@@ -58,6 +59,7 @@ import android.os.Handler;
import android.os.IDeviceIdleController;
import android.os.Looper;
import android.os.Message;
+import android.os.PowerExemptionManager;
import android.os.PowerExemptionManager.ReasonCode;
import android.os.PowerExemptionManager.TempAllowListType;
import android.os.PowerManager;
@@ -2015,6 +2017,12 @@ public class DeviceIdleController extends SystemService
public void unregisterStationaryListener(StationaryListener listener) {
DeviceIdleController.this.unregisterStationaryListener(listener);
}
+
+ @Override
+ public @TempAllowListType int getTempAllowListType(@ReasonCode int reasonCode,
+ @TempAllowListType int defaultType) {
+ return DeviceIdleController.this.getTempAllowListType(reasonCode, defaultType);
+ }
}
private class LocalPowerAllowlistService implements PowerAllowlistInternal {
@@ -2689,6 +2697,18 @@ public class DeviceIdleController extends SystemService
}
}
+ private @TempAllowListType int getTempAllowListType(@ReasonCode int reasonCode,
+ @TempAllowListType int defaultType) {
+ switch (reasonCode) {
+ case PowerExemptionManager.REASON_PUSH_MESSAGING_OVER_QUOTA:
+ return mLocalActivityManager.getPushMessagingOverQuotaBehavior();
+ case PowerExemptionManager.REASON_DENIED:
+ return TEMPORARY_ALLOW_LIST_TYPE_NONE;
+ default:
+ return defaultType;
+ }
+ }
+
void addPowerSaveTempAllowlistAppChecked(String packageName, long duration,
int userId, @ReasonCode int reasonCode, @Nullable String reason)
throws RemoteException {
@@ -2705,9 +2725,12 @@ public class DeviceIdleController extends SystemService
"addPowerSaveTempWhitelistApp", null);
final long token = Binder.clearCallingIdentity();
try {
- addPowerSaveTempAllowlistAppInternal(callingUid,
- packageName, duration, TEMPORARY_ALLOW_LIST_TYPE_FOREGROUND_SERVICE_ALLOWED,
- userId, true, reasonCode, reason);
+ @TempAllowListType int type = getTempAllowListType(reasonCode,
+ TEMPORARY_ALLOW_LIST_TYPE_FOREGROUND_SERVICE_ALLOWED);
+ if (type != TEMPORARY_ALLOW_LIST_TYPE_NONE) {
+ addPowerSaveTempAllowlistAppInternal(callingUid,
+ packageName, duration, type, userId, true, reasonCode, reason);
+ }
} finally {
Binder.restoreCallingIdentity(token);
}
@@ -2741,16 +2764,6 @@ public class DeviceIdleController extends SystemService
void addPowerSaveTempAllowlistAppInternal(int callingUid, String packageName,
long durationMs, @TempAllowListType int tempAllowListType, int userId, boolean sync,
@ReasonCode int reasonCode, @Nullable String reason) {
- synchronized (this) {
- int callingAppId = UserHandle.getAppId(callingUid);
- if (callingAppId >= Process.FIRST_APPLICATION_UID) {
- if (!mPowerSaveWhitelistSystemAppIds.get(callingAppId)) {
- throw new SecurityException(
- "Calling app " + UserHandle.formatUid(callingUid)
- + " is not on whitelist");
- }
- }
- }
try {
int uid = getContext().getPackageManager().getPackageUidAsUser(packageName, userId);
addPowerSaveTempWhitelistAppDirectInternal(callingUid, uid, durationMs,
diff --git a/cmds/sm/src/com/android/commands/sm/Sm.java b/cmds/sm/src/com/android/commands/sm/Sm.java
index 260c8a47ea3c..f5bee6c0c724 100644
--- a/cmds/sm/src/com/android/commands/sm/Sm.java
+++ b/cmds/sm/src/com/android/commands/sm/Sm.java
@@ -258,7 +258,7 @@ public final class Sm {
public void runDisableAppDataIsolation() throws RemoteException {
if (!SystemProperties.getBoolean(
- ANDROID_VOLD_APP_DATA_ISOLATION_ENABLED_PROPERTY, false)) {
+ ANDROID_VOLD_APP_DATA_ISOLATION_ENABLED_PROPERTY, true)) {
throw new IllegalStateException("Storage app data isolation is not enabled.");
}
final String pkgName = nextArg();
diff --git a/core/api/current.txt b/core/api/current.txt
index bdeb2e2bc2c3..a37203357cec 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -7466,9 +7466,10 @@ package android.app.admin {
field public static final int LOCK_TASK_FEATURE_SYSTEM_INFO = 1; // 0x1
field public static final int MAKE_USER_EPHEMERAL = 2; // 0x2
field public static final String MIME_TYPE_PROVISIONING_NFC = "application/com.android.managedprovisioning";
- field public static final int NEARBY_STREAMING_DISABLED = 0; // 0x0
- field public static final int NEARBY_STREAMING_ENABLED = 1; // 0x1
- field public static final int NEARBY_STREAMING_SAME_MANAGED_ACCOUNT_ONLY = 2; // 0x2
+ field public static final int NEARBY_STREAMING_DISABLED = 1; // 0x1
+ field public static final int NEARBY_STREAMING_ENABLED = 2; // 0x2
+ field public static final int NEARBY_STREAMING_NOT_CONTROLLED_BY_POLICY = 0; // 0x0
+ field public static final int NEARBY_STREAMING_SAME_MANAGED_ACCOUNT_ONLY = 3; // 0x3
field public static final int OPERATION_SAFETY_REASON_DRIVING_DISTRACTION = 1; // 0x1
field public static final int PASSWORD_COMPLEXITY_HIGH = 327680; // 0x50000
field public static final int PASSWORD_COMPLEXITY_LOW = 65536; // 0x10000
diff --git a/core/api/system-current.txt b/core/api/system-current.txt
index ed141780b166..c7960dd406ed 100644
--- a/core/api/system-current.txt
+++ b/core/api/system-current.txt
@@ -850,7 +850,7 @@ package android.app {
field public static final int DEFAULT_PRIORITY = 0; // 0x0
field public static final String EXTRA_CALLING_PACKAGE = "android.app.extra.CALLING_PACKAGE";
field public static final String EXTRA_PRIORITY = "android.app.extra.PRIORITY";
- field public static final int PROJECTION_TYPE_ALL = 65535; // 0xffff
+ field public static final int PROJECTION_TYPE_ALL = -1; // 0xffffffff
field public static final int PROJECTION_TYPE_AUTOMOTIVE = 1; // 0x1
field public static final int PROJECTION_TYPE_NONE = 0; // 0x0
}
@@ -10783,10 +10783,6 @@ package android.telecom {
method public final void addExistingConnection(@NonNull android.telecom.PhoneAccountHandle, @NonNull android.telecom.Connection, @NonNull android.telecom.Conference);
}
- @Deprecated public abstract class DiagnosticCall extends android.telecom.CallDiagnostics {
- ctor @Deprecated public DiagnosticCall();
- }
-
public abstract class InCallService extends android.app.Service {
method @Deprecated public android.telecom.Phone getPhone();
method @Deprecated public void onPhoneCreated(android.telecom.Phone);
diff --git a/core/api/test-current.txt b/core/api/test-current.txt
index 61a9954c6336..875a9e7cd2e0 100644
--- a/core/api/test-current.txt
+++ b/core/api/test-current.txt
@@ -391,7 +391,7 @@ package android.app {
method public boolean isUiModeLocked();
method @RequiresPermission(value=android.Manifest.permission.TOGGLE_AUTOMOTIVE_PROJECTION, conditional=true) public boolean releaseProjection(int);
method @RequiresPermission(value=android.Manifest.permission.TOGGLE_AUTOMOTIVE_PROJECTION, conditional=true) public boolean requestProjection(int);
- field public static final int PROJECTION_TYPE_ALL = 65535; // 0xffff
+ field public static final int PROJECTION_TYPE_ALL = -1; // 0xffffffff
field public static final int PROJECTION_TYPE_AUTOMOTIVE = 1; // 0x1
field public static final int PROJECTION_TYPE_NONE = 0; // 0x0
}
diff --git a/core/java/android/app/ActivityManagerInternal.java b/core/java/android/app/ActivityManagerInternal.java
index 605340061994..ab610e4e71c6 100644
--- a/core/java/android/app/ActivityManagerInternal.java
+++ b/core/java/android/app/ActivityManagerInternal.java
@@ -583,4 +583,9 @@ public abstract class ActivityManagerInternal {
* Is the FGS started from an uid temporarily allowed to have while-in-use permission?
*/
public abstract boolean isTempAllowlistedForFgsWhileInUse(int uid);
+
+ /**
+ * Return the temp allowlist type when server push messaging is over the quota.
+ */
+ public abstract @TempAllowListType int getPushMessagingOverQuotaBehavior();
}
diff --git a/core/java/android/app/UiModeManager.java b/core/java/android/app/UiModeManager.java
index 9b99ab8e31cb..24fd04bedeae 100644
--- a/core/java/android/app/UiModeManager.java
+++ b/core/java/android/app/UiModeManager.java
@@ -713,7 +713,7 @@ public class UiModeManager {
*/
@SystemApi
@TestApi
- public static final int PROJECTION_TYPE_ALL = 0xffff;
+ public static final int PROJECTION_TYPE_ALL = -1; // All bits on
/** @hide */
@IntDef(prefix = {"PROJECTION_TYPE_"}, value = {
diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java
index cbf2d6a12bec..f07f45389d82 100644
--- a/core/java/android/app/admin/DevicePolicyManager.java
+++ b/core/java/android/app/admin/DevicePolicyManager.java
@@ -1678,23 +1678,30 @@ public class DevicePolicyManager {
})
public @interface PasswordComplexity {}
+ /**
+ * Indicates that nearby streaming is not controlled by policy, which means nearby streaming is
+ * allowed.
+ */
+ public static final int NEARBY_STREAMING_NOT_CONTROLLED_BY_POLICY = 0;
+
/** Indicates that nearby streaming is disabled. */
- public static final int NEARBY_STREAMING_DISABLED = 0;
+ public static final int NEARBY_STREAMING_DISABLED = 1;
/** Indicates that nearby streaming is enabled. */
- public static final int NEARBY_STREAMING_ENABLED = 1;
+ public static final int NEARBY_STREAMING_ENABLED = 2;
/**
* Indicates that nearby streaming is enabled only to devices offering a comparable level of
* security, with the same authenticated managed account.
*/
- public static final int NEARBY_STREAMING_SAME_MANAGED_ACCOUNT_ONLY = 2;
+ public static final int NEARBY_STREAMING_SAME_MANAGED_ACCOUNT_ONLY = 3;
/**
* @hide
*/
@Retention(RetentionPolicy.SOURCE)
@IntDef(prefix = {"NEARBY_STREAMING_"}, value = {
+ NEARBY_STREAMING_NOT_CONTROLLED_BY_POLICY,
NEARBY_STREAMING_DISABLED,
NEARBY_STREAMING_ENABLED,
NEARBY_STREAMING_SAME_MANAGED_ACCOUNT_ONLY,
@@ -7199,15 +7206,20 @@ public class DevicePolicyManager {
/**
* Returns the current runtime nearby notification streaming policy set by the device or profile
- * owner. The default is {@link #NEARBY_STREAMING_DISABLED}.
+ * owner.
*/
public @NearbyStreamingPolicy int getNearbyNotificationStreamingPolicy() {
+ return getNearbyNotificationStreamingPolicy(myUserId());
+ }
+
+ /** @hide per-user version */
+ public @NearbyStreamingPolicy int getNearbyNotificationStreamingPolicy(int userId) {
throwIfParentInstance("getNearbyNotificationStreamingPolicy");
if (mService == null) {
- return NEARBY_STREAMING_DISABLED;
+ return NEARBY_STREAMING_NOT_CONTROLLED_BY_POLICY;
}
try {
- return mService.getNearbyNotificationStreamingPolicy();
+ return mService.getNearbyNotificationStreamingPolicy(userId);
} catch (RemoteException re) {
throw re.rethrowFromSystemServer();
}
@@ -7235,15 +7247,19 @@ public class DevicePolicyManager {
/**
* Returns the current runtime nearby app streaming policy set by the device or profile owner.
- * The default is {@link #NEARBY_STREAMING_DISABLED}.
*/
public @NearbyStreamingPolicy int getNearbyAppStreamingPolicy() {
+ return getNearbyAppStreamingPolicy(myUserId());
+ }
+
+ /** @hide per-user version */
+ public @NearbyStreamingPolicy int getNearbyAppStreamingPolicy(int userId) {
throwIfParentInstance("getNearbyAppStreamingPolicy");
if (mService == null) {
- return NEARBY_STREAMING_DISABLED;
+ return NEARBY_STREAMING_NOT_CONTROLLED_BY_POLICY;
}
try {
- return mService.getNearbyAppStreamingPolicy();
+ return mService.getNearbyAppStreamingPolicy(userId);
} catch (RemoteException re) {
throw re.rethrowFromSystemServer();
}
diff --git a/core/java/android/app/admin/IDevicePolicyManager.aidl b/core/java/android/app/admin/IDevicePolicyManager.aidl
index 8e86f6545f23..370db60fb825 100644
--- a/core/java/android/app/admin/IDevicePolicyManager.aidl
+++ b/core/java/android/app/admin/IDevicePolicyManager.aidl
@@ -134,10 +134,10 @@ interface IDevicePolicyManager {
boolean getScreenCaptureDisabled(in ComponentName who, int userHandle, boolean parent);
void setNearbyNotificationStreamingPolicy(int policy);
- int getNearbyNotificationStreamingPolicy();
+ int getNearbyNotificationStreamingPolicy(int userId);
void setNearbyAppStreamingPolicy(int policy);
- int getNearbyAppStreamingPolicy();
+ int getNearbyAppStreamingPolicy(int userId);
void setKeyguardDisabledFeatures(in ComponentName who, int which, boolean parent);
int getKeyguardDisabledFeatures(in ComponentName who, int userHandle, boolean parent);
diff --git a/core/java/android/content/ClipboardManager.java b/core/java/android/content/ClipboardManager.java
index 11adfa3cecc0..d41cda102103 100644
--- a/core/java/android/content/ClipboardManager.java
+++ b/core/java/android/content/ClipboardManager.java
@@ -100,6 +100,10 @@ public class ClipboardManager extends android.text.ClipboardManager {
/**
* Callback that is invoked by {@link android.content.ClipboardManager} when the primary
* clip changes.
+ *
+ * <p>This is called when the result of {@link ClipDescription#getClassificationStatus()}
+ * changes, as well as when new clip data is set. So in cases where text classification is
+ * performed, this callback may be invoked multiple times for the same clip.
*/
void onPrimaryClipChanged();
}
diff --git a/core/java/android/content/pm/IPackageManager.aidl b/core/java/android/content/pm/IPackageManager.aidl
index 5e72325eed43..c2ac80e7c98f 100644
--- a/core/java/android/content/pm/IPackageManager.aidl
+++ b/core/java/android/content/pm/IPackageManager.aidl
@@ -310,8 +310,8 @@ interface IPackageManager {
void restorePreferredActivities(in byte[] backup, int userId);
byte[] getDefaultAppsBackup(int userId);
void restoreDefaultApps(in byte[] backup, int userId);
- byte[] getIntentFilterVerificationBackup(int userId);
- void restoreIntentFilterVerification(in byte[] backup, int userId);
+ byte[] getDomainVerificationBackup(int userId);
+ void restoreDomainVerification(in byte[] backup, int userId);
/**
* Report the set of 'Home' activity candidates, plus (if any) which of them
diff --git a/core/java/android/graphics/fonts/FontUpdateRequest.java b/core/java/android/graphics/fonts/FontUpdateRequest.java
index 4dd5a72d446e..cda1638a24dc 100644
--- a/core/java/android/graphics/fonts/FontUpdateRequest.java
+++ b/core/java/android/graphics/fonts/FontUpdateRequest.java
@@ -147,7 +147,7 @@ public final class FontUpdateRequest implements Parcelable {
public static Font readFromXml(XmlPullParser parser) throws IOException {
String psName = parser.getAttributeValue(null, ATTR_POSTSCRIPT_NAME);
if (psName == null) {
- throw new IOException("name attribute is missing font tag.");
+ throw new IOException("name attribute is missing in font tag.");
}
int index = getAttributeValueInt(parser, ATTR_INDEX, 0);
int weight = getAttributeValueInt(parser, ATTR_WEIGHT, FontStyle.FONT_WEIGHT_NORMAL);
@@ -210,7 +210,7 @@ public final class FontUpdateRequest implements Parcelable {
private static final String ATTR_NAME = "name";
private static final String TAG_FONT = "font";
- private final @Nullable String mName;
+ private final @NonNull String mName;
private final @NonNull List<Font> mFonts;
public Family(String name, List<Font> fonts) {
@@ -281,6 +281,9 @@ public final class FontUpdateRequest implements Parcelable {
throw new IOException("Unexpected parser state: must be START_TAG with family");
}
String name = parser.getAttributeValue(null, ATTR_NAME);
+ if (name == null) {
+ throw new IOException("name attribute is missing in family tag.");
+ }
int type = 0;
while ((type = parser.next()) != XmlPullParser.END_DOCUMENT) {
if (type == XmlPullParser.START_TAG && parser.getName().equals(TAG_FONT)) {
diff --git a/core/java/android/hardware/display/BrightnessInfo.java b/core/java/android/hardware/display/BrightnessInfo.java
index 4289860d4e8a..c5d37c2d0b90 100644
--- a/core/java/android/hardware/display/BrightnessInfo.java
+++ b/core/java/android/hardware/display/BrightnessInfo.java
@@ -33,7 +33,8 @@ public final class BrightnessInfo implements Parcelable {
@IntDef(prefix = {"HIGH_BRIGHTNESS_MODE_"}, value = {
HIGH_BRIGHTNESS_MODE_OFF,
- HIGH_BRIGHTNESS_MODE_SUNLIGHT
+ HIGH_BRIGHTNESS_MODE_SUNLIGHT,
+ HIGH_BRIGHTNESS_MODE_HDR
})
@Retention(RetentionPolicy.SOURCE)
public @interface HighBrightnessMode {}
@@ -50,6 +51,12 @@ public final class BrightnessInfo implements Parcelable {
*/
public static final int HIGH_BRIGHTNESS_MODE_SUNLIGHT = 1;
+ /**
+ * High brightness mode is ON due to high ambient light (sunlight). The high brightness range is
+ * currently accessible to the user.
+ */
+ public static final int HIGH_BRIGHTNESS_MODE_HDR = 2;
+
/** Brightness */
public final float brightness;
@@ -73,6 +80,21 @@ public final class BrightnessInfo implements Parcelable {
this.highBrightnessMode = highBrightnessMode;
}
+ /**
+ * @return User-friendly string for specified {@link HighBrightnessMode} parameter.
+ */
+ public static String hbmToString(@HighBrightnessMode int highBrightnessMode) {
+ switch (highBrightnessMode) {
+ case HIGH_BRIGHTNESS_MODE_OFF:
+ return "off";
+ case HIGH_BRIGHTNESS_MODE_HDR:
+ return "hdr";
+ case HIGH_BRIGHTNESS_MODE_SUNLIGHT:
+ return "sunlight";
+ }
+ return "invalid";
+ }
+
@Override
public int describeContents() {
return 0;
diff --git a/core/java/android/view/OnReceiveContentListener.java b/core/java/android/view/OnReceiveContentListener.java
index 3d9968c7f2c6..1e930e61c3bb 100644
--- a/core/java/android/view/OnReceiveContentListener.java
+++ b/core/java/android/view/OnReceiveContentListener.java
@@ -74,13 +74,17 @@ public interface OnReceiveContentListener {
* preserve the common behavior for inserting text. See the class javadoc for a sample
* implementation.
*
- * <p>If implementing handling for text: if the view has a selection, the selection should
- * be overwritten by the passed-in content; if there's no selection, the passed-in content
- * should be inserted at the current cursor position.
- *
- * <p>If implementing handling for non-text content (e.g. images): the content may be
- * inserted inline, or it may be added as an attachment (could potentially be shown in a
- * completely separate view).
+ * <p>Handling different content
+ * <ul>
+ * <li>Text. If the {@link ContentInfo#getSource() source} is
+ * {@link ContentInfo#SOURCE_AUTOFILL autofill}, the view's content should be fully
+ * replaced by the passed-in text. For sources other than autofill, the passed-in text
+ * should overwrite the current selection or be inserted at the current cursor position
+ * if there is no selection.
+ * <li>Non-text content (e.g. images). The content may be inserted inline if the widget
+ * supports this, or it may be added as an attachment (could potentially be shown in a
+ * completely separate view).
+ * </ul>
*
* @param view The view where the content insertion was requested.
* @param payload The content to insert and related metadata.
diff --git a/core/java/com/android/internal/accessibility/dialog/AccessibilityTarget.java b/core/java/com/android/internal/accessibility/dialog/AccessibilityTarget.java
index d84175ed1e7c..5b0abd389e7d 100644
--- a/core/java/com/android/internal/accessibility/dialog/AccessibilityTarget.java
+++ b/core/java/com/android/internal/accessibility/dialog/AccessibilityTarget.java
@@ -24,6 +24,7 @@ import static com.android.internal.accessibility.util.ShortcutUtils.optInValueTo
import static com.android.internal.accessibility.util.ShortcutUtils.optOutValueFromSettings;
import android.annotation.NonNull;
+import android.annotation.Nullable;
import android.content.Context;
import android.graphics.drawable.Drawable;
import android.view.View;
@@ -33,6 +34,7 @@ import android.view.accessibility.AccessibilityManager.ShortcutType;
import com.android.internal.accessibility.common.ShortcutConstants;
import com.android.internal.accessibility.common.ShortcutConstants.AccessibilityFragmentType;
import com.android.internal.accessibility.dialog.TargetAdapter.ViewHolder;
+import com.android.internal.annotations.VisibleForTesting;
/**
* Abstract base class for creating various target related to accessibility service,
@@ -51,7 +53,8 @@ public abstract class AccessibilityTarget implements TargetOperations, OnTargetS
private Drawable mIcon;
private String mKey;
- AccessibilityTarget(Context context, @ShortcutType int shortcutType,
+ @VisibleForTesting
+ public AccessibilityTarget(Context context, @ShortcutType int shortcutType,
@AccessibilityFragmentType int fragmentType, boolean isShortcutSwitched, String id,
CharSequence label, Drawable icon, String key) {
mContext = context;
@@ -103,6 +106,16 @@ public abstract class AccessibilityTarget implements TargetOperations, OnTargetS
}
}
+ /**
+ * Gets the state description of this feature target.
+ *
+ * @return the state description
+ */
+ @Nullable
+ public CharSequence getStateDescription() {
+ return null;
+ }
+
public void setShortcutEnabled(boolean enabled) {
mShortcutEnabled = enabled;
}
diff --git a/core/java/com/android/internal/accessibility/dialog/ToggleAccessibilityServiceTarget.java b/core/java/com/android/internal/accessibility/dialog/ToggleAccessibilityServiceTarget.java
index 239e531dbfb8..469d10ff98aa 100644
--- a/core/java/com/android/internal/accessibility/dialog/ToggleAccessibilityServiceTarget.java
+++ b/core/java/com/android/internal/accessibility/dialog/ToggleAccessibilityServiceTarget.java
@@ -51,10 +51,14 @@ class ToggleAccessibilityServiceTarget extends AccessibilityServiceTarget {
final boolean isEditMenuMode =
shortcutMenuMode == ShortcutMenuMode.EDIT;
holder.mStatusView.setVisibility(isEditMenuMode ? View.GONE : View.VISIBLE);
+ holder.mStatusView.setText(getStateDescription());
+ }
+ @Override
+ public CharSequence getStateDescription() {
final int statusResId = isAccessibilityServiceEnabled(getContext(), getId())
? R.string.accessibility_shortcut_menu_item_status_on
: R.string.accessibility_shortcut_menu_item_status_off;
- holder.mStatusView.setText(getContext().getString(statusResId));
+ return getContext().getString(statusResId);
}
}
diff --git a/core/java/com/android/internal/accessibility/dialog/ToggleAllowListingFeatureTarget.java b/core/java/com/android/internal/accessibility/dialog/ToggleAllowListingFeatureTarget.java
index 38aac708de15..ebdaed6dbe39 100644
--- a/core/java/com/android/internal/accessibility/dialog/ToggleAllowListingFeatureTarget.java
+++ b/core/java/com/android/internal/accessibility/dialog/ToggleAllowListingFeatureTarget.java
@@ -48,11 +48,15 @@ class ToggleAllowListingFeatureTarget extends AccessibilityTarget {
final boolean isEditMenuMode =
shortcutMenuMode == ShortcutMenuMode.EDIT;
holder.mStatusView.setVisibility(isEditMenuMode ? View.GONE : View.VISIBLE);
+ holder.mStatusView.setText(getStateDescription());
+ }
+ @Override
+ public CharSequence getStateDescription() {
final int statusResId = isFeatureEnabled()
? R.string.accessibility_shortcut_menu_item_status_on
: R.string.accessibility_shortcut_menu_item_status_off;
- holder.mStatusView.setText(getContext().getString(statusResId));
+ return getContext().getString(statusResId);
}
private boolean isFeatureEnabled() {
diff --git a/core/java/com/android/server/backup/PreferredActivityBackupHelper.java b/core/java/com/android/server/backup/PreferredActivityBackupHelper.java
index 80636706a9a6..503c71990adb 100644
--- a/core/java/com/android/server/backup/PreferredActivityBackupHelper.java
+++ b/core/java/com/android/server/backup/PreferredActivityBackupHelper.java
@@ -16,10 +16,12 @@
package com.android.server.backup;
+import android.annotation.StringDef;
+import android.annotation.UserIdInt;
import android.app.AppGlobals;
import android.app.backup.BlobBackupHelper;
import android.content.pm.IPackageManager;
-import android.os.UserHandle;
+import android.content.pm.verify.domain.DomainVerificationManager;
import android.util.Slog;
public class PreferredActivityBackupHelper extends BlobBackupHelper {
@@ -27,7 +29,7 @@ public class PreferredActivityBackupHelper extends BlobBackupHelper {
private static final boolean DEBUG = false;
// current schema of the backup state blob
- private static final int STATE_VERSION = 3;
+ private static final int STATE_VERSION = 4;
// key under which the preferred-activity state blob is committed to backup
private static final String KEY_PREFERRED = "preferred-activity";
@@ -35,14 +37,41 @@ public class PreferredActivityBackupHelper extends BlobBackupHelper {
// key for default-browser [etc] state
private static final String KEY_DEFAULT_APPS = "default-apps";
- // intent-filter verification state
+ /**
+ * Intent-filter verification state
+ * @deprecated Replaced by {@link #KEY_DOMAIN_VERIFICATION}, retained to ensure the key is
+ * never reused.
+ */
+ @Deprecated
private static final String KEY_INTENT_VERIFICATION = "intent-verification";
- public PreferredActivityBackupHelper() {
- super(STATE_VERSION,
- KEY_PREFERRED,
- KEY_DEFAULT_APPS,
- KEY_INTENT_VERIFICATION);
+ /**
+ * State for {@link DomainVerificationManager}.
+ */
+ private static final String KEY_DOMAIN_VERIFICATION = "domain-verification";
+
+ private static final String[] KEYS = new String[] {
+ KEY_PREFERRED,
+ KEY_DEFAULT_APPS,
+ KEY_INTENT_VERIFICATION,
+ KEY_DOMAIN_VERIFICATION
+ };
+
+ @StringDef(value = {
+ KEY_PREFERRED,
+ KEY_DEFAULT_APPS,
+ KEY_INTENT_VERIFICATION,
+ KEY_DOMAIN_VERIFICATION
+ })
+ private @interface Key {
+ }
+
+ @UserIdInt
+ private final int mUserId;
+
+ public PreferredActivityBackupHelper(@UserIdInt int userId) {
+ super(STATE_VERSION, KEYS);
+ mUserId = userId;
}
@Override
@@ -52,14 +81,16 @@ public class PreferredActivityBackupHelper extends BlobBackupHelper {
Slog.d(TAG, "Handling backup of " + key);
}
try {
- // TODO: http://b/22388012
switch (key) {
case KEY_PREFERRED:
- return pm.getPreferredActivityBackup(UserHandle.USER_SYSTEM);
+ return pm.getPreferredActivityBackup(mUserId);
case KEY_DEFAULT_APPS:
- return pm.getDefaultAppsBackup(UserHandle.USER_SYSTEM);
+ return pm.getDefaultAppsBackup(mUserId);
case KEY_INTENT_VERIFICATION:
- return pm.getIntentFilterVerificationBackup(UserHandle.USER_SYSTEM);
+ // Deprecated
+ return null;
+ case KEY_DOMAIN_VERIFICATION:
+ return pm.getDomainVerificationBackup(mUserId);
default:
Slog.w(TAG, "Unexpected backup key " + key);
}
@@ -70,22 +101,24 @@ public class PreferredActivityBackupHelper extends BlobBackupHelper {
}
@Override
- protected void applyRestoredPayload(String key, byte[] payload) {
+ protected void applyRestoredPayload(@Key String key, byte[] payload) {
IPackageManager pm = AppGlobals.getPackageManager();
if (DEBUG) {
Slog.d(TAG, "Handling restore of " + key);
}
try {
- // TODO: http://b/22388012
switch (key) {
case KEY_PREFERRED:
- pm.restorePreferredActivities(payload, UserHandle.USER_SYSTEM);
+ pm.restorePreferredActivities(payload, mUserId);
break;
case KEY_DEFAULT_APPS:
- pm.restoreDefaultApps(payload, UserHandle.USER_SYSTEM);
+ pm.restoreDefaultApps(payload, mUserId);
break;
case KEY_INTENT_VERIFICATION:
- pm.restoreIntentFilterVerification(payload, UserHandle.USER_SYSTEM);
+ // Deprecated
+ break;
+ case KEY_DOMAIN_VERIFICATION:
+ pm.restoreDomainVerification(payload, mUserId);
break;
default:
Slog.w(TAG, "Unexpected restore key " + key);
diff --git a/core/jni/com_android_internal_os_Zygote.cpp b/core/jni/com_android_internal_os_Zygote.cpp
index 8b7096646cff..fee82bac489c 100644
--- a/core/jni/com_android_internal_os_Zygote.cpp
+++ b/core/jni/com_android_internal_os_Zygote.cpp
@@ -825,7 +825,7 @@ static void MountEmulatedStorage(uid_t uid, jint mount_mode,
PrepareDir(user_source, 0710, user_id ? AID_ROOT : AID_SHELL,
multiuser_get_uid(user_id, AID_EVERYBODY), fail_fn);
- bool isAppDataIsolationEnabled = GetBoolProperty(kVoldAppDataIsolation, false);
+ bool isAppDataIsolationEnabled = GetBoolProperty(kVoldAppDataIsolation, true);
if (mount_mode == MOUNT_EXTERNAL_PASS_THROUGH) {
const std::string pass_through_source = StringPrintf("/mnt/pass_through/%d", user_id);
diff --git a/core/res/res/values/attrs.xml b/core/res/res/values/attrs.xml
index d4fa28531acd..480b47835100 100644
--- a/core/res/res/values/attrs.xml
+++ b/core/res/res/values/attrs.xml
@@ -3602,6 +3602,10 @@
<attr name="__removed2" format="boolean" />
<!-- Specifies whether the IME supports showing inline suggestions. -->
<attr name="supportsInlineSuggestions" format="boolean" />
+ <!-- Specifies whether the IME suppresses system spell checker.
+ The default value is false. If an IME sets this attribute to true,
+ the system spell checker will be disabled while the IME has an
+ active input session. -->
<attr name="suppressesSpellChecker" format="boolean" />
<!-- Specifies whether the IME wants to be shown in the Input Method picker. Defaults to
true. Set this to false if the IME is intended to be accessed programmatically.
diff --git a/libs/WindowManager/Shell/res/layout/bubble_overflow_container.xml b/libs/WindowManager/Shell/res/layout/bubble_overflow_container.xml
index 8224d95fd9ad..270186a199bb 100644
--- a/libs/WindowManager/Shell/res/layout/bubble_overflow_container.xml
+++ b/libs/WindowManager/Shell/res/layout/bubble_overflow_container.xml
@@ -27,24 +27,26 @@
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/bubble_overflow_recycler"
- android:layout_gravity="center_horizontal"
android:nestedScrollingEnabled="false"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"/>
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:layout_gravity="center_horizontal"
+ android:gravity="center"/>
<LinearLayout
android:id="@+id/bubble_overflow_empty_state"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
+ android:visibility="gone"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
android:paddingLeft="@dimen/bubble_overflow_empty_state_padding"
android:paddingRight="@dimen/bubble_overflow_empty_state_padding"
android:orientation="vertical"
android:gravity="center">
<ImageView
+ android:id="@+id/bubble_overflow_empty_state_image"
android:layout_width="@dimen/bubble_empty_overflow_image_height"
android:layout_height="@dimen/bubble_empty_overflow_image_height"
- android:id="@+id/bubble_overflow_empty_state_image"
android:scaleType="fitCenter"
android:layout_gravity="center"/>
@@ -60,12 +62,12 @@
<TextView
android:id="@+id/bubble_overflow_empty_subtitle"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
android:fontFamily="@*android:string/config_bodyFontFamily"
android:textAppearance="@*android:style/TextAppearance.DeviceDefault.Body2"
android:textColor="?android:attr/textColorSecondary"
android:text="@string/bubble_overflow_empty_subtitle"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
android:paddingBottom="@dimen/bubble_empty_overflow_subtitle_padding"
android:gravity="center"/>
</LinearLayout>
diff --git a/libs/WindowManager/Shell/res/layout/bubble_overflow_view.xml b/libs/WindowManager/Shell/res/layout/bubble_overflow_view.xml
index c1f67bd27d93..d07107c8f0a0 100644
--- a/libs/WindowManager/Shell/res/layout/bubble_overflow_view.xml
+++ b/libs/WindowManager/Shell/res/layout/bubble_overflow_view.xml
@@ -32,12 +32,14 @@
android:id="@+id/bubble_view_name"
android:textAppearance="@*android:style/TextAppearance.DeviceDefault.ListItem"
android:textSize="13sp"
- android:layout_width="fill_parent"
+ android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:maxLines="1"
android:lines="2"
android:ellipsize="end"
android:layout_gravity="center"
android:paddingTop="@dimen/bubble_overflow_text_padding"
+ android:paddingEnd="@dimen/bubble_overflow_text_padding"
+ android:paddingStart="@dimen/bubble_overflow_text_padding"
android:gravity="center"/>
</LinearLayout>
diff --git a/libs/WindowManager/Shell/res/values/dimen.xml b/libs/WindowManager/Shell/res/values/dimen.xml
index ef731235a3c4..d94030dba652 100644
--- a/libs/WindowManager/Shell/res/values/dimen.xml
+++ b/libs/WindowManager/Shell/res/values/dimen.xml
@@ -125,7 +125,9 @@
<dimen name="bubble_expanded_view_slop">8dp</dimen>
<!-- Default (and minimum) height of the expanded view shown when the bubble is expanded -->
<dimen name="bubble_expanded_default_height">180dp</dimen>
- <!-- Default height of bubble overflow -->
+ <!-- On large screens the width of the expanded view is restricted to this size. -->
+ <dimen name="bubble_expanded_view_tablet_width">412dp</dimen>
+ <!-- Default (and minimum) height of bubble overflow -->
<dimen name="bubble_overflow_height">480dp</dimen>
<!-- Bubble overflow padding when there are no bubbles -->
<dimen name="bubble_overflow_empty_state_padding">16dp</dimen>
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 abe1f7179dda..696f705782c0 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
@@ -19,6 +19,7 @@ package com.android.wm.shell.bubbles;
import static android.app.ActivityTaskManager.INVALID_TASK_ID;
import static android.content.Intent.FLAG_ACTIVITY_MULTIPLE_TASK;
import static android.content.Intent.FLAG_ACTIVITY_NEW_DOCUMENT;
+import static android.view.ViewGroup.LayoutParams.MATCH_PARENT;
import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT;
import static com.android.wm.shell.bubbles.BubbleDebugConfig.DEBUG_BUBBLE_EXPANDED_VIEW;
@@ -335,7 +336,10 @@ public class BubbleExpandedView extends LinearLayout {
mOverflowView = (BubbleOverflowContainerView) LayoutInflater.from(getContext()).inflate(
R.layout.bubble_overflow_container, null /* root */);
mOverflowView.setBubbleController(mController);
- mExpandedViewContainer.addView(mOverflowView);
+ FrameLayout.LayoutParams lp = new FrameLayout.LayoutParams(MATCH_PARENT, MATCH_PARENT);
+ mExpandedViewContainer.addView(mOverflowView, lp);
+ mExpandedViewContainer.setLayoutParams(
+ new LinearLayout.LayoutParams(MATCH_PARENT, WRAP_CONTENT));
bringChildToFront(mOverflowView);
mSettingsIcon.setVisibility(GONE);
} else {
@@ -600,9 +604,9 @@ public class BubbleExpandedView extends LinearLayout {
return;
}
- if (mBubble != null || mIsOverflow) {
+ if ((mBubble != null && mTaskView != null) || mIsOverflow) {
float desiredHeight = mIsOverflow
- ? mOverflowHeight
+ ? mPositioner.isLargeScreen() ? getMaxExpandedHeight() : mOverflowHeight
: mBubble.getDesiredHeight(mContext);
desiredHeight = Math.max(desiredHeight, mMinHeight);
float height = Math.min(desiredHeight, getMaxExpandedHeight());
@@ -657,10 +661,10 @@ public class BubbleExpandedView extends LinearLayout {
+ getBubbleKey());
}
mExpandedViewContainerLocation = containerLocationOnScreen;
+ updateHeight();
if (mTaskView != null
&& mTaskView.getVisibility() == VISIBLE
&& mTaskView.isAttachedToWindow()) {
- updateHeight();
mTaskView.onLocationChanged();
}
if (mIsOverflow) {
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 f7c7285a7b6e..af5b3a61f393 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
@@ -26,7 +26,6 @@ import android.content.res.Resources;
import android.content.res.TypedArray;
import android.graphics.Color;
import android.util.AttributeSet;
-import android.util.DisplayMetrics;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
@@ -51,8 +50,6 @@ import java.util.function.Consumer;
* Container view for showing aged out bubbles.
*/
public class BubbleOverflowContainerView extends LinearLayout {
- static final String EXTRA_BUBBLE_CONTROLLER = "bubble_controller";
-
private static final String TAG = TAG_WITH_CLASS_NAME ? "BubbleOverflowActivity" : TAG_BUBBLES;
private LinearLayout mEmptyState;
@@ -64,18 +61,16 @@ public class BubbleOverflowContainerView extends LinearLayout {
private RecyclerView mRecyclerView;
private List<Bubble> mOverflowBubbles = new ArrayList<>();
- private class NoScrollGridLayoutManager extends GridLayoutManager {
- NoScrollGridLayoutManager(Context context, int columns) {
+ private class OverflowGridLayoutManager extends GridLayoutManager {
+ OverflowGridLayoutManager(Context context, int columns) {
super(context, columns);
}
- @Override
- public boolean canScrollVertically() {
- if (getResources().getConfiguration().orientation
- == Configuration.ORIENTATION_LANDSCAPE) {
- return super.canScrollVertically();
- }
- return false;
- }
+
+// @Override
+// public boolean canScrollVertically() {
+// // TODO (b/162006693): this should be based on items in the list & available height
+// return true;
+// }
@Override
public int getColumnCountForAccessibility(RecyclerView.Recycler recycler,
@@ -137,47 +132,24 @@ public class BubbleOverflowContainerView extends LinearLayout {
Resources res = getResources();
final int columns = res.getInteger(R.integer.bubbles_overflow_columns);
mRecyclerView.setLayoutManager(
- new NoScrollGridLayoutManager(getContext(), columns));
-
- DisplayMetrics displayMetrics = new DisplayMetrics();
- getContext().getDisplay().getMetrics(displayMetrics);
-
- final int overflowPadding = res.getDimensionPixelSize(R.dimen.bubble_overflow_padding);
- final int recyclerViewWidth = displayMetrics.widthPixels - (overflowPadding * 2);
- final int viewWidth = recyclerViewWidth / columns;
-
- final int maxOverflowBubbles = res.getInteger(R.integer.bubbles_max_overflow);
- final int rows = (int) Math.ceil((double) maxOverflowBubbles / columns);
- final int recyclerViewHeight = res.getDimensionPixelSize(R.dimen.bubble_overflow_height)
- - res.getDimensionPixelSize(R.dimen.bubble_overflow_padding);
- final int viewHeight = recyclerViewHeight / rows;
-
+ new OverflowGridLayoutManager(getContext(), columns));
mAdapter = new BubbleOverflowAdapter(getContext(), mOverflowBubbles,
mController::promoteBubbleFromOverflow,
- mController.getPositioner(),
- viewWidth, viewHeight);
+ mController.getPositioner());
mRecyclerView.setAdapter(mAdapter);
mOverflowBubbles.clear();
mOverflowBubbles.addAll(mController.getOverflowBubbles());
mAdapter.notifyDataSetChanged();
- // Currently BubbleExpandedView.mExpandedViewContainer is WRAP_CONTENT so use the same
- // width we would use for the recycler view
- LayoutParams lp = (LayoutParams) mEmptyState.getLayoutParams();
- lp.width = recyclerViewWidth;
- updateEmptyStateVisibility();
-
mController.setOverflowListener(mDataListener);
+ updateEmptyStateVisibility();
updateTheme();
}
void updateEmptyStateVisibility() {
- if (mOverflowBubbles.isEmpty()) {
- mEmptyState.setVisibility(View.VISIBLE);
- } else {
- mEmptyState.setVisibility(View.GONE);
- }
+ mEmptyState.setVisibility(mOverflowBubbles.isEmpty() ? View.VISIBLE : View.GONE);
+ mRecyclerView.setVisibility(mOverflowBubbles.isEmpty() ? View.GONE : View.VISIBLE);
}
/**
@@ -258,20 +230,15 @@ class BubbleOverflowAdapter extends RecyclerView.Adapter<BubbleOverflowAdapter.V
private Consumer<Bubble> mPromoteBubbleFromOverflow;
private BubblePositioner mPositioner;
private List<Bubble> mBubbles;
- private int mWidth;
- private int mHeight;
BubbleOverflowAdapter(Context context,
List<Bubble> list,
Consumer<Bubble> promoteBubble,
- BubblePositioner positioner,
- int width, int height) {
+ BubblePositioner positioner) {
mContext = context;
mBubbles = list;
mPromoteBubbleFromOverflow = promoteBubble;
mPositioner = positioner;
- mWidth = width;
- mHeight = height;
}
@Override
@@ -284,8 +251,6 @@ class BubbleOverflowAdapter extends RecyclerView.Adapter<BubbleOverflowAdapter.V
LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(
LinearLayout.LayoutParams.WRAP_CONTENT,
LinearLayout.LayoutParams.WRAP_CONTENT);
- params.width = mWidth;
- params.height = mHeight;
overflowView.setLayoutParams(params);
// Ensure name has enough contrast.
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubblePositioner.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubblePositioner.java
index a81c2d8bef0a..ae1a053ae19e 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubblePositioner.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubblePositioner.java
@@ -70,10 +70,16 @@ public class BubblePositioner {
private int mBubbleSize;
private int mBubbleBitmapSize;
+ private int mExpandedViewLargeScreenWidth;
+ private int mExpandedViewPadding;
+ private int mPointerHeight;
+ private int mBubblePaddingTop;
private PointF mPinLocation;
private PointF mRestingStackPosition;
+ private int[] mLeftRightPadding = new int[2];
+ private boolean mIsLargeScreen;
private boolean mShowingInTaskbar;
private @TaskbarPosition int mTaskbarPosition = TASKBAR_POSITION_NONE;
private int mTaskbarIconSize;
@@ -99,15 +105,17 @@ public class BubblePositioner {
return;
}
WindowInsets metricInsets = windowMetrics.getWindowInsets();
-
Insets insets = metricInsets.getInsetsIgnoringVisibility(WindowInsets.Type.navigationBars()
| WindowInsets.Type.statusBars()
| WindowInsets.Type.displayCutout());
+ mIsLargeScreen = mContext.getResources().getConfiguration().smallestScreenWidthDp >= 600;
+
if (BubbleDebugConfig.DEBUG_POSITIONER) {
Log.w(TAG, "update positioner:"
- + " rotation= " + mRotation
+ + " rotation: " + mRotation
+ " insets: " + insets
+ + " isLargeScreen: " + mIsLargeScreen
+ " bounds: " + windowMetrics.getBounds()
+ " showingInTaskbar: " + mShowingInTaskbar);
}
@@ -143,6 +151,11 @@ public class BubblePositioner {
Resources res = mContext.getResources();
mBubbleSize = res.getDimensionPixelSize(R.dimen.individual_bubble_size);
mBubbleBitmapSize = res.getDimensionPixelSize(R.dimen.bubble_bitmap_size);
+ mExpandedViewLargeScreenWidth = res.getDimensionPixelSize(
+ R.dimen.bubble_expanded_view_tablet_width);
+ mExpandedViewPadding = res.getDimensionPixelSize(R.dimen.bubble_expanded_view_padding);
+ mPointerHeight = res.getDimensionPixelSize(R.dimen.bubble_pointer_height);
+ mBubblePaddingTop = res.getDimensionPixelSize(R.dimen.bubble_padding_top);
if (mShowingInTaskbar) {
adjustForTaskbar();
}
@@ -189,13 +202,16 @@ public class BubblePositioner {
return mInsets;
}
- /**
- * @return whether the device is in landscape orientation.
- */
+ /** @return whether the device is in landscape orientation. */
public boolean isLandscape() {
return mRotation == Surface.ROTATION_90 || mRotation == Surface.ROTATION_270;
}
+ /** @return whether the screen is considered large. */
+ public boolean isLargeScreen() {
+ return mIsLargeScreen;
+ }
+
/**
* Indicates how bubbles appear when expanded.
*
@@ -204,7 +220,7 @@ public class BubblePositioner {
* to the left or right side.
*/
public boolean showBubblesVertically() {
- return isLandscape() || mShowingInTaskbar;
+ return isLandscape() || mShowingInTaskbar || mIsLargeScreen;
}
/** Size of the bubble account for badge & dot. */
@@ -224,6 +240,45 @@ public class BubblePositioner {
}
/**
+ * Calculates the left & right padding for the bubble expanded view.
+ *
+ * On larger screens the width of the expanded view is restricted via this padding.
+ * On landscape the bubble overflow expanded view is also restricted via this padding.
+ */
+ public int[] getExpandedViewPadding(boolean onLeft, boolean isOverflow) {
+ int leftPadding = mInsets.left + mExpandedViewPadding;
+ int rightPadding = mInsets.right + mExpandedViewPadding;
+ final boolean isLargeOrOverflow = mIsLargeScreen || isOverflow;
+ if (showBubblesVertically()) {
+ if (!onLeft) {
+ rightPadding += mPointerHeight + mBubbleSize;
+ leftPadding += isLargeOrOverflow
+ ? (mPositionRect.width() - rightPadding - mExpandedViewLargeScreenWidth)
+ : 0;
+ } else {
+ //TODO: pointer height should be padding between pointer & bubbles here & above
+ leftPadding += mPointerHeight + mBubbleSize;
+ rightPadding += isLargeOrOverflow
+ ? (mPositionRect.width() - leftPadding - mExpandedViewLargeScreenWidth)
+ : 0;
+ }
+ }
+ mLeftRightPadding[0] = leftPadding;
+ mLeftRightPadding[1] = rightPadding;
+ return mLeftRightPadding;
+ }
+
+ /** Calculates the y position of the expanded view when it is expanded. */
+ public float getExpandedViewY() {
+ final int top = getAvailableRect().top;
+ if (showBubblesVertically()) {
+ return top + mExpandedViewPadding;
+ } else {
+ return top + mBubbleSize + mBubblePaddingTop;
+ }
+ }
+
+ /**
* Sets the stack's most recent position along the edge of the screen. This is saved when the
* last bubble is removed, so that the stack can be restored in its previous position.
*/
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 0f5d0eff7efa..c4d33877f17d 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
@@ -35,7 +35,6 @@ import android.content.res.TypedArray;
import android.graphics.Color;
import android.graphics.ColorMatrix;
import android.graphics.ColorMatrixColorFilter;
-import android.graphics.Insets;
import android.graphics.Outline;
import android.graphics.Paint;
import android.graphics.PointF;
@@ -246,7 +245,6 @@ public class BubbleStackView extends FrameLayout
private int mMaxBubbles;
private int mBubbleSize;
private int mBubbleElevation;
- private int mBubblePaddingTop;
private int mBubbleTouchPadding;
private int mExpandedViewPadding;
private int mPointerHeight;
@@ -768,7 +766,6 @@ public class BubbleStackView extends FrameLayout
mMaxBubbles = res.getInteger(R.integer.bubbles_max_rendered);
mBubbleSize = res.getDimensionPixelSize(R.dimen.individual_bubble_size);
mBubbleElevation = res.getDimensionPixelSize(R.dimen.bubble_elevation);
- mBubblePaddingTop = res.getDimensionPixelSize(R.dimen.bubble_padding_top);
mBubbleTouchPadding = res.getDimensionPixelSize(R.dimen.bubble_touch_padding);
mPointerHeight = res.getDimensionPixelSize(R.dimen.bubble_pointer_height);
mImeOffset = res.getDimensionPixelSize(R.dimen.pip_ime_offset);
@@ -905,7 +902,7 @@ public class BubbleStackView extends FrameLayout
afterExpandedViewAnimation();
} /* after */);
mExpandedViewContainer.setTranslationX(0f);
- mExpandedViewContainer.setTranslationY(getExpandedViewY());
+ mExpandedViewContainer.setTranslationY(mPositioner.getExpandedViewY());
mExpandedViewContainer.setAlpha(1f);
}
removeOnLayoutChangeListener(mOrientationChangedListener);
@@ -1247,9 +1244,6 @@ public class BubbleStackView extends FrameLayout
/** Respond to the display size change by recalculating view size and location. */
public void onDisplaySizeChanged() {
updateOverflow();
-
- Resources res = getContext().getResources();
- mBubblePaddingTop = res.getDimensionPixelSize(R.dimen.bubble_padding_top);
mBubbleSize = mPositioner.getBubbleSize();
for (Bubble b : mBubbleData.getBubbles()) {
if (b.getIconView() == null) {
@@ -1267,6 +1261,9 @@ public class BubbleStackView extends FrameLayout
new RelativeStackPosition(
mPositioner.getRestingPosition(),
mStackAnimationController.getAllowableStackPositionRegion()));
+ if (mIsExpanded) {
+ updateExpandedView();
+ }
}
@Override
@@ -1816,7 +1813,7 @@ public class BubbleStackView extends FrameLayout
}
mExpandedViewContainer.setTranslationX(0f);
- mExpandedViewContainer.setTranslationY(getExpandedViewY());
+ mExpandedViewContainer.setTranslationY(mPositioner.getExpandedViewY());
mExpandedViewContainer.setAlpha(1f);
int index;
@@ -1865,7 +1862,7 @@ public class BubbleStackView extends FrameLayout
1f - EXPANDED_VIEW_ANIMATE_SCALE_AMOUNT,
1f - EXPANDED_VIEW_ANIMATE_SCALE_AMOUNT,
bubbleWillBeAt + mBubbleSize / 2f,
- getExpandedViewY());
+ mPositioner.getExpandedViewY());
}
mExpandedViewContainer.setAnimationMatrix(mExpandedViewContainerMatrix);
@@ -1969,7 +1966,7 @@ public class BubbleStackView extends FrameLayout
mExpandedViewContainerMatrix.setScale(
1f, 1f,
expandingFromBubbleAt + mBubbleSize / 2f,
- getExpandedViewY());
+ mPositioner.getExpandedViewY());
}
mExpandedViewAlphaAnimator.reverse();
@@ -2075,7 +2072,7 @@ public class BubbleStackView extends FrameLayout
1f - EXPANDED_VIEW_ANIMATE_SCALE_AMOUNT,
1f - EXPANDED_VIEW_ANIMATE_SCALE_AMOUNT,
expandingFromBubbleDestination + mBubbleSize / 2f,
- getExpandedViewY());
+ mPositioner.getExpandedViewY());
}
mExpandedViewContainer.setAnimationMatrix(mExpandedViewContainerMatrix);
@@ -2313,18 +2310,6 @@ public class BubbleStackView extends FrameLayout
: 0f);
}
- /**
- * Calculates the y position of the expanded view when it is expanded.
- */
- float getExpandedViewY() {
- final int top = mPositioner.getAvailableRect().top;
- if (mPositioner.showBubblesVertically()) {
- return top + mExpandedViewPadding;
- } else {
- return top + mBubbleSize + mBubblePaddingTop;
- }
- }
-
private boolean shouldShowFlyout(Bubble bubble) {
Bubble.FlyoutMessage flyoutMessage = bubble.getFlyoutMessage();
final BadgedImageView bubbleView = bubble.getIconView();
@@ -2697,24 +2682,16 @@ public class BubbleStackView extends FrameLayout
if (DEBUG_BUBBLE_STACK_VIEW) {
Log.d(TAG, "updateExpandedView: mIsExpanded=" + mIsExpanded);
}
-
- // Need to update the padding around the view for any insets
- Insets insets = mPositioner.getInsets();
- int leftPadding = insets.left + mExpandedViewPadding;
- int rightPadding = insets.right + mExpandedViewPadding;
- if (mPositioner.showBubblesVertically()) {
- if (!mStackAnimationController.isStackOnLeftSide()) {
- rightPadding += mPointerHeight + mBubbleSize;
- } else {
- leftPadding += mPointerHeight + mBubbleSize;
- }
- }
- mExpandedViewContainer.setPadding(leftPadding, 0, rightPadding, 0);
+ boolean isOverflowExpanded = mExpandedBubble != null
+ && mBubbleOverflow.KEY.equals(mExpandedBubble.getKey());
+ int[] paddings = mPositioner.getExpandedViewPadding(
+ mStackAnimationController.isStackOnLeftSide(), isOverflowExpanded);
+ mExpandedViewContainer.setPadding(paddings[0], 0, paddings[1], 0);
if (mIsExpansionAnimating) {
mExpandedViewContainer.setVisibility(mIsExpanded ? VISIBLE : GONE);
}
if (mExpandedBubble != null && mExpandedBubble.getExpandedView() != null) {
- mExpandedViewContainer.setTranslationY(getExpandedViewY());
+ mExpandedViewContainer.setTranslationY(mPositioner.getExpandedViewY());
mExpandedViewContainer.setTranslationX(0f);
mExpandedBubble.getExpandedView().updateView(
mExpandedViewContainer.getLocationOnScreen());
@@ -2755,13 +2732,14 @@ public class BubbleStackView extends FrameLayout
return;
}
float bubblePosition = mExpandedAnimationController.getBubbleXOrYForOrientation(index);
+ float expandedViewY = mPositioner.getExpandedViewY();
if (mPositioner.showBubblesVertically()) {
float x = mStackOnLeftOrWillBe
? mPositioner.getAvailableRect().left
: mPositioner.getAvailableRect().right
- mExpandedViewContainer.getPaddingRight()
- mPointerHeight;
- float bubbleCenter = bubblePosition - getExpandedViewY() + (mBubbleSize / 2f);
+ float bubbleCenter = bubblePosition - expandedViewY + (mBubbleSize / 2f);
mExpandedBubble.getExpandedView().setPointerPosition(
x,
bubbleCenter,
@@ -2771,7 +2749,7 @@ public class BubbleStackView extends FrameLayout
float bubbleCenter = bubblePosition + (mBubbleSize / 2f);
mExpandedBubble.getExpandedView().setPointerPosition(
bubbleCenter,
- getExpandedViewY(),
+ expandedViewY,
false,
mStackOnLeftOrWillBe);
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/animation/ExpandedAnimationController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/animation/ExpandedAnimationController.java
index 18aaa9677be6..48bd8943b25a 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/animation/ExpandedAnimationController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/animation/ExpandedAnimationController.java
@@ -16,7 +16,6 @@
package com.android.wm.shell.bubbles.animation;
-import android.content.res.Configuration;
import android.content.res.Resources;
import android.graphics.Path;
import android.graphics.PointF;
@@ -142,6 +141,7 @@ public class ExpandedAnimationController
updateResources();
mExpandedViewPadding = expandedViewPadding;
mOnBubbleAnimatedOutAction = onBubbleAnimatedOutAction;
+ mCollapsePoint = mPositioner.getDefaultStartPosition();
}
/**
@@ -528,17 +528,34 @@ public class ExpandedAnimationController
startOrUpdatePathAnimation(true /* expanding */);
} else if (mAnimatingCollapse) {
startOrUpdatePathAnimation(false /* expanding */);
+ } else if (mPositioner.showBubblesVertically()) {
+ child.setTranslationY(getBubbleXOrYForOrientation(index));
+ if (!mPreparingToCollapse) {
+ // Only animate if we're not collapsing as that animation will handle placing the
+ // new bubble in the stacked position.
+ Rect availableRect = mPositioner.getAvailableRect();
+ boolean onLeft = mCollapsePoint != null
+ && mCollapsePoint.x < (availableRect.width() / 2f);
+ float fromX = onLeft
+ ? -mBubbleSizePx * ANIMATE_TRANSLATION_FACTOR
+ : availableRect.right + mBubbleSizePx * ANIMATE_TRANSLATION_FACTOR;
+ float toX = onLeft
+ ? availableRect.left + mExpandedViewPadding
+ : availableRect.right - mBubbleSizePx - mExpandedViewPadding;
+ animationForChild(child)
+ .translationX(fromX, toX)
+ .start();
+ updateBubblePositions();
+ }
} else {
child.setTranslationX(getBubbleXOrYForOrientation(index));
-
- // If we're preparing to collapse, don't start animations since the collapse animation
- // will take over and animate the new bubble into the correct (stacked) position.
if (!mPreparingToCollapse) {
+ // Only animate if we're not collapsing as that animation will handle placing the
+ // new bubble in the stacked position.
+ float toY = getExpandedY();
+ float fromY = getExpandedY() - mBubbleSizePx * ANIMATE_TRANSLATION_FACTOR;
animationForChild(child)
- .translationY(
- getExpandedY()
- - mBubbleSizePx * ANIMATE_TRANSLATION_FACTOR, /* from */
- getExpandedY() /* to */)
+ .translationY(fromY, toY)
.start();
updateBubblePositions();
}
@@ -617,15 +634,16 @@ public class ExpandedAnimationController
}
}
+ // TODO - could move to method on bubblePositioner if mSpaceBetweenBubbles gets moved
/**
* When bubbles are expanded in portrait, they display at the top of the screen in a horizontal
- * row. When in landscape, they show at the left or right side in a vertical row. This method
- * accounts for screen orientation and will return an x or y value for the position of the
- * bubble in the row.
+ * row. When in landscape or on a large screen, they show at the left or right side in a
+ * vertical row. This method accounts for screen orientation and will return an x or y value
+ * for the position of the bubble in the row.
*
* @param index Bubble index in row.
- * @return the y position of the bubble if {@link Configuration#ORIENTATION_LANDSCAPE} and the
- * x position if {@link Configuration#ORIENTATION_PORTRAIT}.
+ * @return the y position of the bubble if showing vertically and the x position if showing
+ * horizontally.
*/
public float getBubbleXOrYForOrientation(int index) {
if (mLayout == null) {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/animation/StackAnimationController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/animation/StackAnimationController.java
index 56fe126f507e..578f87fbfbf8 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/animation/StackAnimationController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/animation/StackAnimationController.java
@@ -958,17 +958,26 @@ public class StackAnimationController extends
if (!isActiveController()) {
return;
}
- v.setTranslationX(mStackPosition.x);
+
final float yOffset =
getOffsetForChainedPropertyAnimation(DynamicAnimation.TRANSLATION_Y);
- final float endY = mStackPosition.y + yOffset * index;
- final float startY = endY + NEW_BUBBLE_START_Y;
- v.setTranslationY(startY);
+ float endY = mStackPosition.y + yOffset * index;
+ float endX = mStackPosition.x;
+ if (mPositioner.showBubblesVertically()) {
+ v.setTranslationY(endY);
+ final float startX = isStackOnLeftSide()
+ ? endX - NEW_BUBBLE_START_Y
+ : endX + NEW_BUBBLE_START_Y;
+ v.setTranslationX(startX);
+ } else {
+ v.setTranslationX(mStackPosition.x);
+ final float startY = endY + NEW_BUBBLE_START_Y;
+ v.setTranslationY(startY);
+ }
v.setScaleX(NEW_BUBBLE_START_SCALE);
v.setScaleY(NEW_BUBBLE_START_SCALE);
v.setAlpha(0f);
final ViewPropertyAnimator animator = v.animate()
- .translationY(endY)
.scaleX(1f)
.scaleY(1f)
.alpha(1f)
@@ -977,6 +986,11 @@ public class StackAnimationController extends
v.setTag(R.id.reorder_animator_tag, null);
});
v.setTag(R.id.reorder_animator_tag, animator);
+ if (mPositioner.showBubblesVertically()) {
+ animator.translationX(endX);
+ } else {
+ animator.translationY(endY);
+ }
}
/**
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PhonePipMenuController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PhonePipMenuController.java
index 17cde731faa2..9cf0b721cc48 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PhonePipMenuController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PhonePipMenuController.java
@@ -18,6 +18,8 @@ package com.android.wm.shell.pip.phone;
import static android.view.WindowManager.SHELL_ROOT_LAYER_PIP;
+import static com.android.wm.shell.pip.phone.PipMenuView.ANIM_TYPE_HIDE;
+
import android.annotation.Nullable;
import android.app.RemoteAction;
import android.content.Context;
@@ -397,26 +399,26 @@ public class PhonePipMenuController implements PipMenuController {
* Hides the menu view.
*/
public void hideMenu() {
- hideMenu(true /* animate */, true /* resize */);
+ hideMenu(ANIM_TYPE_HIDE, true /* resize */);
}
/**
* Hides the menu view.
*
- * @param animate whether to animate the menu fadeout
+ * @param animationType the animation type to use upon hiding the menu
* @param resize whether or not to resize the PiP with the state change
*/
- public void hideMenu(boolean animate, boolean resize) {
+ public void hideMenu(@PipMenuView.AnimationType int animationType, boolean resize) {
final boolean isMenuVisible = isMenuVisible();
if (DEBUG) {
Log.d(TAG, "hideMenu() state=" + mMenuState
+ " isMenuVisible=" + isMenuVisible
- + " animate=" + animate
+ + " animationType=" + animationType
+ " resize=" + resize
+ " callers=\n" + Debug.getCallers(5, " "));
}
if (isMenuVisible) {
- mPipMenuView.hideMenu(animate, resize);
+ mPipMenuView.hideMenu(resize, animationType);
}
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipMenuView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipMenuView.java
index a57e8cdd0928..2b45346cfa5c 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipMenuView.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipMenuView.java
@@ -32,6 +32,7 @@ import android.animation.AnimatorListenerAdapter;
import android.animation.AnimatorSet;
import android.animation.ObjectAnimator;
import android.animation.ValueAnimator;
+import android.annotation.IntDef;
import android.app.PendingIntent.CanceledException;
import android.app.RemoteAction;
import android.content.ComponentName;
@@ -64,6 +65,8 @@ import com.android.wm.shell.animation.Interpolators;
import com.android.wm.shell.common.ShellExecutor;
import com.android.wm.shell.pip.PipUtils;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
import java.util.ArrayList;
import java.util.List;
@@ -74,9 +77,26 @@ public class PipMenuView extends FrameLayout {
private static final String TAG = "PipMenuView";
+ private static final int ANIMATION_NONE_DURATION_MS = 0;
+ private static final int ANIMATION_HIDE_DURATION_MS = 125;
+
+ /** No animation performed during menu hide. */
+ public static final int ANIM_TYPE_NONE = 0;
+ /** Fade out the menu until it's invisible. Used when the PIP window remains visible. */
+ public static final int ANIM_TYPE_HIDE = 1;
+ /** Fade out the menu in sync with the PIP window. */
+ public static final int ANIM_TYPE_DISMISS = 2;
+
+ @IntDef(prefix = { "ANIM_TYPE_" }, value = {
+ ANIM_TYPE_NONE,
+ ANIM_TYPE_HIDE,
+ ANIM_TYPE_DISMISS
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface AnimationType {}
+
private static final int INITIAL_DISMISS_DELAY = 3500;
private static final int POST_INTERACTION_DISMISS_DELAY = 2000;
- private static final long MENU_FADE_DURATION = 125;
private static final long MENU_SHOW_ON_EXPAND_START_DELAY = 30;
private static final float MENU_BACKGROUND_ALPHA = 0.3f;
@@ -87,6 +107,7 @@ public class PipMenuView extends FrameLayout {
private int mMenuState;
private boolean mAllowMenuTimeout = true;
private boolean mAllowTouches = true;
+ private int mDismissFadeOutDurationMs;
private final List<RemoteAction> mActions = new ArrayList<>();
@@ -167,6 +188,8 @@ public class PipMenuView extends FrameLayout {
mPipMenuIconsAlgorithm = new PipMenuIconsAlgorithm(mContext);
mPipMenuIconsAlgorithm.bindViews((ViewGroup) mViewRoot, (ViewGroup) mTopEndContainer,
mResizeHandle, mSettingsButton, mDismissButton);
+ mDismissFadeOutDurationMs = context.getResources()
+ .getInteger(R.integer.config_pipExitAnimationDuration);
initAccessibility();
}
@@ -258,7 +281,7 @@ public class PipMenuView extends FrameLayout {
mMenuContainerAnimator.playTogether(dismissAnim, resizeAnim);
}
mMenuContainerAnimator.setInterpolator(Interpolators.ALPHA_IN);
- mMenuContainerAnimator.setDuration(MENU_FADE_DURATION);
+ mMenuContainerAnimator.setDuration(ANIMATION_HIDE_DURATION_MS);
if (allowMenuTimeout) {
mMenuContainerAnimator.addListener(new AnimatorListenerAdapter() {
@Override
@@ -320,17 +343,18 @@ public class PipMenuView extends FrameLayout {
hideMenu(null);
}
- void hideMenu(boolean animate, boolean resize) {
- hideMenu(null, true /* notifyMenuVisibility */, animate, resize);
+ void hideMenu(Runnable animationEndCallback) {
+ hideMenu(animationEndCallback, true /* notifyMenuVisibility */, true /* resize */,
+ ANIM_TYPE_HIDE);
}
- void hideMenu(Runnable animationEndCallback) {
- hideMenu(animationEndCallback, true /* notifyMenuVisibility */, true /* animate */,
- true /* resize */);
+ void hideMenu(boolean resize, @AnimationType int animationType) {
+ hideMenu(null /* animationFinishedRunnable */, true /* notifyMenuVisibility */, resize,
+ animationType);
}
- private void hideMenu(final Runnable animationFinishedRunnable, boolean notifyMenuVisibility,
- boolean animate, boolean resize) {
+ void hideMenu(final Runnable animationFinishedRunnable, boolean notifyMenuVisibility,
+ boolean resize, @AnimationType int animationType) {
if (mMenuState != MENU_STATE_NONE) {
cancelDelayedHide();
if (notifyMenuVisibility) {
@@ -348,7 +372,7 @@ public class PipMenuView extends FrameLayout {
mResizeHandle.getAlpha(), 0f);
mMenuContainerAnimator.playTogether(menuAnim, settingsAnim, dismissAnim, resizeAnim);
mMenuContainerAnimator.setInterpolator(Interpolators.ALPHA_OUT);
- mMenuContainerAnimator.setDuration(animate ? MENU_FADE_DURATION : 0);
+ mMenuContainerAnimator.setDuration(getFadeOutDuration(animationType));
mMenuContainerAnimator.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
@@ -478,19 +502,17 @@ public class PipMenuView extends FrameLayout {
private void expandPip() {
// Do not notify menu visibility when hiding the menu, the controller will do this when it
// handles the message
- hideMenu(mController::onPipExpand, false /* notifyMenuVisibility */, true /* animate */,
- true /* resize */);
+ hideMenu(mController::onPipExpand, false /* notifyMenuVisibility */, true /* resize */,
+ ANIM_TYPE_HIDE);
}
private void dismissPip() {
- // Since tapping on the close-button invokes a double-tap wait callback in PipTouchHandler,
- // we want to disable animating the fadeout animation of the buttons in order to call on
- // PipTouchHandler#onPipDismiss fast enough.
- final boolean animate = mMenuState != MENU_STATE_CLOSE;
- // Do not notify menu visibility when hiding the menu, the controller will do this when it
- // handles the message
- hideMenu(mController::onPipDismiss, false /* notifyMenuVisibility */, animate,
- true /* resize */);
+ if (mMenuState != MENU_STATE_NONE) {
+ // Do not call hideMenu() directly. Instead, let the menu controller handle it just as
+ // any other dismissal that will update the touch state and fade out the PIP task
+ // and the menu view at the same time.
+ mController.onPipDismiss();
+ }
}
private void showSettings() {
@@ -514,4 +536,17 @@ public class PipMenuView extends FrameLayout {
mMainExecutor.removeCallbacks(mHideMenuRunnable);
mMainExecutor.executeDelayed(mHideMenuRunnable, recommendedTimeout);
}
+
+ private long getFadeOutDuration(@AnimationType int animationType) {
+ switch (animationType) {
+ case ANIM_TYPE_NONE:
+ return ANIMATION_NONE_DURATION_MS;
+ case ANIM_TYPE_HIDE:
+ return ANIMATION_HIDE_DURATION_MS;
+ case ANIM_TYPE_DISMISS:
+ return mDismissFadeOutDurationMs;
+ default:
+ throw new IllegalStateException("Invalid animation type " + animationType);
+ }
+ }
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipMotionHelper.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipMotionHelper.java
index 67467eda51d0..9401cd6a2954 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipMotionHelper.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipMotionHelper.java
@@ -22,6 +22,8 @@ import static androidx.dynamicanimation.animation.SpringForce.STIFFNESS_MEDIUM;
import static com.android.wm.shell.pip.PipAnimationController.TRANSITION_DIRECTION_EXPAND_OR_UNEXPAND;
import static com.android.wm.shell.pip.PipBoundsState.STASH_TYPE_NONE;
+import static com.android.wm.shell.pip.phone.PipMenuView.ANIM_TYPE_DISMISS;
+import static com.android.wm.shell.pip.phone.PipMenuView.ANIM_TYPE_NONE;
import android.annotation.NonNull;
import android.annotation.Nullable;
@@ -336,7 +338,7 @@ public class PipMotionHelper implements PipAppOpsListener.Callback,
+ " callers=\n" + Debug.getCallers(5, " "));
}
cancelPhysicsAnimation();
- mMenuController.hideMenu(false /* animate */, false /* resize */);
+ mMenuController.hideMenu(ANIM_TYPE_NONE, false /* resize */);
mPipTaskOrganizer.exitPip(skipAnimation ? 0 : LEAVE_PIP_DURATION);
}
@@ -349,7 +351,7 @@ public class PipMotionHelper implements PipAppOpsListener.Callback,
Log.d(TAG, "removePip: callers=\n" + Debug.getCallers(5, " "));
}
cancelPhysicsAnimation();
- mMenuController.hideMenu(true /* animate*/, false /* resize */);
+ mMenuController.hideMenu(ANIM_TYPE_DISMISS, false /* resize */);
mPipTaskOrganizer.removePip();
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipResizeGestureHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipResizeGestureHandler.java
index c2ec1c55a59f..8726ee76d29a 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipResizeGestureHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipResizeGestureHandler.java
@@ -21,6 +21,7 @@ import static com.android.internal.policy.TaskResizingAlgorithm.CTRL_LEFT;
import static com.android.internal.policy.TaskResizingAlgorithm.CTRL_NONE;
import static com.android.internal.policy.TaskResizingAlgorithm.CTRL_RIGHT;
import static com.android.internal.policy.TaskResizingAlgorithm.CTRL_TOP;
+import static com.android.wm.shell.pip.phone.PipMenuView.ANIM_TYPE_NONE;
import android.content.Context;
import android.content.res.Resources;
@@ -471,7 +472,7 @@ public class PipResizeGestureHandler {
}
if (mThresholdCrossed) {
if (mPhonePipMenuController.isMenuVisible()) {
- mPhonePipMenuController.hideMenu(false /* animate */,
+ mPhonePipMenuController.hideMenu(ANIM_TYPE_NONE,
false /* resize */);
}
final Rect currentPipBounds = mPipBoundsState.getBounds();
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipTouchHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipTouchHandler.java
index 6d96312ad962..0a0798ef24c1 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipTouchHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipTouchHandler.java
@@ -25,6 +25,7 @@ import static com.android.wm.shell.pip.PipBoundsState.STASH_TYPE_RIGHT;
import static com.android.wm.shell.pip.phone.PhonePipMenuController.MENU_STATE_CLOSE;
import static com.android.wm.shell.pip.phone.PhonePipMenuController.MENU_STATE_FULL;
import static com.android.wm.shell.pip.phone.PhonePipMenuController.MENU_STATE_NONE;
+import static com.android.wm.shell.pip.phone.PipMenuView.ANIM_TYPE_NONE;
import android.annotation.NonNull;
import android.annotation.SuppressLint;
@@ -881,7 +882,7 @@ public class PipTouchHandler {
&& mPipBoundsState.getBounds().height()
< mPipBoundsState.getMaxSize().y;
if (mMenuController.isMenuVisible()) {
- mMenuController.hideMenu(false /* animate */, false /* resize */);
+ mMenuController.hideMenu(ANIM_TYPE_NONE, false /* resize */);
}
if (toExpand) {
mPipResizeGestureHandler.setUserResizeBounds(mPipBoundsState.getBounds());
diff --git a/media/java/android/media/MediaRouter2.java b/media/java/android/media/MediaRouter2.java
index 232de0b7c57f..fbf7def4093c 100644
--- a/media/java/android/media/MediaRouter2.java
+++ b/media/java/android/media/MediaRouter2.java
@@ -180,7 +180,7 @@ public final class MediaRouter2 {
// SecurityException will be thrown if there's no permission.
serviceBinder.enforceMediaContentControlPermission();
} catch (RemoteException e) {
- Log.e(TAG, "Unable to check MEDIA_CONTENT_CONTROL permission.");
+ e.rethrowFromSystemServer();
}
PackageManager pm = context.getPackageManager();
diff --git a/media/tests/MediaRouter/Android.bp b/media/tests/MediaRouter/Android.bp
index d41bc02906ab..2da6c9884c0f 100644
--- a/media/tests/MediaRouter/Android.bp
+++ b/media/tests/MediaRouter/Android.bp
@@ -18,7 +18,9 @@ android_test {
],
static_libs: [
+ "androidx.test.core",
"androidx.test.rules",
+ "compatibility-device-util-axt",
"mockito-target-minus-junit4",
"testng",
"truth-prebuilt",
diff --git a/media/tests/MediaRouter/AndroidManifest.xml b/media/tests/MediaRouter/AndroidManifest.xml
index 02688d5d641a..018f148dd3d5 100644
--- a/media/tests/MediaRouter/AndroidManifest.xml
+++ b/media/tests/MediaRouter/AndroidManifest.xml
@@ -19,6 +19,7 @@
<application android:label="@string/app_name">
<uses-library android:name="android.test.runner" />
+ <activity android:name="com.android.mediaroutertest.MediaRouter2ManagerTestActivity" />
<service android:name=".StubMediaRoute2ProviderService"
android:exported="true">
<intent-filter>
diff --git a/media/tests/MediaRouter/src/com/android/mediaroutertest/MediaRouter2ManagerTest.java b/media/tests/MediaRouter/src/com/android/mediaroutertest/MediaRouter2ManagerTest.java
index eaa4f03ba81a..3a34e756a50a 100644
--- a/media/tests/MediaRouter/src/com/android/mediaroutertest/MediaRouter2ManagerTest.java
+++ b/media/tests/MediaRouter/src/com/android/mediaroutertest/MediaRouter2ManagerTest.java
@@ -16,7 +16,6 @@
package com.android.mediaroutertest;
-import static android.media.MediaRoute2Info.FEATURE_LIVE_AUDIO;
import static android.media.MediaRoute2Info.FEATURE_REMOTE_PLAYBACK;
import static android.media.MediaRoute2Info.PLAYBACK_VOLUME_FIXED;
import static android.media.MediaRoute2Info.PLAYBACK_VOLUME_VARIABLE;
@@ -61,6 +60,8 @@ import androidx.test.filters.LargeTest;
import androidx.test.filters.SmallTest;
import androidx.test.runner.AndroidJUnit4;
+import com.android.compatibility.common.util.PollingCheck;
+
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
@@ -94,6 +95,7 @@ public class MediaRouter2ManagerTest {
private MediaRouter2 mRouter2;
private Executor mExecutor;
private String mPackageName;
+ private StubMediaRoute2ProviderService mService;
private final List<MediaRouter2Manager.Callback> mManagerCallbacks = new ArrayList<>();
private final List<RouteCallback> mRouteCallbacks = new ArrayList<>();
@@ -105,7 +107,6 @@ public class MediaRouter2ManagerTest {
static {
FEATURES_ALL.add(FEATURE_SAMPLE);
FEATURES_ALL.add(FEATURE_SPECIAL);
- FEATURES_ALL.add(FEATURE_LIVE_AUDIO);
FEATURES_SPECIAL.add(FEATURE_SPECIAL);
}
@@ -115,26 +116,53 @@ public class MediaRouter2ManagerTest {
mContext = InstrumentationRegistry.getTargetContext();
mUiAutomation = InstrumentationRegistry.getInstrumentation().getUiAutomation();
mUiAutomation.adoptShellPermissionIdentity(Manifest.permission.MEDIA_CONTENT_CONTROL);
+ MediaRouter2ManagerTestActivity.startActivity(mContext);
+
mManager = MediaRouter2Manager.getInstance(mContext);
+ mManager.startScan();
mRouter2 = MediaRouter2.getInstance(mContext);
+
// If we need to support thread pool executors, change this to thread pool executor.
mExecutor = Executors.newSingleThreadExecutor();
mPackageName = mContext.getPackageName();
+
+ // In order to make the system bind to the test service,
+ // set a non-empty discovery preference while app is in foreground.
+ List<String> features = new ArrayList<>();
+ features.add("A test feature");
+ RouteDiscoveryPreference preference =
+ new RouteDiscoveryPreference.Builder(features, false).build();
+ mRouter2.registerRouteCallback(mExecutor, new RouteCallback() {}, preference);
+
+ new PollingCheck(TIMEOUT_MS) {
+ @Override
+ protected boolean check() {
+ StubMediaRoute2ProviderService service =
+ StubMediaRoute2ProviderService.getInstance();
+ if (service != null) {
+ mService = service;
+ return true;
+ }
+ return false;
+ }
+ }.run();
}
@After
public void tearDown() {
+ mManager.stopScan();
+
// order matters (callbacks should be cleared at the last)
releaseAllSessions();
// unregister callbacks
clearCallbacks();
- StubMediaRoute2ProviderService instance = StubMediaRoute2ProviderService.getInstance();
- if (instance != null) {
- instance.setProxy(null);
- instance.setSpy(null);
+ if (mService != null) {
+ mService.setProxy(null);
+ mService.setSpy(null);
}
+ MediaRouter2ManagerTestActivity.finishActivity();
mUiAutomation.dropShellPermissionIdentity();
}
@@ -179,13 +207,10 @@ public class MediaRouter2ManagerTest {
MediaRoute2Info routeToRemove = routes.get(ROUTE_ID2);
assertNotNull(routeToRemove);
- StubMediaRoute2ProviderService sInstance =
- StubMediaRoute2ProviderService.getInstance();
- assertNotNull(sInstance);
- sInstance.removeRoute(ROUTE_ID2);
+ mService.removeRoute(ROUTE_ID2);
assertTrue(removedLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
- sInstance.addRoute(routeToRemove);
+ mService.addRoute(routeToRemove);
assertTrue(addedLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
}
@@ -218,10 +243,7 @@ public class MediaRouter2ManagerTest {
MediaRoute2Info routeToRemove = routes.get(ROUTE_ID2);
assertNotNull(routeToRemove);
- StubMediaRoute2ProviderService sInstance =
- StubMediaRoute2ProviderService.getInstance();
- assertNotNull(sInstance);
- sInstance.removeRoute(ROUTE_ID2);
+ mService.removeRoute(ROUTE_ID2);
// Wait until the route is removed.
assertTrue(removedLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
@@ -230,7 +252,7 @@ public class MediaRouter2ManagerTest {
assertNull(newRoutes.get(ROUTE_ID2));
// Revert the removal.
- sInstance.addRoute(routeToRemove);
+ mService.addRoute(routeToRemove);
assertTrue(addedLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
mRouter2.unregisterRouteCallback(routeCallback);
}
@@ -445,9 +467,7 @@ public class MediaRouter2ManagerTest {
CountDownLatch serviceOnReleaseSessionLatch = new CountDownLatch(1);
List<RoutingSessionInfo> sessions = new ArrayList<>();
- StubMediaRoute2ProviderService instance = StubMediaRoute2ProviderService.getInstance();
- assertNotNull(instance);
- instance.setSpy(new StubMediaRoute2ProviderService.Spy() {
+ mService.setSpy(new StubMediaRoute2ProviderService.Spy() {
@Override
public void onReleaseSession(long requestId, String sessionId) {
serviceOnReleaseSessionLatch.countDown();
@@ -652,12 +672,9 @@ public class MediaRouter2ManagerTest {
Map<String, MediaRoute2Info> routes = waitAndGetRoutesWithManager(FEATURES_ALL);
MediaRoute2Info volRoute = routes.get(ROUTE_ID_VARIABLE_VOLUME);
- StubMediaRoute2ProviderService instance = StubMediaRoute2ProviderService.getInstance();
- assertNotNull(instance);
-
final List<Long> requestIds = new ArrayList<>();
final CountDownLatch onSetRouteVolumeLatch = new CountDownLatch(1);
- instance.setProxy(new StubMediaRoute2ProviderService.Proxy() {
+ mService.setProxy(new StubMediaRoute2ProviderService.Proxy() {
@Override
public void onSetRouteVolume(String routeId, int volume, long requestId) {
requestIds.add(requestId);
@@ -687,16 +704,16 @@ public class MediaRouter2ManagerTest {
});
final long invalidRequestId = REQUEST_ID_NONE;
- instance.notifyRequestFailed(invalidRequestId, failureReason);
+ mService.notifyRequestFailed(invalidRequestId, failureReason);
assertFalse(onRequestFailedLatch.await(WAIT_TIME_MS, TimeUnit.MILLISECONDS));
final long validRequestId = requestIds.get(0);
- instance.notifyRequestFailed(validRequestId, failureReason);
+ mService.notifyRequestFailed(validRequestId, failureReason);
assertTrue(onRequestFailedLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
// Test calling notifyRequestFailed() multiple times with the same valid requestId.
// onRequestFailed() shouldn't be called since the requestId has been already handled.
- instance.notifyRequestFailed(validRequestId, failureReason);
+ mService.notifyRequestFailed(validRequestId, failureReason);
assertFalse(onRequestFailedSecondCallLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
}
@@ -813,7 +830,8 @@ public class MediaRouter2ManagerTest {
@Override
public void onRoutesAdded(List<MediaRoute2Info> routes) {
for (MediaRoute2Info route : routes) {
- if (!route.isSystemRoute()) {
+ if (!route.isSystemRoute()
+ && hasMatchingFeature(route.getFeatures(), routeFeatures)) {
addedLatch.countDown();
break;
}
@@ -834,10 +852,10 @@ public class MediaRouter2ManagerTest {
mRouter2.registerRouteCallback(mExecutor, routeCallback,
new RouteDiscoveryPreference.Builder(routeFeatures, true).build());
try {
- if (mManager.getAllRoutes().isEmpty()) {
+ featuresLatch.await(WAIT_TIME_MS, TimeUnit.MILLISECONDS);
+ if (mManager.getAvailableRoutes(mPackageName).isEmpty()) {
addedLatch.await(WAIT_TIME_MS, TimeUnit.MILLISECONDS);
}
- featuresLatch.await(WAIT_TIME_MS, TimeUnit.MILLISECONDS);
return createRouteMap(mManager.getAvailableRoutes(mPackageName));
} finally {
mRouter2.unregisterRouteCallback(routeCallback);
@@ -845,6 +863,15 @@ public class MediaRouter2ManagerTest {
}
}
+ boolean hasMatchingFeature(List<String> features1, List<String> features2) {
+ for (String feature : features1) {
+ if (features2.contains(feature)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
void awaitOnRouteChangedManager(Runnable task, String routeId,
Predicate<MediaRoute2Info> predicate) throws Exception {
CountDownLatch latch = new CountDownLatch(1);
diff --git a/media/tests/MediaRouter/src/com/android/mediaroutertest/MediaRouter2ManagerTestActivity.java b/media/tests/MediaRouter/src/com/android/mediaroutertest/MediaRouter2ManagerTestActivity.java
new file mode 100644
index 000000000000..ac2a8bbd4e50
--- /dev/null
+++ b/media/tests/MediaRouter/src/com/android/mediaroutertest/MediaRouter2ManagerTestActivity.java
@@ -0,0 +1,53 @@
+/*
+ * Copyright (C) 2021 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.mediaroutertest;
+
+import android.app.Activity;
+import android.content.Context;
+import android.content.Intent;
+import android.os.Bundle;
+import android.view.WindowManager;
+
+import androidx.test.core.app.ActivityScenario;
+
+public class MediaRouter2ManagerTestActivity extends Activity {
+
+ private static ActivityScenario<MediaRouter2ManagerTestActivity> sActivityScenario;
+
+ public static ActivityScenario<MediaRouter2ManagerTestActivity> startActivity(Context context) {
+ Intent intent = new Intent(Intent.ACTION_MAIN);
+ intent.setClass(context, MediaRouter2ManagerTestActivity.class);
+ sActivityScenario = ActivityScenario.launch(intent);
+ return sActivityScenario;
+ }
+
+ public static void finishActivity() {
+ if (sActivityScenario != null) {
+ // TODO: Sometimes calling this takes about 5 seconds. Need to figure out why.
+ sActivityScenario.close();
+ sActivityScenario = null;
+ }
+ }
+
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setTurnScreenOn(true);
+ setShowWhenLocked(true);
+ getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
+ }
+}
diff --git a/native/android/surface_control.cpp b/native/android/surface_control.cpp
index 7540a143c2f3..a8c2ea544d38 100644
--- a/native/android/surface_control.cpp
+++ b/native/android/surface_control.cpp
@@ -438,27 +438,44 @@ void ASurfaceTransaction_setGeometry(ASurfaceTransaction* aSurfaceTransaction,
const ARect& destination, int32_t transform) {
CHECK_NOT_NULL(aSurfaceTransaction);
CHECK_NOT_NULL(aSurfaceControl);
+ CHECK_VALID_RECT(source);
CHECK_VALID_RECT(destination);
+ sp<SurfaceControl> surfaceControl = ASurfaceControl_to_SurfaceControl(aSurfaceControl);
+ Transaction* transaction = ASurfaceTransaction_to_Transaction(aSurfaceTransaction);
+
Rect sourceRect = static_cast<const Rect&>(source);
+ Rect destRect = static_cast<const Rect&>(destination);
// Adjust the source so its top and left are not negative
sourceRect.left = std::max(sourceRect.left, 0);
sourceRect.top = std::max(sourceRect.top, 0);
- LOG_ALWAYS_FATAL_IF(sourceRect.isEmpty(), "invalid arg passed as source argument");
-
- sp<SurfaceControl> surfaceControl = ASurfaceControl_to_SurfaceControl(aSurfaceControl);
- Transaction* transaction = ASurfaceTransaction_to_Transaction(aSurfaceTransaction);
+ if (!sourceRect.isValid()) {
+ sourceRect.makeInvalid();
+ }
transaction->setBufferCrop(surfaceControl, sourceRect);
- float dsdx = (destination.right - destination.left) /
- static_cast<float>(sourceRect.right - sourceRect.left);
- float dsdy = (destination.bottom - destination.top) /
- static_cast<float>(sourceRect.bottom - sourceRect.top);
+ int destW = destRect.width();
+ int destH = destRect.height();
+ if (destRect.left < 0) {
+ destRect.left = 0;
+ destRect.right = destW;
+ }
+ if (destRect.top < 0) {
+ destRect.top = 0;
+ destRect.bottom = destH;
+ }
+
+ if (!sourceRect.isEmpty()) {
+ float sx = destW / static_cast<float>(sourceRect.width());
+ float sy = destH / static_cast<float>(sourceRect.height());
+ transaction->setPosition(surfaceControl, destRect.left - (sourceRect.left * sx),
+ destRect.top - (sourceRect.top * sy));
+ transaction->setMatrix(surfaceControl, sx, 0, 0, sy);
+ } else {
+ transaction->setPosition(surfaceControl, destRect.left, destRect.top);
+ }
- transaction->setPosition(surfaceControl, destination.left - (sourceRect.left * dsdx),
- destination.top - (sourceRect.top * dsdy));
- transaction->setMatrix(surfaceControl, dsdx, 0, 0, dsdy);
transaction->setTransform(surfaceControl, transform);
bool transformToInverseDisplay = (NATIVE_WINDOW_TRANSFORM_INVERSE_DISPLAY & transform) ==
NATIVE_WINDOW_TRANSFORM_INVERSE_DISPLAY;
diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/ActivityLaunchAnimator.kt b/packages/SystemUI/animation/src/com/android/systemui/animation/ActivityLaunchAnimator.kt
index 6d6bc07c01b5..20273d05f059 100644
--- a/packages/SystemUI/animation/src/com/android/systemui/animation/ActivityLaunchAnimator.kt
+++ b/packages/SystemUI/animation/src/com/android/systemui/animation/ActivityLaunchAnimator.kt
@@ -8,6 +8,7 @@ import android.app.PendingIntent
import android.content.Context
import android.graphics.Matrix
import android.graphics.Rect
+import android.os.Looper
import android.os.RemoteException
import android.util.MathUtils
import android.view.IRemoteAnimationFinishedCallback
@@ -73,16 +74,20 @@ class ActivityLaunchAnimator(context: Context) {
* in [Controller.onLaunchAnimationProgress]. No animation will start if there is no window
* opening.
*
- * If [controller] is null, then the intent will be started and no animation will run.
+ * If [controller] is null or [animate] is false, then the intent will be started and no
+ * animation will run.
*
* This method will throw any exception thrown by [intentStarter].
*/
+ @JvmOverloads
inline fun startIntentWithAnimation(
controller: Controller?,
+ animate: Boolean = true,
intentStarter: (RemoteAnimationAdapter?) -> Int
) {
- if (controller == null) {
+ if (controller == null || !animate) {
intentStarter(null)
+ controller?.callOnIntentStartedOnMainThread(willAnimate = false)
return
}
@@ -95,7 +100,7 @@ class ActivityLaunchAnimator(context: Context) {
val launchResult = intentStarter(animationAdapter)
val willAnimate = launchResult == ActivityManager.START_TASK_TO_FRONT ||
launchResult == ActivityManager.START_SUCCESS
- runner.context.mainExecutor.execute { controller.onIntentStarted(willAnimate) }
+ controller.callOnIntentStartedOnMainThread(willAnimate)
// If we expect an animation, post a timeout to cancel it in case the remote animation is
// never started.
@@ -104,17 +109,30 @@ class ActivityLaunchAnimator(context: Context) {
}
}
+ @PublishedApi
+ internal fun Controller.callOnIntentStartedOnMainThread(willAnimate: Boolean) {
+ if (Looper.myLooper() != Looper.getMainLooper()) {
+ this.getRootView().context.mainExecutor.execute {
+ this.onIntentStarted(willAnimate)
+ }
+ } else {
+ this.onIntentStarted(willAnimate)
+ }
+ }
+
/**
* Same as [startIntentWithAnimation] but allows [intentStarter] to throw a
* [PendingIntent.CanceledException] which must then be handled by the caller. This is useful
* for Java caller starting a [PendingIntent].
*/
@Throws(PendingIntent.CanceledException::class)
+ @JvmOverloads
fun startPendingIntentWithAnimation(
controller: Controller?,
+ animate: Boolean = true,
intentStarter: PendingIntentStarter
) {
- startIntentWithAnimation(controller) { intentStarter.startPendingIntent(it) }
+ startIntentWithAnimation(controller, animate) { intentStarter.startPendingIntent(it) }
}
/** Create a new animation [Runner] controlled by [controller]. */
diff --git a/packages/SystemUI/res/layout/ongoing_privacy_chip.xml b/packages/SystemUI/res/layout/ongoing_privacy_chip.xml
index bad582669079..676e49298cef 100644
--- a/packages/SystemUI/res/layout/ongoing_privacy_chip.xml
+++ b/packages/SystemUI/res/layout/ongoing_privacy_chip.xml
@@ -22,14 +22,16 @@
android:layout_height="match_parent"
android:layout_width="wrap_content"
android:layout_gravity="center_vertical|end"
- android:focusable="true"
- android:minWidth="48dp" >
+ android:focusable="true" >
<LinearLayout
android:id="@+id/icons_container"
android:layout_height="@dimen/ongoing_appops_chip_height"
android:layout_width="wrap_content"
- android:gravity="center_vertical"
+ android:paddingStart="10dp"
+ android:paddingEnd="10dp"
+ android:gravity="center"
android:layout_gravity="center"
+ android:minWidth="56dp"
/>
</com.android.systemui.privacy.OngoingPrivacyChip> \ No newline at end of file
diff --git a/packages/SystemUI/res/layout/people_tile_emoji_background_large.xml b/packages/SystemUI/res/layout/people_tile_emoji_background_large.xml
new file mode 100644
index 000000000000..61d3ea5df6df
--- /dev/null
+++ b/packages/SystemUI/res/layout/people_tile_emoji_background_large.xml
@@ -0,0 +1,58 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2021 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.
+ -->
+<RelativeLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@+id/emojis"
+ android:theme="@android:style/Theme.DeviceDefault.DayNight"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:paddingTop="10dp">
+ <TextView
+ android:id="@+id/emoji1"
+ android:textAppearance="@*android:style/TextAppearance.DeviceDefault.ListItem"
+ android:textSize="34sp"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_alignParentTop="true"
+ android:layout_centerHorizontal="true"
+ android:paddingStart="40dp"
+ android:maxLines="1"
+ android:alpha="0.2"/>
+ <TextView
+ android:id="@+id/emoji2"
+ android:textAppearance="@*android:style/TextAppearance.DeviceDefault.ListItem"
+ android:textSize="34sp"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_centerVertical="true"
+ android:layout_alignParentStart="true"
+ android:paddingTop="5dp"
+ android:paddingStart="27dp"
+ android:maxLines="1"
+ android:alpha="0.2"/>
+ <TextView
+ android:id="@+id/emoji3"
+ android:textAppearance="@*android:style/TextAppearance.DeviceDefault.ListItem"
+ android:textSize="34sp"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_alignParentBottom="true"
+ android:layout_alignParentEnd="true"
+ android:paddingEnd="25dp"
+ android:maxLines="1"
+ android:alpha="0.2"/>
+</RelativeLayout> \ No newline at end of file
diff --git a/packages/SystemUI/res/layout/people_tile_emoji_background_medium.xml b/packages/SystemUI/res/layout/people_tile_emoji_background_medium.xml
new file mode 100644
index 000000000000..a5f300fa54b1
--- /dev/null
+++ b/packages/SystemUI/res/layout/people_tile_emoji_background_medium.xml
@@ -0,0 +1,58 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2021 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.
+ -->
+<LinearLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@+id/emojis"
+ android:theme="@android:style/Theme.DeviceDefault.DayNight"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:orientation="horizontal"
+ android:gravity="center_vertical">
+ <TextView
+ android:id="@+id/emoji1"
+ android:textAppearance="@*android:style/TextAppearance.DeviceDefault.ListItem"
+ android:textSize="27sp"
+ android:paddingTop="-2dp"
+ android:layout_width="0dp"
+ android:layout_height="wrap_content"
+ android:layout_weight="2"
+ android:gravity="end"
+ android:maxLines="1"
+ android:alpha="0.2"/>
+ <TextView
+ android:id="@+id/emoji2"
+ android:textAppearance="@*android:style/TextAppearance.DeviceDefault.ListItem"
+ android:textSize="27sp"
+ android:layout_width="0dp"
+ android:layout_height="wrap_content"
+ android:layout_weight="1"
+ android:gravity="end"
+ android:paddingTop="20dp"
+ android:maxLines="1"
+ android:alpha="0.2"/>
+ <TextView
+ android:id="@+id/emoji3"
+ android:textAppearance="@*android:style/TextAppearance.DeviceDefault.ListItem"
+ android:textSize="27sp"
+ android:layout_width="0dp"
+ android:layout_height="wrap_content"
+ android:layout_weight="1"
+ android:layout_gravity="top"
+ android:paddingTop="10dp"
+ android:maxLines="1"
+ android:alpha="0.2"/>
+</LinearLayout> \ No newline at end of file
diff --git a/packages/SystemUI/res/layout/people_tile_large_with_content.xml b/packages/SystemUI/res/layout/people_tile_large_with_content.xml
index af2c5de3b52f..6f8de3b68589 100644
--- a/packages/SystemUI/res/layout/people_tile_large_with_content.xml
+++ b/packages/SystemUI/res/layout/people_tile_large_with_content.xml
@@ -67,68 +67,79 @@
android:visibility="gone"
/>
</RelativeLayout>
-
- <TextView
- android:layout_gravity="center"
- android:id="@+id/name"
+ <RelativeLayout
android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:paddingBottom="12dp"
- android:gravity="start"
- android:singleLine="true"
- android:ellipsize="end"
- android:text="@string/empty_user_name"
- android:textAppearance="@*android:style/TextAppearance.DeviceDefault.ListItem"
- android:textColor="?android:attr/textColorPrimary"
- android:textSize="14sp" />
+ android:layout_height="match_parent">
+ <include layout="@layout/people_tile_punctuation_background_large" />
+ <include layout="@layout/people_tile_emoji_background_large" />
- <LinearLayout
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:paddingBottom="4dp"
- android:gravity="center_vertical"
- android:orientation="horizontal">
+ <LinearLayout
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:orientation="vertical">
+ <TextView
+ android:layout_gravity="center"
+ android:id="@+id/name"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:paddingBottom="12dp"
+ android:gravity="start"
+ android:singleLine="true"
+ android:ellipsize="end"
+ android:text="@string/empty_user_name"
+ android:textAppearance="@*android:style/TextAppearance.DeviceDefault.ListItem"
+ android:textColor="?android:attr/textColorPrimary"
+ android:textSize="14sp" />
+
+ <LinearLayout
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:paddingBottom="4dp"
+ android:gravity="center_vertical"
+ android:orientation="horizontal">
+
+ <ImageView
+ android:id="@+id/predefined_icon"
+ android:tint="?android:attr/colorAccent"
+ android:gravity="start|center_vertical"
+ android:paddingEnd="6dp"
+ android:layout_width="24dp"
+ android:layout_height="18dp" />
+
+ <TextView
+ android:layout_gravity="center"
+ android:id="@+id/subtext"
+ android:gravity="center_vertical"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:ellipsize="end"
+ android:singleLine="true"
+ android:text="@string/empty_user_name"
+ android:textAppearance="@*android:style/TextAppearance.DeviceDefault.ListItem"
+ android:textColor="?android:attr/textColorSecondary"
+ android:textSize="12sp" />
+ </LinearLayout>
<ImageView
- android:id="@+id/predefined_icon"
- android:tint="?android:attr/colorAccent"
- android:gravity="start|center_vertical"
- android:paddingEnd="6dp"
- android:layout_width="24dp"
- android:layout_height="18dp" />
+ android:id="@+id/image"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:background="@drawable/people_space_content_background"
+ android:gravity="center"
+ android:scaleType="centerCrop" />
<TextView
android:layout_gravity="center"
- android:id="@+id/subtext"
- android:gravity="center_vertical"
+ android:id="@+id/text_content"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:ellipsize="end"
- android:singleLine="true"
- android:text="@string/empty_user_name"
+ android:maxLines="2"
+ android:singleLine="false"
+ android:text="@string/empty_status"
android:textAppearance="@*android:style/TextAppearance.DeviceDefault.ListItem"
- android:textColor="?android:attr/textColorSecondary"
+ android:textColor="?android:attr/textColorPrimary"
android:textSize="12sp" />
- </LinearLayout>
-
- <ImageView
- android:id="@+id/image"
- android:layout_width="match_parent"
- android:layout_height="match_parent"
- android:background="@drawable/people_space_content_background"
- android:gravity="center"
- android:scaleType="centerCrop" />
-
- <TextView
- android:layout_gravity="center"
- android:id="@+id/text_content"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:ellipsize="end"
- android:maxLines="2"
- android:singleLine="false"
- android:text="@string/empty_status"
- android:textAppearance="@*android:style/TextAppearance.DeviceDefault.ListItem"
- android:textColor="?android:attr/textColorPrimary"
- android:textSize="12sp" />
+ </LinearLayout>
+ </RelativeLayout>
</LinearLayout>
diff --git a/packages/SystemUI/res/layout/people_tile_medium_with_content.xml b/packages/SystemUI/res/layout/people_tile_medium_with_content.xml
index 70706600e6da..a8c15abc8459 100644
--- a/packages/SystemUI/res/layout/people_tile_medium_with_content.xml
+++ b/packages/SystemUI/res/layout/people_tile_medium_with_content.xml
@@ -21,121 +21,127 @@
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
- <LinearLayout
+ <RelativeLayout
android:background="@drawable/people_space_tile_view_card"
- android:id="@+id/item"
- android:orientation="vertical"
- android:layout_gravity="center"
- android:padding="8dp"
android:layout_width="match_parent"
android:layout_height="match_parent">
-
+ <include layout="@layout/people_tile_punctuation_background_medium" />
+ <include layout="@layout/people_tile_emoji_background_medium" />
<LinearLayout
- android:orientation="horizontal"
- android:gravity="top"
- android:layout_weight="1"
+ android:id="@+id/item"
+ android:orientation="vertical"
+ android:layout_gravity="center"
+ android:padding="8dp"
android:layout_width="match_parent"
- android:layout_height="0dp">
+ android:layout_height="match_parent">
+
+ <LinearLayout
+ android:orientation="horizontal"
+ android:gravity="top"
+ android:layout_weight="1"
+ android:layout_width="match_parent"
+ android:layout_height="0dp">
- <ImageView
- android:gravity="start"
- android:id="@+id/person_icon"
- android:layout_marginStart="-2dp"
- android:layout_marginTop="-2dp"
- android:layout_width="52dp"
- android:layout_height="52dp" />
+ <ImageView
+ android:gravity="start"
+ android:id="@+id/person_icon"
+ android:layout_marginStart="-2dp"
+ android:layout_marginTop="-2dp"
+ android:layout_width="52dp"
+ android:layout_height="52dp" />
- <ImageView
- android:id="@+id/availability"
- android:layout_marginStart="-2dp"
- android:layout_width="10dp"
- android:layout_height="10dp"
- android:background="@drawable/circle_green_10dp" />
+ <ImageView
+ android:id="@+id/availability"
+ android:layout_marginStart="-2dp"
+ android:layout_width="10dp"
+ android:layout_height="10dp"
+ android:background="@drawable/circle_green_10dp" />
+
+ <LinearLayout
+ android:orientation="vertical"
+ android:gravity="top|start"
+ android:paddingStart="12dp"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content">
+
+ <TextView
+ android:id="@+id/subtext"
+ android:text="@string/empty_user_name"
+ android:textAppearance="@*android:style/TextAppearance.DeviceDefault.ListItem"
+ android:textColor="?android:attr/textColorSecondary"
+ android:textSize="12sp"
+ android:paddingBottom="4dp"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:singleLine="true"
+ android:ellipsize="end" />
+
+ <ImageView
+ android:id="@+id/image"
+ android:gravity="center"
+ android:background="@drawable/people_space_content_background"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:scaleType="centerCrop" />
+
+ <TextView
+ android:id="@+id/text_content"
+ android:text="@string/empty_status"
+ android:textAppearance="@*android:style/TextAppearance.DeviceDefault.ListItem"
+ android:textColor="?android:attr/textColorPrimary"
+ android:textSize="12sp"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:maxLines="2"
+ android:singleLine="false"
+ android:ellipsize="end" />
+ </LinearLayout>
+ </LinearLayout>
<LinearLayout
- android:orientation="vertical"
- android:gravity="top|start"
- android:paddingStart="12dp"
+ android:gravity="bottom"
+ android:layout_gravity="center_vertical"
+ android:orientation="horizontal"
+ android:paddingTop="2dp"
android:layout_width="match_parent"
- android:layout_height="wrap_content">
-
+ android:layout_height="wrap_content"
+ android:clipToOutline="true">
<TextView
- android:id="@+id/subtext"
+ android:id="@+id/name"
+ android:gravity="center_vertical"
+ android:layout_weight="1"
android:text="@string/empty_user_name"
android:textAppearance="@*android:style/TextAppearance.DeviceDefault.ListItem"
- android:textColor="?android:attr/textColorSecondary"
- android:textSize="12sp"
- android:paddingBottom="4dp"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
+ android:textColor="?android:attr/textColorPrimary"
+ android:textSize="14sp"
android:singleLine="true"
- android:ellipsize="end" />
-
- <ImageView
- android:id="@+id/image"
- android:gravity="center"
- android:background="@drawable/people_space_content_background"
- android:layout_width="match_parent"
- android:layout_height="match_parent"
- android:scaleType="centerCrop" />
-
+ android:ellipsize="end"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content" />
<TextView
- android:id="@+id/text_content"
- android:text="@string/empty_status"
+ android:id="@+id/messages_count"
+ android:gravity="end"
+ android:paddingStart="8dp"
+ android:paddingEnd="8dp"
android:textAppearance="@*android:style/TextAppearance.DeviceDefault.ListItem"
android:textColor="?android:attr/textColorPrimary"
- android:textSize="12sp"
- android:layout_width="match_parent"
+ android:background="@drawable/people_space_messages_count_background"
+ android:textSize="14sp"
+ android:maxLines="1"
+ android:ellipsize="end"
+ android:layout_width="wrap_content"
android:layout_height="wrap_content"
- android:maxLines="2"
- android:singleLine="false"
- android:ellipsize="end" />
+ android:visibility="gone"
+ />
+ <ImageView
+ android:id="@+id/predefined_icon"
+ android:tint="?android:attr/colorAccent"
+ android:gravity="end|center_vertical"
+ android:paddingStart="6dp"
+ android:layout_width="24dp"
+ android:layout_height="18dp" />
</LinearLayout>
</LinearLayout>
-
- <LinearLayout
- android:gravity="bottom"
- android:layout_gravity="center_vertical"
- android:orientation="horizontal"
- android:paddingTop="2dp"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:clipToOutline="true">
- <TextView
- android:id="@+id/name"
- android:gravity="center_vertical"
- android:layout_weight="1"
- android:text="@string/empty_user_name"
- android:textAppearance="@*android:style/TextAppearance.DeviceDefault.ListItem"
- android:textColor="?android:attr/textColorPrimary"
- android:textSize="14sp"
- android:singleLine="true"
- android:ellipsize="end"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content" />
- <TextView
- android:id="@+id/messages_count"
- android:gravity="end"
- android:paddingStart="8dp"
- android:paddingEnd="8dp"
- android:textAppearance="@*android:style/TextAppearance.DeviceDefault.ListItem"
- android:textColor="?android:attr/textColorPrimary"
- android:background="@drawable/people_space_messages_count_background"
- android:textSize="14sp"
- android:maxLines="1"
- android:ellipsize="end"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:visibility="gone"
- />
- <ImageView
- android:id="@+id/predefined_icon"
- android:tint="?android:attr/colorAccent"
- android:gravity="end|center_vertical"
- android:paddingStart="6dp"
- android:layout_width="24dp"
- android:layout_height="18dp" />
- </LinearLayout>
- </LinearLayout>
+ </RelativeLayout>
</LinearLayout>
diff --git a/packages/SystemUI/res/layout/people_tile_punctuation_background_large.xml b/packages/SystemUI/res/layout/people_tile_punctuation_background_large.xml
new file mode 100644
index 000000000000..2ffe59a2d032
--- /dev/null
+++ b/packages/SystemUI/res/layout/people_tile_punctuation_background_large.xml
@@ -0,0 +1,109 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2021 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.
+ -->
+<RelativeLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"
+ android:theme="@android:style/Theme.DeviceDefault.DayNight"
+ android:id="@+id/punctuations"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:paddingTop="10dp">
+ <TextView
+ android:id="@+id/punctuation1"
+ android:textColor="?androidprv:attr/colorSurfaceVariant"
+ android:textAppearance="@*android:style/TextAppearance.DeviceDefault.ListItem"
+ android:textSize="34sp"
+ android:textStyle="bold"
+ android:paddingTop="-2dp"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_alignParentTop="true"
+ android:layout_centerHorizontal="true"
+ android:maxLines="1"
+ android:rotation="5"/>
+ <TextView
+ android:id="@+id/punctuation2"
+ android:textColor="?androidprv:attr/colorSurfaceVariant"
+ android:textAppearance="@*android:style/TextAppearance.DeviceDefault.ListItem"
+ android:textSize="34sp"
+ android:textStyle="bold"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_centerVertical="true"
+ android:layout_alignParentStart="true"
+ android:paddingTop="5dp"
+ android:paddingStart="27dp"
+ android:includeFontPadding="false"
+ android:maxLines="1"
+ android:rotation="350"/>
+ <TextView
+ android:id="@+id/punctuation3"
+ android:textColor="?androidprv:attr/colorSurfaceVariant"
+ android:textAppearance="@*android:style/TextAppearance.DeviceDefault.ListItem"
+ android:textSize="34sp"
+ android:textStyle="bold"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_alignParentBottom="true"
+ android:layout_alignParentEnd="true"
+ android:paddingEnd="25dp"
+ android:includeFontPadding="false"
+ android:maxLines="1"
+ android:rotation="350"/>
+ <TextView
+ android:id="@+id/punctuation4"
+ android:textColor="?androidprv:attr/colorSurfaceVariant"
+ android:textAppearance="@*android:style/TextAppearance.DeviceDefault.ListItem"
+ android:textSize="34sp"
+ android:textStyle="bold"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_alignParentBottom="true"
+ android:layout_alignParentStart="true"
+ android:paddingStart="15dp"
+ android:includeFontPadding="false"
+ android:maxLines="1"
+ android:rotation="10"/>
+ <TextView
+ android:id="@+id/punctuation5"
+ android:textColor="?androidprv:attr/colorSurfaceVariant"
+ android:textAppearance="@*android:style/TextAppearance.DeviceDefault.ListItem"
+ android:textSize="34sp"
+ android:textStyle="bold"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_alignParentTop="true"
+ android:layout_alignParentEnd="true"
+ android:paddingTop="15dp"
+ android:includeFontPadding="false"
+ android:maxLines="1"
+ android:rotation="5"/>
+ <TextView
+ android:id="@+id/punctuation6"
+ android:textColor="?androidprv:attr/colorSurfaceVariant"
+ android:textAppearance="@*android:style/TextAppearance.DeviceDefault.ListItem"
+ android:textSize="34sp"
+ android:textStyle="bold"
+ android:paddingStart="20dp"
+ android:paddingTop="30dp"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_centerVertical="true"
+ android:layout_centerHorizontal="true"
+ android:maxLines="1"
+ android:rotation="350"/>
+</RelativeLayout> \ No newline at end of file
diff --git a/packages/SystemUI/res/layout/people_tile_punctuation_background_medium.xml b/packages/SystemUI/res/layout/people_tile_punctuation_background_medium.xml
new file mode 100644
index 000000000000..75cdde0e97e4
--- /dev/null
+++ b/packages/SystemUI/res/layout/people_tile_punctuation_background_medium.xml
@@ -0,0 +1,95 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2021 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.
+ -->
+<LinearLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"
+ android:theme="@android:style/Theme.DeviceDefault.DayNight"
+ android:id="@+id/punctuations"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:orientation="horizontal">
+ <TextView
+ android:id="@+id/punctuation1"
+ android:textColor="?androidprv:attr/colorSurfaceVariant"
+ android:textAppearance="@*android:style/TextAppearance.DeviceDefault.ListItem"
+ android:textSize="27sp"
+ android:textStyle="bold"
+ android:paddingTop="-2dp"
+ android:layout_width="0dp"
+ android:layout_height="match_parent"
+ android:layout_weight="4"
+ android:gravity="center_vertical|end"
+ android:maxLines="1"
+ android:rotation="5"/>
+ <TextView
+ android:id="@+id/punctuation2"
+ android:textColor="?androidprv:attr/colorSurfaceVariant"
+ android:textAppearance="@*android:style/TextAppearance.DeviceDefault.ListItem"
+ android:textSize="27sp"
+ android:textStyle="bold"
+ android:layout_width="0dp"
+ android:layout_height="match_parent"
+ android:layout_weight="1"
+ android:layout_gravity="top"
+ android:gravity="top|end"
+ android:includeFontPadding="false"
+ android:paddingTop="25dp"
+ android:maxLines="1"
+ android:rotation="350"/>
+ <TextView
+ android:id="@+id/punctuation3"
+ android:textColor="?androidprv:attr/colorSurfaceVariant"
+ android:textAppearance="@*android:style/TextAppearance.DeviceDefault.ListItem"
+ android:textSize="27sp"
+ android:textStyle="bold"
+ android:layout_width="0dp"
+ android:layout_height="match_parent"
+ android:layout_weight="1"
+ android:gravity="center|end"
+ android:includeFontPadding="false"
+ android:paddingTop="10dp"
+ android:maxLines="1"
+ android:rotation="350"/>
+ <TextView
+ android:id="@+id/punctuation4"
+ android:textColor="?androidprv:attr/colorSurfaceVariant"
+ android:textAppearance="@*android:style/TextAppearance.DeviceDefault.ListItem"
+ android:textSize="27sp"
+ android:textStyle="bold"
+ android:layout_width="0dp"
+ android:layout_height="match_parent"
+ android:layout_weight="1"
+ android:paddingTop="10dp"
+ android:gravity="start|top"
+ android:includeFontPadding="false"
+ android:maxLines="1"
+ android:rotation="10"/>
+ <TextView
+ android:id="@+id/punctuation5"
+ android:textColor="?androidprv:attr/colorSurfaceVariant"
+ android:textAppearance="@*android:style/TextAppearance.DeviceDefault.ListItem"
+ android:textSize="27sp"
+ android:textStyle="bold"
+ android:layout_width="0dp"
+ android:layout_height="match_parent"
+ android:layout_weight="1"
+ android:paddingTop="15dp"
+ android:gravity="start|center_vertical"
+ android:includeFontPadding="false"
+ android:maxLines="1"
+ android:rotation="350"/>
+</LinearLayout> \ No newline at end of file
diff --git a/packages/SystemUI/res/values/colors.xml b/packages/SystemUI/res/values/colors.xml
index bf13c2178d45..ee25a1059d63 100644
--- a/packages/SystemUI/res/values/colors.xml
+++ b/packages/SystemUI/res/values/colors.xml
@@ -280,8 +280,7 @@
<color name="screenrecord_status_color">#E94235</color>
- <!-- TODO(b/178093014) Colors for privacy dialog. These should be changed to the new palette -->
- <color name="privacy_circle">#1E8E3E</color> <!-- g600 -->
+ <color name="privacy_circle">#5BB974</color> <!-- g400 -->
<!-- Accessibility floating menu -->
<color name="accessibility_floating_menu_background">#CCFFFFFF</color> <!-- 80% -->
diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml
index 6c66d0748f76..13c285f12393 100644
--- a/packages/SystemUI/res/values/dimens.xml
+++ b/packages/SystemUI/res/values/dimens.xml
@@ -181,6 +181,9 @@
<!-- Radius for notifications corners without adjacent notifications -->
<dimen name="notification_corner_radius">28dp</dimen>
+ <!-- Distance over which notification corner animations run, near the shelf while scrolling. -->
+ <dimen name="notification_corner_animation_distance">48dp</dimen>
+
<!-- Radius for notifications corners with adjacent notifications -->
<dimen name="notification_corner_radius_small">4dp</dimen>
@@ -1237,7 +1240,7 @@
<!-- Icon size of Ongoing App Ops chip -->
<dimen name="ongoing_appops_chip_icon_size">16dp</dimen>
<!-- Radius of Ongoing App Ops chip corners -->
- <dimen name="ongoing_appops_chip_bg_corner_radius">16dp</dimen>
+ <dimen name="ongoing_appops_chip_bg_corner_radius">28dp</dimen>
<dimen name="ongoing_appops_dialog_side_margins">@dimen/notification_shade_content_margin_horizontal</dimen>
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index cdbfa13da127..673a03ddac4e 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -2729,6 +2729,8 @@
<string name="accessibility_floating_button_action_move_to_edge_and_hide_to_half">Move to edge and hide</string>
<!-- Action in accessibility menu to move the accessibility floating button out the edge and show. [CHAR LIMIT=36]-->
<string name="accessibility_floating_button_action_move_out_edge_and_show">Move out edge and show</string>
+ <!-- Action in accessibility menu to toggle on/off the accessibility feature. [CHAR LIMIT=30]-->
+ <string name="accessibility_floating_button_action_double_tap_to_toggle">toggle</string>
<!-- Device Controls strings -->
<!-- Device Controls empty state, title [CHAR LIMIT=30] -->
diff --git a/packages/SystemUI/res/values/styles.xml b/packages/SystemUI/res/values/styles.xml
index 2e45acc2cbfa..ac9ced93f2df 100644
--- a/packages/SystemUI/res/values/styles.xml
+++ b/packages/SystemUI/res/values/styles.xml
@@ -787,15 +787,16 @@
<style name="TextAppearance.ControlSetup">
<item name="android:fontFamily">@*android:string/config_headlineFontFamily</item>
<item name="android:textColor">@color/control_primary_text</item>
- <item name="android:singleLine">true</item>
</style>
<style name="TextAppearance.ControlSetup.Title">
<item name="android:textSize">@dimen/controls_setup_title</item>
+ <item name="android:singleLine">true</item>
</style>
<style name="TextAppearance.ControlSetup.Subtitle">
<item name="android:textSize">@dimen/controls_setup_subtitle</item>
+ <item name="android:maxLines">2</item>
</style>
<!-- The attributes used for title (textAppearanceLarge) and message (textAppearanceMedium)
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/AccessibilityFloatingMenu.java b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/AccessibilityFloatingMenu.java
index 7b4ce61d2cfe..3b3bad3b255f 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/AccessibilityFloatingMenu.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/AccessibilityFloatingMenu.java
@@ -70,9 +70,16 @@ public class AccessibilityFloatingMenu implements IAccessibilityFloatingMenu {
}
};
+ private final ContentObserver mEnabledA11yServicesContentObserver =
+ new ContentObserver(mHandler) {
+ @Override
+ public void onChange(boolean selfChange) {
+ mMenuView.onEnabledFeaturesChanged();
+ }
+ };
+
public AccessibilityFloatingMenu(Context context) {
- mContext = context;
- mMenuView = new AccessibilityFloatingMenuView(context);
+ this(context, new AccessibilityFloatingMenuView(context));
}
@VisibleForTesting
@@ -153,11 +160,17 @@ public class AccessibilityFloatingMenu implements IAccessibilityFloatingMenu {
Settings.Secure.getUriFor(Settings.Secure.ACCESSIBILITY_FLOATING_MENU_OPACITY),
/* notifyForDescendants */ false, mFadeOutContentObserver,
UserHandle.USER_CURRENT);
+ mContext.getContentResolver().registerContentObserver(
+ Settings.Secure.getUriFor(Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES),
+ /* notifyForDescendants */ false,
+ mEnabledA11yServicesContentObserver, UserHandle.USER_CURRENT);
}
private void unregisterContentObservers() {
mContext.getContentResolver().unregisterContentObserver(mContentObserver);
mContext.getContentResolver().unregisterContentObserver(mSizeContentObserver);
mContext.getContentResolver().unregisterContentObserver(mFadeOutContentObserver);
+ mContext.getContentResolver().unregisterContentObserver(
+ mEnabledA11yServicesContentObserver);
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/AccessibilityFloatingMenuView.java b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/AccessibilityFloatingMenuView.java
index 5502a20ce398..934e20dfbd05 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/AccessibilityFloatingMenuView.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/AccessibilityFloatingMenuView.java
@@ -24,6 +24,7 @@ import android.animation.AnimatorListenerAdapter;
import android.animation.ValueAnimator;
import android.annotation.IntDef;
import android.content.Context;
+import android.content.pm.ActivityInfo;
import android.content.res.Configuration;
import android.content.res.Resources;
import android.graphics.PixelFormat;
@@ -105,6 +106,7 @@ public class AccessibilityFloatingMenuView extends FrameLayout
private float mRadius;
private float mPercentageY = LOCATION_Y_PERCENTAGE;
private float mSquareScaledTouchSlop;
+ private final Configuration mLastConfiguration;
private final RecyclerView mListView;
private final AccessibilityTargetAdapter mAdapter;
private float mFadeOutValue;
@@ -202,6 +204,8 @@ public class AccessibilityFloatingMenuView extends FrameLayout
}
});
+ mLastConfiguration = new Configuration(getResources().getConfiguration());
+
updateDimensions();
initListView();
updateStrokeWith(getResources().getConfiguration().uiMode, mAlignment);
@@ -368,7 +372,7 @@ public class AccessibilityFloatingMenuView extends FrameLayout
mTargets.clear();
mTargets.addAll(newTargets);
- mAdapter.notifyDataSetChanged();
+ onEnabledFeaturesChanged();
updateRadiusWith(mSizeType, mRadiusType, mTargets.size());
updateScrollModeWith(hasExceededMaxLayoutHeight());
@@ -416,6 +420,10 @@ public class AccessibilityFloatingMenuView extends FrameLayout
setAlpha(mIsFadeEffectEnabled ? mFadeOutValue : /* completely opaque */ 1.0f);
}
+ void onEnabledFeaturesChanged() {
+ mAdapter.notifyDataSetChanged();
+ }
+
@VisibleForTesting
void fadeIn() {
if (!mIsFadeEffectEnabled) {
@@ -601,13 +609,17 @@ public class AccessibilityFloatingMenuView extends FrameLayout
params.gravity = Gravity.START | Gravity.TOP;
params.x = getMaxWindowX();
params.y = (int) (getMaxWindowY() * mPercentageY);
-
+ updateAccessibilityTitle(params);
return params;
}
@Override
protected void onConfigurationChanged(Configuration newConfig) {
super.onConfigurationChanged(newConfig);
+ final int diff = newConfig.diff(mLastConfiguration);
+ if ((diff & ActivityInfo.CONFIG_LOCALE) != 0) {
+ updateAccessibilityTitle(mCurrentLayoutParams);
+ }
updateDimensions();
updateListView();
@@ -616,6 +628,8 @@ public class AccessibilityFloatingMenuView extends FrameLayout
updateStrokeWith(newConfig.uiMode, mAlignment);
updateLocationWith(mAlignment, mPercentageY);
updateScrollModeWith(hasExceededMaxLayoutHeight());
+
+ mLastConfiguration.setTo(newConfig);
}
@VisibleForTesting
@@ -724,6 +738,11 @@ public class AccessibilityFloatingMenuView extends FrameLayout
setInset(insetLeft, insetRight);
}
+ private void updateAccessibilityTitle(WindowManager.LayoutParams params) {
+ params.accessibilityTitle = getResources().getString(
+ com.android.internal.R.string.accessibility_select_shortcut_menu_title);
+ }
+
private void setInset(int left, int right) {
final LayerDrawable layerDrawable = getMenuLayerDrawable();
if (layerDrawable.getLayerInsetLeft(INDEX_MENU_ITEM) == left
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/AccessibilityTargetAdapter.java b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/AccessibilityTargetAdapter.java
index fd0c4ef0a5be..76106e7c2cf1 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/AccessibilityTargetAdapter.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/AccessibilityTargetAdapter.java
@@ -24,9 +24,12 @@ import android.view.ViewGroup;
import androidx.annotation.IntDef;
import androidx.annotation.NonNull;
+import androidx.core.view.ViewCompat;
+import androidx.core.view.accessibility.AccessibilityNodeInfoCompat;
import androidx.recyclerview.widget.RecyclerView;
import androidx.recyclerview.widget.RecyclerView.Adapter;
+import com.android.internal.accessibility.common.ShortcutConstants.AccessibilityFragmentType;
import com.android.internal.accessibility.dialog.AccessibilityTarget;
import com.android.systemui.R;
import com.android.systemui.accessibility.floatingmenu.AccessibilityTargetAdapter.ViewHolder;
@@ -78,9 +81,20 @@ public class AccessibilityTargetAdapter extends Adapter<ViewHolder> {
@Override
public void onBindViewHolder(@NonNull ViewHolder holder, int position) {
- holder.mIconView.setBackground(mTargets.get(position).getIcon());
+ final AccessibilityTarget target = mTargets.get(position);
+ holder.mIconView.setBackground(target.getIcon());
holder.updateIconWidthHeight(mIconWidthHeight);
- holder.itemView.setOnClickListener((v) -> mTargets.get(position).onSelected());
+ holder.itemView.setOnClickListener((v) -> target.onSelected());
+ holder.itemView.setStateDescription(target.getStateDescription());
+ holder.itemView.setContentDescription(target.getLabel());
+
+ final String clickHint = target.getFragmentType() == AccessibilityFragmentType.TOGGLE
+ ? holder.itemView.getResources().getString(
+ R.string.accessibility_floating_button_action_double_tap_to_toggle)
+ : null;
+ ViewCompat.replaceAccessibilityAction(holder.itemView,
+ AccessibilityNodeInfoCompat.AccessibilityActionCompat.ACTION_CLICK,
+ clickHint, /* command= */ null);
}
@ItemType
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java
index 666afed41c35..f1431f5cd40b 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java
@@ -51,6 +51,7 @@ import com.android.internal.policy.IKeyguardExitCallback;
import com.android.internal.policy.IKeyguardService;
import com.android.internal.policy.IKeyguardStateCallback;
import com.android.systemui.SystemUIApplication;
+import com.android.wm.shell.transition.Transitions;
import javax.inject.Inject;
@@ -62,16 +63,29 @@ public class KeyguardService extends Service {
* Run Keyguard animation as remote animation in System UI instead of local animation in
* the server process.
*
+ * 0: Runs all keyguard animation as local animation
+ * 1: Only runs keyguard going away animation as remote animation
+ * 2: Runs all keyguard animation as remote animation
+ *
* Note: Must be consistent with WindowManagerService.
*/
private static final String ENABLE_REMOTE_KEYGUARD_ANIMATION_PROPERTY =
"persist.wm.enable_remote_keyguard_animation";
+ private static final int sEnableRemoteKeyguardAnimation =
+ SystemProperties.getInt(ENABLE_REMOTE_KEYGUARD_ANIMATION_PROPERTY, 1);
+
/**
* @see #ENABLE_REMOTE_KEYGUARD_ANIMATION_PROPERTY
*/
- static boolean sEnableRemoteKeyguardAnimation =
- SystemProperties.getBoolean(ENABLE_REMOTE_KEYGUARD_ANIMATION_PROPERTY, false);
+ public static boolean sEnableRemoteKeyguardGoingAwayAnimation =
+ !Transitions.ENABLE_SHELL_TRANSITIONS && sEnableRemoteKeyguardAnimation >= 1;
+
+ /**
+ * @see #ENABLE_REMOTE_KEYGUARD_ANIMATION_PROPERTY
+ */
+ public static boolean sEnableRemoteKeyguardOccludeAnimation =
+ !Transitions.ENABLE_SHELL_TRANSITIONS && sEnableRemoteKeyguardAnimation >= 2;
private final KeyguardViewMediator mKeyguardViewMediator;
private final KeyguardLifecyclesDispatcher mKeyguardLifecyclesDispatcher;
@@ -83,20 +97,22 @@ public class KeyguardService extends Service {
mKeyguardViewMediator = keyguardViewMediator;
mKeyguardLifecyclesDispatcher = keyguardLifecyclesDispatcher;
- if (sEnableRemoteKeyguardAnimation) {
- RemoteAnimationDefinition definition = new RemoteAnimationDefinition();
+ RemoteAnimationDefinition definition = new RemoteAnimationDefinition();
+ if (sEnableRemoteKeyguardGoingAwayAnimation) {
final RemoteAnimationAdapter exitAnimationAdapter =
new RemoteAnimationAdapter(mExitAnimationRunner, 0, 0);
definition.addRemoteAnimation(TRANSIT_OLD_KEYGUARD_GOING_AWAY, exitAnimationAdapter);
definition.addRemoteAnimation(TRANSIT_OLD_KEYGUARD_GOING_AWAY_ON_WALLPAPER,
exitAnimationAdapter);
+ }
+ if (sEnableRemoteKeyguardOccludeAnimation) {
final RemoteAnimationAdapter occludeAnimationAdapter =
new RemoteAnimationAdapter(mOccludeAnimationRunner, 0, 0);
definition.addRemoteAnimation(TRANSIT_OLD_KEYGUARD_OCCLUDE, occludeAnimationAdapter);
definition.addRemoteAnimation(TRANSIT_OLD_KEYGUARD_UNOCCLUDE, occludeAnimationAdapter);
- ActivityTaskManager.getInstance().registerRemoteAnimationsForDisplay(
- DEFAULT_DISPLAY, definition);
}
+ ActivityTaskManager.getInstance().registerRemoteAnimationsForDisplay(
+ DEFAULT_DISPLAY, definition);
}
@Override
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardUnlockAnimationController.kt b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardUnlockAnimationController.kt
index 411c328cd310..85ee0dca8805 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardUnlockAnimationController.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardUnlockAnimationController.kt
@@ -280,7 +280,7 @@ class KeyguardUnlockAnimationController @Inject constructor(
}
override fun onKeyguardDismissAmountChanged() {
- if (!KeyguardService.sEnableRemoteKeyguardAnimation) {
+ if (!KeyguardService.sEnableRemoteKeyguardGoingAwayAnimation) {
return
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
index 48f9a58d7d1a..b7da7addf027 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
@@ -2100,7 +2100,7 @@ public class KeyguardViewMediator extends SystemUI implements Dumpable,
playSounds(false);
}
- if (KeyguardService.sEnableRemoteKeyguardAnimation) {
+ if (KeyguardService.sEnableRemoteKeyguardGoingAwayAnimation) {
mSurfaceBehindRemoteAnimationFinishedCallback = finishedCallback;
mSurfaceBehindRemoteAnimationRunning = true;
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java
index 56375adb3d4e..4e41d75e3f43 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java
@@ -101,6 +101,7 @@ import android.view.WindowManager;
import android.view.accessibility.AccessibilityEvent;
import android.view.accessibility.AccessibilityManager;
import android.view.accessibility.AccessibilityManager.AccessibilityServicesStateChangeListener;
+import android.view.inputmethod.InputMethodManager;
import androidx.annotation.VisibleForTesting;
@@ -1175,6 +1176,9 @@ public class NavigationBar implements View.OnAttachStateChangeListener,
accessibilityButton.setOnLongClickListener(this::onAccessibilityLongClick);
updateAccessibilityServicesState(mAccessibilityManager);
+ ButtonDispatcher imeSwitcherButton = mNavigationBarView.getImeSwitchButton();
+ imeSwitcherButton.setOnClickListener(this::onImeSwitcherClick);
+
updateScreenPinningGestures();
}
@@ -1274,6 +1278,11 @@ public class NavigationBar implements View.OnAttachStateChangeListener,
mCommandQueue.toggleRecentApps();
}
+ private void onImeSwitcherClick(View v) {
+ mContext.getSystemService(InputMethodManager.class).showInputMethodPickerFromSystem(
+ true /* showAuxiliarySubtypes */, mDisplayId);
+ };
+
private boolean onLongPressBackHome(View v) {
return onLongPressNavigationButtons(v, R.id.back, R.id.home);
}
@@ -1282,7 +1291,6 @@ public class NavigationBar implements View.OnAttachStateChangeListener,
return onLongPressNavigationButtons(v, R.id.back, R.id.recent_apps);
}
-
/**
* This handles long-press of both back and recents/home. Back is the common button with
* combination of recents if it is visible or home if recents is invisible.
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarController.java b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarController.java
index 3544f60601a8..66cfae4315d8 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarController.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarController.java
@@ -217,6 +217,9 @@ public class NavigationBarController implements Callbacks,
@Override
public void onNavigationModeChanged(int mode) {
+ if (mNavMode == mode) {
+ return;
+ }
final int oldMode = mNavMode;
mNavMode = mode;
mHandler.post(() -> {
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarInflaterView.java b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarInflaterView.java
index 7342f91a8108..4d9175b8db68 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarInflaterView.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarInflaterView.java
@@ -158,7 +158,6 @@ public class NavigationBarInflaterView extends FrameLayout
}
public void onLikelyDefaultLayoutChange() {
-
// Reevaluate new layout
final String newValue = getDefaultLayout();
if (!Objects.equals(mCurrentLayout, newValue)) {
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarView.java b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarView.java
index 0ed4d861c712..bdd273515619 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarView.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarView.java
@@ -166,6 +166,7 @@ public class NavigationBarView extends FrameLayout implements
private NavigationBarInflaterView mNavigationInflaterView;
private RecentsOnboarding mRecentsOnboarding;
private NotificationPanelViewController mPanelView;
+ private RotationContextButton mRotationContextButton;
private FloatingRotationButton mFloatingRotationButton;
private RotationButtonController mRotationButtonController;
private NavigationBarOverlayController mNavBarOverlayController;
@@ -233,14 +234,6 @@ public class NavigationBarView extends FrameLayout implements
}
}
- private final OnClickListener mImeSwitcherClickListener = new OnClickListener() {
- @Override
- public void onClick(View view) {
- mContext.getSystemService(InputMethodManager.class).showInputMethodPickerFromSystem(
- true /* showAuxiliarySubtypes */, getContext().getDisplayId());
- }
- };
-
private final AccessibilityDelegate mQuickStepAccessibilityDelegate =
new AccessibilityDelegate() {
private AccessibilityAction mToggleOverviewAction;
@@ -311,32 +304,26 @@ public class NavigationBarView extends FrameLayout implements
mIsVertical = false;
mLongClickableAccessibilityButton = false;
mNavBarMode = Dependency.get(NavigationModeController.class).addListener(this);
- boolean isGesturalMode = isGesturalMode(mNavBarMode);
mSysUiFlagContainer = Dependency.get(SysUiState.class);
// Set up the context group of buttons
mContextualButtonGroup = new ContextualButtonGroup(R.id.menu_container);
final ContextualButton imeSwitcherButton = new ContextualButton(R.id.ime_switcher,
mLightContext, R.drawable.ic_ime_switcher_default);
- final RotationContextButton rotateSuggestionButton = new RotationContextButton(
- R.id.rotate_suggestion, mLightContext,
- R.drawable.ic_sysbar_rotate_button_ccw_start_0);
final ContextualButton accessibilityButton =
new ContextualButton(R.id.accessibility_button, mLightContext,
R.drawable.ic_sysbar_accessibility_button);
mContextualButtonGroup.addButton(imeSwitcherButton);
- if (!isGesturalMode) {
- mContextualButtonGroup.addButton(rotateSuggestionButton);
- }
mContextualButtonGroup.addButton(accessibilityButton);
mOverviewProxyService = Dependency.get(OverviewProxyService.class);
+ mRotationContextButton = new RotationContextButton(R.id.rotate_suggestion,
+ mLightContext, R.drawable.ic_sysbar_rotate_button_ccw_start_0);
mFloatingRotationButton = new FloatingRotationButton(context);
mRecentsOnboarding = new RecentsOnboarding(context, mOverviewProxyService);
mRotationButtonController = new RotationButtonController(mLightContext,
- mLightIconColor, mDarkIconColor,
- isGesturalMode ? mFloatingRotationButton : rotateSuggestionButton,
- mRotationButtonListener);
+ mLightIconColor, mDarkIconColor);
+ updateRotationButton();
mNavBarOverlayController = Dependency.get(NavigationBarOverlayController.class);
if (mNavBarOverlayController.isNavigationBarOverlayEnabled()) {
@@ -357,7 +344,6 @@ public class NavigationBarView extends FrameLayout implements
mButtonDispatchers.put(R.id.recent_apps, new ButtonDispatcher(R.id.recent_apps));
mButtonDispatchers.put(R.id.ime_switcher, imeSwitcherButton);
mButtonDispatchers.put(R.id.accessibility_button, accessibilityButton);
- mButtonDispatchers.put(R.id.rotate_suggestion, rotateSuggestionButton);
mButtonDispatchers.put(R.id.menu_container, mContextualButtonGroup);
mDeadZone = new DeadZone(this);
@@ -555,6 +541,23 @@ public class NavigationBarView extends FrameLayout implements
}
}
+ /**
+ * Updates the rotation button based on the current navigation mode.
+ */
+ private void updateRotationButton() {
+ if (isGesturalMode(mNavBarMode)) {
+ mContextualButtonGroup.removeButton(R.id.rotate_suggestion);
+ mButtonDispatchers.remove(R.id.rotate_suggestion);
+ mRotationButtonController.setRotationButton(mFloatingRotationButton,
+ mRotationButtonListener);
+ } else if (mContextualButtonGroup.getContextButton(R.id.rotate_suggestion) == null) {
+ mContextualButtonGroup.addButton(mRotationContextButton);
+ mButtonDispatchers.put(R.id.rotate_suggestion, mRotationContextButton);
+ mRotationButtonController.setRotationButton(mRotationContextButton,
+ mRotationButtonListener);
+ }
+ }
+
public KeyButtonDrawable getBackDrawable() {
KeyButtonDrawable drawable = getDrawable(getBackDrawableRes());
orientBackButton(drawable);
@@ -908,6 +911,7 @@ public class NavigationBarView extends FrameLayout implements
mBarTransitions.onNavigationModeChanged(mNavBarMode);
mEdgeBackGestureHandler.onNavigationModeChanged(mNavBarMode);
mRecentsOnboarding.onNavigationModeChanged(mNavBarMode);
+ updateRotationButton();
if (isGesturalMode(mNavBarMode)) {
mRegionSamplingHelper.start(mSamplingBounds);
@@ -932,7 +936,6 @@ public class NavigationBarView extends FrameLayout implements
mNavigationInflaterView = findViewById(R.id.navigation_inflater);
mNavigationInflaterView.setButtonDispatchers(mButtonDispatchers);
- getImeSwitchButton().setOnClickListener(mImeSwitcherClickListener);
updateOrientationViews();
reloadNavIcons();
}
@@ -1027,6 +1030,9 @@ public class NavigationBarView extends FrameLayout implements
private void updateButtonLocation(ButtonDispatcher button, boolean inScreenSpace,
boolean useNearestRegion) {
+ if (button == null) {
+ return;
+ }
View view = button.getCurrentView();
if (view == null || !button.isVisible()) {
return;
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/RotationButtonController.java b/packages/SystemUI/src/com/android/systemui/navigationbar/RotationButtonController.java
index 4bcb0193c7d0..ddf089bac25e 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/RotationButtonController.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/RotationButtonController.java
@@ -66,10 +66,10 @@ public class RotationButtonController {
private static final int NUM_ACCEPTED_ROTATION_SUGGESTIONS_FOR_INTRODUCTION = 3;
private final Context mContext;
- private final RotationButton mRotationButton;
private final Handler mMainThreadHandler = new Handler(Looper.getMainLooper());
private final UiEventLogger mUiEventLogger = new UiEventLoggerImpl();
private final ViewRippler mViewRippler = new ViewRippler();
+ private RotationButton mRotationButton;
private int mLastRotationSuggestion;
private boolean mPendingRotationSuggestion;
@@ -125,20 +125,21 @@ public class RotationButtonController {
}
RotationButtonController(Context context, @ColorInt int lightIconColor,
- @ColorInt int darkIconColor, RotationButton rotationButton,
- Consumer<Boolean> visibilityChangedCallback) {
+ @ColorInt int darkIconColor) {
mContext = context;
mLightIconColor = lightIconColor;
mDarkIconColor = darkIconColor;
- mRotationButton = rotationButton;
- mRotationButton.setRotationButtonController(this);
mIsNavigationBarShowing = true;
mRotationLockController = Dependency.get(RotationLockController.class);
mAccessibilityManagerWrapper = Dependency.get(AccessibilityManagerWrapper.class);
-
- // Register the task stack listener
mTaskStackListener = new TaskStackListenerImpl();
+ }
+
+ void setRotationButton(RotationButton rotationButton,
+ Consumer<Boolean> visibilityChangedCallback) {
+ mRotationButton = rotationButton;
+ mRotationButton.setRotationButtonController(this);
mRotationButton.setOnClickListener(this::onRotateSuggestionClick);
mRotationButton.setOnHoverListener(this::onRotateSuggestionHover);
mRotationButton.setVisibilityChangedCallback(visibilityChangedCallback);
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/buttons/ContextualButtonGroup.java b/packages/SystemUI/src/com/android/systemui/navigationbar/buttons/ContextualButtonGroup.java
index 50b638bcc903..2ace303986fe 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/buttons/ContextualButtonGroup.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/buttons/ContextualButtonGroup.java
@@ -41,10 +41,23 @@ public class ContextualButtonGroup extends ButtonDispatcher {
* @param button the button added to the group
*/
public void addButton(@NonNull ContextualButton button) {
+ // By default buttons in the context group are not visible until
+ // {@link #setButtonVisibility()) is called to show one of the buttons
+ button.setVisibility(View.INVISIBLE);
button.attachToGroup(this);
mButtonData.add(new ButtonData(button));
}
+ /**
+ * Removes a contextual button from the group.
+ */
+ public void removeButton(@IdRes int buttonResId) {
+ int index = getContextButtonIndex(buttonResId);
+ if (index != INVALID_INDEX) {
+ mButtonData.remove(index);
+ }
+ }
+
public ContextualButton getContextButton(@IdRes int buttonResId) {
int index = getContextButtonIndex(buttonResId);
if (index != INVALID_INDEX) {
diff --git a/packages/SystemUI/src/com/android/systemui/people/PeopleTileViewHelper.java b/packages/SystemUI/src/com/android/systemui/people/PeopleTileViewHelper.java
index d4ddc6546a19..a23db63a70fc 100644
--- a/packages/SystemUI/src/com/android/systemui/people/PeopleTileViewHelper.java
+++ b/packages/SystemUI/src/com/android/systemui/people/PeopleTileViewHelper.java
@@ -51,6 +51,7 @@ import android.os.Bundle;
import android.text.TextUtils;
import android.util.IconDrawableFactory;
import android.util.Log;
+import android.util.Pair;
import android.view.View;
import android.widget.RemoteViews;
import android.widget.TextView;
@@ -63,6 +64,7 @@ import com.android.systemui.people.widget.PeopleTileKey;
import java.text.NumberFormat;
import java.time.Duration;
+import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Locale;
@@ -102,6 +104,39 @@ public class PeopleTileViewHelper {
private static final Pattern ANY_DOUBLE_MARK_PATTERN = Pattern.compile("[!?][!?]+");
private static final Pattern MIXED_MARK_PATTERN = Pattern.compile("![?].*|.*[?]!");
+ // This regex can be used to match Unicode emoji characters and character sequences. It's from
+ // the official Unicode site (https://unicode.org/reports/tr51/#EBNF_and_Regex) with minor
+ // changes to fit our needs. It should be updated once new emoji categories are added.
+ //
+ // Emoji categories that can be matched by this regex:
+ // - Country flags. "\p{RI}\p{RI}" matches country flags since they always consist of 2 Unicode
+ // scalars.
+ // - Single-Character Emoji. "\p{Emoji}" matches Single-Character Emojis.
+ // - Emoji with modifiers. E.g. Emojis with different skin tones. "\p{Emoji}\p{EMod}" matches
+ // them.
+ // - Emoji Presentation. Those are characters which can normally be drawn as either text or as
+ // Emoji. "\p{Emoji}\x{FE0F}" matches them.
+ // - Emoji Keycap. E.g. Emojis for number 0 to 9. "\p{Emoji}\x{FE0F}\x{20E3}" matches them.
+ // - Emoji tag sequence. "\p{Emoji}[\x{E0020}-\x{E007E}]+\x{E007F}" matches them.
+ // - Emoji Zero-Width Joiner (ZWJ) Sequence. A ZWJ emoji is actually multiple emojis joined by
+ // the jointer "0x200D".
+ //
+ // Note: since "\p{Emoji}" also matches some ASCII characters like digits 0-9, we use
+ // "\p{Emoji}&&\p{So}" to exclude them. This is the change we made from the official emoji
+ // regex.
+ private static final String UNICODE_EMOJI_REGEX =
+ "\\p{RI}\\p{RI}|"
+ + "("
+ + "\\p{Emoji}(\\p{EMod}|\\x{FE0F}\\x{20E3}?|[\\x{E0020}-\\x{E007E}]+\\x{E007F})"
+ + "|[\\p{Emoji}&&\\p{So}]"
+ + ")"
+ + "("
+ + "\\x{200D}"
+ + "\\p{Emoji}(\\p{EMod}|\\x{FE0F}\\x{20E3}?|[\\x{E0020}-\\x{E007E}]+\\x{E007F})"
+ + "?)*";
+
+ private static final Pattern EMOJI_PATTERN = Pattern.compile(UNICODE_EMOJI_REGEX);
+
public static final String EMPTY_STRING = "";
private int mMediumVerticalPadding;
@@ -375,7 +410,7 @@ public class PeopleTileViewHelper {
} else {
setMaxLines(views, !TextUtils.isEmpty(sender));
CharSequence content = mTile.getNotificationContent();
- views = setPunctuationRemoteViewsFields(views, content);
+ views = decorateBackground(views, content);
views.setColorAttr(R.id.text_content, "setTextColor", android.R.attr.textColorPrimary);
views.setTextViewText(R.id.text_content, mTile.getNotificationContent());
views.setViewVisibility(R.id.image, View.GONE);
@@ -506,33 +541,53 @@ public class PeopleTileViewHelper {
}
}
- private RemoteViews setPunctuationRemoteViewsFields(
- RemoteViews views, CharSequence content) {
- String punctuation = getBackgroundTextFromMessage(content.toString());
+ private RemoteViews decorateBackground(RemoteViews views, CharSequence content) {
int visibility = View.GONE;
- if (punctuation != null) {
- visibility = View.VISIBLE;
- }
- views.setTextViewText(R.id.punctuation1, punctuation);
- views.setTextViewText(R.id.punctuation2, punctuation);
- views.setTextViewText(R.id.punctuation3, punctuation);
- views.setTextViewText(R.id.punctuation4, punctuation);
- views.setTextViewText(R.id.punctuation5, punctuation);
- views.setTextViewText(R.id.punctuation6, punctuation);
-
- views.setViewVisibility(R.id.punctuation1, visibility);
- views.setViewVisibility(R.id.punctuation2, visibility);
- views.setViewVisibility(R.id.punctuation3, visibility);
- views.setViewVisibility(R.id.punctuation4, visibility);
- views.setViewVisibility(R.id.punctuation5, visibility);
- views.setViewVisibility(R.id.punctuation6, visibility);
+ CharSequence emoji = getDoubleEmoji(content);
+ if (!TextUtils.isEmpty(emoji)) {
+ setEmojiBackground(views, emoji);
+ setPunctuationBackground(views, null);
+ return views;
+ }
+
+ CharSequence punctuation = getDoublePunctuation(content);
+ setEmojiBackground(views, null);
+ setPunctuationBackground(views, punctuation);
+ return views;
+ }
+
+ private RemoteViews setEmojiBackground(RemoteViews views, CharSequence content) {
+ if (TextUtils.isEmpty(content)) {
+ views.setViewVisibility(R.id.emojis, View.GONE);
+ return views;
+ }
+ views.setTextViewText(R.id.emoji1, content);
+ views.setTextViewText(R.id.emoji2, content);
+ views.setTextViewText(R.id.emoji3, content);
+
+ views.setViewVisibility(R.id.emojis, View.VISIBLE);
+ return views;
+ }
+
+ private RemoteViews setPunctuationBackground(RemoteViews views, CharSequence content) {
+ if (TextUtils.isEmpty(content)) {
+ views.setViewVisibility(R.id.punctuations, View.GONE);
+ return views;
+ }
+ views.setTextViewText(R.id.punctuation1, content);
+ views.setTextViewText(R.id.punctuation2, content);
+ views.setTextViewText(R.id.punctuation3, content);
+ views.setTextViewText(R.id.punctuation4, content);
+ views.setTextViewText(R.id.punctuation5, content);
+ views.setTextViewText(R.id.punctuation6, content);
+ views.setViewVisibility(R.id.punctuations, View.VISIBLE);
return views;
}
- /** Gets character for mTile background decoration based on notification content. */
+ /** Returns punctuation character(s) if {@code message} has double punctuation ("!" or "?"). */
@VisibleForTesting
- String getBackgroundTextFromMessage(String message) {
+ CharSequence getDoublePunctuation(CharSequence message) {
if (!ANY_DOUBLE_MARK_PATTERN.matcher(message).find()) {
return null;
}
@@ -554,6 +609,48 @@ public class PeopleTileViewHelper {
return "!";
}
+ /** Returns emoji if {@code message} has two of the same emoji in sequence. */
+ @VisibleForTesting
+ CharSequence getDoubleEmoji(CharSequence message) {
+ Matcher unicodeEmojiMatcher = EMOJI_PATTERN.matcher(message);
+ // Stores the start and end indices of each matched emoji.
+ List<Pair<Integer, Integer>> emojiIndices = new ArrayList<>();
+ // Stores each emoji text.
+ List<CharSequence> emojiTexts = new ArrayList<>();
+
+ // Scan message for emojis
+ while (unicodeEmojiMatcher.find()) {
+ int start = unicodeEmojiMatcher.start();
+ int end = unicodeEmojiMatcher.end();
+ emojiIndices.add(new Pair(start, end));
+ emojiTexts.add(message.subSequence(start, end));
+ }
+
+ if (DEBUG) Log.d(TAG, "Number of emojis in the message: " + emojiIndices.size());
+ if (emojiIndices.size() < 2) {
+ return null;
+ }
+
+ for (int i = 1; i < emojiIndices.size(); ++i) {
+ Pair<Integer, Integer> second = emojiIndices.get(i);
+ Pair<Integer, Integer> first = emojiIndices.get(i - 1);
+
+ // Check if second emoji starts right after first starts
+ if (second.first == first.second) {
+ // Check if emojis in sequence are the same
+ if (Objects.equals(emojiTexts.get(i), emojiTexts.get(i - 1))) {
+ if (DEBUG) {
+ Log.d(TAG, "Two of the same emojis in sequence: " + emojiTexts.get(i));
+ }
+ return emojiTexts.get(i);
+ }
+ }
+ }
+
+ // No equal emojis in sequence.
+ return null;
+ }
+
private RemoteViews getViewForContentLayout() {
RemoteViews views = new RemoteViews(mContext.getPackageName(),
getLayoutForContent());
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/UserDetailView.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/UserDetailView.java
index 2458223310cf..a9723341e787 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/UserDetailView.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/UserDetailView.java
@@ -95,7 +95,7 @@ public class UserDetailView extends PseudoGridView {
public UserDetailItemView createUserDetailItemView(View convertView, ViewGroup parent,
UserSwitcherController.UserRecord item) {
UserDetailItemView v = UserDetailItemView.convertOrInflate(
- mContext, convertView, parent);
+ parent.getContext(), convertView, parent);
if (!item.isCurrent || item.isGuest) {
v.setOnClickListener(this);
} else {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationListener.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationListener.java
index 7f31fddbfb6c..5437ce63475e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationListener.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationListener.java
@@ -18,7 +18,6 @@ package com.android.systemui.statusbar;
import static com.android.systemui.statusbar.RemoteInputController.processForRemoteInput;
import static com.android.systemui.statusbar.notification.NotificationEntryManager.UNDEFINED_DISMISS_REASON;
-import static com.android.systemui.statusbar.phone.StatusBar.DEBUG;
import android.annotation.NonNull;
import android.annotation.SuppressLint;
@@ -35,6 +34,7 @@ import android.util.Log;
import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.statusbar.dagger.StatusBarModule;
import com.android.systemui.statusbar.phone.NotificationListenerWithPlugins;
+import com.android.systemui.statusbar.phone.StatusBar;
import java.util.ArrayList;
import java.util.List;
@@ -46,6 +46,7 @@ import java.util.List;
@SuppressLint("OverrideAbstract")
public class NotificationListener extends NotificationListenerWithPlugins {
private static final String TAG = "NotificationListener";
+ private static final boolean DEBUG = StatusBar.DEBUG;
private final Context mContext;
private final NotificationManager mNotificationManager;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java
index fb109f310e15..8f462fea0468 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java
@@ -84,7 +84,7 @@ public class NotificationShelf extends ActivatableNotificationView implements
private int mCutoutHeight;
private int mGapHeight;
private int mIndexOfFirstViewInShelf = -1;
-
+ private float mCornerAnimationDistance;
private NotificationShelfController mController;
public NotificationShelf(Context context, AttributeSet attrs) {
@@ -104,6 +104,7 @@ public class NotificationShelf extends ActivatableNotificationView implements
setClipToPadding(false);
mShelfIcons.setIsStaticLayout(false);
setBottomRoundness(1.0f, false /* animate */);
+ setTopRoundness(1f, false /* animate */);
// Setting this to first in section to get the clipping to the top roundness correct. This
// value determines the way we are clipping to the top roundness of the overall shade
@@ -134,6 +135,8 @@ public class NotificationShelf extends ActivatableNotificationView implements
mIconSize = res.getDimensionPixelSize(com.android.internal.R.dimen.status_bar_icon_size);
mHiddenShelfIconSize = res.getDimensionPixelOffset(R.dimen.hidden_shelf_icon_size);
mGapHeight = res.getDimensionPixelSize(R.dimen.qs_notification_padding);
+ mCornerAnimationDistance = res.getDimensionPixelSize(
+ R.dimen.notification_corner_animation_distance);
mShelfIcons.setInNotificationIconShelf(true);
if (!mShowNotificationShelf) {
@@ -256,7 +259,7 @@ public class NotificationShelf extends ActivatableNotificationView implements
boolean aboveShelf = ViewState.getFinalTranslationZ(child) > baseZHeight
|| child.isPinned();
boolean isLastChild = child == lastChild;
- float rowTranslationY = child.getTranslationY();
+ final float viewStart = child.getTranslationY();
final float inShelfAmount = updateShelfTransformation(i, child, scrollingFast,
expandingAnimated, isLastChild);
@@ -278,7 +281,7 @@ public class NotificationShelf extends ActivatableNotificationView implements
ExpandableNotificationRow expandableRow = (ExpandableNotificationRow) child;
numViewsInShelf += inShelfAmount;
int ownColorUntinted = expandableRow.getBackgroundColorWithoutTint();
- if (rowTranslationY >= shelfStart && mNotGoneIndex == -1) {
+ if (viewStart >= shelfStart && mNotGoneIndex == -1) {
mNotGoneIndex = notGoneIndex;
setTintColor(previousColor);
setOverrideTintColor(colorTwoBefore, transitionAmount);
@@ -317,26 +320,44 @@ public class NotificationShelf extends ActivatableNotificationView implements
notGoneIndex++;
}
+ final float viewEnd = viewStart + child.getActualHeight();
+ final float cornerAnimationDistance = mCornerAnimationDistance
+ * mAmbientState.getExpansionFraction();
+ final float cornerAnimationTop = shelfStart - cornerAnimationDistance;
+
if (child instanceof ActivatableNotificationView) {
ActivatableNotificationView anv =
(ActivatableNotificationView) child;
- if (anv.isFirstInSection() && previousAnv != null
- && previousAnv.isLastInSection()) {
- // If the top of the shelf is between the view before a gap and the view after a
- // gap then we need to adjust the shelf's top roundness.
- float distanceToGapBottom = child.getTranslationY() - getTranslationY();
- float distanceToGapTop = getTranslationY()
- - (previousAnv.getTranslationY() + previousAnv.getActualHeight());
- if (distanceToGapTop > 0) {
- // We interpolate our top roundness so that it's fully rounded if we're at
- // the bottom of the gap, and not rounded at all if we're at the top of the
- // gap (directly up against the bottom of previousAnv)
- // Then we apply the same roundness to the bottom of previousAnv so that the
- // corners join together as the shelf approaches previousAnv.
- firstElementRoundness = (float) Math.min(1.0,
- distanceToGapTop / mGapHeight);
- previousAnv.setBottomRoundness(firstElementRoundness,
- false /* don't animate */);
+
+ if (viewStart < shelfStart
+ && !mHostLayoutController.isViewAffectedBySwipe(anv)
+ && !mAmbientState.isPulsing()
+ && !mAmbientState.isDozing()) {
+
+ if (viewEnd >= cornerAnimationTop) {
+ // Round bottom corners within animation bounds
+ final float changeFraction = MathUtils.saturate(
+ (viewEnd - cornerAnimationTop) / cornerAnimationDistance);
+ final float roundness = anv.isLastInSection() ? 1f : changeFraction * 1f;
+ anv.setBottomRoundness(roundness, false);
+
+ } else if (viewEnd < cornerAnimationTop) {
+ // Fast scroll skips frames and leaves corners with unfinished rounding.
+ // Reset top and bottom corners outside of animation bounds.
+ anv.setBottomRoundness(anv.isLastInSection() ? 1f : 0f, false);
+ }
+
+ if (viewStart >= cornerAnimationTop) {
+ // Round top corners within animation bounds
+ final float changeFraction = MathUtils.saturate(
+ (viewStart - cornerAnimationTop) / cornerAnimationDistance);
+ final float roundness = anv.isFirstInSection() ? 1f : changeFraction * 1f;
+ anv.setTopRoundness(roundness, false);
+
+ } else if (viewStart < cornerAnimationTop) {
+ // Fast scroll skips frames and leaves corners with unfinished rounding.
+ // Reset top and bottom corners outside of animation bounds.
+ anv.setTopRoundness(anv.isFirstInSection() ? 1f : 0f, false);
}
}
previousAnv = anv;
@@ -394,7 +415,6 @@ public class NotificationShelf extends ActivatableNotificationView implements
private void setFirstElementRoundness(float firstElementRoundness) {
if (mFirstElementRoundness != firstElementRoundness) {
mFirstElementRoundness = firstElementRoundness;
- setTopRoundness(firstElementRoundness, false /* animate */);
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationLaunchAnimatorController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationLaunchAnimatorController.kt
index c85b62fc0d9a..81942209a055 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationLaunchAnimatorController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationLaunchAnimatorController.kt
@@ -6,6 +6,7 @@ import com.android.systemui.animation.ActivityLaunchAnimator
import com.android.systemui.statusbar.NotificationShadeDepthController
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow
import com.android.systemui.statusbar.notification.stack.NotificationListContainer
+import com.android.systemui.statusbar.phone.HeadsUpManagerPhone
import com.android.systemui.statusbar.phone.NotificationShadeWindowViewController
import kotlin.math.ceil
import kotlin.math.max
@@ -14,7 +15,8 @@ import kotlin.math.max
class NotificationLaunchAnimatorControllerProvider(
private val notificationShadeWindowViewController: NotificationShadeWindowViewController,
private val notificationListContainer: NotificationListContainer,
- private val depthController: NotificationShadeDepthController
+ private val depthController: NotificationShadeDepthController,
+ private val headsUpManager: HeadsUpManagerPhone
) {
fun getAnimatorController(
notification: ExpandableNotificationRow
@@ -23,7 +25,8 @@ class NotificationLaunchAnimatorControllerProvider(
notificationShadeWindowViewController,
notificationListContainer,
depthController,
- notification
+ notification,
+ headsUpManager
)
}
}
@@ -37,8 +40,11 @@ class NotificationLaunchAnimatorController(
private val notificationShadeWindowViewController: NotificationShadeWindowViewController,
private val notificationListContainer: NotificationListContainer,
private val depthController: NotificationShadeDepthController,
- private val notification: ExpandableNotificationRow
+ private val notification: ExpandableNotificationRow,
+ private val headsUpManager: HeadsUpManagerPhone
) : ActivityLaunchAnimator.Controller {
+ private val notificationKey = notification.entry.sbn.key
+
override fun getRootView(): View = notification.rootView
override fun createAnimatorState(): ActivityLaunchAnimator.State {
@@ -76,12 +82,25 @@ class NotificationLaunchAnimatorController(
override fun onIntentStarted(willAnimate: Boolean) {
notificationShadeWindowViewController.setExpandAnimationRunning(willAnimate)
+
+ if (!willAnimate) {
+ removeHun(animate = true)
+ }
+ }
+
+ private fun removeHun(animate: Boolean) {
+ if (!headsUpManager.isAlerting(notificationKey)) {
+ return
+ }
+
+ headsUpManager.removeNotification(notificationKey, true /* releaseImmediately */, animate)
}
override fun onLaunchAnimationCancelled() {
// TODO(b/184121838): Should we call InteractionJankMonitor.cancel if the animation started
// here?
notificationShadeWindowViewController.setExpandAnimationRunning(false)
+ removeHun(animate = true)
}
override fun onLaunchAnimationStart(isExpandingFullyAbove: Boolean) {
@@ -99,6 +118,7 @@ class NotificationLaunchAnimatorController(
notificationShadeWindowViewController.setExpandAnimationRunning(false)
notificationListContainer.setExpandingNotification(null)
applyParams(null)
+ removeHun(animate = false)
}
private fun applyParams(params: ExpandAnimationParameters?) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/legacy/NotificationGroupManagerLegacy.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/legacy/NotificationGroupManagerLegacy.java
index d6356de5ea51..d95c265c1460 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/legacy/NotificationGroupManagerLegacy.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/legacy/NotificationGroupManagerLegacy.java
@@ -16,7 +16,9 @@
package com.android.systemui.statusbar.notification.collection.legacy;
+import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.app.Notification;
import android.service.notification.StatusBarNotification;
import android.util.ArraySet;
import android.util.Log;
@@ -31,6 +33,7 @@ import com.android.systemui.statusbar.notification.collection.NotificationEntry;
import com.android.systemui.statusbar.notification.collection.render.GroupExpansionManager;
import com.android.systemui.statusbar.notification.collection.render.GroupMembershipManager;
import com.android.systemui.statusbar.notification.people.PeopleNotificationIdentifier;
+import com.android.systemui.statusbar.phone.StatusBar;
import com.android.systemui.statusbar.policy.HeadsUpManager;
import com.android.systemui.statusbar.policy.OnHeadsUpChangedListener;
import com.android.wm.shell.bubbles.Bubbles;
@@ -39,10 +42,12 @@ import java.io.FileDescriptor;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.HashMap;
+import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
+import java.util.TreeSet;
import javax.inject.Inject;
@@ -58,13 +63,21 @@ import dagger.Lazy;
public class NotificationGroupManagerLegacy implements OnHeadsUpChangedListener, StateListener,
GroupMembershipManager, GroupExpansionManager, Dumpable {
- private static final String TAG = "NotificationGroupManager";
+ private static final String TAG = "NotifGroupManager";
+ private static final boolean DEBUG = StatusBar.DEBUG;
+ private static final boolean SPEW = StatusBar.SPEW;
+ /**
+ * The maximum amount of time (in ms) between the posting of notifications that can be
+ * considered part of the same update batch.
+ */
+ private static final long POST_BATCH_MAX_AGE = 5000;
private final HashMap<String, NotificationGroup> mGroupMap = new HashMap<>();
private final ArraySet<OnGroupExpansionChangeListener> mExpansionChangeListeners =
new ArraySet<>();
private final ArraySet<OnGroupChangeListener> mGroupChangeListeners = new ArraySet<>();
private final Lazy<PeopleNotificationIdentifier> mPeopleNotificationIdentifier;
private final Optional<Bubbles> mBubblesOptional;
+ private final EventBuffer mEventBuffer = new EventBuffer();
private int mBarState = -1;
private HashMap<String, StatusBarNotification> mIsolatedEntries = new HashMap<>();
private HeadsUpManager mHeadsUpManager;
@@ -134,8 +147,14 @@ public class NotificationGroupManagerLegacy implements OnHeadsUpChangedListener,
* When we want to remove an entry from being tracked for grouping
*/
public void onEntryRemoved(NotificationEntry removed) {
+ if (SPEW) {
+ Log.d(TAG, "onEntryRemoved: entry=" + removed);
+ }
onEntryRemovedInternal(removed, removed.getSbn());
- mIsolatedEntries.remove(removed.getKey());
+ StatusBarNotification oldSbn = mIsolatedEntries.remove(removed.getKey());
+ if (oldSbn != null) {
+ updateSuppression(mGroupMap.get(oldSbn.getGroupKey()));
+ }
}
/**
@@ -162,6 +181,9 @@ public class NotificationGroupManagerLegacy implements OnHeadsUpChangedListener,
// the close future. See b/23676310 for reference.
return;
}
+ if (SPEW) {
+ Log.d(TAG, "onEntryRemovedInternal: entry=" + removed + " group=" + group.groupKey);
+ }
if (isGroupChild(removed.getKey(), isGroup, isGroupSummary)) {
group.children.remove(removed.getKey());
} else {
@@ -182,6 +204,9 @@ public class NotificationGroupManagerLegacy implements OnHeadsUpChangedListener,
* Notify the group manager that a new entry was added
*/
public void onEntryAdded(final NotificationEntry added) {
+ if (SPEW) {
+ Log.d(TAG, "onEntryAdded: entry=" + added);
+ }
updateIsolation(added);
onEntryAddedInternal(added);
}
@@ -195,13 +220,16 @@ public class NotificationGroupManagerLegacy implements OnHeadsUpChangedListener,
String groupKey = getGroupKey(sbn);
NotificationGroup group = mGroupMap.get(groupKey);
if (group == null) {
- group = new NotificationGroup();
+ group = new NotificationGroup(groupKey);
mGroupMap.put(groupKey, group);
for (OnGroupChangeListener listener : mGroupChangeListeners) {
listener.onGroupCreated(group, groupKey);
}
}
+ if (SPEW) {
+ Log.d(TAG, "onEntryAddedInternal: entry=" + added + " group=" + group.groupKey);
+ }
if (isGroupChild) {
NotificationEntry existing = group.children.get(added.getKey());
if (existing != null && existing != added) {
@@ -213,9 +241,11 @@ public class NotificationGroupManagerLegacy implements OnHeadsUpChangedListener,
+ " added removed" + added.isRowRemoved(), new Throwable());
}
group.children.put(added.getKey(), added);
+ addToPostBatchHistory(group, added);
updateSuppression(group);
} else {
group.summary = added;
+ addToPostBatchHistory(group, added);
group.expanded = added.areChildrenExpanded();
updateSuppression(group);
if (!group.children.isEmpty()) {
@@ -231,6 +261,27 @@ public class NotificationGroupManagerLegacy implements OnHeadsUpChangedListener,
}
}
+ private void addToPostBatchHistory(NotificationGroup group, @Nullable NotificationEntry entry) {
+ if (entry == null) {
+ return;
+ }
+ boolean didAdd = group.postBatchHistory.add(new PostRecord(entry));
+ if (didAdd) {
+ trimPostBatchHistory(group.postBatchHistory);
+ }
+ }
+
+ /** remove all history that's too old to be in the batch. */
+ private void trimPostBatchHistory(@NonNull TreeSet<PostRecord> postBatchHistory) {
+ if (postBatchHistory.size() <= 1) {
+ return;
+ }
+ long batchStartTime = postBatchHistory.last().postTime - POST_BATCH_MAX_AGE;
+ while (!postBatchHistory.isEmpty() && postBatchHistory.first().postTime < batchStartTime) {
+ postBatchHistory.pollFirst();
+ }
+ }
+
private void onEntryBecomingChild(NotificationEntry entry) {
updateIsolation(entry);
}
@@ -239,6 +290,9 @@ public class NotificationGroupManagerLegacy implements OnHeadsUpChangedListener,
if (group == null) {
return;
}
+ NotificationEntry prevAlertOverride = group.alertOverride;
+ group.alertOverride = getPriorityConversationAlertOverride(group);
+
int childCount = 0;
boolean hasBubbles = false;
for (NotificationEntry entry : group.children.values()) {
@@ -255,18 +309,148 @@ public class NotificationGroupManagerLegacy implements OnHeadsUpChangedListener,
group.suppressed = group.summary != null && !group.expanded
&& (childCount == 1
|| (childCount == 0
- && group.summary.getSbn().getNotification().isGroupSummary()
- && (hasIsolatedChildren(group) || hasBubbles)));
- if (prevSuppressed != group.suppressed) {
- for (OnGroupChangeListener listener : mGroupChangeListeners) {
- if (!mIsUpdatingUnchangedGroup) {
- listener.onGroupSuppressionChanged(group, group.suppressed);
- listener.onGroupsChanged();
+ && group.summary.getSbn().getNotification().isGroupSummary()
+ && (hasIsolatedChildren(group) || hasBubbles)));
+
+ boolean alertOverrideChanged = prevAlertOverride != group.alertOverride;
+ boolean suppressionChanged = prevSuppressed != group.suppressed;
+ if (alertOverrideChanged || suppressionChanged) {
+ if (DEBUG && alertOverrideChanged) {
+ Log.d(TAG, group + " alertOverride was=" + prevAlertOverride + " now="
+ + group.alertOverride);
+ }
+ if (DEBUG && suppressionChanged) {
+ Log.d(TAG, group + " suppressed changed to " + group.suppressed);
+ }
+ if (!mIsUpdatingUnchangedGroup) {
+ if (alertOverrideChanged) {
+ mEventBuffer.notifyAlertOverrideChanged(group, prevAlertOverride);
+ }
+ if (suppressionChanged) {
+ for (OnGroupChangeListener listener : mGroupChangeListeners) {
+ listener.onGroupSuppressionChanged(group, group.suppressed);
+ }
+ }
+ mEventBuffer.notifyGroupsChanged();
+ } else {
+ if (DEBUG) {
+ Log.d(TAG, group + " did not notify listeners of above change(s)");
}
}
}
}
+ /**
+ * Finds the isolated logical child of this group which is should be alerted instead.
+ *
+ * Notifications from priority conversations are isolated from their groups to make them more
+ * prominent, however apps may post these with a GroupAlertBehavior that has the group receiving
+ * the alert. This would lead to the group alerting even though the conversation that was
+ * updated was not actually a part of that group. This method finds the best priority
+ * conversation in this situation, if there is one, so they can be set as the alertOverride of
+ * the group.
+ *
+ * @param group the group to check
+ * @return the entry which should receive the alert instead of the group, if any.
+ */
+ @Nullable
+ private NotificationEntry getPriorityConversationAlertOverride(NotificationGroup group) {
+ // GOAL: if there is a priority child which wouldn't alert based on its groupAlertBehavior,
+ // but which should be alerting (because priority conversations are isolated), find it.
+ if (group == null || group.summary == null) {
+ if (SPEW) {
+ Log.d(TAG, "getPriorityConversationAlertOverride: null group or summary");
+ }
+ return null;
+ }
+ if (isIsolated(group.summary.getKey())) {
+ if (SPEW) {
+ Log.d(TAG, "getPriorityConversationAlertOverride: isolated group");
+ }
+ return null;
+ }
+
+ // Precondiions:
+ // * Only necessary when all notifications in the group use GROUP_ALERT_SUMMARY
+ // * Only necessary when at least one notification in the group is on a priority channel
+ if (group.summary.getSbn().getNotification().getGroupAlertBehavior()
+ != Notification.GROUP_ALERT_SUMMARY) {
+ if (SPEW) {
+ Log.d(TAG, "getPriorityConversationAlertOverride: summary != GROUP_ALERT_SUMMARY");
+ }
+ return null;
+ }
+
+ // Get the important children first, copy the keys for the final importance check,
+ // then add the non-isolated children to the map for unified lookup.
+ HashMap<String, NotificationEntry> children = getImportantConversations(group);
+ if (children == null || children.isEmpty()) {
+ if (SPEW) {
+ Log.d(TAG, "getPriorityConversationAlertOverride: no important conversations");
+ }
+ return null;
+ }
+ HashSet<String> importantChildKeys = new HashSet<>(children.keySet());
+ children.putAll(group.children);
+
+ // Ensure all children have GROUP_ALERT_SUMMARY
+ for (NotificationEntry child : children.values()) {
+ if (child.getSbn().getNotification().getGroupAlertBehavior()
+ != Notification.GROUP_ALERT_SUMMARY) {
+ if (SPEW) {
+ Log.d(TAG, "getPriorityConversationAlertOverride: "
+ + "child != GROUP_ALERT_SUMMARY");
+ }
+ return null;
+ }
+ }
+
+ // Create a merged post history from all the children
+ TreeSet<PostRecord> combinedHistory = new TreeSet<>(group.postBatchHistory);
+ for (String importantChildKey : importantChildKeys) {
+ NotificationGroup importantChildGroup = mGroupMap.get(importantChildKey);
+ combinedHistory.addAll(importantChildGroup.postBatchHistory);
+ }
+ trimPostBatchHistory(combinedHistory);
+
+ // This is a streamlined implementation of the following idea:
+ // * From the subset of notifications in the latest 'batch' of updates. A batch is:
+ // * Notifs posted less than POST_BATCH_MAX_AGE before the most recently posted.
+ // * Only including notifs newer than the second-to-last post of any notification.
+ // * Find the newest child in the batch -- the with the largest 'when' value.
+ // * If the newest child is a priority conversation, set that as the override.
+ HashSet<String> batchKeys = new HashSet<>();
+ long newestChildWhen = -1;
+ NotificationEntry newestChild = null;
+ // Iterate backwards through the post history, tracking the child with the smallest sort key
+ for (PostRecord record : combinedHistory.descendingSet()) {
+ if (batchKeys.contains(record.key)) {
+ // Once you see a notification again, the batch has ended
+ break;
+ }
+ batchKeys.add(record.key);
+ NotificationEntry child = children.get(record.key);
+ if (child != null) {
+ long childWhen = child.getSbn().getNotification().when;
+ if (newestChild == null || childWhen > newestChildWhen) {
+ newestChildWhen = childWhen;
+ newestChild = child;
+ }
+ }
+ }
+ if (newestChild != null && importantChildKeys.contains(newestChild.getKey())) {
+ if (SPEW) {
+ Log.d(TAG, "getPriorityConversationAlertOverride: result=" + newestChild);
+ }
+ return newestChild;
+ }
+ if (SPEW) {
+ Log.d(TAG, "getPriorityConversationAlertOverride: result=null, newestChild="
+ + newestChild);
+ }
+ return null;
+ }
+
private boolean hasIsolatedChildren(NotificationGroup group) {
return getNumberOfIsolatedChildren(group.summary.getSbn().getGroupKey()) != 0;
}
@@ -281,12 +465,33 @@ public class NotificationGroupManagerLegacy implements OnHeadsUpChangedListener,
return count;
}
+ @Nullable
+ private HashMap<String, NotificationEntry> getImportantConversations(NotificationGroup group) {
+ String groupKey = group.summary.getSbn().getGroupKey();
+ HashMap<String, NotificationEntry> result = null;
+ for (StatusBarNotification sbn : mIsolatedEntries.values()) {
+ if (sbn.getGroupKey().equals(groupKey)) {
+ NotificationEntry entry = mGroupMap.get(sbn.getKey()).summary;
+ if (isImportantConversation(entry)) {
+ if (result == null) {
+ result = new HashMap<>();
+ }
+ result.put(sbn.getKey(), entry);
+ }
+ }
+ }
+ return result;
+ }
+
/**
* Update an entry's group information
* @param entry notification entry to update
* @param oldNotification previous notification info before this update
*/
public void onEntryUpdated(NotificationEntry entry, StatusBarNotification oldNotification) {
+ if (SPEW) {
+ Log.d(TAG, "onEntryUpdated: entry=" + entry);
+ }
onEntryUpdated(entry, oldNotification.getGroupKey(), oldNotification.isGroup(),
oldNotification.getNotification().isGroupSummary());
}
@@ -325,7 +530,17 @@ public class NotificationGroupManagerLegacy implements OnHeadsUpChangedListener,
* Whether the given notification is the summary of a group that is being suppressed
*/
public boolean isSummaryOfSuppressedGroup(StatusBarNotification sbn) {
- return isGroupSuppressed(getGroupKey(sbn)) && sbn.getNotification().isGroupSummary();
+ return sbn.getNotification().isGroupSummary() && isGroupSuppressed(getGroupKey(sbn));
+ }
+
+ /**
+ * If the given notification is a summary, get the group for it.
+ */
+ public NotificationGroup getGroupForSummary(StatusBarNotification sbn) {
+ if (sbn.getNotification().isGroupSummary()) {
+ return mGroupMap.get(getGroupKey(sbn));
+ }
+ return null;
}
private boolean isOnlyChild(StatusBarNotification sbn) {
@@ -545,9 +760,7 @@ public class NotificationGroupManagerLegacy implements OnHeadsUpChangedListener,
if (!sbn.isGroup() || sbn.getNotification().isGroupSummary()) {
return false;
}
- int peopleNotificationType =
- mPeopleNotificationIdentifier.get().getPeopleNotificationType(entry);
- if (peopleNotificationType == PeopleNotificationIdentifier.TYPE_IMPORTANT_PERSON) {
+ if (isImportantConversation(entry)) {
return true;
}
if (mHeadsUpManager != null && !mHeadsUpManager.isAlerting(entry.getKey())) {
@@ -560,18 +773,25 @@ public class NotificationGroupManagerLegacy implements OnHeadsUpChangedListener,
|| isGroupNotFullyVisible(notificationGroup));
}
+ private boolean isImportantConversation(NotificationEntry entry) {
+ int peopleNotificationType =
+ mPeopleNotificationIdentifier.get().getPeopleNotificationType(entry);
+ return peopleNotificationType == PeopleNotificationIdentifier.TYPE_IMPORTANT_PERSON;
+ }
+
/**
* Isolate a notification from its group so that it visually shows as its own group.
*
* @param entry the notification to isolate
*/
private void isolateNotification(NotificationEntry entry) {
- StatusBarNotification sbn = entry.getSbn();
-
+ if (SPEW) {
+ Log.d(TAG, "isolateNotification: entry=" + entry);
+ }
// We will be isolated now, so lets update the groups
onEntryRemovedInternal(entry, entry.getSbn());
- mIsolatedEntries.put(sbn.getKey(), sbn);
+ mIsolatedEntries.put(entry.getKey(), entry.getSbn());
onEntryAddedInternal(entry);
// We also need to update the suppression of the old group, because this call comes
@@ -588,6 +808,14 @@ public class NotificationGroupManagerLegacy implements OnHeadsUpChangedListener,
* Update the isolation of an entry, splitting it from the group.
*/
public void updateIsolation(NotificationEntry entry) {
+ // We need to buffer a few events because we do isolation changes in 3 steps:
+ // removeInternal, update mIsolatedEntries, addInternal. This means that often the
+ // alertOverride will update on the removal, however processing the event in that case can
+ // cause problems because the mIsolatedEntries map is not in its final state, so the event
+ // listener may be unable to correctly determine the true state of the group. By delaying
+ // the alertOverride change until after the add phase, we can ensure that listeners only
+ // have to handle a consistent state.
+ mEventBuffer.startBuffering();
boolean isIsolated = isIsolated(entry.getSbn().getKey());
if (shouldIsolate(entry)) {
if (!isIsolated) {
@@ -596,6 +824,7 @@ public class NotificationGroupManagerLegacy implements OnHeadsUpChangedListener,
} else if (isIsolated) {
stopIsolatingNotification(entry);
}
+ mEventBuffer.flushAndStopBuffering();
}
/**
@@ -604,15 +833,15 @@ public class NotificationGroupManagerLegacy implements OnHeadsUpChangedListener,
* @param entry the notification to un-isolate
*/
private void stopIsolatingNotification(NotificationEntry entry) {
- StatusBarNotification sbn = entry.getSbn();
- if (isIsolated(sbn.getKey())) {
- // not isolated anymore, we need to update the groups
- onEntryRemovedInternal(entry, entry.getSbn());
- mIsolatedEntries.remove(sbn.getKey());
- onEntryAddedInternal(entry);
- for (OnGroupChangeListener listener : mGroupChangeListeners) {
- listener.onGroupsChanged();
- }
+ if (SPEW) {
+ Log.d(TAG, "stopIsolatingNotification: entry=" + entry);
+ }
+ // not isolated anymore, we need to update the groups
+ onEntryRemovedInternal(entry, entry.getSbn());
+ mIsolatedEntries.remove(entry.getKey());
+ onEntryAddedInternal(entry);
+ for (OnGroupChangeListener listener : mGroupChangeListeners) {
+ listener.onGroupsChanged();
}
}
@@ -648,33 +877,154 @@ public class NotificationGroupManagerLegacy implements OnHeadsUpChangedListener,
}
/**
+ * A record of a notification being posted, containing the time of the post and the key of the
+ * notification entry. These are stored in a TreeSet by the NotificationGroup and used to
+ * calculate a batch of notifications.
+ */
+ public static class PostRecord implements Comparable<PostRecord> {
+ public final long postTime;
+ public final String key;
+
+ /** constructs a record containing the post time and key from the notification entry */
+ public PostRecord(@NonNull NotificationEntry entry) {
+ this.postTime = entry.getSbn().getPostTime();
+ this.key = entry.getKey();
+ }
+
+ @Override
+ public int compareTo(PostRecord o) {
+ int postTimeComparison = Long.compare(this.postTime, o.postTime);
+ return postTimeComparison == 0
+ ? String.CASE_INSENSITIVE_ORDER.compare(this.key, o.key)
+ : postTimeComparison;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+ PostRecord that = (PostRecord) o;
+ return postTime == that.postTime && key.equals(that.key);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(postTime, key);
+ }
+ }
+
+ /**
* Represents a notification group in the notification shade.
*/
public static class NotificationGroup {
+ public final String groupKey;
public final HashMap<String, NotificationEntry> children = new HashMap<>();
+ public final TreeSet<PostRecord> postBatchHistory = new TreeSet<>();
public NotificationEntry summary;
public boolean expanded;
/**
* Is this notification group suppressed, i.e its summary is hidden
*/
public boolean suppressed;
+ /**
+ * The child (which is isolated from this group) to which the alert should be transferred,
+ * due to priority conversations.
+ */
+ public NotificationEntry alertOverride;
+
+ NotificationGroup(String groupKey) {
+ this.groupKey = groupKey;
+ }
@Override
public String toString() {
- String result = " summary:\n "
- + (summary != null ? summary.getSbn() : "null")
- + (summary != null && summary.getDebugThrowable() != null
- ? Log.getStackTraceString(summary.getDebugThrowable())
- : "");
- result += "\n children size: " + children.size();
+ StringBuilder sb = new StringBuilder();
+ sb.append(" groupKey: ").append(groupKey);
+ sb.append("\n summary:");
+ appendEntry(sb, summary);
+ sb.append("\n children size: ").append(children.size());
for (NotificationEntry child : children.values()) {
- result += "\n " + child.getSbn()
- + (child.getDebugThrowable() != null
- ? Log.getStackTraceString(child.getDebugThrowable())
- : "");
+ appendEntry(sb, child);
+ }
+ sb.append("\n alertOverride:");
+ appendEntry(sb, alertOverride);
+ sb.append("\n summary suppressed: ").append(suppressed);
+ return sb.toString();
+ }
+
+ private void appendEntry(StringBuilder sb, NotificationEntry entry) {
+ sb.append("\n ").append(entry != null ? entry.getSbn() : "null");
+ if (entry != null && entry.getDebugThrowable() != null) {
+ sb.append(Log.getStackTraceString(entry.getDebugThrowable()));
+ }
+ }
+ }
+
+ /**
+ * This class is a toggleable buffer for a subset of events of {@link OnGroupChangeListener}.
+ * When buffering, instead of notifying the listeners it will set internal state that will allow
+ * it to notify listeners of those events later
+ */
+ private class EventBuffer {
+ private final HashMap<String, NotificationEntry> mOldAlertOverrideByGroup = new HashMap<>();
+ private boolean mIsBuffering = false;
+ private boolean mDidGroupsChange = false;
+
+ void notifyAlertOverrideChanged(NotificationGroup group,
+ NotificationEntry oldAlertOverride) {
+ if (mIsBuffering) {
+ // The value in this map is the override before the event. If there is an entry
+ // already in the map, then we are effectively coalescing two events, which means
+ // we need to preserve the original initial value.
+ mOldAlertOverrideByGroup.putIfAbsent(group.groupKey, oldAlertOverride);
+ } else {
+ for (OnGroupChangeListener listener : mGroupChangeListeners) {
+ listener.onGroupAlertOverrideChanged(group, oldAlertOverride,
+ group.alertOverride);
+ }
+ }
+ }
+
+ void notifyGroupsChanged() {
+ if (mIsBuffering) {
+ mDidGroupsChange = true;
+ } else {
+ for (OnGroupChangeListener listener : mGroupChangeListeners) {
+ listener.onGroupsChanged();
+ }
+ }
+ }
+
+ void startBuffering() {
+ mIsBuffering = true;
+ }
+
+ void flushAndStopBuffering() {
+ // stop buffering so that we can call our own helpers
+ mIsBuffering = false;
+ // alert all group alert override changes for groups that were not removed
+ for (Map.Entry<String, NotificationEntry> entry : mOldAlertOverrideByGroup.entrySet()) {
+ NotificationGroup group = mGroupMap.get(entry.getKey());
+ if (group == null) {
+ // The group can be null if this alertOverride changed before the group was
+ // permanently removed, meaning that there's no guarantee that listeners will
+ // that field clear.
+ continue;
+ }
+ NotificationEntry oldAlertOverride = entry.getValue();
+ if (group.alertOverride == oldAlertOverride) {
+ // If the final alertOverride equals the initial, it means we coalesced two
+ // events which undid the change, so we can drop it entirely.
+ continue;
+ }
+ notifyAlertOverrideChanged(group, oldAlertOverride);
+ }
+ mOldAlertOverrideByGroup.clear();
+ // alert that groups changed
+ if (mDidGroupsChange) {
+ notifyGroupsChanged();
+ mDidGroupsChange = false;
}
- result += "\n summary suppressed: " + suppressed;
- return result;
}
}
@@ -714,6 +1064,18 @@ public class NotificationGroupManagerLegacy implements OnHeadsUpChangedListener,
boolean suppressed) {}
/**
+ * The alert override of a group has changed.
+ *
+ * @param group the group that has changed
+ * @param oldAlertOverride the previous notification to which the group's alerts were sent
+ * @param newAlertOverride the notification to which the group's alerts should now be sent
+ */
+ default void onGroupAlertOverrideChanged(
+ NotificationGroup group,
+ @Nullable NotificationEntry oldAlertOverride,
+ @Nullable NotificationEntry newAlertOverride) {}
+
+ /**
* A group of children just received a summary notification and should therefore become
* children of it.
*
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationBackgroundView.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationBackgroundView.java
index af8b4d99792a..f6ab409998bf 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationBackgroundView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationBackgroundView.java
@@ -84,8 +84,7 @@ public class NotificationBackgroundView extends View {
int bottom = mActualHeight;
if (mBottomIsRounded
&& mBottomAmountClips
- && !mExpandAnimationRunning
- && !mLastInSection) {
+ && !mExpandAnimationRunning) {
bottom -= mClipBottomAmount;
}
int left = 0;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationRoundnessManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationRoundnessManager.java
index b1ac12e84fb3..4b49e3a90ede 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationRoundnessManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationRoundnessManager.java
@@ -71,6 +71,13 @@ public class NotificationRoundnessManager {
}
}
+ public boolean isViewAffectedBySwipe(ExpandableView expandableView) {
+ return expandableView != null
+ && (expandableView == mSwipedView
+ || expandableView == mViewBeforeSwipedView
+ || expandableView == mViewAfterSwipedView);
+ }
+
boolean updateViewWithoutCallback(ExpandableView view,
boolean animate) {
if (view == null
@@ -78,38 +85,35 @@ public class NotificationRoundnessManager {
|| view == mViewAfterSwipedView) {
return false;
}
- float topRoundness = getRoundness(view, true /* top */);
- float bottomRoundness = getRoundness(view, false /* top */);
- boolean topChanged = view.setTopRoundness(topRoundness, animate);
- boolean bottomChanged = view.setBottomRoundness(bottomRoundness, animate);
- boolean firstInSection = isFirstInSection(view, false /* exclude first section */);
- boolean lastInSection = isLastInSection(view, false /* exclude last section */);
- view.setFirstInSection(firstInSection);
- view.setLastInSection(lastInSection);
- return (firstInSection || lastInSection) && (topChanged || bottomChanged);
+
+ final float topRoundness = getRoundness(view, true /* top */);
+ final float bottomRoundness = getRoundness(view, false /* top */);
+
+ final boolean topChanged = view.setTopRoundness(topRoundness, animate);
+ final boolean bottomChanged = view.setBottomRoundness(bottomRoundness, animate);
+
+ final boolean isFirstInSection = isFirstInSection(view);
+ final boolean isLastInSection = isLastInSection(view);
+
+ view.setFirstInSection(isFirstInSection);
+ view.setLastInSection(isLastInSection);
+
+ return (isFirstInSection || isLastInSection) && (topChanged || bottomChanged);
}
- private boolean isFirstInSection(ExpandableView view, boolean includeFirstSection) {
- int numNonEmptySections = 0;
+ private boolean isFirstInSection(ExpandableView view) {
for (int i = 0; i < mFirstInSectionViews.length; i++) {
if (view == mFirstInSectionViews[i]) {
- return includeFirstSection || numNonEmptySections > 0;
- }
- if (mFirstInSectionViews[i] != null) {
- numNonEmptySections++;
+ return true;
}
}
return false;
}
- private boolean isLastInSection(ExpandableView view, boolean includeLastSection) {
- int numNonEmptySections = 0;
+ private boolean isLastInSection(ExpandableView view) {
for (int i = mLastInSectionViews.length - 1; i >= 0; i--) {
if (view == mLastInSectionViews[i]) {
- return includeLastSection || numNonEmptySections > 0;
- }
- if (mLastInSectionViews[i] != null) {
- numNonEmptySections++;
+ return true;
}
}
return false;
@@ -172,10 +176,10 @@ public class NotificationRoundnessManager {
|| (view.isHeadsUpAnimatingAway()) && !mExpanded)) {
return 1.0f;
}
- if (isFirstInSection(view, true /* include first section */) && top) {
+ if (isFirstInSection(view) && top) {
return 1.0f;
}
- if (isLastInSection(view, true /* include last section */) && !top) {
+ if (isLastInSection(view) && !top) {
return 1.0f;
}
if (view == mTrackedHeadsUp) {
@@ -229,10 +233,8 @@ public class NotificationRoundnessManager {
: section.getLastVisibleChild());
if (newView == oldView) {
isStillPresent = true;
- if (oldView.isFirstInSection() != isFirstInSection(oldView,
- false /* exclude first section */)
- || oldView.isLastInSection() != isLastInSection(oldView,
- false /* exclude last section */)) {
+ if (oldView.isFirstInSection() != isFirstInSection(oldView)
+ || oldView.isLastInSection() != isLastInSection(oldView)) {
adjacentSectionChanged = true;
}
break;
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 ae9467eb651b..b039df3f32af 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
@@ -740,6 +740,10 @@ public class NotificationStackScrollLayoutController {
return true;
}
+ public boolean isViewAffectedBySwipe(ExpandableView expandableView) {
+ return mNotificationRoundnessManager.isViewAffectedBySwipe(expandableView);
+ }
+
public void addOnExpandedHeightChangedListener(BiConsumer<Float, Float> listener) {
mView.addOnExpandedHeightChangedListener(listener);
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpManagerPhone.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpManagerPhone.java
index 3827123f0160..4b545ebf2a05 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpManagerPhone.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpManagerPhone.java
@@ -301,7 +301,7 @@ public class HeadsUpManagerPhone extends HeadsUpManager implements Dumpable,
}
///////////////////////////////////////////////////////////////////////////////////////////////
- // HeadsUpManager public methods overrides:
+ // HeadsUpManager public methods overrides and overloads:
@Override
public boolean isTrackingHeadsUp() {
@@ -318,6 +318,18 @@ public class HeadsUpManagerPhone extends HeadsUpManager implements Dumpable,
mSwipedOutKeys.add(key);
}
+ public boolean removeNotification(@NonNull String key, boolean releaseImmediately,
+ boolean animate) {
+ if (animate) {
+ return removeNotification(key, releaseImmediately);
+ } else {
+ mAnimationStateHandler.setHeadsUpGoingAwayAnimationsAllowed(false);
+ boolean removed = removeNotification(key, releaseImmediately);
+ mAnimationStateHandler.setHeadsUpGoingAwayAnimationsAllowed(true);
+ return removed;
+ }
+ }
+
///////////////////////////////////////////////////////////////////////////////////////////////
// Dumpable overrides:
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationGroupAlertTransferHelper.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationGroupAlertTransferHelper.java
index 3181f520dca2..9787a9446019 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationGroupAlertTransferHelper.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationGroupAlertTransferHelper.java
@@ -22,12 +22,12 @@ import android.app.Notification;
import android.os.SystemClock;
import android.service.notification.StatusBarNotification;
import android.util.ArrayMap;
+import android.util.Log;
import com.android.internal.statusbar.NotificationVisibility;
import com.android.systemui.Dependency;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.plugins.statusbar.StatusBarStateController.StateListener;
-import com.android.systemui.statusbar.AlertingNotificationManager;
import com.android.systemui.statusbar.notification.NotificationEntryListener;
import com.android.systemui.statusbar.notification.NotificationEntryManager;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
@@ -41,17 +41,21 @@ import com.android.systemui.statusbar.policy.HeadsUpManager;
import com.android.systemui.statusbar.policy.OnHeadsUpChangedListener;
import java.util.ArrayList;
+import java.util.List;
import java.util.Objects;
/**
* A helper class dealing with the alert interactions between {@link NotificationGroupManagerLegacy}
* and {@link HeadsUpManager}. In particular, this class deals with keeping
- * the correct notification in a group alerting based off the group suppression.
+ * the correct notification in a group alerting based off the group suppression and alertOverride.
*/
public class NotificationGroupAlertTransferHelper implements OnHeadsUpChangedListener,
StateListener {
private static final long ALERT_TRANSFER_TIMEOUT = 300;
+ private static final String TAG = "NotifGroupAlertTransfer";
+ private static final boolean DEBUG = StatusBar.DEBUG;
+ private static final boolean SPEW = StatusBar.SPEW;
/**
* The list of entries containing group alert metadata for each group. Keyed by group key.
@@ -142,41 +146,98 @@ public class NotificationGroupAlertTransferHelper implements OnHeadsUpChangedLis
@Override
public void onGroupSuppressionChanged(NotificationGroup group, boolean suppressed) {
- if (suppressed) {
- if (mHeadsUpManager.isAlerting(group.summary.getKey())) {
- handleSuppressedSummaryAlerted(group.summary, mHeadsUpManager);
+ if (DEBUG) {
+ Log.d(TAG, "!! onGroupSuppressionChanged: group.summary=" + group.summary
+ + " suppressed=" + suppressed);
+ }
+ NotificationEntry oldAlertOverride = group.alertOverride;
+ onGroupChanged(group, oldAlertOverride);
+ }
+
+ @Override
+ public void onGroupAlertOverrideChanged(NotificationGroup group,
+ @Nullable NotificationEntry oldAlertOverride,
+ @Nullable NotificationEntry newAlertOverride) {
+ if (DEBUG) {
+ Log.d(TAG, "!! onGroupAlertOverrideChanged: group.summary=" + group.summary
+ + " oldAlertOverride=" + oldAlertOverride
+ + " newAlertOverride=" + newAlertOverride);
+ }
+ onGroupChanged(group, oldAlertOverride);
+ }
+ };
+
+ /**
+ * Called when either the suppressed or alertOverride fields of the group changed
+ *
+ * @param group the group which changed
+ * @param oldAlertOverride the previous value of group.alertOverride
+ */
+ private void onGroupChanged(NotificationGroup group,
+ NotificationEntry oldAlertOverride) {
+ // Group summary can be null if we are no longer suppressed because the summary was
+ // removed. In that case, we don't need to alert the summary.
+ if (group.summary == null) {
+ if (DEBUG) {
+ Log.d(TAG, "onGroupChanged: summary is null");
+ }
+ return;
+ }
+ if (group.suppressed || group.alertOverride != null) {
+ checkForForwardAlertTransfer(group.summary, oldAlertOverride);
+ } else {
+ if (DEBUG) {
+ Log.d(TAG, "onGroupChanged: maybe transfer back");
+ }
+ GroupAlertEntry groupAlertEntry = mGroupAlertEntries.get(mGroupManager.getGroupKey(
+ group.summary.getSbn()));
+ // Group is no longer suppressed or overridden.
+ // We should check if we need to transfer the alert back to the summary.
+ if (groupAlertEntry.mAlertSummaryOnNextAddition) {
+ if (!mHeadsUpManager.isAlerting(group.summary.getKey())) {
+ alertNotificationWhenPossible(group.summary);
}
+ groupAlertEntry.mAlertSummaryOnNextAddition = false;
} else {
- // Group summary can be null if we are no longer suppressed because the summary was
- // removed. In that case, we don't need to alert the summary.
- if (group.summary == null) {
- return;
- }
- GroupAlertEntry groupAlertEntry = mGroupAlertEntries.get(mGroupManager.getGroupKey(
- group.summary.getSbn()));
- // Group is no longer suppressed. We should check if we need to transfer the alert
- // back to the summary now that it's no longer suppressed.
- if (groupAlertEntry.mAlertSummaryOnNextAddition) {
- if (!mHeadsUpManager.isAlerting(group.summary.getKey())) {
- alertNotificationWhenPossible(group.summary, mHeadsUpManager);
- }
- groupAlertEntry.mAlertSummaryOnNextAddition = false;
- } else {
- checkShouldTransferBack(groupAlertEntry);
- }
+ checkShouldTransferBack(groupAlertEntry);
}
}
- };
+ }
@Override
public void onHeadsUpStateChanged(NotificationEntry entry, boolean isHeadsUp) {
- onAlertStateChanged(entry, isHeadsUp, mHeadsUpManager);
+ if (DEBUG) {
+ Log.d(TAG, "!! onHeadsUpStateChanged: entry=" + entry + " isHeadsUp=" + isHeadsUp);
+ }
+ if (isHeadsUp && entry.getSbn().getNotification().isGroupSummary()) {
+ // a group summary is alerting; trigger the forward transfer checks
+ checkForForwardAlertTransfer(entry, /* oldAlertOverride */ null);
+ }
}
- private void onAlertStateChanged(NotificationEntry entry, boolean isAlerting,
- AlertingNotificationManager alertManager) {
- if (isAlerting && mGroupManager.isSummaryOfSuppressedGroup(entry.getSbn())) {
- handleSuppressedSummaryAlerted(entry, alertManager);
+ /**
+ * Handles changes in a group's suppression or alertOverride, but where at least one of those
+ * conditions is still true (either the group is suppressed, the group has an alertOverride,
+ * or both). The method determined which kind of child needs to receive the alert, finds the
+ * entry currently alerting, and makes the transfer.
+ *
+ * Internally, this is handled with two main cases: the override needs the alert, or there is
+ * no override but the summary is suppressed (so an isolated child needs the alert).
+ *
+ * @param summary the notification entry of the summary of the logical group.
+ * @param oldAlertOverride the former value of group.alertOverride, before whatever event
+ * required us to check for for a transfer condition.
+ */
+ private void checkForForwardAlertTransfer(NotificationEntry summary,
+ NotificationEntry oldAlertOverride) {
+ if (DEBUG) {
+ Log.d(TAG, "checkForForwardAlertTransfer: enter");
+ }
+ NotificationGroup group = mGroupManager.getGroupForSummary(summary.getSbn());
+ if (group != null && group.alertOverride != null) {
+ handleOverriddenSummaryAlerted(summary);
+ } else if (mGroupManager.isSummaryOfSuppressedGroup(summary.getSbn())) {
+ handleSuppressedSummaryAlerted(summary, oldAlertOverride);
}
}
@@ -186,9 +247,16 @@ public class NotificationGroupAlertTransferHelper implements OnHeadsUpChangedLis
// see as early as we can if we need to abort a transfer.
@Override
public void onPendingEntryAdded(NotificationEntry entry) {
+ if (DEBUG) {
+ Log.d(TAG, "!! onPendingEntryAdded: entry=" + entry);
+ }
String groupKey = mGroupManager.getGroupKey(entry.getSbn());
GroupAlertEntry groupAlertEntry = mGroupAlertEntries.get(groupKey);
- if (groupAlertEntry != null) {
+ if (groupAlertEntry != null && groupAlertEntry.mGroup.alertOverride == null) {
+ // new pending group entries require us to transfer back from the child to the
+ // group, but alertOverrides are only present in very limited circumstances, so
+ // while it's possible the group should ALSO alert, the previous detection which set
+ // this alertOverride won't be invalidated by this notification added to this group.
checkShouldTransferBack(groupAlertEntry);
}
}
@@ -262,43 +330,128 @@ public class NotificationGroupAlertTransferHelper implements OnHeadsUpChangedLis
}
/**
- * Handles the scenario where a summary that has been suppressed is alerted. A suppressed
+ * Handles the scenario where a summary that has been suppressed is itself, or has a former
+ * alertOverride (in the form of an isolated logical child) which was alerted. A suppressed
* summary should for all intents and purposes be invisible to the user and as a result should
* not alert. When this is the case, it is our responsibility to pass the alert to the
* appropriate child which will be the representative notification alerting for the group.
*
- * @param summary the summary that is suppressed and alerting
- * @param alertManager the alert manager that manages the alerting summary
+ * @param summary the summary that is suppressed and (potentially) alerting
+ * @param oldAlertOverride the alertOverride before whatever event triggered this method. If
+ * the alert override was removed, this will be the entry that should
+ * be transferred back from.
*/
private void handleSuppressedSummaryAlerted(@NonNull NotificationEntry summary,
- @NonNull AlertingNotificationManager alertManager) {
- StatusBarNotification sbn = summary.getSbn();
+ NotificationEntry oldAlertOverride) {
+ if (DEBUG) {
+ Log.d(TAG, "handleSuppressedSummaryAlerted: summary=" + summary);
+ }
GroupAlertEntry groupAlertEntry =
- mGroupAlertEntries.get(mGroupManager.getGroupKey(sbn));
+ mGroupAlertEntries.get(mGroupManager.getGroupKey(summary.getSbn()));
+
if (!mGroupManager.isSummaryOfSuppressedGroup(summary.getSbn())
- || !alertManager.isAlerting(sbn.getKey())
|| groupAlertEntry == null) {
+ if (DEBUG) {
+ Log.d(TAG, "handleSuppressedSummaryAlerted: invalid state");
+ }
+ return;
+ }
+ boolean summaryIsAlerting = mHeadsUpManager.isAlerting(summary.getKey());
+ boolean priorityIsAlerting = oldAlertOverride != null
+ && mHeadsUpManager.isAlerting(oldAlertOverride.getKey());
+ if (!summaryIsAlerting && !priorityIsAlerting) {
+ if (DEBUG) {
+ Log.d(TAG, "handleSuppressedSummaryAlerted: no summary or override alerting");
+ }
return;
}
if (pendingInflationsWillAddChildren(groupAlertEntry.mGroup)) {
// New children will actually be added to this group, let's not transfer the alert.
+ if (DEBUG) {
+ Log.d(TAG, "handleSuppressedSummaryAlerted: pending inflations");
+ }
return;
}
NotificationEntry child =
mGroupManager.getLogicalChildren(summary.getSbn()).iterator().next();
- if (child != null) {
- if (child.getRow().keepInParent()
- || child.isRowRemoved()
- || child.isRowDismissed()) {
- // The notification is actually already removed. No need to alert it.
- return;
+ if (summaryIsAlerting) {
+ if (DEBUG) {
+ Log.d(TAG, "handleSuppressedSummaryAlerted: transfer summary -> child");
}
- if (!alertManager.isAlerting(child.getKey()) && onlySummaryAlerts(summary)) {
- groupAlertEntry.mLastAlertTransferTime = SystemClock.elapsedRealtime();
+ tryTransferAlertState(summary, /*from*/ summary, /*to*/ child, groupAlertEntry);
+ return;
+ }
+ // Summary didn't have the alert, so we're in "transfer back" territory. First, make sure
+ // it's not too late to transfer back, then transfer the alert from the oldAlertOverride to
+ // the isolated child which should receive the alert.
+ if (!canStillTransferBack(groupAlertEntry)) {
+ if (DEBUG) {
+ Log.d(TAG, "handleSuppressedSummaryAlerted: transfer from override: too late");
+ }
+ return;
+ }
+
+ if (DEBUG) {
+ Log.d(TAG, "handleSuppressedSummaryAlerted: transfer override -> child");
+ }
+ tryTransferAlertState(summary, /*from*/ oldAlertOverride, /*to*/ child, groupAlertEntry);
+ }
+
+ /**
+ * Checks for and handles the scenario where the given entry is the summary of a group which
+ * has an alertOverride, and either the summary itself or one of its logical isolated children
+ * is currently alerting (which happens if the summary is suppressed).
+ */
+ private void handleOverriddenSummaryAlerted(NotificationEntry summary) {
+ if (DEBUG) {
+ Log.d(TAG, "handleOverriddenSummaryAlerted: summary=" + summary);
+ }
+ GroupAlertEntry groupAlertEntry =
+ mGroupAlertEntries.get(mGroupManager.getGroupKey(summary.getSbn()));
+ NotificationGroup group = mGroupManager.getGroupForSummary(summary.getSbn());
+ if (group == null || group.alertOverride == null || groupAlertEntry == null) {
+ if (DEBUG) {
+ Log.d(TAG, "handleOverriddenSummaryAlerted: invalid state");
+ }
+ return;
+ }
+ boolean summaryIsAlerting = mHeadsUpManager.isAlerting(summary.getKey());
+ if (summaryIsAlerting) {
+ if (DEBUG) {
+ Log.d(TAG, "handleOverriddenSummaryAlerted: transfer summary -> override");
+ }
+ tryTransferAlertState(summary, /*from*/ summary, group.alertOverride, groupAlertEntry);
+ return;
+ }
+ // Summary didn't have the alert, so we're in "transfer back" territory. First, make sure
+ // it's not too late to transfer back, then remove the alert from any of the logical
+ // children, and if one of them was alerting, we can alert the override.
+ if (!canStillTransferBack(groupAlertEntry)) {
+ if (DEBUG) {
+ Log.d(TAG, "handleOverriddenSummaryAlerted: transfer from child: too late");
+ }
+ return;
+ }
+ List<NotificationEntry> children = mGroupManager.getLogicalChildren(summary.getSbn());
+ if (children == null) {
+ if (DEBUG) {
+ Log.d(TAG, "handleOverriddenSummaryAlerted: no children");
+ }
+ return;
+ }
+ children.remove(group.alertOverride); // do not release the alert on our desired destination
+ boolean releasedChild = releaseChildAlerts(children);
+ if (releasedChild) {
+ if (DEBUG) {
+ Log.d(TAG, "handleOverriddenSummaryAlerted: transfer child -> override");
+ }
+ tryTransferAlertState(summary, /*from*/ null, group.alertOverride, groupAlertEntry);
+ } else {
+ if (DEBUG) {
+ Log.d(TAG, "handleOverriddenSummaryAlerted: no child alert released");
}
- transferAlertState(summary, child, alertManager);
}
}
@@ -307,14 +460,37 @@ public class NotificationGroupAlertTransferHelper implements OnHeadsUpChangedLis
* immediately to have the incorrect one up as short as possible. The second should alert
* when possible.
*
+ * @param summary entry of the summary
* @param fromEntry entry to transfer alert from
* @param toEntry entry to transfer to
- * @param alertManager alert manager for the alert type
*/
- private void transferAlertState(@NonNull NotificationEntry fromEntry, @NonNull NotificationEntry toEntry,
- @NonNull AlertingNotificationManager alertManager) {
- alertManager.removeNotification(fromEntry.getKey(), true /* releaseImmediately */);
- alertNotificationWhenPossible(toEntry, alertManager);
+ private void tryTransferAlertState(
+ NotificationEntry summary,
+ NotificationEntry fromEntry,
+ NotificationEntry toEntry,
+ GroupAlertEntry groupAlertEntry) {
+ if (toEntry != null) {
+ if (toEntry.getRow().keepInParent()
+ || toEntry.isRowRemoved()
+ || toEntry.isRowDismissed()) {
+ // The notification is actually already removed. No need to alert it.
+ return;
+ }
+ if (!mHeadsUpManager.isAlerting(toEntry.getKey()) && onlySummaryAlerts(summary)) {
+ groupAlertEntry.mLastAlertTransferTime = SystemClock.elapsedRealtime();
+ }
+ if (DEBUG) {
+ Log.d(TAG, "transferAlertState: fromEntry=" + fromEntry + " toEntry=" + toEntry);
+ }
+ transferAlertState(fromEntry, toEntry);
+ }
+ }
+ private void transferAlertState(@Nullable NotificationEntry fromEntry,
+ @NonNull NotificationEntry toEntry) {
+ if (fromEntry != null) {
+ mHeadsUpManager.removeNotification(fromEntry.getKey(), true /* releaseImmediately */);
+ }
+ alertNotificationWhenPossible(toEntry);
}
/**
@@ -326,11 +502,13 @@ public class NotificationGroupAlertTransferHelper implements OnHeadsUpChangedLis
* more children are coming. Thus, if a child is added within a certain timeframe after we
* transfer, we back out and alert the summary again.
*
+ * An alert can only transfer back within a small window of time after a transfer away from the
+ * summary to a child happened.
+ *
* @param groupAlertEntry group alert entry to check
*/
private void checkShouldTransferBack(@NonNull GroupAlertEntry groupAlertEntry) {
- if (SystemClock.elapsedRealtime() - groupAlertEntry.mLastAlertTransferTime
- < ALERT_TRANSFER_TIMEOUT) {
+ if (canStillTransferBack(groupAlertEntry)) {
NotificationEntry summary = groupAlertEntry.mGroup.summary;
if (!onlySummaryAlerts(summary)) {
@@ -338,30 +516,17 @@ public class NotificationGroupAlertTransferHelper implements OnHeadsUpChangedLis
}
ArrayList<NotificationEntry> children = mGroupManager.getLogicalChildren(
summary.getSbn());
- int numChildren = children.size();
+ int numActiveChildren = children.size();
int numPendingChildren = getPendingChildrenNotAlerting(groupAlertEntry.mGroup);
- numChildren += numPendingChildren;
+ int numChildren = numActiveChildren + numPendingChildren;
if (numChildren <= 1) {
return;
}
- boolean releasedChild = false;
- for (int i = 0; i < children.size(); i++) {
- NotificationEntry entry = children.get(i);
- if (onlySummaryAlerts(entry) && mHeadsUpManager.isAlerting(entry.getKey())) {
- releasedChild = true;
- mHeadsUpManager.removeNotification(
- entry.getKey(), true /* releaseImmediately */);
- }
- if (mPendingAlerts.containsKey(entry.getKey())) {
- // This is the child that would've been removed if it was inflated.
- releasedChild = true;
- mPendingAlerts.get(entry.getKey()).mAbortOnInflation = true;
- }
- }
+ boolean releasedChild = releaseChildAlerts(children);
if (releasedChild && !mHeadsUpManager.isAlerting(summary.getKey())) {
- boolean notifyImmediately = (numChildren - numPendingChildren) > 1;
+ boolean notifyImmediately = numActiveChildren > 1;
if (notifyImmediately) {
- alertNotificationWhenPossible(summary, mHeadsUpManager);
+ alertNotificationWhenPossible(summary);
} else {
// Should wait until the pending child inflates before alerting.
groupAlertEntry.mAlertSummaryOnNextAddition = true;
@@ -371,25 +536,61 @@ public class NotificationGroupAlertTransferHelper implements OnHeadsUpChangedLis
}
}
+ private boolean canStillTransferBack(@NonNull GroupAlertEntry groupAlertEntry) {
+ return SystemClock.elapsedRealtime() - groupAlertEntry.mLastAlertTransferTime
+ < ALERT_TRANSFER_TIMEOUT;
+ }
+
+ private boolean releaseChildAlerts(List<NotificationEntry> children) {
+ boolean releasedChild = false;
+ if (SPEW) {
+ Log.d(TAG, "releaseChildAlerts: numChildren=" + children.size());
+ }
+ for (int i = 0; i < children.size(); i++) {
+ NotificationEntry entry = children.get(i);
+ if (SPEW) {
+ Log.d(TAG, "releaseChildAlerts: checking i=" + i + " entry=" + entry
+ + " onlySummaryAlerts=" + onlySummaryAlerts(entry)
+ + " isAlerting=" + mHeadsUpManager.isAlerting(entry.getKey())
+ + " isPendingAlert=" + mPendingAlerts.containsKey(entry.getKey()));
+ }
+ if (onlySummaryAlerts(entry) && mHeadsUpManager.isAlerting(entry.getKey())) {
+ releasedChild = true;
+ mHeadsUpManager.removeNotification(
+ entry.getKey(), true /* releaseImmediately */);
+ }
+ if (mPendingAlerts.containsKey(entry.getKey())) {
+ // This is the child that would've been removed if it was inflated.
+ releasedChild = true;
+ mPendingAlerts.get(entry.getKey()).mAbortOnInflation = true;
+ }
+ }
+ if (SPEW) {
+ Log.d(TAG, "releaseChildAlerts: didRelease=" + releasedChild);
+ }
+ return releasedChild;
+ }
+
/**
* Tries to alert the notification. If its content view is not inflated, we inflate and continue
* when the entry finishes inflating the view.
*
* @param entry entry to show
- * @param alertManager alert manager for the alert type
*/
- private void alertNotificationWhenPossible(@NonNull NotificationEntry entry,
- @NonNull AlertingNotificationManager alertManager) {
- @InflationFlag int contentFlag = alertManager.getContentFlag();
+ private void alertNotificationWhenPossible(@NonNull NotificationEntry entry) {
+ @InflationFlag int contentFlag = mHeadsUpManager.getContentFlag();
final RowContentBindParams params = mRowContentBindStage.getStageParams(entry);
if ((params.getContentViews() & contentFlag) == 0) {
+ if (DEBUG) {
+ Log.d(TAG, "alertNotificationWhenPossible: async requestRebind entry=" + entry);
+ }
mPendingAlerts.put(entry.getKey(), new PendingAlertInfo(entry));
params.requireContentViews(contentFlag);
mRowContentBindStage.requestRebind(entry, en -> {
PendingAlertInfo alertInfo = mPendingAlerts.remove(entry.getKey());
if (alertInfo != null) {
if (alertInfo.isStillValid()) {
- alertNotificationWhenPossible(entry, mHeadsUpManager);
+ alertNotificationWhenPossible(entry);
} else {
// The transfer is no longer valid. Free the content.
mRowContentBindStage.getStageParams(entry).markContentViewsFreeable(
@@ -400,10 +601,16 @@ public class NotificationGroupAlertTransferHelper implements OnHeadsUpChangedLis
});
return;
}
- if (alertManager.isAlerting(entry.getKey())) {
- alertManager.updateNotification(entry.getKey(), true /* alert */);
+ if (mHeadsUpManager.isAlerting(entry.getKey())) {
+ if (DEBUG) {
+ Log.d(TAG, "alertNotificationWhenPossible: continue alerting entry=" + entry);
+ }
+ mHeadsUpManager.updateNotification(entry.getKey(), true /* alert */);
} else {
- alertManager.showNotification(entry);
+ if (DEBUG) {
+ Log.d(TAG, "alertNotificationWhenPossible: start alerting entry=" + entry);
+ }
+ mHeadsUpManager.showNotification(entry);
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
index 137b7226f137..6ef4663ea5f3 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
@@ -1412,7 +1412,8 @@ public class StatusBar extends SystemUI implements DemoMode,
mNotificationAnimationProvider = new NotificationLaunchAnimatorControllerProvider(
mNotificationShadeWindowViewController,
mStackScrollerController.getNotificationListContainer(),
- mNotificationShadeDepthControllerLazy.get()
+ mNotificationShadeDepthControllerLazy.get(),
+ mHeadsUpManager
);
// TODO: inject this.
@@ -2785,7 +2786,7 @@ public class StatusBar extends SystemUI implements DemoMode,
intent, mLockscreenUserManager.getCurrentUserId());
ActivityLaunchAnimator.Controller animController = null;
- if (animationController != null && areLaunchAnimationsEnabled()) {
+ if (animationController != null) {
animController = dismissShade ? new StatusBarLaunchAnimatorController(
animationController, this, true /* isLaunchForActivity */)
: animationController;
@@ -2801,46 +2802,48 @@ public class StatusBar extends SystemUI implements DemoMode,
intent.setFlags(
Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TOP);
intent.addFlags(flags);
- int[] result = new int[] { ActivityManager.START_CANCELED };
-
- mActivityLaunchAnimator.startIntentWithAnimation(animCallbackForLambda, (adapter) -> {
- ActivityOptions options = new ActivityOptions(
- getActivityOptions(mDisplayId, adapter));
- options.setDisallowEnterPictureInPictureWhileLaunching(
- disallowEnterPictureInPictureWhileLaunching);
- if (CameraIntents.isInsecureCameraIntent(intent)) {
- // Normally an activity will set it's requested rotation
- // animation on its window. However when launching an activity
- // causes the orientation to change this is too late. In these cases
- // the default animation is used. This doesn't look good for
- // the camera (as it rotates the camera contents out of sync
- // with physical reality). So, we ask the WindowManager to
- // force the crossfade animation if an orientation change
- // happens to occur during the launch.
- options.setRotationAnimationHint(
- WindowManager.LayoutParams.ROTATION_ANIMATION_SEAMLESS);
- }
- if (intent.getAction() == Settings.Panel.ACTION_VOLUME) {
- // Settings Panel is implemented as activity(not a dialog), so
- // underlying app is paused and may enter picture-in-picture mode
- // as a result.
- // So we need to disable picture-in-picture mode here
- // if it is volume panel.
- options.setDisallowEnterPictureInPictureWhileLaunching(true);
- }
+ int[] result = new int[]{ActivityManager.START_CANCELED};
+
+ mActivityLaunchAnimator.startIntentWithAnimation(animCallbackForLambda,
+ areLaunchAnimationsEnabled(), (adapter) -> {
+ ActivityOptions options = new ActivityOptions(
+ getActivityOptions(mDisplayId, adapter));
+ options.setDisallowEnterPictureInPictureWhileLaunching(
+ disallowEnterPictureInPictureWhileLaunching);
+ if (CameraIntents.isInsecureCameraIntent(intent)) {
+ // Normally an activity will set it's requested rotation
+ // animation on its window. However when launching an activity
+ // causes the orientation to change this is too late. In these cases
+ // the default animation is used. This doesn't look good for
+ // the camera (as it rotates the camera contents out of sync
+ // with physical reality). So, we ask the WindowManager to
+ // force the crossfade animation if an orientation change
+ // happens to occur during the launch.
+ options.setRotationAnimationHint(
+ WindowManager.LayoutParams.ROTATION_ANIMATION_SEAMLESS);
+ }
+ if (intent.getAction() == Settings.Panel.ACTION_VOLUME) {
+ // Settings Panel is implemented as activity(not a dialog), so
+ // underlying app is paused and may enter picture-in-picture mode
+ // as a result.
+ // So we need to disable picture-in-picture mode here
+ // if it is volume panel.
+ options.setDisallowEnterPictureInPictureWhileLaunching(true);
+ }
- try {
- result[0] = ActivityTaskManager.getService().startActivityAsUser(
- null, mContext.getBasePackageName(), mContext.getAttributionTag(),
- intent,
- intent.resolveTypeIfNeeded(mContext.getContentResolver()),
- null, null, 0, Intent.FLAG_ACTIVITY_NEW_TASK, null,
- options.toBundle(), UserHandle.CURRENT.getIdentifier());
- } catch (RemoteException e) {
- Log.w(TAG, "Unable to start activity", e);
- }
- return result[0];
- });
+ try {
+ result[0] = ActivityTaskManager.getService().startActivityAsUser(
+ null, mContext.getBasePackageName(),
+ mContext.getAttributionTag(),
+ intent,
+ intent.resolveTypeIfNeeded(mContext.getContentResolver()),
+ null, null, 0, Intent.FLAG_ACTIVITY_NEW_TASK, null,
+ options.toBundle(), UserHandle.CURRENT.getIdentifier());
+ } catch (RemoteException e) {
+ Log.w(TAG, "Unable to start activity", e);
+ }
+ return result[0];
+ });
if (callback != null) {
callback.onActivityStarted(result[0]);
@@ -4559,19 +4562,17 @@ public class StatusBar extends SystemUI implements DemoMode,
&& mActivityIntentHelper.wouldLaunchResolverActivity(intent.getIntent(),
mLockscreenUserManager.getCurrentUserId());
- boolean animate = animationController != null && areLaunchAnimationsEnabled();
- boolean collapse = !animate;
+ boolean collapse = animationController == null;
executeActionDismissingKeyguard(() -> {
try {
// We wrap animationCallback with a StatusBarLaunchAnimatorController so that the
// shade is collapsed after the animation (or when it is cancelled, aborted, etc).
ActivityLaunchAnimator.Controller controller =
- animate ? new StatusBarLaunchAnimatorController(animationController, this,
- intent.isActivity())
- : null;
+ animationController != null ? new StatusBarLaunchAnimatorController(
+ animationController, this, intent.isActivity()) : null;
mActivityLaunchAnimator.startPendingIntentWithAnimation(
- controller,
+ controller, areLaunchAnimationsEnabled(),
(animationAdapter) -> intent.sendAndReturnResult(null, 0, null, null, null,
null, getActivityOptions(mDisplayId, animationAdapter)));
} catch (PendingIntent.CanceledException e) {
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 4356b52d27ea..ab58aae6857e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarter.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarter.java
@@ -283,7 +283,13 @@ public class StatusBarNotificationActivityStarter implements NotificationActivit
mLogger.logHandleClickAfterKeyguardDismissed(entry.getKey());
// TODO: Some of this code may be able to move to NotificationEntryManager.
- removeHUN(row);
+ String key = row.getEntry().getSbn().getKey();
+ if (mHeadsUpManager != null && mHeadsUpManager.isAlerting(key)) {
+ // Release the HUN notification to the shade.
+ if (mPresenter.isPresenterFullyCollapsed()) {
+ HeadsUpUtil.setIsClickedHeadsUpNotification(row, true);
+ }
+ }
final Runnable runnable = () -> handleNotificationClickAfterPanelCollapsed(
entry, row, controller, intent,
@@ -331,6 +337,7 @@ public class StatusBarNotificationActivityStarter implements NotificationActivit
// bypass work challenge
if (mStatusBarRemoteInputCallback.startWorkChallengeIfNecessary(userId,
intent.getIntentSender(), notificationKey)) {
+ removeHUN(row);
// Show work challenge, do not run PendingIntent and
// remove notification
collapseOnMainThread();
@@ -350,6 +357,7 @@ public class StatusBarNotificationActivityStarter implements NotificationActivit
final boolean canBubble = entry.canBubble();
if (canBubble) {
mLogger.logExpandingBubble(notificationKey);
+ removeHUN(row);
expandBubbleStackOnMainThread(entry);
} else {
startNotificationIntent(
@@ -422,14 +430,13 @@ public class StatusBarNotificationActivityStarter implements NotificationActivit
boolean isActivityIntent) {
mLogger.logStartNotificationIntent(entry.getKey(), intent);
try {
- ActivityLaunchAnimator.Controller animationController = null;
- if (!wasOccluded && mStatusBar.areLaunchAnimationsEnabled()) {
- animationController = new StatusBarLaunchAnimatorController(
- mNotificationAnimationProvider.getAnimatorController(row), mStatusBar,
- isActivityIntent);
- }
+ ActivityLaunchAnimator.Controller animationController =
+ new StatusBarLaunchAnimatorController(
+ mNotificationAnimationProvider.getAnimatorController(row), mStatusBar,
+ isActivityIntent);
mActivityLaunchAnimator.startPendingIntentWithAnimation(animationController,
+ !wasOccluded && mStatusBar.areLaunchAnimationsEnabled(),
(adapter) -> {
long eventTime = row.getAndResetLastActionUpTime();
Bundle options = eventTime > 0
@@ -442,13 +449,6 @@ public class StatusBarNotificationActivityStarter implements NotificationActivit
return intent.sendAndReturnResult(mContext, 0, fillInIntent, null,
null, null, options);
});
-
- // Note that other cases when we should still collapse (like activity already on top) is
- // handled by the StatusBarLaunchAnimatorController.
- boolean shouldCollapse = animationController == null;
- if (shouldCollapse) {
- collapseOnMainThread();
- }
} catch (PendingIntent.CanceledException e) {
// the stack trace isn't very helpful here.
// Just log the exception message.
@@ -462,34 +462,19 @@ public class StatusBarNotificationActivityStarter implements NotificationActivit
ExpandableNotificationRow row) {
mActivityStarter.dismissKeyguardThenExecute(() -> {
AsyncTask.execute(() -> {
- ActivityLaunchAnimator.Controller animationController = null;
- if (mStatusBar.areLaunchAnimationsEnabled()) {
- animationController = new StatusBarLaunchAnimatorController(
- mNotificationAnimationProvider.getAnimatorController(row), mStatusBar,
- true /* isActivityIntent */);
- }
+ ActivityLaunchAnimator.Controller animationController =
+ new StatusBarLaunchAnimatorController(
+ mNotificationAnimationProvider.getAnimatorController(row),
+ mStatusBar, true /* isActivityIntent */);
mActivityLaunchAnimator.startIntentWithAnimation(
- animationController,
+ animationController, mStatusBar.areLaunchAnimationsEnabled(),
(adapter) -> TaskStackBuilder.create(mContext)
.addNextIntentWithParentStack(intent)
.startActivities(getActivityOptions(
mStatusBar.getDisplayId(),
adapter),
new UserHandle(UserHandle.getUserId(appUid))));
-
- // Note that other cases when we should still collapse (like activity already on
- // top) is handled by the StatusBarLaunchAnimatorController.
- boolean shouldCollapse = animationController == null;
-
- // Putting it back on the main thread, since we're touching views
- mMainThreadHandler.post(() -> {
- removeHUN(row);
- if (shouldCollapse) {
- mCommandQueue.animateCollapsePanels(CommandQueue.FLAG_EXCLUDE_RECENTS_PANEL,
- true /* force */);
- }
- });
});
return true;
}, null, false /* afterKeyguardGone */);
@@ -508,26 +493,16 @@ public class StatusBarNotificationActivityStarter implements NotificationActivit
tsb.addNextIntent(intent);
}
- ActivityLaunchAnimator.Controller animationController = null;
- if (mStatusBar.areLaunchAnimationsEnabled()) {
- animationController = new StatusBarLaunchAnimatorController(
- ActivityLaunchAnimator.Controller.fromView(view), mStatusBar,
- true /* isActivityIntent */);
- }
+ ActivityLaunchAnimator.Controller animationController =
+ new StatusBarLaunchAnimatorController(
+ ActivityLaunchAnimator.Controller.fromView(view), mStatusBar,
+ true /* isActivityIntent */);
mActivityLaunchAnimator.startIntentWithAnimation(animationController,
+ mStatusBar.areLaunchAnimationsEnabled(),
(adapter) -> tsb.startActivities(
getActivityOptions(mStatusBar.getDisplayId(), adapter),
UserHandle.CURRENT));
-
- // Note that other cases when we should still collapse (like activity already on
- // top) is handled by the StatusBarLaunchAnimatorController.
- boolean shouldCollapse = animationController == null;
- if (shouldCollapse) {
- // Putting it back on the main thread, since we're touching views
- mMainThreadHandler.post(() -> mCommandQueue.animateCollapsePanels(
- CommandQueue.FLAG_EXCLUDE_RECENTS_PANEL, true /* force */));
- }
});
return true;
}, null, false /* afterKeyguardGone */);
@@ -536,11 +511,6 @@ public class StatusBarNotificationActivityStarter implements NotificationActivit
private void removeHUN(ExpandableNotificationRow row) {
String key = row.getEntry().getSbn().getKey();
if (mHeadsUpManager != null && mHeadsUpManager.isAlerting(key)) {
- // Release the HUN notification to the shade.
- if (mPresenter.isPresenterFullyCollapsed()) {
- HeadsUpUtil.setIsClickedHeadsUpNotification(row, true);
- }
-
// In most cases, when FLAG_AUTO_CANCEL is set, the notification will
// become canceled shortly by NoMan, but we can't assume that.
mHeadsUpManager.removeNotification(key, true /* releaseImmediately */);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/SmartReplyStateInflater.kt b/packages/SystemUI/src/com/android/systemui/statusbar/policy/SmartReplyStateInflater.kt
index 0bf2d503e5a0..e3e2572b9b4c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/SmartReplyStateInflater.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/SmartReplyStateInflater.kt
@@ -341,7 +341,6 @@ interface SmartActionInflater {
activityStarter.startPendingIntentDismissingKeyguard(action.actionIntent, entry.row) {
smartReplyController
.smartActionClicked(entry, actionIndex, action, smartActions.fromAssistant)
- headsUpManager.removeNotification(entry.key, true /* releaseImmediately */)
}
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/AccessibilityTargetAdapterTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/AccessibilityTargetAdapterTest.java
index 899625eee7d9..afd5f77f7a4c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/AccessibilityTargetAdapterTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/AccessibilityTargetAdapterTest.java
@@ -65,9 +65,9 @@ public class AccessibilityTargetAdapterTest extends SysuiTestCase {
mTargets.add(mAccessibilityTarget);
mAdapter = new AccessibilityTargetAdapter(mTargets);
- final View root = LayoutInflater.from(mContext).inflate(
+ final View rootView = LayoutInflater.from(mContext).inflate(
R.layout.accessibility_floating_menu_item, null);
- mViewHolder = new ViewHolder(root);
+ mViewHolder = new ViewHolder(rootView);
when(mAccessibilityTarget.getIcon()).thenReturn(mIcon);
when(mIcon.getConstantState()).thenReturn(mConstantState);
}
@@ -82,4 +82,27 @@ public class AccessibilityTargetAdapterTest extends SysuiTestCase {
assertThat(actualIconWith).isEqualTo(iconWidthHeight);
}
+
+ @Test
+ public void getContentDescription_invisibleToggleTarget_descriptionWithoutState() {
+ when(mAccessibilityTarget.getFragmentType()).thenReturn(/* InvisibleToggle */ 1);
+ when(mAccessibilityTarget.getLabel()).thenReturn("testLabel");
+ when(mAccessibilityTarget.getStateDescription()).thenReturn("testState");
+
+ mAdapter.onBindViewHolder(mViewHolder, 0);
+
+ assertThat(mViewHolder.itemView.getContentDescription().toString().contentEquals(
+ "testLabel")).isTrue();
+ }
+
+ @Test
+ public void getStateDescription_toggleTarget_switchOff_stateOffText() {
+ when(mAccessibilityTarget.getFragmentType()).thenReturn(/* Toggle */ 2);
+ when(mAccessibilityTarget.getStateDescription()).thenReturn("testState");
+
+ mAdapter.onBindViewHolder(mViewHolder, 0);
+
+ assertThat(mViewHolder.itemView.getStateDescription().toString().contentEquals(
+ "testState")).isTrue();
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/animation/ActivityLaunchAnimatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/animation/ActivityLaunchAnimatorTest.kt
index c023610b6052..fbba09a255e7 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/animation/ActivityLaunchAnimatorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/animation/ActivityLaunchAnimatorTest.kt
@@ -44,12 +44,13 @@ class ActivityLaunchAnimatorTest : SysuiTestCase() {
private fun startIntentWithAnimation(
controller: ActivityLaunchAnimator.Controller? = this.controller,
+ animate: Boolean = true,
intentStarter: (RemoteAnimationAdapter?) -> Int
) {
// We start in a new thread so that we can ensure that the callbacks are called in the main
// thread.
thread {
- activityLaunchAnimator.startIntentWithAnimation(controller, intentStarter)
+ activityLaunchAnimator.startIntentWithAnimation(controller, animate, intentStarter)
}.join()
}
@@ -95,6 +96,16 @@ class ActivityLaunchAnimatorTest : SysuiTestCase() {
}
@Test
+ fun doesNotAnimateIfAnimateIsFalse() {
+ val willAnimateCaptor = ArgumentCaptor.forClass(Boolean::class.java)
+ startIntentWithAnimation(animate = false) { ActivityManager.START_SUCCESS }
+
+ waitForIdleSync()
+ verify(controller).onIntentStarted(willAnimateCaptor.capture())
+ assertFalse(willAnimateCaptor.value)
+ }
+
+ @Test
fun doesNotStartIfAnimationIsCancelled() {
val runner = activityLaunchAnimator.createRunner(controller)
runner.onAnimationCancelled()
diff --git a/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarRotationContextTest.java b/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarRotationContextTest.java
index 47f41836f1a6..b85af4846390 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarRotationContextTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarRotationContextTest.java
@@ -62,8 +62,8 @@ public class NavigationBarRotationContextTest extends SysuiTestCase {
final View view = new View(mContext);
mRotationButton = mock(RotationButton.class);
- mRotationButtonController = spy(new RotationButtonController(mContext, 0, 0,
- mRotationButton, (visibility) -> {}));
+ mRotationButtonController = spy(new RotationButtonController(mContext, 0, 0));
+ mRotationButtonController.setRotationButton(mRotationButton, (visibility) -> {});
final KeyButtonDrawable kbd = mock(KeyButtonDrawable.class);
doReturn(view).when(mRotationButton).getCurrentView();
doReturn(true).when(mRotationButton).acceptRotationProposal();
diff --git a/packages/SystemUI/tests/src/com/android/systemui/people/PeopleTileViewHelperTest.java b/packages/SystemUI/tests/src/com/android/systemui/people/PeopleTileViewHelperTest.java
index d353d529865b..764cdee7e36d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/people/PeopleTileViewHelperTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/people/PeopleTileViewHelperTest.java
@@ -76,6 +76,14 @@ public class PeopleTileViewHelperTest extends SysuiTestCase {
private static final String NAME = "username";
private static final UserHandle USER = new UserHandle(0);
private static final String SENDER = "sender";
+
+ private static final CharSequence EMOJI_BR_FLAG = "\ud83c\udde7\ud83c\uddf7";
+ private static final CharSequence EMOJI_BEAR = "\ud83d\udc3b";
+ private static final CharSequence EMOJI_THUMBS_UP_BROWN_SKIN = "\uD83D\uDC4D\uD83C\uDFFD";
+ private static final CharSequence EMOJI_JOY = "\uD83D\uDE02";
+ private static final CharSequence EMOJI_FAMILY =
+ "\ud83d\udc69\u200d\ud83d\udc69\u200d\ud83d\udc67\u200d\ud83d\udc67";
+
private static final PeopleSpaceTile PERSON_TILE_WITHOUT_NOTIFICATION =
new PeopleSpaceTile
.Builder(SHORTCUT_ID_1, NAME, ICON, new Intent())
@@ -701,94 +709,151 @@ public class PeopleTileViewHelperTest extends SysuiTestCase {
@Test
- public void testGetBackgroundTextFromMessageNoPunctuation() {
- String backgroundText = mPeopleTileViewHelper.getBackgroundTextFromMessage("test");
+ public void testGetDoublePunctuationNoPunctuation() {
+ CharSequence backgroundText = mPeopleTileViewHelper.getDoublePunctuation("test");
assertThat(backgroundText).isNull();
}
@Test
- public void testGetBackgroundTextFromMessageSingleExclamation() {
- String backgroundText = mPeopleTileViewHelper.getBackgroundTextFromMessage("test!");
+ public void testGetDoublePunctuationSingleExclamation() {
+ CharSequence backgroundText = mPeopleTileViewHelper.getDoublePunctuation("test!");
assertThat(backgroundText).isNull();
}
@Test
- public void testGetBackgroundTextFromMessageSingleQuestion() {
- String backgroundText = mPeopleTileViewHelper.getBackgroundTextFromMessage("?test");
+ public void testGetDoublePunctuationSingleQuestion() {
+ CharSequence backgroundText = mPeopleTileViewHelper.getDoublePunctuation("?test");
assertThat(backgroundText).isNull();
}
@Test
- public void testGetBackgroundTextFromMessageSeparatedMarks() {
- String backgroundText = mPeopleTileViewHelper.getBackgroundTextFromMessage("test! right!");
+ public void testGetDoublePunctuationSeparatedMarks() {
+ CharSequence backgroundText = mPeopleTileViewHelper.getDoublePunctuation("test! right!");
assertThat(backgroundText).isNull();
}
@Test
- public void testGetBackgroundTextFromMessageDoubleExclamation() {
- String backgroundText = mPeopleTileViewHelper.getBackgroundTextFromMessage("!!test");
+ public void testGetDoublePunctuationDoubleExclamation() {
+ CharSequence backgroundText = mPeopleTileViewHelper.getDoublePunctuation("!!test");
assertThat(backgroundText).isEqualTo("!");
}
@Test
- public void testGetBackgroundTextFromMessageDoubleQuestion() {
- String backgroundText = mPeopleTileViewHelper.getBackgroundTextFromMessage("test??");
+ public void testGetDoublePunctuationDoubleQuestion() {
+ CharSequence backgroundText = mPeopleTileViewHelper.getDoublePunctuation("test??");
assertThat(backgroundText).isEqualTo("?");
}
@Test
- public void testGetBackgroundTextFromMessageMixed() {
- String backgroundText = mPeopleTileViewHelper.getBackgroundTextFromMessage("test?!");
+ public void testGetDoublePunctuationMixed() {
+ CharSequence backgroundText = mPeopleTileViewHelper.getDoublePunctuation("test?!");
assertThat(backgroundText).isEqualTo("!?");
}
@Test
- public void testGetBackgroundTextFromMessageMixedInTheMiddle() {
- String backgroundText = mPeopleTileViewHelper.getBackgroundTextFromMessage(
+ public void testGetDoublePunctuationMixedInTheMiddle() {
+ CharSequence backgroundText = mPeopleTileViewHelper.getDoublePunctuation(
"test!? in the middle");
assertThat(backgroundText).isEqualTo("!?");
}
@Test
- public void testGetBackgroundTextFromMessageMixedDifferentOrder() {
- String backgroundText = mPeopleTileViewHelper.getBackgroundTextFromMessage(
+ public void testGetDoublePunctuationMixedDifferentOrder() {
+ CharSequence backgroundText = mPeopleTileViewHelper.getDoublePunctuation(
"test!? in the middle");
assertThat(backgroundText).isEqualTo("!?");
}
@Test
- public void testGetBackgroundTextFromMessageMultiple() {
- String backgroundText = mPeopleTileViewHelper.getBackgroundTextFromMessage(
+ public void testGetDoublePunctuationMultiple() {
+ CharSequence backgroundText = mPeopleTileViewHelper.getDoublePunctuation(
"test!?!!? in the middle");
assertThat(backgroundText).isEqualTo("!?");
}
@Test
- public void testGetBackgroundTextFromMessageQuestionFirst() {
- String backgroundText = mPeopleTileViewHelper.getBackgroundTextFromMessage(
+ public void testGetDoublePunctuationQuestionFirst() {
+ CharSequence backgroundText = mPeopleTileViewHelper.getDoublePunctuation(
"test?? in the middle!!");
assertThat(backgroundText).isEqualTo("?");
}
@Test
- public void testGetBackgroundTextFromMessageExclamationFirst() {
- String backgroundText = mPeopleTileViewHelper.getBackgroundTextFromMessage(
+ public void testGetDoublePunctuationExclamationFirst() {
+ CharSequence backgroundText = mPeopleTileViewHelper.getDoublePunctuation(
"test!! in the middle??");
assertThat(backgroundText).isEqualTo("!");
}
+ @Test
+ public void testGetDoubleEmojisNoEmojis() {
+ CharSequence backgroundText = mPeopleTileViewHelper
+ .getDoubleEmoji("This string has no emojis.");
+ assertThat(backgroundText).isNull();
+ }
+
+ @Test
+ public void testGetDoubleEmojisSingleEmoji() {
+ CharSequence backgroundText = mPeopleTileViewHelper.getDoubleEmoji(
+ "This string has one emoji " + EMOJI_JOY + " in the middle.");
+ assertThat(backgroundText).isNull();
+ }
+
+ @Test
+ public void testGetDoubleEmojisSingleEmojiThenTwoEmojis() {
+ CharSequence backgroundText = mPeopleTileViewHelper.getDoubleEmoji(
+ "This string has one emoji " + EMOJI_JOY + " in the middle, then two "
+ + EMOJI_BEAR + EMOJI_BEAR);
+ assertEquals(backgroundText, EMOJI_BEAR);
+ }
+
+ @Test
+ public void testGetDoubleEmojisTwoEmojisWithModifier() {
+ CharSequence backgroundText = mPeopleTileViewHelper.getDoubleEmoji(
+ "Yes! " + EMOJI_THUMBS_UP_BROWN_SKIN + EMOJI_THUMBS_UP_BROWN_SKIN + " Sure.");
+ assertEquals(backgroundText, EMOJI_THUMBS_UP_BROWN_SKIN);
+ }
+
+ @Test
+ public void testGetDoubleEmojisTwoFlagEmojis() {
+ CharSequence backgroundText = mPeopleTileViewHelper.getDoubleEmoji(
+ "Let's travel to " + EMOJI_BR_FLAG + EMOJI_BR_FLAG + " next year.");
+ assertEquals(backgroundText, EMOJI_BR_FLAG);
+ }
+
+ @Test
+ public void testGetDoubleEmojiTwoBears() {
+ CharSequence backgroundText = mPeopleTileViewHelper.getDoubleEmoji(
+ EMOJI_BEAR.toString() + EMOJI_BEAR.toString() + "bears!");
+ assertEquals(backgroundText, EMOJI_BEAR);
+ }
+
+ @Test
+ public void testGetDoubleEmojiTwoEmojisTwice() {
+ CharSequence backgroundText = mPeopleTileViewHelper.getDoubleEmoji(
+ "Two sets of two emojis: " + EMOJI_FAMILY + EMOJI_FAMILY + EMOJI_BEAR + EMOJI_BEAR);
+ assertEquals(backgroundText, EMOJI_FAMILY);
+ }
+
+ @Test
+ public void testGetDoubleEmojiTwoEmojisSeparated() {
+ CharSequence backgroundText = mPeopleTileViewHelper.getDoubleEmoji(
+ "Two emojis " + EMOJI_BEAR + " separated " + EMOJI_BEAR + ".");
+ assertThat(backgroundText).isNull();
+ }
+
private int getSizeInDp(int dimenResourceId) {
return (int) (mContext.getResources().getDimension(dimenResourceId)
/ mContext.getResources().getDisplayMetrics().density);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/UserDetailViewAdapterTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/UserDetailViewAdapterTest.kt
index af75f2c66195..3a3d1546984d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/UserDetailViewAdapterTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/UserDetailViewAdapterTest.kt
@@ -67,6 +67,7 @@ class UserDetailViewAdapterTest : SysuiTestCase() {
mContext.addMockSystemService(Context.LAYOUT_INFLATER_SERVICE, mLayoutInflater)
`when`(mLayoutInflater.inflate(anyInt(), any(ViewGroup::class.java), anyBoolean()))
.thenReturn(mInflatedUserDetailItemView)
+ `when`(mParent.context).thenReturn(mContext)
adapter = UserDetailView.Adapter(mContext, mUserSwitcherController, uiEventLogger,
falsingManagerFake)
mPicture = UserIcons.convertToBitmap(mContext.getDrawable(R.drawable.ic_avatar_user))
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationRoundnessManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationRoundnessManagerTest.java
index 919ddcb488c2..0772c03d10d0 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationRoundnessManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationRoundnessManagerTest.java
@@ -315,8 +315,8 @@ public class NotificationRoundnessManagerTest extends SysuiTestCase {
@Test
public void testNoViewsFirstOrLastInSectionWhenSecondSectionEmpty() {
- Assert.assertFalse(mFirst.isFirstInSection());
- Assert.assertFalse(mFirst.isLastInSection());
+ Assert.assertTrue(mFirst.isFirstInSection());
+ Assert.assertTrue(mFirst.isLastInSection());
}
@Test
@@ -325,8 +325,8 @@ public class NotificationRoundnessManagerTest extends SysuiTestCase {
createSection(null, null),
createSection(mSecond, mSecond)
});
- Assert.assertFalse(mSecond.isFirstInSection());
- Assert.assertFalse(mSecond.isLastInSection());
+ Assert.assertTrue(mSecond.isFirstInSection());
+ Assert.assertTrue(mSecond.isLastInSection());
}
@Test
@@ -335,10 +335,10 @@ public class NotificationRoundnessManagerTest extends SysuiTestCase {
createSection(mFirst, mFirst),
createSection(mSecond, mSecond)
});
- Assert.assertFalse(mFirst.isFirstInSection());
+ Assert.assertTrue(mFirst.isFirstInSection());
Assert.assertTrue(mFirst.isLastInSection());
Assert.assertTrue(mSecond.isFirstInSection());
- Assert.assertFalse(mSecond.isLastInSection());
+ Assert.assertTrue(mSecond.isLastInSection());
}
private NotificationSection createSection(ExpandableNotificationRow first,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarterTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarterTest.java
index 5170168c174c..be86af585fc5 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarterTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarterTest.java
@@ -61,6 +61,7 @@ import com.android.systemui.statusbar.NotificationClickNotifier;
import com.android.systemui.statusbar.NotificationLockscreenUserManager;
import com.android.systemui.statusbar.NotificationPresenter;
import com.android.systemui.statusbar.NotificationRemoteInputManager;
+import com.android.systemui.statusbar.NotificationShadeDepthController;
import com.android.systemui.statusbar.RemoteInputController;
import com.android.systemui.statusbar.StatusBarState;
import com.android.systemui.statusbar.notification.NotificationActivityStarter;
@@ -73,6 +74,7 @@ import com.android.systemui.statusbar.notification.interruption.NotificationInte
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
import com.android.systemui.statusbar.notification.row.NotificationTestHelper;
import com.android.systemui.statusbar.notification.row.OnUserInteractionCallback;
+import com.android.systemui.statusbar.notification.stack.NotificationListContainer;
import com.android.systemui.statusbar.policy.KeyguardStateController;
import com.android.systemui.util.concurrency.FakeExecutor;
import com.android.systemui.util.time.FakeSystemClock;
@@ -184,6 +186,14 @@ public class StatusBarNotificationActivityStarterTest extends SysuiTestCase {
when(mOnUserInteractionCallback.getGroupSummaryToDismiss(mNotificationRow.getEntry()))
.thenReturn(null);
+ HeadsUpManagerPhone headsUpManager = mock(HeadsUpManagerPhone.class);
+ NotificationLaunchAnimatorControllerProvider notificationAnimationProvider =
+ new NotificationLaunchAnimatorControllerProvider(
+ mock(NotificationShadeWindowViewController.class), mock(
+ NotificationListContainer.class),
+ mock(NotificationShadeDepthController.class),
+ headsUpManager);
+
mNotificationActivityStarter =
new StatusBarNotificationActivityStarter.Builder(
getContext(),
@@ -192,7 +202,7 @@ public class StatusBarNotificationActivityStarterTest extends SysuiTestCase {
mUiBgExecutor,
mEntryManager,
mNotifPipeline,
- mock(HeadsUpManagerPhone.class),
+ headsUpManager,
mActivityStarter,
mClickNotifier,
mock(StatusBarStateController.class),
@@ -220,8 +230,7 @@ public class StatusBarNotificationActivityStarterTest extends SysuiTestCase {
.setNotificationPanelViewController(
mock(NotificationPanelViewController.class))
.setActivityLaunchAnimator(mActivityLaunchAnimator)
- .setNotificationAnimatorControllerProvider(
- mock(NotificationLaunchAnimatorControllerProvider.class))
+ .setNotificationAnimatorControllerProvider(notificationAnimationProvider)
.build();
// set up dismissKeyguardThenExecute to synchronously invoke the OnDismissAction arg
@@ -259,7 +268,8 @@ public class StatusBarNotificationActivityStarterTest extends SysuiTestCase {
// Then
verify(mShadeController, atLeastOnce()).collapsePanel();
- verify(mActivityLaunchAnimator).startPendingIntentWithAnimation(eq(null), any());
+ verify(mActivityLaunchAnimator).startPendingIntentWithAnimation(any(),
+ eq(false) /* animate */, any());
verify(mAssistManager).hideAssist();
diff --git a/services/core/java/com/android/server/StorageManagerService.java b/services/core/java/com/android/server/StorageManagerService.java
index 0c785da5c242..a1a4418b8d17 100644
--- a/services/core/java/com/android/server/StorageManagerService.java
+++ b/services/core/java/com/android/server/StorageManagerService.java
@@ -1850,7 +1850,7 @@ class StorageManagerService extends IStorageManager.Stub
public StorageManagerService(Context context) {
sSelf = this;
mVoldAppDataIsolationEnabled = SystemProperties.getBoolean(
- ANDROID_VOLD_APP_DATA_ISOLATION_ENABLED_PROPERTY, false);
+ ANDROID_VOLD_APP_DATA_ISOLATION_ENABLED_PROPERTY, true);
mContext = context;
mResolver = mContext.getContentResolver();
mCallbacks = new Callbacks(FgThread.get().getLooper());
diff --git a/services/core/java/com/android/server/UiModeManagerService.java b/services/core/java/com/android/server/UiModeManagerService.java
index d8af01eb7bda..ab3060ae4bce 100644
--- a/services/core/java/com/android/server/UiModeManagerService.java
+++ b/services/core/java/com/android/server/UiModeManagerService.java
@@ -1051,9 +1051,9 @@ final class UiModeManagerService extends SystemService {
}
private static void assertSingleProjectionType(@UiModeManager.ProjectionType int p) {
- // To be a single projection type it must be greater than zero and an exact power of two.
+ // To be a single projection type it must be non-zero and an exact power of two.
boolean projectionTypeIsPowerOfTwoOrZero = (p & p - 1) == 0;
- if (p <= 0 || !projectionTypeIsPowerOfTwoOrZero) {
+ if (p == 0 || !projectionTypeIsPowerOfTwoOrZero) {
throw new IllegalArgumentException("Must specify exactly one projection type.");
}
}
diff --git a/services/core/java/com/android/server/am/ActivityManagerConstants.java b/services/core/java/com/android/server/am/ActivityManagerConstants.java
index bf574521b895..d8eccef8488e 100644
--- a/services/core/java/com/android/server/am/ActivityManagerConstants.java
+++ b/services/core/java/com/android/server/am/ActivityManagerConstants.java
@@ -16,6 +16,9 @@
package com.android.server.am;
+import static android.os.PowerExemptionManager.TEMPORARY_ALLOW_LIST_TYPE_FOREGROUND_SERVICE_NOT_ALLOWED;
+import static android.os.PowerExemptionManager.TEMPORARY_ALLOW_LIST_TYPE_NONE;
+
import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_POWER_QUICK;
import android.app.ActivityThread;
@@ -27,6 +30,7 @@ import android.net.Uri;
import android.os.Build;
import android.os.Handler;
import android.os.Message;
+import android.os.PowerExemptionManager;
import android.provider.DeviceConfig;
import android.provider.DeviceConfig.OnPropertiesChangedListener;
import android.provider.DeviceConfig.Properties;
@@ -139,6 +143,11 @@ final class ActivityManagerConstants extends ContentObserver {
private static final long DEFAULT_FG_TO_BG_FGS_GRACE_DURATION = 5 * 1000;
private static final int DEFAULT_FGS_START_FOREGROUND_TIMEOUT_MS = 10 * 1000;
private static final float DEFAULT_FGS_ATOM_SAMPLE_RATE = 1; // 100 %
+ /**
+ * Same as {@link TEMPORARY_ALLOW_LIST_TYPE_FOREGROUND_SERVICE_NOT_ALLOWED}
+ */
+ private static final int
+ DEFAULT_PUSH_MESSAGING_OVER_QUOTA_BEHAVIOR = 1;
// Flag stored in the DeviceConfig API.
/**
@@ -210,6 +219,13 @@ final class ActivityManagerConstants extends ContentObserver {
private static final String KEY_DEFERRED_FGS_NOTIFICATION_EXCLUSION_TIME =
"deferred_fgs_notification_exclusion_time";
+ /**
+ * Default value for mPushMessagingOverQuotaBehavior if not explicitly set in
+ * Settings.Global.
+ */
+ private static final String KEY_PUSH_MESSAGING_OVER_QUOTA_BEHAVIOR =
+ "push_messaging_over_quota_behavior";
+
// Maximum number of cached processes we will allow.
public int MAX_CACHED_PROCESSES = DEFAULT_MAX_CACHED_PROCESSES;
@@ -413,6 +429,13 @@ final class ActivityManagerConstants extends ContentObserver {
// before another FGS notifiction from that app can be deferred.
volatile long mFgsNotificationDeferralExclusionTime = 2 * 60 * 1000L;
+ /**
+ * When server pushing message is over the quote, select one of the temp allow list type as
+ * defined in {@link PowerExemptionManager.TempAllowListType}
+ */
+ volatile @PowerExemptionManager.TempAllowListType int mPushMessagingOverQuotaBehavior =
+ DEFAULT_PUSH_MESSAGING_OVER_QUOTA_BEHAVIOR;
+
/*
* At boot time, broadcast receiver ACTION_BOOT_COMPLETED, ACTION_LOCKED_BOOT_COMPLETED and
* ACTION_PRE_BOOT_COMPLETED are temp allowlisted to start FGS for a duration of time in
@@ -605,6 +628,9 @@ final class ActivityManagerConstants extends ContentObserver {
case KEY_DEFERRED_FGS_NOTIFICATION_EXCLUSION_TIME:
updateFgsNotificationDeferralExclusionTime();
break;
+ case KEY_PUSH_MESSAGING_OVER_QUOTA_BEHAVIOR:
+ updatePushMessagingOverQuotaBehavior();
+ break;
case KEY_OOMADJ_UPDATE_POLICY:
updateOomAdjUpdatePolicy();
break;
@@ -909,6 +935,19 @@ final class ActivityManagerConstants extends ContentObserver {
/*default value*/ 2 * 60 * 1000L);
}
+ private void updatePushMessagingOverQuotaBehavior() {
+ mPushMessagingOverQuotaBehavior = DeviceConfig.getInt(
+ DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
+ KEY_PUSH_MESSAGING_OVER_QUOTA_BEHAVIOR,
+ DEFAULT_PUSH_MESSAGING_OVER_QUOTA_BEHAVIOR);
+ if (mPushMessagingOverQuotaBehavior < TEMPORARY_ALLOW_LIST_TYPE_NONE
+ || mPushMessagingOverQuotaBehavior
+ > TEMPORARY_ALLOW_LIST_TYPE_FOREGROUND_SERVICE_NOT_ALLOWED) {
+ mPushMessagingOverQuotaBehavior =
+ DEFAULT_PUSH_MESSAGING_OVER_QUOTA_BEHAVIOR;
+ }
+ }
+
private void updateOomAdjUpdatePolicy() {
OOMADJ_UPDATE_QUICK = DeviceConfig.getInt(
DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
@@ -1166,6 +1205,8 @@ final class ActivityManagerConstants extends ContentObserver {
pw.print("="); pw.println(mFgsStartRestrictionCheckCallerTargetSdk);
pw.print(" "); pw.print(KEY_FGS_ATOM_SAMPLE_RATE);
pw.print("="); pw.println(mDefaultFgsAtomSampleRate);
+ pw.print(" "); pw.print(KEY_PUSH_MESSAGING_OVER_QUOTA_BEHAVIOR);
+ pw.print("="); pw.println(mPushMessagingOverQuotaBehavior);
pw.println();
if (mOverrideMaxCachedProcesses >= 0) {
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index 00b13b1bb6b0..9aedf1504df5 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -51,7 +51,8 @@ import static android.os.IServiceManager.DUMP_FLAG_PRIORITY_NORMAL;
import static android.os.IServiceManager.DUMP_FLAG_PROTO;
import static android.os.InputConstants.DEFAULT_DISPATCHING_TIMEOUT_MILLIS;
import static android.os.PowerExemptionManager.REASON_SYSTEM_ALLOW_LISTED;
-import static android.os.PowerWhitelistManager.TEMPORARY_ALLOWLIST_TYPE_FOREGROUND_SERVICE_ALLOWED;
+import static android.os.PowerExemptionManager.TEMPORARY_ALLOW_LIST_TYPE_FOREGROUND_SERVICE_ALLOWED;
+import static android.os.PowerExemptionManager.TEMPORARY_ALLOW_LIST_TYPE_NONE;
import static android.os.Process.BLUETOOTH_UID;
import static android.os.Process.FIRST_APPLICATION_UID;
import static android.os.Process.INVALID_UID;
@@ -14603,15 +14604,20 @@ public class ActivityManagerService extends IActivityManager.Stub
*/
@GuardedBy("this")
void tempAllowlistUidLocked(int targetUid, long duration, @ReasonCode int reasonCode,
- String reason, int type, int callingUid) {
+ String reason, @TempAllowListType int type, int callingUid) {
synchronized (mProcLock) {
+ // The temp allowlist type could change according to the reasonCode.
+ type = mLocalDeviceIdleController.getTempAllowListType(reasonCode, type);
+ if (type == TEMPORARY_ALLOW_LIST_TYPE_NONE) {
+ return;
+ }
mPendingTempAllowlist.put(targetUid,
new PendingTempAllowlist(targetUid, duration, reasonCode, reason, type,
callingUid));
setUidTempAllowlistStateLSP(targetUid, true);
mUiHandler.obtainMessage(PUSH_TEMP_ALLOWLIST_UI_MSG).sendToTarget();
- if (type == TEMPORARY_ALLOWLIST_TYPE_FOREGROUND_SERVICE_ALLOWED) {
+ if (type == TEMPORARY_ALLOW_LIST_TYPE_FOREGROUND_SERVICE_ALLOWED) {
mFgsStartTempAllowList.add(targetUid, duration,
new FgsTempAllowListItem(duration, reasonCode, reason, callingUid));
}
@@ -15285,7 +15291,7 @@ public class ActivityManagerService extends IActivityManager.Stub
synchronized (mProcLock) {
mDeviceIdleTempAllowlist = appids;
if (adding) {
- if (type == TEMPORARY_ALLOWLIST_TYPE_FOREGROUND_SERVICE_ALLOWED) {
+ if (type == TEMPORARY_ALLOW_LIST_TYPE_FOREGROUND_SERVICE_ALLOWED) {
mFgsStartTempAllowList.add(changingUid, durationMs,
new FgsTempAllowListItem(durationMs, reasonCode, reason,
callingUid));
@@ -16152,6 +16158,13 @@ public class ActivityManagerService extends IActivityManager.Stub
return mServices.canAllowWhileInUsePermissionInFgsLocked(pid, uid, packageName);
}
}
+
+ @Override
+ public @TempAllowListType int getPushMessagingOverQuotaBehavior() {
+ synchronized (ActivityManagerService.this) {
+ return mConstants.mPushMessagingOverQuotaBehavior;
+ }
+ }
}
long inputDispatchingTimedOut(int pid, final boolean aboveSystem, String reason) {
diff --git a/services/core/java/com/android/server/am/ProcessList.java b/services/core/java/com/android/server/am/ProcessList.java
index 5a9c4dea4703..2a39326d6b92 100644
--- a/services/core/java/com/android/server/am/ProcessList.java
+++ b/services/core/java/com/android/server/am/ProcessList.java
@@ -792,7 +792,7 @@ public final class ProcessList {
mAppDataIsolationEnabled =
SystemProperties.getBoolean(ANDROID_APP_DATA_ISOLATION_ENABLED_PROPERTY, true);
mVoldAppDataIsolationEnabled = SystemProperties.getBoolean(
- ANDROID_VOLD_APP_DATA_ISOLATION_ENABLED_PROPERTY, false);
+ ANDROID_VOLD_APP_DATA_ISOLATION_ENABLED_PROPERTY, true);
mAppDataIsolationAllowlistedApps = new ArrayList<>(
SystemConfig.getInstance().getAppDataIsolationWhitelistedApps());
diff --git a/services/core/java/com/android/server/apphibernation/AppHibernationService.java b/services/core/java/com/android/server/apphibernation/AppHibernationService.java
index 52388ff2877c..9396241c3f98 100644
--- a/services/core/java/com/android/server/apphibernation/AppHibernationService.java
+++ b/services/core/java/com/android/server/apphibernation/AppHibernationService.java
@@ -393,6 +393,7 @@ public final class AppHibernationService extends SystemService {
Trace.traceBegin(Trace.TRACE_TAG_SYSTEM_SERVER, "unhibernatePackage");
pkgState.hibernated = false;
pkgState.lastUnhibernatedMs = System.currentTimeMillis();
+ final long caller = Binder.clearCallingIdentity();
// Deliver LOCKED_BOOT_COMPLETE AND BOOT_COMPLETE broadcast so app can re-register
// their alarms/jobs/etc.
try {
@@ -435,8 +436,10 @@ public final class AppHibernationService extends SystemService {
userId);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
+ } finally {
+ Binder.restoreCallingIdentity(caller);
+ Trace.traceEnd(Trace.TRACE_TAG_SYSTEM_SERVER);
}
- Trace.traceEnd(Trace.TRACE_TAG_SYSTEM_SERVER);
}
/**
diff --git a/services/core/java/com/android/server/backup/SystemBackupAgent.java b/services/core/java/com/android/server/backup/SystemBackupAgent.java
index d98298cbef5a..fa1820456fb9 100644
--- a/services/core/java/com/android/server/backup/SystemBackupAgent.java
+++ b/services/core/java/com/android/server/backup/SystemBackupAgent.java
@@ -94,7 +94,7 @@ public class SystemBackupAgent extends BackupAgentHelper {
mUserId = user.getIdentifier();
addHelper(SYNC_SETTINGS_HELPER, new AccountSyncSettingsBackupHelper(this, mUserId));
- addHelper(PREFERRED_HELPER, new PreferredActivityBackupHelper());
+ addHelper(PREFERRED_HELPER, new PreferredActivityBackupHelper(mUserId));
addHelper(NOTIFICATION_HELPER, new NotificationBackupHelper(mUserId));
addHelper(PERMISSION_HELPER, new PermissionBackupHelper(mUserId));
addHelper(USAGE_STATS_HELPER, new UsageStatsBackupHelper(this));
diff --git a/services/core/java/com/android/server/biometrics/sensors/RevokeChallengeClient.java b/services/core/java/com/android/server/biometrics/sensors/RevokeChallengeClient.java
index 90fa1b4ad1d6..7f86c628028a 100644
--- a/services/core/java/com/android/server/biometrics/sensors/RevokeChallengeClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/RevokeChallengeClient.java
@@ -42,7 +42,6 @@ public abstract class RevokeChallengeClient<T> extends HalClientMonitor<T> {
super.start(callback);
startHalOperation();
- mCallback.onClientFinished(this, true /* success */);
}
@Override
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceGenerateChallengeClient.java b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceGenerateChallengeClient.java
index 8cbb896f80a2..5804622a545f 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceGenerateChallengeClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceGenerateChallengeClient.java
@@ -45,6 +45,7 @@ public class FaceGenerateChallengeClient extends GenerateChallengeClient<ISessio
getFreshDaemon().generateChallenge();
} catch (RemoteException e) {
Slog.e(TAG, "Unable to generateChallenge", e);
+ mCallback.onClientFinished(this, false /* success */);
}
}
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceRevokeChallengeClient.java b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceRevokeChallengeClient.java
index 229417361ddb..99bf893d5ce8 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceRevokeChallengeClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceRevokeChallengeClient.java
@@ -48,6 +48,7 @@ public class FaceRevokeChallengeClient extends RevokeChallengeClient<ISession> {
getFreshDaemon().revokeChallenge(mChallenge);
} catch (RemoteException e) {
Slog.e(TAG, "Unable to revokeChallenge", e);
+ mCallback.onClientFinished(this, false /* success */);
}
}
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceGenerateChallengeClient.java b/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceGenerateChallengeClient.java
index 72c5ee5e78c4..24af817a262f 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceGenerateChallengeClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceGenerateChallengeClient.java
@@ -67,6 +67,7 @@ public class FaceGenerateChallengeClient extends GenerateChallengeClient<IBiomet
}
} catch (RemoteException e) {
Slog.e(TAG, "generateChallenge failed", e);
+ mCallback.onClientFinished(this, false /* success */);
}
}
}
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceRevokeChallengeClient.java b/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceRevokeChallengeClient.java
index 28580dece284..ff3e770cdcb2 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceRevokeChallengeClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceRevokeChallengeClient.java
@@ -43,8 +43,10 @@ public class FaceRevokeChallengeClient extends RevokeChallengeClient<IBiometrics
protected void startHalOperation() {
try {
getFreshDaemon().revokeChallenge();
+ mCallback.onClientFinished(this, true /* success */);
} catch (RemoteException e) {
Slog.e(TAG, "revokeChallenge failed", e);
+ mCallback.onClientFinished(this, false /* success */);
}
}
}
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintGenerateChallengeClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintGenerateChallengeClient.java
index 83c64210d6f6..15a85e6dc309 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintGenerateChallengeClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintGenerateChallengeClient.java
@@ -47,18 +47,17 @@ class FingerprintGenerateChallengeClient extends GenerateChallengeClient<ISessio
getFreshDaemon().generateChallenge();
} catch (RemoteException e) {
Slog.e(TAG, "Unable to generateChallenge", e);
+ mCallback.onClientFinished(this, false /* success */);
}
}
void onChallengeGenerated(int sensorId, int userId, long challenge) {
try {
getListener().onChallengeGenerated(sensorId, challenge);
- mCallback.onClientFinished(FingerprintGenerateChallengeClient.this,
- true /* success */);
+ mCallback.onClientFinished(this, true /* success */);
} catch (RemoteException e) {
Slog.e(TAG, "Unable to send challenge", e);
- mCallback.onClientFinished(FingerprintGenerateChallengeClient.this,
- false /* success */);
+ mCallback.onClientFinished(this, false /* success */);
}
}
}
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintRevokeChallengeClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintRevokeChallengeClient.java
index d9bf1c3aa303..90c69789b541 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintRevokeChallengeClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintRevokeChallengeClient.java
@@ -48,6 +48,7 @@ class FingerprintRevokeChallengeClient extends RevokeChallengeClient<ISession> {
getFreshDaemon().revokeChallenge(mChallenge);
} catch (RemoteException e) {
Slog.e(TAG, "Unable to revokeChallenge", e);
+ mCallback.onClientFinished(this, false /* success */);
}
}
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintGenerateChallengeClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintGenerateChallengeClient.java
index 5169c7de8cdd..302ec2bd4c81 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintGenerateChallengeClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintGenerateChallengeClient.java
@@ -55,6 +55,7 @@ public class FingerprintGenerateChallengeClient
}
} catch (RemoteException e) {
Slog.e(TAG, "preEnroll failed", e);
+ mCallback.onClientFinished(this, false /* success */);
}
}
}
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintRevokeChallengeClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintRevokeChallengeClient.java
index 8f58cae51575..93d8ff3db177 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintRevokeChallengeClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintRevokeChallengeClient.java
@@ -45,8 +45,10 @@ public class FingerprintRevokeChallengeClient
protected void startHalOperation() {
try {
getFreshDaemon().postEnroll();
+ mCallback.onClientFinished(this, true /* success */);
} catch (RemoteException e) {
Slog.e(TAG, "revokeChallenge/postEnroll failed", e);
+ mCallback.onClientFinished(this, false /* success */);
}
}
}
diff --git a/services/core/java/com/android/server/clipboard/ClipboardService.java b/services/core/java/com/android/server/clipboard/ClipboardService.java
index e2aa07102ba3..ab67b138fdbf 100644
--- a/services/core/java/com/android/server/clipboard/ClipboardService.java
+++ b/services/core/java/com/android/server/clipboard/ClipboardService.java
@@ -606,6 +606,10 @@ public class ClipboardService extends SystemService {
description.setTimestamp(System.currentTimeMillis());
}
}
+ sendClipChangedBroadcast(clipboard);
+ }
+
+ private void sendClipChangedBroadcast(PerUserClipboard clipboard) {
final long ident = Binder.clearCallingIdentity();
final int n = clipboard.primaryClipListeners.beginBroadcast();
try {
@@ -615,7 +619,7 @@ public class ClipboardService extends SystemService {
clipboard.primaryClipListeners.getBroadcastCookie(i);
if (clipboardAccessAllowed(AppOpsManager.OP_READ_CLIPBOARD, li.mPackageName,
- li.mUid, UserHandle.getUserId(li.mUid))) {
+ li.mUid, UserHandle.getUserId(li.mUid))) {
clipboard.primaryClipListeners.getBroadcastItem(i)
.dispatchPrimaryClipChanged();
}
@@ -632,7 +636,8 @@ public class ClipboardService extends SystemService {
@GuardedBy("mLock")
private void startClassificationLocked(@NonNull ClipData clip, @UserIdInt int userId) {
- if (clip.getItemCount() == 0) {
+ CharSequence text = (clip.getItemCount() == 0) ? null : clip.getItemAt(0).getText();
+ if (TextUtils.isEmpty(text) || text.length() > mMaxClassificationLength) {
clip.getDescription().setClassificationStatus(
ClipDescription.CLASSIFICATION_NOT_PERFORMED);
return;
@@ -650,20 +655,17 @@ public class ClipboardService extends SystemService {
} finally {
Binder.restoreCallingIdentity(ident);
}
- CharSequence text = clip.getItemAt(0).getText();
- if (TextUtils.isEmpty(text) || text.length() > mMaxClassificationLength
- || text.length() > classifier.getMaxGenerateLinksTextLength()) {
+ if (text.length() > classifier.getMaxGenerateLinksTextLength()) {
clip.getDescription().setClassificationStatus(
ClipDescription.CLASSIFICATION_NOT_PERFORMED);
return;
}
- getClipboardLocked(userId).mTextClassifier = classifier;
- mWorkerHandler.post(() -> doClassification(text, clip, classifier));
+ mWorkerHandler.post(() -> doClassification(text, clip, classifier, userId));
}
@WorkerThread
private void doClassification(
- CharSequence text, ClipData clip, TextClassifier classifier) {
+ CharSequence text, ClipData clip, TextClassifier classifier, @UserIdInt int userId) {
TextLinks.Request request = new TextLinks.Request.Builder(text).build();
TextLinks links = classifier.generateLinks(request);
@@ -680,13 +682,53 @@ public class ClipboardService extends SystemService {
}
synchronized (mLock) {
- clip.getDescription().setConfidenceScores(confidences);
- if (!links.getLinks().isEmpty()) {
- clip.getItemAt(0).setTextLinks(links);
+ PerUserClipboard clipboard = getClipboardLocked(userId);
+ if (clipboard.primaryClip == clip) {
+ applyClassificationAndSendBroadcastLocked(
+ clipboard, confidences, links, classifier);
+
+ // Also apply to related profiles if needed
+ List<UserInfo> related = getRelatedProfiles(userId);
+ if (related != null) {
+ int size = related.size();
+ for (int i = 0; i < size; i++) {
+ int id = related.get(i).id;
+ if (id != userId) {
+ final boolean canCopyIntoProfile = !hasRestriction(
+ UserManager.DISALLOW_SHARE_INTO_MANAGED_PROFILE, id);
+ if (canCopyIntoProfile) {
+ PerUserClipboard relatedClipboard = getClipboardLocked(id);
+ if (hasTextLocked(relatedClipboard, text)) {
+ applyClassificationAndSendBroadcastLocked(
+ relatedClipboard, confidences, links, classifier);
+ }
+ }
+ }
+ }
+ }
}
}
}
+ @GuardedBy("mLock")
+ private void applyClassificationAndSendBroadcastLocked(
+ PerUserClipboard clipboard, ArrayMap<String, Float> confidences, TextLinks links,
+ TextClassifier classifier) {
+ clipboard.mTextClassifier = classifier;
+ clipboard.primaryClip.getDescription().setConfidenceScores(confidences);
+ if (!links.getLinks().isEmpty()) {
+ clipboard.primaryClip.getItemAt(0).setTextLinks(links);
+ }
+ sendClipChangedBroadcast(clipboard);
+ }
+
+ @GuardedBy("mLock")
+ private boolean hasTextLocked(PerUserClipboard clipboard, @NonNull CharSequence text) {
+ return clipboard.primaryClip != null
+ && clipboard.primaryClip.getItemCount() > 0
+ && text.equals(clipboard.primaryClip.getItemAt(0).getText());
+ }
+
private boolean isDeviceLocked(@UserIdInt int userId) {
final long token = Binder.clearCallingIdentity();
try {
diff --git a/services/core/java/com/android/server/display/AutomaticBrightnessController.java b/services/core/java/com/android/server/display/AutomaticBrightnessController.java
index 91b96dc17473..1a07cb854cae 100644
--- a/services/core/java/com/android/server/display/AutomaticBrightnessController.java
+++ b/services/core/java/com/android/server/display/AutomaticBrightnessController.java
@@ -738,13 +738,19 @@ class AutomaticBrightnessController {
float value = mBrightnessMapper.getBrightness(mAmbientLux, mForegroundAppPackageName,
mForegroundAppCategory);
float newScreenAutoBrightness = clampScreenBrightness(value);
+
+ // The min/max range can change for brightness due to HBM. See if the current brightness
+ // value still falls within the current range (which could have changed).
+ final boolean currentBrightnessWithinAllowedRange = BrightnessSynchronizer.floatEquals(
+ mScreenAutoBrightness, clampScreenBrightness(mScreenAutoBrightness));
// If screenAutoBrightness is set, we should have screen{Brightening,Darkening}Threshold,
// in which case we ignore the new screen brightness if it doesn't differ enough from the
// previous one.
if (!Float.isNaN(mScreenAutoBrightness)
&& !isManuallySet
&& newScreenAutoBrightness > mScreenDarkeningThreshold
- && newScreenAutoBrightness < mScreenBrighteningThreshold) {
+ && newScreenAutoBrightness < mScreenBrighteningThreshold
+ && currentBrightnessWithinAllowedRange) {
if (mLoggingEnabled) {
Slog.d(TAG, "ignoring newScreenAutoBrightness: "
+ mScreenDarkeningThreshold + " < " + newScreenAutoBrightness
diff --git a/services/core/java/com/android/server/display/DisplayBlanker.java b/services/core/java/com/android/server/display/DisplayBlanker.java
index e2129ba13626..8de49af3de22 100644
--- a/services/core/java/com/android/server/display/DisplayBlanker.java
+++ b/services/core/java/com/android/server/display/DisplayBlanker.java
@@ -20,5 +20,8 @@ package com.android.server.display;
* Interface used to update the actual display state.
*/
public interface DisplayBlanker {
- void requestDisplayState(int displayId, int state, float brightness);
+ /**
+ * Requests the specified display state and brightness levels for the specified displayId.
+ */
+ void requestDisplayState(int displayId, int state, float brightness, float sdrBrightness);
}
diff --git a/services/core/java/com/android/server/display/DisplayDevice.java b/services/core/java/com/android/server/display/DisplayDevice.java
index b3070b7cf1ba..35f29579b417 100644
--- a/services/core/java/com/android/server/display/DisplayDevice.java
+++ b/services/core/java/com/android/server/display/DisplayDevice.java
@@ -156,10 +156,12 @@ abstract class DisplayDevice {
*
* @param state The new display state.
* @param brightnessState The new display brightnessState.
+ * @param sdrBrightnessState The new display brightnessState for SDR layers.
* @return A runnable containing work to be deferred until after we have
* exited the critical section, or null if none.
*/
- public Runnable requestDisplayStateLocked(int state, float brightnessState) {
+ public Runnable requestDisplayStateLocked(int state, float brightnessState,
+ float sdrBrightnessState) {
return null;
}
diff --git a/services/core/java/com/android/server/display/DisplayDeviceConfig.java b/services/core/java/com/android/server/display/DisplayDeviceConfig.java
index 2d7145fef69c..c46cfe3d1d2f 100644
--- a/services/core/java/com/android/server/display/DisplayDeviceConfig.java
+++ b/services/core/java/com/android/server/display/DisplayDeviceConfig.java
@@ -104,6 +104,7 @@ public class DisplayDeviceConfig {
private float mBrightnessRampSlowIncrease = Float.NaN;
private Spline mBrightnessToBacklightSpline;
private Spline mBacklightToBrightnessSpline;
+ private Spline mBacklightToNitsSpline;
private List<String> mQuirks;
private boolean mIsHighBrightnessModeEnabled = false;
private HighBrightnessModeData mHbmData;
@@ -219,6 +220,20 @@ public class DisplayDeviceConfig {
}
/**
+ * Calculates the nits value for the specified backlight value if a mapping exists.
+ *
+ * @return The mapped nits or 0 if no mapping exits.
+ */
+ public float getNitsFromBacklight(float backlight) {
+ if (mBacklightToNitsSpline == null) {
+ Slog.wtf(TAG, "requesting nits when no mapping exists.");
+ return -1;
+ }
+ backlight = Math.max(backlight, mBacklightMinimum);
+ return mBacklightToNitsSpline.interpolate(backlight);
+ }
+
+ /**
* Return an array of equal length to backlight and nits, that covers the entire system
* brightness range of 0.0-1.0.
*
@@ -258,6 +273,13 @@ public class DisplayDeviceConfig {
}
/**
+ * @return true if a nits to backlight mapping is defined in this config, false otherwise.
+ */
+ public boolean hasNitsMapping() {
+ return mBacklightToNitsSpline != null;
+ }
+
+ /**
* @param quirkValue The quirk to test.
* @return {@code true} if the specified quirk is present in this configuration,
* {@code false} otherwise.
@@ -584,6 +606,7 @@ public class DisplayDeviceConfig {
}
mBrightnessToBacklightSpline = Spline.createSpline(mBrightness, mBacklight);
mBacklightToBrightnessSpline = Spline.createSpline(mBacklight, mBrightness);
+ mBacklightToNitsSpline = Spline.createSpline(mBacklight, mNits);
}
private void loadQuirks(DisplayConfiguration config) {
diff --git a/services/core/java/com/android/server/display/DisplayManagerService.java b/services/core/java/com/android/server/display/DisplayManagerService.java
index 789f08fb3187..0a4b137fa6cd 100644
--- a/services/core/java/com/android/server/display/DisplayManagerService.java
+++ b/services/core/java/com/android/server/display/DisplayManagerService.java
@@ -257,7 +257,8 @@ public final class DisplayManagerService extends SystemService {
private final DisplayBlanker mDisplayBlanker = new DisplayBlanker() {
// Synchronized to avoid race conditions when updating multiple display states.
@Override
- public synchronized void requestDisplayState(int displayId, int state, float brightness) {
+ public synchronized void requestDisplayState(int displayId, int state, float brightness,
+ float sdrBrightness) {
boolean allInactive = true;
boolean allOff = true;
final boolean stateChanged;
@@ -288,7 +289,7 @@ public final class DisplayManagerService extends SystemService {
// The order of operations is important for legacy reasons.
if (state == Display.STATE_OFF) {
- requestDisplayStateInternal(displayId, state, brightness);
+ requestDisplayStateInternal(displayId, state, brightness, sdrBrightness);
}
if (stateChanged) {
@@ -296,7 +297,7 @@ public final class DisplayManagerService extends SystemService {
}
if (state != Display.STATE_OFF) {
- requestDisplayStateInternal(displayId, state, brightness);
+ requestDisplayStateInternal(displayId, state, brightness, sdrBrightness);
}
}
};
@@ -316,7 +317,7 @@ public final class DisplayManagerService extends SystemService {
// A map from LogicalDisplay ID to display brightness.
@GuardedBy("mSyncRoot")
- private final SparseArray<Float> mDisplayBrightnesses = new SparseArray<>();
+ private final SparseArray<BrightnessPair> mDisplayBrightnesses = new SparseArray<>();
// Set to true when there are pending display changes that have yet to be applied
// to the surface flinger state.
@@ -667,11 +668,8 @@ public final class DisplayManagerService extends SystemService {
}
}
- private void requestDisplayStateInternal(int displayId, int state, float brightnessState) {
- if (state == Display.STATE_UNKNOWN) {
- state = Display.STATE_ON;
- }
- if (state == Display.STATE_OFF) {
+ private float clampBrightness(int displayState, float brightnessState) {
+ if (displayState == Display.STATE_OFF) {
brightnessState = PowerManager.BRIGHTNESS_OFF_FLOAT;
} else if (brightnessState != PowerManager.BRIGHTNESS_OFF_FLOAT
&& brightnessState < PowerManager.BRIGHTNESS_MIN) {
@@ -679,6 +677,17 @@ public final class DisplayManagerService extends SystemService {
} else if (brightnessState > PowerManager.BRIGHTNESS_MAX) {
brightnessState = PowerManager.BRIGHTNESS_MAX;
}
+ return brightnessState;
+ }
+
+ private void requestDisplayStateInternal(int displayId, int state, float brightnessState,
+ float sdrBrightnessState) {
+ if (state == Display.STATE_UNKNOWN) {
+ state = Display.STATE_ON;
+ }
+
+ brightnessState = clampBrightness(state, brightnessState);
+ sdrBrightnessState = clampBrightness(state, sdrBrightnessState);
// Update the display state within the lock.
// Note that we do not need to schedule traversals here although it
@@ -688,20 +697,26 @@ public final class DisplayManagerService extends SystemService {
synchronized (mSyncRoot) {
final int index = mDisplayStates.indexOfKey(displayId);
+ final BrightnessPair brightnessPair =
+ index < 0 ? null : mDisplayBrightnesses.valueAt(index);
if (index < 0 || (mDisplayStates.valueAt(index) == state
- && BrightnessSynchronizer.floatEquals(mDisplayBrightnesses.valueAt(index),
- brightnessState))) {
+ && BrightnessSynchronizer.floatEquals(
+ brightnessPair.brightness, brightnessState)
+ && BrightnessSynchronizer.floatEquals(
+ brightnessPair.sdrBrightness, sdrBrightnessState))) {
return; // Display no longer exists or no change.
}
traceMessage = "requestDisplayStateInternal("
+ displayId + ", "
+ Display.stateToString(state)
- + ", brightness=" + brightnessState + ")";
+ + ", brightness=" + brightnessState
+ + ", sdrBrightness=" + sdrBrightnessState + ")";
Trace.asyncTraceBegin(Trace.TRACE_TAG_POWER, traceMessage, displayId);
mDisplayStates.setValueAt(index, state);
- mDisplayBrightnesses.setValueAt(index, brightnessState);
+ brightnessPair.brightness = brightnessState;
+ brightnessPair.sdrBrightness = sdrBrightnessState;
runnable = updateDisplayStateLocked(mLogicalDisplayMapper.getDisplayLocked(displayId)
.getPrimaryDisplayDeviceLocked());
}
@@ -1235,7 +1250,9 @@ public final class DisplayManagerService extends SystemService {
addDisplayPowerControllerLocked(display);
mDisplayStates.append(displayId, Display.STATE_UNKNOWN);
- mDisplayBrightnesses.append(displayId, display.getDisplayInfoLocked().brightnessDefault);
+ final float brightnessDefault = display.getDisplayInfoLocked().brightnessDefault;
+ mDisplayBrightnesses.append(displayId,
+ new BrightnessPair(brightnessDefault, brightnessDefault));
DisplayManagerGlobal.invalidateLocalDisplayInfoCaches();
@@ -1265,11 +1282,6 @@ public final class DisplayManagerService extends SystemService {
// this point.
sendDisplayEventLocked(displayId, DisplayManagerGlobal.EVENT_DISPLAY_CHANGED);
scheduleTraversalLocked(false);
-
- DisplayPowerController dpc = mDisplayPowerControllers.get(displayId);
- if (dpc != null) {
- dpc.onDisplayChanged();
- }
}
private void handleLogicalDisplayFrameRateOverridesChangedLocked(
@@ -1301,6 +1313,11 @@ public final class DisplayManagerService extends SystemService {
if (work != null) {
mHandler.post(work);
}
+ final int displayId = display.getDisplayIdLocked();
+ DisplayPowerController dpc = mDisplayPowerControllers.get(displayId);
+ if (dpc != null) {
+ dpc.onDisplayChanged();
+ }
handleLogicalDisplayChangedLocked(display);
}
@@ -1326,8 +1343,9 @@ public final class DisplayManagerService extends SystemService {
// Only send a request for display state if display state has already been initialized.
if (state != Display.STATE_UNKNOWN) {
- final float brightness = mDisplayBrightnesses.get(displayId);
- return device.requestDisplayStateLocked(state, brightness);
+ final BrightnessPair brightnessPair = mDisplayBrightnesses.get(displayId);
+ return device.requestDisplayStateLocked(state, brightnessPair.brightness,
+ brightnessPair.sdrBrightness);
}
}
return null;
@@ -1935,10 +1953,11 @@ public final class DisplayManagerService extends SystemService {
for (int i = 0; i < displayStateCount; i++) {
final int displayId = mDisplayStates.keyAt(i);
final int displayState = mDisplayStates.valueAt(i);
- final float brightness = mDisplayBrightnesses.valueAt(i);
+ final BrightnessPair brightnessPair = mDisplayBrightnesses.valueAt(i);
pw.println(" Display Id=" + displayId);
pw.println(" Display State=" + Display.stateToString(displayState));
- pw.println(" Display Brightness=" + brightness);
+ pw.println(" Display Brightness=" + brightnessPair.brightness);
+ pw.println(" Display SdrBrightness=" + brightnessPair.sdrBrightness);
}
IndentingPrintWriter ipw = new IndentingPrintWriter(pw, " ");
@@ -3277,6 +3296,16 @@ public final class DisplayManagerService extends SystemService {
}
};
+ private class BrightnessPair {
+ public float brightness;
+ public float sdrBrightness;
+
+ BrightnessPair(float brightness, float sdrBrightness) {
+ this.brightness = brightness;
+ this.sdrBrightness = sdrBrightness;
+ }
+ }
+
/**
* Functional interface for providing time.
* TODO(b/184781936): merge with PowerManagerService.Clock
@@ -3288,5 +3317,4 @@ public final class DisplayManagerService extends SystemService {
*/
long uptimeMillis();
}
-
}
diff --git a/services/core/java/com/android/server/display/DisplayPowerController.java b/services/core/java/com/android/server/display/DisplayPowerController.java
index 3340e3c73fa1..7a50a34ae4ad 100644
--- a/services/core/java/com/android/server/display/DisplayPowerController.java
+++ b/services/core/java/com/android/server/display/DisplayPowerController.java
@@ -38,6 +38,7 @@ import android.hardware.display.DisplayManagerInternal.DisplayPowerRequest;
import android.metrics.LogMaker;
import android.net.Uri;
import android.os.Handler;
+import android.os.IBinder;
import android.os.Looper;
import android.os.Message;
import android.os.PowerManager;
@@ -61,6 +62,7 @@ import com.android.internal.logging.MetricsLogger;
import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
import com.android.server.LocalServices;
import com.android.server.am.BatteryStatsService;
+import com.android.server.display.RampAnimator.DualRampAnimator;
import com.android.server.display.color.ColorDisplayService.ColorDisplayServiceInternal;
import com.android.server.display.color.ColorDisplayService.ReduceBrightColorsListener;
import com.android.server.display.whitebalance.DisplayWhiteBalanceController;
@@ -223,6 +225,8 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call
@GuardedBy("mCachedBrightnessInfo")
private final CachedBrightnessInfo mCachedBrightnessInfo = new CachedBrightnessInfo();
+ private DisplayDevice mDisplayDevice;
+
// True if we should fade the screen while turning it off, false if we should play
// a stylish color fade animation instead.
private boolean mColorFadeFadesConfig;
@@ -424,7 +428,7 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call
// Animators.
private ObjectAnimator mColorFadeOnAnimator;
private ObjectAnimator mColorFadeOffAnimator;
- private RampAnimator<DisplayPowerState> mScreenBrightnessRampAnimator;
+ private DualRampAnimator<DisplayPowerState> mScreenBrightnessRampAnimator;
private BrightnessSetting.BrightnessSettingListener mBrightnessSettingListener;
// True if this DisplayPowerController has been stopped and should no longer be running.
@@ -442,6 +446,7 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call
Runnable onBrightnessChangeRunnable) {
mLogicalDisplay = logicalDisplay;
mDisplayId = mLogicalDisplay.getDisplayIdLocked();
+ mDisplayDevice = mLogicalDisplay.getPrimaryDisplayDeviceLocked();
mHandler = new DisplayControllerHandler(handler.getLooper());
if (mDisplayId == Display.DEFAULT_DISPLAY) {
@@ -780,12 +785,29 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call
* when displays get swapped on foldable devices. For example, different brightness properties
* of each display need to be properly reflected in AutomaticBrightnessController.
*/
+ @GuardedBy("DisplayManagerService.mSyncRoot")
public void onDisplayChanged() {
- // TODO: b/175821789 - Support high brightness on multiple (folding) displays
- mUniqueDisplayId = mLogicalDisplay.getPrimaryDisplayDeviceLocked().getUniqueId();
- mDisplayDeviceConfig = mLogicalDisplay.getPrimaryDisplayDeviceLocked()
- .getDisplayDeviceConfig();
- loadAmbientLightSensor();
+ final DisplayDevice device = mLogicalDisplay.getPrimaryDisplayDeviceLocked();
+ if (device == null) {
+ Slog.wtf(TAG, "Display Device is null in DisplayPowerController for display: "
+ + mLogicalDisplay.getDisplayIdLocked());
+ return;
+ }
+
+ final String uniqueId = device.getUniqueId();
+ final DisplayDeviceConfig config = device.getDisplayDeviceConfig();
+ final IBinder token = device.getDisplayTokenLocked();
+ mHandler.post(() -> {
+ if (mDisplayDevice == device) {
+ return;
+ }
+ mDisplayDevice = device;
+ mUniqueDisplayId = uniqueId;
+ mDisplayDeviceConfig = config;
+
+ loadAmbientLightSensor();
+ mHbmController.resetHbmData(token, config.getHighBrightnessModeData());
+ });
}
/**
@@ -855,8 +877,9 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call
mColorFadeOffAnimator.addListener(mAnimatorListener);
}
- mScreenBrightnessRampAnimator = new RampAnimator<>(
- mPowerState, DisplayPowerState.SCREEN_BRIGHTNESS_FLOAT);
+ mScreenBrightnessRampAnimator = new DualRampAnimator<>(mPowerState,
+ DisplayPowerState.SCREEN_BRIGHTNESS_FLOAT,
+ DisplayPowerState.SCREEN_SDR_BRIGHTNESS_FLOAT);
mScreenBrightnessRampAnimator.setListener(mRampAnimatorListener);
noteScreenState(mPowerState.getScreenState());
@@ -902,6 +925,7 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call
/** Clean up all resources that are accessed via the {@link #mHandler} thread. */
private void cleanupHandlerThreadAfterStop() {
setProximitySensorEnabled(false);
+ mHbmController.stop();
mHandler.removeCallbacksAndMessages(null);
if (mUnfinishedBusiness) {
mCallbacks.releaseSuspendBlocker();
@@ -1205,9 +1229,10 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call
// timeout is about to expire.
if (mPowerRequest.policy == DisplayPowerRequest.POLICY_DIM) {
if (brightnessState > PowerManager.BRIGHTNESS_MIN) {
- brightnessState = Math.max(Math.min(brightnessState
- - SCREEN_DIM_MINIMUM_REDUCTION_FLOAT,
- mScreenBrightnessDimConfig), PowerManager.BRIGHTNESS_MIN);
+ brightnessState = Math.max(
+ Math.min(brightnessState - SCREEN_DIM_MINIMUM_REDUCTION_FLOAT,
+ mScreenBrightnessDimConfig),
+ PowerManager.BRIGHTNESS_MIN);
mBrightnessReasonTemp.addModifier(BrightnessReason.MODIFIER_DIMMED);
}
if (!mAppliedDimming) {
@@ -1282,12 +1307,27 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call
// transformations to the brightness have pushed it outside of the currently
// allowed range.
float animateValue = clampScreenBrightness(brightnessState);
+
+ // If there are any HDR layers on the screen, we have a special brightness value that we
+ // use instead. We still preserve the calculated brightness for Standard Dynamic Range
+ // (SDR) layers, but the main brightness value will be the one for HDR.
+ float sdrAnimateValue = animateValue;
+ if (mHbmController.getHighBrightnessMode() == BrightnessInfo.HIGH_BRIGHTNESS_MODE_HDR
+ && ((mBrightnessReason.modifier & BrightnessReason.MODIFIER_DIMMED) == 0
+ || (mBrightnessReason.modifier & BrightnessReason.MODIFIER_LOW_POWER) == 0)) {
+ animateValue = mHbmController.getHdrBrightnessValue();
+ }
+
final float currentBrightness = mPowerState.getScreenBrightness();
+ final float currentSdrBrightness = mPowerState.getSdrScreenBrightness();
if (isValidBrightnessValue(animateValue)
- && !BrightnessSynchronizer.floatEquals(animateValue, currentBrightness)) {
+ && (!BrightnessSynchronizer.floatEquals(animateValue, currentBrightness)
+ || !BrightnessSynchronizer.floatEquals(
+ sdrAnimateValue, currentSdrBrightness))) {
if (initialRampSkip || hasBrightnessBuckets
|| wasOrWillBeInVr || !isDisplayContentVisible || brightnessIsTemporary) {
- animateScreenBrightness(animateValue, SCREEN_ANIMATION_RATE_MINIMUM);
+ animateScreenBrightness(animateValue, sdrAnimateValue,
+ SCREEN_ANIMATION_RATE_MINIMUM);
} else {
boolean isIncreasing = animateValue > currentBrightness;
final float rampSpeed;
@@ -1300,7 +1340,7 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call
} else {
rampSpeed = mBrightnessRampRateFastDecrease;
}
- animateScreenBrightness(animateValue, rampSpeed);
+ animateScreenBrightness(animateValue, sdrAnimateValue, rampSpeed);
}
}
@@ -1446,9 +1486,11 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call
private HighBrightnessModeController createHbmController() {
final DisplayDeviceConfig ddConfig =
mLogicalDisplay.getPrimaryDisplayDeviceLocked().getDisplayDeviceConfig();
+ final IBinder displayToken =
+ mLogicalDisplay.getPrimaryDisplayDeviceLocked().getDisplayTokenLocked();
final DisplayDeviceConfig.HighBrightnessModeData hbmData =
ddConfig != null ? ddConfig.getHighBrightnessModeData() : null;
- return new HighBrightnessModeController(mHandler, PowerManager.BRIGHTNESS_MIN,
+ return new HighBrightnessModeController(mHandler, displayToken, PowerManager.BRIGHTNESS_MIN,
PowerManager.BRIGHTNESS_MAX, hbmData,
() -> {
sendUpdatePowerStateLocked();
@@ -1596,11 +1638,12 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call
&& brightnessState <= PowerManager.BRIGHTNESS_MAX;
}
- private void animateScreenBrightness(float target, float rate) {
+ private void animateScreenBrightness(float target, float sdrTarget, float rate) {
if (DEBUG) {
- Slog.d(TAG, "Animating brightness: target=" + target +", rate=" + rate);
+ Slog.d(TAG, "Animating brightness: target=" + target + ", sdrTarget=" + sdrTarget
+ + ", rate=" + rate);
}
- if (mScreenBrightnessRampAnimator.animateTo(target, rate)) {
+ if (mScreenBrightnessRampAnimator.animateTo(target, sdrTarget, rate)) {
Trace.traceCounter(Trace.TRACE_TAG_POWER, "TargetScreenBrightness", (int) target);
// TODO(b/153319140) remove when we can get this from the above trace invocation
SystemProperties.set("debug.tracing.screen_brightness", String.valueOf(target));
@@ -2295,6 +2338,7 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call
return;
}
handleSettingsChange(false /*userSwitch*/);
+ break;
}
}
}
@@ -2392,7 +2436,8 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call
static final int MODIFIER_DIMMED = 0x1;
static final int MODIFIER_LOW_POWER = 0x2;
- static final int MODIFIER_MASK = 0x3;
+ static final int MODIFIER_HDR = 0x4;
+ static final int MODIFIER_MASK = MODIFIER_DIMMED | MODIFIER_LOW_POWER | MODIFIER_HDR;
// ADJUSTMENT_*
// These things can happen at any point, even if the main brightness reason doesn't
@@ -2464,6 +2509,9 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call
if ((modifier & MODIFIER_DIMMED) != 0) {
sb.append(" dim");
}
+ if ((modifier & MODIFIER_HDR) != 0) {
+ sb.append(" hdr");
+ }
int strlen = sb.length();
if (sb.charAt(strlen - 1) == '[') {
sb.setLength(strlen - 2);
diff --git a/services/core/java/com/android/server/display/DisplayPowerState.java b/services/core/java/com/android/server/display/DisplayPowerState.java
index 77aff5b03dd2..b58dd38348aa 100644
--- a/services/core/java/com/android/server/display/DisplayPowerState.java
+++ b/services/core/java/com/android/server/display/DisplayPowerState.java
@@ -62,6 +62,7 @@ final class DisplayPowerState {
private int mScreenState;
private float mScreenBrightness;
+ private float mSdrScreenBrightness;
private boolean mScreenReady;
private boolean mScreenUpdatePending;
@@ -92,6 +93,7 @@ final class DisplayPowerState {
mScreenState = displayState;
mScreenBrightness = (displayState != Display.STATE_OFF) ? PowerManager.BRIGHTNESS_MAX
: PowerManager.BRIGHTNESS_OFF_FLOAT;
+ mSdrScreenBrightness = mScreenBrightness;
scheduleScreenUpdate();
mColorFadePrepared = false;
@@ -126,6 +128,19 @@ final class DisplayPowerState {
}
};
+ public static final FloatProperty<DisplayPowerState> SCREEN_SDR_BRIGHTNESS_FLOAT =
+ new FloatProperty<DisplayPowerState>("sdrScreenBrightnessFloat") {
+ @Override
+ public void setValue(DisplayPowerState object, float value) {
+ object.setSdrScreenBrightness(value);
+ }
+
+ @Override
+ public Float get(DisplayPowerState object) {
+ return object.getSdrScreenBrightness();
+ }
+ };
+
/**
* Sets whether the screen is on, off, or dozing.
*/
@@ -149,12 +164,38 @@ final class DisplayPowerState {
}
/**
+ * Sets the display's SDR brightness.
+ *
+ * @param brightness The brightness, ranges from 0.0f (minimum / off) to 1.0f (brightest).
+ */
+ public void setSdrScreenBrightness(float brightness) {
+ if (!BrightnessSynchronizer.floatEquals(mSdrScreenBrightness, brightness)) {
+ if (DEBUG) {
+ Slog.d(TAG, "setSdrScreenBrightness: brightness=" + brightness);
+ }
+
+ mSdrScreenBrightness = brightness;
+ if (mScreenState != Display.STATE_OFF) {
+ mScreenReady = false;
+ scheduleScreenUpdate();
+ }
+ }
+ }
+
+ /**
+ * Gets the screen SDR brightness.
+ */
+ public float getSdrScreenBrightness() {
+ return mSdrScreenBrightness;
+ }
+
+ /**
* Sets the display brightness.
*
* @param brightness The brightness, ranges from 0.0f (minimum / off) to 1.0f (brightest).
*/
public void setScreenBrightness(float brightness) {
- if (mScreenBrightness != brightness) {
+ if (!BrightnessSynchronizer.floatEquals(mScreenBrightness, brightness)) {
if (DEBUG) {
Slog.d(TAG, "setScreenBrightness: brightness=" + brightness);
}
@@ -286,6 +327,7 @@ final class DisplayPowerState {
pw.println(" mStopped=" + mStopped);
pw.println(" mScreenState=" + Display.stateToString(mScreenState));
pw.println(" mScreenBrightness=" + mScreenBrightness);
+ pw.println(" mSdrScreenBrightness=" + mSdrScreenBrightness);
pw.println(" mScreenReady=" + mScreenReady);
pw.println(" mScreenUpdatePending=" + mScreenUpdatePending);
pw.println(" mColorFadePrepared=" + mColorFadePrepared);
@@ -332,7 +374,10 @@ final class DisplayPowerState {
float brightnessState = mScreenState != Display.STATE_OFF
&& mColorFadeLevel > 0f ? mScreenBrightness : PowerManager.BRIGHTNESS_OFF_FLOAT;
- if (mPhotonicModulator.setState(mScreenState, brightnessState)) {
+ float sdrBrightnessState = mScreenState != Display.STATE_OFF
+ && mColorFadeLevel > 0f
+ ? mSdrScreenBrightness : PowerManager.BRIGHTNESS_OFF_FLOAT;
+ if (mPhotonicModulator.setState(mScreenState, brightnessState, sdrBrightnessState)) {
if (DEBUG) {
Slog.d(TAG, "Screen ready");
}
@@ -373,8 +418,10 @@ final class DisplayPowerState {
private int mPendingState = INITIAL_SCREEN_STATE;
private float mPendingBacklight = INITIAL_BACKLIGHT_FLOAT;
+ private float mPendingSdrBacklight = INITIAL_BACKLIGHT_FLOAT;
private int mActualState = INITIAL_SCREEN_STATE;
private float mActualBacklight = INITIAL_BACKLIGHT_FLOAT;
+ private float mActualSdrBacklight = INITIAL_BACKLIGHT_FLOAT;
private boolean mStateChangeInProgress;
private boolean mBacklightChangeInProgress;
@@ -382,11 +429,13 @@ final class DisplayPowerState {
super("PhotonicModulator");
}
- public boolean setState(int state, float brightnessState) {
+ public boolean setState(int state, float brightnessState, float sdrBrightnessState) {
synchronized (mLock) {
boolean stateChanged = state != mPendingState;
- boolean backlightChanged = !BrightnessSynchronizer.floatEquals(
- brightnessState, mPendingBacklight);
+ boolean backlightChanged =
+ !BrightnessSynchronizer.floatEquals(brightnessState, mPendingBacklight)
+ || !BrightnessSynchronizer.floatEquals(
+ sdrBrightnessState, mPendingSdrBacklight);
if (stateChanged || backlightChanged) {
if (DEBUG) {
Slog.d(TAG, "Requesting new screen state: state="
@@ -395,6 +444,7 @@ final class DisplayPowerState {
mPendingState = state;
mPendingBacklight = brightnessState;
+ mPendingSdrBacklight = sdrBrightnessState;
boolean changeInProgress = mStateChangeInProgress || mBacklightChangeInProgress;
mStateChangeInProgress = stateChanged || mStateChangeInProgress;
mBacklightChangeInProgress = backlightChanged || mBacklightChangeInProgress;
@@ -413,8 +463,10 @@ final class DisplayPowerState {
pw.println("Photonic Modulator State:");
pw.println(" mPendingState=" + Display.stateToString(mPendingState));
pw.println(" mPendingBacklight=" + mPendingBacklight);
+ pw.println(" mPendingSdrBacklight=" + mPendingSdrBacklight);
pw.println(" mActualState=" + Display.stateToString(mActualState));
pw.println(" mActualBacklight=" + mActualBacklight);
+ pw.println(" mActualSdrBacklight=" + mActualSdrBacklight);
pw.println(" mStateChangeInProgress=" + mStateChangeInProgress);
pw.println(" mBacklightChangeInProgress=" + mBacklightChangeInProgress);
}
@@ -427,13 +479,17 @@ final class DisplayPowerState {
final int state;
final boolean stateChanged;
final float brightnessState;
+ final float sdrBrightnessState;
final boolean backlightChanged;
synchronized (mLock) {
state = mPendingState;
stateChanged = (state != mActualState);
brightnessState = mPendingBacklight;
- backlightChanged = !BrightnessSynchronizer.floatEquals(
- brightnessState, mActualBacklight);
+ sdrBrightnessState = mPendingSdrBacklight;
+ backlightChanged =
+ !BrightnessSynchronizer.floatEquals(brightnessState, mActualBacklight)
+ || !BrightnessSynchronizer.floatEquals(
+ sdrBrightnessState, mActualSdrBacklight);
if (!stateChanged) {
// State changed applied, notify outer class.
postScreenUpdateThreadSafe();
@@ -454,14 +510,17 @@ final class DisplayPowerState {
}
mActualState = state;
mActualBacklight = brightnessState;
+ mActualSdrBacklight = sdrBrightnessState;
}
// Apply pending change.
if (DEBUG) {
Slog.d(TAG, "Updating screen state: id=" + mDisplayId + ", state="
- + Display.stateToString(state) + ", backlight=" + brightnessState);
+ + Display.stateToString(state) + ", backlight=" + brightnessState
+ + ", sdrBacklight=" + sdrBrightnessState);
}
- mBlanker.requestDisplayState(mDisplayId, state, brightnessState);
+ mBlanker.requestDisplayState(mDisplayId, state, brightnessState,
+ sdrBrightnessState);
}
}
}
diff --git a/services/core/java/com/android/server/display/HighBrightnessModeController.java b/services/core/java/com/android/server/display/HighBrightnessModeController.java
index e6486bd2a79a..b9487779dfd3 100644
--- a/services/core/java/com/android/server/display/HighBrightnessModeController.java
+++ b/services/core/java/com/android/server/display/HighBrightnessModeController.java
@@ -18,9 +18,11 @@ package com.android.server.display;
import android.hardware.display.BrightnessInfo;
import android.os.Handler;
+import android.os.IBinder;
import android.os.PowerManager;
import android.os.SystemClock;
import android.util.Slog;
+import android.view.SurfaceControlHdrLayerInfoListener;
import com.android.internal.annotations.VisibleForTesting;
import com.android.server.display.DisplayDeviceConfig.HighBrightnessModeData;
@@ -45,17 +47,21 @@ class HighBrightnessModeController {
private final float mBrightnessMin;
private final float mBrightnessMax;
- private final HighBrightnessModeData mHbmData;
private final Handler mHandler;
private final Runnable mHbmChangeCallback;
private final Runnable mRecalcRunnable;
private final Clock mClock;
+ private SurfaceControlHdrLayerInfoListener mHdrListener;
+ private HighBrightnessModeData mHbmData;
+ private IBinder mRegisteredDisplayToken;
+
private boolean mIsInAllowedAmbientRange = false;
private boolean mIsTimeAvailable = false;
private boolean mIsAutoBrightnessEnabled = false;
private float mAutoBrightness;
private int mHbmMode = BrightnessInfo.HIGH_BRIGHTNESS_MODE_OFF;
+ private boolean mIsHdrLayerPresent = false;
/**
* If HBM is currently running, this is the start time for the current HBM session.
@@ -69,23 +75,26 @@ class HighBrightnessModeController {
*/
private LinkedList<HbmEvent> mEvents = new LinkedList<>();
- HighBrightnessModeController(Handler handler, float brightnessMin, float brightnessMax,
- HighBrightnessModeData hbmData, Runnable hbmChangeCallback) {
- this(SystemClock::uptimeMillis, handler, brightnessMin, brightnessMax, hbmData,
- hbmChangeCallback);
+ HighBrightnessModeController(Handler handler, IBinder displayToken, float brightnessMin,
+ float brightnessMax, HighBrightnessModeData hbmData, Runnable hbmChangeCallback) {
+ this(SystemClock::uptimeMillis, handler, displayToken, brightnessMin, brightnessMax,
+ hbmData, hbmChangeCallback);
}
@VisibleForTesting
- HighBrightnessModeController(Clock clock, Handler handler, float brightnessMin,
- float brightnessMax, HighBrightnessModeData hbmData, Runnable hbmChangeCallback) {
+ HighBrightnessModeController(Clock clock, Handler handler, IBinder displayToken,
+ float brightnessMin, float brightnessMax, HighBrightnessModeData hbmData,
+ Runnable hbmChangeCallback) {
mClock = clock;
mHandler = handler;
mBrightnessMin = brightnessMin;
mBrightnessMax = brightnessMax;
- mHbmData = hbmData;
mHbmChangeCallback = hbmChangeCallback;
mAutoBrightness = PowerManager.BRIGHTNESS_INVALID_FLOAT;
mRecalcRunnable = this::recalculateTimeAllowance;
+ mHdrListener = new HdrListener();
+
+ resetHbmData(displayToken, hbmData);
}
void setAutoBrightnessEnabled(boolean isEnabled) {
@@ -117,6 +126,10 @@ class HighBrightnessModeController {
}
}
+ float getHdrBrightnessValue() {
+ return mBrightnessMax;
+ }
+
void onAmbientLuxChange(float ambientLux) {
if (!deviceSupportsHbm() || !mIsAutoBrightnessEnabled) {
return;
@@ -138,11 +151,12 @@ class HighBrightnessModeController {
// If we are starting or ending a high brightness mode session, store the current
// session in mRunningStartTimeMillis, or the old one in mEvents.
- final boolean wasOldBrightnessHigh = oldAutoBrightness > mHbmData.transitionPoint;
- final boolean isNewBrightnessHigh = mAutoBrightness > mHbmData.transitionPoint;
- if (wasOldBrightnessHigh != isNewBrightnessHigh) {
+ final boolean wasHbmDrainingAvailableTime = mRunningStartTimeMillis != -1;
+ final boolean shouldHbmDrainAvailableTime = mAutoBrightness > mHbmData.transitionPoint
+ && !mIsHdrLayerPresent;
+ if (wasHbmDrainingAvailableTime != shouldHbmDrainAvailableTime) {
final long currentTime = mClock.uptimeMillis();
- if (isNewBrightnessHigh) {
+ if (shouldHbmDrainAvailableTime) {
mRunningStartTimeMillis = currentTime;
} else {
mEvents.addFirst(new HbmEvent(mRunningStartTimeMillis, currentTime));
@@ -161,30 +175,49 @@ class HighBrightnessModeController {
return mHbmMode;
}
+ void stop() {
+ registerHdrListener(null /*displayToken*/);
+ }
+
+ void resetHbmData(IBinder displayToken, HighBrightnessModeData hbmData) {
+ mHbmData = hbmData;
+ unregisterHdrListener();
+ if (deviceSupportsHbm()) {
+ registerHdrListener(displayToken);
+ recalculateTimeAllowance();
+ }
+ }
+
void dump(PrintWriter pw) {
pw.println("HighBrightnessModeController:");
- pw.println(" mBrightnessMin=" + mBrightnessMin);
- pw.println(" mBrightnessMax=" + mBrightnessMax);
+ pw.println(" mCurrentMin=" + getCurrentBrightnessMin());
+ pw.println(" mCurrentMax=" + getCurrentBrightnessMax());
+ pw.println(" mHbmMode=" + BrightnessInfo.hbmToString(mHbmMode));
+ pw.println(" remainingTime=" + calculateRemainingTime(mClock.uptimeMillis()));
pw.println(" mHbmData=" + mHbmData);
pw.println(" mIsInAllowedAmbientRange=" + mIsInAllowedAmbientRange);
pw.println(" mIsTimeAvailable= " + mIsTimeAvailable);
pw.println(" mIsAutoBrightnessEnabled=" + mIsAutoBrightnessEnabled);
pw.println(" mAutoBrightness=" + mAutoBrightness);
+ pw.println(" mIsHdrLayerPresent=" + mIsHdrLayerPresent);
+ pw.println(" mBrightnessMin=" + mBrightnessMin);
+ pw.println(" mBrightnessMax=" + mBrightnessMax);
}
private boolean isCurrentlyAllowed() {
- return mIsAutoBrightnessEnabled && mIsTimeAvailable && mIsInAllowedAmbientRange;
+ return mIsHdrLayerPresent
+ || (mIsAutoBrightnessEnabled && mIsTimeAvailable && mIsInAllowedAmbientRange);
}
private boolean deviceSupportsHbm() {
return mHbmData != null;
}
- /**
- * Recalculates the allowable HBM time.
- */
- private void recalculateTimeAllowance() {
- final long currentTime = mClock.uptimeMillis();
+ private long calculateRemainingTime(long currentTime) {
+ if (!deviceSupportsHbm()) {
+ return 0;
+ }
+
long timeAlreadyUsed = 0;
// First, lets see how much time we've taken for any currently running
@@ -222,8 +255,15 @@ class HighBrightnessModeController {
Slog.d(TAG, "Time already used after all sessions: " + timeAlreadyUsed);
}
- // See how much allowable time we have left.
- final long remainingTime = Math.max(0, mHbmData.timeMaxMillis - timeAlreadyUsed);
+ return Math.max(0, mHbmData.timeMaxMillis - timeAlreadyUsed);
+ }
+
+ /**
+ * Recalculates the allowable HBM time.
+ */
+ private void recalculateTimeAllowance() {
+ final long currentTime = mClock.uptimeMillis();
+ final long remainingTime = calculateRemainingTime(currentTime);
// We allow HBM if there is more than the minimum required time available
// or if brightness is already in the high range, if there is any time left at all.
@@ -242,6 +282,7 @@ class HighBrightnessModeController {
// If we are not allowed...timeout when the oldest event moved outside of the timing
// window by at least minTime. Basically, we're calculating the soonest time we can
// get {@code timeMinMillis} back to us.
+ final long windowstartTimeMillis = currentTime - mHbmData.timeWindowMillis;
final HbmEvent lastEvent = mEvents.getLast();
final long startTimePlusMinMillis =
Math.max(windowstartTimeMillis, lastEvent.startTimeMillis)
@@ -278,12 +319,36 @@ class HighBrightnessModeController {
}
private int calculateHighBrightnessMode() {
- if (deviceSupportsHbm() && isCurrentlyAllowed()) {
+ if (!deviceSupportsHbm()) {
+ return BrightnessInfo.HIGH_BRIGHTNESS_MODE_OFF;
+ } else if (mIsHdrLayerPresent) {
+ return BrightnessInfo.HIGH_BRIGHTNESS_MODE_HDR;
+ } else if (isCurrentlyAllowed()) {
return BrightnessInfo.HIGH_BRIGHTNESS_MODE_SUNLIGHT;
}
+
return BrightnessInfo.HIGH_BRIGHTNESS_MODE_OFF;
}
+ private void registerHdrListener(IBinder displayToken) {
+ if (mRegisteredDisplayToken == displayToken) {
+ return;
+ }
+
+ unregisterHdrListener();
+ mRegisteredDisplayToken = displayToken;
+ if (mRegisteredDisplayToken != null) {
+ mHdrListener.register(mRegisteredDisplayToken);
+ }
+ }
+
+ private void unregisterHdrListener() {
+ if (mRegisteredDisplayToken != null) {
+ mHdrListener.unregister(mRegisteredDisplayToken);
+ mIsHdrLayerPresent = false;
+ }
+ }
+
/**
* Represents an event in which High Brightness Mode was enabled.
*/
@@ -302,4 +367,18 @@ class HighBrightnessModeController {
+ ((endTimeMillis - startTimeMillis) / 1000) + "]";
}
}
+
+ private class HdrListener extends SurfaceControlHdrLayerInfoListener {
+ @Override
+ public void onHdrInfoChanged(IBinder displayToken, int numberOfHdrLayers,
+ int maxW, int maxH, int flags) {
+ mHandler.post(() -> {
+ mIsHdrLayerPresent = numberOfHdrLayers > 0;
+ // Calling the auto-brightness update so that we can recalculate
+ // auto-brightness with HDR in mind. When HDR layers are present,
+ // we don't limit auto-brightness' HBM time limits.
+ onAutoBrightnessChanged(mAutoBrightness);
+ });
+ }
+ }
}
diff --git a/services/core/java/com/android/server/display/LocalDisplayAdapter.java b/services/core/java/com/android/server/display/LocalDisplayAdapter.java
index 2546118f1cc7..754e35eff6c1 100644
--- a/services/core/java/com/android/server/display/LocalDisplayAdapter.java
+++ b/services/core/java/com/android/server/display/LocalDisplayAdapter.java
@@ -67,6 +67,8 @@ final class LocalDisplayAdapter extends DisplayAdapter {
private static final int NO_DISPLAY_MODE_ID = 0;
+ private static final float NITS_INVALID = -1;
+
private final LongSparseArray<LocalDisplayDevice> mDevices = new LongSparseArray<>();
private final Injector mInjector;
@@ -190,6 +192,7 @@ final class LocalDisplayAdapter extends DisplayAdapter {
private int mState = Display.STATE_UNKNOWN;
// This is only set in the runnable returned from requestDisplayStateLocked.
private float mBrightnessState = PowerManager.BRIGHTNESS_INVALID_FLOAT;
+ private float mSdrBrightnessState = PowerManager.BRIGHTNESS_INVALID_FLOAT;
private int mDefaultModeId;
private int mDefaultModeGroup;
private int mActiveModeId;
@@ -644,13 +647,15 @@ final class LocalDisplayAdapter extends DisplayAdapter {
}
@Override
- public Runnable requestDisplayStateLocked(final int state, final float brightnessState) {
+ public Runnable requestDisplayStateLocked(final int state, final float brightnessState,
+ final float sdrBrightnessState) {
// Assume that the brightness is off if the display is being turned off.
assert state != Display.STATE_OFF || BrightnessSynchronizer.floatEquals(
brightnessState, PowerManager.BRIGHTNESS_OFF_FLOAT);
final boolean stateChanged = (mState != state);
- final boolean brightnessChanged = (!BrightnessSynchronizer.floatEquals(
- mBrightnessState, brightnessState));
+ final boolean brightnessChanged =
+ !(BrightnessSynchronizer.floatEquals(mBrightnessState, brightnessState)
+ && BrightnessSynchronizer.floatEquals(mSdrBrightnessState, sdrBrightnessState));
if (stateChanged || brightnessChanged) {
final long physicalDisplayId = mPhysicalDisplayId;
final IBinder token = getDisplayTokenLocked();
@@ -702,8 +707,9 @@ final class LocalDisplayAdapter extends DisplayAdapter {
// Apply brightness changes given that we are in a non-suspended state.
if (brightnessChanged || vrModeChange) {
- setDisplayBrightness(brightnessState);
+ setDisplayBrightness(brightnessState, sdrBrightnessState);
mBrightnessState = brightnessState;
+ mSdrBrightnessState = sdrBrightnessState;
}
// Enter the final desired state, possibly suspended.
@@ -764,8 +770,8 @@ final class LocalDisplayAdapter extends DisplayAdapter {
}
}
- private void setDisplayBrightness(float brightness) {
- // Ensure brightnessState is valid, before processing and sending to
+ private void setDisplayBrightness(float brightness, float sdrBrightness) {
+ // Ensure brightnessState is valid before processing and sending to
// surface control
if (Float.isNaN(brightness)) {
return;
@@ -774,17 +780,31 @@ final class LocalDisplayAdapter extends DisplayAdapter {
if (DEBUG) {
Slog.d(TAG, "setDisplayBrightness("
+ "id=" + physicalDisplayId
- + ", brightness=" + brightness + ")");
+ + ", brightness=" + brightness
+ + ", sdrBrightness=" + sdrBrightness + ")");
}
Trace.traceBegin(Trace.TRACE_TAG_POWER, "setDisplayBrightness("
- + "id=" + physicalDisplayId + ", brightness=" + brightness + ")");
+ + "id=" + physicalDisplayId + ", brightness=" + brightness
+ + ", sdrBrightness=" + sdrBrightness + ")");
try {
- float backlight = brightnessToBacklight(brightness);
- mBacklightAdapter.setBacklight(backlight);
+ final float backlight = brightnessToBacklight(brightness);
+ float nits = NITS_INVALID;
+ float sdrBacklight = PowerManager.BRIGHTNESS_INVALID_FLOAT;
+ float sdrNits = NITS_INVALID;
+ if (getDisplayDeviceConfig().hasNitsMapping()
+ && sdrBrightness != PowerManager.BRIGHTNESS_INVALID_FLOAT) {
+ nits = backlightToNits(backlight);
+ sdrBacklight = brightnessToBacklight(sdrBrightness);
+ sdrNits = backlightToNits(sdrBacklight);
+ }
+ mBacklightAdapter.setBacklight(sdrBacklight, sdrNits, backlight, nits);
Trace.traceCounter(Trace.TRACE_TAG_POWER,
"ScreenBrightness",
BrightnessSynchronizer.brightnessFloatToInt(brightness));
+ Trace.traceCounter(Trace.TRACE_TAG_POWER,
+ "SdrScreenBrightness",
+ BrightnessSynchronizer.brightnessFloatToInt(sdrBrightness));
} finally {
Trace.traceEnd(Trace.TRACE_TAG_POWER);
}
@@ -793,6 +813,10 @@ final class LocalDisplayAdapter extends DisplayAdapter {
private float brightnessToBacklight(float brightness) {
return getDisplayDeviceConfig().getBacklightFromBrightness(brightness);
}
+
+ private float backlightToNits(float backlight) {
+ return getDisplayDeviceConfig().getNitsFromBacklight(backlight);
+ }
};
}
return null;
@@ -1242,6 +1266,13 @@ final class LocalDisplayAdapter extends DisplayAdapter {
public boolean setDisplayBrightness(IBinder displayToken, float brightness) {
return SurfaceControl.setDisplayBrightness(displayToken, brightness);
}
+
+ public boolean setDisplayBrightness(IBinder displayToken, float sdrBacklight,
+ float sdrNits, float displayBacklight, float displayNits) {
+ return SurfaceControl.setDisplayBrightness(displayToken, sdrBacklight, sdrNits,
+ displayBacklight, displayNits);
+ }
+
}
static class BacklightAdapter {
@@ -1273,9 +1304,14 @@ final class LocalDisplayAdapter extends DisplayAdapter {
}
// Set backlight within min and max backlight values
- void setBacklight(float backlight) {
+ void setBacklight(float sdrBacklight, float sdrNits, float backlight, float nits) {
if (mUseSurfaceControlBrightness || mForceSurfaceControl) {
- mSurfaceControlProxy.setDisplayBrightness(mDisplayToken, backlight);
+ if (sdrBacklight == PowerManager.BRIGHTNESS_INVALID_FLOAT) {
+ mSurfaceControlProxy.setDisplayBrightness(mDisplayToken, backlight);
+ } else {
+ mSurfaceControlProxy.setDisplayBrightness(mDisplayToken, sdrBacklight, sdrNits,
+ backlight, nits);
+ }
} else if (mBacklight != null) {
mBacklight.setBrightness(backlight);
}
diff --git a/services/core/java/com/android/server/display/RampAnimator.java b/services/core/java/com/android/server/display/RampAnimator.java
index 26004a8da1a1..20feafa2d19c 100644
--- a/services/core/java/com/android/server/display/RampAnimator.java
+++ b/services/core/java/com/android/server/display/RampAnimator.java
@@ -26,7 +26,7 @@ import com.android.internal.display.BrightnessSynchronizer;
* A custom animator that progressively updates a property value at
* a given variable rate until it reaches a particular target value.
*/
-final class RampAnimator<T> {
+class RampAnimator<T> {
private final T mObject;
private final FloatProperty<T> mProperty;
private final Choreographer mChoreographer;
@@ -174,4 +174,52 @@ final class RampAnimator<T> {
public interface Listener {
void onAnimationEnd();
}
+
+ static class DualRampAnimator<T> {
+ private final RampAnimator<T> mFirst;
+ private final RampAnimator<T> mSecond;
+ private final Listener mInternalListener = new Listener() {
+ @Override
+ public void onAnimationEnd() {
+ if (mListener != null && !isAnimating()) {
+ mListener.onAnimationEnd();
+ }
+ }
+ };
+
+ private Listener mListener;
+
+ DualRampAnimator(T object, FloatProperty<T> firstProperty,
+ FloatProperty<T> secondProperty) {
+ mFirst = new RampAnimator(object, firstProperty);
+ mFirst.setListener(mInternalListener);
+ mSecond = new RampAnimator(object, secondProperty);
+ mSecond.setListener(mInternalListener);
+ }
+
+ /**
+ * Starts animating towards the specified values.
+ *
+ * If this is the first time the property is being set or if the rate is 0,
+ * the value jumps directly to the target.
+ *
+ * @param firstTarget The first target value.
+ * @param secondTarget The second target value.
+ * @param rate The convergence rate in units per second, or 0 to set the value immediately.
+ * @return True if either target differs from the previous target.
+ */
+ public boolean animateTo(float firstTarget, float secondTarget, float rate) {
+ final boolean firstRetval = mFirst.animateTo(firstTarget, rate);
+ final boolean secondRetval = mSecond.animateTo(secondTarget, rate);
+ return firstRetval && secondRetval;
+ }
+
+ public void setListener(Listener listener) {
+ mListener = listener;
+ }
+
+ public boolean isAnimating() {
+ return mFirst.isAnimating() && mSecond.isAnimating();
+ }
+ }
}
diff --git a/services/core/java/com/android/server/display/VirtualDisplayAdapter.java b/services/core/java/com/android/server/display/VirtualDisplayAdapter.java
index 52a810bd8caa..b7931c8a8424 100644
--- a/services/core/java/com/android/server/display/VirtualDisplayAdapter.java
+++ b/services/core/java/com/android/server/display/VirtualDisplayAdapter.java
@@ -293,7 +293,8 @@ public class VirtualDisplayAdapter extends DisplayAdapter {
}
@Override
- public Runnable requestDisplayStateLocked(int state, float brightnessState) {
+ public Runnable requestDisplayStateLocked(int state, float brightnessState,
+ float sdrBrightnessState) {
if (state != mDisplayState) {
mDisplayState = state;
if (state == Display.STATE_OFF) {
diff --git a/services/core/java/com/android/server/graphics/fonts/UpdatableFontDir.java b/services/core/java/com/android/server/graphics/fonts/UpdatableFontDir.java
index 38d1aab70550..06a9832f1b54 100644
--- a/services/core/java/com/android/server/graphics/fonts/UpdatableFontDir.java
+++ b/services/core/java/com/android/server/graphics/fonts/UpdatableFontDir.java
@@ -28,6 +28,7 @@ import android.system.ErrnoException;
import android.system.Os;
import android.text.FontConfig;
import android.util.ArrayMap;
+import android.util.AtomicFile;
import android.util.Base64;
import android.util.Slog;
@@ -43,6 +44,7 @@ import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
+import java.util.Objects;
import java.util.function.Function;
import java.util.function.Supplier;
@@ -120,8 +122,7 @@ final class UpdatableFontDir {
private final File mFilesDir;
private final FontFileParser mParser;
private final FsverityUtil mFsverityUtil;
- private final File mConfigFile;
- private final File mTmpConfigFile;
+ private final AtomicFile mConfigFile;
private final Supplier<Long> mCurrentTimeSupplier;
private final Function<Map<String, File>, FontConfig> mConfigSupplier;
@@ -131,13 +132,13 @@ final class UpdatableFontDir {
/**
* A mutable map containing mapping from font file name (e.g. "NotoColorEmoji.ttf") to {@link
* FontFileInfo}. All files in this map are validated, and have higher revision numbers than
- * corresponding font files in {@link #mPreinstalledFontDirs}.
+ * corresponding font files returned by {@link #mConfigSupplier}.
*/
private final ArrayMap<String, FontFileInfo> mFontFileInfoMap = new ArrayMap<>();
UpdatableFontDir(File filesDir, FontFileParser parser, FsverityUtil fsverityUtil) {
this(filesDir, parser, fsverityUtil, new File(CONFIG_XML_FILE),
- () -> System.currentTimeMillis(),
+ System::currentTimeMillis,
(map) -> SystemFonts.getSystemFontConfig(map, 0, 0)
);
}
@@ -149,8 +150,7 @@ final class UpdatableFontDir {
mFilesDir = filesDir;
mParser = parser;
mFsverityUtil = fsverityUtil;
- mConfigFile = configFile;
- mTmpConfigFile = new File(configFile.getAbsoluteFile() + ".tmp");
+ mConfigFile = new AtomicFile(configFile);
mCurrentTimeSupplier = currentTimeSupplier;
mConfigSupplier = configSupplier;
}
@@ -166,18 +166,16 @@ final class UpdatableFontDir {
mConfigVersion = 1;
boolean success = false;
try {
- PersistentSystemFontConfig.Config config = new PersistentSystemFontConfig.Config();
- try (FileInputStream fis = new FileInputStream(mConfigFile)) {
- PersistentSystemFontConfig.loadFromXml(fis, config);
- } catch (IOException | XmlPullParserException e) {
- // The font config file is missing on the first boot. Just do nothing.
- return;
- }
+ PersistentSystemFontConfig.Config config = readPersistentConfig();
mLastModifiedMillis = config.lastModifiedMillis;
File[] dirs = mFilesDir.listFiles();
- if (dirs == null) return;
- FontConfig fontConfig = getSystemFontConfig();
+ if (dirs == null) {
+ // mFilesDir should be created by init script.
+ Slog.e(TAG, "Could not read: " + mFilesDir);
+ return;
+ }
+ FontConfig fontConfig = null;
for (File dir : dirs) {
if (!dir.getName().startsWith(RANDOM_DIR_PREFIX)) {
Slog.e(TAG, "Unexpected dir found: " + dir);
@@ -194,6 +192,9 @@ final class UpdatableFontDir {
return;
}
FontFileInfo fontFileInfo = validateFontFile(files[0]);
+ if (fontConfig == null) {
+ fontConfig = getSystemFontConfig();
+ }
addFileToMapIfSameOrNewer(fontFileInfo, fontConfig, true /* deleteOldFile */);
}
success = true;
@@ -216,15 +217,9 @@ final class UpdatableFontDir {
FileUtils.deleteContents(mFilesDir);
mLastModifiedMillis = mCurrentTimeSupplier.get();
- try (FileOutputStream fos = new FileOutputStream(mConfigFile)) {
- PersistentSystemFontConfig.Config config = new PersistentSystemFontConfig.Config();
- config.lastModifiedMillis = mLastModifiedMillis;
- PersistentSystemFontConfig.writeToXml(fos, config);
- } catch (Exception e) {
- throw new SystemFontException(
- FontManager.RESULT_ERROR_FAILED_UPDATE_CONFIG,
- "Failed to write config XML.", e);
- }
+ PersistentSystemFontConfig.Config config = new PersistentSystemFontConfig.Config();
+ config.lastModifiedMillis = mLastModifiedMillis;
+ writePersistentConfig(config);
mConfigVersion++;
}
@@ -234,6 +229,18 @@ final class UpdatableFontDir {
* before this method is called.
*/
public void update(List<FontUpdateRequest> requests) throws SystemFontException {
+ for (FontUpdateRequest request : requests) {
+ switch (request.getType()) {
+ case FontUpdateRequest.TYPE_UPDATE_FONT_FILE:
+ Objects.requireNonNull(request.getFd());
+ Objects.requireNonNull(request.getSignature());
+ break;
+ case FontUpdateRequest.TYPE_UPDATE_FONT_FAMILY:
+ Objects.requireNonNull(request.getFontFamily());
+ Objects.requireNonNull(request.getFontFamily().getName());
+ break;
+ }
+ }
// Backup the mapping for rollback.
ArrayMap<String, FontFileInfo> backupMap = new ArrayMap<>(mFontFileInfoMap);
PersistentSystemFontConfig.Config curConfig = readPersistentConfig();
@@ -277,20 +284,7 @@ final class UpdatableFontDir {
newConfig.updatedFontDirs.add(info.getRandomizedFontDir().getName());
}
newConfig.fontFamilies.addAll(familyMap.values());
-
- try (FileOutputStream fos = new FileOutputStream(mTmpConfigFile)) {
- PersistentSystemFontConfig.writeToXml(fos, newConfig);
- } catch (Exception e) {
- throw new SystemFontException(
- FontManager.RESULT_ERROR_FAILED_UPDATE_CONFIG,
- "Failed to write config XML.", e);
- }
-
- if (!mFsverityUtil.rename(mTmpConfigFile, mConfigFile)) {
- throw new SystemFontException(
- FontManager.RESULT_ERROR_FAILED_UPDATE_CONFIG,
- "Failed to stage the config file.");
- }
+ writePersistentConfig(newConfig);
mConfigVersion++;
success = true;
} finally {
@@ -420,7 +414,7 @@ final class UpdatableFontDir {
/**
* Add the given {@link FontFileInfo} to {@link #mFontFileInfoMap} if its font revision is
* equal to or higher than the revision of currently used font file (either in
- * {@link #mFontFileInfoMap} or {@link #mPreinstalledFontDirs}).
+ * {@link #mFontFileInfoMap} or {@code fontConfig}).
*/
private boolean addFileToMapIfSameOrNewer(FontFileInfo fontFileInfo, FontConfig fontConfig,
boolean deleteOldFile) {
@@ -568,7 +562,7 @@ final class UpdatableFontDir {
private PersistentSystemFontConfig.Config readPersistentConfig() {
PersistentSystemFontConfig.Config config = new PersistentSystemFontConfig.Config();
- try (FileInputStream fis = new FileInputStream(mConfigFile)) {
+ try (FileInputStream fis = mConfigFile.openRead()) {
PersistentSystemFontConfig.loadFromXml(fis, config);
} catch (IOException | XmlPullParserException e) {
// The font config file is missing on the first boot. Just do nothing.
@@ -576,6 +570,23 @@ final class UpdatableFontDir {
return config;
}
+ private void writePersistentConfig(PersistentSystemFontConfig.Config config)
+ throws SystemFontException {
+ FileOutputStream fos = null;
+ try {
+ fos = mConfigFile.startWrite();
+ PersistentSystemFontConfig.writeToXml(fos, config);
+ mConfigFile.finishWrite(fos);
+ } catch (IOException e) {
+ if (fos != null) {
+ mConfigFile.failWrite(fos);
+ }
+ throw new SystemFontException(
+ FontManager.RESULT_ERROR_FAILED_UPDATE_CONFIG,
+ "Failed to write config XML.", e);
+ }
+ }
+
/* package */ int getConfigVersion() {
return mConfigVersion;
}
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index 9a3fe82f2d0f..03153d375d33 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -1782,6 +1782,24 @@ public class PackageManagerService extends IPackageManager.Stub
}
@Override
+ public <ExceptionOne extends Exception, ExceptionTwo extends Exception> void
+ withPackageSettingsThrowing2(
+ @NonNull Throwing2Consumer<Function<String, PackageSetting>, ExceptionOne,
+ ExceptionTwo> block) throws ExceptionOne, ExceptionTwo {
+ final Computer snapshot = snapshotComputer();
+
+ // This method needs to either lock or not lock consistently throughout the method,
+ // so if the live computer is returned, force a wrapping sync block.
+ if (snapshot == mLiveComputer) {
+ synchronized (mLock) {
+ block.accept(snapshot::getPackageSetting);
+ }
+ } else {
+ block.accept(snapshot::getPackageSetting);
+ }
+ }
+
+ @Override
public <Output, ExceptionType extends Exception> Output
withPackageSettingsReturningThrowing(@NonNull ThrowingFunction<Function<String,
PackageSetting>, Output, ExceptionType> block) throws ExceptionType {
@@ -22459,21 +22477,44 @@ public class PackageManagerService extends IPackageManager.Stub
}
@Override
- public byte[] getIntentFilterVerificationBackup(int userId) {
+ public byte[] getDomainVerificationBackup(int userId) {
if (Binder.getCallingUid() != Process.SYSTEM_UID) {
- throw new SecurityException("Only the system may call getIntentFilterVerificationBackup()");
+ throw new SecurityException("Only the system may call getDomainVerificationBackup()");
}
- // TODO(b/170746586)
- return null;
+ try {
+ try (ByteArrayOutputStream output = new ByteArrayOutputStream()) {
+ TypedXmlSerializer serializer = Xml.resolveSerializer(output);
+ mDomainVerificationManager.writeSettings(serializer, true, userId);
+ return output.toByteArray();
+ }
+ } catch (Exception e) {
+ if (DEBUG_BACKUP) {
+ Slog.e(TAG, "Unable to write domain verification for backup", e);
+ }
+ return null;
+ }
}
@Override
- public void restoreIntentFilterVerification(byte[] backup, int userId) {
+ public void restoreDomainVerification(byte[] backup, int userId) {
if (Binder.getCallingUid() != Process.SYSTEM_UID) {
throw new SecurityException("Only the system may call restorePreferredActivities()");
}
- // TODO(b/170746586)
+
+ try {
+ ByteArrayInputStream input = new ByteArrayInputStream(backup);
+ TypedXmlPullParser parser = Xml.resolvePullParser(input);
+
+ // User ID input isn't necessary here as it assumes the user integers match and that
+ // the only states inside the backup XML are for the target user.
+ mDomainVerificationManager.restoreSettings(parser);
+ input.close();
+ } catch (Exception e) {
+ if (DEBUG_BACKUP) {
+ Slog.e(TAG, "Exception restoring domain verification: " + e.getMessage());
+ }
+ }
}
@Override
diff --git a/services/core/java/com/android/server/pm/Settings.java b/services/core/java/com/android/server/pm/Settings.java
index b6d4a5b88f8a..f891f0e5441d 100644
--- a/services/core/java/com/android/server/pm/Settings.java
+++ b/services/core/java/com/android/server/pm/Settings.java
@@ -2339,7 +2339,8 @@ public final class Settings implements Watchable, Snappable {
}
}
- mDomainVerificationManager.writeSettings(serializer);
+ mDomainVerificationManager.writeSettings(serializer, false /* includeSignatures */,
+ UserHandle.USER_ALL);
mKeySetManagerService.writeKeySetManagerServiceLPr(serializer);
@@ -2913,13 +2914,7 @@ public final class Settings implements Watchable, Snappable {
}
str.close();
-
- } catch (XmlPullParserException e) {
- mReadMessages.append("Error reading: " + e.toString());
- PackageManagerService.reportSettingsProblem(Log.ERROR, "Error reading settings: " + e);
- Slog.wtf(PackageManagerService.TAG, "Error reading package manager settings", e);
-
- } catch (java.io.IOException e) {
+ } catch (IOException | XmlPullParserException e) {
mReadMessages.append("Error reading: " + e.toString());
PackageManagerService.reportSettingsProblem(Log.ERROR, "Error reading settings: " + e);
Slog.wtf(PackageManagerService.TAG, "Error reading package manager settings", e);
diff --git a/services/core/java/com/android/server/pm/verify/domain/DomainVerificationManagerInternal.java b/services/core/java/com/android/server/pm/verify/domain/DomainVerificationManagerInternal.java
index 5aed3670e5c7..b54b1698a3f4 100644
--- a/services/core/java/com/android/server/pm/verify/domain/DomainVerificationManagerInternal.java
+++ b/services/core/java/com/android/server/pm/verify/domain/DomainVerificationManagerInternal.java
@@ -234,8 +234,8 @@ public interface DomainVerificationManagerInternal {
* This will mutate internal {@link DomainVerificationPkgState} and so will hold the internal
* lock. This should never be called from within the domain verification classes themselves.
* <p>
- * This will NOT call {@link #writeSettings(TypedXmlSerializer)}. That must be handled by the
- * caller.
+ * This will NOT call {@link #writeSettings(TypedXmlSerializer, boolean, int)}. That must be
+ * handled by the caller.
*/
void addPackage(@NonNull PackageSetting newPkgSetting);
@@ -249,20 +249,27 @@ public interface DomainVerificationManagerInternal {
* This will mutate internal {@link DomainVerificationPkgState} and so will hold the internal
* lock. This should never be called from within the domain verification classes themselves.
* <p>
- * This will NOT call {@link #writeSettings(TypedXmlSerializer)}. That must be handled by the
- * caller.
+ * This will NOT call {@link #writeSettings(TypedXmlSerializer, boolean, int)}. That must be
+ * handled by the caller.
*/
void migrateState(@NonNull PackageSetting oldPkgSetting, @NonNull PackageSetting newPkgSetting);
/**
* Serializes the entire internal state. This is equivalent to a full backup of the existing
* verification state. This write includes legacy state, as a sibling tag the modern state.
+ *
+ * @param includeSignatures Whether to include the package signatures in the output, mainly
+ * used for backing up the user settings and ensuring they're
+ * re-attached to the same package.
+ * @param userId The user to write out. Supports {@link UserHandle#USER_ALL} if all users
+ * should be written.
*/
- void writeSettings(@NonNull TypedXmlSerializer serializer) throws IOException;
+ void writeSettings(@NonNull TypedXmlSerializer serializer, boolean includeSignatures,
+ @UserIdInt int userId) throws IOException;
/**
* Read back a list of {@link DomainVerificationPkgState}s previously written by {@link
- * #writeSettings(TypedXmlSerializer)}. Assumes that the
+ * #writeSettings(TypedXmlSerializer, boolean, int)}. Assumes that the
* {@link DomainVerificationPersistence#TAG_DOMAIN_VERIFICATIONS} tag has already been entered.
* <p>
* This is expected to only be used to re-attach states for packages already known to be on the
@@ -298,7 +305,7 @@ public interface DomainVerificationManagerInternal {
/**
* Restore a list of {@link DomainVerificationPkgState}s previously written by {@link
- * #writeSettings(TypedXmlSerializer)}. Assumes that the
+ * #writeSettings(TypedXmlSerializer, boolean, int)}. Assumes that the
* {@link DomainVerificationPersistence#TAG_DOMAIN_VERIFICATIONS}
* tag has already been entered.
* <p>
@@ -403,7 +410,7 @@ public interface DomainVerificationManagerInternal {
/**
* Notify that a settings change has been made and that eventually
- * {@link #writeSettings(TypedXmlSerializer)} should be invoked by the parent.
+ * {@link #writeSettings(TypedXmlSerializer, boolean, int)} should be invoked by the parent.
*/
void scheduleWriteSettings();
@@ -447,6 +454,15 @@ public interface DomainVerificationManagerInternal {
throws ExceptionType;
/**
+ * Variant which throws 2 exceptions.
+ * @see #withPackageSettings(Consumer)
+ */
+ <ExceptionOne extends Exception, ExceptionTwo extends Exception> void
+ withPackageSettingsThrowing2(
+ @NonNull Throwing2Consumer<Function<String, PackageSetting>, ExceptionOne,
+ ExceptionTwo> block) throws ExceptionOne, ExceptionTwo;
+
+ /**
* Variant which returns a value to the caller and throws.
* @see #withPackageSettings(Consumer)
*/
@@ -461,6 +477,11 @@ public interface DomainVerificationManagerInternal {
void accept(Input input) throws ExceptionType;
}
+ interface Throwing2Consumer<Input, ExceptionOne extends Exception,
+ ExceptionTwo extends Exception> {
+ void accept(Input input) throws ExceptionOne, ExceptionTwo;
+ }
+
interface ThrowingFunction<Input, Output, ExceptionType extends Exception> {
Output apply(Input input) throws ExceptionType;
}
diff --git a/services/core/java/com/android/server/pm/verify/domain/DomainVerificationPersistence.java b/services/core/java/com/android/server/pm/verify/domain/DomainVerificationPersistence.java
index f0ad98c26ede..e803457fcb97 100644
--- a/services/core/java/com/android/server/pm/verify/domain/DomainVerificationPersistence.java
+++ b/services/core/java/com/android/server/pm/verify/domain/DomainVerificationPersistence.java
@@ -18,8 +18,10 @@ package com.android.server.pm.verify.domain;
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.annotation.UserIdInt;
import android.content.pm.Signature;
import android.content.pm.verify.domain.DomainVerificationState;
+import android.os.UserHandle;
import android.text.TextUtils;
import android.util.ArrayMap;
import android.util.ArraySet;
@@ -77,7 +79,8 @@ public class DomainVerificationPersistence {
@NonNull DomainVerificationStateMap<DomainVerificationPkgState> attached,
@NonNull ArrayMap<String, DomainVerificationPkgState> pending,
@NonNull ArrayMap<String, DomainVerificationPkgState> restored,
- @Nullable Function<String, String> pkgNameToSignature) throws IOException {
+ @UserIdInt int userId, @Nullable Function<String, String> pkgNameToSignature)
+ throws IOException {
try (SettingsXml.Serializer serializer = SettingsXml.serializer(xmlSerializer)) {
try (SettingsXml.WriteSection ignored = serializer.startSection(
TAG_DOMAIN_VERIFICATIONS)) {
@@ -98,26 +101,27 @@ public class DomainVerificationPersistence {
}
try (SettingsXml.WriteSection activeSection = serializer.startSection(TAG_ACTIVE)) {
- writePackageStates(activeSection, active, pkgNameToSignature);
+ writePackageStates(activeSection, active, userId, pkgNameToSignature);
}
try (SettingsXml.WriteSection restoredSection = serializer.startSection(
TAG_RESTORED)) {
- writePackageStates(restoredSection, restored.values(), pkgNameToSignature);
+ writePackageStates(restoredSection, restored.values(), userId,
+ pkgNameToSignature);
}
}
}
}
private static void writePackageStates(@NonNull SettingsXml.WriteSection section,
- @NonNull Collection<DomainVerificationPkgState> states,
+ @NonNull Collection<DomainVerificationPkgState> states, int userId,
@Nullable Function<String, String> pkgNameToSignature) throws IOException {
if (states.isEmpty()) {
return;
}
for (DomainVerificationPkgState state : states) {
- writePkgStateToXml(section, state, pkgNameToSignature);
+ writePkgStateToXml(section, state, userId, pkgNameToSignature);
}
}
@@ -211,7 +215,7 @@ public class DomainVerificationPersistence {
}
private static void writePkgStateToXml(@NonNull SettingsXml.WriteSection parentSection,
- @NonNull DomainVerificationPkgState pkgState,
+ @NonNull DomainVerificationPkgState pkgState, @UserIdInt int userId,
@Nullable Function<String, String> pkgNameToSignature) throws IOException {
String packageName = pkgState.getPackageName();
String signature = pkgNameToSignature == null
@@ -231,11 +235,12 @@ public class DomainVerificationPersistence {
pkgState.isHasAutoVerifyDomains())
.attribute(ATTR_SIGNATURE, signature)) {
writeStateMap(parentSection, pkgState.getStateMap());
- writeUserStates(parentSection, pkgState.getUserStates());
+ writeUserStates(parentSection, userId, pkgState.getUserStates());
}
}
private static void writeUserStates(@NonNull SettingsXml.WriteSection parentSection,
+ @UserIdInt int userId,
@NonNull SparseArray<DomainVerificationInternalUserState> states) throws IOException {
int size = states.size();
if (size == 0) {
@@ -243,8 +248,15 @@ public class DomainVerificationPersistence {
}
try (SettingsXml.WriteSection section = parentSection.startSection(TAG_USER_STATES)) {
- for (int index = 0; index < size; index++) {
- writeUserStateToXml(section, states.valueAt(index));
+ if (userId == UserHandle.USER_ALL) {
+ for (int index = 0; index < size; index++) {
+ writeUserStateToXml(section, states.valueAt(index));
+ }
+ } else {
+ DomainVerificationInternalUserState userState = states.get(userId);
+ if (userState != null) {
+ writeUserStateToXml(section, userState);
+ }
}
}
}
@@ -278,7 +290,7 @@ public class DomainVerificationPersistence {
return null;
}
- boolean allowLinkHandling = section.getBoolean(ATTR_ALLOW_LINK_HANDLING, true);
+ boolean allowLinkHandling = section.getBoolean(ATTR_ALLOW_LINK_HANDLING, false);
ArraySet<String> enabledHosts = new ArraySet<>();
SettingsXml.ChildSection child = section.children();
diff --git a/services/core/java/com/android/server/pm/verify/domain/DomainVerificationService.java b/services/core/java/com/android/server/pm/verify/domain/DomainVerificationService.java
index 3a4b849bac3c..f96eeb39e69e 100644
--- a/services/core/java/com/android/server/pm/verify/domain/DomainVerificationService.java
+++ b/services/core/java/com/android/server/pm/verify/domain/DomainVerificationService.java
@@ -129,10 +129,10 @@ public class DomainVerificationService extends SystemService
private final PlatformCompat mPlatformCompat;
@NonNull
- private final DomainVerificationSettings mSettings;
+ private final DomainVerificationCollector mCollector;
@NonNull
- private final DomainVerificationCollector mCollector;
+ private final DomainVerificationSettings mSettings;
@NonNull
private final DomainVerificationEnforcer mEnforcer;
@@ -159,8 +159,8 @@ public class DomainVerificationService extends SystemService
super(context);
mSystemConfig = systemConfig;
mPlatformCompat = platformCompat;
- mSettings = new DomainVerificationSettings();
mCollector = new DomainVerificationCollector(platformCompat, systemConfig);
+ mSettings = new DomainVerificationSettings(mCollector);
mEnforcer = new DomainVerificationEnforcer(context);
mDebug = new DomainVerificationDebug(mCollector);
mShell = new DomainVerificationShell(this);
@@ -1045,21 +1045,29 @@ public class DomainVerificationService extends SystemService
}
@Override
- public void writeSettings(@NonNull TypedXmlSerializer serializer) throws IOException {
+ public void writeSettings(@NonNull TypedXmlSerializer serializer, boolean includeSignatures,
+ @UserIdInt int userId)
+ throws IOException {
mConnection.withPackageSettingsThrowing(pkgSettings -> {
synchronized (mLock) {
- mSettings.writeSettings(serializer, mAttachedPkgStates, pkgName -> {
- PackageSetting pkgSetting = pkgSettings.apply(pkgName);
- if (pkgSetting == null) {
- // If querying for a user restored package that isn't installed on the
- // device yet, there will be no signature to write out. In that case,
- // it's expected that this returns null and it falls back to the restored
- // state's stored signature if it exists.
- return null;
- }
+ Function<String, String> pkgNameToSignature = null;
+ if (includeSignatures) {
+ pkgNameToSignature = pkgName -> {
+ PackageSetting pkgSetting = pkgSettings.apply(pkgName);
+ if (pkgSetting == null) {
+ // If querying for a user restored package that isn't installed on the
+ // device yet, there will be no signature to write out. In that case,
+ // it's expected that this returns null and it falls back to the
+ // restored state's stored signature if it exists.
+ return null;
+ }
- return PackageUtils.computeSignaturesSha256Digest(pkgSetting.getSignatures());
- });
+ return PackageUtils.computeSignaturesSha256Digest(
+ pkgSetting.getSignatures());
+ };
+ }
+
+ mSettings.writeSettings(serializer, mAttachedPkgStates, userId, pkgNameToSignature);
}
});
@@ -1067,11 +1075,14 @@ public class DomainVerificationService extends SystemService
}
@Override
- public void readSettings(@NonNull TypedXmlPullParser parser)
- throws IOException, XmlPullParserException {
- synchronized (mLock) {
- mSettings.readSettings(parser, mAttachedPkgStates);
- }
+ public void readSettings(@NonNull TypedXmlPullParser parser) throws IOException,
+ XmlPullParserException {
+ mConnection.<IOException, XmlPullParserException>withPackageSettingsThrowing2(
+ pkgSettings -> {
+ synchronized (mLock) {
+ mSettings.readSettings(parser, mAttachedPkgStates, pkgSettings);
+ }
+ });
}
@Override
@@ -1083,9 +1094,12 @@ public class DomainVerificationService extends SystemService
@Override
public void restoreSettings(@NonNull TypedXmlPullParser parser)
throws IOException, XmlPullParserException {
- synchronized (mLock) {
- mSettings.restoreSettings(parser, mAttachedPkgStates);
- }
+ mConnection.<IOException, XmlPullParserException>withPackageSettingsThrowing2(
+ pkgSettings -> {
+ synchronized (mLock) {
+ mSettings.restoreSettings(parser, mAttachedPkgStates, pkgSettings);
+ }
+ });
}
@Override
@@ -2031,6 +2045,15 @@ public class DomainVerificationService extends SystemService
}
@Override
+ public <ExceptionOne extends Exception, ExceptionTwo extends Exception> void
+ withPackageSettingsThrowing2(
+ @NonNull Throwing2Consumer<Function<String, PackageSetting>, ExceptionOne,
+ ExceptionTwo> block) throws ExceptionOne, ExceptionTwo {
+ enforceLocking();
+ mConnection.withPackageSettingsThrowing2(block);
+ }
+
+ @Override
public <Output, ExceptionType extends Exception> Output
withPackageSettingsReturningThrowing(@NonNull ThrowingFunction<Function<String,
PackageSetting>, Output, ExceptionType> block) throws ExceptionType {
diff --git a/services/core/java/com/android/server/pm/verify/domain/DomainVerificationSettings.java b/services/core/java/com/android/server/pm/verify/domain/DomainVerificationSettings.java
index c8e95b585b7a..3b2990e33963 100644
--- a/services/core/java/com/android/server/pm/verify/domain/DomainVerificationSettings.java
+++ b/services/core/java/com/android/server/pm/verify/domain/DomainVerificationSettings.java
@@ -20,7 +20,6 @@ import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.UserIdInt;
import android.content.pm.verify.domain.DomainVerificationState;
-import android.os.UserHandle;
import android.util.ArrayMap;
import android.util.ArraySet;
import android.util.SparseArray;
@@ -29,6 +28,8 @@ import android.util.TypedXmlSerializer;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
+import com.android.server.pm.PackageSetting;
+import com.android.server.pm.parsing.pkg.AndroidPackage;
import com.android.server.pm.verify.domain.models.DomainVerificationInternalUserState;
import com.android.server.pm.verify.domain.models.DomainVerificationPkgState;
import com.android.server.pm.verify.domain.models.DomainVerificationStateMap;
@@ -36,10 +37,15 @@ import com.android.server.pm.verify.domain.models.DomainVerificationStateMap;
import org.xmlpull.v1.XmlPullParserException;
import java.io.IOException;
+import java.util.Collections;
+import java.util.Set;
import java.util.function.Function;
class DomainVerificationSettings {
+ @NonNull
+ private final DomainVerificationCollector mCollector;
+
/**
* States read from disk that have yet to attach to a package, but are expected to, generally in
* the context of scanning packages already on disk. This is expected to be empty once the boot
@@ -67,24 +73,35 @@ class DomainVerificationSettings {
*/
private final Object mLock = new Object();
+ public DomainVerificationSettings(@NonNull DomainVerificationCollector collector) {
+ mCollector = collector;
+ }
+
+ public void writeSettings(@NonNull TypedXmlSerializer xmlSerializer,
+ @NonNull DomainVerificationStateMap<DomainVerificationPkgState> liveState,
+ @NonNull Function<String, String> pkgSignatureFunction) {
+
+ }
public void writeSettings(@NonNull TypedXmlSerializer xmlSerializer,
@NonNull DomainVerificationStateMap<DomainVerificationPkgState> liveState,
- @NonNull Function<String, String> pkgSignatureFunction) throws IOException {
+ @UserIdInt int userId, @NonNull Function<String, String> pkgSignatureFunction)
+ throws IOException {
synchronized (mLock) {
DomainVerificationPersistence.writeToXml(xmlSerializer, liveState,
- mPendingPkgStates, mRestoredPkgStates, pkgSignatureFunction);
+ mPendingPkgStates, mRestoredPkgStates, userId, pkgSignatureFunction);
}
}
/**
* Parses a previously stored set of states and merges them with {@param liveState}, directly
* mutating the values. This is intended for reading settings written by {@link
- * #writeSettings(TypedXmlSerializer, DomainVerificationStateMap, Function)} on the same device
- * setup.
+ * #writeSettings(TypedXmlSerializer, DomainVerificationStateMap, int, Function)} on the same
+ * device setup.
*/
public void readSettings(@NonNull TypedXmlPullParser parser,
- @NonNull DomainVerificationStateMap<DomainVerificationPkgState> liveState)
+ @NonNull DomainVerificationStateMap<DomainVerificationPkgState> liveState,
+ @NonNull Function<String, PackageSetting> pkgSettingFunction)
throws IOException, XmlPullParserException {
DomainVerificationPersistence.ReadResult result =
DomainVerificationPersistence.readFromXml(parser);
@@ -101,7 +118,7 @@ class DomainVerificationSettings {
// This branch should never be possible. Settings should be read from disk
// before any states are attached. But just in case, handle it.
if (!existingState.getId().equals(pkgState.getId())) {
- mergePkgState(existingState, pkgState);
+ mergePkgState(existingState, pkgState, pkgSettingFunction);
}
} else {
mPendingPkgStates.put(pkgName, pkgState);
@@ -121,7 +138,8 @@ class DomainVerificationSettings {
* mutating the values. This is intended for restoration across device setups.
*/
public void restoreSettings(@NonNull TypedXmlPullParser parser,
- @NonNull DomainVerificationStateMap<DomainVerificationPkgState> liveState)
+ @NonNull DomainVerificationStateMap<DomainVerificationPkgState> liveState,
+ @NonNull Function<String, PackageSetting> pkgSettingFunction)
throws IOException, XmlPullParserException {
// TODO(b/170746586): Restoration assumes user IDs match, which is probably not the case on
// a new device.
@@ -148,7 +166,7 @@ class DomainVerificationSettings {
}
if (existingState != null) {
- mergePkgState(existingState, newState);
+ mergePkgState(existingState, newState, pkgSettingFunction);
} else {
// If there's no existing state, that means the new state has to be transformed
// in preparation for attaching to brand new package that may eventually be
@@ -190,31 +208,34 @@ class DomainVerificationSettings {
* specific error codes are fresher than the restored state. Essentially state is only restored
* to grant additional verifications to an app.
* <p>
- * For user selection state, presence in either state will be considered an enabled host. NOTE:
- * only {@link UserHandle#USER_SYSTEM} is merged. There is no restore path in place for
- * multiple users.
- * <p>
- * TODO(b/170746586): Figure out the restore path for multiple users
- * <p>
- * This will mutate {@param oldState} to contain the merged state.
+ * For user selection state, presence in either state will be considered an enabled host. This
+ * assumes that all user IDs on the device match. If this isn't the case, then restore may set
+ * unexpected values.
+ *
+ * NOTE: This will mutate {@param oldState} to contain the merged state.
*/
@VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE)
- public static void mergePkgState(@NonNull DomainVerificationPkgState oldState,
- @NonNull DomainVerificationPkgState newState) {
+ public void mergePkgState(@NonNull DomainVerificationPkgState oldState,
+ @NonNull DomainVerificationPkgState newState,
+ @NonNull Function<String, PackageSetting> pkgSettingFunction) {
+ PackageSetting pkgSetting = pkgSettingFunction.apply(oldState.getPackageName());
+ AndroidPackage pkg = pkgSetting == null ? null : pkgSetting.getPkg();
+ Set<String> validDomains = pkg == null
+ ? Collections.emptySet() : mCollector.collectValidAutoVerifyDomains(pkg);
+
ArrayMap<String, Integer> oldStateMap = oldState.getStateMap();
ArrayMap<String, Integer> newStateMap = newState.getStateMap();
int size = newStateMap.size();
for (int index = 0; index < size; index++) {
String domain = newStateMap.keyAt(index);
Integer newStateCode = newStateMap.valueAt(index);
- Integer oldStateCodeInteger = oldStateMap.get(domain);
- if (oldStateCodeInteger == null) {
+ if (!validDomains.contains(domain)) {
// Cannot add domains to an app
continue;
}
- int oldStateCode = oldStateCodeInteger;
- if (oldStateCode == DomainVerificationState.STATE_NO_RESPONSE) {
+ Integer oldStateCode = oldStateMap.get(domain);
+ if (oldStateCode == null || oldStateCode == DomainVerificationState.STATE_NO_RESPONSE) {
if (newStateCode == DomainVerificationState.STATE_SUCCESS
|| newStateCode == DomainVerificationState.STATE_RESTORED) {
oldStateMap.put(domain, DomainVerificationState.STATE_RESTORED);
@@ -228,21 +249,21 @@ class DomainVerificationSettings {
SparseArray<DomainVerificationInternalUserState> newSelectionStates =
newState.getUserStates();
- DomainVerificationInternalUserState newUserState =
- newSelectionStates.get(UserHandle.USER_SYSTEM);
- if (newUserState != null) {
- ArraySet<String> newEnabledHosts = newUserState.getEnabledHosts();
- DomainVerificationInternalUserState oldUserState =
- oldSelectionStates.get(UserHandle.USER_SYSTEM);
-
- boolean linkHandlingAllowed = newUserState.isLinkHandlingAllowed();
- if (oldUserState == null) {
- oldUserState = new DomainVerificationInternalUserState(UserHandle.USER_SYSTEM,
- newEnabledHosts, linkHandlingAllowed);
- oldSelectionStates.put(UserHandle.USER_SYSTEM, oldUserState);
- } else {
- oldUserState.addHosts(newEnabledHosts)
- .setLinkHandlingAllowed(linkHandlingAllowed);
+ final int userStateSize = newSelectionStates.size();
+ for (int index = 0; index < userStateSize; index++) {
+ int userId = newSelectionStates.keyAt(index);
+ DomainVerificationInternalUserState newUserState = newSelectionStates.valueAt(index);
+ if (newUserState != null) {
+ ArraySet<String> newEnabledHosts = newUserState.getEnabledHosts();
+ DomainVerificationInternalUserState oldUserState = oldSelectionStates.get(userId);
+ boolean linkHandlingAllowed = newUserState.isLinkHandlingAllowed();
+ if (oldUserState == null) {
+ oldSelectionStates.put(userId, new DomainVerificationInternalUserState(userId,
+ newEnabledHosts, linkHandlingAllowed));
+ } else {
+ oldUserState.addHosts(newEnabledHosts)
+ .setLinkHandlingAllowed(linkHandlingAllowed);
+ }
}
}
}
diff --git a/services/core/java/com/android/server/pm/verify/domain/proxy/DomainVerificationProxy.java b/services/core/java/com/android/server/pm/verify/domain/proxy/DomainVerificationProxy.java
index 09abdd092648..7333d2394b2f 100644
--- a/services/core/java/com/android/server/pm/verify/domain/proxy/DomainVerificationProxy.java
+++ b/services/core/java/com/android/server/pm/verify/domain/proxy/DomainVerificationProxy.java
@@ -31,7 +31,6 @@ import com.android.server.pm.verify.domain.DomainVerificationMessageCodes;
import java.util.Objects;
import java.util.Set;
-// TODO(b/170321181): Combine the proxy versions for supporting v1 and v2 at once
public interface DomainVerificationProxy {
String TAG = "DomainVerificationProxy";
@@ -81,8 +80,7 @@ public interface DomainVerificationProxy {
return new DomainVerificationProxyUnavailable();
}
- default void sendBroadcastForPackages(@NonNull Set<String> packageNames) {
- }
+ void sendBroadcastForPackages(@NonNull Set<String> packageNames);
/**
* Runs a message on the caller's Handler as a result of {@link BaseConnection#schedule(int,
@@ -94,18 +92,12 @@ public interface DomainVerificationProxy {
* @param messageCode One of the values in {@link DomainVerificationMessageCodes}.
* @param object Arbitrary object that was originally included.
*/
- default boolean runMessage(int messageCode, Object object) {
- return false;
- }
+ boolean runMessage(int messageCode, Object object);
- default boolean isCallerVerifier(int callingUid) {
- return false;
- }
+ boolean isCallerVerifier(int callingUid);
@Nullable
- default ComponentName getComponentName() {
- return null;
- }
+ ComponentName getComponentName();
interface BaseConnection {
diff --git a/services/core/java/com/android/server/pm/verify/domain/proxy/DomainVerificationProxyCombined.java b/services/core/java/com/android/server/pm/verify/domain/proxy/DomainVerificationProxyCombined.java
index 8571c08699bd..5732d6b168fb 100644
--- a/services/core/java/com/android/server/pm/verify/domain/proxy/DomainVerificationProxyCombined.java
+++ b/services/core/java/com/android/server/pm/verify/domain/proxy/DomainVerificationProxyCombined.java
@@ -17,6 +17,7 @@
package com.android.server.pm.verify.domain.proxy;
import android.annotation.NonNull;
+import android.content.ComponentName;
import java.util.Set;
@@ -51,4 +52,10 @@ class DomainVerificationProxyCombined implements DomainVerificationProxy {
public boolean isCallerVerifier(int callingUid) {
return mProxyV2.isCallerVerifier(callingUid) || mProxyV1.isCallerVerifier(callingUid);
}
+
+ @NonNull
+ @Override
+ public ComponentName getComponentName() {
+ return mProxyV2.getComponentName();
+ }
}
diff --git a/services/core/java/com/android/server/pm/verify/domain/proxy/DomainVerificationProxyUnavailable.java b/services/core/java/com/android/server/pm/verify/domain/proxy/DomainVerificationProxyUnavailable.java
index bd77983256c5..363f9697633c 100644
--- a/services/core/java/com/android/server/pm/verify/domain/proxy/DomainVerificationProxyUnavailable.java
+++ b/services/core/java/com/android/server/pm/verify/domain/proxy/DomainVerificationProxyUnavailable.java
@@ -16,6 +16,32 @@
package com.android.server.pm.verify.domain.proxy;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.ComponentName;
+
+import java.util.Set;
+
/** Stub implementation for when the verification agent is unavailable */
public class DomainVerificationProxyUnavailable implements DomainVerificationProxy {
+
+ @Override
+ public void sendBroadcastForPackages(@NonNull Set<String> packageNames) {
+ }
+
+ @Override
+ public boolean runMessage(int messageCode, Object object) {
+ return false;
+ }
+
+ @Override
+ public boolean isCallerVerifier(int callingUid) {
+ return false;
+ }
+
+ @Nullable
+ @Override
+ public ComponentName getComponentName() {
+ return null;
+ }
}
diff --git a/services/core/java/com/android/server/pm/verify/domain/proxy/DomainVerificationProxyV1.java b/services/core/java/com/android/server/pm/verify/domain/proxy/DomainVerificationProxyV1.java
index fa36683e4aff..c8e46b62f6b0 100644
--- a/services/core/java/com/android/server/pm/verify/domain/proxy/DomainVerificationProxyV1.java
+++ b/services/core/java/com/android/server/pm/verify/domain/proxy/DomainVerificationProxyV1.java
@@ -298,6 +298,12 @@ public class DomainVerificationProxyV1 implements DomainVerificationProxy {
return builder.toString();
}
+ @NonNull
+ @Override
+ public ComponentName getComponentName() {
+ return mVerifierComponent;
+ }
+
private static class Response {
public final int callingUid;
public final int verificationId;
diff --git a/services/core/java/com/android/server/policy/PhoneWindowManager.java b/services/core/java/com/android/server/policy/PhoneWindowManager.java
index 29496b31b4a5..0ce9650c76b6 100644
--- a/services/core/java/com/android/server/policy/PhoneWindowManager.java
+++ b/services/core/java/com/android/server/policy/PhoneWindowManager.java
@@ -3021,7 +3021,7 @@ public class PhoneWindowManager implements WindowManagerPolicy {
private int handleStartTransitionForKeyguardLw(boolean keyguardGoingAway, long duration) {
final int res = applyKeyguardOcclusionChange();
if (res != 0) return res;
- if (!WindowManagerService.sEnableRemoteKeyguardAnimation && keyguardGoingAway) {
+ if (!WindowManagerService.sEnableRemoteKeyguardGoingAwayAnimation && keyguardGoingAway) {
if (DEBUG_KEYGUARD) Slog.d(TAG, "Starting keyguard exit animation");
startKeyguardExitAnimation(SystemClock.uptimeMillis(), duration);
}
@@ -5153,7 +5153,7 @@ public class PhoneWindowManager implements WindowManagerPolicy {
.compose();
}
// fallback for devices without composition support
- return VibrationEffect.get(VibrationEffect.EFFECT_DOUBLE_CLICK);
+ return VibrationEffect.get(VibrationEffect.EFFECT_HEAVY_CLICK);
default:
return null;
diff --git a/services/core/java/com/android/server/policy/keyguard/KeyguardServiceDelegate.java b/services/core/java/com/android/server/policy/keyguard/KeyguardServiceDelegate.java
index 44f14b4d5b0d..6e478ee7bf1b 100644
--- a/services/core/java/com/android/server/policy/keyguard/KeyguardServiceDelegate.java
+++ b/services/core/java/com/android/server/policy/keyguard/KeyguardServiceDelegate.java
@@ -263,7 +263,8 @@ public class KeyguardServiceDelegate {
*/
@Deprecated
public void setOccluded(boolean isOccluded, boolean animate) {
- if (!WindowManagerService.sEnableRemoteKeyguardAnimation && mKeyguardService != null) {
+ if (!WindowManagerService.sEnableRemoteKeyguardOccludeAnimation
+ && mKeyguardService != null) {
if (DEBUG) Log.v(TAG, "setOccluded(" + isOccluded + ") animate=" + animate);
mKeyguardService.setOccluded(isOccluded, animate);
}
@@ -403,7 +404,8 @@ public class KeyguardServiceDelegate {
}
public void startKeyguardExitAnimation(long startTime, long fadeoutDuration) {
- if (!WindowManagerService.sEnableRemoteKeyguardAnimation && mKeyguardService != null) {
+ if (!WindowManagerService.sEnableRemoteKeyguardGoingAwayAnimation
+ && mKeyguardService != null) {
mKeyguardService.startKeyguardExitAnimation(startTime, fadeoutDuration);
}
}
diff --git a/services/core/java/com/android/server/power/DisplayGroupPowerStateMapper.java b/services/core/java/com/android/server/power/DisplayGroupPowerStateMapper.java
index ea933246e3b2..52d92703c22f 100644
--- a/services/core/java/com/android/server/power/DisplayGroupPowerStateMapper.java
+++ b/services/core/java/com/android/server/power/DisplayGroupPowerStateMapper.java
@@ -141,6 +141,14 @@ public class DisplayGroupPowerStateMapper {
return mDisplayGroupInfos.get(groupId).lastPowerOnTime;
}
+ void setPoweringOnLocked(int groupId, boolean poweringOn) {
+ mDisplayGroupInfos.get(groupId).poweringOn = poweringOn;
+ }
+
+ boolean isPoweringOnLocked(int groupId) {
+ return mDisplayGroupInfos.get(groupId).poweringOn;
+ }
+
/**
* Returns the amalgamated wakefulness of all {@link DisplayGroup DisplayGroups}.
*
@@ -300,6 +308,7 @@ public class DisplayGroupPowerStateMapper {
public int wakefulness;
public boolean ready;
public long lastPowerOnTime;
+ boolean poweringOn;
public boolean sandmanSummoned;
public long lastUserActivityTime;
public long lastUserActivityTimeNoChangeLights;
diff --git a/services/core/java/com/android/server/power/PowerManagerService.java b/services/core/java/com/android/server/power/PowerManagerService.java
index c4aca6c18453..8991981e6e6d 100644
--- a/services/core/java/com/android/server/power/PowerManagerService.java
+++ b/services/core/java/com/android/server/power/PowerManagerService.java
@@ -1831,6 +1831,7 @@ public final class PowerManagerService extends SystemService
setWakefulnessLocked(groupId, WAKEFULNESS_AWAKE, eventTime, uid, reason, opUid,
opPackageName, details);
mDisplayGroupPowerStateMapper.setLastPowerOnTimeLocked(groupId, eventTime);
+ mDisplayGroupPowerStateMapper.setPoweringOnLocked(groupId, true);
} finally {
Trace.traceEnd(Trace.TRACE_TAG_POWER);
}
@@ -3201,9 +3202,12 @@ public final class PowerManagerService extends SystemService
final boolean displayReadyStateChanged =
mDisplayGroupPowerStateMapper.setDisplayGroupReadyLocked(groupId, ready);
- if (ready && displayReadyStateChanged
+ final boolean poweringOn =
+ mDisplayGroupPowerStateMapper.isPoweringOnLocked(groupId);
+ if (ready && displayReadyStateChanged && poweringOn
&& mDisplayGroupPowerStateMapper.getWakefulnessLocked(
groupId) == WAKEFULNESS_AWAKE) {
+ mDisplayGroupPowerStateMapper.setPoweringOnLocked(groupId, false);
Trace.asyncTraceEnd(Trace.TRACE_TAG_POWER, TRACE_SCREEN_ON, groupId);
final int latencyMs = (int) (mClock.uptimeMillis()
- mDisplayGroupPowerStateMapper.getLastPowerOnTimeLocked(groupId));
diff --git a/services/core/java/com/android/server/utils/OWNERS b/services/core/java/com/android/server/utils/OWNERS
new file mode 100644
index 000000000000..cc41f617ab8c
--- /dev/null
+++ b/services/core/java/com/android/server/utils/OWNERS
@@ -0,0 +1,10 @@
+per-file Snappable.java = file:/services/core/java/com/android/server/pm/OWNERS
+per-file Snappable.java = shombert@google.com
+per-file SnapShot* = file:/services/core/java/com/android/server/pm/OWNERS
+per-file SnapShot* = shombert@google.com
+per-file Watchable* = file:/services/core/java/com/android/server/pm/OWNERS
+per-file Watchable* = shombert@google.com
+per-file Watched* = file:/services/core/java/com/android/server/pm/OWNERS
+per-file Watched* = shombert@google.com
+per-file Watcher.java = file:/services/core/java/com/android/server/pm/OWNERS
+per-file Watcher.java = shombert@google.com
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index dfc677278847..bd1d4568db5b 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -421,18 +421,41 @@ public class WindowManagerService extends IWindowManager.Stub
SystemProperties.getBoolean(DISABLE_CUSTOM_TASK_ANIMATION_PROPERTY, true);
/**
+ * Use WMShell for app transition.
+ */
+ public static final String ENABLE_SHELL_TRANSITIONS = "persist.debug.shell_transit";
+
+ /**
+ * @see #ENABLE_SHELL_TRANSITIONS
+ */
+ public static final boolean sEnableShellTransitions =
+ SystemProperties.getBoolean(ENABLE_SHELL_TRANSITIONS, false);
+
+ /**
* Run Keyguard animation as remote animation in System UI instead of local animation in
* the server process.
+ *
+ * 0: Runs all keyguard animation as local animation
+ * 1: Only runs keyguard going away animation as remote animation
+ * 2: Runs all keyguard animation as remote animation
*/
private static final String ENABLE_REMOTE_KEYGUARD_ANIMATION_PROPERTY =
"persist.wm.enable_remote_keyguard_animation";
+ private static final int sEnableRemoteKeyguardAnimation =
+ SystemProperties.getInt(ENABLE_REMOTE_KEYGUARD_ANIMATION_PROPERTY, 1);
+
/**
* @see #ENABLE_REMOTE_KEYGUARD_ANIMATION_PROPERTY
*/
- public static boolean sEnableRemoteKeyguardAnimation =
- SystemProperties.getBoolean(ENABLE_REMOTE_KEYGUARD_ANIMATION_PROPERTY, false);
+ public static final boolean sEnableRemoteKeyguardGoingAwayAnimation = !sEnableShellTransitions
+ && sEnableRemoteKeyguardAnimation >= 1;
+ /**
+ * @see #ENABLE_REMOTE_KEYGUARD_ANIMATION_PROPERTY
+ */
+ public static final boolean sEnableRemoteKeyguardOccludeAnimation = !sEnableShellTransitions
+ && sEnableRemoteKeyguardAnimation >= 2;
/**
* Allows a fullscreen windowing mode activity to launch in its desired orientation directly
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
index 24f73c3ac54c..074eeb942377 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
@@ -63,7 +63,7 @@ import static android.app.admin.DevicePolicyManager.LEAVE_ALL_SYSTEM_APPS_ENABLE
import static android.app.admin.DevicePolicyManager.LOCK_TASK_FEATURE_HOME;
import static android.app.admin.DevicePolicyManager.LOCK_TASK_FEATURE_NOTIFICATIONS;
import static android.app.admin.DevicePolicyManager.LOCK_TASK_FEATURE_OVERVIEW;
-import static android.app.admin.DevicePolicyManager.NEARBY_STREAMING_DISABLED;
+import static android.app.admin.DevicePolicyManager.NEARBY_STREAMING_NOT_CONTROLLED_BY_POLICY;
import static android.app.admin.DevicePolicyManager.NON_ORG_OWNED_PROFILE_KEYGUARD_FEATURES_AFFECT_OWNER;
import static android.app.admin.DevicePolicyManager.OPERATION_SAFETY_REASON_NONE;
import static android.app.admin.DevicePolicyManager.PASSWORD_COMPLEXITY_HIGH;
@@ -7548,21 +7548,25 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager {
}
@Override
- public int getNearbyNotificationStreamingPolicy() {
+ public int getNearbyNotificationStreamingPolicy(final int userId) {
if (!mHasFeature) {
- return NEARBY_STREAMING_DISABLED;
+ return NEARBY_STREAMING_NOT_CONTROLLED_BY_POLICY;
}
final CallerIdentity caller = getCallerIdentity();
Preconditions.checkCallAuthorization(
- isDeviceOwner(caller)
- || isProfileOwner(caller)
- || hasCallingOrSelfPermission(permission.READ_NEARBY_STREAMING_POLICY));
+ isProfileOwner(caller)
+ || isDeviceOwner(caller)
+ || hasCallingOrSelfPermission(permission.READ_NEARBY_STREAMING_POLICY));
synchronized (getLockObject()) {
- final ActiveAdmin admin = getProfileOwnerOrDeviceOwnerLocked(caller);
- return admin.mNearbyNotificationStreamingPolicy;
+ if (mOwners.hasProfileOwner(userId) || mOwners.hasDeviceOwner()) {
+ final ActiveAdmin admin = getDeviceOrProfileOwnerAdminLocked(userId);
+ return admin.mNearbyNotificationStreamingPolicy;
+ }
}
+
+ return NEARBY_STREAMING_NOT_CONTROLLED_BY_POLICY;
}
@Override
@@ -7584,21 +7588,25 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager {
}
@Override
- public int getNearbyAppStreamingPolicy() {
+ public int getNearbyAppStreamingPolicy(final int userId) {
if (!mHasFeature) {
- return NEARBY_STREAMING_DISABLED;
+ return NEARBY_STREAMING_NOT_CONTROLLED_BY_POLICY;
}
final CallerIdentity caller = getCallerIdentity();
Preconditions.checkCallAuthorization(
- isDeviceOwner(caller)
- || isProfileOwner(caller)
- || hasCallingOrSelfPermission(permission.READ_NEARBY_STREAMING_POLICY));
+ isProfileOwner(caller)
+ || isDeviceOwner(caller)
+ || hasCallingOrSelfPermission(permission.READ_NEARBY_STREAMING_POLICY));
synchronized (getLockObject()) {
- final ActiveAdmin admin = getProfileOwnerOrDeviceOwnerLocked(caller);
- return admin.mNearbyAppStreamingPolicy;
+ if (mOwners.hasProfileOwner(userId) || mOwners.hasDeviceOwner()) {
+ final ActiveAdmin admin = getDeviceOrProfileOwnerAdminLocked(userId);
+ return admin.mNearbyAppStreamingPolicy;
+ }
}
+
+ return NEARBY_STREAMING_NOT_CONTROLLED_BY_POLICY;
}
/**
diff --git a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationPackageTest.kt b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationPackageTest.kt
index 8540b8afdd64..83c126842213 100644
--- a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationPackageTest.kt
+++ b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationPackageTest.kt
@@ -48,6 +48,8 @@ import org.mockito.ArgumentMatchers.any
import org.mockito.ArgumentMatchers.anyInt
import org.mockito.ArgumentMatchers.anyLong
import org.mockito.ArgumentMatchers.anyString
+import java.io.ByteArrayInputStream
+import java.io.ByteArrayOutputStream
import java.util.UUID
class DomainVerificationPackageTest {
@@ -513,12 +515,141 @@ class DomainVerificationPackageTest {
assertThat(service.queryValidVerificationPackageNames()).containsExactly(pkgName)
}
+ @Test
+ fun backupAndRestore() {
+ // This test acts as a proxy for true user restore through PackageManager,
+ // as that's much harder to test for real.
+
+ val pkg1 = mockPkgSetting(PKG_ONE, UUID_ONE, SIGNATURE_ONE, listOf(DOMAIN_1, DOMAIN_2))
+ val pkg2 = mockPkgSetting(PKG_TWO, UUID_TWO, SIGNATURE_TWO,
+ listOf(DOMAIN_1, DOMAIN_2, DOMAIN_3))
+ val serviceBefore = makeService(pkg1, pkg2)
+ serviceBefore.addPackage(pkg1)
+ serviceBefore.addPackage(pkg2)
+
+ serviceBefore.setStatus(pkg1.domainSetId, setOf(DOMAIN_1), STATE_SUCCESS)
+ serviceBefore.setDomainVerificationLinkHandlingAllowed(pkg1.getName(), false, 1)
+ serviceBefore.setUserSelection(pkg2.domainSetId, setOf(DOMAIN_2), true, 0)
+ serviceBefore.setUserSelection(pkg2.domainSetId, setOf(DOMAIN_3), true, 1)
+
+ fun assertExpectedState(service: DomainVerificationService) {
+ service.assertState(
+ pkg1, userId = 0, hostToStateMap = mapOf(
+ DOMAIN_1 to DOMAIN_STATE_VERIFIED,
+ DOMAIN_2 to DOMAIN_STATE_NONE,
+ )
+ )
+
+ service.assertState(
+ pkg1, userId = 1, linkHandingAllowed = false, hostToStateMap = mapOf(
+ DOMAIN_1 to DOMAIN_STATE_VERIFIED,
+ DOMAIN_2 to DOMAIN_STATE_NONE,
+ )
+ )
+
+ service.assertState(
+ pkg2, userId = 0, hostToStateMap = mapOf(
+ DOMAIN_1 to DOMAIN_STATE_NONE,
+ DOMAIN_2 to DOMAIN_STATE_SELECTED,
+ DOMAIN_3 to DOMAIN_STATE_NONE
+ )
+ )
+
+ service.assertState(
+ pkg2, userId = 1, hostToStateMap = mapOf(
+ DOMAIN_1 to DOMAIN_STATE_NONE,
+ DOMAIN_2 to DOMAIN_STATE_NONE,
+ DOMAIN_3 to DOMAIN_STATE_SELECTED,
+ )
+ )
+ }
+
+ assertExpectedState(serviceBefore)
+
+ val backupUser0 = ByteArrayOutputStream().use {
+ serviceBefore.writeSettings(Xml.resolveSerializer(it), true, 0)
+ it.toByteArray()
+ }
+
+ val backupUser1 = ByteArrayOutputStream().use {
+ serviceBefore.writeSettings(Xml.resolveSerializer(it), true, 1)
+ it.toByteArray()
+ }
+
+ val serviceAfter = makeService(pkg1, pkg2)
+ serviceAfter.addPackage(pkg1)
+ serviceAfter.addPackage(pkg2)
+
+ // Check the state is default before the restoration applies
+ listOf(0, 1).forEach {
+ serviceAfter.assertState(
+ pkg1, userId = it, hostToStateMap = mapOf(
+ DOMAIN_1 to DOMAIN_STATE_NONE,
+ DOMAIN_2 to DOMAIN_STATE_NONE,
+ )
+ )
+ }
+
+ listOf(0, 1).forEach {
+ serviceAfter.assertState(
+ pkg2, userId = it, hostToStateMap = mapOf(
+ DOMAIN_1 to DOMAIN_STATE_NONE,
+ DOMAIN_2 to DOMAIN_STATE_NONE,
+ DOMAIN_3 to DOMAIN_STATE_NONE,
+ )
+ )
+ }
+
+ ByteArrayInputStream(backupUser1).use {
+ serviceAfter.restoreSettings(Xml.resolvePullParser(it))
+ }
+
+ // Assert user 1 was restored
+ serviceAfter.assertState(
+ pkg1, userId = 1, linkHandingAllowed = false, hostToStateMap = mapOf(
+ DOMAIN_1 to DOMAIN_STATE_VERIFIED,
+ DOMAIN_2 to DOMAIN_STATE_NONE,
+ )
+ )
+
+ serviceAfter.assertState(
+ pkg2, userId = 1, hostToStateMap = mapOf(
+ DOMAIN_1 to DOMAIN_STATE_NONE,
+ DOMAIN_2 to DOMAIN_STATE_NONE,
+ DOMAIN_3 to DOMAIN_STATE_SELECTED,
+ )
+ )
+
+ // User 0 has domain verified (since that's not user-specific)
+ serviceAfter.assertState(
+ pkg1, userId = 0, hostToStateMap = mapOf(
+ DOMAIN_1 to DOMAIN_STATE_VERIFIED,
+ DOMAIN_2 to DOMAIN_STATE_NONE,
+ )
+ )
+
+ // But user 0 is missing any user selected state
+ serviceAfter.assertState(
+ pkg2, userId = 0, hostToStateMap = mapOf(
+ DOMAIN_1 to DOMAIN_STATE_NONE,
+ DOMAIN_2 to DOMAIN_STATE_NONE,
+ DOMAIN_3 to DOMAIN_STATE_NONE,
+ )
+ )
+
+ ByteArrayInputStream(backupUser0).use {
+ serviceAfter.restoreSettings(Xml.resolvePullParser(it))
+ }
+
+ assertExpectedState(serviceAfter)
+ }
+
private fun DomainVerificationService.getInfo(pkgName: String) =
getDomainVerificationInfo(pkgName)
.also { assertThat(it).isNotNull() }!!
- private fun DomainVerificationService.getUserState(pkgName: String) =
- getDomainVerificationUserState(pkgName, USER_ID)
+ private fun DomainVerificationService.getUserState(pkgName: String, userId: Int = USER_ID) =
+ getDomainVerificationUserState(pkgName, userId)
.also { assertThat(it).isNotNull() }!!
private fun makeService(
@@ -600,8 +731,24 @@ class DomainVerificationPackageTest {
whenever(this.domainSetId) { domainSetId }
whenever(getInstantApp(anyInt())) { false }
whenever(firstInstallTime) { 0L }
- whenever(readUserState(USER_ID)) { PackageUserState() }
+ whenever(readUserState(0)) { PackageUserState() }
+ whenever(readUserState(1)) { PackageUserState() }
whenever(signatures) { arrayOf(Signature(signature)) }
whenever(isSystem) { isSystemApp }
}
+
+ private fun DomainVerificationService.assertState(
+ pkg: PackageSetting,
+ userId: Int,
+ linkHandingAllowed: Boolean = true,
+ hostToStateMap: Map<String, Int>
+ ) {
+ getUserState(pkg.getName(), userId).apply {
+ assertThat(this.packageName).isEqualTo(pkg.getName())
+ assertThat(this.identifier).isEqualTo(pkg.domainSetId)
+ assertThat(this.isLinkHandlingAllowed).isEqualTo(linkHandingAllowed)
+ assertThat(this.user.identifier).isEqualTo(userId)
+ assertThat(this.hostToStateMap).containsExactlyEntriesIn(hostToStateMap)
+ }
+ }
}
diff --git a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationPersistenceTest.kt b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationPersistenceTest.kt
index 7ffbbf6af159..ad652dff901c 100644
--- a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationPersistenceTest.kt
+++ b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationPersistenceTest.kt
@@ -17,6 +17,7 @@
package com.android.server.pm.test.verify.domain
import android.content.pm.verify.domain.DomainVerificationState
+import android.os.UserHandle
import android.util.ArrayMap
import android.util.SparseArray
import android.util.TypedXmlPullParser
@@ -110,7 +111,8 @@ class DomainVerificationPersistenceTest {
fun writeAndReadBackNormal() {
val (attached, pending, restored) = mockWriteValues()
val file = tempFolder.newFile().writeXml {
- DomainVerificationPersistence.writeToXml(it, attached, pending, restored, null)
+ DomainVerificationPersistence.writeToXml(it, attached, pending, restored,
+ UserHandle.USER_ALL, null)
}
val xml = file.readText()
@@ -128,7 +130,8 @@ class DomainVerificationPersistenceTest {
fun writeAndReadBackWithSignature() {
val (attached, pending, restored) = mockWriteValues()
val file = tempFolder.newFile().writeXml {
- DomainVerificationPersistence.writeToXml(it, attached, pending, restored) {
+ DomainVerificationPersistence.writeToXml(it, attached, pending, restored,
+ UserHandle.USER_ALL) {
"SIGNATURE_$it"
}
}
@@ -156,7 +159,8 @@ class DomainVerificationPersistenceTest {
fun writeStateSignatureIfFunctionReturnsNull() {
val (attached, pending, restored) = mockWriteValues { "SIGNATURE_$it" }
val file = tempFolder.newFile().writeXml {
- DomainVerificationPersistence.writeToXml(it, attached, pending, restored) { null }
+ DomainVerificationPersistence.writeToXml(it, attached, pending, restored,
+ UserHandle.USER_ALL) { null }
}
val (readActive, readRestored) = file.readXml {
@@ -254,7 +258,7 @@ class DomainVerificationPersistenceTest {
>
<state/>
<user-states>
- <user-state userId="1" allowLinkHandling="false">
+ <user-state userId="1">
<enabled-hosts>
<host name="example-user1.com"/>
<host name="example-user1.org"/>
diff --git a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationProxyTest.kt b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationProxyTest.kt
index a9b77ea6d95b..0a54094b4b85 100644
--- a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationProxyTest.kt
+++ b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationProxyTest.kt
@@ -466,6 +466,44 @@ class DomainVerificationProxyTest {
}
}
+ @Test
+ fun nonNullComponentName() {
+ val connection = mockConnection()
+ DomainVerificationProxy.makeProxy<Connection>(
+ componentTwo,
+ null,
+ context,
+ manager,
+ collector,
+ connection
+ ).run {
+ assertThat(componentName).isEqualTo(componentTwo)
+ }
+
+ DomainVerificationProxy.makeProxy<Connection>(
+ null,
+ componentThree,
+ context,
+ manager,
+ collector,
+ connection
+ ).run {
+ assertThat(componentName).isEqualTo(componentThree)
+ }
+
+ DomainVerificationProxy.makeProxy<Connection>(
+ componentTwo,
+ componentThree,
+ context,
+ manager,
+ collector,
+ connection
+ ).run {
+ // Higher version takes precedence
+ assertThat(componentName).isEqualTo(componentThree)
+ }
+ }
+
private fun mockConnection(block: Connection.() -> Unit = {}) =
mockThrowOnUnmocked<Connection> {
whenever(isCallerPackage(TEST_CALLING_UID_ACCEPT, TEST_PKG_NAME_ONE)) { true }
diff --git a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationTestUtils.kt b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationTestUtils.kt
index e1da727fb64f..00986f741eea 100644
--- a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationTestUtils.kt
+++ b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationTestUtils.kt
@@ -42,6 +42,11 @@ internal object DomainVerificationTestUtils {
Function<String, PackageSetting?>, *>)
.accept { block(it) }
}
+ whenever(withPackageSettingsThrowing2<Exception, Exception>(any())) {
+ (arguments[0] as DomainVerificationManagerInternal.Connection.Throwing2Consumer<
+ Function<String, PackageSetting?>, *, *>)
+ .accept { block(it) }
+ }
whenever(withPackageSettingsReturningThrowing<Any, Exception>(any())) {
(arguments[0] as DomainVerificationManagerInternal.Connection.ThrowingFunction<
Function<String, PackageSetting?>, *, *>)
diff --git a/services/tests/mockingservicestests/src/com/android/server/display/LocalDisplayAdapterTest.java b/services/tests/mockingservicestests/src/com/android/server/display/LocalDisplayAdapterTest.java
index f1d8e6c167d7..e1012a9a4cad 100644
--- a/services/tests/mockingservicestests/src/com/android/server/display/LocalDisplayAdapterTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/display/LocalDisplayAdapterTest.java
@@ -649,14 +649,14 @@ public class LocalDisplayAdapterTest {
// Test as default display
BacklightAdapter ba = new BacklightAdapter(displayToken, true /*isDefault*/,
mSurfaceControlProxy);
- ba.setBacklight(0.514f);
- verify(mSurfaceControlProxy).setDisplayBrightness(displayToken, 0.514f);
+ ba.setBacklight(0.514f, 100f, 0.614f, 500f);
+ verify(mSurfaceControlProxy).setDisplayBrightness(displayToken, 0.514f, 100f, 0.614f, 500f);
// Test as not default display
BacklightAdapter ba2 = new BacklightAdapter(displayToken, false /*isDefault*/,
mSurfaceControlProxy);
- ba2.setBacklight(0.323f);
- verify(mSurfaceControlProxy).setDisplayBrightness(displayToken, 0.323f);
+ ba2.setBacklight(0.323f, 101f, 0.723f, 601f);
+ verify(mSurfaceControlProxy).setDisplayBrightness(displayToken, 0.323f, 101f, 0.723f, 601f);
}
@Test
@@ -668,7 +668,7 @@ public class LocalDisplayAdapterTest {
BacklightAdapter ba = new BacklightAdapter(displayToken, true /*isDefault*/,
mSurfaceControlProxy);
- ba.setBacklight(0.123f);
+ ba.setBacklight(1f, 1f, 0.123f, 1f);
verify(mMockedBacklight).setBrightness(0.123f);
}
@@ -681,7 +681,7 @@ public class LocalDisplayAdapterTest {
BacklightAdapter ba = new BacklightAdapter(displayToken, false /*isDefault*/,
mSurfaceControlProxy);
- ba.setBacklight(0.456f);
+ ba.setBacklight(0.456f, 1f, 1f, 1f);
// Adapter does not forward any brightness in this case.
verify(mMockedBacklight, never()).setBrightness(anyFloat());
diff --git a/services/tests/servicestests/src/com/android/server/display/HighBrightnessModeControllerTest.java b/services/tests/servicestests/src/com/android/server/display/HighBrightnessModeControllerTest.java
index 88a21b4a8fa8..8e4cdc91d0e6 100644
--- a/services/tests/servicestests/src/com/android/server/display/HighBrightnessModeControllerTest.java
+++ b/services/tests/servicestests/src/com/android/server/display/HighBrightnessModeControllerTest.java
@@ -21,6 +21,7 @@ import static android.hardware.display.BrightnessInfo.HIGH_BRIGHTNESS_MODE_SUNLI
import static org.junit.Assert.assertEquals;
+import android.os.Binder;
import android.os.Handler;
import android.os.Message;
import android.os.test.TestLooper;
@@ -55,6 +56,7 @@ public class HighBrightnessModeControllerTest {
private OffsettableClock mClock;
private TestLooper mTestLooper;
private Handler mHandler;
+ private Binder mDisplayToken;
private static final HighBrightnessModeData DEFAULT_HBM_DATA =
new HighBrightnessModeData(MINIMUM_LUX, TRANSITION_POINT, TIME_WINDOW_MILLIS,
@@ -64,6 +66,7 @@ public class HighBrightnessModeControllerTest {
public void setUp() {
mClock = new OffsettableClock.Stopped();
mTestLooper = new TestLooper(mClock::now);
+ mDisplayToken = null;
mHandler = new Handler(mTestLooper.getLooper(), new Handler.Callback() {
@Override
public boolean handleMessage(Message msg) {
@@ -79,14 +82,14 @@ public class HighBrightnessModeControllerTest {
@Test
public void testNoHbmData() {
final HighBrightnessModeController hbmc = new HighBrightnessModeController(
- mClock::now, mHandler, DEFAULT_MIN, DEFAULT_MAX, null, () -> {});
+ mClock::now, mHandler, mDisplayToken, DEFAULT_MIN, DEFAULT_MAX, null, () -> {});
assertState(hbmc, DEFAULT_MIN, DEFAULT_MAX, HIGH_BRIGHTNESS_MODE_OFF);
}
@Test
public void testNoHbmData_Enabled() {
final HighBrightnessModeController hbmc = new HighBrightnessModeController(
- mClock::now, mHandler, DEFAULT_MIN, DEFAULT_MAX, null, () -> {});
+ mClock::now, mHandler, mDisplayToken, DEFAULT_MIN, DEFAULT_MAX, null, () -> {});
hbmc.setAutoBrightnessEnabled(true);
hbmc.onAmbientLuxChange(MINIMUM_LUX - 1); // below allowed range
assertState(hbmc, DEFAULT_MIN, DEFAULT_MAX, HIGH_BRIGHTNESS_MODE_OFF);
@@ -264,8 +267,8 @@ public class HighBrightnessModeControllerTest {
// Creates instance with standard initialization values.
private HighBrightnessModeController createDefaultHbm() {
- return new HighBrightnessModeController(mClock::now, mHandler, DEFAULT_MIN, DEFAULT_MAX,
- DEFAULT_HBM_DATA, () -> {});
+ return new HighBrightnessModeController(mClock::now, mHandler, mDisplayToken, DEFAULT_MIN,
+ DEFAULT_MAX, DEFAULT_HBM_DATA, () -> {});
}
private void advanceTime(long timeMs) {
diff --git a/services/tests/servicestests/src/com/android/server/graphics/fonts/UpdatableFontDirTest.java b/services/tests/servicestests/src/com/android/server/graphics/fonts/UpdatableFontDirTest.java
index 765c13a98ac0..7b4803724180 100644
--- a/services/tests/servicestests/src/com/android/server/graphics/fonts/UpdatableFontDirTest.java
+++ b/services/tests/servicestests/src/com/android/server/graphics/fonts/UpdatableFontDirTest.java
@@ -857,14 +857,15 @@ public final class UpdatableFontDirTest {
mConfigFile, mCurrentTimeSupplier, mConfigSupplier);
dir.loadFontFileMap();
+ List<FontUpdateRequest> requests = Arrays.asList(
+ newFontUpdateRequest("test.ttf,1,test", GOOD_SIGNATURE),
+ newAddFontFamilyRequest("<family lang='en'>"
+ + " <font>test.ttf</font>"
+ + "</family>"));
try {
- dir.update(Arrays.asList(
- newFontUpdateRequest("test.ttf,1,test", GOOD_SIGNATURE),
- newAddFontFamilyRequest("<family lang='en'>"
- + " <font>test.ttf</font>"
- + "</family>")));
+ dir.update(requests);
fail("Expect NullPointerException");
- } catch (FontManagerService.SystemFontException e) {
+ } catch (NullPointerException e) {
// Expect
}
}
diff --git a/services/tests/uiservicestests/src/com/android/server/UiModeManagerServiceTest.java b/services/tests/uiservicestests/src/com/android/server/UiModeManagerServiceTest.java
index 4df469e79814..81c237b8ccac 100644
--- a/services/tests/uiservicestests/src/com/android/server/UiModeManagerServiceTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/UiModeManagerServiceTest.java
@@ -555,12 +555,13 @@ public class UiModeManagerServiceTest extends UiServiceTestCase {
public void requestProjection() throws Exception {
when(mPackageManager.getPackageUid(PACKAGE_NAME, 0)).thenReturn(TestInjector.CALLING_UID);
// Should work for all powers of two.
- for (int p = 1; p < PROJECTION_TYPE_ALL; p = p * 2) {
- assertTrue(mService.requestProjection(mBinder, p, PACKAGE_NAME));
- assertTrue((mService.getActiveProjectionTypes() & p) != 0);
- assertThat(mService.getProjectingPackages(p), contains(PACKAGE_NAME));
+ for (int i = 0; i < Integer.SIZE; ++i) {
+ int projectionType = 1 << i;
+ assertTrue(mService.requestProjection(mBinder, projectionType, PACKAGE_NAME));
+ assertTrue((mService.getActiveProjectionTypes() & projectionType) != 0);
+ assertThat(mService.getProjectingPackages(projectionType), contains(PACKAGE_NAME));
// Subsequent calls should still succeed.
- assertTrue(mService.requestProjection(mBinder, p, PACKAGE_NAME));
+ assertTrue(mService.requestProjection(mBinder, projectionType, PACKAGE_NAME));
}
assertEquals(PROJECTION_TYPE_ALL, mService.getActiveProjectionTypes());
}
@@ -613,19 +614,17 @@ public class UiModeManagerServiceTest extends UiServiceTestCase {
@Test
public void releaseProjection() throws Exception {
when(mPackageManager.getPackageUid(PACKAGE_NAME, 0)).thenReturn(TestInjector.CALLING_UID);
- // Should work for all powers of two.
- for (int p = 1; p < PROJECTION_TYPE_ALL; p = p * 2) {
- mService.requestProjection(mBinder, p, PACKAGE_NAME);
- }
+ requestAllPossibleProjectionTypes();
assertEquals(PROJECTION_TYPE_ALL, mService.getActiveProjectionTypes());
assertTrue(mService.releaseProjection(PROJECTION_TYPE_AUTOMOTIVE, PACKAGE_NAME));
int everythingButAutomotive = PROJECTION_TYPE_ALL & ~PROJECTION_TYPE_AUTOMOTIVE;
assertEquals(everythingButAutomotive, mService.getActiveProjectionTypes());
- for (int p = 1; p < PROJECTION_TYPE_ALL; p = p * 2) {
- assertEquals(p != PROJECTION_TYPE_AUTOMOTIVE,
- (boolean) mService.releaseProjection(p, PACKAGE_NAME));
+ for (int i = 0; i < Integer.SIZE; ++i) {
+ int projectionType = 1 << i;
+ assertEquals(projectionType != PROJECTION_TYPE_AUTOMOTIVE,
+ (boolean) mService.releaseProjection(projectionType, PACKAGE_NAME));
}
assertEquals(PROJECTION_TYPE_NONE, mService.getActiveProjectionTypes());
@@ -634,9 +633,7 @@ public class UiModeManagerServiceTest extends UiServiceTestCase {
@Test
public void binderDeath_releasesProjection() throws Exception {
when(mPackageManager.getPackageUid(PACKAGE_NAME, 0)).thenReturn(TestInjector.CALLING_UID);
- for (int p = 1; p < PROJECTION_TYPE_ALL; p = p * 2) {
- mService.requestProjection(mBinder, p, PACKAGE_NAME);
- }
+ requestAllPossibleProjectionTypes();
assertEquals(PROJECTION_TYPE_ALL, mService.getActiveProjectionTypes());
ArgumentCaptor<IBinder.DeathRecipient> deathRecipientCaptor = ArgumentCaptor.forClass(
IBinder.DeathRecipient.class);
@@ -814,6 +811,12 @@ public class UiModeManagerServiceTest extends UiServiceTestCase {
verify(listener, never()).onProjectionStateChanged(anyInt(), any());
}
+ private void requestAllPossibleProjectionTypes() throws RemoteException {
+ for (int i = 0; i < Integer.SIZE; ++i) {
+ mService.requestProjection(mBinder, 1 << i, PACKAGE_NAME);
+ }
+ }
+
private static class TestInjector extends UiModeManagerService.Injector {
private static final int CALLING_UID = 8675309;
diff --git a/services/usb/java/com/android/server/usb/UsbService.java b/services/usb/java/com/android/server/usb/UsbService.java
index ac6b8fe0b970..3d3538d7ae49 100644
--- a/services/usb/java/com/android/server/usb/UsbService.java
+++ b/services/usb/java/com/android/server/usb/UsbService.java
@@ -200,7 +200,7 @@ public class UsbService extends IUsbManager.Stub {
final IntentFilter filter = new IntentFilter();
filter.setPriority(IntentFilter.SYSTEM_HIGH_PRIORITY);
filter.addAction(DevicePolicyManager.ACTION_DEVICE_POLICY_MANAGER_STATE_CHANGED);
- mContext.registerReceiver(receiver, filter, null, null);
+ mContext.registerReceiverAsUser(receiver, UserHandle.ALL, filter, null, null);
}
/**
diff --git a/telecomm/java/android/telecom/DiagnosticCall.java b/telecomm/java/android/telecom/DiagnosticCall.java
deleted file mode 100644
index a6b7258052a4..000000000000
--- a/telecomm/java/android/telecom/DiagnosticCall.java
+++ /dev/null
@@ -1,27 +0,0 @@
-/*
- * Copyright (C) 2021 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.telecom;
-
-import android.annotation.SystemApi;
-
-/**
- * @deprecated use {@link CallDiagnostics} instead.
- * @hide
- */
-@SystemApi
-public abstract class DiagnosticCall extends CallDiagnostics {
-}
diff --git a/telephony/java/android/telephony/CellIdentityNr.java b/telephony/java/android/telephony/CellIdentityNr.java
index cdd54cd25311..4f5052151af5 100644
--- a/telephony/java/android/telephony/CellIdentityNr.java
+++ b/telephony/java/android/telephony/CellIdentityNr.java
@@ -233,7 +233,7 @@ public final class CellIdentityNr extends CellIdentity {
}
/**
- * @return Mobile Network Code in string fomrat, or {@code null} if unknown.
+ * @return Mobile Network Code in string format, or {@code null} if unknown.
*/
@Nullable
public String getMncString() {