summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--apct-tests/perftests/multiuser/src/android/multiuser/UserLifecycleTests.java265
-rw-r--r--core/java/android/permission/flags.aconfig8
-rw-r--r--core/java/android/service/dreams/DreamService.java27
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipAnimationController.java9
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashScreenExitAnimationUtils.java3
-rw-r--r--packages/PackageInstaller/src/com/android/packageinstaller/v2/model/InstallRepository.kt138
-rw-r--r--packages/PackageInstaller/src/com/android/packageinstaller/v2/model/InstallStages.kt5
-rw-r--r--packages/PackageInstaller/src/com/android/packageinstaller/v2/model/PackageUtil.kt49
-rw-r--r--packages/PackageInstaller/src/com/android/packageinstaller/v2/ui/fragments/ExternalSourcesBlockedFragment.java2
-rw-r--r--packages/PackageInstaller/src/com/android/packageinstaller/v2/ui/fragments/InstallConfirmationFragment.java9
-rw-r--r--packages/SettingsProvider/res/values/defaults.xml2
-rw-r--r--packages/SystemUI/aconfig/systemui.aconfig10
-rw-r--r--packages/SystemUI/compose/features/src/com/android/systemui/dialog/ui/composable/AlertDialogContent.kt2
-rw-r--r--packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/Notifications.kt136
-rw-r--r--packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/GoneScene.kt30
-rw-r--r--packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/volume/ui/composable/VolumeSlider.kt3
-rw-r--r--packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/ui/composable/VolumePanelRoot.kt6
-rw-r--r--packages/SystemUI/res/layout/alert_dialog_systemui.xml1
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/data/repository/HeadsUpRepository.kt3
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/domain/interactor/HeadsUpNotificationInteractor.kt7
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java4
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/shared/TransparentHeaderFix.kt53
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java7
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java33
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationsPlaceholderViewModel.kt11
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpNotificationViewControllerEmptyImpl.kt34
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpTouchHelper.java12
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutControllerTest.java7
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/data/repository/HeadsUpNotificationRepositoryKosmos.kt5
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationsPlaceholderViewModelKosmos.kt2
-rw-r--r--services/core/java/com/android/server/appop/DiscreteRegistry.java8
-rw-r--r--services/core/java/com/android/server/inputmethod/InputMethodManagerService.java30
-rw-r--r--services/core/java/com/android/server/inputmethod/InputMethodMenuController.java2
-rw-r--r--services/core/java/com/android/server/inputmethod/InputMethodSettings.java51
-rw-r--r--services/tests/dreamservicetests/AndroidManifest.xml29
-rw-r--r--services/tests/dreamservicetests/res/xml/test_dream_metadata_nonexistent_settings.xml20
-rw-r--r--services/tests/dreamservicetests/res/xml/test_dream_metadata_nopackage_nonexistent_settings.xml20
-rw-r--r--services/tests/dreamservicetests/src/com/android/server/dreams/DreamServiceTest.java22
-rw-r--r--services/tests/dreamservicetests/src/com/android/server/dreams/TestDreamSettingsActivity.java20
-rw-r--r--tests/inputmethod/ConcurrentMultiSessionImeTest/src/com/android/server/inputmethod/multisessiontest/ConcurrentMultiUserTest.java1
40 files changed, 870 insertions, 216 deletions
diff --git a/apct-tests/perftests/multiuser/src/android/multiuser/UserLifecycleTests.java b/apct-tests/perftests/multiuser/src/android/multiuser/UserLifecycleTests.java
index 762e2af09cd3..0750c8dfea74 100644
--- a/apct-tests/perftests/multiuser/src/android/multiuser/UserLifecycleTests.java
+++ b/apct-tests/perftests/multiuser/src/android/multiuser/UserLifecycleTests.java
@@ -145,7 +145,9 @@ public class UserLifecycleTests {
Intent.ACTION_USER_STARTED,
Intent.ACTION_MEDIA_MOUNTED,
Intent.ACTION_USER_UNLOCKED,
- Intent.ACTION_USER_STOPPED);
+ Intent.ACTION_USER_STOPPED,
+ Intent.ACTION_PROFILE_AVAILABLE,
+ Intent.ACTION_PROFILE_UNAVAILABLE);
mUserSwitchWaiter = new UserSwitchWaiter(TAG, TIMEOUT_IN_SECOND);
removeAnyPreviousTestUsers();
if (mAm.getCurrentUser() != UserHandle.USER_SYSTEM) {
@@ -1230,6 +1232,255 @@ public class UserLifecycleTests {
}
}
+ /** Tests creating a private profile. */
+ @Test(timeout = TIMEOUT_MAX_TEST_TIME_MS)
+ public void createPrivateProfile() throws RemoteException {
+ while (mRunner.keepRunning()) {
+ Log.i(TAG, "Starting timer");
+
+ final int userId = createPrivateProfileUser();
+
+ mRunner.pauseTiming();
+ Log.i(TAG, "Stopping timer");
+ removeUser(userId);
+ waitCoolDownPeriod();
+ mRunner.resumeTimingForNextIteration();
+ }
+ }
+
+ /**
+ * Tests creating and starting a newly created private profile. This test waits for the
+ * {@link Intent.ACTION_USER_STARTED} to be received.
+ */
+ @Test(timeout = TIMEOUT_MAX_TEST_TIME_MS)
+ public void createAndStartPrivateProfile_realistic() throws RemoteException {
+ while (mRunner.keepRunning()) {
+ Log.i(TAG, "Starting timer");
+ final int userId = createPrivateProfileUser();
+
+ // Don't use this.startUserInBackgroundAndWaitForUnlock() since only waiting until
+ // ACTION_USER_STARTED.
+ runThenWaitForBroadcasts(userId, () -> {
+ mIam.startUserInBackground(userId);
+ }, Intent.ACTION_USER_STARTED);
+
+ mRunner.pauseTiming();
+ Log.i(TAG, "Stopping timer");
+ removeUser(userId);
+ waitCoolDownPeriod();
+ mRunner.resumeTimingForNextIteration();
+ }
+ }
+
+ /** Tests for stopping the private profile. */
+ @Test(timeout = TIMEOUT_MAX_TEST_TIME_MS)
+ public void privateProfileStopped() throws RemoteException {
+ while (mRunner.keepRunning()) {
+ mRunner.pauseTiming();
+ final int userId = createPrivateProfileUser();
+ runThenWaitForBroadcasts(userId, () -> {
+ startUserInBackgroundAndWaitForUnlock(userId);
+ }, Intent.ACTION_MEDIA_MOUNTED);
+
+ mRunner.resumeTiming();
+ Log.i(TAG, "Starting timer");
+
+ stopUser(userId);
+
+ mRunner.pauseTiming();
+ Log.i(TAG, "Stopping timer");
+ removeUser(userId);
+ mRunner.resumeTimingForNextIteration();
+ }
+ }
+
+ /**
+ * Tests unlocking a newly-created private profile using the
+ * {@link UserManager#requestQuietModeEnabled} api.
+ */
+ @Test(timeout = TIMEOUT_MAX_TEST_TIME_MS)
+ public void privateProfileUnlock() throws RemoteException {
+
+ while (mRunner.keepRunning()) {
+ mRunner.pauseTiming();
+ final int userId = createPrivateProfileUser();
+ startUserInBackgroundAndWaitForUnlock(userId);
+ mUm.requestQuietModeEnabled(true, UserHandle.of(userId));
+ waitCoolDownPeriod();
+ mRunner.resumeTiming();
+ Log.i(TAG, "Starting timer");
+
+ mUm.requestQuietModeEnabled(false, UserHandle.of(userId));
+
+ mRunner.pauseTiming();
+ Log.i(TAG, "Stopping timer");
+ removeUser(userId);
+ mRunner.resumeTimingForNextIteration();
+ }
+ }
+
+ /**
+ * Tests unlocking a newly-created private profile using the
+ * {@link UserManager#requestQuietModeEnabled} api and waiting for the
+ * {@link Intent.ACTION_PROFILE_AVAILABLE} to be received.
+ */
+ @Test(timeout = TIMEOUT_MAX_TEST_TIME_MS)
+ public void privateProfileUnlock_realistic() throws RemoteException {
+
+ while (mRunner.keepRunning()) {
+ mRunner.pauseTiming();
+ final int userId = createPrivateProfileUser();
+ startUserInBackgroundAndWaitForUnlock(userId);
+ mUm.requestQuietModeEnabled(true, UserHandle.of(userId));
+ waitCoolDownPeriod();
+ mRunner.resumeTiming();
+ Log.i(TAG, "Starting timer");
+
+ runThenWaitForBroadcasts(userId, () -> {
+ mUm.requestQuietModeEnabled(false, UserHandle.of(userId));
+ }, Intent.ACTION_PROFILE_AVAILABLE);
+
+ mRunner.pauseTiming();
+ Log.i(TAG, "Stopping timer");
+ removeUser(userId);
+ mRunner.resumeTimingForNextIteration();
+ }
+ }
+
+ /**
+ * Tests locking a newly-created private profile using the
+ * {@link UserManager#requestQuietModeEnabled} api.
+ */
+ @Test(timeout = TIMEOUT_MAX_TEST_TIME_MS)
+ public void privateProfileLock() throws RemoteException {
+
+ while (mRunner.keepRunning()) {
+ mRunner.pauseTiming();
+ final int userId = createPrivateProfileUser();
+ startUserInBackgroundAndWaitForUnlock(userId);
+ mRunner.resumeTiming();
+ Log.i(TAG, "Starting timer");
+
+ mUm.requestQuietModeEnabled(true, UserHandle.of(userId));
+
+ mRunner.pauseTiming();
+ Log.i(TAG, "Stopping timer");
+ removeUser(userId);
+ mRunner.resumeTimingForNextIteration();
+ }
+ }
+
+ /**
+ * Tests locking a newly-created private profile using the
+ * {@link UserManager#requestQuietModeEnabled} api and waiting for the
+ * {@link Intent.ACTION_PROFILE_UNAVAILABLE} to be received.
+ */
+ @Test(timeout = TIMEOUT_MAX_TEST_TIME_MS)
+ public void privateProfileLock_realistic() throws RemoteException {
+
+ while (mRunner.keepRunning()) {
+ mRunner.pauseTiming();
+ final int userId = createPrivateProfileUser();
+ startUserInBackgroundAndWaitForUnlock(userId);
+ mRunner.resumeTiming();
+ Log.i(TAG, "Starting timer");
+
+ runThenWaitForBroadcasts(userId, () -> {
+ mUm.requestQuietModeEnabled(true, UserHandle.of(userId));
+ }, Intent.ACTION_PROFILE_UNAVAILABLE);
+
+
+ mRunner.pauseTiming();
+ Log.i(TAG, "Stopping timer");
+ removeUser(userId);
+ mRunner.resumeTimingForNextIteration();
+ }
+ }
+
+ /** Tests removing a newly-created private profile */
+ @Test(timeout = TIMEOUT_MAX_TEST_TIME_MS)
+ public void privateProfileRemove() throws RemoteException {
+ while (mRunner.keepRunning()) {
+ mRunner.pauseTiming();
+ final int userId = createPrivateProfileUser();
+ mRunner.resumeTiming();
+ Log.i(TAG, "Starting timer");
+ removeUser(userId);
+ mRunner.pauseTiming();
+ Log.i(TAG, "Stopping timer");
+ mRunner.resumeTimingForNextIteration();
+ }
+ }
+
+ /** Tests installing a pre-existing app in a private profile. */
+ @Test(timeout = TIMEOUT_MAX_TEST_TIME_MS)
+ public void privateProfileInstall() throws RemoteException {
+ while (mRunner.keepRunning()) {
+ mRunner.pauseTiming();
+ final int userId = createPrivateProfileUser();
+ startUserInBackgroundAndWaitForUnlock(userId);
+ mRunner.resumeTiming();
+ Log.i(TAG, "Starting timer");
+
+ installPreexistingApp(userId, DUMMY_PACKAGE_NAME);
+
+ mRunner.pauseTiming();
+ Log.i(TAG, "Stopping timer");
+ removeUser(userId);
+ waitCoolDownPeriod();
+ mRunner.resumeTimingForNextIteration();
+ }
+ }
+
+ /**
+ * Tests creating a new private profile, starting it, installing an app, and launching that app
+ * in it.
+ */
+ @Test(timeout = TIMEOUT_MAX_TEST_TIME_MS)
+ public void privateProfileCreateStartInstallAndLaunchApp() throws RemoteException {
+ while (mRunner.keepRunning()) {
+ mRunner.pauseTiming();
+ WindowManagerGlobal.getWindowManagerService().dismissKeyguard(null, null);
+ mRunner.resumeTiming();
+ Log.i(TAG, "Starting timer");
+
+ final int userId = createPrivateProfileUser();
+ startUserInBackgroundAndWaitForUnlock(userId);
+ installPreexistingApp(userId, DUMMY_PACKAGE_NAME);
+ startApp(userId, DUMMY_PACKAGE_NAME);
+
+ mRunner.pauseTiming();
+ Log.i(TAG, "Stopping timer");
+ removeUser(userId);
+ waitCoolDownPeriod();
+ mRunner.resumeTimingForNextIteration();
+ }
+ }
+
+ /**
+ * Tests starting & launching an already-installed app in a private profile.
+ */
+ @Test(timeout = TIMEOUT_MAX_TEST_TIME_MS)
+ public void privateProfileStartAndLaunchApp() throws RemoteException {
+ while (mRunner.keepRunning()) {
+ mRunner.pauseTiming();
+ final int userId = createPrivateProfileUser();
+ WindowManagerGlobal.getWindowManagerService().dismissKeyguard(null, null);
+ installPreexistingApp(userId, DUMMY_PACKAGE_NAME);
+ mRunner.resumeTiming();
+ Log.i(TAG, "Starting timer");
+
+ startUserInBackgroundAndWaitForUnlock(userId);
+ startApp(userId, DUMMY_PACKAGE_NAME);
+
+ mRunner.pauseTiming();
+ Log.i(TAG, "Stopping timer");
+ removeUser(userId);
+ waitCoolDownPeriod();
+ mRunner.resumeTimingForNextIteration();
+ }
+ }
+
/** Creates a new user, returning its userId. */
private int createUserNoFlags() {
return createUserWithFlags(/* flags= */ 0);
@@ -1252,6 +1503,18 @@ public class UserLifecycleTests {
return userInfo.id;
}
+ /** Creates a private profile under the current user, returning its userId. */
+ private int createPrivateProfileUser() {
+ final UserInfo userInfo = mUm.createProfileForUser(TEST_USER_NAME,
+ UserManager.USER_TYPE_PROFILE_PRIVATE, /* flags */ 0, mAm.getCurrentUser());
+ attestFalse(
+ "Creating a private profile failed. Most likely there is already a pre-existing "
+ + "private profile on the device.",
+ userInfo == null);
+ mUsersToRemove.add(userInfo.id);
+ return userInfo.id;
+ }
+
/**
* Start user in background and wait for it to unlock by waiting for
* UserState.mUnlockProgress.finish().
diff --git a/core/java/android/permission/flags.aconfig b/core/java/android/permission/flags.aconfig
index e029e520f1b1..5ef597d7104a 100644
--- a/core/java/android/permission/flags.aconfig
+++ b/core/java/android/permission/flags.aconfig
@@ -213,3 +213,11 @@ flag {
purpose: PURPOSE_BUGFIX
}
}
+
+flag {
+ name: "location_bypass_privacy_dashboard_enabled"
+ is_exported: true
+ namespace: "permissions"
+ description: "Show access entry of location bypass permission in the Privacy Dashboard"
+ bug: "325536053"
+}
diff --git a/core/java/android/service/dreams/DreamService.java b/core/java/android/service/dreams/DreamService.java
index 2948129ae956..6b0d301c3baf 100644
--- a/core/java/android/service/dreams/DreamService.java
+++ b/core/java/android/service/dreams/DreamService.java
@@ -1405,7 +1405,8 @@ public class DreamService extends Service implements Window.Callback {
convertToComponentName(
rawMetadata.getString(
com.android.internal.R.styleable.Dream_settingsActivity),
- serviceInfo),
+ serviceInfo,
+ packageManager),
rawMetadata.getDrawable(
com.android.internal.R.styleable.Dream_previewImage),
rawMetadata.getBoolean(R.styleable.Dream_showClockAndComplications,
@@ -1420,26 +1421,38 @@ public class DreamService extends Service implements Window.Callback {
}
@Nullable
- private static ComponentName convertToComponentName(@Nullable String flattenedString,
- ServiceInfo serviceInfo) {
+ private static ComponentName convertToComponentName(
+ @Nullable String flattenedString,
+ ServiceInfo serviceInfo,
+ PackageManager packageManager) {
if (flattenedString == null) {
return null;
}
- if (!flattenedString.contains("/")) {
- return new ComponentName(serviceInfo.packageName, flattenedString);
+ final ComponentName cn =
+ flattenedString.contains("/")
+ ? ComponentName.unflattenFromString(flattenedString)
+ : new ComponentName(serviceInfo.packageName, flattenedString);
+
+ if (cn == null) {
+ return null;
}
// Ensure that the component is from the same package as the dream service. If not,
// treat the component as invalid and return null instead.
- final ComponentName cn = ComponentName.unflattenFromString(flattenedString);
- if (cn == null) return null;
if (!cn.getPackageName().equals(serviceInfo.packageName)) {
Log.w(TAG,
"Inconsistent package name in component: " + cn.getPackageName()
+ ", should be: " + serviceInfo.packageName);
return null;
}
+
+ // Ensure that the activity exists. If not, treat the component as invalid and return null.
+ if (new Intent().setComponent(cn).resolveActivityInfo(packageManager, 0) == null) {
+ Log.w(TAG, "Dream settings activity not found: " + cn);
+ return null;
+ }
+
return cn;
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipAnimationController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipAnimationController.java
index dc449d1aaf74..a8346a9b3b48 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipAnimationController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipAnimationController.java
@@ -17,6 +17,7 @@
package com.android.wm.shell.pip;
import static android.util.RotationUtils.rotateBounds;
+import static android.view.Surface.ROTATION_0;
import static android.view.Surface.ROTATION_270;
import static android.view.Surface.ROTATION_90;
@@ -625,6 +626,14 @@ public class PipAnimationController {
}
} else {
adjustedSourceRectHint.set(sourceRectHint);
+ if (isInPipDirection(direction)
+ && rotationDelta == ROTATION_0
+ && taskInfo.displayCutoutInsets != null) {
+ // TODO: this is to special case the issues on Foldable device
+ // with display cutout. This aligns with what's in SwipePipToHomeAnimator.
+ adjustedSourceRectHint.offset(taskInfo.displayCutoutInsets.left,
+ taskInfo.displayCutoutInsets.top);
+ }
}
final Rect sourceHintRectInsets = new Rect();
if (!adjustedSourceRectHint.isEmpty()) {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashScreenExitAnimationUtils.java b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashScreenExitAnimationUtils.java
index 6325c686a682..ea8c0eb30061 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashScreenExitAnimationUtils.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashScreenExitAnimationUtils.java
@@ -390,7 +390,8 @@ public class SplashScreenExitAnimationUtils {
SurfaceControl firstWindowSurface, ViewGroup splashScreenView,
TransactionPool transactionPool, Rect firstWindowFrame,
int mainWindowShiftLength, float roundedCornerRadius) {
- mFromYDelta = fromYDelta - Math.max(firstWindowFrame.top, roundedCornerRadius);
+ mFromYDelta = firstWindowFrame.top
+ - Math.max(firstWindowFrame.top, roundedCornerRadius);
mToYDelta = toYDelta;
mOccludeHoleView = occludeHoleView;
mApplier = new SyncRtSurfaceTransactionApplier(occludeHoleView);
diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/v2/model/InstallRepository.kt b/packages/PackageInstaller/src/com/android/packageinstaller/v2/model/InstallRepository.kt
index a23df64f3582..c260426d47f5 100644
--- a/packages/PackageInstaller/src/com/android/packageinstaller/v2/model/InstallRepository.kt
+++ b/packages/PackageInstaller/src/com/android/packageinstaller/v2/model/InstallRepository.kt
@@ -95,9 +95,22 @@ class InstallRepository(private val context: Context) {
*/
var stagedSessionId = SessionInfo.INVALID_ID
private set
+
+ /**
+ * UID of the last caller of Pia. This can point to a 3P installer if it uses intents to install
+ * an APK, or receives a
+ * [STATUS_PENDING_USER_ACTION][PackageInstaller.STATUS_PENDING_USER_ACTION] status code.
+ * It may point to Pia, when it receives the STATUS_PENDING_USER_ACTION status code in case of
+ * an update-ownership change.
+ */
private var callingUid = Process.INVALID_UID
+
+ /**
+ * UID of the origin of the installation. This UID is used to fetch the app-label of the
+ * source of the install, and also check whether the source app has the AppOp to install other
+ * apps.
+ */
private var originatingUid = Process.INVALID_UID
- private var originatingUidFromSessionInfo = Process.INVALID_UID
private var callingPackage: String? = null
private var sessionStager: SessionStager? = null
private lateinit var intent: Intent
@@ -136,68 +149,60 @@ class InstallRepository(private val context: Context) {
stagedSessionId = intent.getIntExtra(EXTRA_STAGED_SESSION_ID, SessionInfo.INVALID_ID)
callingPackage = callerInfo.packageName
-
- // Uid of the source package, coming from ActivityManager
callingUid = callerInfo.uid
- if (callingUid == Process.INVALID_UID) {
- Log.e(LOG_TAG, "Could not determine the launching uid.")
+
+ val sourceInfo: ApplicationInfo? = getSourceInfo(callingPackage)
+ if (callingUid == Process.INVALID_UID && sourceInfo == null) {
+ // Caller's identity could not be determined. Abort the install
+ Log.e(LOG_TAG, "Cannot determine caller since UID is invalid and sourceInfo is null")
+ return InstallAborted(ABORT_REASON_INTERNAL_ERROR)
}
- originatingUidFromSessionInfo = callingUid
+ originatingUid = callingUid
val sessionInfo: SessionInfo? =
if (sessionId != SessionInfo.INVALID_ID)
packageInstaller.getSessionInfo(sessionId)
else null
if (sessionInfo != null) {
- callingPackage = sessionInfo.installerPackageName
callingAttributionTag = sessionInfo.installerAttributionTag
if (sessionInfo.originatingUid != Process.INVALID_UID) {
- originatingUidFromSessionInfo = sessionInfo.originatingUid
+ originatingUid = sessionInfo.originatingUid
}
}
- val sourceInfo: ApplicationInfo? = getSourceInfo(callingPackage)
- // Uid of the source package, with a preference to uid from ApplicationInfo
- originatingUid = sourceInfo?.uid ?: callingUid
appOpRequestInfo = AppOpRequestInfo(
getPackageNameForUid(context, originatingUid, callingPackage),
- originatingUid, callingAttributionTag
+ originatingUid,
+ callingAttributionTag
)
- if(localLogv) {
- Log.i(LOG_TAG, "Intent: $intent\n" +
- "sessionId: $sessionId\n" +
- "staged sessionId: $stagedSessionId\n" +
- "calling package: $callingPackage\n" +
- "callingUid: $callingUid\n" +
- "originatingUid: $originatingUid")
- }
-
- if (callingUid == Process.INVALID_UID && sourceInfo == null) {
- // Caller's identity could not be determined. Abort the install
- Log.e(LOG_TAG, "Cannot determine caller since UID is invalid and sourceInfo is null")
- return InstallAborted(ABORT_REASON_INTERNAL_ERROR)
+ if (localLogv) {
+ Log.i(
+ LOG_TAG, "Intent: $intent\n" +
+ "sessionId: $sessionId\n" +
+ "staged sessionId: $stagedSessionId\n" +
+ "calling package: $callingPackage\n" +
+ "callingUid: $callingUid\n" +
+ "originatingUid: $originatingUid\n" +
+ "sourceInfo: $sourceInfo"
+ )
}
if ((sessionId != SessionInfo.INVALID_ID
- && !isCallerSessionOwner(packageInstaller, originatingUid, sessionId))
+ && !isCallerSessionOwner(packageInstaller, callingUid, sessionId))
|| (stagedSessionId != SessionInfo.INVALID_ID
&& !isCallerSessionOwner(packageInstaller, Process.myUid(), stagedSessionId))
) {
- Log.e(LOG_TAG, "UID is not the owner of the session:\n" +
- "CallingUid: $originatingUid | SessionId: $sessionId\n" +
- "My UID: ${Process.myUid()} | StagedSessionId: $stagedSessionId")
+ Log.e(
+ LOG_TAG, "UID is not the owner of the session:\n" +
+ "CallingUid: $callingUid | SessionId: $sessionId\n" +
+ "My UID: ${Process.myUid()} | StagedSessionId: $stagedSessionId"
+ )
return InstallAborted(ABORT_REASON_INTERNAL_ERROR)
}
- isTrustedSource = isInstallRequestFromTrustedSource(sourceInfo, this.intent, originatingUid)
- if (!isInstallPermissionGrantedOrRequested(
- context, callingUid, originatingUid, isTrustedSource
- )
- ) {
- Log.e(LOG_TAG, "UID $originatingUid needs to declare " +
- Manifest.permission.REQUEST_INSTALL_PACKAGES
- )
+ isTrustedSource = isInstallRequestFromTrustedSource(sourceInfo, this.intent, callingUid)
+ if (!isInstallPermissionGrantedOrRequested(context, callingUid, isTrustedSource)) {
return InstallAborted(ABORT_REASON_INTERNAL_ERROR)
}
@@ -205,7 +210,7 @@ class InstallRepository(private val context: Context) {
if (restriction != null) {
val adminSupportDetailsIntent =
devicePolicyManager!!.createAdminSupportIntent(restriction)
- Log.e(LOG_TAG, "$restriction set in place. Cannot install." )
+ Log.e(LOG_TAG, "$restriction set in place. Cannot install.")
return InstallAborted(
ABORT_REASON_POLICY, message = restriction, resultIntent = adminSupportDetailsIntent
)
@@ -230,12 +235,12 @@ class InstallRepository(private val context: Context) {
private fun isInstallRequestFromTrustedSource(
sourceInfo: ApplicationInfo?,
intent: Intent,
- originatingUid: Int,
+ callingUid: Int,
): Boolean {
val isNotUnknownSource = intent.getBooleanExtra(Intent.EXTRA_NOT_UNKNOWN_SOURCE, false)
return (sourceInfo != null && sourceInfo.isPrivilegedApp
&& (isNotUnknownSource
- || isPermissionGranted(context, Manifest.permission.INSTALL_PACKAGES, originatingUid)))
+ || isPermissionGranted(context, Manifest.permission.INSTALL_PACKAGES, callingUid)))
}
private fun getDevicePolicyRestrictions(): String? {
@@ -320,7 +325,7 @@ class InstallRepository(private val context: Context) {
Intent.EXTRA_INSTALL_RESULT, PackageManager.INSTALL_FAILED_INVALID_APK
),
activityResultCode = Activity.RESULT_FIRST_USER,
- errorDialogType = if (e is IOException) DLG_PACKAGE_ERROR else DLG_NONE
+ errorDialogType = if (e is IOException) DLG_PACKAGE_ERROR else DLG_NONE
)
return
}
@@ -401,8 +406,9 @@ class InstallRepository(private val context: Context) {
Log.e(LOG_TAG, "Cannot parse package $debugPathName. Assuming defaults.", e)
params.setSize(pfd.statSize)
} catch (e: IOException) {
- Log.e(LOG_TAG, "Cannot calculate installed size $debugPathName. " +
- "Try only apk size.", e
+ Log.e(
+ LOG_TAG, "Cannot calculate installed size $debugPathName. " +
+ "Try only apk size.", e
)
}
} else {
@@ -661,10 +667,9 @@ class InstallRepository(private val context: Context) {
if (isAppUpdating(pkgInfo)) {
val existingUpdateOwnerLabel = getExistingUpdateOwnerLabel(pkgInfo)
- val originatingPackageNameFromSessionInfo =
- getPackageNameForUid(context, originatingUidFromSessionInfo, callingPackage)
- val requestedUpdateOwnerLabel =
- getApplicationLabel(originatingPackageNameFromSessionInfo)
+ val originatingPackageName =
+ getPackageNameForUid(context, originatingUid, callingPackage)
+ val requestedUpdateOwnerLabel = getApplicationLabel(originatingPackageName)
if (!TextUtils.isEmpty(existingUpdateOwnerLabel)
&& userActionReason == PackageInstaller.REASON_REMIND_OWNERSHIP
@@ -768,14 +773,14 @@ class InstallRepository(private val context: Context) {
}
private fun handleUnknownSources(requestInfo: AppOpRequestInfo): InstallStage {
- if (requestInfo.callingPackage == null) {
+ if (requestInfo.originatingPackage == null) {
Log.i(LOG_TAG, "No source found for package " + newPackageInfo?.packageName)
return InstallUserActionRequired(USER_ACTION_REASON_ANONYMOUS_SOURCE)
}
// Shouldn't use static constant directly, see b/65534401.
val appOpStr = AppOpsManager.permissionToOp(Manifest.permission.REQUEST_INSTALL_PACKAGES)
val appOpMode = appOpsManager!!.noteOpNoThrow(
- appOpStr!!, requestInfo.originatingUid, requestInfo.callingPackage,
+ appOpStr!!, requestInfo.originatingUid, requestInfo.originatingPackage,
requestInfo.attributionTag, "Started package installation activity"
)
if (localLogv) {
@@ -786,20 +791,20 @@ class InstallRepository(private val context: Context) {
AppOpsManager.MODE_DEFAULT, AppOpsManager.MODE_ERRORED -> {
if (appOpMode == AppOpsManager.MODE_DEFAULT) {
appOpsManager.setMode(
- appOpStr, requestInfo.originatingUid, requestInfo.callingPackage,
+ appOpStr, requestInfo.originatingUid, requestInfo.originatingPackage,
AppOpsManager.MODE_ERRORED
)
}
try {
val sourceInfo =
- packageManager.getApplicationInfo(requestInfo.callingPackage, 0)
+ packageManager.getApplicationInfo(requestInfo.originatingPackage, 0)
val sourceAppSnippet = getAppSnippet(context, sourceInfo)
InstallUserActionRequired(
USER_ACTION_REASON_UNKNOWN_SOURCE, appSnippet = sourceAppSnippet,
- dialogMessage = requestInfo.callingPackage
+ sourceApp = requestInfo.originatingPackage
)
} catch (e: PackageManager.NameNotFoundException) {
- Log.e(LOG_TAG, "Did not find appInfo for " + requestInfo.callingPackage)
+ Log.e(LOG_TAG, "Did not find appInfo for " + requestInfo.originatingPackage)
InstallAborted(ABORT_REASON_INTERNAL_ERROR)
}
}
@@ -841,8 +846,10 @@ class InstallRepository(private val context: Context) {
setStageBasedOnResult(PackageInstaller.STATUS_SUCCESS, -1, null)
} catch (e: PackageManager.NameNotFoundException) {
setStageBasedOnResult(
- PackageInstaller.STATUS_FAILURE, PackageManager.INSTALL_FAILED_INTERNAL_ERROR,
- null)
+ PackageInstaller.STATUS_FAILURE,
+ PackageManager.INSTALL_FAILED_INTERNAL_ERROR,
+ null
+ )
}
return
}
@@ -862,7 +869,8 @@ class InstallRepository(private val context: Context) {
}
} catch (e: OutOfIdsException) {
setStageBasedOnResult(
- PackageInstaller.STATUS_FAILURE, PackageManager.INSTALL_FAILED_INTERNAL_ERROR, null)
+ PackageInstaller.STATUS_FAILURE, PackageManager.INSTALL_FAILED_INTERNAL_ERROR, null
+ )
return
}
val broadcastIntent = Intent(BROADCAST_ACTION)
@@ -880,7 +888,8 @@ class InstallRepository(private val context: Context) {
Log.e(LOG_TAG, "Session $stagedSessionId could not be opened.", e)
packageInstaller.abandonSession(stagedSessionId)
setStageBasedOnResult(
- PackageInstaller.STATUS_FAILURE, PackageManager.INSTALL_FAILED_INTERNAL_ERROR, null)
+ PackageInstaller.STATUS_FAILURE, PackageManager.INSTALL_FAILED_INTERNAL_ERROR, null
+ )
}
}
@@ -890,9 +899,11 @@ class InstallRepository(private val context: Context) {
message: String?,
) {
if (localLogv) {
- Log.i(LOG_TAG, "Status code: $statusCode\n" +
- "legacy status: $legacyStatus\n" +
- "message: $message")
+ Log.i(
+ LOG_TAG, "Status code: $statusCode\n" +
+ "legacy status: $legacyStatus\n" +
+ "message: $message"
+ )
}
if (statusCode == PackageInstaller.STATUS_SUCCESS) {
val shouldReturnResult = intent.getBooleanExtra(Intent.EXTRA_RETURN_RESULT, false)
@@ -905,11 +916,12 @@ class InstallRepository(private val context: Context) {
_installResult.setValue(InstallSuccess(appSnippet, shouldReturnResult, resultIntent))
} else {
if (statusCode != PackageInstaller.STATUS_FAILURE_ABORTED) {
- _installResult.setValue(InstallFailed(appSnippet, statusCode, legacyStatus, message))
+ _installResult.setValue(
+ InstallFailed(appSnippet, statusCode, legacyStatus, message)
+ )
} else {
_installResult.setValue(InstallAborted(ABORT_REASON_INTERNAL_ERROR))
}
-
}
}
@@ -953,7 +965,7 @@ class InstallRepository(private val context: Context) {
data class CallerInfo(val packageName: String?, val uid: Int)
data class AppOpRequestInfo(
- val callingPackage: String?,
+ val originatingPackage: String?,
val originatingUid: Int,
val attributionTag: String?,
)
diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/v2/model/InstallStages.kt b/packages/PackageInstaller/src/com/android/packageinstaller/v2/model/InstallStages.kt
index bbb9bca6db51..5dd4d2905f47 100644
--- a/packages/PackageInstaller/src/com/android/packageinstaller/v2/model/InstallStages.kt
+++ b/packages/PackageInstaller/src/com/android/packageinstaller/v2/model/InstallStages.kt
@@ -43,7 +43,10 @@ data class InstallUserActionRequired(
val actionReason: Int,
private val appSnippet: PackageUtil.AppSnippet? = null,
val isAppUpdating: Boolean = false,
- val dialogMessage: String? = null,
+ /**
+ * This holds either a package name or the app label of the install source.
+ */
+ val sourceApp: String? = null,
) : InstallStage(STAGE_USER_ACTION_REQUIRED) {
val appIcon: Drawable?
diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/v2/model/PackageUtil.kt b/packages/PackageInstaller/src/com/android/packageinstaller/v2/model/PackageUtil.kt
index bae6f6876580..85728528a25c 100644
--- a/packages/PackageInstaller/src/com/android/packageinstaller/v2/model/PackageUtil.kt
+++ b/packages/PackageInstaller/src/com/android/packageinstaller/v2/model/PackageUtil.kt
@@ -128,18 +128,19 @@ object PackageUtil {
/**
* @param context the [Context] object
- * @param callingUid the UID of the caller who's permission is being checked
- * @param originatingUid the UID from where install is being originated. This could be same as
- * callingUid or it will be the UID of the package performing a session based install
- * @param isTrustedSource whether install request is coming from a privileged app or an app that
- * has [Manifest.permission.INSTALL_PACKAGES] permission granted
- * @return `true` if the package is granted the said permission
+ * @param callingUid the UID of the caller of Pia
+ * @param isTrustedSource indicates whether install request is coming from a privileged app
+ * that has passed EXTRA_NOT_UNKNOWN_SOURCE as `true` in the installation intent, or that has
+ * the [INSTALL_PACKAGES][Manifest.permission.INSTALL_PACKAGES] permission granted.
+ *
+ * @return `true` if the package is either a system downloads provider, a document manager,
+ * a trusted source, or has declared the
+ * [REQUEST_INSTALL_PACKAGES][Manifest.permission.REQUEST_INSTALL_PACKAGES] in its manifest.
*/
@JvmStatic
fun isInstallPermissionGrantedOrRequested(
context: Context,
callingUid: Int,
- originatingUid: Int,
isTrustedSource: Boolean,
): Boolean {
val isDocumentsManager =
@@ -148,19 +149,18 @@ object PackageUtil {
getSystemDownloadsProviderInfo(context.packageManager, callingUid) != null
if (!isTrustedSource && !isSystemDownloadsProvider && !isDocumentsManager) {
- val targetSdkVersion = getMaxTargetSdkVersionForUid(context, originatingUid)
+ val targetSdkVersion = getMaxTargetSdkVersionForUid(context, callingUid)
if (targetSdkVersion < 0) {
- // Invalid originating uid supplied. Abort install.
- Log.w(LOG_TAG, "Cannot get target sdk version for uid $originatingUid")
+ // Invalid calling uid supplied. Abort install.
+ Log.e(LOG_TAG, "Cannot get target SDK version for uid $callingUid")
return false
} else if (targetSdkVersion >= Build.VERSION_CODES.O
&& !isUidRequestingPermission(
- context.packageManager, originatingUid,
- Manifest.permission.REQUEST_INSTALL_PACKAGES
+ context.packageManager, callingUid, Manifest.permission.REQUEST_INSTALL_PACKAGES
)
) {
Log.e(
- LOG_TAG, "Requesting uid " + originatingUid + " needs to declare permission "
+ LOG_TAG, "Requesting uid " + callingUid + " needs to declare permission "
+ Manifest.permission.REQUEST_INSTALL_PACKAGES
)
return false
@@ -204,13 +204,13 @@ object PackageUtil {
* @return `true` if the caller is the session owner
*/
@JvmStatic
- fun isCallerSessionOwner(pi: PackageInstaller, originatingUid: Int, sessionId: Int): Boolean {
- if (originatingUid == Process.ROOT_UID) {
+ fun isCallerSessionOwner(pi: PackageInstaller, callingUid: Int, sessionId: Int): Boolean {
+ if (callingUid == Process.ROOT_UID) {
return true
}
val sessionInfo = pi.getSessionInfo(sessionId) ?: return false
val installerUid = sessionInfo.getInstallerUid()
- return originatingUid == installerUid
+ return callingUid == installerUid
}
/**
@@ -362,8 +362,8 @@ object PackageUtil {
* @return the packageName corresponding to a UID.
*/
@JvmStatic
- fun getPackageNameForUid(context: Context, sourceUid: Int, callingPackage: String?): String? {
- if (sourceUid == Process.INVALID_UID) {
+ fun getPackageNameForUid(context: Context, uid: Int, preferredPkgName: String?): String? {
+ if (uid == Process.INVALID_UID) {
return null
}
// If the sourceUid belongs to the system downloads provider, we explicitly return the
@@ -371,20 +371,21 @@ object PackageUtil {
// packages, resulting in uncertainty about which package will end up first in the list
// of packages associated with this UID
val pm = context.packageManager
- val systemDownloadProviderInfo = getSystemDownloadsProviderInfo(pm, sourceUid)
+ val systemDownloadProviderInfo = getSystemDownloadsProviderInfo(pm, uid)
if (systemDownloadProviderInfo != null) {
return systemDownloadProviderInfo.packageName
}
- val packagesForUid = pm.getPackagesForUid(sourceUid) ?: return null
+
+ val packagesForUid = pm.getPackagesForUid(uid) ?: return null
if (packagesForUid.size > 1) {
- if (callingPackage != null) {
+ Log.i(LOG_TAG, "Multiple packages found for source uid $uid")
+ if (preferredPkgName != null) {
for (packageName in packagesForUid) {
- if (packageName == callingPackage) {
+ if (packageName == preferredPkgName) {
return packageName
}
}
}
- Log.i(LOG_TAG, "Multiple packages found for source uid $sourceUid")
}
return packagesForUid[0]
}
@@ -439,7 +440,7 @@ object PackageUtil {
*/
data class AppSnippet(var label: CharSequence?, var icon: Drawable?) {
override fun toString(): String {
- return "AppSnippet[label = ${label}, hasIcon = ${icon != null}]"
+ return "AppSnippet[label = $label, hasIcon = ${icon != null}]"
}
}
}
diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/v2/ui/fragments/ExternalSourcesBlockedFragment.java b/packages/PackageInstaller/src/com/android/packageinstaller/v2/ui/fragments/ExternalSourcesBlockedFragment.java
index a95137d57a32..343a213780b3 100644
--- a/packages/PackageInstaller/src/com/android/packageinstaller/v2/ui/fragments/ExternalSourcesBlockedFragment.java
+++ b/packages/PackageInstaller/src/com/android/packageinstaller/v2/ui/fragments/ExternalSourcesBlockedFragment.java
@@ -63,7 +63,7 @@ public class ExternalSourcesBlockedFragment extends DialogFragment {
.setMessage(R.string.untrusted_external_source_warning)
.setPositiveButton(R.string.external_sources_settings,
(dialog, which) -> mInstallActionListener.sendUnknownAppsIntent(
- mDialogData.getDialogMessage()))
+ mDialogData.getSourceApp()))
.setNegativeButton(R.string.cancel,
(dialog, which) -> mInstallActionListener.onNegativeResponse(
mDialogData.getStageCode()))
diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/v2/ui/fragments/InstallConfirmationFragment.java b/packages/PackageInstaller/src/com/android/packageinstaller/v2/ui/fragments/InstallConfirmationFragment.java
index 99b1eec9cd9e..e186590fa5e2 100644
--- a/packages/PackageInstaller/src/com/android/packageinstaller/v2/ui/fragments/InstallConfirmationFragment.java
+++ b/packages/PackageInstaller/src/com/android/packageinstaller/v2/ui/fragments/InstallConfirmationFragment.java
@@ -64,7 +64,7 @@ public class InstallConfirmationFragment extends DialogFragment {
int positiveBtnTextRes;
if (mDialogData.isAppUpdating()) {
- if (mDialogData.getDialogMessage() != null) {
+ if (mDialogData.getSourceApp() != null) {
positiveBtnTextRes = R.string.update_anyway;
} else {
positiveBtnTextRes = R.string.update;
@@ -88,9 +88,10 @@ public class InstallConfirmationFragment extends DialogFragment {
TextView viewToEnable;
if (mDialogData.isAppUpdating()) {
viewToEnable = dialogView.requireViewById(R.id.install_confirm_question_update);
- String dialogMessage = mDialogData.getDialogMessage();
- if (dialogMessage != null) {
- viewToEnable.setText(Html.fromHtml(dialogMessage, Html.FROM_HTML_MODE_LEGACY));
+ String sourcePackageName = mDialogData.getSourceApp();
+ if (sourcePackageName != null) {
+ // Show the update-ownership change message
+ viewToEnable.setText(Html.fromHtml(sourcePackageName, Html.FROM_HTML_MODE_LEGACY));
}
} else {
viewToEnable = dialogView.requireViewById(R.id.install_confirm_question);
diff --git a/packages/SettingsProvider/res/values/defaults.xml b/packages/SettingsProvider/res/values/defaults.xml
index 097840ead0d3..5ddf005d9468 100644
--- a/packages/SettingsProvider/res/values/defaults.xml
+++ b/packages/SettingsProvider/res/values/defaults.xml
@@ -45,7 +45,7 @@
<integer name="def_location_mode">3</integer>
<!-- 0 == off, 1 == on-->
<integer name="def_paired_device_location_mode">1</integer>
- <bool name="assisted_gps_enabled">true</bool>
+ <bool name="assisted_gps_enabled">false</bool>
<bool name="def_netstats_enabled">true</bool>
<bool name="def_usb_mass_storage_enabled">true</bool>
<bool name="def_wifi_on">false</bool>
diff --git a/packages/SystemUI/aconfig/systemui.aconfig b/packages/SystemUI/aconfig/systemui.aconfig
index 46726f1c867c..83b74557ea07 100644
--- a/packages/SystemUI/aconfig/systemui.aconfig
+++ b/packages/SystemUI/aconfig/systemui.aconfig
@@ -146,6 +146,16 @@ flag {
}
flag {
+ name: "notification_transparent_header_fix"
+ namespace: "systemui"
+ description: "fix the transparent group header issue for async header inflation."
+ bug: "340161724"
+ metadata {
+ purpose: PURPOSE_BUGFIX
+ }
+}
+
+flag {
name: "pss_app_selector_abrupt_exit_fix"
namespace: "systemui"
description: "Fixes the app selector abruptly disappearing without an animation, when the"
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/dialog/ui/composable/AlertDialogContent.kt b/packages/SystemUI/compose/features/src/com/android/systemui/dialog/ui/composable/AlertDialogContent.kt
index 418df5c7778d..0b9669410b8e 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/dialog/ui/composable/AlertDialogContent.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/dialog/ui/composable/AlertDialogContent.kt
@@ -89,7 +89,7 @@ fun AlertDialogContent(
// Content.
val contentColor = LocalAndroidColorScheme.current.onSurfaceVariant
- Box(Modifier.defaultMinSize(minHeight = 48.dp)) {
+ Box {
CompositionLocalProvider(LocalContentColor provides contentColor) {
ProvideTextStyle(
MaterialTheme.typography.bodyMedium.copy(textAlign = TextAlign.Center)
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/Notifications.kt b/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/Notifications.kt
index 2eea2f0565c0..a568824473cb 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/Notifications.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/Notifications.kt
@@ -19,13 +19,19 @@ package com.android.systemui.notifications.ui.composable
import android.util.Log
import androidx.compose.animation.core.Animatable
+import androidx.compose.animation.core.tween
import androidx.compose.foundation.ScrollState
import androidx.compose.foundation.background
import androidx.compose.foundation.clickable
+import androidx.compose.foundation.gestures.Orientation
+import androidx.compose.foundation.gestures.animateScrollBy
+import androidx.compose.foundation.gestures.rememberScrollableState
import androidx.compose.foundation.gestures.scrollBy
+import androidx.compose.foundation.gestures.scrollable
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.WindowInsets
+import androidx.compose.foundation.layout.absoluteOffset
import androidx.compose.foundation.layout.asPaddingValues
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
@@ -41,9 +47,11 @@ import androidx.compose.runtime.Composable
import androidx.compose.runtime.DisposableEffect
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableFloatStateOf
import androidx.compose.runtime.mutableIntStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
+import androidx.compose.runtime.setValue
import androidx.compose.runtime.snapshotFlow
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
@@ -52,6 +60,7 @@ import androidx.compose.ui.draw.drawBehind
import androidx.compose.ui.graphics.BlendMode
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.graphicsLayer
+import androidx.compose.ui.input.nestedscroll.NestedScrollConnection
import androidx.compose.ui.input.nestedscroll.nestedScroll
import androidx.compose.ui.layout.LayoutCoordinates
import androidx.compose.ui.layout.boundsInWindow
@@ -59,10 +68,12 @@ import androidx.compose.ui.layout.onGloballyPositioned
import androidx.compose.ui.layout.onPlaced
import androidx.compose.ui.layout.onSizeChanged
import androidx.compose.ui.layout.positionInWindow
+import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.res.dimensionResource
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.IntOffset
+import androidx.compose.ui.unit.Velocity
import androidx.compose.ui.unit.dp
import androidx.compose.ui.util.lerp
import androidx.lifecycle.compose.collectAsStateWithLifecycle
@@ -71,6 +82,7 @@ import com.android.compose.animation.scene.LowestZIndexScenePicker
import com.android.compose.animation.scene.NestedScrollBehavior
import com.android.compose.animation.scene.SceneScope
import com.android.compose.modifiers.thenIf
+import com.android.internal.policy.SystemBarUtils
import com.android.systemui.common.ui.compose.windowinsets.LocalRawScreenHeight
import com.android.systemui.common.ui.compose.windowinsets.LocalScreenCornerRadius
import com.android.systemui.res.R
@@ -137,6 +149,87 @@ fun SceneScope.HeadsUpNotificationSpace(
)
}
+/**
+ * A version of [HeadsUpNotificationSpace] that can be swiped up off the top edge of the screen by
+ * the user. When swiped up, the heads up notification is snoozed.
+ */
+@Composable
+fun SceneScope.SnoozeableHeadsUpNotificationSpace(
+ stackScrollView: NotificationScrollView,
+ viewModel: NotificationsPlaceholderViewModel,
+) {
+ val context = LocalContext.current
+ val density = LocalDensity.current
+ val statusBarHeight = SystemBarUtils.getStatusBarHeight(context)
+ val headsUpPadding =
+ with(density) { dimensionResource(id = R.dimen.heads_up_status_bar_padding).roundToPx() }
+
+ val isHeadsUp by viewModel.isHeadsUpOrAnimatingAway.collectAsStateWithLifecycle(false)
+
+ var scrollOffset by remember { mutableFloatStateOf(0f) }
+ val minScrollOffset = -(statusBarHeight + headsUpPadding.toFloat())
+ val maxScrollOffset = 0f
+
+ val scrollableState = rememberScrollableState { delta ->
+ consumeDeltaWithinRange(
+ current = scrollOffset,
+ setCurrent = { scrollOffset = it },
+ min = minScrollOffset,
+ max = maxScrollOffset,
+ delta
+ )
+ }
+
+ val nestedScrollConnection =
+ object : NestedScrollConnection {
+ override suspend fun onPreFling(available: Velocity): Velocity {
+ if (
+ velocityOrPositionalThresholdReached(scrollOffset, minScrollOffset, available.y)
+ ) {
+ scrollableState.animateScrollBy(minScrollOffset, tween())
+ } else {
+ scrollableState.animateScrollBy(-minScrollOffset, tween())
+ }
+ return available
+ }
+ }
+
+ LaunchedEffect(isHeadsUp) { scrollOffset = 0f }
+
+ LaunchedEffect(scrollableState.isScrollInProgress) {
+ if (!scrollableState.isScrollInProgress && scrollOffset <= minScrollOffset) {
+ viewModel.snoozeHun()
+ }
+ }
+
+ HeadsUpNotificationSpace(
+ stackScrollView = stackScrollView,
+ viewModel = viewModel,
+ modifier =
+ Modifier.absoluteOffset {
+ IntOffset(
+ x = 0,
+ y =
+ calculateHeadsUpPlaceholderYOffset(
+ scrollOffset.roundToInt(),
+ minScrollOffset.roundToInt(),
+ stackScrollView.topHeadsUpHeight
+ )
+ )
+ }
+ .thenIf(isHeadsUp) {
+ Modifier.verticalNestedScrollToScene(
+ bottomBehavior = NestedScrollBehavior.EdgeAlways
+ )
+ .nestedScroll(nestedScrollConnection)
+ .scrollable(
+ orientation = Orientation.Vertical,
+ state = scrollableState,
+ )
+ }
+ )
+}
+
/** Adds the space where notification stack should appear in the scene. */
@Composable
fun SceneScope.ConstrainedNotificationStack(
@@ -480,6 +573,47 @@ private fun calculateCornerRadius(
}
}
+private fun calculateHeadsUpPlaceholderYOffset(
+ scrollOffset: Int,
+ minScrollOffset: Int,
+ topHeadsUpHeight: Int,
+): Int {
+ return -minScrollOffset +
+ (scrollOffset * (-minScrollOffset + topHeadsUpHeight) / -minScrollOffset)
+}
+
+private fun velocityOrPositionalThresholdReached(
+ scrollOffset: Float,
+ minScrollOffset: Float,
+ availableVelocityY: Float,
+): Boolean {
+ return availableVelocityY < HUN_SNOOZE_VELOCITY_THRESHOLD ||
+ (availableVelocityY <= 0f &&
+ scrollOffset < minScrollOffset * HUN_SNOOZE_POSITIONAL_THRESHOLD_FRACTION)
+}
+
+/**
+ * Takes a range, current value, and delta, and updates the current value by the delta, coercing the
+ * result within the given range. Returns how much of the delta was consumed.
+ */
+private fun consumeDeltaWithinRange(
+ current: Float,
+ setCurrent: (Float) -> Unit,
+ min: Float,
+ max: Float,
+ delta: Float
+): Float {
+ return if (delta < 0 && current > min) {
+ val remainder = (current + delta - min).coerceAtMost(0f)
+ setCurrent((current + delta).coerceAtLeast(min))
+ delta - remainder
+ } else if (delta > 0 && current < max) {
+ val remainder = (current + delta).coerceAtLeast(0f)
+ setCurrent((current + delta).coerceAtMost(max))
+ delta - remainder
+ } else 0f
+}
+
private inline fun debugLog(
viewModel: NotificationsPlaceholderViewModel,
msg: () -> Any,
@@ -514,3 +648,5 @@ private const val TAG = "FlexiNotifs"
private val DEBUG_STACK_COLOR = Color(1f, 0f, 0f, 0.2f)
private val DEBUG_HUN_COLOR = Color(0f, 0f, 1f, 0.2f)
private val DEBUG_BOX_COLOR = Color(0f, 1f, 0f, 0.2f)
+private const val HUN_SNOOZE_POSITIONAL_THRESHOLD_FRACTION = 0.25f
+private const val HUN_SNOOZE_VELOCITY_THRESHOLD = -70f
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/GoneScene.kt b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/GoneScene.kt
index 4e334c274657..a44041a6ebf7 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/GoneScene.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/GoneScene.kt
@@ -17,26 +17,19 @@
package com.android.systemui.scene.ui.composable
import androidx.compose.foundation.layout.Spacer
-import androidx.compose.foundation.layout.absoluteOffset
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
-import androidx.compose.ui.platform.LocalContext
-import androidx.compose.ui.platform.LocalDensity
-import androidx.compose.ui.res.dimensionResource
-import androidx.compose.ui.unit.IntOffset
import com.android.compose.animation.scene.SceneScope
import com.android.compose.animation.scene.UserAction
import com.android.compose.animation.scene.UserActionResult
import com.android.compose.animation.scene.animateSceneDpAsState
import com.android.compose.animation.scene.animateSceneFloatAsState
-import com.android.internal.policy.SystemBarUtils
import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.notifications.ui.composable.HeadsUpNotificationSpace
+import com.android.systemui.notifications.ui.composable.SnoozeableHeadsUpNotificationSpace
import com.android.systemui.qs.ui.composable.QuickSettings
import com.android.systemui.qs.ui.composable.QuickSettings.SharedValues.MediaLandscapeTopOffset
import com.android.systemui.qs.ui.composable.QuickSettings.SharedValues.MediaOffset.Default
-import com.android.systemui.res.R
import com.android.systemui.scene.shared.model.Scenes
import com.android.systemui.scene.ui.viewmodel.GoneSceneViewModel
import com.android.systemui.statusbar.notification.stack.ui.view.NotificationScrollView
@@ -72,28 +65,9 @@ constructor(
)
animateSceneDpAsState(value = Default, key = MediaLandscapeTopOffset, canOverflow = false)
Spacer(modifier.fillMaxSize())
- HeadsUpNotificationStack(
+ SnoozeableHeadsUpNotificationSpace(
stackScrollView = notificationStackScrolLView.get(),
viewModel = notificationsPlaceholderViewModel
)
}
}
-
-@Composable
-private fun SceneScope.HeadsUpNotificationStack(
- stackScrollView: NotificationScrollView,
- viewModel: NotificationsPlaceholderViewModel,
-) {
- val context = LocalContext.current
- val density = LocalDensity.current
- val statusBarHeight = SystemBarUtils.getStatusBarHeight(context)
- val headsUpPadding =
- with(density) { dimensionResource(id = R.dimen.heads_up_status_bar_padding).roundToPx() }
-
- HeadsUpNotificationSpace(
- stackScrollView = stackScrollView,
- viewModel = viewModel,
- modifier =
- Modifier.absoluteOffset { IntOffset(x = 0, y = statusBarHeight + headsUpPadding) }
- )
-}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/volume/ui/composable/VolumeSlider.kt b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/volume/ui/composable/VolumeSlider.kt
index fbf91b702fb9..a23bb67215b5 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/volume/ui/composable/VolumeSlider.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/volume/ui/composable/VolumeSlider.kt
@@ -48,6 +48,7 @@ import com.android.compose.PlatformSlider
import com.android.compose.PlatformSliderColors
import com.android.systemui.common.shared.model.Icon
import com.android.systemui.common.ui.compose.Icon
+import com.android.systemui.compose.modifiers.sysuiResTag
import com.android.systemui.volume.panel.component.volume.slider.ui.viewmodel.SliderState
@Composable
@@ -62,7 +63,7 @@ fun VolumeSlider(
val value by valueState(state)
PlatformSlider(
modifier =
- modifier.clearAndSetSemantics {
+ modifier.sysuiResTag(state.label).clearAndSetSemantics {
if (state.isEnabled) {
contentDescription = state.label
state.a11yClickDescription?.let {
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/ui/composable/VolumePanelRoot.kt b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/ui/composable/VolumePanelRoot.kt
index ab14911ab425..9da2a1b06f30 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/ui/composable/VolumePanelRoot.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/ui/composable/VolumePanelRoot.kt
@@ -25,20 +25,25 @@ import androidx.compose.foundation.layout.padding
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.ui.Alignment
+import androidx.compose.ui.ExperimentalComposeUiApi
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.semantics.paneTitle
import androidx.compose.ui.semantics.semantics
import androidx.compose.ui.unit.dp
import androidx.lifecycle.compose.collectAsStateWithLifecycle
+import com.android.systemui.compose.modifiers.sysuiResTag
import com.android.systemui.res.R
import com.android.systemui.volume.panel.ui.layout.ComponentsLayout
import com.android.systemui.volume.panel.ui.viewmodel.VolumePanelState
import com.android.systemui.volume.panel.ui.viewmodel.VolumePanelViewModel
+/** Same as android.platform.systemui_tapl.ui.VolumePanel#VolumePanelTestTag */
+private const val VolumePanelTestTag = "VolumePanel"
private val padding = 24.dp
@Composable
+@OptIn(ExperimentalComposeUiApi::class)
fun VolumePanelRoot(
viewModel: VolumePanelViewModel,
modifier: Modifier = Modifier,
@@ -52,6 +57,7 @@ fun VolumePanelRoot(
Components(
componentsState,
modifier
+ .sysuiResTag(VolumePanelTestTag)
.semantics { paneTitle = accessibilityTitle }
.padding(
start = padding,
diff --git a/packages/SystemUI/res/layout/alert_dialog_systemui.xml b/packages/SystemUI/res/layout/alert_dialog_systemui.xml
index fd0623812745..14751ef901c8 100644
--- a/packages/SystemUI/res/layout/alert_dialog_systemui.xml
+++ b/packages/SystemUI/res/layout/alert_dialog_systemui.xml
@@ -31,7 +31,6 @@
android:id="@*android:id/contentPanel"
android:layout_width="match_parent"
android:layout_height="wrap_content"
- android:minHeight="48dp"
android:paddingStart="@dimen/dialog_side_padding"
android:paddingEnd="@dimen/dialog_side_padding"
>
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/data/repository/HeadsUpRepository.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/data/repository/HeadsUpRepository.kt
index e9306a5bd209..069ae9381012 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/data/repository/HeadsUpRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/data/repository/HeadsUpRepository.kt
@@ -41,4 +41,7 @@ interface HeadsUpRepository {
val activeHeadsUpRows: Flow<Set<HeadsUpRowRepository>>
fun setHeadsUpAnimatingAway(animatingAway: Boolean)
+
+ /** Snooze the currently pinned HUN. */
+ fun snooze()
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/domain/interactor/HeadsUpNotificationInteractor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/domain/interactor/HeadsUpNotificationInteractor.kt
index 4a6553f724c2..cdab108c0254 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/domain/interactor/HeadsUpNotificationInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/domain/interactor/HeadsUpNotificationInteractor.kt
@@ -101,10 +101,17 @@ constructor(
fun headsUpRow(key: HeadsUpRowKey): HeadsUpRowInteractor =
HeadsUpRowInteractor(key as HeadsUpRowRepository)
+
fun elementKeyFor(key: HeadsUpRowKey) = (key as HeadsUpRowRepository).elementKey
+
fun setHeadsUpAnimatingAway(animatingAway: Boolean) {
headsUpRepository.setHeadsUpAnimatingAway(animatingAway)
}
+
+ /** Snooze the currently pinned HUN. */
+ fun snooze() {
+ headsUpRepository.snooze()
+ }
}
class HeadsUpRowInteractor(repository: HeadsUpRowRepository)
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java
index 4a043d3617c3..9e943ee597f3 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java
@@ -104,6 +104,7 @@ import com.android.systemui.statusbar.notification.row.shared.AsyncGroupHeaderVi
import com.android.systemui.statusbar.notification.row.wrapper.NotificationCompactMessagingTemplateViewWrapper;
import com.android.systemui.statusbar.notification.row.wrapper.NotificationViewWrapper;
import com.android.systemui.statusbar.notification.shared.NotificationContentAlphaOptimization;
+import com.android.systemui.statusbar.notification.shared.TransparentHeaderFix;
import com.android.systemui.statusbar.notification.stack.AmbientState;
import com.android.systemui.statusbar.notification.stack.AnimationProperties;
import com.android.systemui.statusbar.notification.stack.ExpandableViewState;
@@ -1584,6 +1585,9 @@ public class ExpandableNotificationRow extends ActivatableNotificationView
/* headerView= */ headerView,
/* onClickListener= */ mExpandClickListener
);
+ if (TransparentHeaderFix.isEnabled()) {
+ updateBackgroundForGroupState();
+ }
}
/**
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/shared/TransparentHeaderFix.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/shared/TransparentHeaderFix.kt
new file mode 100644
index 000000000000..0b01510baec4
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/shared/TransparentHeaderFix.kt
@@ -0,0 +1,53 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.notification.shared
+
+import com.android.systemui.Flags
+import com.android.systemui.flags.FlagToken
+import com.android.systemui.flags.RefactorFlagUtils
+
+/** Helper for reading or using the notification transparent header fix flag state. */
+@Suppress("NOTHING_TO_INLINE")
+object TransparentHeaderFix {
+ /** The aconfig flag name */
+ const val FLAG_NAME = Flags.FLAG_NOTIFICATION_TRANSPARENT_HEADER_FIX
+
+ /** A token used for dependency declaration */
+ val token: FlagToken
+ get() = FlagToken(FLAG_NAME, isEnabled)
+
+ /** Is the flag enabled */
+ @JvmStatic
+ inline val isEnabled
+ get() = Flags.notificationTransparentHeaderFix()
+
+ /**
+ * Called to ensure code is only run when the flag is enabled. This protects users from the
+ * unintended behaviors caused by accidentally running new logic, while also crashing on an eng
+ * build to ensure that the refactor author catches issues in testing.
+ */
+ @JvmStatic
+ inline fun isUnexpectedlyInLegacyMode() =
+ RefactorFlagUtils.isUnexpectedlyInLegacyMode(isEnabled, FLAG_NAME)
+
+ /**
+ * Called to ensure code is only run when the flag is disabled. This will throw an exception if
+ * the flag is enabled to ensure that the refactor author catches issues in testing.
+ */
+ @JvmStatic
+ inline fun assertInLegacyMode() = RefactorFlagUtils.assertInLegacyMode(isEnabled, FLAG_NAME)
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
index 715c6e65d1de..cb0333414e41 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
@@ -3499,6 +3499,13 @@ public class NotificationStackScrollLayout
setIsBeingDragged(true);
}
+ // Only when scene container is enabled, mark that we are being dragged so that we start
+ // dispatching the rest of the gesture to scene container.
+ void startDraggingOnHun() {
+ SceneContainerFlag.isUnexpectedlyInLegacyMode();
+ setIsBeingDragged(true);
+ }
+
@Override
public boolean onGenericMotionEvent(MotionEvent event) {
if (!isScrollingEnabled()
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 12f8f6996cb6..58b72e52dd7b 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
@@ -59,6 +59,7 @@ import com.android.internal.logging.MetricsLogger;
import com.android.internal.logging.UiEvent;
import com.android.internal.logging.UiEventLogger;
import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
+import com.android.internal.statusbar.IStatusBarService;
import com.android.internal.view.OneShotPreDrawListener;
import com.android.systemui.Dumpable;
import com.android.systemui.ExpandHelper;
@@ -126,6 +127,7 @@ import com.android.systemui.statusbar.notification.row.NotificationSnooze;
import com.android.systemui.statusbar.notification.shared.NotificationsHeadsUpRefactor;
import com.android.systemui.statusbar.notification.stack.ui.viewbinder.NotificationListViewBinder;
import com.android.systemui.statusbar.phone.HeadsUpAppearanceController;
+import com.android.systemui.statusbar.phone.HeadsUpNotificationViewControllerEmptyImpl;
import com.android.systemui.statusbar.phone.HeadsUpTouchHelper;
import com.android.systemui.statusbar.phone.KeyguardBypassController;
import com.android.systemui.statusbar.policy.ConfigurationController;
@@ -168,6 +170,7 @@ public class NotificationStackScrollLayoutController implements Dumpable {
private final NotificationVisibilityProvider mVisibilityProvider;
private final NotificationWakeUpCoordinator mWakeUpCoordinator;
private final HeadsUpManager mHeadsUpManager;
+ private HeadsUpTouchHelper mHeadsUpTouchHelper;
private final NotificationRoundnessManager mNotificationRoundnessManager;
private final TunerService mTunerService;
private final DeviceProvisionedController mDeviceProvisionedController;
@@ -706,6 +709,7 @@ public class NotificationStackScrollLayoutController implements Dumpable {
NotificationVisibilityProvider visibilityProvider,
NotificationWakeUpCoordinator wakeUpCoordinator,
HeadsUpManager headsUpManager,
+ Provider<IStatusBarService> statusBarService,
NotificationRoundnessManager notificationRoundnessManager,
TunerService tunerService,
DeviceProvisionedController deviceProvisionedController,
@@ -758,6 +762,14 @@ public class NotificationStackScrollLayoutController implements Dumpable {
mVisibilityProvider = visibilityProvider;
mWakeUpCoordinator = wakeUpCoordinator;
mHeadsUpManager = headsUpManager;
+ if (SceneContainerFlag.isEnabled()) {
+ mHeadsUpTouchHelper = new HeadsUpTouchHelper(
+ mHeadsUpManager,
+ statusBarService.get(),
+ getHeadsUpCallback(),
+ new HeadsUpNotificationViewControllerEmptyImpl()
+ );
+ }
mNotificationRoundnessManager = notificationRoundnessManager;
mTunerService = tunerService;
mDeviceProvisionedController = deviceProvisionedController;
@@ -2000,6 +2012,13 @@ public class NotificationStackScrollLayoutController implements Dumpable {
&& !mView.isExpandingNotification()) {
scrollWantsIt = mView.onInterceptTouchEventScroll(ev);
}
+ boolean hunWantsIt = false;
+ if (shouldHeadsUpHandleTouch()) {
+ hunWantsIt = mHeadsUpTouchHelper.onInterceptTouchEvent(ev);
+ if (hunWantsIt) {
+ mView.startDraggingOnHun();
+ }
+ }
boolean swipeWantsIt = false;
if (mLongPressedView == null && !mView.isBeingDragged()
&& !mView.isExpandingNotification()
@@ -2029,7 +2048,7 @@ public class NotificationStackScrollLayoutController implements Dumpable {
&& ev.getActionMasked() != MotionEvent.ACTION_DOWN) {
mJankMonitor.begin(mView, CUJ_NOTIFICATION_SHADE_SCROLL_FLING);
}
- return swipeWantsIt || scrollWantsIt || expandWantsIt || longPressWantsIt;
+ return swipeWantsIt || scrollWantsIt || expandWantsIt || longPressWantsIt || hunWantsIt;
}
@Override
@@ -2081,6 +2100,10 @@ public class NotificationStackScrollLayoutController implements Dumpable {
&& !expandingNotification && !mView.getDisallowScrollingInThisMotion()) {
scrollerWantsIt = mView.onScrollTouch(ev);
}
+ boolean hunWantsIt = false;
+ if (shouldHeadsUpHandleTouch()) {
+ hunWantsIt = mHeadsUpTouchHelper.onTouchEvent(ev);
+ }
// Check if we need to clear any snooze leavebehinds
if (guts != null && !NotificationSwipeHelper.isTouchInView(ev, guts)
@@ -2101,7 +2124,8 @@ public class NotificationStackScrollLayoutController implements Dumpable {
mView.setCheckForLeaveBehind(true);
}
traceJankOnTouchEvent(ev.getActionMasked(), scrollerWantsIt);
- return horizontalSwipeWantsIt || scrollerWantsIt || expandWantsIt || longPressWantsIt;
+ return horizontalSwipeWantsIt || scrollerWantsIt || expandWantsIt || longPressWantsIt
+ || hunWantsIt;
}
private void traceJankOnTouchEvent(int action, boolean scrollerWantsIt) {
@@ -2128,6 +2152,11 @@ public class NotificationStackScrollLayoutController implements Dumpable {
break;
}
}
+
+ private boolean shouldHeadsUpHandleTouch() {
+ return SceneContainerFlag.isEnabled() && mLongPressedView == null
+ && !mSwipeHelper.isSwiping();
+ }
}
private class NotifStackControllerImpl implements NotifStackController {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationsPlaceholderViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationsPlaceholderViewModel.kt
index 08e81d556216..eb1f7784f3aa 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationsPlaceholderViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationsPlaceholderViewModel.kt
@@ -23,6 +23,7 @@ import com.android.systemui.flags.Flags
import com.android.systemui.scene.shared.flag.SceneContainerFlag
import com.android.systemui.shade.domain.interactor.ShadeInteractor
import com.android.systemui.shade.ui.viewmodel.ShadeSceneViewModel
+import com.android.systemui.statusbar.notification.domain.interactor.HeadsUpNotificationInteractor
import com.android.systemui.statusbar.notification.stack.domain.interactor.NotificationStackAppearanceInteractor
import com.android.systemui.statusbar.notification.stack.shared.model.ShadeScrimBounds
import com.android.systemui.statusbar.notification.stack.shared.model.ShadeScrimRounding
@@ -43,6 +44,7 @@ constructor(
private val interactor: NotificationStackAppearanceInteractor,
shadeInteractor: ShadeInteractor,
private val shadeSceneViewModel: ShadeSceneViewModel,
+ private val headsUpNotificationInteractor: HeadsUpNotificationInteractor,
featureFlags: FeatureFlagsClassic,
) : FlowDumperImpl(dumpManager) {
/** DEBUG: whether the placeholder should be made slightly visible for positional debugging. */
@@ -74,6 +76,10 @@ constructor(
/** Whether or not the notification scrim should be clickable. */
val isClickable: StateFlow<Boolean> = shadeSceneViewModel.isClickable
+ /** True when a HUN is pinned or animating away. */
+ val isHeadsUpOrAnimatingAway: Flow<Boolean> =
+ headsUpNotificationInteractor.isHeadsUpOrAnimatingAway
+
/** Corner rounding of the stack */
val shadeScrimRounding: Flow<ShadeScrimRounding> =
interactor.shadeScrimRounding.dumpWhileCollecting("shadeScrimRounding")
@@ -103,6 +109,11 @@ constructor(
fun setScrolledToTop(scrolledToTop: Boolean) {
interactor.setScrolledToTop(scrolledToTop)
}
+
+ /** Snooze the currently pinned HUN. */
+ fun snoozeHun() {
+ headsUpNotificationInteractor.snooze()
+ }
}
// Expansion fraction thresholds (between 0-1f) at which the corresponding value should be
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpNotificationViewControllerEmptyImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpNotificationViewControllerEmptyImpl.kt
new file mode 100644
index 000000000000..9f76429bd6a5
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpNotificationViewControllerEmptyImpl.kt
@@ -0,0 +1,34 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.phone
+
+import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow
+import com.android.systemui.statusbar.phone.HeadsUpTouchHelper.HeadsUpNotificationViewController
+
+/** Empty impl of [HeadsUpNotificationViewController] for use with Scene Container */
+class HeadsUpNotificationViewControllerEmptyImpl : HeadsUpNotificationViewController {
+ override fun setHeadsUpDraggingStartingHeight(startHeight: Int) {}
+
+ override fun setTrackedHeadsUp(expandableNotificationRow: ExpandableNotificationRow?) {}
+
+ override fun startExpand(
+ newX: Float,
+ newY: Float,
+ startTracking: Boolean,
+ expandedHeight: Float
+ ) {}
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpTouchHelper.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpTouchHelper.java
index 198272e9b162..7f16e18716c4 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpTouchHelper.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpTouchHelper.java
@@ -23,6 +23,7 @@ import android.view.ViewConfiguration;
import com.android.internal.statusbar.IStatusBarService;
import com.android.systemui.Gefingerpoken;
+import com.android.systemui.scene.shared.flag.SceneContainerFlag;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
import com.android.systemui.statusbar.notification.row.ExpandableView;
@@ -122,9 +123,14 @@ public class HeadsUpTouchHelper implements Gefingerpoken {
+ mPickedChild.getTranslationY());
mPanel.setHeadsUpDraggingStartingHeight(startHeight);
mPanel.startExpand(x, y, true /* startTracking */, startHeight);
- // This call needs to be after the expansion start otherwise we will get a
- // flicker of one frame as it's not expanded yet.
- mHeadsUpManager.unpinAll(true);
+
+ // TODO(b/340514839): Figure out where to move this side effect in flexiglass
+ if (!SceneContainerFlag.isEnabled()) {
+ // This call needs to be after the expansion start otherwise we will get a
+ // flicker of one frame as it's not expanded yet.
+ mHeadsUpManager.unpinAll(true);
+ }
+
clearNotificationEffects();
endMotion();
return true;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutControllerTest.java
index ce2491bb098e..1060b62f071f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutControllerTest.java
@@ -53,6 +53,7 @@ import androidx.test.filters.SmallTest;
import com.android.internal.logging.MetricsLogger;
import com.android.internal.logging.UiEventLogger;
import com.android.internal.logging.nano.MetricsProto;
+import com.android.internal.statusbar.IStatusBarService;
import com.android.systemui.ExpandHelper;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.bouncer.domain.interactor.PrimaryBouncerInteractor;
@@ -99,6 +100,7 @@ import com.android.systemui.statusbar.notification.row.NotificationGutsManager;
import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController.NotificationPanelEvent;
import com.android.systemui.statusbar.notification.stack.NotificationSwipeHelper.NotificationCallback;
import com.android.systemui.statusbar.notification.stack.ui.viewbinder.NotificationListViewBinder;
+import com.android.systemui.statusbar.phone.HeadsUpTouchHelper;
import com.android.systemui.statusbar.phone.KeyguardBypassController;
import com.android.systemui.statusbar.policy.ConfigurationController;
import com.android.systemui.statusbar.policy.DeviceProvisionedController;
@@ -135,6 +137,8 @@ public class NotificationStackScrollLayoutControllerTest extends SysuiTestCase {
@Mock private NotificationVisibilityProvider mVisibilityProvider;
@Mock private NotificationWakeUpCoordinator mNotificationWakeUpCoordinator;
@Mock private HeadsUpManager mHeadsUpManager;
+ @Mock private HeadsUpTouchHelper.Callback mHeadsUpCallback;
+ @Mock private Provider<IStatusBarService> mStatusBarService;
@Mock private NotificationRoundnessManager mNotificationRoundnessManager;
@Mock private TunerService mTunerService;
@Mock private DeviceProvisionedController mDeviceProvisionedController;
@@ -973,6 +977,8 @@ public class NotificationStackScrollLayoutControllerTest extends SysuiTestCase {
when(mNotificationStackScrollLayout.getViewTreeObserver())
.thenReturn(viewTreeObserver);
when(mNotificationStackScrollLayout.getContext()).thenReturn(getContext());
+ when(mNotificationStackScrollLayout.getHeadsUpCallback()).thenReturn(mHeadsUpCallback);
+ when(mHeadsUpCallback.getContext()).thenReturn(getContext());
mController = new NotificationStackScrollLayoutController(
mNotificationStackScrollLayout,
true,
@@ -981,6 +987,7 @@ public class NotificationStackScrollLayoutControllerTest extends SysuiTestCase {
mVisibilityProvider,
mNotificationWakeUpCoordinator,
mHeadsUpManager,
+ mStatusBarService,
mNotificationRoundnessManager,
mTunerService,
mDeviceProvisionedController,
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/data/repository/HeadsUpNotificationRepositoryKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/data/repository/HeadsUpNotificationRepositoryKosmos.kt
index 7bf77e5199fe..492e87bbcac4 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/data/repository/HeadsUpNotificationRepositoryKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/data/repository/HeadsUpNotificationRepositoryKosmos.kt
@@ -30,7 +30,12 @@ class FakeHeadsUpNotificationRepository : HeadsUpRepository {
override val topHeadsUpRow: Flow<HeadsUpRowRepository?> = MutableStateFlow(null)
override val activeHeadsUpRows: MutableStateFlow<Set<HeadsUpRowRepository>> =
MutableStateFlow(emptySet())
+
override fun setHeadsUpAnimatingAway(animatingAway: Boolean) {
isHeadsUpAnimatingAway.value = animatingAway
}
+
+ override fun snooze() {
+ // do nothing
+ }
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationsPlaceholderViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationsPlaceholderViewModelKosmos.kt
index a0e9303890ba..afb8acb3d819 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationsPlaceholderViewModelKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationsPlaceholderViewModelKosmos.kt
@@ -22,6 +22,7 @@ import com.android.systemui.kosmos.Kosmos
import com.android.systemui.kosmos.Kosmos.Fixture
import com.android.systemui.shade.domain.interactor.shadeInteractor
import com.android.systemui.shade.ui.viewmodel.shadeSceneViewModel
+import com.android.systemui.statusbar.notification.stack.domain.interactor.headsUpNotificationInteractor
import com.android.systemui.statusbar.notification.stack.domain.interactor.notificationStackAppearanceInteractor
val Kosmos.notificationsPlaceholderViewModel by Fixture {
@@ -30,6 +31,7 @@ val Kosmos.notificationsPlaceholderViewModel by Fixture {
interactor = notificationStackAppearanceInteractor,
shadeInteractor = shadeInteractor,
shadeSceneViewModel = shadeSceneViewModel,
+ headsUpNotificationInteractor = headsUpNotificationInteractor,
featureFlags = featureFlagsClassic,
)
}
diff --git a/services/core/java/com/android/server/appop/DiscreteRegistry.java b/services/core/java/com/android/server/appop/DiscreteRegistry.java
index b1a12f7338da..5d83ad67d535 100644
--- a/services/core/java/com/android/server/appop/DiscreteRegistry.java
+++ b/services/core/java/com/android/server/appop/DiscreteRegistry.java
@@ -26,6 +26,7 @@ import static android.app.AppOpsManager.FILTER_BY_PACKAGE_NAME;
import static android.app.AppOpsManager.FILTER_BY_UID;
import static android.app.AppOpsManager.OP_CAMERA;
import static android.app.AppOpsManager.OP_COARSE_LOCATION;
+import static android.app.AppOpsManager.OP_EMERGENCY_LOCATION;
import static android.app.AppOpsManager.OP_FINE_LOCATION;
import static android.app.AppOpsManager.OP_FLAGS_ALL;
import static android.app.AppOpsManager.OP_FLAG_SELF;
@@ -135,9 +136,10 @@ final class DiscreteRegistry {
private static final String PROPERTY_DISCRETE_FLAGS = "discrete_history_op_flags";
private static final String PROPERTY_DISCRETE_OPS_LIST = "discrete_history_ops_cslist";
private static final String DEFAULT_DISCRETE_OPS = OP_FINE_LOCATION + "," + OP_COARSE_LOCATION
- + "," + OP_CAMERA + "," + OP_RECORD_AUDIO + "," + OP_PHONE_CALL_MICROPHONE + ","
- + OP_PHONE_CALL_CAMERA + "," + OP_RECEIVE_AMBIENT_TRIGGER_AUDIO + ","
- + OP_RECEIVE_SANDBOX_TRIGGER_AUDIO + "," + OP_RESERVED_FOR_TESTING;
+ + "," + OP_EMERGENCY_LOCATION + "," + OP_CAMERA + "," + OP_RECORD_AUDIO + ","
+ + OP_PHONE_CALL_MICROPHONE + "," + OP_PHONE_CALL_CAMERA + ","
+ + OP_RECEIVE_AMBIENT_TRIGGER_AUDIO + "," + OP_RECEIVE_SANDBOX_TRIGGER_AUDIO
+ + "," + OP_RESERVED_FOR_TESTING;
private static final long DEFAULT_DISCRETE_HISTORY_CUTOFF = Duration.ofDays(7).toMillis();
private static final long MAXIMUM_DISCRETE_HISTORY_CUTOFF = Duration.ofDays(30).toMillis();
private static final long DEFAULT_DISCRETE_HISTORY_QUANTIZATION =
diff --git a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
index 5228be48371c..07a7c5536352 100644
--- a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
+++ b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
@@ -3038,7 +3038,7 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl.
// getCurrentInputMethodSubtype.
subtypeId = NOT_A_SUBTYPE_ID;
// TODO(b/347083680): The method below has questionable behaviors.
- newSubtype = getCurrentInputMethodSubtypeLocked();
+ newSubtype = getCurrentInputMethodSubtypeLocked(userId);
if (newSubtype != null) {
for (int i = 0; i < subtypeCount; ++i) {
if (Objects.equals(newSubtype, info.getSubtypeAt(i))) {
@@ -5486,7 +5486,7 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl.
newSubtypeHashcode = INVALID_SUBTYPE_HASHCODE;
// If the subtype is not specified, choose the most applicable one
// TODO(b/347083680): The method below has questionable behaviors.
- newSubtype = getCurrentInputMethodSubtypeLocked();
+ newSubtype = getCurrentInputMethodSubtypeLocked(userId);
}
}
settings.putSelectedSubtype(newSubtypeHashcode);
@@ -5541,37 +5541,25 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl.
Manifest.permission.INTERACT_ACROSS_USERS_FULL, null);
}
synchronized (ImfLock.class) {
- if (mCurrentUserId == userId) {
- // TODO(b/347083680): The method below has questionable behaviors.
- return getCurrentInputMethodSubtypeLocked();
- }
-
- return InputMethodSettingsRepository.get(userId)
- .getCurrentInputMethodSubtypeForNonCurrentUsers();
+ // TODO(b/347083680): The method below has questionable behaviors.
+ return getCurrentInputMethodSubtypeLocked(userId);
}
}
/**
* Returns the current {@link InputMethodSubtype} for the current user.
*
- * <p>CAVEATS: You must also update
- * {@link InputMethodSettings#getCurrentInputMethodSubtypeForNonCurrentUsers()}
- * when you update the algorithm of this method.</p>
- *
- * <p>TODO: Address code duplication between this and
- * {@link InputMethodSettings#getCurrentInputMethodSubtypeForNonCurrentUsers()}.</p>
- *
* <p>Also this method has had questionable behaviors:</p>
* <ul>
- * <li>Calling this method can update {@link #mCurrentSubtype}.</li>
- * <li>This method may return {@link #mCurrentSubtype} as-is, even if it does not belong
- * to the current IME.</li>
+ * <li>Calling this method can update {@link InputMethodBindingController#mCurrentSubtype}.
+ * </li>
+ * <li>This method may return {@link InputMethodBindingController#mCurrentSubtype} as-is,
+ * even if it does not belong to the current IME.</li>
* </ul>
* <p>TODO(b/347083680): Address above issues.</p>
*/
@GuardedBy("ImfLock.class")
- InputMethodSubtype getCurrentInputMethodSubtypeLocked() {
- final int userId = mCurrentUserId;
+ InputMethodSubtype getCurrentInputMethodSubtypeLocked(@UserIdInt int userId) {
final var selectedMethodId = getInputMethodBindingController(userId).getSelectedMethodId();
if (selectedMethodId == null) {
return null;
diff --git a/services/core/java/com/android/server/inputmethod/InputMethodMenuController.java b/services/core/java/com/android/server/inputmethod/InputMethodMenuController.java
index 154ee1d9fde8..a5d963bd5be3 100644
--- a/services/core/java/com/android/server/inputmethod/InputMethodMenuController.java
+++ b/services/core/java/com/android/server/inputmethod/InputMethodMenuController.java
@@ -85,7 +85,7 @@ final class InputMethodMenuController {
if (preferredInputMethodSubtypeId == NOT_A_SUBTYPE_ID) {
final InputMethodSubtype currentSubtype =
- mService.getCurrentInputMethodSubtypeLocked();
+ mService.getCurrentInputMethodSubtypeLocked(userId);
if (currentSubtype != null) {
final String curMethodId = bindingController.getSelectedMethodId();
final InputMethodInfo currentImi =
diff --git a/services/core/java/com/android/server/inputmethod/InputMethodSettings.java b/services/core/java/com/android/server/inputmethod/InputMethodSettings.java
index 5569e1e5211e..0152158cbb7a 100644
--- a/services/core/java/com/android/server/inputmethod/InputMethodSettings.java
+++ b/services/core/java/com/android/server/inputmethod/InputMethodSettings.java
@@ -571,57 +571,6 @@ final class InputMethodSettings {
}
}
- /**
- * A variant of {@link InputMethodManagerService#getCurrentInputMethodSubtypeLocked()} for
- * non-current users.
- *
- * <p>TODO: Address code duplication between this and
- * {@link InputMethodManagerService#getCurrentInputMethodSubtypeLocked()}.</p>
- *
- * @return {@link InputMethodSubtype} if exists. {@code null} otherwise.
- */
- @Nullable
- InputMethodSubtype getCurrentInputMethodSubtypeForNonCurrentUsers() {
- final String selectedMethodId = getSelectedInputMethod();
- if (selectedMethodId == null) {
- return null;
- }
- final InputMethodInfo imi = mMethodMap.get(selectedMethodId);
- if (imi == null || imi.getSubtypeCount() == 0) {
- return null;
- }
-
- final int subtypeHashCode = getSelectedInputMethodSubtypeHashCode();
- if (subtypeHashCode != INVALID_SUBTYPE_HASHCODE) {
- final int subtypeIndex = SubtypeUtils.getSubtypeIdFromHashCode(imi,
- subtypeHashCode);
- if (subtypeIndex >= 0) {
- return imi.getSubtypeAt(subtypeIndex);
- }
- }
-
- // If there are no selected subtypes, the framework will try to find the most applicable
- // subtype from explicitly or implicitly enabled subtypes.
- final List<InputMethodSubtype> explicitlyOrImplicitlyEnabledSubtypes =
- getEnabledInputMethodSubtypeList(imi, true);
- // If there is only one explicitly or implicitly enabled subtype, just returns it.
- if (explicitlyOrImplicitlyEnabledSubtypes.isEmpty()) {
- return null;
- }
- if (explicitlyOrImplicitlyEnabledSubtypes.size() == 1) {
- return explicitlyOrImplicitlyEnabledSubtypes.get(0);
- }
- final String locale = SystemLocaleWrapper.get(mUserId).get(0).toString();
- final InputMethodSubtype subtype = SubtypeUtils.findLastResortApplicableSubtype(
- explicitlyOrImplicitlyEnabledSubtypes, SubtypeUtils.SUBTYPE_MODE_KEYBOARD,
- locale, true);
- if (subtype != null) {
- return subtype;
- }
- return SubtypeUtils.findLastResortApplicableSubtype(
- explicitlyOrImplicitlyEnabledSubtypes, null, locale, true);
- }
-
@NonNull
AdditionalSubtypeMap getNewAdditionalSubtypeMap(@NonNull String imeId,
@NonNull ArrayList<InputMethodSubtype> subtypes,
diff --git a/services/tests/dreamservicetests/AndroidManifest.xml b/services/tests/dreamservicetests/AndroidManifest.xml
index 6092ef6f9427..449521b05135 100644
--- a/services/tests/dreamservicetests/AndroidManifest.xml
+++ b/services/tests/dreamservicetests/AndroidManifest.xml
@@ -53,6 +53,35 @@
android:name="android.service.dream"
android:resource="@xml/test_dream_metadata_invalid" />
</service>
+
+ <service
+ android:name="com.android.server.dreams.TestDreamServiceWithNonexistentSettings"
+ android:exported="false"
+ android:label="Test Dream" >
+ <intent-filter>
+ <action android:name="android.service.dreams.DreamService" />
+ <category android:name="android.intent.category.DEFAULT" />
+ </intent-filter>
+ <meta-data
+ android:name="android.service.dream"
+ android:resource="@xml/test_dream_metadata_nonexistent_settings" />
+ </service>
+
+ <service
+ android:name="com.android.server.dreams.TestDreamServiceNoPackageNonexistentSettings"
+ android:exported="false"
+ android:label="Test Dream" >
+ <intent-filter>
+ <action android:name="android.service.dreams.DreamService" />
+ <category android:name="android.intent.category.DEFAULT" />
+ </intent-filter>
+ <meta-data
+ android:name="android.service.dream"
+ android:resource="@xml/test_dream_metadata_nopackage_nonexistent_settings" />
+ </service>
+
+ <activity android:name=".TestDreamSettingsActivity">
+ </activity>
</application>
<instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
diff --git a/services/tests/dreamservicetests/res/xml/test_dream_metadata_nonexistent_settings.xml b/services/tests/dreamservicetests/res/xml/test_dream_metadata_nonexistent_settings.xml
new file mode 100644
index 000000000000..f91f0586ec6c
--- /dev/null
+++ b/services/tests/dreamservicetests/res/xml/test_dream_metadata_nonexistent_settings.xml
@@ -0,0 +1,20 @@
+<!--
+ ~ Copyright (C) 2024 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<!-- The settings activity does not exist, which is invalid. -->
+<dream xmlns:android="http://schemas.android.com/apk/res/android"
+ android:settingsActivity="com.android.frameworks.dreamservicetests/.TestDreamSettingsActivityNonexistent"
+ android:showClockAndComplications="false"/>
diff --git a/services/tests/dreamservicetests/res/xml/test_dream_metadata_nopackage_nonexistent_settings.xml b/services/tests/dreamservicetests/res/xml/test_dream_metadata_nopackage_nonexistent_settings.xml
new file mode 100644
index 000000000000..24032c9efcea
--- /dev/null
+++ b/services/tests/dreamservicetests/res/xml/test_dream_metadata_nopackage_nonexistent_settings.xml
@@ -0,0 +1,20 @@
+<!--
+ ~ Copyright (C) 2024 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<!-- The settings activity's ComponentName is invalid. -->
+<dream xmlns:android="http://schemas.android.com/apk/res/android"
+ android:settingsActivity="com.android.frameworks.dreamservicetests.TestDreamSettingsActivityNonexistentNoPackage"
+ android:showClockAndComplications="false"/>
diff --git a/services/tests/dreamservicetests/src/com/android/server/dreams/DreamServiceTest.java b/services/tests/dreamservicetests/src/com/android/server/dreams/DreamServiceTest.java
index b98af6bc7dd0..b4e1abff3bf7 100644
--- a/services/tests/dreamservicetests/src/com/android/server/dreams/DreamServiceTest.java
+++ b/services/tests/dreamservicetests/src/com/android/server/dreams/DreamServiceTest.java
@@ -91,6 +91,28 @@ public class DreamServiceTest {
}
@Test
+ public void testMetadataParsing_nonexistentSettingsActivity()
+ throws PackageManager.NameNotFoundException {
+ final String testDreamClassName =
+ "com.android.server.dreams.TestDreamServiceWithNonexistentSettings";
+ final DreamService.DreamMetadata metadata = getDreamMetadata(testDreamClassName);
+
+ assertThat(metadata.settingsActivity).isNull();
+ assertThat(metadata.dreamCategory).isEqualTo(DreamService.DREAM_CATEGORY_DEFAULT);
+ }
+
+ @Test
+ public void testMetadataParsing_noPackage_nonexistentSettingsActivity()
+ throws PackageManager.NameNotFoundException {
+ final String testDreamClassName =
+ "com.android.server.dreams.TestDreamServiceNoPackageNonexistentSettings";
+ final DreamService.DreamMetadata metadata = getDreamMetadata(testDreamClassName);
+
+ assertThat(metadata.settingsActivity).isNull();
+ assertThat(metadata.dreamCategory).isEqualTo(DreamService.DREAM_CATEGORY_DEFAULT);
+ }
+
+ @Test
public void testMetadataParsing_exceptionReading() {
final PackageManager packageManager = Mockito.mock(PackageManager.class);
final ServiceInfo serviceInfo = Mockito.mock(ServiceInfo.class);
diff --git a/services/tests/dreamservicetests/src/com/android/server/dreams/TestDreamSettingsActivity.java b/services/tests/dreamservicetests/src/com/android/server/dreams/TestDreamSettingsActivity.java
new file mode 100644
index 000000000000..bb8db8a9b655
--- /dev/null
+++ b/services/tests/dreamservicetests/src/com/android/server/dreams/TestDreamSettingsActivity.java
@@ -0,0 +1,20 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.dreams;
+
+public class TestDreamSettingsActivity {
+}
diff --git a/tests/inputmethod/ConcurrentMultiSessionImeTest/src/com/android/server/inputmethod/multisessiontest/ConcurrentMultiUserTest.java b/tests/inputmethod/ConcurrentMultiSessionImeTest/src/com/android/server/inputmethod/multisessiontest/ConcurrentMultiUserTest.java
index 7aea993b1105..0db0d954d14d 100644
--- a/tests/inputmethod/ConcurrentMultiSessionImeTest/src/com/android/server/inputmethod/multisessiontest/ConcurrentMultiUserTest.java
+++ b/tests/inputmethod/ConcurrentMultiSessionImeTest/src/com/android/server/inputmethod/multisessiontest/ConcurrentMultiUserTest.java
@@ -95,7 +95,6 @@ public final class ConcurrentMultiUserTest {
}
}
- @Ignore("b/345557347")
@Test
public void driverShowImeNotAffectPassenger() {
assertDriverImeHidden();