diff options
15 files changed, 476 insertions, 207 deletions
diff --git a/core/java/android/content/res/Configuration.java b/core/java/android/content/res/Configuration.java index 29f21f14088e..3dedc4131251 100644 --- a/core/java/android/content/res/Configuration.java +++ b/core/java/android/content/res/Configuration.java @@ -751,9 +751,15 @@ public final class Configuration implements Parcelable, Comparable<Configuration public static final int SCREEN_WIDTH_DP_UNDEFINED = 0; /** - * The current width of the available screen space in dp units, excluding - * the area occupied by screen decorations at the edges of the display. - * Corresponds to the + * The width of the available screen space in dp units excluding the area + * occupied by {@link android.view.WindowInsets window insets}. + * + * <aside class="note"><b>Note:</b> The width measurement excludes window + * insets even when the app is displayed edge to edge using + * {@link android.view.Window#setDecorFitsSystemWindows(boolean) + * Window#setDecorFitsSystemWindows(boolean)}.</aside> + * + * <p>Corresponds to the * <a href="{@docRoot}guide/topics/resources/providing-resources.html#AvailableWidthHeightQualifier"> * available width</a> resource qualifier. Defaults to * {@link #SCREEN_WIDTH_DP_UNDEFINED} if no width is specified. @@ -763,21 +769,25 @@ public final class Configuration implements Parcelable, Comparable<Configuration * (for example, when apps are displayed side by side in split-screen mode * in landscape orientation). * + * <p>For embedded activities, equals the width of the individual + * activities, not the width of the app window or the device screen. + * * <p>In multiple-screen scenarios, the width measurement can span screens. * For example, if the app is spanning both screens of a dual-screen device * (with the screens side by side), {@code screenWidthDp} represents the - * width of both screens, excluding the area occupied by screen decorations. - * When the app is restricted to a single screen in a multiple-screen + * width of both screens excluding the area occupied by window insets. When + * the app is restricted to a single screen in a multiple-screen * environment, {@code screenWidthDp} is the width of the screen on which - * the app is running. + * the app is displayed excluding window insets. * * <p>Differs from {@link android.view.WindowMetrics} by not including - * screen decorations in the width measurement and by expressing the - * measurement in dp rather than px. Use {@code screenWidthDp} to obtain the - * horizontal display area available to the app, excluding the area occupied - * by screen decorations. Use {@link android.view.WindowMetrics#getBounds()} - * to obtain the width of the display area available to the app, including - * the area occupied by screen decorations. + * window insets in the width measurement and by expressing the measurement + * in dp rather than px. Use {@code screenWidthDp} to obtain the width of + * the display area available to an app or embedded activity excluding the + * area occupied by window insets. Use + * {@link android.view.WindowMetrics#getBounds()} to obtain the horizontal + * display area available to an app or embedded activity including the area + * occupied by window insets. */ public int screenWidthDp; @@ -788,9 +798,16 @@ public final class Configuration implements Parcelable, Comparable<Configuration public static final int SCREEN_HEIGHT_DP_UNDEFINED = 0; /** - * The current height of the available screen space in dp units, excluding - * the area occupied by screen decorations at the edges of the display (such - * as the status bar, navigation bar, and cutouts). Corresponds to the + * The height of the available screen space in dp units excluding the area + * occupied by {@link android.view.WindowInsets window insets}, such as the + * status bar, navigation bar, and cutouts. + * + * <aside class="note"><b>Note:</b> The height measurement excludes window + * insets even when the app is displayed edge to edge using + * {@link android.view.Window#setDecorFitsSystemWindows(boolean) + * Window#setDecorFitsSystemWindows(boolean)}.</aside> + * + * <p>Corresponds to the * <a href="{@docRoot}guide/topics/resources/providing-resources.html#AvailableWidthHeightQualifier"> * available height</a> resource qualifier. Defaults to * {@link #SCREEN_HEIGHT_DP_UNDEFINED} if no height is specified. @@ -800,22 +817,25 @@ public final class Configuration implements Parcelable, Comparable<Configuration * (for example, when apps are displayed one above another in split-screen * mode in portrait orientation). * + * <p>For embedded activities, equals the height of the individual + * activities, not the height of the app window or the device screen. + * * <p>In multiple-screen scenarios, the height measurement can span screens. * For example, if the app is spanning both screens of a dual-screen device * rotated 90 degrees (one screen above the other), {@code screenHeightDp} - * represents the height of both screens, excluding the area occupied by - * screen decorations. When the app is restricted to a single screen in a + * represents the height of both screens excluding the area occupied by + * window insets. When the app is restricted to a single screen in a * multiple-screen environment, {@code screenHeightDp} is the height of the - * screen on which the app is running. + * screen on which the app is displayed excluding window insets. * * <p>Differs from {@link android.view.WindowMetrics} by not including - * screen decorations in the height measurement and by expressing the - * measurement in dp rather than px. Use {@code screenHeightDp} to obtain - * the vertical display area available to the app, excluding the area - * occupied by screen decorations. Use - * {@link android.view.WindowMetrics#getBounds()} to obtain the height of - * the display area available to the app, including the area occupied by - * screen decorations. + * window insets in the height measurement and by expressing the measurement + * in dp rather than px. Use {@code screenHeightDp} to obtain the height of + * the display area available to an app or embedded activity excluding the + * area occupied by window insets. Use + * {@link android.view.WindowMetrics#getBounds()} to obtain the vertical + * display area available to an app or embedded activity including the area + * occupied by window insets. */ public int screenHeightDp; @@ -826,12 +846,12 @@ public final class Configuration implements Parcelable, Comparable<Configuration public static final int SMALLEST_SCREEN_WIDTH_DP_UNDEFINED = 0; /** - * The smallest screen size an application will see in normal operation, - * corresponding to - * <a href="{@docRoot}guide/topics/resources/providing-resources.html#SmallestScreenWidthQualifier">smallest - * screen width</a> resource qualifier. - * This is the smallest value of both screenWidthDp and screenHeightDp - * in both portrait and landscape. Set to + * The smallest screen size an application will see in normal operation. + * Corresponds to the + * <a href="{@docRoot}guide/topics/resources/providing-resources.html#SmallestScreenWidthQualifier"> + * smallest width</a> resource qualifier. This is the smallest value of + * {@link #screenWidthDp} and {@link #screenHeightDp} in both portrait and + * landscape orientations. Defaults to * {@link #SMALLEST_SCREEN_WIDTH_DP_UNDEFINED} if no width is specified. */ public int smallestScreenWidthDp; diff --git a/core/java/com/android/internal/app/chooser/DisplayResolveInfo.java b/core/java/com/android/internal/app/chooser/DisplayResolveInfo.java index 5ebc9154023c..96cc5e1bd7d2 100644 --- a/core/java/com/android/internal/app/chooser/DisplayResolveInfo.java +++ b/core/java/com/android/internal/app/chooser/DisplayResolveInfo.java @@ -35,7 +35,6 @@ import com.android.internal.app.ResolverActivity; import com.android.internal.app.ResolverListAdapter.ResolveInfoPresentationGetter; import java.util.ArrayList; -import java.util.Arrays; import java.util.List; /** @@ -206,7 +205,7 @@ public class DisplayResolveInfo implements TargetInfo, Parcelable { dest.writeCharSequence(mDisplayLabel); dest.writeCharSequence(mExtendedInfo); dest.writeParcelable(mResolvedIntent, 0); - dest.writeParcelableArray((Intent[]) mSourceIntents.toArray(), 0); + dest.writeTypedList(mSourceIntents); dest.writeBoolean(mIsSuspended); dest.writeBoolean(mPinned); dest.writeParcelable(mResolveInfo, 0); @@ -227,9 +226,7 @@ public class DisplayResolveInfo implements TargetInfo, Parcelable { mDisplayLabel = in.readCharSequence(); mExtendedInfo = in.readCharSequence(); mResolvedIntent = in.readParcelable(null /* ClassLoader */, android.content.Intent.class); - mSourceIntents.addAll( - Arrays.asList((Intent[]) in.readParcelableArray(null /* ClassLoader */, - Intent.class))); + in.readTypedList(mSourceIntents, Intent.CREATOR); mIsSuspended = in.readBoolean(); mPinned = in.readBoolean(); mResolveInfo = in.readParcelable(null /* ClassLoader */, android.content.pm.ResolveInfo.class); diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml index 45713babf034..66488415ff9f 100644 --- a/packages/SystemUI/res/values/dimens.xml +++ b/packages/SystemUI/res/values/dimens.xml @@ -386,7 +386,7 @@ <dimen name="split_shade_notifications_scrim_margin_bottom">0dp</dimen> - <dimen name="shelf_and_lock_icon_overlap">5dp</dimen> + <dimen name="shelf_and_lock_icon_overlap">@dimen/notification_shelf_height</dimen> <dimen name="notification_panel_margin_horizontal">0dp</dimen> 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 ba57d57d0fd3..270eb13c4224 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 @@ -1668,7 +1668,7 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable continue; } float childTop = slidingChild.getTranslationY(); - float top = childTop + slidingChild.getClipTopAmount(); + float top = childTop + Math.max(0, slidingChild.getClipTopAmount()); float bottom = childTop + slidingChild.getActualHeight() - slidingChild.getClipBottomAmount(); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackSizeCalculator.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackSizeCalculator.kt index 22234b1ef17d..ae854e2df91a 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackSizeCalculator.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackSizeCalculator.kt @@ -39,7 +39,11 @@ private const val TAG = "NotifStackSizeCalc" private val DEBUG = Compile.IS_DEBUG && Log.isLoggable(TAG, Log.DEBUG) private val SPEW = Compile.IS_DEBUG && Log.isLoggable(TAG, Log.VERBOSE) -/** Calculates number of notifications to display and the height of the notification stack. */ +/** + * Calculates number of notifications to display and the height of the notification stack. + * "Notifications" refers to any ExpandableView that we show on lockscreen, which can include the + * media player. + */ @SysUISingleton class NotificationStackSizeCalculator @Inject @@ -65,21 +69,60 @@ constructor( } /** - * Given the [totalAvailableSpace] constraint, calculates how many notification to show. - * - * This number is only valid in keyguard. + * Returns whether notifications and (shelf if visible) can fit in total space available. + * [spaceForShelf] is extra vertical space allowed for the shelf to overlap the lock icon. + */ + private fun canStackFitInSpace( + stackHeight: StackHeight, + spaceForNotifications: Float, + spaceForShelf: Float, + ): Boolean { + + val (notificationsHeight, shelfHeightWithSpaceBefore) = stackHeight + var canFit: Boolean + + if (shelfHeightWithSpaceBefore == 0f) { + canFit = notificationsHeight <= spaceForNotifications + log { + "canStackFitInSpace[$canFit] = notificationsHeight[$notificationsHeight]" + + " <= spaceForNotifications[$spaceForNotifications]" + } + } else { + canFit = + (notificationsHeight + shelfHeightWithSpaceBefore) <= + (spaceForNotifications + spaceForShelf) + log { + "canStackFitInSpace[$canFit] = (notificationsHeight[$notificationsHeight]" + + " + shelfHeightWithSpaceBefore[$shelfHeightWithSpaceBefore])" + + " <= (spaceForNotifications[$spaceForNotifications] " + + " + spaceForShelf[$spaceForShelf])" + } + } + return canFit + } + + /** + * Given the [spaceForNotifications] and [spaceForShelf] constraints, calculate how many + * notifications to show. This number is only valid in keyguard. * * @param totalAvailableSpace space for notifications. This includes the space for the shelf. */ fun computeMaxKeyguardNotifications( stack: NotificationStackScrollLayout, - totalAvailableSpace: Float, + spaceForNotifications: Float, + spaceForShelf: Float, shelfIntrinsicHeight: Float ): Int { + log { "\n" } val stackHeightSequence = computeHeightPerNotificationLimit(stack, shelfIntrinsicHeight) var maxNotifications = - stackHeightSequence.lastIndexWhile { stackHeight -> stackHeight <= totalAvailableSpace } + stackHeightSequence.lastIndexWhile { heightResult -> + canStackFitInSpace( + heightResult, + spaceForNotifications = spaceForNotifications, + spaceForShelf = spaceForShelf) + } if (onLockscreen()) { maxNotifications = min(maxKeyguardNotifications, maxNotifications) @@ -90,7 +133,8 @@ constructor( log { val sequence = if (SPEW) " stackHeightSequence=${stackHeightSequence.toList()}" else "" "computeMaxKeyguardNotifications(" + - "availableSpace=$totalAvailableSpace" + + " spaceForNotifications=$spaceForNotifications" + + " spaceForShelf=$spaceForShelf" + " shelfHeight=$shelfIntrinsicHeight) -> $maxNotifications$sequence" } return maxNotifications @@ -112,33 +156,51 @@ constructor( maxNotifications: Int, shelfIntrinsicHeight: Float ): Float { + log { "\n" } val heightPerMaxNotifications = computeHeightPerNotificationLimit(stack, shelfIntrinsicHeight) - val height = + + val (notificationsHeight, shelfHeightWithSpaceBefore) = heightPerMaxNotifications.elementAtOrElse(maxNotifications) { heightPerMaxNotifications.last() // Height with all notifications visible. } - log { "computeHeight(maxNotifications=$maxNotifications) -> $height" } - return height + log { + "computeHeight(maxNotifications=$maxNotifications," + + "shelfIntrinsicHeight=$shelfIntrinsicHeight) -> " + + "${notificationsHeight + shelfHeightWithSpaceBefore}" + + " = ($notificationsHeight + $shelfHeightWithSpaceBefore)" + } + return notificationsHeight + shelfHeightWithSpaceBefore } - /** The ith result in the sequence is the height with ith max notifications. */ + private data class StackHeight( + // Float height with ith max notifications (not including shelf) + val notificationsHeight: Float, + + // Float height of shelf (0 if shelf is not showing), and space before the shelf that + // changes during the lockscreen <=> full shade transition. + val shelfHeightWithSpaceBefore: Float + ) + private fun computeHeightPerNotificationLimit( stack: NotificationStackScrollLayout, - shelfIntrinsicHeight: Float - ): Sequence<Float> = sequence { + shelfHeight: Float + ): Sequence<StackHeight> = sequence { + log { "computeHeightPerNotificationLimit" } + val children = stack.showableChildren().toList() - var height = 0f + var notifications = 0f var previous: ExpandableView? = null val onLockscreen = onLockscreen() - yield(dividerHeight + shelfIntrinsicHeight) // Only shelf. + // Only shelf. This should never happen, since we allow 1 view minimum (EmptyViewState). + yield(StackHeight(notificationsHeight = 0f, shelfHeightWithSpaceBefore = shelfHeight)) children.forEachIndexed { i, currentNotification -> - height += spaceNeeded(currentNotification, i, previous, stack, onLockscreen) + notifications += spaceNeeded(currentNotification, i, previous, stack, onLockscreen) previous = currentNotification - val shelfHeight = + val shelfWithSpaceBefore = if (i == children.lastIndex) { 0f // No shelf needed. } else { @@ -149,10 +211,16 @@ constructor( previous = currentNotification, current = children[firstViewInShelfIndex], currentIndex = firstViewInShelfIndex) - spaceBeforeShelf + shelfIntrinsicHeight + spaceBeforeShelf + shelfHeight } - - yield(height + shelfHeight) + log { + "i=$i notificationsHeight=$notifications " + + "shelfHeightWithSpaceBefore=$shelfWithSpaceBefore" + } + yield( + StackHeight( + notificationsHeight = notifications, + shelfHeightWithSpaceBefore = shelfWithSpaceBefore)) } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java index c521161e85f2..ba1088fba6ed 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java @@ -1520,50 +1520,77 @@ public class NotificationPanelViewController extends PanelViewController { return (mQs != null ? mQs.getHeader().getHeight() : 0) + mQsPeekHeight; } - /** - * @return Space available to show notifications on lockscreen. - */ - @VisibleForTesting - float getSpaceForLockscreenNotifications() { - float staticTopPadding = mClockPositionAlgorithm.getLockscreenMinStackScrollerPadding() - // getMinStackScrollerPadding is from the top of the screen, - // but we need it from the top of the NSSL. - - mNotificationStackScrollLayoutController.getTop(); - - // Space between bottom of notifications and top of lock icon or udfps background. - float lockIconPadding = mLockIconViewController.getTop(); - if (mLockIconViewController.getTop() != 0) { + /** Returns space between top of lock icon and bottom of NotificationStackScrollLayout. */ + private float getLockIconPadding() { + float lockIconPadding = 0f; + if (mLockIconViewController.getTop() != 0f) { lockIconPadding = mNotificationStackScrollLayoutController.getBottom() - - mLockIconViewController.getTop() - - mShelfAndLockIconOverlap; + - mLockIconViewController.getTop(); } + return lockIconPadding; + } + + /** Returns space available to show notifications on lockscreen. */ + @VisibleForTesting + float getVerticalSpaceForLockscreenNotifications() { + final float lockIconPadding = getLockIconPadding(); float bottomPadding = Math.max(lockIconPadding, Math.max(mIndicationBottomPadding, mAmbientIndicationBottomPadding)); - mKeyguardNotificationBottomPadding = bottomPadding; + + float staticTopPadding = mClockPositionAlgorithm.getLockscreenMinStackScrollerPadding() + // getMinStackScrollerPadding is from the top of the screen, + // but we need it from the top of the NSSL. + - mNotificationStackScrollLayoutController.getTop(); mKeyguardNotificationTopPadding = staticTopPadding; // To debug the available space, enable debug lines in this class. If you change how the // available space is calculated, please also update those lines. - float availableSpace = + final float verticalSpace = mNotificationStackScrollLayoutController.getHeight() - staticTopPadding - bottomPadding; if (SPEW_LOGCAT) { - Log.d(TAG, "getSpaceForLockscreenNotifications()" - + " availableSpace=" + availableSpace - + " NSSL.height=" + mNotificationStackScrollLayoutController.getHeight() - + " NSSL.top=" + mNotificationStackScrollLayoutController.getTop() - + " staticTopPadding=" + staticTopPadding - + " bottomPadding=" + bottomPadding - + " lockIconPadding=" + lockIconPadding - + " mIndicationBottomPadding=" + mIndicationBottomPadding - + " mAmbientIndicationBottomPadding=" + mAmbientIndicationBottomPadding + Log.i(TAG, "\n"); + Log.i(TAG, "staticTopPadding[" + staticTopPadding + + "] = Clock.padding[" + + mClockPositionAlgorithm.getLockscreenMinStackScrollerPadding() + + "] - NSSLC.top[" + mNotificationStackScrollLayoutController.getTop() + + "]" + ); + Log.i(TAG, "bottomPadding[" + bottomPadding + + "] = max(ambientIndicationBottomPadding[" + mAmbientIndicationBottomPadding + + "], mIndicationBottomPadding[" + mIndicationBottomPadding + + "], lockIconPadding[" + lockIconPadding + + "])" + ); + Log.i(TAG, "verticalSpaceForNotifications[" + verticalSpace + + "] = NSSL.height[" + mNotificationStackScrollLayoutController.getHeight() + + "] - staticTopPadding[" + staticTopPadding + + "] - bottomPadding[" + bottomPadding + + "]" ); } - return availableSpace; + return verticalSpace; + } + + /** Returns extra space available to show the shelf on lockscreen */ + @VisibleForTesting + float getVerticalSpaceForLockscreenShelf() { + final float lockIconPadding = getLockIconPadding(); + + final float noShelfOverlapBottomPadding = + Math.max(mIndicationBottomPadding, mAmbientIndicationBottomPadding); + + final float extraSpaceForShelf = lockIconPadding - noShelfOverlapBottomPadding; + + if (extraSpaceForShelf > 0f) { + return Math.min(mNotificationShelfController.getIntrinsicHeight(), + extraSpaceForShelf); + } + return 0f; } /** @@ -1579,16 +1606,12 @@ public class NotificationPanelViewController extends PanelViewController { } return mMaxAllowedKeyguardNotifications; } - - final float shelfIntrinsicHeight = - mNotificationShelfController.getVisibility() == View.GONE - ? 0 - : mNotificationShelfController.getIntrinsicHeight(); - return mNotificationStackSizeCalculator.computeMaxKeyguardNotifications( mNotificationStackScrollLayoutController.getView(), - getSpaceForLockscreenNotifications(), - shelfIntrinsicHeight); + getVerticalSpaceForLockscreenNotifications(), + getVerticalSpaceForLockscreenShelf(), + mNotificationShelfController.getIntrinsicHeight() + ); } private void updateClock() { @@ -3812,7 +3835,7 @@ public class NotificationPanelViewController extends PanelViewController { public void setAmbientIndicationTop(int ambientIndicationTop, boolean ambientTextVisible) { int ambientIndicationBottomPadding = 0; if (ambientTextVisible) { - int stackBottom = mNotificationStackScrollLayoutController.getView().getBottom(); + int stackBottom = mNotificationStackScrollLayoutController.getBottom(); ambientIndicationBottomPadding = stackBottom - ambientIndicationTop; } if (mAmbientIndicationBottomPadding != ambientIndicationBottomPadding) { diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackSizeCalculatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackSizeCalculatorTest.kt index ef680210e3fe..55dae9d04019 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackSizeCalculatorTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackSizeCalculatorTest.kt @@ -46,7 +46,8 @@ import org.mockito.MockitoAnnotations class NotificationStackSizeCalculatorTest : SysuiTestCase() { @Mock private lateinit var sysuiStatusBarStateController: SysuiStatusBarStateController - @Mock private lateinit var lockscreenShadeTransitionController: LockscreenShadeTransitionController + @Mock + private lateinit var lockscreenShadeTransitionController: LockscreenShadeTransitionController @Mock private lateinit var stackLayout: NotificationStackScrollLayout private val testableResources = mContext.orCreateTestableResources @@ -74,7 +75,8 @@ class NotificationStackSizeCalculatorTest : SysuiTestCase() { val rows = listOf(createMockRow(height = rowHeight)) val maxNotifications = - computeMaxKeyguardNotifications(rows, availableSpace = 0f, shelfHeight = 0f) + computeMaxKeyguardNotifications( + rows, spaceForNotifications = 0f, spaceForShelf = 0f, shelfHeight = 0f) assertThat(maxNotifications).isEqualTo(0) } @@ -84,7 +86,12 @@ class NotificationStackSizeCalculatorTest : SysuiTestCase() { val numberOfRows = 30 val rows = createLockscreenRows(numberOfRows) - val maxNotifications = computeMaxKeyguardNotifications(rows, Float.MAX_VALUE) + val maxNotifications = + computeMaxKeyguardNotifications( + rows, + spaceForNotifications = Float.MAX_VALUE, + spaceForShelf = Float.MAX_VALUE, + shelfHeight) assertThat(maxNotifications).isEqualTo(numberOfRows) } @@ -93,11 +100,12 @@ class NotificationStackSizeCalculatorTest : SysuiTestCase() { fun computeMaxKeyguardNotifications_spaceForOneAndShelf_returnsOne() { setGapHeight(gapHeight) val shelfHeight = rowHeight / 2 // Shelf absence won't leave room for another row. - val availableSpace = - listOf(rowHeight + dividerHeight, gapHeight + dividerHeight + shelfHeight).sum() + val spaceForNotifications = rowHeight + dividerHeight + val spaceForShelf = gapHeight + dividerHeight + shelfHeight val rows = listOf(createMockRow(rowHeight), createMockRow(rowHeight)) - val maxNotifications = computeMaxKeyguardNotifications(rows, availableSpace, shelfHeight) + val maxNotifications = + computeMaxKeyguardNotifications(rows, spaceForNotifications, spaceForShelf, shelfHeight) assertThat(maxNotifications).isEqualTo(1) } @@ -106,16 +114,19 @@ class NotificationStackSizeCalculatorTest : SysuiTestCase() { fun computeMaxKeyguardNotifications_spaceForTwo_returnsTwo() { setGapHeight(gapHeight) val shelfHeight = shelfHeight + dividerHeight - val availableSpace = + val spaceForNotifications = listOf( rowHeight + dividerHeight, gapHeight + rowHeight + dividerHeight, - gapHeight + dividerHeight + shelfHeight) + ) .sum() + val spaceForShelf = gapHeight + dividerHeight + shelfHeight val rows = listOf(createMockRow(rowHeight), createMockRow(rowHeight), createMockRow(rowHeight)) - val maxNotifications = computeMaxKeyguardNotifications(rows, availableSpace, shelfHeight) + val maxNotifications = + computeMaxKeyguardNotifications( + rows, spaceForNotifications + 1, spaceForShelf, shelfHeight) assertThat(maxNotifications).isEqualTo(2) } @@ -124,19 +135,25 @@ class NotificationStackSizeCalculatorTest : SysuiTestCase() { fun computeHeight_gapBeforeShelf_returnsSpaceUsed() { // Each row in separate section. setGapHeight(gapHeight) - val spaceUsed = - listOf(rowHeight, + + val spaceForNotifications = + listOf( + rowHeight, dividerHeight + gapHeight + rowHeight, - dividerHeight + gapHeight + shelfHeight) + ) .sum() - val availableSpace = spaceUsed + 1; + + val spaceForShelf = dividerHeight + gapHeight + shelfHeight + val spaceUsed = spaceForNotifications + spaceForShelf val rows = listOf(createMockRow(rowHeight), createMockRow(rowHeight), createMockRow(rowHeight)) - val maxNotifications = computeMaxKeyguardNotifications(rows, availableSpace, shelfHeight) + val maxNotifications = + computeMaxKeyguardNotifications(rows, spaceForNotifications, spaceForShelf, shelfHeight) assertThat(maxNotifications).isEqualTo(2) - val height = sizeCalculator.computeHeight(stackLayout, maxNotifications, this.shelfHeight) + val height = + sizeCalculator.computeHeight(stackLayout, maxNotifications, this.shelfHeight) assertThat(height).isEqualTo(spaceUsed) } @@ -145,19 +162,19 @@ class NotificationStackSizeCalculatorTest : SysuiTestCase() { // Both rows are in the same section. setGapHeight(0f) - val rowHeight = rowHeight - val shelfHeight = shelfHeight - val spaceUsed = - listOf(rowHeight, - dividerHeight + shelfHeight) - .sum() - val availableSpace = spaceUsed + 1 + val spaceForNotifications = rowHeight + val spaceForShelf = dividerHeight + shelfHeight + val spaceUsed = spaceForNotifications + spaceForShelf val rows = listOf(createMockRow(rowHeight), createMockRow(rowHeight)) - val maxNotifications = computeMaxKeyguardNotifications(rows, availableSpace, shelfHeight) + // test that we only use space required + val maxNotifications = + computeMaxKeyguardNotifications( + rows, spaceForNotifications + 1, spaceForShelf, shelfHeight) assertThat(maxNotifications).isEqualTo(1) - val height = sizeCalculator.computeHeight(stackLayout, maxNotifications, this.shelfHeight) + val height = + sizeCalculator.computeHeight(stackLayout, maxNotifications, this.shelfHeight) assertThat(height).isEqualTo(spaceUsed) } @@ -191,8 +208,13 @@ class NotificationStackSizeCalculatorTest : SysuiTestCase() { whenever(expandableView.getMinHeight(any())).thenReturn(5) whenever(expandableView.intrinsicHeight).thenReturn(10) - val space = sizeCalculator.spaceNeeded(expandableView, visibleIndex = 0, - previousView = null, stack = stackLayout, onLockscreen = true) + val space = + sizeCalculator.spaceNeeded( + expandableView, + visibleIndex = 0, + previousView = null, + stack = stackLayout, + onLockscreen = true) assertThat(space).isEqualTo(5) } @@ -205,19 +227,25 @@ class NotificationStackSizeCalculatorTest : SysuiTestCase() { whenever(expandableView.getMinHeight(any())).thenReturn(5) whenever(expandableView.intrinsicHeight).thenReturn(10) - val space = sizeCalculator.spaceNeeded(expandableView, visibleIndex = 0, - previousView = null, stack = stackLayout, onLockscreen = false) + val space = + sizeCalculator.spaceNeeded( + expandableView, + visibleIndex = 0, + previousView = null, + stack = stackLayout, + onLockscreen = false) assertThat(space).isEqualTo(10) } private fun computeMaxKeyguardNotifications( rows: List<ExpandableView>, - availableSpace: Float, + spaceForNotifications: Float, + spaceForShelf: Float, shelfHeight: Float = this.shelfHeight ): Int { setupChildren(rows) return sizeCalculator.computeMaxKeyguardNotifications( - stackLayout, availableSpace, shelfHeight) + stackLayout, spaceForNotifications, spaceForShelf, shelfHeight) } private fun setupChildren(children: List<ExpandableView>) { diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationPanelViewControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationPanelViewControllerTest.java index 62058a179dac..018c4531b382 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationPanelViewControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationPanelViewControllerTest.java @@ -583,7 +583,7 @@ public class NotificationPanelViewControllerTest extends SysuiTestCase { listener.onHeightChanged(mock(ExpandableView.class), false); verify(mNotificationStackSizeCalculator) - .computeMaxKeyguardNotifications(any(), anyFloat(), anyFloat()); + .computeMaxKeyguardNotifications(any(), anyFloat(), anyFloat(), anyFloat()); } @Test @@ -599,7 +599,7 @@ public class NotificationPanelViewControllerTest extends SysuiTestCase { listener.onHeightChanged(mock(ExpandableView.class), false); verify(mNotificationStackSizeCalculator, never()) - .computeMaxKeyguardNotifications(any(), anyFloat(), anyFloat()); + .computeMaxKeyguardNotifications(any(), anyFloat(), anyFloat(), anyFloat()); } @Test @@ -622,25 +622,102 @@ public class NotificationPanelViewControllerTest extends SysuiTestCase { .isNotEqualTo(-1); } - @Test - public void getLockscreenSpaceForNotifications_includesOverlapWithLockIcon() { + private void setBottomPadding(int stackBottom, int lockIconPadding, int indicationPadding, + int ambientPadding) { + + when(mNotificationStackScrollLayoutController.getTop()).thenReturn(0); + when(mNotificationStackScrollLayoutController.getHeight()).thenReturn(stackBottom); + when(mNotificationStackScrollLayoutController.getBottom()).thenReturn(stackBottom); + when(mLockIconViewController.getTop()).thenReturn((float) (stackBottom - lockIconPadding)); + when(mResources.getDimensionPixelSize(R.dimen.keyguard_indication_bottom_padding)) - .thenReturn(0); + .thenReturn(indicationPadding); + mNotificationPanelViewController.loadDimens(); + mNotificationPanelViewController.setAmbientIndicationTop( - /* ambientIndicationTop= */ 0, /* ambientTextVisible */ false); + /* ambientIndicationTop= */ stackBottom - ambientPadding, + /* ambientTextVisible= */ true); + } - // Use lock icon padding (100 - 80 - 5 = 15) as bottom padding - when(mNotificationStackScrollLayoutController.getBottom()).thenReturn(100); - when(mLockIconViewController.getTop()).thenReturn(80f); - when(mResources.getDimensionPixelSize(R.dimen.shelf_and_lock_icon_overlap)).thenReturn(5); + @Test + public void getVerticalSpaceForLockscreenNotifications_useLockIconBottomPadding_returnsSpaceAvailable() { + setBottomPadding(/* stackScrollLayoutBottom= */ 100, + /* lockIconPadding= */ 20, + /* indicationPadding= */ 0, + /* ambientPadding= */ 0); + + assertThat(mNotificationPanelViewController.getVerticalSpaceForLockscreenNotifications()) + .isEqualTo(80); + } - // Available space (100 - 0 - 15 = 85) - when(mNotificationStackScrollLayoutController.getHeight()).thenReturn(100); - when(mNotificationStackScrollLayoutController.getTop()).thenReturn(0); - mNotificationPanelViewController.updateResources(); + @Test + public void getVerticalSpaceForLockscreenNotifications_useIndicationBottomPadding_returnsSpaceAvailable() { + setBottomPadding(/* stackScrollLayoutBottom= */ 100, + /* lockIconPadding= */ 0, + /* indicationPadding= */ 30, + /* ambientPadding= */ 0); + + assertThat(mNotificationPanelViewController.getVerticalSpaceForLockscreenNotifications()) + .isEqualTo(70); + } - assertThat(mNotificationPanelViewController.getSpaceForLockscreenNotifications()) - .isEqualTo(85); + @Test + public void getVerticalSpaceForLockscreenNotifications_useAmbientBottomPadding_returnsSpaceAvailable() { + setBottomPadding(/* stackScrollLayoutBottom= */ 100, + /* lockIconPadding= */ 0, + /* indicationPadding= */ 0, + /* ambientPadding= */ 40); + + assertThat(mNotificationPanelViewController.getVerticalSpaceForLockscreenNotifications()) + .isEqualTo(60); + } + + @Test + public void getVerticalSpaceForLockscreenShelf_useLockIconBottomPadding_returnsShelfHeight() { + setBottomPadding(/* stackScrollLayoutBottom= */ 100, + /* lockIconPadding= */ 20, + /* indicationPadding= */ 0, + /* ambientPadding= */ 0); + + when(mNotificationShelfController.getIntrinsicHeight()).thenReturn(5); + assertThat(mNotificationPanelViewController.getVerticalSpaceForLockscreenShelf()) + .isEqualTo(5); + } + + @Test + public void getVerticalSpaceForLockscreenShelf_useIndicationBottomPadding_returnsZero() { + setBottomPadding(/* stackScrollLayoutBottom= */ 100, + /* lockIconPadding= */ 0, + /* indicationPadding= */ 30, + /* ambientPadding= */ 0); + + when(mNotificationShelfController.getIntrinsicHeight()).thenReturn(5); + assertThat(mNotificationPanelViewController.getVerticalSpaceForLockscreenShelf()) + .isEqualTo(0); + } + + @Test + public void getVerticalSpaceForLockscreenShelf_useAmbientBottomPadding_returnsZero() { + setBottomPadding(/* stackScrollLayoutBottom= */ 100, + /* lockIconPadding= */ 0, + /* indicationPadding= */ 0, + /* ambientPadding= */ 40); + + when(mNotificationShelfController.getIntrinsicHeight()).thenReturn(5); + assertThat(mNotificationPanelViewController.getVerticalSpaceForLockscreenShelf()) + .isEqualTo(0); + } + + @Test + public void getVerticalSpaceForLockscreenShelf_useLockIconPadding_returnsLessThanShelfHeight() { + setBottomPadding(/* stackScrollLayoutBottom= */ 100, + /* lockIconPadding= */ 10, + /* indicationPadding= */ 8, + /* ambientPadding= */ 0); + + when(mNotificationShelfController.getIntrinsicHeight()).thenReturn(5); + assertThat(mNotificationPanelViewController.getVerticalSpaceForLockscreenShelf()) + .isEqualTo(2); } @Test diff --git a/services/core/java/com/android/server/UiModeManagerService.java b/services/core/java/com/android/server/UiModeManagerService.java index e68a0a6acaf6..c853ba93f4ab 100644 --- a/services/core/java/com/android/server/UiModeManagerService.java +++ b/services/core/java/com/android/server/UiModeManagerService.java @@ -635,7 +635,12 @@ final class UiModeManagerService extends SystemService { + "permission ENTER_CAR_MODE_PRIORITIZED"); } - assertLegit(callingPackage); + // Allow the user to enable car mode using the shell, + // e.g. 'adb shell cmd uimode car yes' + boolean isShellCaller = mInjector.getCallingUid() == Process.SHELL_UID; + if (!isShellCaller) { + assertLegit(callingPackage); + } final long ident = Binder.clearCallingIdentity(); try { @@ -676,8 +681,13 @@ final class UiModeManagerService extends SystemService { // If the caller is the system, we will allow the DISABLE_CAR_MODE_ALL_PRIORITIES car // mode flag to be specified; this is so that the user can disable car mode at all // priorities using the persistent notification. - boolean isSystemCaller = mInjector.getCallingUid() == Process.SYSTEM_UID; - if (!isSystemCaller) { + // + // We also allow the user to disable car mode using the shell, + // e.g. 'adb shell cmd uimode car no' + int callingUid = mInjector.getCallingUid(); + boolean isSystemCaller = callingUid == Process.SYSTEM_UID; + boolean isShellCaller = callingUid == Process.SHELL_UID; + if (!isSystemCaller && !isShellCaller) { assertLegit(callingPackage); } final int carModeFlags = diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerInternal.java b/services/core/java/com/android/server/wm/ActivityTaskManagerInternal.java index f75d73b04476..8e7dde20955b 100644 --- a/services/core/java/com/android/server/wm/ActivityTaskManagerInternal.java +++ b/services/core/java/com/android/server/wm/ActivityTaskManagerInternal.java @@ -338,14 +338,16 @@ public abstract class ActivityTaskManagerInternal { private final @NonNull IBinder mAssistToken; private final @NonNull IBinder mShareableActivityToken; private final @NonNull IApplicationThread mAppThread; + private final int mUid; public ActivityTokens(@NonNull IBinder activityToken, @NonNull IBinder assistToken, @NonNull IApplicationThread appThread, - @NonNull IBinder shareableActivityToken) { + @NonNull IBinder shareableActivityToken, int uid) { mActivityToken = activityToken; mAssistToken = assistToken; mAppThread = appThread; mShareableActivityToken = shareableActivityToken; + mUid = uid; } /** @@ -375,6 +377,13 @@ public abstract class ActivityTaskManagerInternal { public @NonNull IApplicationThread getApplicationThread() { return mAppThread; } + + /** + * @return The UID of the activity + */ + public int getUid() { + return mUid; + } } public abstract void sendActivityResult(int callingUid, IBinder activityToken, diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java index b8162cd3d008..aa154292fe7e 100644 --- a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java +++ b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java @@ -5820,14 +5820,16 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub { if (token == null && list.get(0).attachedToProcess()) { ActivityRecord topRecord = list.get(0); return new ActivityTokens(topRecord.token, topRecord.assistToken, - topRecord.app.getThread(), topRecord.shareableActivityToken); + topRecord.app.getThread(), topRecord.shareableActivityToken, + topRecord.getUid()); } // find the expected Activity for (int i = 0; i < list.size(); i++) { ActivityRecord record = list.get(i); if (record.shareableActivityToken == token && record.attachedToProcess()) { return new ActivityTokens(record.token, record.assistToken, - record.app.getThread(), record.shareableActivityToken); + record.app.getThread(), record.shareableActivityToken, + record.getUid()); } } return null; diff --git a/services/tests/uiservicestests/src/com/android/server/UiModeManagerServiceTest.java b/services/tests/uiservicestests/src/com/android/server/UiModeManagerServiceTest.java index de81d6b7f443..aee27554b023 100644 --- a/services/tests/uiservicestests/src/com/android/server/UiModeManagerServiceTest.java +++ b/services/tests/uiservicestests/src/com/android/server/UiModeManagerServiceTest.java @@ -75,6 +75,7 @@ import android.content.res.Configuration; import android.content.res.Resources; import android.os.Handler; import android.os.IBinder; +import android.os.Process; import android.os.PowerManager; import android.os.PowerManagerInternal; import android.os.PowerSaveState; @@ -885,7 +886,7 @@ public class UiModeManagerServiceTest extends UiServiceTestCase { @Test public void requestProjection_failsForBogusPackageName() throws Exception { when(mPackageManager.getPackageUidAsUser(eq(PACKAGE_NAME), anyInt())) - .thenReturn(TestInjector.CALLING_UID + 1); + .thenReturn(TestInjector.DEFAULT_CALLING_UID + 1); assertThrows(SecurityException.class, () -> mService.requestProjection(mBinder, PROJECTION_TYPE_AUTOMOTIVE, PACKAGE_NAME)); @@ -905,7 +906,7 @@ public class UiModeManagerServiceTest extends UiServiceTestCase { @Test public void requestProjection_failsIfNoProjectionTypes() throws Exception { when(mPackageManager.getPackageUidAsUser(eq(PACKAGE_NAME), anyInt())) - .thenReturn(TestInjector.CALLING_UID); + .thenReturn(TestInjector.DEFAULT_CALLING_UID); assertThrows(IllegalArgumentException.class, () -> mService.requestProjection(mBinder, PROJECTION_TYPE_NONE, PACKAGE_NAME)); @@ -918,7 +919,7 @@ public class UiModeManagerServiceTest extends UiServiceTestCase { @Test public void requestProjection_failsIfMultipleProjectionTypes() throws Exception { when(mPackageManager.getPackageUidAsUser(eq(PACKAGE_NAME), anyInt())) - .thenReturn(TestInjector.CALLING_UID); + .thenReturn(TestInjector.DEFAULT_CALLING_UID); // Don't use PROJECTION_TYPE_ALL because that's actually == -1 and will fail the > 0 check. int multipleProjectionTypes = PROJECTION_TYPE_AUTOMOTIVE | 0x0002 | 0x0004; @@ -944,13 +945,13 @@ public class UiModeManagerServiceTest extends UiServiceTestCase { @Test public void requestProjection_automotive_failsIfAlreadySetByOtherPackage() throws Exception { when(mPackageManager.getPackageUidAsUser(eq(PACKAGE_NAME), anyInt())) - .thenReturn(TestInjector.CALLING_UID); + .thenReturn(TestInjector.DEFAULT_CALLING_UID); mService.requestProjection(mBinder, PROJECTION_TYPE_AUTOMOTIVE, PACKAGE_NAME); assertEquals(PROJECTION_TYPE_AUTOMOTIVE, mService.getActiveProjectionTypes()); String otherPackage = "Raconteurs"; when(mPackageManager.getPackageUidAsUser(eq(otherPackage), anyInt())) - .thenReturn(TestInjector.CALLING_UID); + .thenReturn(TestInjector.DEFAULT_CALLING_UID); assertFalse(mService.requestProjection(mBinder, PROJECTION_TYPE_AUTOMOTIVE, otherPackage)); assertThat(mService.getProjectingPackages(PROJECTION_TYPE_AUTOMOTIVE), contains(PACKAGE_NAME)); @@ -959,7 +960,7 @@ public class UiModeManagerServiceTest extends UiServiceTestCase { @Test public void requestProjection_failsIfCannotLinkToDeath() throws Exception { when(mPackageManager.getPackageUidAsUser(eq(PACKAGE_NAME), anyInt())) - .thenReturn(TestInjector.CALLING_UID); + .thenReturn(TestInjector.DEFAULT_CALLING_UID); doThrow(new RemoteException()).when(mBinder).linkToDeath(any(), anyInt()); assertFalse(mService.requestProjection(mBinder, PROJECTION_TYPE_AUTOMOTIVE, PACKAGE_NAME)); @@ -969,7 +970,7 @@ public class UiModeManagerServiceTest extends UiServiceTestCase { @Test public void requestProjection() throws Exception { when(mPackageManager.getPackageUidAsUser(eq(PACKAGE_NAME), anyInt())) - .thenReturn(TestInjector.CALLING_UID); + .thenReturn(TestInjector.DEFAULT_CALLING_UID); // Should work for all powers of two. for (int i = 0; i < Integer.SIZE; ++i) { int projectionType = 1 << i; @@ -985,12 +986,12 @@ public class UiModeManagerServiceTest extends UiServiceTestCase { @Test public void releaseProjection_failsForBogusPackageName() throws Exception { when(mPackageManager.getPackageUidAsUser(eq(PACKAGE_NAME), anyInt())) - .thenReturn(TestInjector.CALLING_UID); + .thenReturn(TestInjector.DEFAULT_CALLING_UID); mService.requestProjection(mBinder, PROJECTION_TYPE_AUTOMOTIVE, PACKAGE_NAME); assertEquals(PROJECTION_TYPE_AUTOMOTIVE, mService.getActiveProjectionTypes()); when(mPackageManager.getPackageUidAsUser(eq(PACKAGE_NAME), anyInt())) - .thenReturn(TestInjector.CALLING_UID + 1); + .thenReturn(TestInjector.DEFAULT_CALLING_UID + 1); assertThrows(SecurityException.class, () -> mService.releaseProjection( PROJECTION_TYPE_AUTOMOTIVE, PACKAGE_NAME)); @@ -1000,7 +1001,7 @@ public class UiModeManagerServiceTest extends UiServiceTestCase { @Test public void releaseProjection_failsIfNameNotFound() throws Exception { when(mPackageManager.getPackageUidAsUser(eq(PACKAGE_NAME), anyInt())) - .thenReturn(TestInjector.CALLING_UID); + .thenReturn(TestInjector.DEFAULT_CALLING_UID); mService.requestProjection(mBinder, PROJECTION_TYPE_AUTOMOTIVE, PACKAGE_NAME); assertEquals(PROJECTION_TYPE_AUTOMOTIVE, mService.getActiveProjectionTypes()); when(mPackageManager.getPackageUidAsUser(eq(PACKAGE_NAME), anyInt())) @@ -1014,7 +1015,7 @@ public class UiModeManagerServiceTest extends UiServiceTestCase { @Test public void releaseProjection_enforcesToggleAutomotiveProjectionPermission() throws Exception { when(mPackageManager.getPackageUidAsUser(eq(PACKAGE_NAME), anyInt())) - .thenReturn(TestInjector.CALLING_UID); + .thenReturn(TestInjector.DEFAULT_CALLING_UID); mService.requestProjection(mBinder, PROJECTION_TYPE_AUTOMOTIVE, PACKAGE_NAME); assertEquals(PROJECTION_TYPE_AUTOMOTIVE, mService.getActiveProjectionTypes()); doThrow(new SecurityException()).when(mContext).enforceCallingPermission( @@ -1033,7 +1034,7 @@ public class UiModeManagerServiceTest extends UiServiceTestCase { @Test public void releaseProjection() throws Exception { when(mPackageManager.getPackageUidAsUser(eq(PACKAGE_NAME), anyInt())) - .thenReturn(TestInjector.CALLING_UID); + .thenReturn(TestInjector.DEFAULT_CALLING_UID); requestAllPossibleProjectionTypes(); assertEquals(PROJECTION_TYPE_ALL, mService.getActiveProjectionTypes()); @@ -1053,7 +1054,7 @@ public class UiModeManagerServiceTest extends UiServiceTestCase { @Test public void binderDeath_releasesProjection() throws Exception { when(mPackageManager.getPackageUidAsUser(eq(PACKAGE_NAME), anyInt())) - .thenReturn(TestInjector.CALLING_UID); + .thenReturn(TestInjector.DEFAULT_CALLING_UID); requestAllPossibleProjectionTypes(); assertEquals(PROJECTION_TYPE_ALL, mService.getActiveProjectionTypes()); ArgumentCaptor<IBinder.DeathRecipient> deathRecipientCaptor = ArgumentCaptor.forClass( @@ -1069,7 +1070,7 @@ public class UiModeManagerServiceTest extends UiServiceTestCase { public void getActiveProjectionTypes() throws Exception { assertEquals(PROJECTION_TYPE_NONE, mService.getActiveProjectionTypes()); when(mPackageManager.getPackageUidAsUser(eq(PACKAGE_NAME), anyInt())) - .thenReturn(TestInjector.CALLING_UID); + .thenReturn(TestInjector.DEFAULT_CALLING_UID); mService.requestProjection(mBinder, PROJECTION_TYPE_AUTOMOTIVE, PACKAGE_NAME); assertEquals(PROJECTION_TYPE_AUTOMOTIVE, mService.getActiveProjectionTypes()); mService.releaseProjection(PROJECTION_TYPE_AUTOMOTIVE, PACKAGE_NAME); @@ -1080,7 +1081,7 @@ public class UiModeManagerServiceTest extends UiServiceTestCase { public void getProjectingPackages() throws Exception { assertTrue(mService.getProjectingPackages(PROJECTION_TYPE_ALL).isEmpty()); when(mPackageManager.getPackageUidAsUser(eq(PACKAGE_NAME), anyInt())) - .thenReturn(TestInjector.CALLING_UID); + .thenReturn(TestInjector.DEFAULT_CALLING_UID); mService.requestProjection(mBinder, PROJECTION_TYPE_AUTOMOTIVE, PACKAGE_NAME); assertEquals(1, mService.getProjectingPackages(PROJECTION_TYPE_AUTOMOTIVE).size()); assertEquals(1, mService.getProjectingPackages(PROJECTION_TYPE_ALL).size()); @@ -1105,7 +1106,7 @@ public class UiModeManagerServiceTest extends UiServiceTestCase { public void addOnProjectionStateChangedListener_callsListenerIfProjectionActive() throws Exception { when(mPackageManager.getPackageUidAsUser(eq(PACKAGE_NAME), anyInt())) - .thenReturn(TestInjector.CALLING_UID); + .thenReturn(TestInjector.DEFAULT_CALLING_UID); mService.requestProjection(mBinder, PROJECTION_TYPE_AUTOMOTIVE, PACKAGE_NAME); assertEquals(PROJECTION_TYPE_AUTOMOTIVE, mService.getActiveProjectionTypes()); @@ -1135,7 +1136,7 @@ public class UiModeManagerServiceTest extends UiServiceTestCase { mService.removeOnProjectionStateChangedListener(listener); // Now set automotive projection, should not call back. when(mPackageManager.getPackageUidAsUser(eq(PACKAGE_NAME), anyInt())) - .thenReturn(TestInjector.CALLING_UID); + .thenReturn(TestInjector.DEFAULT_CALLING_UID); mService.requestProjection(mBinder, PROJECTION_TYPE_AUTOMOTIVE, PACKAGE_NAME); verify(listener, never()).onProjectionStateChanged(anyInt(), any()); } @@ -1152,7 +1153,7 @@ public class UiModeManagerServiceTest extends UiServiceTestCase { // Now set automotive projection, should call back. when(mPackageManager.getPackageUidAsUser(eq(PACKAGE_NAME), anyInt())) - .thenReturn(TestInjector.CALLING_UID); + .thenReturn(TestInjector.DEFAULT_CALLING_UID); mService.requestProjection(mBinder, PROJECTION_TYPE_AUTOMOTIVE, PACKAGE_NAME); verify(listener).onProjectionStateChanged(eq(PROJECTION_TYPE_AUTOMOTIVE), eq(List.of(PACKAGE_NAME))); @@ -1179,9 +1180,9 @@ public class UiModeManagerServiceTest extends UiServiceTestCase { int otherFakeProjectionType = 0x0004; String otherPackageName = "Internet Arms"; when(mPackageManager.getPackageUidAsUser(eq(PACKAGE_NAME), anyInt())) - .thenReturn(TestInjector.CALLING_UID); + .thenReturn(TestInjector.DEFAULT_CALLING_UID); when(mPackageManager.getPackageUidAsUser(eq(otherPackageName), anyInt())) - .thenReturn(TestInjector.CALLING_UID); + .thenReturn(TestInjector.DEFAULT_CALLING_UID); IOnProjectionStateChangedListener listener = mock(IOnProjectionStateChangedListener.class); when(listener.asBinder()).thenReturn(mBinder); // Any binder will do. IOnProjectionStateChangedListener listener2 = mock(IOnProjectionStateChangedListener.class); @@ -1234,7 +1235,7 @@ public class UiModeManagerServiceTest extends UiServiceTestCase { // Now kill the binder for the listener. This should remove it from the list of listeners. listenerDeathRecipient.getValue().binderDied(); when(mPackageManager.getPackageUidAsUser(eq(PACKAGE_NAME), anyInt())) - .thenReturn(TestInjector.CALLING_UID); + .thenReturn(TestInjector.DEFAULT_CALLING_UID); mService.requestProjection(mBinder, PROJECTION_TYPE_AUTOMOTIVE, PACKAGE_NAME); verify(listener, never()).onProjectionStateChanged(anyInt(), any()); } @@ -1242,20 +1243,33 @@ public class UiModeManagerServiceTest extends UiServiceTestCase { @Test public void enableCarMode_failsForBogusPackageName() throws Exception { when(mPackageManager.getPackageUidAsUser(eq(PACKAGE_NAME), anyInt())) - .thenReturn(TestInjector.CALLING_UID + 1); + .thenReturn(TestInjector.DEFAULT_CALLING_UID + 1); assertThrows(SecurityException.class, () -> mService.enableCarMode(0, 0, PACKAGE_NAME)); assertThat(mService.getCurrentModeType()).isNotEqualTo(Configuration.UI_MODE_TYPE_CAR); } @Test + public void enableCarMode_shell() throws Exception { + mUiManagerService = new UiModeManagerService(mContext, /* setupWizardComplete= */ true, + mTwilightManager, new TestInjector(Process.SHELL_UID)); + try { + mUiManagerService.onBootPhase(SystemService.PHASE_SYSTEM_SERVICES_READY); + } catch (SecurityException e) {/* ignore for permission denial */} + mService = mUiManagerService.getService(); + + mService.enableCarMode(0, 0, PACKAGE_NAME); + assertThat(mService.getCurrentModeType()).isEqualTo(Configuration.UI_MODE_TYPE_CAR); + } + + @Test public void disableCarMode_failsForBogusPackageName() throws Exception { when(mPackageManager.getPackageUidAsUser(eq(PACKAGE_NAME), anyInt())) - .thenReturn(TestInjector.CALLING_UID); + .thenReturn(TestInjector.DEFAULT_CALLING_UID); mService.enableCarMode(0, 0, PACKAGE_NAME); assertThat(mService.getCurrentModeType()).isEqualTo(Configuration.UI_MODE_TYPE_CAR); when(mPackageManager.getPackageUidAsUser(eq(PACKAGE_NAME), anyInt())) - .thenReturn(TestInjector.CALLING_UID + 1); + .thenReturn(TestInjector.DEFAULT_CALLING_UID + 1); assertThrows(SecurityException.class, () -> mService.disableCarModeByCallingPackage(0, PACKAGE_NAME)); @@ -1263,8 +1277,24 @@ public class UiModeManagerServiceTest extends UiServiceTestCase { // Clean up when(mPackageManager.getPackageUidAsUser(eq(PACKAGE_NAME), anyInt())) - .thenReturn(TestInjector.CALLING_UID); - mService.disableCarModeByCallingPackage(0, PACKAGE_NAME); + .thenReturn(TestInjector.DEFAULT_CALLING_UID); + mService.disableCarModeByCallingPackage(0, PACKAGE_NAME); + assertThat(mService.getCurrentModeType()).isNotEqualTo(Configuration.UI_MODE_TYPE_CAR); + } + + @Test + public void disableCarMode_shell() throws Exception { + mUiManagerService = new UiModeManagerService(mContext, /* setupWizardComplete= */ true, + mTwilightManager, new TestInjector(Process.SHELL_UID)); + try { + mUiManagerService.onBootPhase(SystemService.PHASE_SYSTEM_SERVICES_READY); + } catch (SecurityException e) {/* ignore for permission denial */} + mService = mUiManagerService.getService(); + + mService.enableCarMode(0, 0, PACKAGE_NAME); + assertThat(mService.getCurrentModeType()).isEqualTo(Configuration.UI_MODE_TYPE_CAR); + + mService.disableCarModeByCallingPackage(0, PACKAGE_NAME); assertThat(mService.getCurrentModeType()).isNotEqualTo(Configuration.UI_MODE_TYPE_CAR); } @@ -1275,10 +1305,20 @@ public class UiModeManagerServiceTest extends UiServiceTestCase { } private static class TestInjector extends UiModeManagerService.Injector { - private static final int CALLING_UID = 8675309; + private static final int DEFAULT_CALLING_UID = 8675309; + + private final int callingUid; + + public TestInjector() { + this(DEFAULT_CALLING_UID); + } + + public TestInjector(int callingUid) { + this.callingUid = callingUid; + } public int getCallingUid() { - return CALLING_UID; + return callingUid; } } } diff --git a/services/usb/java/com/android/server/usb/UsbHostManager.java b/services/usb/java/com/android/server/usb/UsbHostManager.java index b1c85fe043ec..f3892768921c 100644 --- a/services/usb/java/com/android/server/usb/UsbHostManager.java +++ b/services/usb/java/com/android/server/usb/UsbHostManager.java @@ -102,6 +102,7 @@ public class UsbHostManager { private final HashMap<String, ArrayList<UsbDirectMidiDevice>> mMidiDevices = new HashMap<String, ArrayList<UsbDirectMidiDevice>>(); private final HashSet<String> mMidiUniqueCodes = new HashSet<String>(); + private static final int MAX_UNIQUE_CODE_GENERATION_ATTEMPTS = 10; private final Random mRandom = new Random(); private final boolean mHasMidiFeature; @@ -645,11 +646,18 @@ public class UsbHostManager { // Generate a 3 digit code. private String generateNewUsbDeviceIdentifier() { String code; + int numberOfAttempts = 0; do { + if (numberOfAttempts > MAX_UNIQUE_CODE_GENERATION_ATTEMPTS) { + Slog.w(TAG, "MIDI unique code array resetting"); + mMidiUniqueCodes.clear(); + numberOfAttempts = 0; + } code = ""; for (int i = 0; i < 3; i++) { code += mRandom.nextInt(10); } + numberOfAttempts++; } while (mMidiUniqueCodes.contains(code)); mMidiUniqueCodes.add(code); return code; diff --git a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java index 73b2510e6cf1..bcfee82000a4 100644 --- a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java +++ b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java @@ -35,7 +35,6 @@ import android.content.pm.ActivityInfo; import android.content.pm.ApplicationInfo; import android.content.pm.IPackageManager; import android.content.pm.PackageManager; -import android.content.pm.PackageManagerInternal; import android.content.pm.ResolveInfo; import android.content.pm.ServiceInfo; import android.content.pm.ShortcutServiceInternal; @@ -105,7 +104,6 @@ import com.android.server.pm.permission.LegacyPermissionManagerInternal; import com.android.server.soundtrigger.SoundTriggerInternal; import com.android.server.utils.TimingsTraceAndSlog; import com.android.server.wm.ActivityTaskManagerInternal; -import com.android.server.wm.ActivityTaskManagerInternal.ActivityTokens; import java.io.FileDescriptor; import java.io.PrintWriter; @@ -128,7 +126,6 @@ public class VoiceInteractionManagerService extends SystemService { final ActivityManagerInternal mAmInternal; final ActivityTaskManagerInternal mAtmInternal; final UserManagerInternal mUserManagerInternal; - final PackageManagerInternal mPackageManagerInternal; final ArrayMap<Integer, VoiceInteractionManagerServiceStub.SoundTriggerSession> mLoadedKeyphraseIds = new ArrayMap<>(); ShortcutServiceInternal mShortcutServiceInternal; @@ -149,8 +146,6 @@ public class VoiceInteractionManagerService extends SystemService { LocalServices.getService(ActivityTaskManagerInternal.class)); mUserManagerInternal = Objects.requireNonNull( LocalServices.getService(UserManagerInternal.class)); - mPackageManagerInternal = Objects.requireNonNull( - LocalServices.getService(PackageManagerInternal.class)); LegacyPermissionManagerInternal permissionManagerInternal = LocalServices.getService( LegacyPermissionManagerInternal.class); @@ -374,21 +369,6 @@ public class VoiceInteractionManagerService extends SystemService { return new SoundTriggerSessionBinderProxy(session); } - @GuardedBy("this") - private void grantImplicitAccessLocked(int grantRecipientUid, @Nullable Intent intent) { - if (mImpl == null) { - Slog.w(TAG, "Cannot grant implicit access because mImpl is null."); - return; - } - - final int grantRecipientAppId = UserHandle.getAppId(grantRecipientUid); - final int grantRecipientUserId = UserHandle.getUserId(grantRecipientUid); - final int voiceInteractionUid = mImpl.mInfo.getServiceInfo().applicationInfo.uid; - mPackageManagerInternal.grantImplicitAccess( - grantRecipientUserId, intent, grantRecipientAppId, voiceInteractionUid, - /* direct= */ true); - } - private IVoiceInteractionSoundTriggerSession createSoundTriggerSessionForSelfIdentity( IBinder client) { Identity identity = new Identity(); @@ -419,9 +399,10 @@ public class VoiceInteractionManagerService extends SystemService { @Override public void onShown() { synchronized (VoiceInteractionManagerServiceStub.this) { - VoiceInteractionManagerServiceStub.this - .grantImplicitAccessLocked(callingUid, - /* intent= */ null); + if (mImpl != null) { + mImpl.grantImplicitAccessLocked(callingUid, + /* intent= */ null); + } } mAtmInternal.onLocalVoiceInteractionStarted(token, mImpl.mActiveSession.mSession, @@ -995,7 +976,7 @@ public class VoiceInteractionManagerService extends SystemService { mContext.getPackageManager(), PackageManager.MATCH_ALL); if (activityInfo != null) { final int activityUid = activityInfo.applicationInfo.uid; - grantImplicitAccessLocked(activityUid, intent); + mImpl.grantImplicitAccessLocked(activityUid, intent); } else { Slog.w(TAG, "Cannot find ActivityInfo in startVoiceActivity."); } @@ -1039,15 +1020,6 @@ public class VoiceInteractionManagerService extends SystemService { } final long caller = Binder.clearCallingIdentity(); try { - // Getting the UID corresponding to the taskId, and grant the visibility to it. - final ActivityTokens tokens = mAtmInternal - .getAttachedNonFinishingActivityForTask(taskId, /* token= */ null); - final ComponentName componentName = mAtmInternal.getActivityName( - tokens.getActivityToken()); - grantImplicitAccessLocked(mPackageManagerInternal.getPackageUid( - componentName.getPackageName(), PackageManager.MATCH_ALL, - UserHandle.myUserId()), /* intent= */ null); - mImpl.requestDirectActionsLocked(token, taskId, assistToken, cancellationCallback, resultCallback); } finally { diff --git a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerServiceImpl.java b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerServiceImpl.java index 39a6868169a7..b9793cafb6fe 100644 --- a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerServiceImpl.java +++ b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerServiceImpl.java @@ -39,6 +39,7 @@ import android.content.Intent; import android.content.IntentFilter; import android.content.ServiceConnection; import android.content.pm.PackageManager; +import android.content.pm.PackageManagerInternal; import android.content.pm.ParceledListSlice; import android.content.pm.ServiceInfo; import android.hardware.soundtrigger.IRecognitionStatusCallback; @@ -80,6 +81,7 @@ import java.io.FileDescriptor; import java.io.PrintWriter; import java.util.ArrayList; import java.util.List; +import java.util.Objects; class VoiceInteractionManagerServiceImpl implements VoiceInteractionSessionConnection.Callback { final static String TAG = "VoiceInteractionServiceManager"; @@ -100,6 +102,7 @@ class VoiceInteractionManagerServiceImpl implements VoiceInteractionSessionConne final ComponentName mComponent; final IActivityManager mAm; final IActivityTaskManager mAtm; + final PackageManagerInternal mPackageManagerInternal; final VoiceInteractionServiceInfo mInfo; final ComponentName mSessionComponentName; final IWindowManager mIWindowManager; @@ -195,6 +198,8 @@ class VoiceInteractionManagerServiceImpl implements VoiceInteractionSessionConne mComponent = service; mAm = ActivityManager.getService(); mAtm = ActivityTaskManager.getService(); + mPackageManagerInternal = Objects.requireNonNull( + LocalServices.getService(PackageManagerInternal.class)); VoiceInteractionServiceInfo info; try { info = new VoiceInteractionServiceInfo(context.getPackageManager(), service, mUser); @@ -230,6 +235,15 @@ class VoiceInteractionManagerServiceImpl implements VoiceInteractionSessionConne Context.RECEIVER_EXPORTED); } + public void grantImplicitAccessLocked(int grantRecipientUid, @Nullable Intent intent) { + final int grantRecipientAppId = UserHandle.getAppId(grantRecipientUid); + final int grantRecipientUserId = UserHandle.getUserId(grantRecipientUid); + final int voiceInteractionUid = mInfo.getServiceInfo().applicationInfo.uid; + mPackageManagerInternal.grantImplicitAccess( + grantRecipientUserId, intent, grantRecipientAppId, voiceInteractionUid, + /* direct= */ true); + } + public boolean showSessionLocked(Bundle args, int flags, IVoiceInteractionSessionShowCallback showCallback, IBinder activityToken) { if (mActiveSession == null) { @@ -354,6 +368,7 @@ class VoiceInteractionManagerServiceImpl implements VoiceInteractionSessionConne VoiceInteractionManagerServiceImpl.this, token, taskId, assistToken, cancellationCallback, callback), REQUEST_DIRECT_ACTIONS_RETRY_TIME_MS); } else { + grantImplicitAccessLocked(tokens.getUid(), /* intent= */ null); try { tokens.getApplicationThread().requestDirectActions(tokens.getActivityToken(), mActiveSession.mInteractor, cancellationCallback, callback); |