diff options
20 files changed, 340 insertions, 78 deletions
diff --git a/libs/WindowManager/Shell/res/drawable/pip_split.xml b/libs/WindowManager/Shell/res/drawable/pip_split.xml new file mode 100644 index 000000000000..2cfdf6ed259b --- /dev/null +++ b/libs/WindowManager/Shell/res/drawable/pip_split.xml @@ -0,0 +1,27 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + ~ Copyright (C) 2021 The Android Open Source Project + ~ + ~ Licensed under the Apache License, Version 2.0 (the "License"); + ~ you may not use this file except in compliance with the License. + ~ You may obtain a copy of the License at + ~ + ~ http://www.apache.org/licenses/LICENSE-2.0 + ~ + ~ Unless required by applicable law or agreed to in writing, software + ~ distributed under the License is distributed on an "AS IS" BASIS, + ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + ~ See the License for the specific language governing permissions and + ~ limitations under the License. + --> +<vector xmlns:android="http://schemas.android.com/apk/res/android" + android:width="@dimen/pip_expand_action_inner_size" + android:height="@dimen/pip_expand_action_inner_size" + android:viewportWidth="24" + android:viewportHeight="24"> + + <path + android:fillColor="#FFFFFF" + android:pathData="M20,18h-5V6h5V18z M22,18V6c0-1.1-0.9-2-2-2h-5c-1.1,0-2,0.9-2,2v12c0,1.1,0.9,2,2,2h5C21.1,20,22,19.1,22,18z M9,18H4V6h5 + V18z M11,18V6c0-1.1-0.9-2-2-2H4C2.9,4,2,4.9,2,6v12c0,1.1,0.9,2,2,2h5C10.1,20,11,19.1,11,18z" /> +</vector> diff --git a/libs/WindowManager/Shell/res/layout/pip_menu.xml b/libs/WindowManager/Shell/res/layout/pip_menu.xml index 7dc2f31e9871..1dd17bad155b 100644 --- a/libs/WindowManager/Shell/res/layout/pip_menu.xml +++ b/libs/WindowManager/Shell/res/layout/pip_menu.xml @@ -81,12 +81,13 @@ <ImageButton android:id="@+id/enter_split" - android:layout_width="@dimen/pip_action_size" - android:layout_height="@dimen/pip_action_size" + android:layout_width="@dimen/pip_split_icon_size" + android:layout_height="@dimen/pip_split_icon_size" android:layout_gravity="top|start" + android:layout_margin="@dimen/pip_split_icon_margin" android:gravity="center" android:contentDescription="@string/pip_phone_enter_split" - android:src="@drawable/pip_expand" + android:src="@drawable/pip_split" android:background="?android:selectableItemBackgroundBorderless" /> </LinearLayout> diff --git a/libs/WindowManager/Shell/res/values/dimen.xml b/libs/WindowManager/Shell/res/values/dimen.xml index e9b9ec3f7d89..9e77578eafd8 100644 --- a/libs/WindowManager/Shell/res/values/dimen.xml +++ b/libs/WindowManager/Shell/res/values/dimen.xml @@ -67,6 +67,10 @@ <dimen name="pip_resize_handle_margin">4dp</dimen> <dimen name="pip_resize_handle_padding">0dp</dimen> + <!-- PIP Split icon size and margin. --> + <dimen name="pip_split_icon_size">24dp</dimen> + <dimen name="pip_split_icon_margin">12dp</dimen> + <!-- PIP stash offset size, which is the width of visible PIP region when stashed. --> <dimen name="pip_stash_offset">32dp</dimen> diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipMenuView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipMenuView.java index 7bbebe5bf287..b209699c1a19 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipMenuView.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipMenuView.java @@ -99,7 +99,7 @@ public class PipMenuView extends FrameLayout { private static final float MENU_BACKGROUND_ALPHA = 0.3f; private static final float DISABLED_ACTION_ALPHA = 0.54f; - private static final boolean ENABLE_ENTER_SPLIT = false; + private static final boolean ENABLE_ENTER_SPLIT = true; private int mMenuState; private boolean mAllowMenuTimeout = true; diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/DialogLaunchAnimator.kt b/packages/SystemUI/animation/src/com/android/systemui/animation/DialogLaunchAnimator.kt index faa7554525c5..413612ff9a76 100644 --- a/packages/SystemUI/animation/src/com/android/systemui/animation/DialogLaunchAnimator.kt +++ b/packages/SystemUI/animation/src/com/android/systemui/animation/DialogLaunchAnimator.kt @@ -54,8 +54,12 @@ class DialogLaunchAnimator( private val TAG_LAUNCH_ANIMATION_RUNNING = R.id.launch_animation_running } + /** + * The set of dialogs that were animated using this animator and that are still opened (not + * dismissed, but can be hidden). + */ // TODO(b/201264644): Remove this set. - private val currentAnimations = hashSetOf<DialogLaunchAnimation>() + private val openedDialogs = hashSetOf<AnimatedDialog>() /** * Show [dialog] by expanding it from [view]. If [animateBackgroundBoundsChange] is true, then @@ -79,21 +83,29 @@ class DialogLaunchAnimator( "the main thread") } + // If the parent of the view we are launching from is the background of some other animated + // dialog, then this means the caller intent is to launch a dialog from another dialog. In + // this case, we also animate the parent (which is the dialog background). + val dialogContentParent = openedDialogs + .firstOrNull { it.dialogContentParent == view.parent } + ?.dialogContentParent + val animateFrom = dialogContentParent ?: view + // Make sure we don't run the launch animation from the same view twice at the same time. - if (view.getTag(TAG_LAUNCH_ANIMATION_RUNNING) != null) { + if (animateFrom.getTag(TAG_LAUNCH_ANIMATION_RUNNING) != null) { Log.e(TAG, "Not running dialog launch animation as there is already one running") dialog.show() return dialog } - view.setTag(TAG_LAUNCH_ANIMATION_RUNNING, true) + animateFrom.setTag(TAG_LAUNCH_ANIMATION_RUNNING, true) - val launchAnimation = DialogLaunchAnimation( - context, launchAnimator, hostDialogProvider, view, - onDialogDismissed = { currentAnimations.remove(it) }, originalDialog = dialog, + val launchAnimation = AnimatedDialog( + context, launchAnimator, hostDialogProvider, animateFrom, + onDialogDismissed = { openedDialogs.remove(it) }, originalDialog = dialog, animateBackgroundBoundsChange) val hostDialog = launchAnimation.hostDialog - currentAnimations.add(launchAnimation) + openedDialogs.add(launchAnimation) // If the dialog is dismissed/hidden/shown, then we should actually dismiss/hide/show the // host dialog. @@ -151,7 +163,7 @@ class DialogLaunchAnimator( * TODO(b/193634619): Remove this function and animate dialog into opening activity instead. */ fun disableAllCurrentDialogsExitAnimations() { - currentAnimations.forEach { it.exitAnimationDisabled = true } + openedDialogs.forEach { it.exitAnimationDisabled = true } } } @@ -206,7 +218,7 @@ interface DialogListener { fun onSizeChanged() } -private class DialogLaunchAnimation( +private class AnimatedDialog( private val context: Context, private val launchAnimator: LaunchAnimator, hostDialogProvider: HostDialogProvider, @@ -215,10 +227,10 @@ private class DialogLaunchAnimation( private val touchSurface: View, /** - * A callback that will be called with this [DialogLaunchAnimation] after the dialog was + * A callback that will be called with this [AnimatedDialog] after the dialog was * dismissed and the exit animation is done. */ - private val onDialogDismissed: (DialogLaunchAnimation) -> Unit, + private val onDialogDismissed: (AnimatedDialog) -> Unit, /** The original dialog whose content will be shown and animate in/out in [hostDialog]. */ private val originalDialog: Dialog, @@ -241,7 +253,7 @@ private class DialogLaunchAnimation( * the same size as the original dialog window and to which we will set the original dialog * window background. */ - private val dialogContentParent = FrameLayout(context) + val dialogContentParent = FrameLayout(context) /** * The background color of [originalDialogView], taking into consideration the [originalDialog] @@ -574,7 +586,7 @@ private class DialogLaunchAnimation( } dismissDialogs(false /* instantDismiss */) - onDialogDismissed(this@DialogLaunchAnimation) + onDialogDismissed(this@AnimatedDialog) return } @@ -610,7 +622,7 @@ private class DialogLaunchAnimation( // and instantly dismiss the dialog. GhostView.removeGhost(touchSurface) dismissDialogs(true /* instantDismiss */) - onDialogDismissed(this@DialogLaunchAnimation) + onDialogDismissed(this@AnimatedDialog) return true } diff --git a/packages/SystemUI/res/values-sw600dp-land/config.xml b/packages/SystemUI/res/values-sw600dp-land/config.xml index 040df865bfe5..362e18d785ac 100644 --- a/packages/SystemUI/res/values-sw600dp-land/config.xml +++ b/packages/SystemUI/res/values-sw600dp-land/config.xml @@ -33,5 +33,4 @@ <!-- Notifications are sized to match the width of two (of 4) qs tiles in landscape. --> <bool name="config_skinnyNotifsInLandscape">false</bool> - <dimen name="keyguard_indication_margin_bottom">25dp</dimen> </resources> diff --git a/packages/SystemUI/res/values-sw600dp-land/dimens.xml b/packages/SystemUI/res/values-sw600dp-land/dimens.xml new file mode 100644 index 000000000000..3cfe05638032 --- /dev/null +++ b/packages/SystemUI/res/values-sw600dp-land/dimens.xml @@ -0,0 +1,28 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + * Copyright (c) 2021, The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. +*/ +--> +<resources> + + <!-- keyguard--> + <dimen name="keyguard_indication_margin_bottom">25dp</dimen> + <dimen name="ambient_indication_margin_bottom">115dp</dimen> + <dimen name="lock_icon_margin_bottom">60dp</dimen> + + <!-- margin from keyguard status bar to clock. For split shade it should be + keyguard_split_shade_top_margin - status_bar_header_height_keyguard = 8dp --> + <dimen name="keyguard_clock_top_margin">8dp</dimen> +</resources> diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml index e5ea3688ec0d..3b3dc3899f33 100644 --- a/packages/SystemUI/res/values/dimens.xml +++ b/packages/SystemUI/res/values/dimens.xml @@ -753,6 +753,9 @@ <!-- Minimum distance the user has to drag down to go to the full shade. --> <dimen name="keyguard_drag_down_min_distance">100dp</dimen> + <!-- The margin from the top of the screen to notifications and keyguard status view in + split shade on keyguard--> + <dimen name="keyguard_split_shade_top_margin">68dp</dimen> <!-- The margin between the status view and the notifications on Keyguard.--> <dimen name="keyguard_status_view_bottom_margin">20dp</dimen> <!-- Minimum margin between clock and status bar --> @@ -935,7 +938,9 @@ <dimen name="keyguard_lock_padding">20dp</dimen> <dimen name="keyguard_indication_margin_bottom">32dp</dimen> - <dimen name="lock_icon_margin_bottom">98dp</dimen> + <dimen name="lock_icon_margin_bottom">110dp</dimen> + <dimen name="ambient_indication_margin_bottom">71dp</dimen> + <!-- The text size for battery level --> <dimen name="battery_level_text_size">12sp</dimen> diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/recents/model/Task.java b/packages/SystemUI/shared/src/com/android/systemui/shared/recents/model/Task.java index 3f2ff742d072..3128ffdbc67b 100644 --- a/packages/SystemUI/shared/src/com/android/systemui/shared/recents/model/Task.java +++ b/packages/SystemUI/shared/src/com/android/systemui/shared/recents/model/Task.java @@ -23,17 +23,14 @@ import android.app.ActivityManager.TaskDescription; import android.app.TaskInfo; import android.content.ComponentName; import android.content.Intent; -import android.content.pm.ActivityInfo; -import android.graphics.Color; import android.graphics.drawable.Drawable; import android.os.Parcel; import android.os.Parcelable; import android.view.ViewDebug; -import com.android.systemui.shared.recents.utilities.Utilities; +import androidx.annotation.Nullable; import java.io.PrintWriter; -import java.util.ArrayList; import java.util.Objects; /** @@ -202,8 +199,8 @@ public class Task { * The icon is the task description icon (if provided), which falls back to the activity icon, * which can then fall back to the application icon. */ - public Drawable icon; - public ThumbnailData thumbnail; + @Nullable public Drawable icon; + @Nullable public ThumbnailData thumbnail; @ViewDebug.ExportedProperty(category="recents") @Deprecated public String title; diff --git a/packages/SystemUI/src/com/android/keyguard/LockIconViewController.java b/packages/SystemUI/src/com/android/keyguard/LockIconViewController.java index c7be3ce01c54..88476398e09d 100644 --- a/packages/SystemUI/src/com/android/keyguard/LockIconViewController.java +++ b/packages/SystemUI/src/com/android/keyguard/LockIconViewController.java @@ -43,6 +43,7 @@ import android.view.GestureDetector.SimpleOnGestureListener; import android.view.LayoutInflater; import android.view.MotionEvent; import android.view.View; +import android.view.WindowManager; import android.view.accessibility.AccessibilityManager; import android.view.accessibility.AccessibilityNodeInfo; @@ -85,7 +86,6 @@ public class LockIconViewController extends ViewController<LockIconView> impleme private static final float sDefaultDensity = (float) DisplayMetrics.DENSITY_DEVICE_STABLE / (float) DisplayMetrics.DENSITY_DEFAULT; private static final int sLockIconRadiusPx = (int) (sDefaultDensity * 36); - private static final float sDistAboveKgBottomAreaPx = sDefaultDensity * 12; private static final AudioAttributes VIBRATION_SONIFICATION_ATTRIBUTES = new AudioAttributes.Builder() .setContentType(AudioAttributes.CONTENT_TYPE_SONIFICATION) @@ -126,7 +126,7 @@ public class LockIconViewController extends ViewController<LockIconView> impleme private boolean mUdfpsSupported; private float mHeightPixels; private float mWidthPixels; - private int mBottomPadding; // in pixels + private int mBottomPaddingPx; private boolean mShowUnlockIcon; private boolean mShowLockIcon; @@ -347,11 +347,11 @@ public class LockIconViewController extends ViewController<LockIconView> impleme } private void updateConfiguration() { - final DisplayMetrics metrics = mView.getContext().getResources().getDisplayMetrics(); - mWidthPixels = metrics.widthPixels; - mHeightPixels = metrics.heightPixels; - mBottomPadding = mView.getContext().getResources().getDimensionPixelSize( - R.dimen.lock_icon_margin_bottom); + WindowManager windowManager = getContext().getSystemService(WindowManager.class); + Rect bounds = windowManager.getCurrentWindowMetrics().getBounds(); + mWidthPixels = bounds.right; + mHeightPixels = bounds.bottom; + mBottomPaddingPx = getResources().getDimensionPixelSize(R.dimen.lock_icon_margin_bottom); mUnlockedLabel = mView.getContext().getResources().getString( R.string.accessibility_unlock_button); @@ -370,8 +370,8 @@ public class LockIconViewController extends ViewController<LockIconView> impleme } else { mView.setCenterLocation( new PointF(mWidthPixels / 2, - mHeightPixels - mBottomPadding - sDistAboveKgBottomAreaPx - - sLockIconRadiusPx), sLockIconRadiusPx); + mHeightPixels - mBottomPaddingPx - sLockIconRadiusPx), + sLockIconRadiusPx); } mView.getHitRect(mSensorTouchLocation); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardClockPositionAlgorithm.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardClockPositionAlgorithm.java index 4f3bbdbff030..7ca8652e1b3c 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardClockPositionAlgorithm.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardClockPositionAlgorithm.java @@ -57,21 +57,6 @@ public class KeyguardClockPositionAlgorithm { private int mUserSwitchPreferredY; /** - * Whether or not there is a custom clock face on keyguard. - */ - private boolean mHasCustomClock; - - /** - * Whether or not the NSSL contains any visible notifications. - */ - private boolean mHasVisibleNotifs; - - /** - * Height of notification stack: Sum of height of each notification. - */ - private int mNotificationStackHeight; - - /** * Minimum top margin to avoid overlap with status bar, lock icon, or multi-user switcher * avatar. */ @@ -88,6 +73,16 @@ public class KeyguardClockPositionAlgorithm { private int mContainerTopPadding; /** + * Top margin of notifications introduced by presence of split shade header / status bar + */ + private int mSplitShadeTopNotificationsMargin; + + /** + * Target margin for notifications and clock from the top of the screen in split shade + */ + private int mSplitShadeTargetTopMargin; + + /** * @see NotificationPanelViewController#getExpandedFraction() */ private float mPanelExpansion; @@ -152,6 +147,10 @@ public class KeyguardClockPositionAlgorithm { public void loadDimens(Resources res) { mStatusViewBottomMargin = res.getDimensionPixelSize( R.dimen.keyguard_status_view_bottom_margin); + mSplitShadeTopNotificationsMargin = + res.getDimensionPixelSize(R.dimen.split_shade_header_height); + mSplitShadeTargetTopMargin = + res.getDimensionPixelSize(R.dimen.keyguard_split_shade_top_margin); mContainerTopPadding = res.getDimensionPixelSize(R.dimen.keyguard_clock_top_margin); @@ -214,7 +213,7 @@ public class KeyguardClockPositionAlgorithm { if (mBypassEnabled) { return (int) (mUnlockedStackScrollerPadding + mOverStretchAmount); } else if (mIsSplitShade) { - return clockYPosition; + return Math.max(0, clockYPosition - mSplitShadeTopNotificationsMargin); } else { return clockYPosition + mKeyguardStatusHeight; } @@ -224,14 +223,18 @@ public class KeyguardClockPositionAlgorithm { if (mBypassEnabled) { return mUnlockedStackScrollerPadding; } else if (mIsSplitShade) { - return mMinTopMargin; + return Math.max(mSplitShadeTargetTopMargin, mMinTopMargin); } else { return mMinTopMargin + mKeyguardStatusHeight; } } private int getExpandedPreferredClockY() { - return mMinTopMargin + mUserSwitchHeight; + if (mIsSplitShade) { + return Math.max(mSplitShadeTargetTopMargin, mMinTopMargin); + } else { + return mMinTopMargin; + } } public int getLockscreenStatusViewHeight() { 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 32659e416535..dedd6daf49a6 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java @@ -3558,7 +3558,12 @@ public class NotificationPanelViewController extends PanelViewController { mNotificationStackScrollLayoutController.setPulsing(pulsing, animatePulse); } - public void setAmbientIndicationBottomPadding(int ambientIndicationBottomPadding) { + public void setAmbientIndicationTop(int ambientIndicationTop, boolean ambientTextVisible) { + int ambientIndicationBottomPadding = 0; + if (ambientTextVisible) { + int stackBottom = mNotificationStackScrollLayoutController.getView().getBottom(); + ambientIndicationBottomPadding = stackBottom - ambientIndicationTop; + } if (mAmbientIndicationBottomPadding != ambientIndicationBottomPadding) { mAmbientIndicationBottomPadding = ambientIndicationBottomPadding; updateMaxDisplayedNotifications(true); diff --git a/packages/SystemUI/tests/src/com/android/systemui/animation/DialogLaunchAnimatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/animation/DialogLaunchAnimatorTest.kt index 209df6b54f8f..d4c3840356d1 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/animation/DialogLaunchAnimatorTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/animation/DialogLaunchAnimatorTest.kt @@ -86,7 +86,14 @@ class DialogLaunchAnimatorTest : SysuiTestCase() { assertFalse(dialog.isShowing) assertFalse(dialog.onStopCalled) - runOnMainThreadAndWaitForIdleSync { dialog.dismiss() } + runOnMainThreadAndWaitForIdleSync { + // TODO(b/204561691): Remove this call to disableAllCurrentDialogsExitAnimations() and + // make sure that the test still pass on git_master/cf_x86_64_phone-userdebug in + // Forrest. + dialogLaunchAnimator.disableAllCurrentDialogsExitAnimations() + + dialog.dismiss() + } assertFalse(hostDialog.isShowing) assertFalse(dialog.isShowing) assertTrue(hostDialog.wasDismissed) diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/LockIconViewControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/keyguard/LockIconViewControllerTest.java index d64319b278b4..e01583e1cb1e 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/LockIconViewControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/LockIconViewControllerTest.java @@ -32,6 +32,7 @@ import static org.mockito.Mockito.when; import android.content.Context; import android.content.res.Resources; import android.graphics.PointF; +import android.graphics.Rect; import android.graphics.drawable.AnimatedStateListDrawable; import android.hardware.biometrics.BiometricSourceType; import android.hardware.biometrics.SensorLocationInternal; @@ -39,10 +40,10 @@ import android.hardware.fingerprint.FingerprintSensorPropertiesInternal; import android.os.Vibrator; import android.testing.AndroidTestingRunner; import android.testing.TestableLooper; -import android.util.DisplayMetrics; import android.util.Pair; import android.view.LayoutInflater; import android.view.View; +import android.view.WindowManager; import android.view.accessibility.AccessibilityManager; import androidx.test.filters.SmallTest; @@ -70,6 +71,7 @@ import com.airbnb.lottie.LottieAnimationView; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; +import org.mockito.Answers; import org.mockito.ArgumentCaptor; import org.mockito.Captor; import org.mockito.Mock; @@ -88,7 +90,7 @@ public class LockIconViewControllerTest extends SysuiTestCase { private @Mock AnimatedStateListDrawable mIconDrawable; private @Mock Context mContext; private @Mock Resources mResources; - private @Mock DisplayMetrics mDisplayMetrics; + private @Mock(answer = Answers.RETURNS_DEEP_STUBS) WindowManager mWindowManager; private @Mock StatusBarStateController mStatusBarStateController; private @Mock KeyguardUpdateMonitor mKeyguardUpdateMonitor; private @Mock KeyguardViewController mKeyguardViewController; @@ -137,7 +139,9 @@ public class LockIconViewControllerTest extends SysuiTestCase { when(mLockIconView.getContext()).thenReturn(mContext); when(mLockIconView.findViewById(R.layout.udfps_aod_lock_icon)).thenReturn(mAodFp); when(mContext.getResources()).thenReturn(mResources); - when(mResources.getDisplayMetrics()).thenReturn(mDisplayMetrics); + when(mContext.getSystemService(WindowManager.class)).thenReturn(mWindowManager); + Rect windowBounds = new Rect(0, 0, 800, 1200); + when(mWindowManager.getCurrentWindowMetrics().getBounds()).thenReturn(windowBounds); when(mResources.getString(R.string.accessibility_unlock_button)).thenReturn(UNLOCKED_LABEL); when(mResources.getDrawable(anyInt(), any())).thenReturn(mIconDrawable); diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardClockPositionAlgorithmTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardClockPositionAlgorithmTest.java index 624bedc30be9..11826954baee 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardClockPositionAlgorithmTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardClockPositionAlgorithmTest.java @@ -263,6 +263,34 @@ public class KeyguardClockPositionAlgorithmTest extends SysuiTestCase { } @Test + public void clockPositionedDependingOnMarginInSplitShade() { + when(mResources.getDimensionPixelSize(R.dimen.keyguard_split_shade_top_margin)) + .thenReturn(400); + mClockPositionAlgorithm.loadDimens(mResources); + givenLockScreen(); + mIsSplitShade = true; + // WHEN the position algorithm is run + positionClock(); + + assertThat(mClockPosition.clockY).isEqualTo(400); + } + + @Test + public void notifPaddingMakesUpToFullMarginInSplitShade() { + when(mResources.getDimensionPixelSize(R.dimen.keyguard_split_shade_top_margin)) + .thenReturn(100); + when(mResources.getDimensionPixelSize(R.dimen.split_shade_header_height)) + .thenReturn(70); + mClockPositionAlgorithm.loadDimens(mResources); + givenLockScreen(); + mIsSplitShade = true; + // WHEN the position algorithm is run + positionClock(); + // THEN the notif padding makes up lacking margin (margin - header height = 30). + assertThat(mClockPosition.stackScrollerPadding).isEqualTo(30); + } + + @Test public void notifPaddingExpandedAlignedWithClockInSplitShadeMode() { givenLockScreen(); mIsSplitShade = true; @@ -271,7 +299,7 @@ public class KeyguardClockPositionAlgorithmTest extends SysuiTestCase { positionClock(); // THEN the padding DOESN'T adjust for keyguard status height. assertThat(mClockPosition.stackScrollerPaddingExpanded) - .isEqualTo(mClockPosition.clockYFullyDozing); + .isEqualTo(mClockPosition.clockY); } @Test diff --git a/services/core/java/com/android/server/display/DisplayManagerService.java b/services/core/java/com/android/server/display/DisplayManagerService.java index a3b5e79cc8c7..fadaf1082e46 100644 --- a/services/core/java/com/android/server/display/DisplayManagerService.java +++ b/services/core/java/com/android/server/display/DisplayManagerService.java @@ -2862,14 +2862,31 @@ public final class DisplayManagerService extends SystemService { @Override // Binder call public void setBrightnessConfigurationForUser( BrightnessConfiguration c, @UserIdInt int userId, String packageName) { - mLogicalDisplayMapper.forEachLocked(logicalDisplay -> { - if (logicalDisplay.getDisplayInfoLocked().type != Display.TYPE_INTERNAL) { - return; + mContext.enforceCallingOrSelfPermission( + Manifest.permission.CONFIGURE_DISPLAY_BRIGHTNESS, + "Permission required to change the display's brightness configuration"); + if (userId != UserHandle.getCallingUserId()) { + mContext.enforceCallingOrSelfPermission( + Manifest.permission.INTERACT_ACROSS_USERS, + "Permission required to change the display brightness" + + " configuration of another user"); + } + final long token = Binder.clearCallingIdentity(); + try { + synchronized (mSyncRoot) { + mLogicalDisplayMapper.forEachLocked(logicalDisplay -> { + if (logicalDisplay.getDisplayInfoLocked().type != Display.TYPE_INTERNAL) { + return; + } + final DisplayDevice displayDevice = + logicalDisplay.getPrimaryDisplayDeviceLocked(); + setBrightnessConfigurationForDisplayInternal(c, displayDevice.getUniqueId(), + userId, packageName); + }); } - final DisplayDevice displayDevice = logicalDisplay.getPrimaryDisplayDeviceLocked(); - setBrightnessConfigurationForDisplay(c, displayDevice.getUniqueId(), userId, - packageName); - }); + } finally { + Binder.restoreCallingIdentity(token); + } } @Override // Binder call diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java index 2f550c6e9338..47d8022a5acc 100755 --- a/services/core/java/com/android/server/notification/NotificationManagerService.java +++ b/services/core/java/com/android/server/notification/NotificationManagerService.java @@ -7699,7 +7699,10 @@ public class NotificationManagerService extends SystemService { final int waitMs = mAudioManager.getFocusRampTimeMs( AudioManager.AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK, record.getAudioAttributes()); - if (DBG) Slog.v(TAG, "Delaying vibration by " + waitMs + "ms"); + if (DBG) { + Slog.v(TAG, "Delaying vibration for notification " + + record.getKey() + " by " + waitMs + "ms"); + } try { Thread.sleep(waitMs); } catch (InterruptedException e) { } @@ -7707,9 +7710,17 @@ public class NotificationManagerService extends SystemService { // so need to check the notification still valide for vibrate. synchronized (mNotificationLock) { if (mNotificationsByKey.get(record.getKey()) != null) { - vibrate(record, effect, true); + if (record.getKey().equals(mVibrateNotificationKey)) { + vibrate(record, effect, true); + } else { + if (DBG) { + Slog.v(TAG, "No vibration for notification " + + record.getKey() + ": a new notification is " + + "vibrating, or effects were cleared while waiting"); + } + } } else { - Slog.e(TAG, "No vibration for canceled notification : " + Slog.w(TAG, "No vibration for canceled notification " + record.getKey()); } } diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java index 4190ff0b9bc7..1b7a012094f6 100644 --- a/services/core/java/com/android/server/wm/Task.java +++ b/services/core/java/com/android/server/wm/Task.java @@ -1126,7 +1126,11 @@ class Task extends TaskFragment { if (inMultiWindowMode() || !hasChild()) return false; if (intent != null) { final int returnHomeFlags = FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_TASK_ON_HOME; - return intent != null && (intent.getFlags() & returnHomeFlags) == returnHomeFlags; + final Task task = getDisplayArea() != null ? getDisplayArea().getRootHomeTask() : null; + final boolean isLockTaskModeViolation = task != null + && mAtmService.getLockTaskController().isLockTaskModeViolation(task); + return (intent.getFlags() & returnHomeFlags) == returnHomeFlags + && !isLockTaskModeViolation; } final Task bottomTask = getBottomMostTask(); return bottomTask != this && bottomTask.returnsToHomeRootTask(); diff --git a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java index 3ac30d0258a5..cadc81600bbb 100644 --- a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java +++ b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java @@ -51,6 +51,7 @@ import static com.google.common.truth.Truth.assertThat; import static com.google.common.truth.Truth.assertWithMessage; import static org.junit.Assert.fail; +import static org.junit.Assume.assumeTrue; import static org.mockito.Matchers.any; import static org.mockito.Matchers.anyBoolean; import static org.mockito.Matchers.anyInt; @@ -184,6 +185,8 @@ public class DevicePolicyManagerTest extends DpmTestBase { public DevicePolicyManager parentDpm; public DevicePolicyManagerServiceTestable dpms; + private boolean mIsAutomotive; + /* * The CA cert below is the content of cacert.pem as generated by: * @@ -266,6 +269,9 @@ public class DevicePolicyManagerTest extends DpmTestBase { setUpUserManager(); when(getServices().lockPatternUtils.hasSecureLockScreen()).thenReturn(true); + + mIsAutomotive = mContext.getPackageManager() + .hasSystemFeature(PackageManager.FEATURE_AUTOMOTIVE); } private TransferOwnershipMetadataManager getMockTransferMetadataManager() { @@ -2117,10 +2123,13 @@ public class DevicePolicyManagerTest extends DpmTestBase { assertThat(dpm.getPasswordExpirationTimeout(admin1)) .isEqualTo(originalPasswordExpirationTimeout); - int originalPasswordQuality = dpm.getPasswordQuality(admin1); - assertExpectException(SecurityException.class, /* messageRegex= */ null, - () -> dpm.setPasswordQuality(admin1, DevicePolicyManager.PASSWORD_QUALITY_NUMERIC)); - assertThat(dpm.getPasswordQuality(admin1)).isEqualTo(originalPasswordQuality); + if (isDeprecatedPasswordApisSupported()) { + int originalPasswordQuality = dpm.getPasswordQuality(admin1); + assertExpectException(SecurityException.class, /* messageRegex= */ null, + () -> dpm.setPasswordQuality(admin1, + DevicePolicyManager.PASSWORD_QUALITY_NUMERIC)); + assertThat(dpm.getPasswordQuality(admin1)).isEqualTo(originalPasswordQuality); + } } @Test @@ -5231,6 +5240,8 @@ public class DevicePolicyManagerTest extends DpmTestBase { @Test public void testIsActivePasswordSufficient() throws Exception { + assumeDeprecatedPasswordApisSupported(); + mContext.binder.callingUid = DpmMockContext.CALLER_SYSTEM_USER_UID; mContext.packageName = admin1.getPackageName(); setupDeviceOwner(); @@ -5283,6 +5294,8 @@ public class DevicePolicyManagerTest extends DpmTestBase { @Test public void testIsActivePasswordSufficient_noLockScreen() throws Exception { + assumeDeprecatedPasswordApisSupported(); + // If there is no lock screen, the password is considered empty no matter what, because // it provides no security. when(getServices().lockPatternUtils.hasSecureLockScreen()).thenReturn(false); @@ -5363,6 +5376,8 @@ public class DevicePolicyManagerTest extends DpmTestBase { @Test public void testGetAggregatedPasswordMetrics_IgnoreProfileRequirement() throws Exception { + assumeDeprecatedPasswordApisSupported(); + final int managedProfileUserId = CALLER_USER_HANDLE; final int managedProfileAdminUid = UserHandle.getUid(managedProfileUserId, DpmMockContext.SYSTEM_UID); @@ -5392,6 +5407,8 @@ public class DevicePolicyManagerTest extends DpmTestBase { @Test public void testCanSetPasswordRequirementOnParentPreS() throws Exception { + assumeDeprecatedPasswordApisSupported(); + final int managedProfileUserId = CALLER_USER_HANDLE; final int managedProfileAdminUid = UserHandle.getUid(managedProfileUserId, DpmMockContext.SYSTEM_UID); @@ -5407,6 +5424,8 @@ public class DevicePolicyManagerTest extends DpmTestBase { @Test public void testCannotSetPasswordRequirementOnParent() throws Exception { + assumeDeprecatedPasswordApisSupported(); + final int managedProfileUserId = CALLER_USER_HANDLE; final int managedProfileAdminUid = UserHandle.getUid(managedProfileUserId, DpmMockContext.SYSTEM_UID); @@ -5427,6 +5446,8 @@ public class DevicePolicyManagerTest extends DpmTestBase { @Test public void isActivePasswordSufficient_SeparateWorkChallenge_ProfileQualityRequirementMet() throws Exception { + assumeDeprecatedPasswordApisSupported(); + // Create work profile with empty separate challenge final int managedProfileUserId = 15; final int managedProfileAdminUid = UserHandle.getUid(managedProfileUserId, 19436); @@ -5450,6 +5471,8 @@ public class DevicePolicyManagerTest extends DpmTestBase { @Test public void isActivePasswordSufficient_SeparateWorkChallenge_ProfileComplexityRequirementMet() throws Exception { + assumeDeprecatedPasswordApisSupported(); + // Create work profile with empty separate challenge final int managedProfileUserId = 15; final int managedProfileAdminUid = UserHandle.getUid(managedProfileUserId, 19436); @@ -5473,6 +5496,8 @@ public class DevicePolicyManagerTest extends DpmTestBase { @Test public void isActivePasswordSufficient_SeparateWorkChallenge_ParentQualityRequirementMet() throws Exception { + assumeDeprecatedPasswordApisSupported(); + // Create work profile with empty separate challenge final int managedProfileUserId = 15; final int managedProfileAdminUid = UserHandle.getUid(managedProfileUserId, 19436); @@ -5519,6 +5544,8 @@ public class DevicePolicyManagerTest extends DpmTestBase { @Test public void isActivePasswordSufficient_UnifiedWorkChallenge_ProfileQualityRequirementMet() throws Exception { + assumeDeprecatedPasswordApisSupported(); + // Create work profile with unified challenge final int managedProfileUserId = 15; final int managedProfileAdminUid = UserHandle.getUid(managedProfileUserId, 19436); @@ -5565,6 +5592,8 @@ public class DevicePolicyManagerTest extends DpmTestBase { @Test public void isActivePasswordSufficient_UnifiedWorkChallenge_ParentQualityRequirementMet() throws Exception { + assumeDeprecatedPasswordApisSupported(); + // Create work profile with unified challenge final int managedProfileUserId = 15; final int managedProfileAdminUid = UserHandle.getUid(managedProfileUserId, 19436); @@ -5625,6 +5654,8 @@ public class DevicePolicyManagerTest extends DpmTestBase { @Test public void testPasswordQualityAppliesToParentPreS() throws Exception { + assumeDeprecatedPasswordApisSupported(); + final int managedProfileUserId = CALLER_USER_HANDLE; final int managedProfileAdminUid = UserHandle.getUid(managedProfileUserId, DpmMockContext.SYSTEM_UID); @@ -7285,6 +7316,8 @@ public class DevicePolicyManagerTest extends DpmTestBase { @Test public void testSetRequiredPasswordComplexity_UnauthorizedCallersOnDO() throws Exception { + assumeDeprecatedPasswordApisSupported(); + mContext.binder.callingUid = DpmMockContext.CALLER_SYSTEM_USER_UID; setupDeviceOwner(); // DO must be able to set it. @@ -7300,6 +7333,8 @@ public class DevicePolicyManagerTest extends DpmTestBase { @Test public void testSetRequiredPasswordComplexity_UnauthorizedCallersOnPO() throws Exception { + assumeDeprecatedPasswordApisSupported(); + mContext.binder.callingUid = DpmMockContext.CALLER_UID; setupProfileOwner(); // PO must be able to set it. @@ -7314,6 +7349,8 @@ public class DevicePolicyManagerTest extends DpmTestBase { @Test public void testSetRequiredPasswordComplexity_validValuesOnly() throws Exception { + assumeDeprecatedPasswordApisSupported(); + mContext.binder.callingUid = DpmMockContext.CALLER_UID; setupProfileOwner(); @@ -7335,6 +7372,8 @@ public class DevicePolicyManagerTest extends DpmTestBase { @Test public void testSetRequiredPasswordComplexity_setAndGet() throws Exception { + assumeDeprecatedPasswordApisSupported(); + mContext.binder.callingUid = DpmMockContext.CALLER_UID; setupProfileOwner(); @@ -7348,6 +7387,8 @@ public class DevicePolicyManagerTest extends DpmTestBase { @Test public void testSetRequiredPasswordComplexityOnParent_setAndGet() throws Exception { + assumeDeprecatedPasswordApisSupported(); + final int managedProfileUserId = 15; final int managedProfileAdminUid = UserHandle.getUid(managedProfileUserId, 19436); @@ -7366,6 +7407,8 @@ public class DevicePolicyManagerTest extends DpmTestBase { @Test public void testSetRequiredPasswordComplexity_isSufficient() throws Exception { + assumeDeprecatedPasswordApisSupported(); + mContext.binder.callingUid = DpmMockContext.CALLER_SYSTEM_USER_UID; mContext.packageName = admin1.getPackageName(); setupDeviceOwner(); @@ -7395,6 +7438,8 @@ public class DevicePolicyManagerTest extends DpmTestBase { @Test public void testSetRequiredPasswordComplexity_resetBySettingQuality() throws Exception { + assumeDeprecatedPasswordApisSupported(); + mContext.binder.callingUid = DpmMockContext.CALLER_UID; setupProfileOwner(); @@ -7407,6 +7452,8 @@ public class DevicePolicyManagerTest extends DpmTestBase { @Test public void testSetRequiredPasswordComplexity_overridesQuality() throws Exception { + assumeDeprecatedPasswordApisSupported(); + mContext.binder.callingUid = DpmMockContext.CALLER_UID; setupProfileOwner(); @@ -7421,6 +7468,8 @@ public class DevicePolicyManagerTest extends DpmTestBase { @Test public void testSetRequiredPasswordComplexityFailsWithQualityOnParent() throws Exception { + assumeDeprecatedPasswordApisSupported(); + final int managedProfileUserId = CALLER_USER_HANDLE; final int managedProfileAdminUid = UserHandle.getUid(managedProfileUserId, DpmMockContext.SYSTEM_UID); @@ -7435,6 +7484,8 @@ public class DevicePolicyManagerTest extends DpmTestBase { @Test public void testSetQualityOnParentFailsWithComplexityOnProfile() throws Exception { + assumeDeprecatedPasswordApisSupported(); + final int managedProfileUserId = CALLER_USER_HANDLE; final int managedProfileAdminUid = UserHandle.getUid(managedProfileUserId, DpmMockContext.SYSTEM_UID); @@ -8015,4 +8066,13 @@ public class DevicePolicyManagerTest extends DpmTestBase { when(mContext.getResources().getStringArray(R.array.vendor_policy_exempt_apps)) .thenReturn(new String[0]); } + + private boolean isDeprecatedPasswordApisSupported() { + return !mIsAutomotive; + } + + private void assumeDeprecatedPasswordApisSupported() { + assumeTrue("device doesn't support deprecated password APIs", + isDeprecatedPasswordApisSupported()); + } } diff --git a/services/tests/uiservicestests/src/com/android/server/notification/BuzzBeepBlinkTest.java b/services/tests/uiservicestests/src/com/android/server/notification/BuzzBeepBlinkTest.java index ea46eab6e8f9..d593e8000048 100644 --- a/services/tests/uiservicestests/src/com/android/server/notification/BuzzBeepBlinkTest.java +++ b/services/tests/uiservicestests/src/com/android/server/notification/BuzzBeepBlinkTest.java @@ -32,7 +32,6 @@ import static junit.framework.Assert.assertTrue; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotEquals; -import static org.junit.Assert.assertNotNull; import static org.mockito.Matchers.any; import static org.mockito.Matchers.anyBoolean; import static org.mockito.Matchers.anyInt; @@ -40,6 +39,7 @@ import static org.mockito.Matchers.anyObject; import static org.mockito.Matchers.anyString; import static org.mockito.Matchers.argThat; import static org.mockito.Matchers.eq; +import static org.mockito.Mockito.after; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; import static org.mockito.Mockito.spy; @@ -48,6 +48,7 @@ import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; +import android.annotation.SuppressLint; import android.app.ActivityManager; import android.app.KeyguardManager; import android.app.Notification; @@ -73,7 +74,6 @@ import android.provider.Settings; import android.service.notification.NotificationListenerService; import android.service.notification.StatusBarNotification; import android.test.suitebuilder.annotation.SmallTest; -import android.util.Slog; import android.view.accessibility.AccessibilityEvent; import android.view.accessibility.AccessibilityManager; import android.view.accessibility.IAccessibilityManager; @@ -102,6 +102,7 @@ import java.util.Objects; @SmallTest @RunWith(AndroidJUnit4.class) +@SuppressLint("GuardedBy") // It's ok for this test to access guarded methods from the service. public class BuzzBeepBlinkTest extends UiServiceTestCase { @Mock AudioManager mAudioManager; @@ -156,6 +157,7 @@ public class BuzzBeepBlinkTest extends UiServiceTestCase { when(mAudioManager.getRingtonePlayer()).thenReturn(mRingtonePlayer); when(mAudioManager.getStreamVolume(anyInt())).thenReturn(10); when(mAudioManager.getRingerModeInternal()).thenReturn(AudioManager.RINGER_MODE_NORMAL); + when(mAudioManager.getFocusRampTimeMs(anyInt(), any(AudioAttributes.class))).thenReturn(50); when(mUsageStats.isAlertRateLimited(any())).thenReturn(false); when(mVibrator.hasFrequencyControl()).thenReturn(false); when(mKeyguardManager.isDeviceLocked(anyInt())).thenReturn(false); @@ -444,6 +446,11 @@ public class BuzzBeepBlinkTest extends UiServiceTestCase { timeout(MAX_VIBRATION_DELAY).times(1)); } + private void verifyDelayedNeverVibrate() { + verify(mVibrator, after(MAX_VIBRATION_DELAY).never()).vibrate(anyInt(), anyString(), any(), + anyString(), any(AudioAttributes.class)); + } + private void verifyVibrate(ArgumentMatcher<VibrationEffect> effectMatcher, VerificationMode verification) { ArgumentCaptor<AudioAttributes> captor = ArgumentCaptor.forClass(AudioAttributes.class); @@ -1588,8 +1595,51 @@ public class BuzzBeepBlinkTest extends UiServiceTestCase { // beep wasn't reset verifyNeverBeep(); verifyNeverVibrate(); - verify(mRingtonePlayer, never()).stopAsync(); - verify(mVibrator, never()).cancel(); + verifyNeverStopAudio(); + verifyNeverStopVibrate(); + } + + @Test + public void testRingtoneInsistentBeep_clearEffectsStopsSoundAndVibration() throws Exception { + NotificationChannel ringtoneChannel = + new NotificationChannel("ringtone", "", IMPORTANCE_HIGH); + ringtoneChannel.setSound(Uri.fromParts("a", "b", "c"), + new AudioAttributes.Builder().setUsage(USAGE_NOTIFICATION_RINGTONE).build()); + ringtoneChannel.enableVibration(true); + NotificationRecord ringtoneNotification = getCallRecord(1, ringtoneChannel, true); + mService.addNotification(ringtoneNotification); + assertFalse(mService.shouldMuteNotificationLocked(ringtoneNotification)); + mService.buzzBeepBlinkLocked(ringtoneNotification); + verifyBeepLooped(); + verifyDelayedVibrateLooped(); + + mService.clearSoundLocked(); + mService.clearVibrateLocked(); + + verifyStopAudio(); + verifyStopVibrate(); + } + + @Test + public void testRingtoneInsistentBeep_neverVibratesWhenEffectsClearedBeforeDelay() + throws Exception { + NotificationChannel ringtoneChannel = + new NotificationChannel("ringtone", "", IMPORTANCE_HIGH); + ringtoneChannel.setSound(Uri.fromParts("a", "b", "c"), + new AudioAttributes.Builder().setUsage(USAGE_NOTIFICATION_RINGTONE).build()); + ringtoneChannel.enableVibration(true); + NotificationRecord ringtoneNotification = getCallRecord(1, ringtoneChannel, true); + mService.addNotification(ringtoneNotification); + assertFalse(mService.shouldMuteNotificationLocked(ringtoneNotification)); + mService.buzzBeepBlinkLocked(ringtoneNotification); + verifyBeepLooped(); + verifyNeverVibrate(); + + mService.clearSoundLocked(); + mService.clearVibrateLocked(); + + verifyStopAudio(); + verifyDelayedNeverVibrate(); } @Test |