Merge "Enable protolog by default for WMShell" into tm-qpr-dev
diff --git a/core/api/current.txt b/core/api/current.txt
index 487e57d1..c451049 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -30978,6 +30978,7 @@
field public static final int S = 31; // 0x1f
field public static final int S_V2 = 32; // 0x20
field public static final int TIRAMISU = 33; // 0x21
+ field public static final int UPSIDE_DOWN_CAKE = 10000; // 0x2710
}
public final class Bundle extends android.os.BaseBundle implements java.lang.Cloneable android.os.Parcelable {
diff --git a/core/java/android/content/Intent.java b/core/java/android/content/Intent.java
index 3daee1f..809dc3c4 100644
--- a/core/java/android/content/Intent.java
+++ b/core/java/android/content/Intent.java
@@ -5741,14 +5741,13 @@
/**
* Optional argument to be used with {@link #ACTION_CHOOSER}.
- * A {@link android.app.PendingIntent} to be sent when the user wants to do payload reselection
- * in the sharesheet.
- * A reselection action allows the user to return to the source app to change the content being
- * shared.
+ * A {@link android.app.PendingIntent} to be sent when the user wants to modify the content that
+ * they're sharing. This can be used to allow the user to return to the source app to, for
+ * example, select different media.
* @hide
*/
- public static final String EXTRA_CHOOSER_PAYLOAD_RESELECTION_ACTION =
- "android.intent.extra.CHOOSER_PAYLOAD_RESELECTION_ACTION";
+ public static final String EXTRA_CHOOSER_MODIFY_SHARE_ACTION =
+ "android.intent.extra.CHOOSER_MODIFY_SHARE_ACTION";
/**
* An {@code ArrayList} of {@code String} annotations describing content for
diff --git a/core/java/android/os/Build.java b/core/java/android/os/Build.java
index 0b956f8..dbd602f 100755
--- a/core/java/android/os/Build.java
+++ b/core/java/android/os/Build.java
@@ -1167,6 +1167,11 @@
* Tiramisu.
*/
public static final int TIRAMISU = 33;
+
+ /**
+ * Upside Down Cake.
+ */
+ public static final int UPSIDE_DOWN_CAKE = CUR_DEVELOPMENT;
}
/** The type of build, like "user" or "eng". */
diff --git a/core/java/com/android/internal/policy/GestureNavigationSettingsObserver.java b/core/java/com/android/internal/policy/GestureNavigationSettingsObserver.java
index d2b612a..f1ed3be 100644
--- a/core/java/com/android/internal/policy/GestureNavigationSettingsObserver.java
+++ b/core/java/com/android/internal/policy/GestureNavigationSettingsObserver.java
@@ -56,6 +56,9 @@
}
};
+ /**
+ * Registers the observer for all users.
+ */
public void register() {
ContentResolver r = mContext.getContentResolver();
r.registerContentObserver(
@@ -73,7 +76,10 @@
mOnPropertiesChangedListener);
}
- public void registerForCurrentUser() {
+ /**
+ * Registers the observer for the calling user.
+ */
+ public void registerForCallingUser() {
ContentResolver r = mContext.getContentResolver();
r.registerContentObserver(
Settings.Secure.getUriFor(Settings.Secure.BACK_GESTURE_INSET_SCALE_LEFT),
@@ -103,12 +109,46 @@
}
}
+ /**
+ * Returns the left sensitivity for the current user. To be used in code that runs primarily
+ * in one user's process.
+ */
public int getLeftSensitivity(Resources userRes) {
- return getSensitivity(userRes, Settings.Secure.BACK_GESTURE_INSET_SCALE_LEFT);
+ final float scale = Settings.Secure.getFloatForUser(mContext.getContentResolver(),
+ Settings.Secure.BACK_GESTURE_INSET_SCALE_LEFT, 1.0f, UserHandle.USER_CURRENT);
+ return (int) (getUnscaledInset(userRes) * scale);
}
+ /**
+ * Returns the left sensitivity for the calling user. To be used in code that runs in a
+ * per-user process.
+ */
+ @SuppressWarnings("NonUserGetterCalled")
+ public int getLeftSensitivityForCallingUser(Resources userRes) {
+ final float scale = Settings.Secure.getFloat(mContext.getContentResolver(),
+ Settings.Secure.BACK_GESTURE_INSET_SCALE_LEFT, 1.0f);
+ return (int) (getUnscaledInset(userRes) * scale);
+ }
+
+ /**
+ * Returns the right sensitivity for the current user. To be used in code that runs primarily
+ * in one user's process.
+ */
public int getRightSensitivity(Resources userRes) {
- return getSensitivity(userRes, Settings.Secure.BACK_GESTURE_INSET_SCALE_RIGHT);
+ final float scale = Settings.Secure.getFloatForUser(mContext.getContentResolver(),
+ Settings.Secure.BACK_GESTURE_INSET_SCALE_RIGHT, 1.0f, UserHandle.USER_CURRENT);
+ return (int) (getUnscaledInset(userRes) * scale);
+ }
+
+ /**
+ * Returns the right sensitivity for the calling user. To be used in code that runs in a
+ * per-user process.
+ */
+ @SuppressWarnings("NonUserGetterCalled")
+ public int getRightSensitivityForCallingUser(Resources userRes) {
+ final float scale = Settings.Secure.getFloat(mContext.getContentResolver(),
+ Settings.Secure.BACK_GESTURE_INSET_SCALE_RIGHT, 1.0f);
+ return (int) (getUnscaledInset(userRes) * scale);
}
public boolean areNavigationButtonForcedVisible() {
@@ -116,7 +156,7 @@
Settings.Secure.USER_SETUP_COMPLETE, 0, UserHandle.USER_CURRENT) == 0;
}
- private int getSensitivity(Resources userRes, String side) {
+ private float getUnscaledInset(Resources userRes) {
final DisplayMetrics dm = userRes.getDisplayMetrics();
final float defaultInset = userRes.getDimension(
com.android.internal.R.dimen.config_backGestureInset) / dm.density;
@@ -127,8 +167,6 @@
: defaultInset;
final float inset = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, backGestureInset,
dm);
- final float scale = Settings.Secure.getFloatForUser(
- mContext.getContentResolver(), side, 1.0f, UserHandle.USER_CURRENT);
- return (int) (inset * scale);
+ return inset;
}
}
diff --git a/libs/WindowManager/Shell/res/values/dimen.xml b/libs/WindowManager/Shell/res/values/dimen.xml
index 6f31d06..1f9b6cf 100644
--- a/libs/WindowManager/Shell/res/values/dimen.xml
+++ b/libs/WindowManager/Shell/res/values/dimen.xml
@@ -331,30 +331,6 @@
-->
<dimen name="overridable_minimal_size_pip_resizable_task">48dp</dimen>
- <!-- The size of the drag handle / menu shown along with a floating task. -->
- <dimen name="floating_task_menu_size">32dp</dimen>
-
- <!-- The size of menu items in the floating task menu. -->
- <dimen name="floating_task_menu_item_size">24dp</dimen>
-
- <!-- The horizontal margin of menu items in the floating task menu. -->
- <dimen name="floating_task_menu_item_padding">5dp</dimen>
-
- <!-- The width of visible floating view region when stashed. -->
- <dimen name="floating_task_stash_offset">32dp</dimen>
-
- <!-- The amount of elevation for a floating task. -->
- <dimen name="floating_task_elevation">8dp</dimen>
-
- <!-- The amount of padding around the bottom and top of the task. -->
- <dimen name="floating_task_vertical_padding">8dp</dimen>
-
- <!-- The normal size of the dismiss target. -->
- <dimen name="floating_task_dismiss_circle_size">150dp</dimen>
-
- <!-- The smaller size of the dismiss target (shrinks when something is in the target). -->
- <dimen name="floating_dismiss_circle_small">120dp</dimen>
-
<!-- The thickness of shadows of a window that has focus in DIP. -->
<dimen name="freeform_decor_shadow_focused_thickness">20dp</dimen>
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipAnimationController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipAnimationController.java
index d9ac76e..23f73f6 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipAnimationController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipAnimationController.java
@@ -209,7 +209,7 @@
/**
* Quietly cancel the animator by removing the listeners first.
*/
- static void quietCancel(@NonNull ValueAnimator animator) {
+ public static void quietCancel(@NonNull ValueAnimator animator) {
animator.removeAllUpdateListeners();
animator.removeAllListeners();
animator.cancel();
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java
index ec34f73..fa3efeb 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java
@@ -715,6 +715,12 @@
private void onDisplayChanged(DisplayLayout layout, boolean saveRestoreSnapFraction) {
if (!mPipBoundsState.getDisplayLayout().isSameGeometry(layout)) {
+ PipAnimationController.PipTransitionAnimator animator =
+ mPipAnimationController.getCurrentAnimator();
+ if (animator != null && animator.isRunning()) {
+ // cancel any running animator, as it is using stale display layout information
+ PipAnimationController.quietCancel(animator);
+ }
onDisplayChangedUncheck(layout, saveRestoreSnapFraction);
}
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java
index 0c3eaf0..2a6fbd2 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java
@@ -499,6 +499,11 @@
break;
}
}
+ } else if (mSideStage.getChildCount() != 0) {
+ // There are chances the entering app transition got canceled by performing
+ // rotation transition. Checks if there is any child task existed in split
+ // screen before fallback to cancel entering flow.
+ openingToSide = true;
}
if (isEnteringSplit && !openingToSide) {
@@ -515,7 +520,7 @@
}
}
- if (!isEnteringSplit && openingToSide) {
+ if (!isEnteringSplit && apps != null) {
final WindowContainerTransaction evictWct = new WindowContainerTransaction();
prepareEvictNonOpeningChildTasks(position, apps, evictWct);
mSyncQueue.queue(evictWct);
@@ -598,6 +603,11 @@
break;
}
}
+ } else if (mSideStage.getChildCount() != 0) {
+ // There are chances the entering app transition got canceled by performing
+ // rotation transition. Checks if there is any child task existed in split
+ // screen before fallback to cancel entering flow.
+ openingToSide = true;
}
if (isEnteringSplit && !openingToSide && apps != null) {
@@ -624,7 +634,7 @@
}
- if (!isEnteringSplit && openingToSide) {
+ if (!isEnteringSplit && apps != null) {
final WindowContainerTransaction evictWct = new WindowContainerTransaction();
prepareEvictNonOpeningChildTasks(position, apps, evictWct);
mSyncQueue.queue(evictWct);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragResizeInputListener.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragResizeInputListener.java
index 7d954ad..81c4176 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragResizeInputListener.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragResizeInputListener.java
@@ -215,6 +215,7 @@
@Override
public void close() {
+ mInputEventReceiver.dispose();
mInputChannel.dispose();
try {
mWindowSession.remove(mFakeWindow);
diff --git a/media/tests/MediaFrameworkTest/AndroidManifest.xml b/media/tests/MediaFrameworkTest/AndroidManifest.xml
index 33872ed..0da5de7 100644
--- a/media/tests/MediaFrameworkTest/AndroidManifest.xml
+++ b/media/tests/MediaFrameworkTest/AndroidManifest.xml
@@ -37,7 +37,6 @@
</activity>
<activity android:label="Camera2CtsActivity"
android:name="Camera2SurfaceViewActivity"
- android:screenOrientation="landscape"
android:configChanges="keyboardHidden|orientation|screenSize">
</activity>
</application>
diff --git a/packages/SettingsLib/ActivityEmbedding/src/com/android/settingslib/activityembedding/ActivityEmbeddingUtils.java b/packages/SettingsLib/ActivityEmbedding/src/com/android/settingslib/activityembedding/ActivityEmbeddingUtils.java
index 2db0a8f..33fc37e 100644
--- a/packages/SettingsLib/ActivityEmbedding/src/com/android/settingslib/activityembedding/ActivityEmbeddingUtils.java
+++ b/packages/SettingsLib/ActivityEmbedding/src/com/android/settingslib/activityembedding/ActivityEmbeddingUtils.java
@@ -25,7 +25,7 @@
import android.util.Log;
import androidx.core.os.BuildCompat;
-import androidx.window.embedding.SplitController;
+import androidx.window.embedding.ActivityEmbeddingController;
import com.android.settingslib.utils.BuildCompatUtils;
@@ -86,7 +86,7 @@
* @param activity Activity that needs the check
*/
public static boolean isActivityEmbedded(Activity activity) {
- return SplitController.getInstance().isActivityEmbedded(activity);
+ return ActivityEmbeddingController.getInstance(activity).isActivityEmbedded(activity);
}
/**
diff --git a/packages/SystemUI/docs/device-entry/quickaffordance.md b/packages/SystemUI/docs/device-entry/quickaffordance.md
index 79d5718..d662649 100644
--- a/packages/SystemUI/docs/device-entry/quickaffordance.md
+++ b/packages/SystemUI/docs/device-entry/quickaffordance.md
@@ -52,6 +52,11 @@
* Unselect an already-selected quick affordance from a slot
* Unselect all already-selected quick affordances from a slot
+## Device Policy
+Returning `DevicePolicyManager.KEYGUARD_DISABLE_FEATURES_ALL` or
+`DevicePolicyManager.KEYGUARD_DISABLE_SHORTCUTS_ALL` from
+`DevicePolicyManager#getKeyguardDisabledFeatures` will disable the keyguard quick affordance feature on the device.
+
## Testing
* Add a unit test for your implementation of `KeyguardQuickAffordanceConfig`
* Manually verify that your implementation works in multi-user environments from both the main user and a secondary user
diff --git a/packages/SystemUI/plugin/bcsmartspace/src/com/android/systemui/plugins/BcSmartspaceDataPlugin.java b/packages/SystemUI/plugin/bcsmartspace/src/com/android/systemui/plugins/BcSmartspaceDataPlugin.java
index e0cc8f4..e0d0184 100644
--- a/packages/SystemUI/plugin/bcsmartspace/src/com/android/systemui/plugins/BcSmartspaceDataPlugin.java
+++ b/packages/SystemUI/plugin/bcsmartspace/src/com/android/systemui/plugins/BcSmartspaceDataPlugin.java
@@ -118,12 +118,6 @@
void setPrimaryTextColor(int color);
/**
- * When the view is displayed on Dream, set the flag to true, immediately after the view is
- * created.
- */
- void setIsDreaming(boolean isDreaming);
-
- /**
* Set the UI surface for the cards. Should be called immediately after the view is created.
*/
void setUiSurface(String uiSurface);
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index e6ac59e..464ce03 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -479,6 +479,8 @@
<string name="accessibility_desc_notification_shade">Notification shade.</string>
<!-- Content description for the quick settings panel (not shown on the screen). [CHAR LIMIT=NONE] -->
<string name="accessibility_desc_quick_settings">Quick settings.</string>
+ <!-- Content description for the split notification shade that also includes QS (not shown on the screen). [CHAR LIMIT=NONE] -->
+ <string name="accessibility_desc_qs_notification_shade">Quick settings and Notification shade.</string>
<!-- Content description for the lock screen (not shown on the screen). [CHAR LIMIT=NONE] -->
<string name="accessibility_desc_lock_screen">Lock screen.</string>
<!-- Content description for the work profile lock screen. This prevents work profile apps from being used, but personal apps can be used as normal (not shown on the screen). [CHAR LIMIT=NONE] -->
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
index be9264d..8e11a99 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
@@ -2765,7 +2765,8 @@
boolean shouldListen = shouldListenKeyguardState && shouldListenUserState
&& shouldListenBouncerState && shouldListenUdfpsState
- && shouldListenSideFpsState;
+ && shouldListenSideFpsState
+ && !isFingerprintLockedOut();
logListenerModelData(
new KeyguardFingerprintListenModel(
System.currentTimeMillis(),
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java b/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java
index 2dc0cd3..3302073 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java
@@ -129,7 +129,6 @@
private float mScaleFactor = 1f;
// sensor locations without any resolution scaling nor rotation adjustments:
@Nullable private final Point mFaceSensorLocationDefault;
- @Nullable private final Point mFingerprintSensorLocationDefault;
// cached sensor locations:
@Nullable private Point mFaceSensorLocation;
@Nullable private Point mFingerprintSensorLocation;
@@ -586,11 +585,23 @@
@Nullable private Point getFingerprintSensorLocationInNaturalOrientation() {
if (getUdfpsLocation() != null) {
return getUdfpsLocation();
+ } else {
+ int xFpLocation = mCachedDisplayInfo.getNaturalWidth() / 2;
+ try {
+ xFpLocation = mContext.getResources().getDimensionPixelSize(
+ com.android.systemui.R.dimen
+ .physical_fingerprint_sensor_center_screen_location_x);
+ } catch (Resources.NotFoundException e) {
+ }
+
+ return new Point(
+ (int) (xFpLocation * mScaleFactor),
+ (int) (mContext.getResources().getDimensionPixelSize(
+ com.android.systemui.R.dimen
+ .physical_fingerprint_sensor_center_screen_location_y)
+ * mScaleFactor)
+ );
}
- return new Point(
- (int) (mFingerprintSensorLocationDefault.x * mScaleFactor),
- (int) (mFingerprintSensorLocationDefault.y * mScaleFactor)
- );
}
/**
@@ -774,19 +785,6 @@
}
mDisplay = mContext.getDisplay();
- mDisplay.getDisplayInfo(mCachedDisplayInfo);
- int xFpLocation = mCachedDisplayInfo.getNaturalWidth() / 2;
- try {
- xFpLocation = mContext.getResources().getDimensionPixelSize(
- com.android.systemui.R.dimen
- .physical_fingerprint_sensor_center_screen_location_x);
- } catch (Resources.NotFoundException e) {
- }
- mFingerprintSensorLocationDefault = new Point(
- xFpLocation,
- mContext.getResources().getDimensionPixelSize(com.android.systemui.R.dimen
- .physical_fingerprint_sensor_center_screen_location_y)
- );
updateSensorLocations();
IntentFilter filter = new IntentFilter();
@@ -1246,7 +1244,6 @@
pw.println(" mScaleFactor=" + mScaleFactor);
pw.println(" faceAuthSensorLocationDefault=" + mFaceSensorLocationDefault);
pw.println(" faceAuthSensorLocation=" + getFaceSensorLocation());
- pw.println(" fingerprintSensorLocationDefault=" + mFingerprintSensorLocationDefault);
pw.println(" fingerprintSensorLocationInNaturalOrientation="
+ getFingerprintSensorLocationInNaturalOrientation());
pw.println(" fingerprintSensorLocation=" + getFingerprintSensorLocation());
diff --git a/packages/SystemUI/src/com/android/systemui/classifier/BrightLineFalsingManager.java b/packages/SystemUI/src/com/android/systemui/classifier/BrightLineFalsingManager.java
index e9ac840..f6b7133 100644
--- a/packages/SystemUI/src/com/android/systemui/classifier/BrightLineFalsingManager.java
+++ b/packages/SystemUI/src/com/android/systemui/classifier/BrightLineFalsingManager.java
@@ -402,7 +402,7 @@
|| mAccessibilityManager.isTouchExplorationEnabled()
|| mDataProvider.isA11yAction()
|| (mFeatureFlags.isEnabled(Flags.FALSING_OFF_FOR_UNFOLDED)
- && !mDataProvider.isFolded());
+ && mDataProvider.isUnfolded());
}
@Override
diff --git a/packages/SystemUI/src/com/android/systemui/classifier/FalsingDataProvider.java b/packages/SystemUI/src/com/android/systemui/classifier/FalsingDataProvider.java
index 5f347c1..bc0f995 100644
--- a/packages/SystemUI/src/com/android/systemui/classifier/FalsingDataProvider.java
+++ b/packages/SystemUI/src/com/android/systemui/classifier/FalsingDataProvider.java
@@ -380,8 +380,8 @@
return mBatteryController.isWirelessCharging() || mDockManager.isDocked();
}
- public boolean isFolded() {
- return Boolean.TRUE.equals(mFoldStateListener.getFolded());
+ public boolean isUnfolded() {
+ return Boolean.FALSE.equals(mFoldStateListener.getFolded());
}
/** Implement to be alerted abotu the beginning and ending of falsing tracking. */
diff --git a/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsActivity.kt b/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsActivity.kt
index d8d8c0e..3a3f9b4 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsActivity.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsActivity.kt
@@ -134,7 +134,8 @@
super.onStop()
mExitToDream = false
- uiController.hide()
+ // parent is set in onStart, so the field is initialized when we get here
+ uiController.hide(parent)
controlsSettingsDialogManager.closeDialog()
}
diff --git a/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsUiController.kt b/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsUiController.kt
index c1cec9d..58673bb 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsUiController.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsUiController.kt
@@ -31,7 +31,13 @@
}
fun show(parent: ViewGroup, onDismiss: Runnable, activityContext: Context)
- fun hide()
+
+ /**
+ * Hide the controls content if it's attached to this parent.
+ */
+ fun hide(parent: ViewGroup)
+
+ val isShowing: Boolean
/**
* Returns the preferred activity to start, depending on if the user has favorited any
diff --git a/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsUiControllerImpl.kt b/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsUiControllerImpl.kt
index 58f4835..9405c60 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsUiControllerImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsUiControllerImpl.kt
@@ -168,6 +168,9 @@
private lateinit var activityContext: Context
private lateinit var listingCallback: ControlsListingController.ControlsListingCallback
+ override val isShowing: Boolean
+ get() = !hidden
+
init {
dumpManager.registerDumpable(javaClass.name, this)
}
@@ -727,21 +730,27 @@
controlActionCoordinator.closeDialogs()
}
- override fun hide() {
- hidden = true
+ override fun hide(parent: ViewGroup) {
+ // We need to check for the parent because it's possible that we have started showing in a
+ // different activity. In that case, make sure to only clear things associated with the
+ // passed parent
+ if (parent == this.parent) {
+ Log.d(ControlsUiController.TAG, "hide()")
+ hidden = true
- closeDialogs(true)
- controlsController.get().unsubscribe()
- taskViewController?.dismiss()
- taskViewController = null
+ closeDialogs(true)
+ controlsController.get().unsubscribe()
+ taskViewController?.dismiss()
+ taskViewController = null
+ controlsById.clear()
+ controlViewsById.clear()
+
+ controlsListingController.get().removeCallback(listingCallback)
+
+ if (!retainCache) RenderInfo.clearCache()
+ }
parent.removeAllViews()
- controlsById.clear()
- controlViewsById.clear()
-
- controlsListingController.get().removeCallback(listingCallback)
-
- if (!retainCache) RenderInfo.clearCache()
}
override fun onRefreshState(componentName: ComponentName, controls: List<Control>) {
diff --git a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
index 2d81ec8..9fe1739 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
+++ b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
@@ -59,7 +59,7 @@
)
// TODO(b/254512517): Tracking Bug
- val FSI_REQUIRES_KEYGUARD = unreleasedFlag(110, "fsi_requires_keyguard", teamfood = true)
+ val FSI_REQUIRES_KEYGUARD = releasedFlag(110, "fsi_requires_keyguard")
// TODO(b/259130119): Tracking Bug
val FSI_ON_DND_UPDATE = unreleasedFlag(259130119, "fsi_on_dnd_update", teamfood = true)
@@ -187,7 +187,7 @@
// TODO(b/262780002): Tracking Bug
@JvmField
- val REVAMPED_WALLPAPER_UI = unreleasedFlag(222, "revamped_wallpaper_ui", teamfood = false)
+ val REVAMPED_WALLPAPER_UI = unreleasedFlag(222, "revamped_wallpaper_ui", teamfood = true)
/** A different path for unocclusion transitions back to keyguard */
// TODO(b/262859270): Tracking Bug
@@ -217,6 +217,7 @@
unreleasedFlag(
228,
"lock_screen_long_press_enabled",
+ teamfood = true,
)
// 300 - power menu
diff --git a/packages/SystemUI/src/com/android/systemui/flags/ServerFlagReader.kt b/packages/SystemUI/src/com/android/systemui/flags/ServerFlagReader.kt
index 7acd3f3..9b748d0 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/ServerFlagReader.kt
+++ b/packages/SystemUI/src/com/android/systemui/flags/ServerFlagReader.kt
@@ -57,6 +57,7 @@
override fun onPropertiesChanged(properties: DeviceConfig.Properties) {
if (isTestHarness) {
Log.w(TAG, "Ignore server flag changes in Test Harness mode.")
+ return
}
if (properties.namespace != namespace) {
return
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/CustomizationProvider.kt b/packages/SystemUI/src/com/android/systemui/keyguard/CustomizationProvider.kt
index 680c504..27a5974 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/CustomizationProvider.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/CustomizationProvider.kt
@@ -131,7 +131,7 @@
throw UnsupportedOperationException()
}
- return insertSelection(values)
+ return runBlocking(mainDispatcher) { insertSelection(values) }
}
override fun query(
@@ -171,7 +171,7 @@
throw UnsupportedOperationException()
}
- return deleteSelection(uri, selectionArgs)
+ return runBlocking(mainDispatcher) { deleteSelection(uri, selectionArgs) }
}
override fun call(method: String, arg: String?, extras: Bundle?): Bundle? {
@@ -189,7 +189,7 @@
}
}
- private fun insertSelection(values: ContentValues?): Uri? {
+ private suspend fun insertSelection(values: ContentValues?): Uri? {
if (values == null) {
throw IllegalArgumentException("Cannot insert selection, no values passed in!")
}
@@ -311,7 +311,7 @@
}
}
- private fun querySlots(): Cursor {
+ private suspend fun querySlots(): Cursor {
return MatrixCursor(
arrayOf(
Contract.LockScreenQuickAffordances.SlotTable.Columns.ID,
@@ -330,7 +330,7 @@
}
}
- private fun queryFlags(): Cursor {
+ private suspend fun queryFlags(): Cursor {
return MatrixCursor(
arrayOf(
Contract.FlagsTable.Columns.NAME,
@@ -353,7 +353,7 @@
}
}
- private fun deleteSelection(
+ private suspend fun deleteSelection(
uri: Uri,
selectionArgs: Array<out String>?,
): Int {
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardFaceAuthManager.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardFaceAuthManager.kt
new file mode 100644
index 0000000..2069891
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardFaceAuthManager.kt
@@ -0,0 +1,342 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.keyguard.data.repository
+
+import android.app.StatusBarManager
+import android.content.Context
+import android.hardware.face.FaceManager
+import android.os.CancellationSignal
+import com.android.internal.logging.InstanceId
+import com.android.internal.logging.UiEventLogger
+import com.android.keyguard.FaceAuthUiEvent
+import com.android.systemui.Dumpable
+import com.android.systemui.R
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.dagger.qualifiers.Main
+import com.android.systemui.dump.DumpManager
+import com.android.systemui.keyguard.shared.model.AcquiredAuthenticationStatus
+import com.android.systemui.keyguard.shared.model.AuthenticationStatus
+import com.android.systemui.keyguard.shared.model.DetectionStatus
+import com.android.systemui.keyguard.shared.model.ErrorAuthenticationStatus
+import com.android.systemui.keyguard.shared.model.FailedAuthenticationStatus
+import com.android.systemui.keyguard.shared.model.HelpAuthenticationStatus
+import com.android.systemui.keyguard.shared.model.SuccessAuthenticationStatus
+import com.android.systemui.log.FaceAuthenticationLogger
+import com.android.systemui.log.SessionTracker
+import com.android.systemui.statusbar.phone.KeyguardBypassController
+import com.android.systemui.user.data.repository.UserRepository
+import java.io.PrintWriter
+import java.util.Arrays
+import java.util.stream.Collectors
+import javax.inject.Inject
+import kotlinx.coroutines.CoroutineDispatcher
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.Job
+import kotlinx.coroutines.delay
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.filterNotNull
+import kotlinx.coroutines.launch
+import kotlinx.coroutines.withContext
+
+/**
+ * API to run face authentication and detection for device entry / on keyguard (as opposed to the
+ * biometric prompt).
+ */
+interface KeyguardFaceAuthManager {
+ /**
+ * Trigger face authentication.
+ *
+ * [uiEvent] provided should be logged whenever face authentication runs. Invocation should be
+ * ignored if face authentication is already running. Results should be propagated through
+ * [authenticationStatus]
+ */
+ suspend fun authenticate(uiEvent: FaceAuthUiEvent)
+
+ /**
+ * Trigger face detection.
+ *
+ * Invocation should be ignored if face authentication is currently running.
+ */
+ suspend fun detect()
+
+ /** Stop currently running face authentication or detection. */
+ fun cancel()
+
+ /** Provide the current status of face authentication. */
+ val authenticationStatus: Flow<AuthenticationStatus>
+
+ /** Provide the current status of face detection. */
+ val detectionStatus: Flow<DetectionStatus>
+
+ /** Current state of whether face authentication is locked out or not. */
+ val isLockedOut: Flow<Boolean>
+
+ /** Current state of whether face authentication is running. */
+ val isAuthRunning: Flow<Boolean>
+
+ /** Is face detection supported. */
+ val isDetectionSupported: Boolean
+}
+
+@SysUISingleton
+class KeyguardFaceAuthManagerImpl
+@Inject
+constructor(
+ context: Context,
+ private val faceManager: FaceManager? = null,
+ private val userRepository: UserRepository,
+ private val keyguardBypassController: KeyguardBypassController? = null,
+ @Application private val applicationScope: CoroutineScope,
+ @Main private val mainDispatcher: CoroutineDispatcher,
+ private val sessionTracker: SessionTracker,
+ private val uiEventsLogger: UiEventLogger,
+ private val faceAuthLogger: FaceAuthenticationLogger,
+ dumpManager: DumpManager,
+) : KeyguardFaceAuthManager, Dumpable {
+ private var cancellationSignal: CancellationSignal? = null
+ private val lockscreenBypassEnabled: Boolean
+ get() = keyguardBypassController?.bypassEnabled ?: false
+ private var faceAcquiredInfoIgnoreList: Set<Int>
+
+ private val faceLockoutResetCallback =
+ object : FaceManager.LockoutResetCallback() {
+ override fun onLockoutReset(sensorId: Int) {
+ _isLockedOut.value = false
+ }
+ }
+
+ init {
+ faceManager?.addLockoutResetCallback(faceLockoutResetCallback)
+ faceAcquiredInfoIgnoreList =
+ Arrays.stream(
+ context.resources.getIntArray(
+ R.array.config_face_acquire_device_entry_ignorelist
+ )
+ )
+ .boxed()
+ .collect(Collectors.toSet())
+ dumpManager.registerCriticalDumpable("KeyguardFaceAuthManagerImpl", this)
+ }
+
+ private val faceAuthCallback =
+ object : FaceManager.AuthenticationCallback() {
+ override fun onAuthenticationFailed() {
+ _authenticationStatus.value = FailedAuthenticationStatus
+ faceAuthLogger.authenticationFailed()
+ onFaceAuthRequestCompleted()
+ }
+
+ override fun onAuthenticationAcquired(acquireInfo: Int) {
+ _authenticationStatus.value = AcquiredAuthenticationStatus(acquireInfo)
+ faceAuthLogger.authenticationAcquired(acquireInfo)
+ }
+
+ override fun onAuthenticationError(errorCode: Int, errString: CharSequence?) {
+ val errorStatus = ErrorAuthenticationStatus(errorCode, errString.toString())
+ if (errorStatus.isLockoutError()) {
+ _isLockedOut.value = true
+ }
+ _authenticationStatus.value = errorStatus
+ if (errorStatus.isCancellationError()) {
+ cancelNotReceivedHandlerJob?.cancel()
+ applicationScope.launch {
+ faceAuthLogger.launchingQueuedFaceAuthRequest(
+ faceAuthRequestedWhileCancellation
+ )
+ faceAuthRequestedWhileCancellation?.let { authenticate(it) }
+ faceAuthRequestedWhileCancellation = null
+ }
+ }
+ faceAuthLogger.authenticationError(
+ errorCode,
+ errString,
+ errorStatus.isLockoutError(),
+ errorStatus.isCancellationError()
+ )
+ onFaceAuthRequestCompleted()
+ }
+
+ override fun onAuthenticationHelp(code: Int, helpStr: CharSequence?) {
+ if (faceAcquiredInfoIgnoreList.contains(code)) {
+ return
+ }
+ _authenticationStatus.value = HelpAuthenticationStatus(code, helpStr.toString())
+ }
+
+ override fun onAuthenticationSucceeded(result: FaceManager.AuthenticationResult) {
+ _authenticationStatus.value = SuccessAuthenticationStatus(result)
+ faceAuthLogger.faceAuthSuccess(result)
+ onFaceAuthRequestCompleted()
+ }
+ }
+
+ private fun onFaceAuthRequestCompleted() {
+ cancellationInProgress = false
+ _isAuthRunning.value = false
+ cancellationSignal = null
+ }
+
+ private val detectionCallback =
+ FaceManager.FaceDetectionCallback { sensorId, userId, isStrong ->
+ faceAuthLogger.faceDetected()
+ _detectionStatus.value = DetectionStatus(sensorId, userId, isStrong)
+ }
+
+ private var cancellationInProgress = false
+ private var faceAuthRequestedWhileCancellation: FaceAuthUiEvent? = null
+
+ override suspend fun authenticate(uiEvent: FaceAuthUiEvent) {
+ if (_isAuthRunning.value) {
+ faceAuthLogger.ignoredFaceAuthTrigger(uiEvent)
+ return
+ }
+
+ if (cancellationInProgress) {
+ faceAuthLogger.queuingRequestWhileCancelling(
+ faceAuthRequestedWhileCancellation,
+ uiEvent
+ )
+ faceAuthRequestedWhileCancellation = uiEvent
+ return
+ } else {
+ faceAuthRequestedWhileCancellation = null
+ }
+
+ withContext(mainDispatcher) {
+ // We always want to invoke face auth in the main thread.
+ cancellationSignal = CancellationSignal()
+ _isAuthRunning.value = true
+ uiEventsLogger.logWithInstanceIdAndPosition(
+ uiEvent,
+ 0,
+ null,
+ keyguardSessionId,
+ uiEvent.extraInfo
+ )
+ faceAuthLogger.authenticating(uiEvent)
+ faceManager?.authenticate(
+ null,
+ cancellationSignal,
+ faceAuthCallback,
+ null,
+ currentUserId,
+ lockscreenBypassEnabled
+ )
+ }
+ }
+
+ override suspend fun detect() {
+ if (!isDetectionSupported) {
+ faceAuthLogger.detectionNotSupported(faceManager, faceManager?.sensorPropertiesInternal)
+ return
+ }
+ if (_isAuthRunning.value) {
+ faceAuthLogger.skippingBecauseAlreadyRunning("detection")
+ return
+ }
+
+ cancellationSignal = CancellationSignal()
+ withContext(mainDispatcher) {
+ // We always want to invoke face detect in the main thread.
+ faceAuthLogger.faceDetectionStarted()
+ faceManager?.detectFace(cancellationSignal, detectionCallback, currentUserId)
+ }
+ }
+
+ private val currentUserId: Int
+ get() = userRepository.getSelectedUserInfo().id
+
+ override fun cancel() {
+ if (cancellationSignal == null) return
+
+ cancellationSignal?.cancel()
+ cancelNotReceivedHandlerJob =
+ applicationScope.launch {
+ delay(DEFAULT_CANCEL_SIGNAL_TIMEOUT)
+ faceAuthLogger.cancelSignalNotReceived(
+ _isAuthRunning.value,
+ _isLockedOut.value,
+ cancellationInProgress,
+ faceAuthRequestedWhileCancellation
+ )
+ onFaceAuthRequestCompleted()
+ }
+ cancellationInProgress = true
+ _isAuthRunning.value = false
+ }
+
+ private var cancelNotReceivedHandlerJob: Job? = null
+
+ private val _authenticationStatus: MutableStateFlow<AuthenticationStatus?> =
+ MutableStateFlow(null)
+ override val authenticationStatus: Flow<AuthenticationStatus>
+ get() = _authenticationStatus.filterNotNull()
+
+ private val _detectionStatus = MutableStateFlow<DetectionStatus?>(null)
+ override val detectionStatus: Flow<DetectionStatus>
+ get() = _detectionStatus.filterNotNull()
+
+ private val _isLockedOut = MutableStateFlow(false)
+ override val isLockedOut: Flow<Boolean> = _isLockedOut
+
+ override val isDetectionSupported =
+ faceManager?.sensorPropertiesInternal?.firstOrNull()?.supportsFaceDetection ?: false
+
+ private val _isAuthRunning = MutableStateFlow(false)
+ override val isAuthRunning: Flow<Boolean>
+ get() = _isAuthRunning
+
+ private val keyguardSessionId: InstanceId?
+ get() = sessionTracker.getSessionId(StatusBarManager.SESSION_KEYGUARD)
+
+ companion object {
+ const val TAG = "KeyguardFaceAuthManager"
+
+ /**
+ * If no cancel signal has been received after this amount of time, assume that it is
+ * cancelled.
+ */
+ const val DEFAULT_CANCEL_SIGNAL_TIMEOUT = 3000L
+ }
+
+ override fun dump(pw: PrintWriter, args: Array<out String>) {
+ pw.println("KeyguardFaceAuthManagerImpl state:")
+ pw.println(" cancellationInProgress: $cancellationInProgress")
+ pw.println(" _isLockedOut.value: ${_isLockedOut.value}")
+ pw.println(" _isAuthRunning.value: ${_isAuthRunning.value}")
+ pw.println(" isDetectionSupported: $isDetectionSupported")
+ pw.println(" FaceManager state:")
+ pw.println(" faceManager: $faceManager")
+ pw.println(" sensorPropertiesInternal: ${faceManager?.sensorPropertiesInternal}")
+ pw.println(
+ " supportsFaceDetection: " +
+ "${faceManager?.sensorPropertiesInternal?.firstOrNull()?.supportsFaceDetection}"
+ )
+ pw.println(
+ " faceAuthRequestedWhileCancellation: ${faceAuthRequestedWhileCancellation?.reason}"
+ )
+ pw.println(" cancellationSignal: $cancellationSignal")
+ pw.println(" faceAcquiredInfoIgnoreList: $faceAcquiredInfoIgnoreList")
+ pw.println(" _authenticationStatus: ${_authenticationStatus.value}")
+ pw.println(" _detectionStatus: ${_detectionStatus.value}")
+ pw.println(" currentUserId: $currentUserId")
+ pw.println(" keyguardSessionId: $keyguardSessionId")
+ pw.println(" lockscreenBypassEnabled: $lockscreenBypassEnabled")
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractor.kt
index b2da793..dfbe1c2 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractor.kt
@@ -18,12 +18,14 @@
package com.android.systemui.keyguard.domain.interactor
import android.app.AlertDialog
+import android.app.admin.DevicePolicyManager
import android.content.Intent
import android.util.Log
import com.android.internal.widget.LockPatternUtils
import com.android.systemui.animation.DialogLaunchAnimator
import com.android.systemui.animation.Expandable
import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.flags.FeatureFlags
import com.android.systemui.flags.Flags
import com.android.systemui.keyguard.data.quickaffordance.KeyguardQuickAffordanceConfig
@@ -41,13 +43,17 @@
import com.android.systemui.statusbar.policy.KeyguardStateController
import dagger.Lazy
import javax.inject.Inject
+import kotlinx.coroutines.CoroutineDispatcher
+import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.flatMapLatest
import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.onStart
+import kotlinx.coroutines.withContext
+@OptIn(ExperimentalCoroutinesApi::class)
@SysUISingleton
class KeyguardQuickAffordanceInteractor
@Inject
@@ -61,6 +67,8 @@
private val featureFlags: FeatureFlags,
private val repository: Lazy<KeyguardQuickAffordanceRepository>,
private val launchAnimator: DialogLaunchAnimator,
+ private val devicePolicyManager: DevicePolicyManager,
+ @Background private val backgroundDispatcher: CoroutineDispatcher,
) {
private val isUsingRepository: Boolean
get() = featureFlags.isEnabled(Flags.CUSTOMIZABLE_LOCK_SCREEN_QUICK_AFFORDANCES)
@@ -74,9 +82,13 @@
get() = featureFlags.isEnabled(Flags.CUSTOMIZABLE_LOCK_SCREEN_QUICK_AFFORDANCES)
/** Returns an observable for the quick affordance at the given position. */
- fun quickAffordance(
+ suspend fun quickAffordance(
position: KeyguardQuickAffordancePosition
): Flow<KeyguardQuickAffordanceModel> {
+ if (isFeatureDisabledByDevicePolicy()) {
+ return flowOf(KeyguardQuickAffordanceModel.Hidden)
+ }
+
return combine(
quickAffordanceAlwaysVisible(position),
keyguardInteractor.isDozing,
@@ -148,13 +160,20 @@
*
* @return `true` if the affordance was selected successfully; `false` otherwise.
*/
- fun select(slotId: String, affordanceId: String): Boolean {
+ suspend fun select(slotId: String, affordanceId: String): Boolean {
check(isUsingRepository)
+ if (isFeatureDisabledByDevicePolicy()) {
+ return false
+ }
val slots = repository.get().getSlotPickerRepresentations()
val slot = slots.find { it.id == slotId } ?: return false
val selections =
- repository.get().getCurrentSelections().getOrDefault(slotId, emptyList()).toMutableList()
+ repository
+ .get()
+ .getCurrentSelections()
+ .getOrDefault(slotId, emptyList())
+ .toMutableList()
val alreadySelected = selections.remove(affordanceId)
if (!alreadySelected) {
while (selections.size > 0 && selections.size >= slot.maxSelectedAffordances) {
@@ -183,8 +202,11 @@
* @return `true` if the affordance was successfully removed; `false` otherwise (for example, if
* the affordance was not on the slot to begin with).
*/
- fun unselect(slotId: String, affordanceId: String?): Boolean {
+ suspend fun unselect(slotId: String, affordanceId: String?): Boolean {
check(isUsingRepository)
+ if (isFeatureDisabledByDevicePolicy()) {
+ return false
+ }
val slots = repository.get().getSlotPickerRepresentations()
if (slots.find { it.id == slotId } == null) {
@@ -203,7 +225,11 @@
}
val selections =
- repository.get().getCurrentSelections().getOrDefault(slotId, emptyList()).toMutableList()
+ repository
+ .get()
+ .getCurrentSelections()
+ .getOrDefault(slotId, emptyList())
+ .toMutableList()
return if (selections.remove(affordanceId)) {
repository
.get()
@@ -219,6 +245,10 @@
/** Returns affordance IDs indexed by slot ID, for all known slots. */
suspend fun getSelections(): Map<String, List<KeyguardQuickAffordancePickerRepresentation>> {
+ if (isFeatureDisabledByDevicePolicy()) {
+ return emptyMap()
+ }
+
val slots = repository.get().getSlotPickerRepresentations()
val selections = repository.get().getCurrentSelections()
val affordanceById =
@@ -343,13 +373,17 @@
return repository.get().getAffordancePickerRepresentations()
}
- fun getSlotPickerRepresentations(): List<KeyguardSlotPickerRepresentation> {
+ suspend fun getSlotPickerRepresentations(): List<KeyguardSlotPickerRepresentation> {
check(isUsingRepository)
+ if (isFeatureDisabledByDevicePolicy()) {
+ return emptyList()
+ }
+
return repository.get().getSlotPickerRepresentations()
}
- fun getPickerFlags(): List<KeyguardPickerFlag> {
+ suspend fun getPickerFlags(): List<KeyguardPickerFlag> {
return listOf(
KeyguardPickerFlag(
name = Contract.FlagsTable.FLAG_NAME_REVAMPED_WALLPAPER_UI,
@@ -357,7 +391,9 @@
),
KeyguardPickerFlag(
name = Contract.FlagsTable.FLAG_NAME_CUSTOM_LOCK_SCREEN_QUICK_AFFORDANCES_ENABLED,
- value = featureFlags.isEnabled(Flags.CUSTOMIZABLE_LOCK_SCREEN_QUICK_AFFORDANCES),
+ value =
+ !isFeatureDisabledByDevicePolicy() &&
+ featureFlags.isEnabled(Flags.CUSTOMIZABLE_LOCK_SCREEN_QUICK_AFFORDANCES),
),
KeyguardPickerFlag(
name = Contract.FlagsTable.FLAG_NAME_CUSTOM_CLOCKS_ENABLED,
@@ -374,6 +410,17 @@
)
}
+ private suspend fun isFeatureDisabledByDevicePolicy(): Boolean {
+ val flags =
+ withContext(backgroundDispatcher) {
+ devicePolicyManager.getKeyguardDisabledFeatures(null, userTracker.userId)
+ }
+ val flagsToCheck =
+ DevicePolicyManager.KEYGUARD_DISABLE_FEATURES_ALL or
+ DevicePolicyManager.KEYGUARD_DISABLE_SHORTCUTS_ALL
+ return flagsToCheck and flags != 0
+ }
+
companion object {
private const val TAG = "KeyguardQuickAffordanceInteractor"
private const val DELIMITER = "::"
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/FaceAuthenticationModels.kt b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/FaceAuthenticationModels.kt
new file mode 100644
index 0000000..b1c5f8f
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/FaceAuthenticationModels.kt
@@ -0,0 +1,53 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.keyguard.shared.model
+
+import android.hardware.face.FaceManager
+
+/** Authentication status provided by [com.android.keyguard.faceauth.KeyguardFaceAuthManager] */
+sealed class AuthenticationStatus
+
+/** Success authentication status. */
+data class SuccessAuthenticationStatus(val successResult: FaceManager.AuthenticationResult) :
+ AuthenticationStatus()
+
+/** Face authentication help message. */
+data class HelpAuthenticationStatus(val msgId: Int, val msg: String?) : AuthenticationStatus()
+
+/** Face acquired message. */
+data class AcquiredAuthenticationStatus(val acquiredInfo: Int) : AuthenticationStatus()
+
+/** Face authentication failed message. */
+object FailedAuthenticationStatus : AuthenticationStatus()
+
+/** Face authentication error message */
+data class ErrorAuthenticationStatus(val msgId: Int, val msg: String?) : AuthenticationStatus() {
+ /**
+ * Method that checks if [msgId] is a lockout error. A lockout error means that face
+ * authentication is locked out.
+ */
+ fun isLockoutError() = msgId == FaceManager.FACE_ERROR_LOCKOUT_PERMANENT
+
+ /**
+ * Method that checks if [msgId] is a cancellation error. This means that face authentication
+ * was cancelled before it completed.
+ */
+ fun isCancellationError() = msgId == FaceManager.FACE_ERROR_CANCELED
+}
+
+/** Face detection success message. */
+data class DetectionStatus(val sensorId: Int, val userId: Int, val isStrongBiometric: Boolean)
diff --git a/packages/SystemUI/src/com/android/systemui/log/FaceAuthenticationLogger.kt b/packages/SystemUI/src/com/android/systemui/log/FaceAuthenticationLogger.kt
new file mode 100644
index 0000000..f7349a2
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/log/FaceAuthenticationLogger.kt
@@ -0,0 +1,181 @@
+package com.android.systemui.log
+
+import android.hardware.face.FaceManager
+import android.hardware.face.FaceSensorPropertiesInternal
+import com.android.keyguard.FaceAuthUiEvent
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.log.dagger.FaceAuthLog
+import com.android.systemui.plugins.log.LogBuffer
+import com.android.systemui.plugins.log.LogLevel.DEBUG
+import com.google.errorprone.annotations.CompileTimeConstant
+import javax.inject.Inject
+
+private const val TAG = "KeyguardFaceAuthManagerLog"
+
+/**
+ * Helper class for logging for [com.android.keyguard.faceauth.KeyguardFaceAuthManager]
+ *
+ * To enable logcat echoing for an entire buffer:
+ *
+ * ```
+ * adb shell settings put global systemui/buffer/KeyguardFaceAuthManagerLog <logLevel>
+ *
+ * ```
+ */
+@SysUISingleton
+class FaceAuthenticationLogger
+@Inject
+constructor(
+ @FaceAuthLog private val logBuffer: LogBuffer,
+) {
+ fun ignoredFaceAuthTrigger(uiEvent: FaceAuthUiEvent) {
+ logBuffer.log(
+ TAG,
+ DEBUG,
+ { str1 = uiEvent.reason },
+ {
+ "Ignoring trigger because face auth is currently running. " +
+ "Trigger reason: $str1"
+ }
+ )
+ }
+
+ fun queuingRequestWhileCancelling(
+ alreadyQueuedRequest: FaceAuthUiEvent?,
+ newRequest: FaceAuthUiEvent
+ ) {
+ logBuffer.log(
+ TAG,
+ DEBUG,
+ {
+ str1 = alreadyQueuedRequest?.reason
+ str2 = newRequest.reason
+ },
+ {
+ "Face auth requested while previous request is being cancelled, " +
+ "already queued request: $str1 queueing the new request: $str2"
+ }
+ )
+ }
+
+ fun authenticating(uiEvent: FaceAuthUiEvent) {
+ logBuffer.log(TAG, DEBUG, { str1 = uiEvent.reason }, { "Running authenticate for $str1" })
+ }
+
+ fun detectionNotSupported(
+ faceManager: FaceManager?,
+ sensorPropertiesInternal: MutableList<FaceSensorPropertiesInternal>?
+ ) {
+ logBuffer.log(
+ TAG,
+ DEBUG,
+ {
+ bool1 = faceManager == null
+ bool2 = sensorPropertiesInternal.isNullOrEmpty()
+ bool2 = sensorPropertiesInternal?.firstOrNull()?.supportsFaceDetection ?: false
+ },
+ {
+ "skipping detection request because it is not supported, " +
+ "faceManager isNull: $bool1, " +
+ "sensorPropertiesInternal isNullOrEmpty: $bool2, " +
+ "supportsFaceDetection: $bool3"
+ }
+ )
+ }
+
+ fun skippingBecauseAlreadyRunning(@CompileTimeConstant operation: String) {
+ logBuffer.log(TAG, DEBUG, "isAuthRunning is true, skipping $operation")
+ }
+
+ fun faceDetectionStarted() {
+ logBuffer.log(TAG, DEBUG, "Face detection started.")
+ }
+
+ fun faceDetected() {
+ logBuffer.log(TAG, DEBUG, "Face detected")
+ }
+
+ fun cancelSignalNotReceived(
+ isAuthRunning: Boolean,
+ isLockedOut: Boolean,
+ cancellationInProgress: Boolean,
+ faceAuthRequestedWhileCancellation: FaceAuthUiEvent?
+ ) {
+ logBuffer.log(
+ TAG,
+ DEBUG,
+ {
+ bool1 = isAuthRunning
+ bool2 = isLockedOut
+ bool3 = cancellationInProgress
+ str1 = "${faceAuthRequestedWhileCancellation?.reason}"
+ },
+ {
+ "Cancel signal was not received, running timeout handler to reset state. " +
+ "State before reset: " +
+ "isAuthRunning: $bool1, " +
+ "isLockedOut: $bool2, " +
+ "cancellationInProgress: $bool3, " +
+ "faceAuthRequestedWhileCancellation: $str1"
+ }
+ )
+ }
+
+ fun authenticationFailed() {
+ logBuffer.log(TAG, DEBUG, "Face authentication failed")
+ }
+
+ fun authenticationAcquired(acquireInfo: Int) {
+ logBuffer.log(
+ TAG,
+ DEBUG,
+ { int1 = acquireInfo },
+ { "Face acquired during face authentication: acquireInfo: $int1 " }
+ )
+ }
+
+ fun authenticationError(
+ errorCode: Int,
+ errString: CharSequence?,
+ lockoutError: Boolean,
+ cancellationError: Boolean
+ ) {
+ logBuffer.log(
+ TAG,
+ DEBUG,
+ {
+ int1 = errorCode
+ str1 = "$errString"
+ bool1 = lockoutError
+ bool2 = cancellationError
+ },
+ {
+ "Received authentication error: errorCode: $int1, " +
+ "errString: $str1, " +
+ "isLockoutError: $bool1, " +
+ "isCancellationError: $bool2"
+ }
+ )
+ }
+
+ fun launchingQueuedFaceAuthRequest(faceAuthRequestedWhileCancellation: FaceAuthUiEvent?) {
+ logBuffer.log(
+ TAG,
+ DEBUG,
+ { str1 = "${faceAuthRequestedWhileCancellation?.reason}" },
+ { "Received cancellation error and starting queued face auth request: $str1" }
+ )
+ }
+
+ fun faceAuthSuccess(result: FaceManager.AuthenticationResult) {
+ logBuffer.log(
+ TAG,
+ DEBUG,
+ {
+ int1 = result.userId
+ bool1 = result.isStrongBiometric
+ },
+ { "Face authenticated successfully: userId: $int1, isStrongBiometric: $bool1" }
+ )
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/log/dagger/FaceAuthLog.kt b/packages/SystemUI/src/com/android/systemui/log/dagger/FaceAuthLog.kt
new file mode 100644
index 0000000..b97e3a7
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/log/dagger/FaceAuthLog.kt
@@ -0,0 +1,6 @@
+package com.android.systemui.log.dagger
+
+import javax.inject.Qualifier
+
+/** A [com.android.systemui.log.LogBuffer] for Face authentication triggered by SysUI. */
+@Qualifier @MustBeDocumented @Retention(AnnotationRetention.RUNTIME) annotation class FaceAuthLog()
diff --git a/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java b/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java
index 5341cd5..817de79 100644
--- a/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java
+++ b/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java
@@ -350,6 +350,17 @@
}
/**
+ * Provides a {@link LogBuffer} for use by
+ * {@link com.android.keyguard.faceauth.KeyguardFaceAuthManagerImpl}.
+ */
+ @Provides
+ @SysUISingleton
+ @FaceAuthLog
+ public static LogBuffer provideFaceAuthLog(LogBufferFactory factory) {
+ return factory.create("KeyguardFaceAuthManagerLog", 300);
+ }
+
+ /**
* Provides a {@link LogBuffer} for bluetooth-related logs.
*/
@Provides
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java b/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java
index 7067c220..a0be151 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java
@@ -25,8 +25,6 @@
import android.content.res.Resources;
import android.graphics.Rect;
import android.os.Bundle;
-import android.os.Handler;
-import android.os.Message;
import android.util.ArrayMap;
import android.util.AttributeSet;
import android.util.Log;
@@ -108,6 +106,11 @@
private boolean mShouldMoveMediaOnExpansion = true;
private boolean mUsingCombinedHeaders = false;
private QSLogger mQsLogger;
+ /**
+ * Specifies if we can collapse to QQS in current state. In split shade that should be always
+ * false. It influences available accessibility actions.
+ */
+ private boolean mCanCollapse = true;
public QSPanel(Context context, AttributeSet attrs) {
super(context, attrs);
@@ -650,7 +653,9 @@
@Override
public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) {
super.onInitializeAccessibilityNodeInfo(info);
- info.addAction(AccessibilityNodeInfo.AccessibilityAction.ACTION_COLLAPSE);
+ if (mCanCollapse) {
+ info.addAction(AccessibilityNodeInfo.AccessibilityAction.ACTION_COLLAPSE);
+ }
}
@Override
@@ -669,15 +674,11 @@
mCollapseExpandAction = action;
}
- private class H extends Handler {
- private static final int ANNOUNCE_FOR_ACCESSIBILITY = 1;
-
- @Override
- public void handleMessage(Message msg) {
- if (msg.what == ANNOUNCE_FOR_ACCESSIBILITY) {
- announceForAccessibility((CharSequence) msg.obj);
- }
- }
+ /**
+ * Specifies if these expanded QS can collapse to QQS.
+ */
+ public void setCanCollapse(boolean canCollapse) {
+ mCanCollapse = canCollapse;
}
public interface QSTileLayout {
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSPanelController.java b/packages/SystemUI/src/com/android/systemui/qs/QSPanelController.java
index cabe1da..01dbb18 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSPanelController.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSPanelController.java
@@ -148,9 +148,10 @@
}
@Override
- protected void onSplitShadeChanged() {
+ protected void onSplitShadeChanged(boolean shouldUseSplitNotificationShade) {
((PagedTileLayout) mView.getOrCreateTileLayout())
.forceTilesRedistribution("Split shade state changed");
+ mView.setCanCollapse(!shouldUseSplitNotificationShade);
}
/** */
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSPanelControllerBase.java b/packages/SystemUI/src/com/android/systemui/qs/QSPanelControllerBase.java
index e85d0a3..bbdf6cc 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSPanelControllerBase.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSPanelControllerBase.java
@@ -104,14 +104,14 @@
switchTileLayoutIfNeeded();
onConfigurationChanged();
if (previousSplitShadeState != mShouldUseSplitNotificationShade) {
- onSplitShadeChanged();
+ onSplitShadeChanged(mShouldUseSplitNotificationShade);
}
}
};
protected void onConfigurationChanged() { }
- protected void onSplitShadeChanged() { }
+ protected void onSplitShadeChanged(boolean shouldUseSplitNotificationShade) { }
private final Function1<Boolean, Unit> mMediaHostVisibilityListener = (visible) -> {
if (mMediaVisibilityChangedListener != null) {
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeader.java b/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeader.java
index 946fe54..e696d13 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeader.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeader.java
@@ -152,9 +152,7 @@
Configuration config = mContext.getResources().getConfiguration();
setDatePrivacyContainersWidth(config.orientation == Configuration.ORIENTATION_LANDSCAPE);
- // QS will always show the estimate, and BatteryMeterView handles the case where
- // it's unavailable or charging
- mBatteryRemainingIcon.setPercentShowMode(BatteryMeterView.MODE_ESTIMATE);
+ updateBatteryMode();
mIconsAlphaAnimatorFixed = new TouchAnimator.Builder()
.addFloat(mIconContainer, "alpha", 0, 1)
@@ -460,24 +458,24 @@
(LinearLayout.LayoutParams) mDatePrivacySeparator.getLayoutParams();
LinearLayout.LayoutParams mClockIconsSeparatorLayoutParams =
(LinearLayout.LayoutParams) mClockIconsSeparator.getLayoutParams();
- if (cutout != null) {
- Rect topCutout = cutout.getBoundingRectTop();
- if (topCutout.isEmpty() || hasCornerCutout) {
- datePrivacySeparatorLayoutParams.width = 0;
- mDatePrivacySeparator.setVisibility(View.GONE);
- mClockIconsSeparatorLayoutParams.width = 0;
- setSeparatorVisibility(false);
- mShowClockIconsSeparator = false;
- mHasCenterCutout = false;
- } else {
- datePrivacySeparatorLayoutParams.width = topCutout.width();
- mDatePrivacySeparator.setVisibility(View.VISIBLE);
- mClockIconsSeparatorLayoutParams.width = topCutout.width();
- mShowClockIconsSeparator = true;
- setSeparatorVisibility(mKeyguardExpansionFraction == 0f);
- mHasCenterCutout = true;
- }
+
+ Rect topCutout = cutout == null ? null : cutout.getBoundingRectTop();
+ if (topCutout == null || topCutout.isEmpty() || hasCornerCutout) {
+ datePrivacySeparatorLayoutParams.width = 0;
+ mDatePrivacySeparator.setVisibility(View.GONE);
+ mClockIconsSeparatorLayoutParams.width = 0;
+ setSeparatorVisibility(false);
+ mShowClockIconsSeparator = false;
+ mHasCenterCutout = false;
+ } else {
+ datePrivacySeparatorLayoutParams.width = topCutout.width();
+ mDatePrivacySeparator.setVisibility(View.VISIBLE);
+ mClockIconsSeparatorLayoutParams.width = topCutout.width();
+ mShowClockIconsSeparator = true;
+ setSeparatorVisibility(mKeyguardExpansionFraction == 0f);
+ mHasCenterCutout = true;
}
+
mDatePrivacySeparator.setLayoutParams(datePrivacySeparatorLayoutParams);
mClockIconsSeparator.setLayoutParams(mClockIconsSeparatorLayoutParams);
mCutOutPaddingLeft = sbInsets.first;
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/FontScalingTile.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/FontScalingTile.kt
index 721046d..9b4ac1b 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/FontScalingTile.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/FontScalingTile.kt
@@ -27,6 +27,8 @@
import com.android.systemui.animation.DialogLaunchAnimator
import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.dagger.qualifiers.Main
+import com.android.systemui.flags.FeatureFlags
+import com.android.systemui.flags.Flags
import com.android.systemui.plugins.ActivityStarter
import com.android.systemui.plugins.FalsingManager
import com.android.systemui.plugins.qs.QSTile
@@ -50,7 +52,8 @@
activityStarter: ActivityStarter,
qsLogger: QSLogger,
private val dialogLaunchAnimator: DialogLaunchAnimator,
- private val systemSettings: SystemSettings
+ private val systemSettings: SystemSettings,
+ private val featureFlags: FeatureFlags
) :
QSTileImpl<QSTile.State?>(
host,
@@ -65,7 +68,7 @@
private val icon = ResourceIcon.get(R.drawable.ic_qs_font_scaling)
override fun isAvailable(): Boolean {
- return false
+ return featureFlags.isEnabled(Flags.ENABLE_FONT_SCALING_TILE)
}
override fun newTileState(): QSTile.State {
diff --git a/packages/SystemUI/src/com/android/systemui/shade/DebugDrawable.java b/packages/SystemUI/src/com/android/systemui/shade/DebugDrawable.java
index ae303eb..fb2ddc1 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/DebugDrawable.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/DebugDrawable.java
@@ -39,6 +39,7 @@
private final NotificationPanelView mView;
private final NotificationStackScrollLayoutController mNotificationStackScrollLayoutController;
private final LockIconViewController mLockIconViewController;
+ private final QuickSettingsController mQsController;
private final Set<Integer> mDebugTextUsedYPositions;
private final Paint mDebugPaint;
@@ -46,12 +47,14 @@
NotificationPanelViewController notificationPanelViewController,
NotificationPanelView notificationPanelView,
NotificationStackScrollLayoutController notificationStackScrollLayoutController,
- LockIconViewController lockIconViewController
+ LockIconViewController lockIconViewController,
+ QuickSettingsController quickSettingsController
) {
mNotificationPanelViewController = notificationPanelViewController;
mView = notificationPanelView;
mNotificationStackScrollLayoutController = notificationStackScrollLayoutController;
mLockIconViewController = lockIconViewController;
+ mQsController = quickSettingsController;
mDebugTextUsedYPositions = new HashSet<>();
mDebugPaint = new Paint();
}
@@ -71,12 +74,19 @@
Color.RED, "getMaxPanelHeight()");
drawDebugInfo(canvas, (int) mNotificationPanelViewController.getExpandedHeight(),
Color.BLUE, "getExpandedHeight()");
- drawDebugInfo(canvas, mNotificationPanelViewController.calculatePanelHeightQsExpanded(),
+ drawDebugInfo(canvas, mQsController.calculatePanelHeightExpanded(
+ mNotificationPanelViewController.getClockPositionResult()
+ .stackScrollerPadding),
Color.GREEN, "calculatePanelHeightQsExpanded()");
- drawDebugInfo(canvas, mNotificationPanelViewController.calculatePanelHeightQsExpanded(),
+ drawDebugInfo(canvas, mQsController.calculatePanelHeightExpanded(
+ mNotificationPanelViewController.getClockPositionResult()
+ .stackScrollerPadding),
Color.YELLOW, "calculatePanelHeightShade()");
drawDebugInfo(canvas,
- (int) mNotificationPanelViewController.calculateNotificationsTopPadding(),
+ (int) mQsController.calculateNotificationsTopPadding(
+ mNotificationPanelViewController.isExpanding(),
+ mNotificationPanelViewController.getKeyguardNotificationStaticPadding(),
+ mNotificationPanelViewController.getExpandedFraction()),
Color.MAGENTA, "calculateNotificationsTopPadding()");
drawDebugInfo(canvas, mNotificationPanelViewController.getClockPositionResult().clockY,
Color.GRAY, "mClockPositionResult.clockY");
diff --git a/packages/SystemUI/src/com/android/systemui/shade/LargeScreenShadeHeaderController.kt b/packages/SystemUI/src/com/android/systemui/shade/LargeScreenShadeHeaderController.kt
index 197232e..9d8ed46 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/LargeScreenShadeHeaderController.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/LargeScreenShadeHeaderController.kt
@@ -17,6 +17,7 @@
package com.android.systemui.shade
import android.animation.Animator
+import android.animation.AnimatorListenerAdapter
import android.annotation.IdRes
import android.app.StatusBarManager
import android.content.res.Configuration
@@ -24,6 +25,7 @@
import android.os.Trace
import android.os.Trace.TRACE_TAG_APP
import android.util.Pair
+import android.view.DisplayCutout
import android.view.View
import android.view.WindowInsets
import android.widget.TextView
@@ -91,7 +93,8 @@
private val featureFlags: FeatureFlags,
private val qsCarrierGroupControllerBuilder: QSCarrierGroupController.Builder,
private val combinedShadeHeadersConstraintManager: CombinedShadeHeadersConstraintManager,
- private val demoModeController: DemoModeController
+ private val demoModeController: DemoModeController,
+ private val qsBatteryModeController: QsBatteryModeController,
) : ViewController<View>(header), Dumpable {
companion object {
@@ -129,9 +132,8 @@
private val iconContainer: StatusIconContainer = header.findViewById(R.id.statusIcons)
private val qsCarrierGroup: QSCarrierGroup = header.findViewById(R.id.carrier_group)
- private var cutoutLeft = 0
- private var cutoutRight = 0
private var roundedCorners = 0
+ private var cutout: DisplayCutout? = null
private var lastInsets: WindowInsets? = null
private var qsDisabled = false
@@ -144,6 +146,14 @@
updateListeners()
}
+ private var customizing = false
+ set(value) {
+ if (field != value) {
+ field = value
+ updateVisibility()
+ }
+ }
+
/**
* Whether the QQS/QS part of the shade is visible. This is particularly important in
* Lockscreen, as the shade is visible but QS is not.
@@ -175,14 +185,9 @@
*/
var shadeExpandedFraction = -1f
set(value) {
- if (field != value) {
- val oldAlpha = header.alpha
+ if (qsVisible && field != value) {
header.alpha = ShadeInterpolation.getContentAlpha(value)
field = value
- if ((oldAlpha == 0f && header.alpha > 0f) ||
- (oldAlpha > 0f && header.alpha == 0f)) {
- updateVisibility()
- }
}
}
@@ -273,7 +278,6 @@
// battery settings same as in QS icons
batteryMeterViewController.ignoreTunerUpdates()
- batteryIcon.setPercentShowMode(BatteryMeterView.MODE_ESTIMATE)
iconManager = tintedIconManagerFactory.create(iconContainer, StatusBarLocation.QS)
iconManager.setTint(
@@ -305,6 +309,7 @@
if (header is MotionLayout) {
header.setOnApplyWindowInsetsListener(insetListener)
+
clock.addOnLayoutChangeListener { v, _, _, _, _, _, _, _, _ ->
val newPivot = if (v.isLayoutRtl) v.width.toFloat() else 0f
v.pivotX = newPivot
@@ -317,6 +322,7 @@
dumpManager.registerDumpable(this)
configurationController.addCallback(configurationControllerListener)
demoModeController.addCallback(demoModeReceiver)
+ statusBarIconController.addIconGroup(iconManager)
}
override fun onViewDetached() {
@@ -324,6 +330,7 @@
dumpManager.unregisterDumpable(this::class.java.simpleName)
configurationController.removeCallback(configurationControllerListener)
demoModeController.removeCallback(demoModeReceiver)
+ statusBarIconController.removeIconGroup(iconManager)
}
fun disable(state1: Int, state2: Int, animate: Boolean) {
@@ -338,31 +345,10 @@
.setDuration(duration)
.alpha(if (show) 0f else 1f)
.setInterpolator(if (show) Interpolators.ALPHA_OUT else Interpolators.ALPHA_IN)
- .setUpdateListener {
- updateVisibility()
- }
- .setListener(endAnimationListener)
+ .setListener(CustomizerAnimationListener(show))
.start()
}
- private val endAnimationListener = object : Animator.AnimatorListener {
- override fun onAnimationCancel(animation: Animator?) {
- clearListeners()
- }
-
- override fun onAnimationEnd(animation: Animator?) {
- clearListeners()
- }
-
- override fun onAnimationRepeat(animation: Animator?) {}
-
- override fun onAnimationStart(animation: Animator?) {}
-
- private fun clearListeners() {
- header.animate().setListener(null).setUpdateListener(null)
- }
- }
-
private fun loadConstraints() {
if (header is MotionLayout) {
// Use resources.getXml instead of passing the resource id due to bug b/205018300
@@ -376,11 +362,13 @@
}
private fun updateConstraintsForInsets(view: MotionLayout, insets: WindowInsets) {
- val cutout = insets.displayCutout
+ val cutout = insets.displayCutout.also {
+ this.cutout = it
+ }
val sbInsets: Pair<Int, Int> = insetsProvider.getStatusBarContentInsetsForCurrentRotation()
- cutoutLeft = sbInsets.first
- cutoutRight = sbInsets.second
+ val cutoutLeft = sbInsets.first
+ val cutoutRight = sbInsets.second
val hasCornerCutout: Boolean = insetsProvider.currentRotationHasCornerCutout()
updateQQSPaddings()
// Set these guides as the left/right limits for content that lives in the top row, using
@@ -408,6 +396,13 @@
}
view.updateAllConstraints(changes)
+ updateBatteryMode()
+ }
+
+ private fun updateBatteryMode() {
+ qsBatteryModeController.getBatteryMode(cutout, qsExpandedFraction)?.let {
+ batteryIcon.setPercentShowMode(it)
+ }
}
private fun updateScrollY() {
@@ -443,7 +438,7 @@
private fun updateVisibility() {
val visibility = if (!largeScreenActive && !combinedHeaders || qsDisabled) {
View.GONE
- } else if (qsVisible && header.alpha > 0f) {
+ } else if (qsVisible && !customizing) {
View.VISIBLE
} else {
View.INVISIBLE
@@ -475,6 +470,7 @@
if (header is MotionLayout && !largeScreenActive && visible) {
logInstantEvent("updatePosition: $qsExpandedFraction")
header.progress = qsExpandedFraction
+ updateBatteryMode()
}
}
@@ -491,10 +487,8 @@
if (visible) {
updateSingleCarrier(qsCarrierGroupController.isSingleCarrier)
qsCarrierGroupController.setOnSingleCarrierChangedListener { updateSingleCarrier(it) }
- statusBarIconController.addIconGroup(iconManager)
} else {
qsCarrierGroupController.setOnSingleCarrierChangedListener(null)
- statusBarIconController.removeIconGroup(iconManager)
}
}
@@ -511,6 +505,7 @@
val padding = resources.getDimensionPixelSize(R.dimen.qs_panel_padding)
header.setPadding(padding, header.paddingTop, padding, header.paddingBottom)
updateQQSPaddings()
+ qsBatteryModeController.updateResources()
}
private fun updateQQSPaddings() {
@@ -566,4 +561,23 @@
@VisibleForTesting
internal fun simulateViewDetached() = this.onViewDetached()
+
+ inner class CustomizerAnimationListener(
+ private val enteringCustomizing: Boolean,
+ ) : AnimatorListenerAdapter() {
+ override fun onAnimationEnd(animation: Animator?) {
+ super.onAnimationEnd(animation)
+ header.animate().setListener(null)
+ if (enteringCustomizing) {
+ customizing = true
+ }
+ }
+
+ override fun onAnimationStart(animation: Animator?) {
+ super.onAnimationStart(animation)
+ if (!enteringCustomizing) {
+ customizing = false
+ }
+ }
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
index cd45b32..2175a33 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
@@ -23,14 +23,12 @@
import static androidx.constraintlayout.widget.ConstraintSet.END;
import static androidx.constraintlayout.widget.ConstraintSet.PARENT_ID;
-import static com.android.internal.jank.InteractionJankMonitor.CUJ_NOTIFICATION_SHADE_QS_EXPAND_COLLAPSE;
import static com.android.keyguard.KeyguardClockSwitch.LARGE;
import static com.android.keyguard.KeyguardClockSwitch.SMALL;
import static com.android.systemui.animation.Interpolators.EMPHASIZED_ACCELERATE;
import static com.android.systemui.animation.Interpolators.EMPHASIZED_DECELERATE;
import static com.android.systemui.classifier.Classifier.BOUNCER_UNLOCK;
import static com.android.systemui.classifier.Classifier.GENERIC;
-import static com.android.systemui.classifier.Classifier.QS_COLLAPSE;
import static com.android.systemui.classifier.Classifier.QUICK_SETTINGS;
import static com.android.systemui.classifier.Classifier.UNLOCK;
import static com.android.systemui.shade.ShadeExpansionStateManagerKt.STATE_CLOSED;
@@ -53,7 +51,6 @@
import android.animation.ValueAnimator;
import android.annotation.NonNull;
import android.annotation.Nullable;
-import android.app.Fragment;
import android.app.StatusBarManager;
import android.content.ContentResolver;
import android.content.res.Resources;
@@ -101,10 +98,8 @@
import androidx.constraintlayout.widget.ConstraintSet;
import com.android.internal.annotations.VisibleForTesting;
-import com.android.internal.jank.InteractionJankMonitor;
import com.android.internal.logging.MetricsLogger;
import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
-import com.android.internal.policy.ScreenDecorationsUtils;
import com.android.internal.policy.SystemBarUtils;
import com.android.internal.util.LatencyTracker;
import com.android.keyguard.ActiveUnlockConfig;
@@ -135,7 +130,6 @@
import com.android.systemui.dump.DumpsysTableLogger;
import com.android.systemui.flags.FeatureFlags;
import com.android.systemui.flags.Flags;
-import com.android.systemui.fragments.FragmentHostManager.FragmentListener;
import com.android.systemui.fragments.FragmentService;
import com.android.systemui.keyguard.KeyguardUnlockAnimationController;
import com.android.systemui.keyguard.domain.interactor.AlternateBouncerInteractor;
@@ -177,7 +171,6 @@
import com.android.systemui.statusbar.NotificationShadeWindowController;
import com.android.systemui.statusbar.NotificationShelfController;
import com.android.systemui.statusbar.PulseExpansionHandler;
-import com.android.systemui.statusbar.QsFrameTranslateController;
import com.android.systemui.statusbar.RemoteInputController;
import com.android.systemui.statusbar.StatusBarState;
import com.android.systemui.statusbar.SysuiStatusBarStateController;
@@ -263,13 +256,13 @@
private static final VibrationEffect ADDITIONAL_TAP_REQUIRED_VIBRATION_EFFECT =
VibrationEffect.get(VibrationEffect.EFFECT_STRENGTH_MEDIUM, false);
/** The parallax amount of the quick settings translation when dragging down the panel. */
- private static final float QS_PARALLAX_AMOUNT = 0.175f;
+ public static final float QS_PARALLAX_AMOUNT = 0.175f;
/** Fling expanding QS. */
public static final int FLING_EXPAND = 0;
/** Fling collapsing QS, potentially stopping when QS becomes QQS. */
- private static final int FLING_COLLAPSE = 1;
+ public static final int FLING_COLLAPSE = 1;
/** Fling until QS is completely hidden. */
- private static final int FLING_HIDE = 2;
+ public static final int FLING_HIDE = 2;
/** The delay to reset the hint text when the hint animation is finished running. */
private static final int HINT_RESET_DELAY_MS = 1200;
private static final long ANIMATION_DELAY_ICON_FADE_IN =
@@ -291,7 +284,7 @@
private static final int MAX_TIME_TO_OPEN_WHEN_FLINGING_FROM_LAUNCHER = 300;
private static final int MAX_DOWN_EVENT_BUFFER_SIZE = 50;
private static final String COUNTER_PANEL_OPEN = "panel_open";
- private static final String COUNTER_PANEL_OPEN_QS = "panel_open_qs";
+ public static final String COUNTER_PANEL_OPEN_QS = "panel_open_qs";
private static final String COUNTER_PANEL_OPEN_PEEK = "panel_open_peek";
private static final Rect M_DUMMY_DIRTY_RECT = new Rect(0, 0, 1, 1);
private static final Rect EMPTY_RECT = new Rect();
@@ -310,14 +303,10 @@
private final SystemClock mSystemClock;
private final ShadeLogger mShadeLog;
private final DozeParameters mDozeParameters;
- private final Runnable mCollapseExpandAction = this::collapseOrExpand;
- private final NsslOverscrollTopChangedListener mOnOverscrollTopChangedListener =
- new NsslOverscrollTopChangedListener();
private final NotificationStackScrollLayout.OnEmptySpaceClickListener
mOnEmptySpaceClickListener = (x, y) -> onEmptySpaceClick();
private final ShadeHeadsUpChangedListener mOnHeadsUpChangedListener =
new ShadeHeadsUpChangedListener();
- private final QS.HeightListener mHeightListener = this::onQsHeightChanged;
private final ConfigurationListener mConfigurationListener = new ConfigurationListener();
private final SettingsChangeObserver mSettingsChangeObserver;
private final StatusBarStateListener mStatusBarStateListener = new StatusBarStateListener();
@@ -327,7 +316,6 @@
private final ConfigurationController mConfigurationController;
private final Provider<FlingAnimationUtils.Builder> mFlingAnimationUtilsBuilder;
private final NotificationStackScrollLayoutController mNotificationStackScrollLayoutController;
- private final InteractionJankMonitor mInteractionJankMonitor;
private final LayoutInflater mLayoutInflater;
private final FeatureFlags mFeatureFlags;
private final PowerManager mPowerManager;
@@ -346,12 +334,9 @@
private final KeyguardStatusBarViewComponent.Factory mKeyguardStatusBarViewComponentFactory;
private final FragmentService mFragmentService;
private final ScrimController mScrimController;
- private final NotificationRemoteInputManager mRemoteInputManager;
private final LockscreenShadeTransitionController mLockscreenShadeTransitionController;
- private final ShadeTransitionController mShadeTransitionController;
private final TapAgainViewController mTapAgainViewController;
private final LargeScreenShadeHeaderController mLargeScreenShadeHeaderController;
- private final RecordingController mRecordingController;
private final boolean mVibrateOnOpening;
private final VelocityTracker mVelocityTracker = VelocityTracker.obtain();
private final FlingAnimationUtils mFlingAnimationUtilsClosing;
@@ -363,12 +348,11 @@
private final Interpolator mBounceInterpolator;
private final NotificationShadeWindowController mNotificationShadeWindowController;
private final ShadeExpansionStateManager mShadeExpansionStateManager;
- private final QS.ScrollListener mQsScrollListener = this::onQsPanelScrollChanged;
private final FalsingTapListener mFalsingTapListener = this::falsingAdditionalTapRequired;
- private final FragmentListener mQsFragmentListener = new QsFragmentListener();
private final AccessibilityDelegate mAccessibilityDelegate = new ShadeAccessibilityDelegate();
private final NotificationGutsManager mGutsManager;
private final AlternateBouncerInteractor mAlternateBouncerInteractor;
+ private final QuickSettingsController mQsController;
private long mDownTime;
private boolean mTouchSlopExceededBeforeDown;
@@ -395,9 +379,6 @@
private KeyguardUserSwitcherController mKeyguardUserSwitcherController;
private KeyguardStatusBarView mKeyguardStatusBar;
private KeyguardStatusBarViewController mKeyguardStatusBarViewController;
- private QS mQs;
- private FrameLayout mQsFrame;
- private final QsFrameTranslateController mQsFrameTranslateController;
private KeyguardStatusViewController mKeyguardStatusViewController;
private final LockIconViewController mLockIconViewController;
private NotificationsQuickSettingsContainer mNotificationContainerParent;
@@ -405,47 +386,19 @@
private final Provider<KeyguardBottomAreaViewController>
mKeyguardBottomAreaViewControllerProvider;
private boolean mAnimateNextPositionUpdate;
- private float mQuickQsHeaderHeight;
private final ScreenOffAnimationController mScreenOffAnimationController;
private final UnlockedScreenOffAnimationController mUnlockedScreenOffAnimationController;
- private int mQsTrackingPointer;
- private VelocityTracker mQsVelocityTracker;
private TrackingStartedListener mTrackingStartedListener;
private OpenCloseListener mOpenCloseListener;
private GestureRecorder mGestureRecorder;
- private boolean mQsTracking;
- /** Whether the ongoing gesture might both trigger the expansion in both the view and QS. */
- private boolean mConflictingQsExpansionGesture;
private boolean mPanelExpanded;
- /**
- * Indicates that QS is in expanded state which can happen by:
- * - single pane shade: expanding shade and then expanding QS
- * - split shade: just expanding shade (QS are expanded automatically)
- */
- private boolean mQsExpanded;
- private boolean mQsExpandedWhenExpandingStarted;
- private boolean mQsFullyExpanded;
- private boolean mKeyguardShowing;
private boolean mKeyguardQsUserSwitchEnabled;
private boolean mKeyguardUserSwitcherEnabled;
private boolean mDozing;
private boolean mDozingOnDown;
private boolean mBouncerShowing;
private int mBarState;
- private float mInitialHeightOnTouch;
- private float mInitialTouchX;
- private float mInitialTouchY;
- private float mQsExpansionHeight;
- private int mQsMinExpansionHeight;
- private int mQsMaxExpansionHeight;
- private int mQsPeekHeight;
- private boolean mStackScrollerOverscrolling;
- private boolean mQsExpansionFromOverscroll;
- private float mLastOverscroll;
- private boolean mQsExpansionEnabledPolicy = true;
- private boolean mQsExpansionEnabledAmbient = true;
- private ValueAnimator mQsExpansionAnimator;
private FlingAnimationUtils mFlingAnimationUtils;
private int mStatusBarMinHeight;
private int mStatusBarHeaderHeightKeyguard;
@@ -455,8 +408,6 @@
private int mDisplayTopInset = 0; // in pixels
private int mDisplayRightInset = 0; // in pixels
private int mDisplayLeftInset = 0; // in pixels
- private int mLargeScreenShadeHeaderHeight;
- private int mSplitShadeNotificationsScrimMarginBottom;
private final KeyguardClockPositionAlgorithm
mClockPositionAlgorithm =
@@ -466,23 +417,7 @@
new KeyguardClockPositionAlgorithm.Result();
private boolean mIsExpanding;
- /**
- * Determines if QS should be already expanded when expanding shade.
- * Used for split shade, two finger gesture as well as accessibility shortcut to QS.
- * It needs to be set when movement starts as it resets at the end of expansion/collapse.
- */
- private boolean mQsExpandImmediate;
- private boolean mTwoFingerQsExpandPossible;
private String mHeaderDebugInfo;
- /**
- * If we are in a panel collapsing motion, we reset scrollY of our scroll view but still
- * need to take this into account in our panel height calculation.
- */
- private boolean mQsAnimatorExpand;
- private ValueAnimator mQsSizeChangeAnimator;
- private boolean mQsScrimEnabled = true;
- private boolean mQsTouchAboveFalsingThreshold;
- private int mQsFalsingThreshold;
/**
* Indicates drag starting height when swiping down or up on heads-up notifications.
@@ -564,51 +499,12 @@
private Runnable mExpandAfterLayoutRunnable;
private Runnable mHideExpandedRunnable;
- /**
- * The padding between the start of notifications and the qs boundary on the lockscreen.
- * On lockscreen, notifications aren't inset this extra amount, but we still want the
- * qs boundary to be padded.
- */
- private int mLockscreenNotificationQSPadding;
- /**
- * The amount of progress we are currently in if we're transitioning to the full shade.
- * 0.0f means we're not transitioning yet, while 1 means we're all the way in the full
- * shade. This value can also go beyond 1.1 when we're overshooting!
- */
- private float mTransitioningToFullShadeProgress;
- /**
- * Position of the qs bottom during the full shade transition. This is needed as the toppadding
- * can change during state changes, which makes it much harder to do animations
- */
- private int mTransitionToFullShadeQSPosition;
- /** Distance a full shade transition takes in order for qs to fully transition to the shade. */
- private int mDistanceForQSFullShadeTransition;
- /** The translation amount for QS for the full shade transition. */
- private float mQsTranslationForFullShadeTransition;
-
/** The maximum overshoot allowed for the top padding for the full shade transition. */
private int mMaxOverscrollAmountForPulse;
- /** Should we animate the next bounds update. */
- private boolean mAnimateNextNotificationBounds;
- /** The delay for the next bounds animation. */
- private long mNotificationBoundsAnimationDelay;
- /** The duration of the notification bounds animation. */
- private long mNotificationBoundsAnimationDuration;
/** Whether a collapse that started on the panel should allow the panel to intercept. */
private boolean mIsPanelCollapseOnQQS;
- private boolean mAnimatingQS;
- /** The end bounds of a clipping animation. */
- private final Rect mQsClippingAnimationEndBounds = new Rect();
- /** The animator for the qs clipping bounds. */
- private ValueAnimator mQsClippingAnimation = null;
- /** Whether the current animator is resetting the qs translation. */
- private boolean mIsQsTranslationResetAnimator;
- /** Whether the current animator is resetting the pulse expansion after a drag down. */
- private boolean mIsPulseExpansionResetAnimator;
- private final Rect mLastQsClipBounds = new Rect();
- private final Region mQsInterceptRegion = new Region();
/** Alpha of the views which only show on the keyguard but not in shade / shade locked. */
private float mKeyguardOnlyContentAlpha = 1.0f;
/** Y translation of the views that only show on the keyguard but in shade / shade locked. */
@@ -618,15 +514,6 @@
private boolean mIsGestureNavigation;
private int mOldLayoutDirection;
private NotificationShelfController mNotificationShelfController;
- private int mScrimCornerRadius;
- private int mScreenCornerRadius;
- private boolean mQSAnimatingHiddenFromCollapsed;
- private boolean mUseLargeScreenShadeHeader;
- private boolean mEnableQsClipping;
-
- private int mQsClipTop;
- private int mQsClipBottom;
- private boolean mQsVisible;
private final ContentResolver mContentResolver;
private float mMinFraction;
@@ -685,7 +572,6 @@
private boolean mInstantExpanding;
private boolean mAnimateAfterExpanding;
private boolean mIsFlinging;
- private boolean mLastFlingWasExpanding;
private String mViewName;
private float mInitialExpandY;
private float mInitialExpandX;
@@ -803,6 +689,7 @@
TapAgainViewController tapAgainViewController,
NavigationModeController navigationModeController,
NavigationBarController navigationBarController,
+ QuickSettingsController quickSettingsController,
FragmentService fragmentService,
ContentResolver contentResolver,
RecordingController recordingController,
@@ -812,8 +699,6 @@
ShadeExpansionStateManager shadeExpansionStateManager,
NotificationRemoteInputManager remoteInputManager,
Optional<SysUIUnfoldComponent> unfoldComponent,
- InteractionJankMonitor interactionJankMonitor,
- QsFrameTranslateController qsFrameTranslateController,
SysUiState sysUiState,
Provider<KeyguardBottomAreaViewController> keyguardBottomAreaViewControllerProvider,
KeyguardUnlockAnimationController keyguardUnlockAnimationController,
@@ -873,6 +758,7 @@
mResources = mView.getResources();
mKeyguardStateController = keyguardStateController;
+ mQsController = quickSettingsController;
mKeyguardIndicationController = keyguardIndicationController;
mStatusBarStateController = (SysuiStatusBarStateController) statusBarStateController;
mNotificationShadeWindowController = notificationShadeWindowController;
@@ -903,7 +789,6 @@
mVibratorHelper = vibratorHelper;
mVibrateOnOpening = mResources.getBoolean(R.bool.config_vibrateOnIconAnimation);
mStatusBarTouchableRegionManager = statusBarTouchableRegionManager;
- mInteractionJankMonitor = interactionJankMonitor;
mSystemClock = systemClock;
mKeyguardMediaController = keyguardMediaController;
mMetricsLogger = metricsLogger;
@@ -939,7 +824,6 @@
mView.setAccessibilityPaneTitle(determineAccessibilityPaneTitle());
setPanelAlpha(255, false /* animate */);
mCommandQueue = commandQueue;
- mRecordingController = recordingController;
mDisplayId = displayId;
mPulseExpansionHandler = pulseExpansionHandler;
mDozeParameters = dozeParameters;
@@ -948,20 +832,19 @@
mMediaDataManager = mediaDataManager;
mTapAgainViewController = tapAgainViewController;
mSysUiState = sysUiState;
- pulseExpansionHandler.setPulseExpandAbortListener(() -> {
- if (mQs != null) {
- mQs.animateHeaderSlidingOut();
- }
- });
statusBarWindowStateController.addListener(this::onStatusBarWindowStateChanged);
mKeyguardBypassController = bypassController;
mUpdateMonitor = keyguardUpdateMonitor;
mLockscreenShadeTransitionController = lockscreenShadeTransitionController;
- mShadeTransitionController = shadeTransitionController;
lockscreenShadeTransitionController.setNotificationPanelController(this);
shadeTransitionController.setNotificationPanelViewController(this);
dynamicPrivacyController.addListener(this::onDynamicPrivacyChanged);
-
+ quickSettingsController.setExpansionHeightListener(this::onQsSetExpansionHeightCalled);
+ quickSettingsController.setQsStateUpdateListener(this::onQsStateUpdated);
+ quickSettingsController.setApplyClippingImmediatelyListener(
+ this::onQsClippingImmediatelyApplied);
+ quickSettingsController.setFlingQsWithoutClickListener(this::onFlingQsWithoutClick);
+ quickSettingsController.setExpansionHeightSetToMaxListener(this::onExpansionHeightSetToMax);
shadeExpansionStateManager.addStateListener(this::onPanelStateChanged);
mBottomAreaShadeAlphaAnimator = ValueAnimator.ofFloat(1f, 0);
@@ -976,7 +859,6 @@
mLockIconViewController = lockIconViewController;
mScreenOffAnimationController = screenOffAnimationController;
mUnlockedScreenOffAnimationController = unlockedScreenOffAnimationController;
- mRemoteInputManager = remoteInputManager;
mLastDownEvents = new NPVCDownEventState.Buffer(MAX_DOWN_EVENT_BUFFER_SIZE);
int currentMode = navigationModeController.addListener(
@@ -995,7 +877,8 @@
if (DEBUG_DRAWABLE) {
mView.getOverlay().add(new DebugDrawable(this, mView,
- mNotificationStackScrollLayoutController, mLockIconViewController));
+ mNotificationStackScrollLayoutController, mLockIconViewController,
+ mQsController));
}
mKeyguardUnfoldTransition = unfoldComponent.map(
@@ -1004,8 +887,6 @@
SysUIUnfoldComponent::getNotificationPanelUnfoldAnimationController);
mUnocclusionTransitionFlagEnabled = featureFlags.isEnabled(Flags.UNOCCLUSION_TRANSITION);
-
- mQsFrameTranslateController = qsFrameTranslateController;
updateUserSwitcherFlags();
mKeyguardBottomAreaViewModel = keyguardBottomAreaViewModel;
mKeyguardBottomAreaInteractor = keyguardBottomAreaInteractor;
@@ -1121,19 +1002,16 @@
mNotificationStackScrollLayoutController.attach(stackScrollLayout);
mNotificationStackScrollLayoutController.setOnHeightChangedListener(
new NsslHeightChangedListener());
- mNotificationStackScrollLayoutController.setOverscrollTopChangedListener(
- mOnOverscrollTopChangedListener);
- mNotificationStackScrollLayoutController.setOnScrollListener(this::onNotificationScrolled);
- mNotificationStackScrollLayoutController.setOnStackYChanged(this::onStackYChanged);
mNotificationStackScrollLayoutController.setOnEmptySpaceClickListener(
mOnEmptySpaceClickListener);
+ mQsController.initNotificationStackScrollLayoutController();
+ mShadeExpansionStateManager.addQsExpansionListener(this::onQsExpansionChanged);
addTrackingHeadsUpListener(mNotificationStackScrollLayoutController::setTrackingHeadsUp);
setKeyguardBottomArea(mView.findViewById(R.id.keyguard_bottom_area));
initBottomArea();
mWakeUpCoordinator.setStackScroller(mNotificationStackScrollLayoutController);
- mQsFrame = mView.findViewById(R.id.qs_frame);
mPulseExpansionHandler.setUp(mNotificationStackScrollLayoutController);
mWakeUpCoordinator.addListener(new NotificationWakeUpCoordinator.WakeUpListener() {
@Override
@@ -1236,24 +1114,14 @@
.setMaxLengthSeconds(0.4f).build();
mStatusBarMinHeight = SystemBarUtils.getStatusBarHeight(mView.getContext());
mStatusBarHeaderHeightKeyguard = Utils.getStatusBarHeaderHeightKeyguard(mView.getContext());
- mQsPeekHeight = mResources.getDimensionPixelSize(R.dimen.qs_peek_height);
mClockPositionAlgorithm.loadDimens(mResources);
- mQsFalsingThreshold = mResources.getDimensionPixelSize(R.dimen.qs_falsing_threshold);
mIndicationBottomPadding = mResources.getDimensionPixelSize(
R.dimen.keyguard_indication_bottom_padding);
int statusbarHeight = SystemBarUtils.getStatusBarHeight(mView.getContext());
mHeadsUpInset = statusbarHeight + mResources.getDimensionPixelSize(
R.dimen.heads_up_status_bar_padding);
- mDistanceForQSFullShadeTransition = mResources.getDimensionPixelSize(
- R.dimen.lockscreen_shade_qs_transition_distance);
mMaxOverscrollAmountForPulse = mResources.getDimensionPixelSize(
R.dimen.pulse_expansion_max_top_overshoot);
- mScrimCornerRadius = mResources.getDimensionPixelSize(
- R.dimen.notification_scrim_corner_radius);
- mScreenCornerRadius = (int) ScreenDecorationsUtils.getWindowCornerRadius(
- mView.getContext());
- mLockscreenNotificationQSPadding = mResources.getDimensionPixelSize(
- R.dimen.notification_side_paddings);
mUdfpsMaxYBurnInOffset = mResources.getDimensionPixelSize(R.dimen.udfps_burn_in_offset_y);
mSplitShadeScrimTransitionDistance = mResources.getDimensionPixelSize(
R.dimen.split_shade_scrim_transition_distance);
@@ -1267,6 +1135,8 @@
R.dimen.gone_to_dreaming_transition_lockscreen_translation_y);
mLockscreenToOccludedTransitionTranslationY = mResources.getDimensionPixelSize(
R.dimen.lockscreen_to_occluded_transition_lockscreen_translation_y);
+ // TODO (b/265193930): remove this and make QsController listen to NotificationPanelViews
+ mQsController.loadDimens();
}
private void updateViewControllers(KeyguardStatusView keyguardStatusView,
@@ -1309,40 +1179,13 @@
}
public void updateResources() {
- mSplitShadeNotificationsScrimMarginBottom =
- mResources.getDimensionPixelSize(
- R.dimen.split_shade_notifications_scrim_margin_bottom);
final boolean newSplitShadeEnabled =
LargeScreenUtils.shouldUseSplitNotificationShade(mResources);
final boolean splitShadeChanged = mSplitShadeEnabled != newSplitShadeEnabled;
mSplitShadeEnabled = newSplitShadeEnabled;
-
- if (mQs != null) {
- mQs.setInSplitShade(mSplitShadeEnabled);
- }
-
- mUseLargeScreenShadeHeader =
- LargeScreenUtils.shouldUseLargeScreenShadeHeader(mView.getResources());
-
- mLargeScreenShadeHeaderHeight =
- mResources.getDimensionPixelSize(R.dimen.large_screen_shade_header_height);
- // TODO: When the flag is eventually removed, it means that we have a single view that is
- // the same height in QQS and in Large Screen (large_screen_shade_header_height). Eventually
- // the concept of largeScreenHeader or quickQsHeader will disappear outside of the class
- // that controls the view as the offset needs to be the same regardless.
- if (mUseLargeScreenShadeHeader || mFeatureFlags.isEnabled(Flags.COMBINED_QS_HEADERS)) {
- mQuickQsHeaderHeight = mLargeScreenShadeHeaderHeight;
- } else {
- mQuickQsHeaderHeight = SystemBarUtils.getQuickQsOffsetHeight(mView.getContext());
- }
- int topMargin = mUseLargeScreenShadeHeader ? mLargeScreenShadeHeaderHeight :
- mResources.getDimensionPixelSize(R.dimen.notification_panel_margin_top);
- mLargeScreenShadeHeaderController.setLargeScreenActive(mUseLargeScreenShadeHeader);
- mAmbientState.setStackTopMargin(topMargin);
+ mQsController.updateResources();
mNotificationsQSContainerController.updateResources();
-
updateKeyguardStatusViewAlignment(/* animate= */false);
-
mKeyguardMediaController.refreshMediaPosition();
if (splitShadeChanged) {
@@ -1351,17 +1194,15 @@
mSplitShadeFullTransitionDistance =
mResources.getDimensionPixelSize(R.dimen.split_shade_full_transition_distance);
-
- mEnableQsClipping = mResources.getBoolean(R.bool.qs_enable_clipping);
}
private void onSplitShadeEnabledChanged() {
// when we switch between split shade and regular shade we want to enforce setting qs to
// the default state: expanded for split shade and collapsed otherwise
if (!isOnKeyguard() && mPanelExpanded) {
- setQsExpanded(mSplitShadeEnabled);
+ mQsController.setExpanded(mSplitShadeEnabled);
}
- if (isOnKeyguard() && mQsExpanded && mSplitShadeEnabled) {
+ if (isOnKeyguard() && mQsController.getExpanded() && mSplitShadeEnabled) {
// In single column keyguard - when you swipe from the top - QS is fully expanded and
// StatusBarState is KEYGUARD. That state doesn't make sense for split shade,
// where notifications are always visible and we effectively go to fully expanded
@@ -1371,7 +1212,7 @@
mStatusBarStateController.setState(StatusBarState.SHADE_LOCKED, /* force= */false);
}
updateClockAppearance();
- updateQsState();
+ mQsController.updateQsState();
mNotificationStackScrollLayoutController.updateFooter();
}
@@ -1479,11 +1320,6 @@
mNotificationPanelUnfoldAnimationController.ifPresent(u -> u.setup(mView));
}
- @VisibleForTesting
- void setQs(QS qs) {
- mQs = qs;
- }
-
private void attachSplitShadeMediaPlayerContainer(FrameLayout container) {
mKeyguardMediaController.attachSplitShadeContainer(container);
}
@@ -1515,7 +1351,7 @@
if (SPEW_LOGCAT) Log.d(TAG, "Skipping computeMaxKeyguardNotifications() by request");
}
- if (mKeyguardShowing && !mKeyguardBypassController.getBypassEnabled()) {
+ if (getKeyguardShowing() && !mKeyguardBypassController.getBypassEnabled()) {
mNotificationStackScrollLayoutController.setMaxDisplayedNotifications(
mMaxAllowedKeyguardNotifications);
mNotificationStackScrollLayoutController.setKeyguardBottomPaddingForDebug(
@@ -1564,39 +1400,14 @@
mIsFullWidth = isFullWidth;
mScrimController.setClipsQsScrim(isFullWidth);
mNotificationStackScrollLayoutController.setIsFullWidth(isFullWidth);
- if (mQs != null) {
- mQs.setIsNotificationPanelFullWidth(isFullWidth);
- }
- }
-
- private void startQsSizeChangeAnimation(int oldHeight, final int newHeight) {
- if (mQsSizeChangeAnimator != null) {
- oldHeight = (int) mQsSizeChangeAnimator.getAnimatedValue();
- mQsSizeChangeAnimator.cancel();
- }
- mQsSizeChangeAnimator = ValueAnimator.ofInt(oldHeight, newHeight);
- mQsSizeChangeAnimator.setDuration(300);
- mQsSizeChangeAnimator.setInterpolator(Interpolators.FAST_OUT_SLOW_IN);
- mQsSizeChangeAnimator.addUpdateListener(animation -> {
- requestScrollerTopPaddingUpdate(false /* animate */);
- updateExpandedHeightToMaxHeight();
- int height = (int) mQsSizeChangeAnimator.getAnimatedValue();
- mQs.setHeightOverride(height);
- });
- mQsSizeChangeAnimator.addListener(new AnimatorListenerAdapter() {
- @Override
- public void onAnimationEnd(Animator animation) {
- mQsSizeChangeAnimator = null;
- }
- });
- mQsSizeChangeAnimator.start();
+ mQsController.setNotificationPanelFullWidth(isFullWidth);
}
/**
* Positions the clock and notifications dynamically depending on how many notifications are
* showing.
*/
- private void positionClockAndNotifications() {
+ void positionClockAndNotifications() {
positionClockAndNotifications(false /* forceUpdate */);
}
@@ -1621,7 +1432,7 @@
// so we should not add a padding for them
stackScrollerPadding = 0;
} else {
- stackScrollerPadding = getUnlockedStackScrollerPadding();
+ stackScrollerPadding = mQsController.getUnlockedStackScrollerPadding();
}
} else {
stackScrollerPadding = mClockPositionResult.stackScrollerPaddingExpanded;
@@ -1669,8 +1480,9 @@
userSwitcherHeight,
userSwitcherPreferredY,
darkAmount, mOverStretchAmount,
- bypassEnabled, getUnlockedStackScrollerPadding(),
- computeQsExpansionFraction(),
+ bypassEnabled,
+ mQsController.getUnlockedStackScrollerPadding(),
+ mQsController.computeExpansionFraction(),
mDisplayTopInset,
mSplitShadeEnabled,
udfpsAodTopLocation,
@@ -1822,19 +1634,16 @@
return mDozing && mDozeParameters.getAlwaysOn();
}
+ boolean isDozing() {
+ return mDozing;
+ }
+
private boolean hasVisibleNotifications() {
return mNotificationStackScrollLayoutController
.getVisibleNotificationCount() != 0
|| mMediaDataManager.hasActiveMediaOrRecommendation();
}
- /**
- * @return the padding of the stackscroller when unlocked
- */
- private int getUnlockedStackScrollerPadding() {
- return (mQs != null ? mQs.getHeader().getHeight() : 0) + mQsPeekHeight;
- }
-
/** Returns space between top of lock icon and bottom of NotificationStackScrollLayout. */
private float getLockIconPadding() {
float lockIconPadding = 0f;
@@ -1952,23 +1761,23 @@
mAnimateNextPositionUpdate = true;
}
- private void setQsExpansionEnabled() {
- if (mQs == null) return;
- mQs.setHeaderClickable(isQsExpansionEnabled());
- }
+ /** Animate QS closing. */
+ public void animateCloseQs(boolean animateAway) {
+ if (mSplitShadeEnabled) {
+ collapsePanel(true, false, 1.0f);
+ } else {
+ mQsController.animateCloseQs(animateAway);
+ }
- public void setQsExpansionEnabledPolicy(boolean qsExpansionEnabledPolicy) {
- mQsExpansionEnabledPolicy = qsExpansionEnabledPolicy;
- setQsExpansionEnabled();
}
public void resetViews(boolean animate) {
mGutsManager.closeAndSaveGuts(true /* leavebehind */, true /* force */,
true /* controls */, -1 /* x */, -1 /* y */, true /* resetMenu */);
if (animate && !isFullyCollapsed()) {
- animateCloseQs(true /* animateAway */);
+ animateCloseQs(true);
} else {
- closeQs();
+ mQsController.closeQs();
}
mNotificationStackScrollLayoutController.setOverScrollAmount(0f, true /* onTop */, animate,
!animate /* cancelAnimators */);
@@ -1999,8 +1808,8 @@
return;
}
- if (mQsExpanded) {
- setQsExpandImmediate(true);
+ if (mQsController.getExpanded()) {
+ mQsController.setExpandImmediate(true);
setShowShelfOnly(true);
}
debugLog("collapse: %s", this);
@@ -2019,33 +1828,11 @@
}
}
- @VisibleForTesting
- void setQsExpandImmediate(boolean expandImmediate) {
- if (expandImmediate != mQsExpandImmediate) {
- mQsExpandImmediate = expandImmediate;
- mShadeExpansionStateManager.notifyExpandImmediateChange(expandImmediate);
- }
- }
-
- @VisibleForTesting
- boolean isQsExpandImmediate() {
- return mQsExpandImmediate;
- }
-
private void setShowShelfOnly(boolean shelfOnly) {
mNotificationStackScrollLayoutController.setShouldShowShelfOnly(
shelfOnly && !mSplitShadeEnabled);
}
- public void closeQs() {
- cancelQsAnimation();
- setQsExpansionHeight(mQsMinExpansionHeight);
- // qsExpandImmediate is a safety latch in case we're calling closeQS while we're in the
- // middle of animation - we need to make sure that value is always false when shade if
- // fully collapsed or expanded
- setQsExpandImmediate(false);
- }
-
@VisibleForTesting
void cancelHeightAnimator() {
if (mHeightAnimator != null) {
@@ -2061,39 +1848,9 @@
mView.animate().cancel();
}
- /**
- * Animate QS closing by flinging it.
- * If QS is expanded, it will collapse into QQS and stop.
- * If in split shade, it will collapse the whole shade.
- *
- * @param animateAway Do not stop when QS becomes QQS. Fling until QS isn't visible anymore.
- */
- public void animateCloseQs(boolean animateAway) {
- if (mSplitShadeEnabled) {
- collapsePanel(
- /* animate= */true, /* delayed= */false, /* speedUpFactor= */1.0f);
- return;
- }
-
- if (mQsExpansionAnimator != null) {
- if (!mQsAnimatorExpand) {
- return;
- }
- float height = mQsExpansionHeight;
- mQsExpansionAnimator.cancel();
- setQsExpansionHeight(height);
- }
- flingSettings(0 /* vel */, animateAway ? FLING_HIDE : FLING_COLLAPSE);
- }
-
- private boolean isQsExpansionEnabled() {
- return mQsExpansionEnabledPolicy && mQsExpansionEnabledAmbient
- && !mRemoteInputManager.isRemoteInputActive();
- }
-
public void expandWithQs() {
- if (isQsExpansionEnabled()) {
- setQsExpandImmediate(true);
+ if (mQsController.isExpansionEnabled()) {
+ mQsController.setExpandImmediate(true);
setShowShelfOnly(true);
}
if (mSplitShadeEnabled && isOnKeyguard()) {
@@ -2109,8 +1866,8 @@
} else if (isFullyCollapsed()) {
expand(true /* animate */);
} else {
- traceQsJank(true /* startTracing */, false /* wasCancelled */);
- flingSettings(0 /* velocity */, FLING_EXPAND);
+ mQsController.traceQsJank(true /* startTracing */, false /* wasCancelled */);
+ mQsController.flingQs(0, FLING_EXPAND);
}
}
@@ -2125,8 +1882,8 @@
if (mSplitShadeEnabled && (isShadeFullyOpen() || isExpanding())) {
return;
}
- if (isQsExpanded()) {
- flingSettings(0 /* velocity */, FLING_COLLAPSE);
+ if (mQsController.getExpanded()) {
+ mQsController.flingQs(0, FLING_COLLAPSE);
} else {
expand(true /* animate */);
}
@@ -2143,8 +1900,7 @@
@VisibleForTesting
void flingToHeight(float vel, boolean expand, float target,
float collapseSpeedUpFactor, boolean expandBecauseOfFalsing) {
- mLastFlingWasExpanding = expand;
- mShadeLog.logLastFlingWasExpanding(expand);
+ mQsController.setLastShadeFlingWasExpanding(expand);
mHeadsUpTouchHelper.notifyFling(!expand);
mKeyguardStateController.notifyPanelFlingStart(!expand /* flingingToDismiss */);
setClosingWithAlphaFadeout(!expand && !isOnKeyguard() && getFadeoutAlpha() == 1.0f);
@@ -2216,7 +1972,7 @@
@Override
public void onAnimationStart(Animator animation) {
if (!mStatusBarStateController.isDozing()) {
- beginJankMonitoring();
+ mQsController.beginJankMonitoring(isFullyCollapsed());
}
}
@@ -2248,117 +2004,15 @@
setAnimator(null);
mKeyguardStateController.notifyPanelFlingEnd();
if (!cancelled) {
- endJankMonitoring();
+ mQsController.endJankMonitoring();
notifyExpandingFinished();
} else {
- cancelJankMonitoring();
+ mQsController.cancelJankMonitoring();
}
updatePanelExpansionAndVisibility();
mNotificationStackScrollLayoutController.setPanelFlinging(false);
}
- private boolean onQsIntercept(MotionEvent event) {
- debugLog("onQsIntercept");
- int pointerIndex = event.findPointerIndex(mQsTrackingPointer);
- if (pointerIndex < 0) {
- pointerIndex = 0;
- mQsTrackingPointer = event.getPointerId(pointerIndex);
- }
- final float x = event.getX(pointerIndex);
- final float y = event.getY(pointerIndex);
-
- switch (event.getActionMasked()) {
- case MotionEvent.ACTION_DOWN:
- mInitialTouchY = y;
- mInitialTouchX = x;
- initVelocityTracker();
- trackMovement(event);
- float qsExpansionFraction = computeQsExpansionFraction();
- // Intercept the touch if QS is between fully collapsed and fully expanded state
- if (!mSplitShadeEnabled
- && qsExpansionFraction > 0.0 && qsExpansionFraction < 1.0) {
- mShadeLog.logMotionEvent(event,
- "onQsIntercept: down action, QS partially expanded/collapsed");
- return true;
- }
- if (mKeyguardShowing
- && shouldQuickSettingsIntercept(mInitialTouchX, mInitialTouchY, 0)) {
- // Dragging down on the lockscreen statusbar should prohibit other interactions
- // immediately, otherwise we'll wait on the touchslop. This is to allow
- // dragging down to expanded quick settings directly on the lockscreen.
- mView.getParent().requestDisallowInterceptTouchEvent(true);
- }
- if (mQsExpansionAnimator != null) {
- mInitialHeightOnTouch = mQsExpansionHeight;
- mShadeLog.logMotionEvent(event,
- "onQsIntercept: down action, QS tracking enabled");
- mQsTracking = true;
- traceQsJank(true /* startTracing */, false /* wasCancelled */);
- mNotificationStackScrollLayoutController.cancelLongPress();
- }
- break;
- case MotionEvent.ACTION_POINTER_UP:
- final int upPointer = event.getPointerId(event.getActionIndex());
- if (mQsTrackingPointer == upPointer) {
- // gesture is ongoing, find a new pointer to track
- final int newIndex = event.getPointerId(0) != upPointer ? 0 : 1;
- mQsTrackingPointer = event.getPointerId(newIndex);
- mInitialTouchX = event.getX(newIndex);
- mInitialTouchY = event.getY(newIndex);
- }
- break;
-
- case MotionEvent.ACTION_MOVE:
- final float h = y - mInitialTouchY;
- trackMovement(event);
- if (mQsTracking) {
-
- // Already tracking because onOverscrolled was called. We need to update here
- // so we don't stop for a frame until the next touch event gets handled in
- // onTouchEvent.
- setQsExpansionHeight(h + mInitialHeightOnTouch);
- trackMovement(event);
- return true;
- } else {
- mShadeLog.logMotionEvent(event,
- "onQsIntercept: move ignored because qs tracking disabled");
- }
- float touchSlop = getTouchSlop(event);
- if ((h > touchSlop || (h < -touchSlop && mQsExpanded))
- && Math.abs(h) > Math.abs(x - mInitialTouchX)
- && shouldQuickSettingsIntercept(mInitialTouchX, mInitialTouchY, h)) {
- mView.getParent().requestDisallowInterceptTouchEvent(true);
- mShadeLog.onQsInterceptMoveQsTrackingEnabled(h);
- mQsTracking = true;
- traceQsJank(true /* startTracing */, false /* wasCancelled */);
- onQsExpansionStarted();
- notifyExpandingFinished();
- mInitialHeightOnTouch = mQsExpansionHeight;
- mInitialTouchY = y;
- mInitialTouchX = x;
- mNotificationStackScrollLayoutController.cancelLongPress();
- return true;
- } else {
- mShadeLog.logQsTrackingNotStarted(mInitialTouchY, y, h, touchSlop, mQsExpanded,
- mCollapsedOnDown, mKeyguardShowing, isQsExpansionEnabled());
- }
- break;
-
- case MotionEvent.ACTION_CANCEL:
- case MotionEvent.ACTION_UP:
- trackMovement(event);
- mShadeLog.logMotionEvent(event, "onQsIntercept: up action, QS tracking disabled");
- mQsTracking = false;
- break;
- }
- return false;
- }
-
- @VisibleForTesting
- boolean isQsTracking() {
- return mQsTracking;
- }
-
private boolean isInContentBounds(float x, float y) {
float stackScrollerX = mNotificationStackScrollLayoutController.getX();
return !mNotificationStackScrollLayoutController
@@ -2367,29 +2021,14 @@
&& x < stackScrollerX + mNotificationStackScrollLayoutController.getWidth();
}
- private void traceQsJank(boolean startTracing, boolean wasCancelled) {
- if (mInteractionJankMonitor == null) {
- return;
- }
- if (startTracing) {
- mInteractionJankMonitor.begin(mView, CUJ_NOTIFICATION_SHADE_QS_EXPAND_COLLAPSE);
- } else {
- if (wasCancelled) {
- mInteractionJankMonitor.cancel(CUJ_NOTIFICATION_SHADE_QS_EXPAND_COLLAPSE);
- } else {
- mInteractionJankMonitor.end(CUJ_NOTIFICATION_SHADE_QS_EXPAND_COLLAPSE);
- }
- }
- }
-
private void initDownStates(MotionEvent event) {
if (event.getActionMasked() == MotionEvent.ACTION_DOWN) {
- mQsTouchAboveFalsingThreshold = mQsFullyExpanded;
mDozingOnDown = mDozing;
mDownX = event.getX();
mDownY = event.getY();
mCollapsedOnDown = isFullyCollapsed();
- mIsPanelCollapseOnQQS = canPanelCollapseOnQQS(mDownX, mDownY);
+ mQsController.setCollapsedOnDown(mCollapsedOnDown);
+ mIsPanelCollapseOnQQS = mQsController.canPanelCollapseOnQQS(mDownX, mDownY);
mListenForHeadsUp = mCollapsedOnDown && mHeadsUpManager.hasPinnedHeadsUp();
mAllowExpandForSmallExpansion = mExpectingSynthesizedDown;
mTouchSlopExceededBeforeDown = mExpectingSynthesizedDown;
@@ -2399,7 +2038,7 @@
event.getEventTime(),
mDownX,
mDownY,
- mQsTouchAboveFalsingThreshold,
+ mQsController.updateAndGetTouchAboveFalsingThreshold(),
mDozingOnDown,
mCollapsedOnDown,
mIsPanelCollapseOnQQS,
@@ -2414,84 +2053,14 @@
}
}
- /**
- * Can the panel collapse in this motion because it was started on QQS?
- *
- * @param downX the x location where the touch started
- * @param downY the y location where the touch started
- * @return true if the panel could be collapsed because it stared on QQS
- */
- private boolean canPanelCollapseOnQQS(float downX, float downY) {
- if (mCollapsedOnDown || mKeyguardShowing || mQsExpanded) {
- return false;
- }
- View header = mQs == null ? mKeyguardStatusBar : mQs.getHeader();
- return downX >= mQsFrame.getX() && downX <= mQsFrame.getX() + mQsFrame.getWidth()
- && downY <= header.getBottom();
-
- }
-
- private void flingQsWithCurrentVelocity(float y, boolean isCancelMotionEvent) {
- float vel = getCurrentQSVelocity();
- boolean expandsQs = flingExpandsQs(vel);
- if (expandsQs) {
- if (mFalsingManager.isUnlockingDisabled() || isFalseTouch()) {
- expandsQs = false;
- } else {
- logQsSwipeDown(y);
- }
- } else if (vel < 0) {
- mFalsingManager.isFalseTouch(QS_COLLAPSE);
- }
-
- int flingType;
- if (expandsQs && !isCancelMotionEvent) {
- flingType = FLING_EXPAND;
- } else if (mSplitShadeEnabled) {
- flingType = FLING_HIDE;
- } else {
- flingType = FLING_COLLAPSE;
- }
- flingSettings(vel, flingType);
- }
-
- private void logQsSwipeDown(float y) {
- float vel = getCurrentQSVelocity();
- final int
- gesture =
- mBarState == KEYGUARD ? MetricsEvent.ACTION_LS_QS
- : MetricsEvent.ACTION_SHADE_QS_PULL;
- mLockscreenGestureLogger.write(gesture,
- (int) ((y - mInitialTouchY) / mCentralSurfaces.getDisplayDensity()),
- (int) (vel / mCentralSurfaces.getDisplayDensity()));
- }
-
- private boolean flingExpandsQs(float vel) {
+ boolean flingExpandsQs(float vel) {
if (Math.abs(vel) < mFlingAnimationUtils.getMinVelocityPxPerSecond()) {
- return computeQsExpansionFraction() > 0.5f;
+ return mQsController.computeExpansionFraction() > 0.5f;
} else {
return vel > 0;
}
}
- private boolean isFalseTouch() {
- if (mFalsingManager.isClassifierEnabled()) {
- return mFalsingManager.isFalseTouch(Classifier.QUICK_SETTINGS);
- }
- return !mQsTouchAboveFalsingThreshold;
- }
-
- private float computeQsExpansionFraction() {
- if (mQSAnimatingHiddenFromCollapsed) {
- // When hiding QS from collapsed state, the expansion can sometimes temporarily
- // be larger than 0 because of the timing, leading to flickers.
- return 0.0f;
- }
- return Math.min(
- 1f, (mQsExpansionHeight - mQsMinExpansionHeight) / (mQsMaxExpansionHeight
- - mQsMinExpansionHeight));
- }
-
private boolean shouldExpandWhenNotFlinging() {
if (getExpandedFraction() > 0.5f) {
return true;
@@ -2509,121 +2078,13 @@
return mNotificationStackScrollLayoutController.getOpeningHeight();
}
-
- private boolean handleQsTouch(MotionEvent event) {
- if (isSplitShadeAndTouchXOutsideQs(event.getX())) {
- return false;
- }
- final int action = event.getActionMasked();
- boolean collapsedQs = !mQsExpanded && !mSplitShadeEnabled;
- boolean expandedShadeCollapsedQs = getExpandedFraction() == 1f && mBarState != KEYGUARD
- && collapsedQs && isQsExpansionEnabled();
- if (action == MotionEvent.ACTION_DOWN && expandedShadeCollapsedQs) {
- // Down in the empty area while fully expanded - go to QS.
- mShadeLog.logMotionEvent(event, "handleQsTouch: down action, QS tracking enabled");
- mQsTracking = true;
- traceQsJank(true /* startTracing */, false /* wasCancelled */);
- mConflictingQsExpansionGesture = true;
- onQsExpansionStarted();
- mInitialHeightOnTouch = mQsExpansionHeight;
- mInitialTouchY = event.getY();
- mInitialTouchX = event.getX();
- }
- if (!isFullyCollapsed() && !isShadeOrQsHeightAnimationRunning()) {
- handleQsDown(event);
- }
- // defer touches on QQS to shade while shade is collapsing. Added margin for error
- // as sometimes the qsExpansionFraction can be a tiny value instead of 0 when in QQS.
- if (!mSplitShadeEnabled && !mLastFlingWasExpanding
- && computeQsExpansionFraction() <= 0.01 && getExpandedFraction() < 1.0) {
- mShadeLog.logMotionEvent(event,
- "handleQsTouch: shade touched while collapsing, QS tracking disabled");
- mQsTracking = false;
- }
- if (!mQsExpandImmediate && mQsTracking) {
- onQsTouch(event);
- if (!mConflictingQsExpansionGesture && !mSplitShadeEnabled) {
- mShadeLog.logMotionEvent(event,
- "handleQsTouch: not immediate expand or conflicting gesture");
- return true;
- }
- }
- if (action == MotionEvent.ACTION_CANCEL || action == MotionEvent.ACTION_UP) {
- mConflictingQsExpansionGesture = false;
- }
- if (action == MotionEvent.ACTION_DOWN && isFullyCollapsed() && isQsExpansionEnabled()) {
- mTwoFingerQsExpandPossible = true;
- }
- if (mTwoFingerQsExpandPossible && isOpenQsEvent(event) && event.getY(event.getActionIndex())
- < mStatusBarMinHeight) {
- mMetricsLogger.count(COUNTER_PANEL_OPEN_QS, 1);
- setQsExpandImmediate(true);
- setShowShelfOnly(true);
- updateExpandedHeightToMaxHeight();
-
- // Normally, we start listening when the panel is expanded, but here we need to start
- // earlier so the state is already up to date when dragging down.
- setListening(true);
- }
- return false;
+ float getDisplayDensity() {
+ return mCentralSurfaces.getDisplayDensity();
}
- /** Returns whether split shade is enabled and an x coordinate is outside of the QS frame. */
- private boolean isSplitShadeAndTouchXOutsideQs(float touchX) {
- return mSplitShadeEnabled && (touchX < mQsFrame.getX()
- || touchX > mQsFrame.getX() + mQsFrame.getWidth());
- }
-
- private boolean isInQsArea(float x, float y) {
- if (isSplitShadeAndTouchXOutsideQs(x)) {
- return false;
- }
- // Let's reject anything at the very bottom around the home handle in gesture nav
- if (mIsGestureNavigation && y > mView.getHeight() - mNavigationBarBottomHeight) {
- return false;
- }
- return y <= mNotificationStackScrollLayoutController.getBottomMostNotificationBottom()
- || y <= mQs.getView().getY() + mQs.getView().getHeight();
- }
-
- private boolean isOpenQsEvent(MotionEvent event) {
- final int pointerCount = event.getPointerCount();
- final int action = event.getActionMasked();
-
- final boolean
- twoFingerDrag =
- action == MotionEvent.ACTION_POINTER_DOWN && pointerCount == 2;
-
- final boolean
- stylusButtonClickDrag =
- action == MotionEvent.ACTION_DOWN && (event.isButtonPressed(
- MotionEvent.BUTTON_STYLUS_PRIMARY) || event.isButtonPressed(
- MotionEvent.BUTTON_STYLUS_SECONDARY));
-
- final boolean
- mouseButtonClickDrag =
- action == MotionEvent.ACTION_DOWN && (event.isButtonPressed(
- MotionEvent.BUTTON_SECONDARY) || event.isButtonPressed(
- MotionEvent.BUTTON_TERTIARY));
-
- return twoFingerDrag || stylusButtonClickDrag || mouseButtonClickDrag;
- }
-
- private void handleQsDown(MotionEvent event) {
- if (event.getActionMasked() == MotionEvent.ACTION_DOWN && shouldQuickSettingsIntercept(
- event.getX(), event.getY(), -1)) {
- debugLog("handleQsDown");
- mFalsingCollector.onQsDown();
- mShadeLog.logMotionEvent(event, "handleQsDown: down action, QS tracking enabled");
- mQsTracking = true;
- onQsExpansionStarted();
- mInitialHeightOnTouch = mQsExpansionHeight;
- mInitialTouchY = event.getY();
- mInitialTouchX = event.getX();
-
- // If we interrupt an expansion gesture here, make sure to update the state correctly.
- notifyExpandingFinished();
- }
+ /** Return whether a touch is near the gesture handle at the bottom of screen */
+ public boolean isInGestureNavHomeHandleArea(float x, float y) {
+ return mIsGestureNavigation && y > mView.getHeight() - mNavigationBarBottomHeight;
}
/** Input focus transfer is about to happen. */
@@ -2680,7 +2141,7 @@
}
// If we are already running a QS expansion, make sure that we keep the panel open.
- if (mQsExpansionAnimator != null) {
+ if (mQsController.isExpansionAnimating()) {
expands = true;
}
return expands;
@@ -2694,124 +2155,9 @@
return isFullyCollapsed() || mBarState != StatusBarState.SHADE;
}
- private void onQsTouch(MotionEvent event) {
- int pointerIndex = event.findPointerIndex(mQsTrackingPointer);
- if (pointerIndex < 0) {
- pointerIndex = 0;
- mQsTrackingPointer = event.getPointerId(pointerIndex);
- }
- final float y = event.getY(pointerIndex);
- final float x = event.getX(pointerIndex);
- final float h = y - mInitialTouchY;
-
- switch (event.getActionMasked()) {
- case MotionEvent.ACTION_DOWN:
- mShadeLog.logMotionEvent(event, "onQsTouch: down action, QS tracking enabled");
- mQsTracking = true;
- traceQsJank(true /* startTracing */, false /* wasCancelled */);
- mInitialTouchY = y;
- mInitialTouchX = x;
- onQsExpansionStarted();
- mInitialHeightOnTouch = mQsExpansionHeight;
- initVelocityTracker();
- trackMovement(event);
- break;
-
- case MotionEvent.ACTION_POINTER_UP:
- final int upPointer = event.getPointerId(event.getActionIndex());
- if (mQsTrackingPointer == upPointer) {
- // gesture is ongoing, find a new pointer to track
- final int newIndex = event.getPointerId(0) != upPointer ? 0 : 1;
- final float newY = event.getY(newIndex);
- final float newX = event.getX(newIndex);
- mQsTrackingPointer = event.getPointerId(newIndex);
- mInitialHeightOnTouch = mQsExpansionHeight;
- mInitialTouchY = newY;
- mInitialTouchX = newX;
- }
- break;
-
- case MotionEvent.ACTION_MOVE:
- debugLog("onQSTouch move");
- mShadeLog.logMotionEvent(event, "onQsTouch: move action, setting QS expansion");
- setQsExpansionHeight(h + mInitialHeightOnTouch);
- if (h >= getFalsingThreshold()) {
- mQsTouchAboveFalsingThreshold = true;
- }
- trackMovement(event);
- break;
-
- case MotionEvent.ACTION_UP:
- case MotionEvent.ACTION_CANCEL:
- mShadeLog.logMotionEvent(event,
- "onQsTouch: up/cancel action, QS tracking disabled");
- mQsTracking = false;
- mQsTrackingPointer = -1;
- trackMovement(event);
- float fraction = computeQsExpansionFraction();
- if (fraction != 0f || y >= mInitialTouchY) {
- flingQsWithCurrentVelocity(y,
- event.getActionMasked() == MotionEvent.ACTION_CANCEL);
- } else {
- traceQsJank(false /* startTracing */,
- event.getActionMasked() == MotionEvent.ACTION_CANCEL);
- }
- if (mQsVelocityTracker != null) {
- mQsVelocityTracker.recycle();
- mQsVelocityTracker = null;
- }
- break;
- }
- }
-
- private int getFalsingThreshold() {
+ int getFalsingThreshold() {
float factor = mCentralSurfaces.isWakeUpComingFromTouch() ? 1.5f : 1.0f;
- return (int) (mQsFalsingThreshold * factor);
- }
-
- private void setOverScrolling(boolean overscrolling) {
- mStackScrollerOverscrolling = overscrolling;
- if (mQs == null) return;
- mQs.setOverscrolling(overscrolling);
- }
-
- private void onQsExpansionStarted() {
- cancelQsAnimation();
- cancelHeightAnimator();
-
- // Reset scroll position and apply that position to the expanded height.
- float height = mQsExpansionHeight;
- setQsExpansionHeight(height);
- mNotificationStackScrollLayoutController.checkSnoozeLeavebehind();
-
- // When expanding QS, let's authenticate the user if possible,
- // this will speed up notification actions.
- if (height == 0 && !mKeyguardStateController.canDismissLockScreen()) {
- mUpdateMonitor.requestFaceAuth(FaceAuthApiRequestReason.QS_EXPANDED);
- }
- }
-
- @VisibleForTesting
- void setQsExpanded(boolean expanded) {
- boolean changed = mQsExpanded != expanded;
- if (changed) {
- mQsExpanded = expanded;
- updateQsState();
- updateExpandedHeightToMaxHeight();
- setStatusAccessibilityImportance(expanded
- ? View.IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS
- : View.IMPORTANT_FOR_ACCESSIBILITY_AUTO);
- updateSystemUiStateFlags();
- NavigationBarView navigationBarView =
- mNavigationBarController.getNavigationBarView(mDisplayId);
- if (navigationBarView != null) {
- navigationBarView.onStatusBarPanelStateChanged();
- }
- mShadeExpansionStateManager.onQsExpansionChanged(expanded);
- mShadeLog.logQsExpansionChanged("QS Expansion Changed.", expanded,
- mQsMinExpansionHeight, mQsMaxExpansionHeight, mStackScrollerOverscrolling,
- mDozing, mQsAnimatorExpand, mAnimatingQS);
- }
+ return (int) (mQsController.getFalsingThreshold() * factor);
}
private void maybeAnimateBottomAreaAlpha() {
@@ -2843,403 +2189,29 @@
}
}
- private void updateQsState() {
- boolean qsFullScreen = mQsExpanded && !mSplitShadeEnabled;
- mNotificationStackScrollLayoutController.setQsFullScreen(qsFullScreen);
- mNotificationStackScrollLayoutController.setScrollingEnabled(
- mBarState != KEYGUARD && (!qsFullScreen || mQsExpansionFromOverscroll));
-
- if (mKeyguardUserSwitcherController != null && mQsExpanded
- && !mStackScrollerOverscrolling) {
- mKeyguardUserSwitcherController.closeSwitcherIfOpenAndNotSimple(true);
- }
- if (mQs == null) return;
- mQs.setExpanded(mQsExpanded);
- }
-
- void setQsExpansionHeight(float height) {
- height = Math.min(Math.max(height, mQsMinExpansionHeight), mQsMaxExpansionHeight);
- mQsFullyExpanded = height == mQsMaxExpansionHeight && mQsMaxExpansionHeight != 0;
- boolean qsAnimatingAway = !mQsAnimatorExpand && mAnimatingQS;
- if (height > mQsMinExpansionHeight && !mQsExpanded && !mStackScrollerOverscrolling
- && !mDozing && !qsAnimatingAway) {
- setQsExpanded(true);
- } else if (height <= mQsMinExpansionHeight && mQsExpanded) {
- setQsExpanded(false);
- }
- mQsExpansionHeight = height;
- updateQsExpansion();
- requestScrollerTopPaddingUpdate(false /* animate */);
- mKeyguardStatusBarViewController.updateViewState();
- if (mBarState == StatusBarState.SHADE_LOCKED || mBarState == KEYGUARD) {
- updateKeyguardBottomAreaAlpha();
- positionClockAndNotifications();
- }
-
- if (mAccessibilityManager.isEnabled()) {
- mView.setAccessibilityPaneTitle(determineAccessibilityPaneTitle());
- }
-
- if (!mFalsingManager.isUnlockingDisabled() && mQsFullyExpanded
- && mFalsingCollector.shouldEnforceBouncer()) {
- mCentralSurfaces.executeRunnableDismissingKeyguard(null, null /* cancelAction */,
- false /* dismissShade */, true /* afterKeyguardGone */, false /* deferred */);
- }
- if (DEBUG_DRAWABLE) {
- mView.invalidate();
- }
- }
-
- private void updateQsExpansion() {
- if (mQs == null) return;
- final float squishiness;
- if ((mQsExpandImmediate || mQsExpanded) && !mSplitShadeEnabled) {
- squishiness = 1;
- } else if (mTransitioningToFullShadeProgress > 0.0f) {
- squishiness = mLockscreenShadeTransitionController.getQsSquishTransitionFraction();
- } else {
- squishiness = mNotificationStackScrollLayoutController
- .getNotificationSquishinessFraction();
- }
- final float qsExpansionFraction = computeQsExpansionFraction();
- final float adjustedExpansionFraction = mSplitShadeEnabled
- ? 1f : computeQsExpansionFraction();
- mQs.setQsExpansion(adjustedExpansionFraction, getExpandedFraction(), getHeaderTranslation(),
- squishiness);
- mMediaHierarchyManager.setQsExpansion(qsExpansionFraction);
- int qsPanelBottomY = calculateQsBottomPosition(qsExpansionFraction);
- mScrimController.setQsPosition(qsExpansionFraction, qsPanelBottomY);
- setQSClippingBounds();
-
- if (mSplitShadeEnabled) {
- // In split shade we want to pretend that QS are always collapsed so their behaviour and
- // interactions don't influence notifications as they do in portrait. But we want to set
- // 0 explicitly in case we're rotating from non-split shade with QS expansion of 1.
- mNotificationStackScrollLayoutController.setQsExpansionFraction(0);
- } else {
- mNotificationStackScrollLayoutController.setQsExpansionFraction(qsExpansionFraction);
- }
-
- mDepthController.setQsPanelExpansion(qsExpansionFraction);
- mStatusBarKeyguardViewManager.setQsExpansion(qsExpansionFraction);
-
- float shadeExpandedFraction = isOnKeyguard()
- ? getLockscreenShadeDragProgress()
- : getExpandedFraction();
- mLargeScreenShadeHeaderController.setShadeExpandedFraction(shadeExpandedFraction);
- mLargeScreenShadeHeaderController.setQsExpandedFraction(qsExpansionFraction);
- mLargeScreenShadeHeaderController.setQsVisible(mQsVisible);
- }
-
- private float getLockscreenShadeDragProgress() {
+ /** */
+ public float getLockscreenShadeDragProgress() {
// mTransitioningToFullShadeProgress > 0 means we're doing regular lockscreen to shade
// transition. If that's not the case we should follow QS expansion fraction for when
// user is pulling from the same top to go directly to expanded QS
- return mTransitioningToFullShadeProgress > 0
+ return mQsController.getTransitioningToFullShadeProgress() > 0
? mLockscreenShadeTransitionController.getQSDragProgress()
- : computeQsExpansionFraction();
+ : mQsController.computeExpansionFraction();
}
- private void onStackYChanged(boolean shouldAnimate) {
- if (mQs != null) {
- if (shouldAnimate) {
- animateNextNotificationBounds(StackStateAnimator.ANIMATION_DURATION_STANDARD,
- 0 /* delay */);
- mNotificationBoundsAnimationDelay = 0;
- }
- setQSClippingBounds();
- }
- }
-
- private void onNotificationScrolled(int newScrollPosition) {
- updateQSExpansionEnabledAmbient();
- }
-
- private void updateQSExpansionEnabledAmbient() {
- final float scrollRangeToTop = mAmbientState.getTopPadding() - mQuickQsHeaderHeight;
- mQsExpansionEnabledAmbient = mSplitShadeEnabled
- || (mAmbientState.getScrollY() <= scrollRangeToTop);
- setQsExpansionEnabled();
- }
-
- /**
- * Updates scrim bounds, QS clipping, notifications clipping and keyguard status view clipping
- * as well based on the bounds of the shade and QS state.
- */
- private void setQSClippingBounds() {
- float qsExpansionFraction = computeQsExpansionFraction();
- final int qsPanelBottomY = calculateQsBottomPosition(qsExpansionFraction);
- final boolean qsVisible = (qsExpansionFraction > 0 || qsPanelBottomY > 0);
- checkCorrectScrimVisibility(qsExpansionFraction);
-
- int top = calculateTopQsClippingBound(qsPanelBottomY);
- int bottom = calculateBottomQsClippingBound(top);
- int left = calculateLeftQsClippingBound();
- int right = calculateRightQsClippingBound();
- // top should never be lower than bottom, otherwise it will be invisible.
- top = Math.min(top, bottom);
- applyQSClippingBounds(left, top, right, bottom, qsVisible);
- }
-
- private void checkCorrectScrimVisibility(float expansionFraction) {
- // issues with scrims visible on keyguard occur only in split shade
- if (mSplitShadeEnabled) {
- boolean keyguardViewsVisible = mBarState == KEYGUARD && mKeyguardOnlyContentAlpha == 1;
- // expansionFraction == 1 means scrims are fully visible as their size/visibility depend
- // on QS expansion
- if (expansionFraction == 1 && keyguardViewsVisible) {
- Log.wtf(TAG,
- "Incorrect state, scrim is visible at the same time when clock is visible");
- }
- }
- }
-
- private int calculateTopQsClippingBound(int qsPanelBottomY) {
- int top;
- if (mSplitShadeEnabled) {
- top = Math.min(qsPanelBottomY, mLargeScreenShadeHeaderHeight);
- } else {
- if (mTransitioningToFullShadeProgress > 0.0f) {
- // If we're transitioning, let's use the actual value. The else case
- // can be wrong during transitions when waiting for the keyguard to unlock
- top = mTransitionToFullShadeQSPosition;
- } else {
- final float notificationTop = getQSEdgePosition();
- if (isOnKeyguard()) {
- if (mKeyguardBypassController.getBypassEnabled()) {
- // When bypassing on the keyguard, let's use the panel bottom.
- // this should go away once we unify the stackY position and don't have
- // to do this min anymore below.
- top = qsPanelBottomY;
- } else {
- top = (int) Math.min(qsPanelBottomY, notificationTop);
- }
- } else {
- top = (int) notificationTop;
- }
- }
- top += mOverStretchAmount;
- // Correction for instant expansion caused by HUN pull down/
- if (mMinFraction > 0f && mMinFraction < 1f) {
- float realFraction =
- (getExpandedFraction() - mMinFraction) / (1f - mMinFraction);
- top *= MathUtils.saturate(realFraction / mMinFraction);
- }
- }
- return top;
- }
-
- private int calculateBottomQsClippingBound(int top) {
- if (mSplitShadeEnabled) {
- return top + mNotificationStackScrollLayoutController.getHeight()
- + mSplitShadeNotificationsScrimMarginBottom;
- } else {
- return mView.getBottom();
- }
- }
-
- private int calculateLeftQsClippingBound() {
- if (mIsFullWidth) {
- // left bounds can ignore insets, it should always reach the edge of the screen
- return 0;
- } else {
- return mNotificationStackScrollLayoutController.getLeft() + mDisplayLeftInset;
- }
- }
-
- private int calculateRightQsClippingBound() {
- if (mIsFullWidth) {
- return mView.getRight() + mDisplayRightInset;
- } else {
- return mNotificationStackScrollLayoutController.getRight() + mDisplayLeftInset;
- }
- }
-
- /**
- * Applies clipping to quick settings, notifications layout and
- * updates bounds of the notifications background (notifications scrim).
- *
- * The parameters are bounds of the notifications area rectangle, this function
- * calculates bounds for the QS clipping based on the notifications bounds.
- */
- private void applyQSClippingBounds(int left, int top, int right, int bottom,
- boolean qsVisible) {
- if (!mAnimateNextNotificationBounds || mLastQsClipBounds.isEmpty()) {
- if (mQsClippingAnimation != null) {
- // update the end position of the animator
- mQsClippingAnimationEndBounds.set(left, top, right, bottom);
- } else {
- applyQSClippingImmediately(left, top, right, bottom, qsVisible);
- }
- } else {
- mQsClippingAnimationEndBounds.set(left, top, right, bottom);
- final int startLeft = mLastQsClipBounds.left;
- final int startTop = mLastQsClipBounds.top;
- final int startRight = mLastQsClipBounds.right;
- final int startBottom = mLastQsClipBounds.bottom;
- if (mQsClippingAnimation != null) {
- mQsClippingAnimation.cancel();
- }
- mQsClippingAnimation = ValueAnimator.ofFloat(0.0f, 1.0f);
- mQsClippingAnimation.setInterpolator(Interpolators.FAST_OUT_SLOW_IN);
- mQsClippingAnimation.setDuration(mNotificationBoundsAnimationDuration);
- mQsClippingAnimation.setStartDelay(mNotificationBoundsAnimationDelay);
- mQsClippingAnimation.addUpdateListener(animation -> {
- float fraction = animation.getAnimatedFraction();
- int animLeft = (int) MathUtils.lerp(startLeft,
- mQsClippingAnimationEndBounds.left, fraction);
- int animTop = (int) MathUtils.lerp(startTop,
- mQsClippingAnimationEndBounds.top, fraction);
- int animRight = (int) MathUtils.lerp(startRight,
- mQsClippingAnimationEndBounds.right, fraction);
- int animBottom = (int) MathUtils.lerp(startBottom,
- mQsClippingAnimationEndBounds.bottom, fraction);
- applyQSClippingImmediately(animLeft, animTop, animRight, animBottom,
- qsVisible /* qsVisible */);
- });
- mQsClippingAnimation.addListener(new AnimatorListenerAdapter() {
- @Override
- public void onAnimationEnd(Animator animation) {
- mQsClippingAnimation = null;
- mIsQsTranslationResetAnimator = false;
- mIsPulseExpansionResetAnimator = false;
- }
- });
- mQsClippingAnimation.start();
- }
- mAnimateNextNotificationBounds = false;
- mNotificationBoundsAnimationDelay = 0;
- }
-
- private void applyQSClippingImmediately(int left, int top, int right, int bottom,
- boolean qsVisible) {
- int radius = mScrimCornerRadius;
- boolean clipStatusView = false;
- mLastQsClipBounds.set(left, top, right, bottom);
- if (mIsFullWidth) {
- clipStatusView = qsVisible;
- float screenCornerRadius = mRecordingController.isRecording() ? 0 : mScreenCornerRadius;
- radius = (int) MathUtils.lerp(screenCornerRadius, mScrimCornerRadius,
- Math.min(top / (float) mScrimCornerRadius, 1f));
- }
- if (mQs != null) {
- float qsTranslation = 0;
- boolean pulseExpanding = mPulseExpansionHandler.isExpanding();
- if (mTransitioningToFullShadeProgress > 0.0f || pulseExpanding
- || (mQsClippingAnimation != null
- && (mIsQsTranslationResetAnimator || mIsPulseExpansionResetAnimator))) {
- if (pulseExpanding || mIsPulseExpansionResetAnimator) {
- // qsTranslation should only be positive during pulse expansion because it's
- // already translating in from the top
- qsTranslation = Math.max(0, (top - mQs.getHeader().getHeight()) / 2.0f);
- } else if (!mSplitShadeEnabled) {
- qsTranslation = (top - mQs.getHeader().getHeight()) * QS_PARALLAX_AMOUNT;
- }
- }
- mQsTranslationForFullShadeTransition = qsTranslation;
- updateQsFrameTranslation();
- float currentTranslation = mQsFrame.getTranslationY();
- mQsClipTop = mEnableQsClipping
- ? (int) (top - currentTranslation - mQsFrame.getTop()) : 0;
- mQsClipBottom = mEnableQsClipping
- ? (int) (bottom - currentTranslation - mQsFrame.getTop()) : 0;
- mQsVisible = qsVisible;
- mQs.setQsVisible(mQsVisible);
- mQs.setFancyClipping(
- mQsClipTop,
- mQsClipBottom,
- radius,
- qsVisible && !mSplitShadeEnabled);
- mKeyguardInteractor.setQuickSettingsVisible(mQsVisible);
- }
- // The padding on this area is large enough that we can use a cheaper clipping strategy
- mKeyguardStatusViewController.setClipBounds(clipStatusView ? mLastQsClipBounds : null);
- // Increase the height of the notifications scrim when not in split shade
- // (e.g. portrait tablet) so the rounded corners are not visible at the bottom,
- // in this case they are rendered off-screen
- final int notificationsScrimBottom = mSplitShadeEnabled ? bottom : bottom + radius;
- mScrimController.setNotificationsBounds(left, top, right, notificationsScrimBottom);
-
- if (mSplitShadeEnabled) {
- mKeyguardStatusBarViewController.setNoTopClipping();
- } else {
- mKeyguardStatusBarViewController.updateTopClipping(top);
- }
-
- mScrimController.setScrimCornerRadius(radius);
-
- // Convert global clipping coordinates to local ones,
- // relative to NotificationStackScrollLayout
- int nsslLeft = calculateNsslLeft(left);
- int nsslRight = calculateNsslRight(right);
- int nsslTop = getNotificationsClippingTopBounds(top);
- int nsslBottom = bottom - mNotificationStackScrollLayoutController.getTop();
- int bottomRadius = mSplitShadeEnabled ? radius : 0;
- int topRadius = mSplitShadeEnabled && mExpandingFromHeadsUp ? 0 : radius;
- mNotificationStackScrollLayoutController.setRoundedClippingBounds(
- nsslLeft, nsslTop, nsslRight, nsslBottom, topRadius, bottomRadius);
- }
-
- private int calculateNsslLeft(int nsslLeftAbsolute) {
- int left = nsslLeftAbsolute - mNotificationStackScrollLayoutController.getLeft();
- if (mIsFullWidth) {
- return left;
- }
- return left - mDisplayLeftInset;
- }
-
- private int calculateNsslRight(int nsslRightAbsolute) {
- int right = nsslRightAbsolute - mNotificationStackScrollLayoutController.getLeft();
- if (mIsFullWidth) {
- return right;
- }
- return right - mDisplayLeftInset;
- }
-
- private int getNotificationsClippingTopBounds(int qsTop) {
- if (mSplitShadeEnabled && mExpandingFromHeadsUp) {
- // in split shade nssl has extra top margin so clipping at top 0 is not enough, we need
- // to set top clipping bound to negative value to allow HUN to go up to the top edge of
- // the screen without clipping.
- return -mAmbientState.getStackTopMargin();
- } else {
- return qsTop - mNotificationStackScrollLayoutController.getTop();
- }
- }
-
- private float getQSEdgePosition() {
- // TODO: replace StackY with unified calculation
- return Math.max(mQuickQsHeaderHeight * mAmbientState.getExpansionFraction(),
- mAmbientState.getStackY()
- // need to adjust for extra margin introduced by large screen shade header
- + mAmbientState.getStackTopMargin() * mAmbientState.getExpansionFraction()
- - mAmbientState.getScrollY());
- }
-
- private int calculateQsBottomPosition(float qsExpansionFraction) {
- if (mTransitioningToFullShadeProgress > 0.0f) {
- return mTransitionToFullShadeQSPosition;
- } else if (mSplitShadeEnabled) {
- // in split shade - outside lockscreen transition handled above - we simply jump between
- // two qs expansion values - either shade is closed and qs expansion is 0 or shade is
- // open and qs expansion is 1
- int qsBottomTarget = mQs.getDesiredHeight() + mLargeScreenShadeHeaderHeight;
- return qsExpansionFraction > 0 ? qsBottomTarget : 0;
- } else {
- int qsBottomYFrom = (int) getHeaderTranslation() + mQs.getQsMinExpansionHeight();
- int expandedTopMargin = mUseLargeScreenShadeHeader ? mLargeScreenShadeHeaderHeight : 0;
- int qsBottomYTo = mQs.getDesiredHeight() + expandedTopMargin;
- return (int) MathUtils.lerp(qsBottomYFrom, qsBottomYTo, qsExpansionFraction);
- }
- }
-
- private String determineAccessibilityPaneTitle() {
- if (mQs != null && mQs.isCustomizing()) {
+ String determineAccessibilityPaneTitle() {
+ if (mQsController != null && mQsController.isCustomizing()) {
return mResources.getString(R.string.accessibility_desc_quick_settings_edit);
- } else if (mQsExpansionHeight != 0.0f && mQsFullyExpanded) {
+ } else if (mQsController != null && mQsController.getExpansionHeight() != 0.0f
+ && mQsController.getFullyExpanded()) {
// Upon initialisation when we are not layouted yet we don't want to announce that we
// are fully expanded, hence the != 0.0f check.
- return mResources.getString(R.string.accessibility_desc_quick_settings);
+ if (mSplitShadeEnabled) {
+ // In split shade, QS is expanded but it also shows notifications
+ return mResources.getString(R.string.accessibility_desc_qs_notification_shade);
+ } else {
+ return mResources.getString(R.string.accessibility_desc_quick_settings);
+ }
} else if (mBarState == KEYGUARD) {
return mResources.getString(R.string.accessibility_desc_lock_screen);
} else {
@@ -3247,42 +2219,27 @@
}
}
- float calculateNotificationsTopPadding() {
- if (mSplitShadeEnabled) {
- return mKeyguardShowing ? getKeyguardNotificationStaticPadding() : 0;
+ /** Returns the topPadding of notifications when on keyguard not respecting QS expansion. */
+ public int getKeyguardNotificationStaticPadding() {
+ if (!getKeyguardShowing()) {
+ return 0;
}
- if (mKeyguardShowing && (mQsExpandImmediate
- || mIsExpanding && mQsExpandedWhenExpandingStarted)) {
-
- // Either QS pushes the notifications down when fully expanded, or QS is fully above the
- // notifications (mostly on tablets). maxNotificationPadding denotes the normal top
- // padding on Keyguard, maxQsPadding denotes the top padding from the quick settings
- // panel. We need to take the maximum and linearly interpolate with the panel expansion
- // for a nice motion.
- int maxNotificationPadding = getKeyguardNotificationStaticPadding();
- int maxQsPadding = mQsMaxExpansionHeight;
- int max = mBarState == KEYGUARD ? Math.max(
- maxNotificationPadding, maxQsPadding) : maxQsPadding;
- return (int) MathUtils.lerp((float) mQsMinExpansionHeight, (float) max,
- getExpandedFraction());
- } else if (mQsSizeChangeAnimator != null) {
- return Math.max(
- (int) mQsSizeChangeAnimator.getAnimatedValue(),
- getKeyguardNotificationStaticPadding());
- } else if (mKeyguardShowing) {
- // We can only do the smoother transition on Keyguard when we also are not collapsing
- // from a scrolled quick settings.
- return MathUtils.lerp((float) getKeyguardNotificationStaticPadding(),
- (float) (mQsMaxExpansionHeight),
- computeQsExpansionFraction());
+ if (!mKeyguardBypassController.getBypassEnabled()) {
+ return mClockPositionResult.stackScrollerPadding;
+ }
+ int collapsedPosition = mHeadsUpInset;
+ if (!mNotificationStackScrollLayoutController.isPulseExpanding()) {
+ return collapsedPosition;
} else {
- return mQsFrameTranslateController.getNotificationsTopPadding(mQsExpansionHeight,
- mNotificationStackScrollLayoutController);
+ int expandedPosition =
+ mClockPositionResult.stackScrollerPadding;
+ return (int) MathUtils.lerp(collapsedPosition, expandedPosition,
+ mNotificationStackScrollLayoutController.calculateAppearFractionBypass());
}
}
public boolean getKeyguardShowing() {
- return mKeyguardShowing;
+ return mBarState == KEYGUARD;
}
public float getKeyguardNotificationTopPadding() {
@@ -3293,94 +2250,18 @@
return mKeyguardNotificationBottomPadding;
}
- /** Returns the topPadding of notifications when on keyguard not respecting QS expansion. */
- private int getKeyguardNotificationStaticPadding() {
- if (!mKeyguardShowing) {
- return 0;
- }
- if (!mKeyguardBypassController.getBypassEnabled()) {
- return mClockPositionResult.stackScrollerPadding;
- }
- int collapsedPosition = mHeadsUpInset;
- if (!mNotificationStackScrollLayoutController.isPulseExpanding()) {
- return collapsedPosition;
- } else {
- int expandedPosition = mClockPositionResult.stackScrollerPadding;
- return (int) MathUtils.lerp(collapsedPosition, expandedPosition,
- mNotificationStackScrollLayoutController.calculateAppearFractionBypass());
- }
- }
-
- private void requestScrollerTopPaddingUpdate(boolean animate) {
+ void requestScrollerTopPaddingUpdate(boolean animate) {
mNotificationStackScrollLayoutController.updateTopPadding(
- calculateNotificationsTopPadding(), animate);
- if (mKeyguardShowing && mKeyguardBypassController.getBypassEnabled()) {
+ mQsController.calculateNotificationsTopPadding(mIsExpanding,
+ getKeyguardNotificationStaticPadding(), mExpandedFraction), animate);
+ if (getKeyguardShowing()
+ && mKeyguardBypassController.getBypassEnabled()) {
// update the position of the header
- updateQsExpansion();
+ mQsController.updateExpansion();
}
}
/**
- * Set the amount of pixels we have currently dragged down if we're transitioning to the full
- * shade. 0.0f means we're not transitioning yet.
- */
- public void setTransitionToFullShadeAmount(float pxAmount, boolean animate, long delay) {
- if (animate && mIsFullWidth) {
- animateNextNotificationBounds(StackStateAnimator.ANIMATION_DURATION_GO_TO_FULL_SHADE,
- delay);
- mIsQsTranslationResetAnimator = mQsTranslationForFullShadeTransition > 0.0f;
- }
- float endPosition = 0;
- if (pxAmount > 0.0f) {
- if (mSplitShadeEnabled) {
- float qsHeight = MathUtils.lerp(mQsMinExpansionHeight, mQsMaxExpansionHeight,
- mLockscreenShadeTransitionController.getQSDragProgress());
- setQsExpansionHeight(qsHeight);
- }
- if (mNotificationStackScrollLayoutController.getVisibleNotificationCount() == 0
- && !mMediaDataManager.hasActiveMediaOrRecommendation()) {
- // No notifications are visible, let's animate to the height of qs instead
- if (mQs != null) {
- // Let's interpolate to the header height instead of the top padding,
- // because the toppadding is way too low because of the large clock.
- // we still want to take into account the edgePosition though as that nicely
- // overshoots in the stackscroller
- endPosition = getQSEdgePosition()
- - mNotificationStackScrollLayoutController.getTopPadding()
- + mQs.getHeader().getHeight();
- }
- } else {
- // Interpolating to the new bottom edge position!
- endPosition = getQSEdgePosition()
- + mNotificationStackScrollLayoutController.getFullShadeTransitionInset();
- if (isOnKeyguard()) {
- endPosition -= mLockscreenNotificationQSPadding;
- }
- }
- }
-
- // Calculate the overshoot amount such that we're reaching the target after our desired
- // distance, but only reach it fully once we drag a full shade length.
- mTransitioningToFullShadeProgress = Interpolators.FAST_OUT_SLOW_IN.getInterpolation(
- MathUtils.saturate(pxAmount / mDistanceForQSFullShadeTransition));
-
- int position = (int) MathUtils.lerp((float) 0, endPosition,
- mTransitioningToFullShadeProgress);
- if (mTransitioningToFullShadeProgress > 0.0f) {
- // we want at least 1 pixel otherwise the panel won't be clipped
- position = Math.max(1, position);
- }
- mTransitionToFullShadeQSPosition = position;
- updateQsExpansion();
- }
-
- /** Called when pulse expansion has finished and this is going to the full shade. */
- public void onPulseExpansionFinished() {
- animateNextNotificationBounds(StackStateAnimator.ANIMATION_DURATION_GO_TO_FULL_SHADE, 0);
- mIsPulseExpansionResetAnimator = true;
- }
-
- /**
* Set the alpha and translationY of the keyguard elements which only show on the lockscreen,
* but not in shade locked / shade. This is used when dragging down to the full shade.
*/
@@ -3404,154 +2285,14 @@
mKeyguardStatusBarViewController.setAlpha(alpha);
}
- private void trackMovement(MotionEvent event) {
- if (mQsVelocityTracker != null) mQsVelocityTracker.addMovement(event);
- }
-
- private void initVelocityTracker() {
- if (mQsVelocityTracker != null) {
- mQsVelocityTracker.recycle();
- }
- mQsVelocityTracker = VelocityTracker.obtain();
- }
-
- private float getCurrentQSVelocity() {
- if (mQsVelocityTracker == null) {
- return 0;
- }
- mQsVelocityTracker.computeCurrentVelocity(1000);
- return mQsVelocityTracker.getYVelocity();
- }
-
- private void cancelQsAnimation() {
- if (mQsExpansionAnimator != null) {
- mQsExpansionAnimator.cancel();
- }
- }
-
- /** @see #flingSettings(float, int, Runnable, boolean) */
- public void flingSettings(float vel, int type) {
- flingSettings(vel, type, null /* onFinishRunnable */, false /* isClick */);
- }
-
- /**
- * Animates QS or QQS as if the user had swiped up or down.
- *
- * @param vel Finger velocity or 0 when not initiated by touch events.
- * @param type Either {@link #FLING_EXPAND}, {@link #FLING_COLLAPSE} or {@link
- * #FLING_HIDE}.
- * @param onFinishRunnable Runnable to be executed at the end of animation.
- * @param isClick If originated by click (different interpolator and duration.)
- */
- private void flingSettings(float vel, int type, final Runnable onFinishRunnable,
- boolean isClick) {
- float target;
- switch (type) {
- case FLING_EXPAND:
- target = mQsMaxExpansionHeight;
- break;
- case FLING_COLLAPSE:
- target = mQsMinExpansionHeight;
- break;
- case FLING_HIDE:
- default:
- if (mQs != null) {
- mQs.closeDetail();
- }
- target = 0;
- }
- if (target == mQsExpansionHeight) {
- if (onFinishRunnable != null) {
- onFinishRunnable.run();
- }
- traceQsJank(false /* startTracing */, type != FLING_EXPAND /* wasCancelled */);
- return;
- }
-
- // If we move in the opposite direction, reset velocity and use a different duration.
- boolean oppositeDirection = false;
- boolean expanding = type == FLING_EXPAND;
- if (vel > 0 && !expanding || vel < 0 && expanding) {
- vel = 0;
- oppositeDirection = true;
- }
- ValueAnimator animator = ValueAnimator.ofFloat(mQsExpansionHeight, target);
- if (isClick) {
- animator.setInterpolator(Interpolators.TOUCH_RESPONSE);
- animator.setDuration(368);
- } else {
- mFlingAnimationUtils.apply(animator, mQsExpansionHeight, target, vel);
- }
- if (oppositeDirection) {
- animator.setDuration(350);
- }
- animator.addUpdateListener(
- animation -> setQsExpansionHeight((Float) animation.getAnimatedValue()));
- animator.addListener(new AnimatorListenerAdapter() {
- private boolean mIsCanceled;
-
- @Override
- public void onAnimationStart(Animator animation) {
- notifyExpandingStarted();
- }
-
- @Override
- public void onAnimationCancel(Animator animation) {
- mIsCanceled = true;
- }
-
- @Override
- public void onAnimationEnd(Animator animation) {
- mQSAnimatingHiddenFromCollapsed = false;
- mAnimatingQS = false;
- notifyExpandingFinished();
- mNotificationStackScrollLayoutController.resetCheckSnoozeLeavebehind();
- mQsExpansionAnimator = null;
- if (onFinishRunnable != null) {
- onFinishRunnable.run();
- }
- traceQsJank(false /* startTracing */, mIsCanceled /* wasCancelled */);
- }
- });
- // Let's note that we're animating QS. Moving the animator here will cancel it immediately,
- // so we need a separate flag.
- mAnimatingQS = true;
- animator.start();
- mQsExpansionAnimator = animator;
- mQsAnimatorExpand = expanding;
- mQSAnimatingHiddenFromCollapsed = computeQsExpansionFraction() == 0.0f && target == 0;
- }
-
- /**
- * @return Whether we should intercept a gesture to open Quick Settings.
- */
- private boolean shouldQuickSettingsIntercept(float x, float y, float yDiff) {
- if (!isQsExpansionEnabled() || mCollapsedOnDown
- || (mKeyguardShowing && mKeyguardBypassController.getBypassEnabled())
- || mSplitShadeEnabled) {
- return false;
- }
- View header = mKeyguardShowing || mQs == null ? mKeyguardStatusBar : mQs.getHeader();
- int frameTop = mKeyguardShowing || mQs == null ? 0 : mQsFrame.getTop();
- mQsInterceptRegion.set(
- /* left= */ (int) mQsFrame.getX(),
- /* top= */ header.getTop() + frameTop,
- /* right= */ (int) mQsFrame.getX() + mQsFrame.getWidth(),
- /* bottom= */ header.getBottom() + frameTop);
- // Also allow QS to intercept if the touch is near the notch.
- mStatusBarTouchableRegionManager.updateRegionForNotch(mQsInterceptRegion);
- final boolean onHeader = mQsInterceptRegion.contains((int) x, (int) y);
-
- if (mQsExpanded) {
- return onHeader || (yDiff < 0 && isInQsArea(x, y));
- } else {
- return onHeader;
- }
+ /** */
+ public float getKeyguardOnlyContentAlpha() {
+ return mKeyguardOnlyContentAlpha;
}
@VisibleForTesting
boolean canCollapsePanelOnTouch() {
- if (!isInSettings() && mBarState == KEYGUARD) {
+ if (!mQsController.getExpanded() && mBarState == KEYGUARD) {
return true;
}
@@ -3559,20 +2300,22 @@
return true;
}
- return !mSplitShadeEnabled && (isInSettings() || mIsPanelCollapseOnQQS);
+ return !mSplitShadeEnabled && (mQsController.getExpanded() || mIsPanelCollapseOnQQS);
}
int getMaxPanelHeight() {
int min = mStatusBarMinHeight;
if (!(mBarState == KEYGUARD)
&& mNotificationStackScrollLayoutController.getNotGoneChildCount() == 0) {
- int minHeight = mQsMinExpansionHeight;
+ int minHeight = mQsController.getMinExpansionHeight();
min = Math.max(min, minHeight);
}
int maxHeight;
- if (mQsExpandImmediate || mQsExpanded || mIsExpanding && mQsExpandedWhenExpandingStarted
+ if (mQsController.isExpandImmediate() || mQsController.getExpanded()
+ || mIsExpanding && mQsController.getExpandedWhenExpandingStarted()
|| mPulsing || mSplitShadeEnabled) {
- maxHeight = calculatePanelHeightQsExpanded();
+ maxHeight = mQsController.calculatePanelHeightExpanded(
+ mClockPositionResult.stackScrollerPadding);
} else {
maxHeight = calculatePanelHeightShade();
}
@@ -3580,17 +2323,15 @@
if (maxHeight == 0) {
Log.wtf(TAG, "maxPanelHeight is invalid. mOverExpansion: "
+ mOverExpansion + ", calculatePanelHeightQsExpanded: "
- + calculatePanelHeightQsExpanded() + ", calculatePanelHeightShade: "
- + calculatePanelHeightShade() + ", mStatusBarMinHeight = "
- + mStatusBarMinHeight + ", mQsMinExpansionHeight = " + mQsMinExpansionHeight);
+ + mQsController.calculatePanelHeightExpanded(
+ mClockPositionResult.stackScrollerPadding)
+ + ", calculatePanelHeightShade: " + calculatePanelHeightShade()
+ + ", mStatusBarMinHeight = " + mStatusBarMinHeight
+ + ", mQsMinExpansionHeight = " + mQsController.getMinExpansionHeight());
}
return maxHeight;
}
- public boolean isInSettings() {
- return mQsExpanded;
- }
-
public boolean isExpanding() {
return mIsExpanding;
}
@@ -3603,7 +2344,8 @@
mShadeLog.logExpansionChanged("onHeightUpdated: fully expanded.",
mExpandedFraction, isExpanded(), mTracking, mExpansionDragDownAmountPx);
}
- if (!mQsExpanded || mQsExpandImmediate || mIsExpanding && mQsExpandedWhenExpandingStarted) {
+ if (!mQsController.getExpanded() || mQsController.isExpandImmediate()
+ || mIsExpanding && mQsController.getExpandedWhenExpandingStarted()) {
// Updating the clock position will set the top padding which might
// trigger a new panel height and re-position the clock.
// This is a circular dependency and should be avoided, otherwise we'll have
@@ -3614,14 +2356,8 @@
positionClockAndNotifications();
}
}
- // Below is true when QS are expanded and we swipe up from the same bottom of panel to
- // close the whole shade with one motion. Also this will be always true when closing
- // split shade as there QS are always expanded so every collapsing motion is motion from
- // expanded QS to closed panel
- boolean collapsingShadeFromExpandedQs = mQsExpanded && !mQsTracking
- && mQsExpansionAnimator == null && !mQsExpansionFromOverscroll;
boolean goingBetweenClosedShadeAndExpandedQs =
- mQsExpandImmediate || collapsingShadeFromExpandedQs;
+ mQsController.isGoingBetweenClosedShadeAndExpandedQs();
// in split shade we react when HUN is visible only if shade height is over HUN start
// height - which means user is swiping down. Otherwise shade QS will either not show at all
// with HUN movement or it will blink when touching HUN initially
@@ -3631,7 +2367,7 @@
float qsExpansionFraction;
if (mSplitShadeEnabled) {
qsExpansionFraction = 1;
- } else if (mKeyguardShowing) {
+ } else if (getKeyguardShowing()) {
// On Keyguard, interpolate the QS expansion linearly to the panel expansion
qsExpansionFraction = expandedHeight / (getMaxPanelHeight());
} else {
@@ -3640,13 +2376,15 @@
float panelHeightQsCollapsed =
mNotificationStackScrollLayoutController.getIntrinsicPadding()
+ mNotificationStackScrollLayoutController.getLayoutMinHeight();
- float panelHeightQsExpanded = calculatePanelHeightQsExpanded();
+ float panelHeightQsExpanded = mQsController.calculatePanelHeightExpanded(
+ mClockPositionResult.stackScrollerPadding);
qsExpansionFraction = (expandedHeight - panelHeightQsCollapsed)
/ (panelHeightQsExpanded - panelHeightQsCollapsed);
}
- float targetHeight = mQsMinExpansionHeight
- + qsExpansionFraction * (mQsMaxExpansionHeight - mQsMinExpansionHeight);
- setQsExpansionHeight(targetHeight);
+ float targetHeight = mQsController.getMinExpansionHeight() + qsExpansionFraction
+ * (mQsController.getMaxExpansionHeight()
+ - mQsController.getMinExpansionHeight());
+ mQsController.setExpansionHeight(targetHeight);
}
updateExpandedHeight(expandedHeight);
updateHeader();
@@ -3663,8 +2401,8 @@
if (mPanelExpanded != isExpanded) {
mPanelExpanded = isExpanded;
mShadeExpansionStateManager.onShadeExpansionFullyChanged(isExpanded);
- if (!isExpanded && mQs != null && mQs.isCustomizing()) {
- mQs.closeCustomizer();
+ if (!isExpanded) {
+ mQsController.closeQsCustomizer();
}
}
}
@@ -3686,40 +2424,6 @@
}
}
- int calculatePanelHeightQsExpanded() {
- float
- notificationHeight =
- mNotificationStackScrollLayoutController.getHeight()
- - mNotificationStackScrollLayoutController.getEmptyBottomMargin()
- - mNotificationStackScrollLayoutController.getTopPadding();
-
- // When only empty shade view is visible in QS collapsed state, simulate that we would have
- // it in expanded QS state as well so we don't run into troubles when fading the view in/out
- // and expanding/collapsing the whole panel from/to quick settings.
- if (mNotificationStackScrollLayoutController.getNotGoneChildCount() == 0
- && mNotificationStackScrollLayoutController.isShowingEmptyShadeView()) {
- notificationHeight = mNotificationStackScrollLayoutController.getEmptyShadeViewHeight();
- }
- int maxQsHeight = mQsMaxExpansionHeight;
-
- // If an animation is changing the size of the QS panel, take the animated value.
- if (mQsSizeChangeAnimator != null) {
- maxQsHeight = (int) mQsSizeChangeAnimator.getAnimatedValue();
- }
- float totalHeight = Math.max(maxQsHeight,
- mBarState == KEYGUARD ? mClockPositionResult.stackScrollerPadding
- : 0) + notificationHeight
- + mNotificationStackScrollLayoutController.getTopPaddingOverflow();
- if (totalHeight > mNotificationStackScrollLayoutController.getHeight()) {
- float
- fullyCollapsedHeight =
- maxQsHeight + mNotificationStackScrollLayoutController.getLayoutMinHeight();
- totalHeight = Math.max(fullyCollapsedHeight,
- mNotificationStackScrollLayoutController.getHeight());
- }
- return (int) totalHeight;
- }
-
private void updateNotificationTranslucency() {
if (mIsOcclusionTransitionRunning) {
return;
@@ -3738,10 +2442,10 @@
private float getFadeoutAlpha() {
float alpha;
- if (mQsMinExpansionHeight == 0) {
+ if (mQsController.getMinExpansionHeight() == 0) {
return 1.0f;
}
- alpha = getExpandedHeight() / mQsMinExpansionHeight;
+ alpha = getExpandedHeight() / mQsController.getMinExpansionHeight();
alpha = Math.max(0, Math.min(alpha, 1));
alpha = (float) Math.pow(alpha, 0.75);
return alpha;
@@ -3752,30 +2456,7 @@
if (mBarState == KEYGUARD) {
mKeyguardStatusBarViewController.updateViewState();
}
- updateQsExpansion();
- }
-
- private float getHeaderTranslation() {
- if (mSplitShadeEnabled) {
- // in split shade QS don't translate, just (un)squish and overshoot
- return 0;
- }
- if (mBarState == KEYGUARD && !mKeyguardBypassController.getBypassEnabled()) {
- return -mQs.getQsMinExpansionHeight();
- }
- float appearAmount = mNotificationStackScrollLayoutController
- .calculateAppearFraction(mExpandedHeight);
- float startHeight = -mQsExpansionHeight;
- if (mBarState == StatusBarState.SHADE) {
- // Small parallax as we pull down and clip QS
- startHeight = -mQsExpansionHeight * QS_PARALLAX_AMOUNT;
- }
- if (mKeyguardBypassController.getBypassEnabled() && isOnKeyguard()) {
- appearAmount = mNotificationStackScrollLayoutController.calculateAppearFractionBypass();
- startHeight = -mQs.getQsMinExpansionHeight();
- }
- float translation = MathUtils.lerp(startHeight, 0, Math.min(1.0f, appearAmount));
- return Math.min(0, translation);
+ mQsController.updateExpansion();
}
private void updateKeyguardBottomAreaAlpha() {
@@ -3792,28 +2473,12 @@
isUnlockHintRunning() ? 0 : KeyguardBouncerConstants.ALPHA_EXPANSION_THRESHOLD, 1f,
0f, 1f,
getExpandedFraction());
- float alpha = Math.min(expansionAlpha, 1 - computeQsExpansionFraction());
+ float alpha = Math.min(expansionAlpha, 1 - mQsController.computeExpansionFraction());
alpha *= mBottomAreaShadeAlpha;
mKeyguardBottomAreaInteractor.setAlpha(alpha);
mLockIconViewController.setAlpha(alpha);
}
- private void onExpandingStarted() {
- mNotificationStackScrollLayoutController.onExpansionStarted();
- mIsExpanding = true;
- mQsExpandedWhenExpandingStarted = mQsFullyExpanded;
- mMediaHierarchyManager.setCollapsingShadeFromQS(mQsExpandedWhenExpandingStarted &&
- /* We also start expanding when flinging closed Qs. Let's exclude that */
- !mAnimatingQS);
- if (mQsExpanded) {
- onQsExpansionStarted();
- }
- // Since there are QS tiles in the header now, we need to make sure we start listening
- // immediately so they can be up to date.
- if (mQs == null) return;
- mQs.setHeaderListening(true);
- }
-
private void onExpandingFinished() {
if (!mUnocclusionTransitionFlagEnabled) {
mScrimController.onExpandingFinished();
@@ -3823,7 +2488,7 @@
mConversationNotificationManager.onNotificationPanelExpandStateChanged(isFullyCollapsed());
mIsExpanding = false;
mMediaHierarchyManager.setCollapsingShadeFromQS(false);
- mMediaHierarchyManager.setQsExpanded(mQsExpanded);
+ mMediaHierarchyManager.setQsExpanded(mQsController.getExpanded());
if (isFullyCollapsed()) {
DejankUtils.postAfterTraversal(() -> setListening(false));
@@ -3838,10 +2503,10 @@
if (mBarState != SHADE) {
// updating qsExpandImmediate is done in onPanelStateChanged for unlocked shade but
// on keyguard panel state is always OPEN so we need to have that extra update
- setQsExpandImmediate(false);
+ mQsController.setExpandImmediate(false);
}
setShowShelfOnly(false);
- mTwoFingerQsExpandPossible = false;
+ mQsController.setTwoFingerExpandPossible(false);
updateTrackingHeadsUp(null);
mExpandingFromHeadsUp = false;
setPanelScrimMinFraction(0.0f);
@@ -3864,8 +2529,7 @@
private void setListening(boolean listening) {
mKeyguardStatusBarViewController.setBatteryListening(listening);
- if (mQs == null) return;
- mQs.setListening(listening);
+ mQsController.setListening(listening);
}
public void expand(boolean animate) {
@@ -3899,7 +2563,7 @@
this);
if (mAnimateAfterExpanding) {
notifyExpandingStarted();
- beginJankMonitoring();
+ mQsController.beginJankMonitoring(isFullyCollapsed());
fling(0 /* expand */);
} else {
mShadeHeightLogger.logFunctionCall("expand");
@@ -3928,16 +2592,10 @@
mOverExpansion = overExpansion;
// Translating the quick settings by half the overexpansion to center it in the background
// frame
- updateQsFrameTranslation();
+ mQsController.updateQsFrameTranslation();
mNotificationStackScrollLayoutController.setOverExpansion(overExpansion);
}
- private void updateQsFrameTranslation() {
- mQsFrameTranslateController.translateQsFrame(mQsFrame, mQs,
- mNavigationBarBottomHeight + mAmbientState.getStackTopMargin());
-
- }
-
private void falsingAdditionalTapRequired() {
if (mStatusBarStateController.getState() == StatusBarState.SHADE_LOCKED) {
mTapAgainViewController.show();
@@ -3964,8 +2622,8 @@
notifyExpandingStarted();
updatePanelExpansionAndVisibility();
mScrimController.onTrackingStarted();
- if (mQsFullyExpanded) {
- setQsExpandImmediate(true);
+ if (mQsController.getFullyExpanded()) {
+ mQsController.setExpandImmediate(true);
setShowShelfOnly(true);
}
mNotificationStackScrollLayoutController.onPanelTrackingStarted();
@@ -4042,8 +2700,8 @@
// the required distance to be a specific and constant value, to make sure the expansion
// motion has the expected speed. We also only want this on non-lockscreen for now.
if (mSplitShadeEnabled && mBarState == SHADE) {
- boolean transitionFromHeadsUp =
- mHeadsUpManager.isTrackingHeadsUp() || mExpandingFromHeadsUp;
+ boolean transitionFromHeadsUp = (mHeadsUpManager != null
+ && mHeadsUpManager.isTrackingHeadsUp()) || mExpandingFromHeadsUp;
// heads-up starting height is too close to mSplitShadeFullTransitionDistance and
// when dragging HUN transition is already 90% complete. It makes shade become
// immediately visible when starting to drag. We want to set distance so that
@@ -4061,25 +2719,6 @@
}
}
- @VisibleForTesting
- boolean isTrackingBlocked() {
- return mConflictingQsExpansionGesture && mQsExpanded || mBlockingExpansionForCurrentTouch;
- }
-
- public boolean isQsExpanded() {
- return mQsExpanded;
- }
-
- /** Returns whether the QS customizer is currently active. */
- public boolean isQsCustomizing() {
- return mQs.isCustomizing();
- }
-
- /** Close the QS customizer if it is open. */
- public void closeQsCustomizer() {
- mQs.closeCustomizer();
- }
-
public void setIsLaunchAnimationRunning(boolean running) {
boolean wasRunning = mIsLaunchAnimationRunning;
mIsLaunchAnimationRunning = running;
@@ -4104,14 +2743,6 @@
}
}
- public void setQsScrimEnabled(boolean qsScrimEnabled) {
- boolean changed = mQsScrimEnabled != qsScrimEnabled;
- mQsScrimEnabled = qsScrimEnabled;
- if (changed) {
- updateQsState();
- }
- }
-
public void onScreenTurningOn() {
mKeyguardStatusViewController.dozeTimeTick();
}
@@ -4140,7 +2771,7 @@
}
break;
case StatusBarState.SHADE_LOCKED:
- if (!mQsExpanded) {
+ if (!mQsController.getExpanded()) {
mStatusBarStateController.setState(KEYGUARD);
}
break;
@@ -4289,66 +2920,6 @@
return !mShowIconsWhenExpanded;
}
- private void onQsPanelScrollChanged(int scrollY) {
- mLargeScreenShadeHeaderController.setQsScrollY(scrollY);
- if (scrollY > 0 && !mQsFullyExpanded) {
- debugLog("Scrolling while not expanded. Forcing expand");
- // If we are scrolling QS, we should be fully expanded.
- expandWithQs();
- }
- }
-
- private final class QsFragmentListener implements FragmentListener {
- @Override
- public void onFragmentViewCreated(String tag, Fragment fragment) {
- mQs = (QS) fragment;
- mQs.setPanelView(mHeightListener);
- mQs.setCollapseExpandAction(mCollapseExpandAction);
- mQs.setHeaderClickable(isQsExpansionEnabled());
- mQs.setOverscrolling(mStackScrollerOverscrolling);
- mQs.setInSplitShade(mSplitShadeEnabled);
- mQs.setIsNotificationPanelFullWidth(mIsFullWidth);
-
- // recompute internal state when qspanel height changes
- mQs.getView().addOnLayoutChangeListener(
- (v, left, top, right, bottom, oldLeft, oldTop, oldRight, oldBottom) -> {
- final int height = bottom - top;
- final int oldHeight = oldBottom - oldTop;
- if (height != oldHeight) {
- onQsHeightChanged();
- }
- });
- mQs.setCollapsedMediaVisibilityChangedListener((visible) -> {
- if (mQs.getHeader().isShown()) {
- animateNextNotificationBounds(StackStateAnimator.ANIMATION_DURATION_STANDARD,
- 0 /* delay */);
- mNotificationStackScrollLayoutController.animateNextTopPaddingChange();
- }
- });
- mLockscreenShadeTransitionController.setQS(mQs);
- mShadeTransitionController.setQs(mQs);
- mNotificationStackScrollLayoutController.setQsHeader((ViewGroup) mQs.getHeader());
- mQs.setScrollListener(mQsScrollListener);
- updateQsExpansion();
- }
-
- @Override
- public void onFragmentViewDestroyed(String tag, Fragment fragment) {
- // Manual handling of fragment lifecycle is only required because this bridges
- // non-fragment and fragment code. Once we are using a fragment for the notification
- // panel, mQs will not need to be null cause it will be tied to the same lifecycle.
- if (fragment == mQs) {
- mQs = null;
- }
- }
- }
-
- private void animateNextNotificationBounds(long duration, long delay) {
- mAnimateNextNotificationBounds = true;
- mNotificationBoundsAnimationDuration = duration;
- mNotificationBoundsAnimationDelay = delay;
- }
-
public void setTouchAndAnimationDisabled(boolean disabled) {
mTouchDisabled = disabled;
if (mTouchDisabled) {
@@ -4371,9 +2942,11 @@
if (dozing == mDozing) return;
mView.setDozing(dozing);
mDozing = dozing;
+ // TODO (b/) make listeners for this
mNotificationStackScrollLayoutController.setDozing(mDozing, animate);
mKeyguardBottomAreaInteractor.setAnimateDozingTransitions(animate);
mKeyguardStatusBarViewController.setDozing(mDozing);
+ mQsController.setDozing(mDozing);
if (dozing) {
mBottomAreaShadeAlphaAnimator.cancel();
@@ -4564,33 +3137,13 @@
ipw.print("mMaxAllowedKeyguardNotifications=");
ipw.println(mMaxAllowedKeyguardNotifications);
ipw.print("mAnimateNextPositionUpdate="); ipw.println(mAnimateNextPositionUpdate);
- ipw.print("mQuickQsHeaderHeight="); ipw.println(mQuickQsHeaderHeight);
- ipw.print("mQsTrackingPointer="); ipw.println(mQsTrackingPointer);
- ipw.print("mQsTracking="); ipw.println(mQsTracking);
- ipw.print("mConflictingQsExpansionGesture="); ipw.println(mConflictingQsExpansionGesture);
ipw.print("mPanelExpanded="); ipw.println(mPanelExpanded);
- ipw.print("mQsExpanded="); ipw.println(mQsExpanded);
- ipw.print("mQsExpandedWhenExpandingStarted="); ipw.println(mQsExpandedWhenExpandingStarted);
- ipw.print("mQsFullyExpanded="); ipw.println(mQsFullyExpanded);
- ipw.print("mKeyguardShowing="); ipw.println(mKeyguardShowing);
ipw.print("mKeyguardQsUserSwitchEnabled="); ipw.println(mKeyguardQsUserSwitchEnabled);
ipw.print("mKeyguardUserSwitcherEnabled="); ipw.println(mKeyguardUserSwitcherEnabled);
ipw.print("mDozing="); ipw.println(mDozing);
ipw.print("mDozingOnDown="); ipw.println(mDozingOnDown);
ipw.print("mBouncerShowing="); ipw.println(mBouncerShowing);
ipw.print("mBarState="); ipw.println(mBarState);
- ipw.print("mInitialHeightOnTouch="); ipw.println(mInitialHeightOnTouch);
- ipw.print("mInitialTouchX="); ipw.println(mInitialTouchX);
- ipw.print("mInitialTouchY="); ipw.println(mInitialTouchY);
- ipw.print("mQsExpansionHeight="); ipw.println(mQsExpansionHeight);
- ipw.print("mQsMinExpansionHeight="); ipw.println(mQsMinExpansionHeight);
- ipw.print("mQsMaxExpansionHeight="); ipw.println(mQsMaxExpansionHeight);
- ipw.print("mQsPeekHeight="); ipw.println(mQsPeekHeight);
- ipw.print("mStackScrollerOverscrolling="); ipw.println(mStackScrollerOverscrolling);
- ipw.print("mQsExpansionFromOverscroll="); ipw.println(mQsExpansionFromOverscroll);
- ipw.print("mLastOverscroll="); ipw.println(mLastOverscroll);
- ipw.print("mQsExpansionEnabledPolicy="); ipw.println(mQsExpansionEnabledPolicy);
- ipw.print("mQsExpansionEnabledAmbient="); ipw.println(mQsExpansionEnabledAmbient);
ipw.print("mStatusBarMinHeight="); ipw.println(mStatusBarMinHeight);
ipw.print("mStatusBarHeaderHeightKeyguard="); ipw.println(mStatusBarHeaderHeightKeyguard);
ipw.print("mOverStretchAmount="); ipw.println(mOverStretchAmount);
@@ -4599,17 +3152,8 @@
ipw.print("mDisplayTopInset="); ipw.println(mDisplayTopInset);
ipw.print("mDisplayRightInset="); ipw.println(mDisplayRightInset);
ipw.print("mDisplayLeftInset="); ipw.println(mDisplayLeftInset);
- ipw.print("mLargeScreenShadeHeaderHeight="); ipw.println(mLargeScreenShadeHeaderHeight);
- ipw.print("mSplitShadeNotificationsScrimMarginBottom=");
- ipw.println(mSplitShadeNotificationsScrimMarginBottom);
ipw.print("mIsExpanding="); ipw.println(mIsExpanding);
- ipw.print("mQsExpandImmediate="); ipw.println(mQsExpandImmediate);
- ipw.print("mTwoFingerQsExpandPossible="); ipw.println(mTwoFingerQsExpandPossible);
ipw.print("mHeaderDebugInfo="); ipw.println(mHeaderDebugInfo);
- ipw.print("mQsAnimatorExpand="); ipw.println(mQsAnimatorExpand);
- ipw.print("mQsScrimEnabled="); ipw.println(mQsScrimEnabled);
- ipw.print("mQsTouchAboveFalsingThreshold="); ipw.println(mQsTouchAboveFalsingThreshold);
- ipw.print("mQsFalsingThreshold="); ipw.println(mQsFalsingThreshold);
ipw.print("mHeadsUpStartHeight="); ipw.println(mHeadsUpStartHeight);
ipw.print("mListenForHeadsUp="); ipw.println(mListenForHeadsUp);
ipw.print("mNavigationBarBottomHeight="); ipw.println(mNavigationBarBottomHeight);
@@ -4625,7 +3169,6 @@
ipw.println(mBlockingExpansionForCurrentTouch);
ipw.print("mExpectingSynthesizedDown="); ipw.println(mExpectingSynthesizedDown);
ipw.print("mLastEventSynthesizedDown="); ipw.println(mLastEventSynthesizedDown);
- ipw.print("mLastFlingWasExpanding="); ipw.println(mLastFlingWasExpanding);
ipw.print("mInterpolatedDarkAmount="); ipw.println(mInterpolatedDarkAmount);
ipw.print("mLinearDarkAmount="); ipw.println(mLinearDarkAmount);
ipw.print("mPulsing="); ipw.println(mPulsing);
@@ -4636,40 +3179,14 @@
ipw.print("mHeadsUpInset="); ipw.println(mHeadsUpInset);
ipw.print("mHeadsUpPinnedMode="); ipw.println(mHeadsUpPinnedMode);
ipw.print("mAllowExpandForSmallExpansion="); ipw.println(mAllowExpandForSmallExpansion);
- ipw.print("mLockscreenNotificationQSPadding=");
- ipw.println(mLockscreenNotificationQSPadding);
- ipw.print("mTransitioningToFullShadeProgress=");
- ipw.println(mTransitioningToFullShadeProgress);
- ipw.print("mTransitionToFullShadeQSPosition=");
- ipw.println(mTransitionToFullShadeQSPosition);
- ipw.print("mDistanceForQSFullShadeTransition=");
- ipw.println(mDistanceForQSFullShadeTransition);
- ipw.print("mQsTranslationForFullShadeTransition=");
- ipw.println(mQsTranslationForFullShadeTransition);
ipw.print("mMaxOverscrollAmountForPulse="); ipw.println(mMaxOverscrollAmountForPulse);
- ipw.print("mAnimateNextNotificationBounds="); ipw.println(mAnimateNextNotificationBounds);
- ipw.print("mNotificationBoundsAnimationDelay=");
- ipw.println(mNotificationBoundsAnimationDelay);
- ipw.print("mNotificationBoundsAnimationDuration=");
- ipw.println(mNotificationBoundsAnimationDuration);
ipw.print("mIsPanelCollapseOnQQS="); ipw.println(mIsPanelCollapseOnQQS);
- ipw.print("mAnimatingQS="); ipw.println(mAnimatingQS);
- ipw.print("mIsQsTranslationResetAnimator="); ipw.println(mIsQsTranslationResetAnimator);
- ipw.print("mIsPulseExpansionResetAnimator="); ipw.println(mIsPulseExpansionResetAnimator);
ipw.print("mKeyguardOnlyContentAlpha="); ipw.println(mKeyguardOnlyContentAlpha);
ipw.print("mKeyguardOnlyTransitionTranslationY=");
ipw.println(mKeyguardOnlyTransitionTranslationY);
ipw.print("mUdfpsMaxYBurnInOffset="); ipw.println(mUdfpsMaxYBurnInOffset);
ipw.print("mIsGestureNavigation="); ipw.println(mIsGestureNavigation);
ipw.print("mOldLayoutDirection="); ipw.println(mOldLayoutDirection);
- ipw.print("mScrimCornerRadius="); ipw.println(mScrimCornerRadius);
- ipw.print("mScreenCornerRadius="); ipw.println(mScreenCornerRadius);
- ipw.print("mQSAnimatingHiddenFromCollapsed="); ipw.println(mQSAnimatingHiddenFromCollapsed);
- ipw.print("mUseLargeScreenShadeHeader="); ipw.println(mUseLargeScreenShadeHeader);
- ipw.print("mEnableQsClipping="); ipw.println(mEnableQsClipping);
- ipw.print("mQsClipTop="); ipw.println(mQsClipTop);
- ipw.print("mQsClipBottom="); ipw.println(mQsClipBottom);
- ipw.print("mQsVisible="); ipw.println(mQsVisible);
ipw.print("mMinFraction="); ipw.println(mMinFraction);
ipw.print("mStatusViewCentered="); ipw.println(mStatusViewCentered);
ipw.print("mSplitShadeFullTransitionDistance=");
@@ -4854,12 +3371,12 @@
public void updateSystemUiStateFlags() {
if (SysUiState.DEBUG) {
Log.d(TAG, "Updating panel sysui state flags: fullyExpanded="
- + isFullyExpanded() + " inQs=" + isInSettings());
+ + isFullyExpanded() + " inQs=" + mQsController.getExpanded());
}
mSysUiState.setFlag(SYSUI_STATE_NOTIFICATION_PANEL_EXPANDED,
- isFullyExpanded() && !isInSettings())
- .setFlag(SYSUI_STATE_QUICK_SETTINGS_EXPANDED, isFullyExpanded() && isInSettings())
- .commitUpdate(mDisplayId);
+ isFullyExpanded() && !mQsController.getExpanded())
+ .setFlag(SYSUI_STATE_QUICK_SETTINGS_EXPANDED,
+ isFullyExpanded() && mQsController.getExpanded()).commitUpdate(mDisplayId);
}
private void debugLog(String fmt, Object... args) {
@@ -4872,11 +3389,11 @@
void notifyExpandingStarted() {
if (!mExpanding) {
mExpanding = true;
- onExpandingStarted();
+ mIsExpanding = true;
+ mQsController.onExpandingStarted(mQsController.getFullyExpanded());
}
}
- @VisibleForTesting
void notifyExpandingFinished() {
endClosing();
if (mExpanding) {
@@ -4885,7 +3402,7 @@
}
}
- private float getTouchSlop(MotionEvent event) {
+ float getTouchSlop(MotionEvent event) {
// Adjust the touch slop if another gesture may be being performed.
return event.getClassification() == MotionEvent.CLASSIFICATION_AMBIGUOUS_GESTURE
? mTouchSlop * mSlopMultiplier
@@ -4959,7 +3476,7 @@
public void startExpandMotion(float newX, float newY, boolean startTracking,
float expandedHeight) {
if (!mHandlingPointerUp && !mStatusBarStateController.isDozing()) {
- beginJankMonitoring();
+ mQsController.beginJankMonitoring(isFullyCollapsed());
}
mInitialOffsetOnTouch = expandedHeight;
if (!mTracking || isFullyCollapsed()) {
@@ -5121,7 +3638,8 @@
setExpandedHeightInternal(height);
}
- private void updateExpandedHeightToMaxHeight() {
+ /** Try to set expanded height to max. */
+ void updateExpandedHeightToMaxHeight() {
float currentMaxPanelHeight = getMaxPanelHeight();
if (isFullyCollapsed()) {
@@ -5132,7 +3650,8 @@
return;
}
- if (mTracking && !isTrackingBlocked()) {
+ if (mTracking && !(mBlockingExpansionForCurrentTouch
+ || mQsController.isTrackingBlocked())) {
return;
}
@@ -5174,6 +3693,7 @@
mHeightAnimator.end();
}
}
+ mQsController.setShadeExpandedHeight(mExpandedHeight);
mExpansionDragDownAmountPx = h;
mExpandedFraction = Math.min(1f,
maxPanelHeight == 0 ? 0 : mExpandedHeight / maxPanelHeight);
@@ -5213,7 +3733,7 @@
return mExpandedHeight;
}
- private float getExpandedFraction() {
+ float getExpandedFraction() {
return mExpandedFraction;
}
@@ -5244,7 +3764,7 @@
return true;
} else {
// case of two finger swipe from the top of keyguard
- return computeQsExpansionFraction() == 1;
+ return mQsController.computeExpansionFraction() == 1;
}
}
@@ -5342,7 +3862,7 @@
}
/** Returns whether a shade or QS expansion animation is running */
- private boolean isShadeOrQsHeightAnimationRunning() {
+ public boolean isShadeOrQsHeightAnimationRunning() {
return mHeightAnimator != null && !mHintAnimationRunning && !mIsSpringBackAnimation;
}
@@ -5477,44 +3997,127 @@
return mView.isEnabled();
}
- private void beginJankMonitoring() {
- if (mInteractionJankMonitor == null) {
- return;
- }
- InteractionJankMonitor.Configuration.Builder builder =
- InteractionJankMonitor.Configuration.Builder.withView(
- InteractionJankMonitor.CUJ_NOTIFICATION_SHADE_EXPAND_COLLAPSE,
- mView)
- .setTag(isFullyCollapsed() ? "Expand" : "Collapse");
- mInteractionJankMonitor.begin(builder);
+ int getDisplayRightInset() {
+ return mDisplayRightInset;
}
- private void endJankMonitoring() {
- if (mInteractionJankMonitor == null) {
- return;
- }
- InteractionJankMonitor.getInstance().end(
- InteractionJankMonitor.CUJ_NOTIFICATION_SHADE_EXPAND_COLLAPSE);
+ int getDisplayLeftInset() {
+ return mDisplayLeftInset;
}
- private void cancelJankMonitoring() {
- if (mInteractionJankMonitor == null) {
- return;
- }
- InteractionJankMonitor.getInstance().cancel(
- InteractionJankMonitor.CUJ_NOTIFICATION_SHADE_EXPAND_COLLAPSE);
+ float getOverStretchAmount() {
+ return mOverStretchAmount;
+ }
+
+ float getMinFraction() {
+ return mMinFraction;
+ }
+
+ boolean getCollapsedOnDown() {
+ return mCollapsedOnDown;
+ }
+
+ int getNavigationBarBottomHeight() {
+ return mNavigationBarBottomHeight;
+ }
+
+ boolean isExpandingFromHeadsUp() {
+ return mExpandingFromHeadsUp;
+ }
+
+ /** TODO: remove need for this delegate (b/254870148) */
+ public void closeQs() {
+ mQsController.closeQs();
+ }
+
+ /** TODO: remove need for this delegate (b/254870148) */
+ public void setQsScrimEnabled(boolean qsScrimEnabled) {
+ mQsController.setScrimEnabled(qsScrimEnabled);
}
private ShadeExpansionStateManager getShadeExpansionStateManager() {
return mShadeExpansionStateManager;
}
+ private void onQsExpansionChanged(boolean expanded) {
+ updateExpandedHeightToMaxHeight();
+ setStatusAccessibilityImportance(expanded
+ ? View.IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS
+ : View.IMPORTANT_FOR_ACCESSIBILITY_AUTO);
+ updateSystemUiStateFlags();
+ NavigationBarView navigationBarView =
+ mNavigationBarController.getNavigationBarView(mDisplayId);
+ if (navigationBarView != null) {
+ navigationBarView.onStatusBarPanelStateChanged();
+ }
+ }
+
+ @VisibleForTesting
+ void onQsSetExpansionHeightCalled(boolean qsFullyExpanded) {
+ requestScrollerTopPaddingUpdate(false);
+ mKeyguardStatusBarViewController.updateViewState();
+ int barState = getBarState();
+ if (barState == SHADE_LOCKED || barState == KEYGUARD) {
+ updateKeyguardBottomAreaAlpha();
+ positionClockAndNotifications();
+ }
+
+ if (mAccessibilityManager.isEnabled()) {
+ mView.setAccessibilityPaneTitle(determineAccessibilityPaneTitle());
+ }
+
+ if (!mFalsingManager.isUnlockingDisabled() && qsFullyExpanded
+ && mFalsingCollector.shouldEnforceBouncer()) {
+ mCentralSurfaces.executeRunnableDismissingKeyguard(null, null,
+ false, true, false);
+ }
+ if (DEBUG_DRAWABLE) {
+ mView.invalidate();
+ }
+ }
+
+ private void onQsStateUpdated(boolean qsExpanded, boolean isStackScrollerOverscrolling) {
+ if (mKeyguardUserSwitcherController != null && qsExpanded
+ && !isStackScrollerOverscrolling) {
+ mKeyguardUserSwitcherController.closeSwitcherIfOpenAndNotSimple(true);
+ }
+ }
+
+ private void onQsClippingImmediatelyApplied(boolean clipStatusView,
+ Rect lastQsClipBounds, int top, boolean qsFragmentCreated, boolean qsVisible) {
+ if (qsFragmentCreated) {
+ mKeyguardInteractor.setQuickSettingsVisible(qsVisible);
+ }
+
+ // The padding on this area is large enough that
+ // we can use a cheaper clipping strategy
+ mKeyguardStatusViewController.setClipBounds(
+ clipStatusView ? lastQsClipBounds : null);
+ if (mSplitShadeEnabled) {
+ mKeyguardStatusBarViewController.setNoTopClipping();
+ } else {
+ mKeyguardStatusBarViewController.updateTopClipping(top);
+ }
+ }
+
+ private void onFlingQsWithoutClick(ValueAnimator animator, float qsExpansionHeight,
+ float target, float vel) {
+ mFlingAnimationUtils.apply(animator, qsExpansionHeight, target, vel);
+ }
+
+ private void onExpansionHeightSetToMax(boolean requestPaddingUpdate) {
+ if (requestPaddingUpdate) {
+ requestScrollerTopPaddingUpdate(false /* animate */);
+ }
+ updateExpandedHeightToMaxHeight();
+ }
+
private final class NsslHeightChangedListener implements
ExpandableView.OnHeightChangedListener {
@Override
public void onHeightChanged(ExpandableView view, boolean needsAnimation) {
// Block update if we are in QS and just the top padding changed (i.e. view == null).
- if (view == null && mQsExpanded) {
+ if (view == null && mQsController.getExpanded()) {
return;
}
if (needsAnimation && mInterpolatedDarkAmount == 0) {
@@ -5530,7 +4133,7 @@
== firstRow))) {
requestScrollerTopPaddingUpdate(false /* animate */);
}
- if (mKeyguardShowing) {
+ if (getKeyguardShowing()) {
updateMaxDisplayedNotifications(true);
}
updateExpandedHeightToMaxHeight();
@@ -5540,62 +4143,6 @@
public void onReset(ExpandableView view) {}
}
- private void collapseOrExpand() {
- onQsExpansionStarted();
- if (mQsExpanded) {
- flingSettings(0 /* vel */, FLING_COLLAPSE, null /* onFinishRunnable */,
- true /* isClick */);
- } else if (isQsExpansionEnabled()) {
- mLockscreenGestureLogger.write(MetricsEvent.ACTION_SHADE_QS_TAP, 0, 0);
- flingSettings(0 /* vel */, FLING_EXPAND, null /* onFinishRunnable */,
- true /* isClick */);
- }
- }
-
- private final class NsslOverscrollTopChangedListener implements
- NotificationStackScrollLayout.OnOverscrollTopChangedListener {
- @Override
- public void onOverscrollTopChanged(float amount, boolean isRubberbanded) {
- // When in split shade, overscroll shouldn't carry through to QS
- if (mSplitShadeEnabled) {
- return;
- }
- cancelQsAnimation();
- if (!isQsExpansionEnabled()) {
- amount = 0f;
- }
- float rounded = amount >= 1f ? amount : 0f;
- setOverScrolling(rounded != 0f && isRubberbanded);
- mQsExpansionFromOverscroll = rounded != 0f;
- mLastOverscroll = rounded;
- updateQsState();
- setQsExpansionHeight(mQsMinExpansionHeight + rounded);
- }
-
- @Override
- public void flingTopOverscroll(float velocity, boolean open) {
- // in split shade touches affect QS only when touch happens within QS
- if (isSplitShadeAndTouchXOutsideQs(mInitialTouchX)) {
- return;
- }
- mLastOverscroll = 0f;
- mQsExpansionFromOverscroll = false;
- if (open) {
- // During overscrolling, qsExpansion doesn't actually change that the qs is
- // becoming expanded. Any layout could therefore reset the position again. Let's
- // make sure we can expand
- setOverScrolling(false);
- }
- setQsExpansionHeight(mQsExpansionHeight);
- boolean canExpand = isQsExpansionEnabled();
- flingSettings(!canExpand && open ? 0f : velocity,
- open && canExpand ? FLING_EXPAND : FLING_COLLAPSE, () -> {
- setOverScrolling(false);
- updateQsState();
- }, false /* isClick */);
- }
- }
-
private void onDynamicPrivacyChanged() {
// Do not request animation when pulsing or waking up, otherwise the clock will be out
// of sync with the notification panel.
@@ -5645,19 +4192,6 @@
}
}
- private void onQsHeightChanged() {
- mQsMaxExpansionHeight = mQs != null ? mQs.getDesiredHeight() : 0;
- if (mQsExpanded && mQsFullyExpanded) {
- mQsExpansionHeight = mQsMaxExpansionHeight;
- requestScrollerTopPaddingUpdate(false /* animate */);
- updateExpandedHeightToMaxHeight();
- }
- if (mAccessibilityManager.isEnabled()) {
- mView.setAccessibilityPaneTitle(determineAccessibilityPaneTitle());
- }
- mNotificationStackScrollLayoutController.setMaxTopPadding(mQsMaxExpansionHeight);
- }
-
private final class ConfigurationListener implements
ConfigurationController.ConfigurationListener {
@Override
@@ -5733,8 +4267,9 @@
setKeyguardBottomAreaVisibility(statusBarState, goingToFullShade);
+ // TODO: maybe add a listener for barstate
mBarState = statusBarState;
- mKeyguardShowing = keyguardShowing;
+ mQsController.setBarState(statusBarState);
boolean fromShadeToKeyguard = statusBarState == KEYGUARD
&& (oldState == SHADE || oldState == SHADE_LOCKED);
@@ -5742,7 +4277,7 @@
// user can go to keyguard from different shade states and closing animation
// may not fully run - we always want to make sure we close QS when that happens
// as we never need QS open in fresh keyguard state
- closeQs();
+ mQsController.closeQs();
}
if (oldState == KEYGUARD && (goingToFullShade
@@ -5758,7 +4293,7 @@
duration = StackStateAnimator.ANIMATION_DURATION_STANDARD;
}
mKeyguardStatusBarViewController.animateKeyguardStatusBarOut(startDelay, duration);
- updateQSMinHeight();
+ mQsController.updateMinHeight();
} else if (oldState == StatusBarState.SHADE_LOCKED
&& statusBarState == KEYGUARD) {
mKeyguardStatusBarViewController.animateKeyguardStatusBarIn();
@@ -5786,9 +4321,7 @@
keyguardShowing ? View.VISIBLE : View.INVISIBLE);
}
if (keyguardShowing && oldState != mBarState) {
- if (mQs != null) {
- mQs.hideImmediately();
- }
+ mQsController.hideQsImmediately();
}
}
mKeyguardStatusBarViewController.updateForHeadsUp();
@@ -5800,7 +4333,7 @@
// The update needs to happen after the headerSlide in above, otherwise the translation
// would reset
maybeAnimateBottomAreaAlpha();
- updateQsState();
+ mQsController.updateQsState();
}
@Override
@@ -5877,7 +4410,7 @@
@Override
public void onViewAttachedToWindow(View v) {
mFragmentService.getFragmentHostManager(mView)
- .addTagListener(QS.TAG, mQsFragmentListener);
+ .addTagListener(QS.TAG, mQsController.getQsFragmentListener());
mStatusBarStateController.addCallback(mStatusBarStateListener);
mStatusBarStateListener.onStateChanged(mStatusBarStateController.getState());
mConfigurationController.addCallback(mConfigurationListener);
@@ -5894,7 +4427,7 @@
public void onViewDetachedFromWindow(View v) {
mContentResolver.unregisterContentObserver(mSettingsChangeObserver);
mFragmentService.getFragmentHostManager(mView)
- .removeTagListener(QS.TAG, mQsFragmentListener);
+ .removeTagListener(QS.TAG, mQsController.getQsFragmentListener());
mStatusBarStateController.removeCallback(mStatusBarStateListener);
mConfigurationController.removeCallback(mConfigurationListener);
mFalsingManager.removeTapListener(mFalsingTapListener);
@@ -5919,28 +4452,9 @@
// Update Clock Pivot (used by anti-burnin transformations)
mKeyguardStatusViewController.updatePivot(mView.getWidth(), mView.getHeight());
- // Calculate quick setting heights.
- int oldMaxHeight = mQsMaxExpansionHeight;
- if (mQs != null) {
- updateQSMinHeight();
- mQsMaxExpansionHeight = mQs.getDesiredHeight();
- mNotificationStackScrollLayoutController.setMaxTopPadding(mQsMaxExpansionHeight);
- }
+ int oldMaxHeight = mQsController.updateHeightsOnShadeLayoutChange();
positionClockAndNotifications();
- if (mQsExpanded && mQsFullyExpanded) {
- mQsExpansionHeight = mQsMaxExpansionHeight;
- requestScrollerTopPaddingUpdate(false /* animate */);
- updateExpandedHeightToMaxHeight();
-
- // Size has changed, start an animation.
- if (mQsMaxExpansionHeight != oldMaxHeight) {
- startQsSizeChangeAnimation(oldMaxHeight, mQsMaxExpansionHeight);
- }
- } else if (!mQsExpanded && mQsExpansionAnimator == null) {
- setQsExpansionHeight(mQsMinExpansionHeight + mLastOverscroll);
- } else {
- mShadeLog.v("onLayoutChange: qs expansion not set");
- }
+ mQsController.handleShadeLayoutChanged(oldMaxHeight);
updateExpandedHeight(getExpandedHeight());
updateHeader();
@@ -5949,9 +4463,8 @@
// container the desired height so when closing the QS detail, it stays smaller after
// the size change animation is finished but the detail view is still being animated
// away (this animation takes longer than the size change animation).
- if (mQsSizeChangeAnimator == null && mQs != null) {
- mQs.setHeightOverride(mQs.getDesiredHeight());
- }
+ mQsController.setHeightOverrideToDesiredHeight();
+
updateMaxHeadsUpTranslation();
updateGestureExclusionRect();
if (mExpandAfterLayoutRunnable != null) {
@@ -5962,18 +4475,6 @@
}
}
- private void updateQSMinHeight() {
- float previousMin = mQsMinExpansionHeight;
- if (mKeyguardShowing || mSplitShadeEnabled) {
- mQsMinExpansionHeight = 0;
- } else {
- mQsMinExpansionHeight = mQs.getQsMinExpansionHeight();
- }
- if (mQsExpansionHeight == previousMin) {
- mQsExpansionHeight = mQsMinExpansionHeight;
- }
- }
-
@NonNull
private WindowInsets onApplyShadeWindowInsets(WindowInsets insets) {
// the same types of insets that are handled in NotificationShadeWindowView
@@ -5982,6 +4483,7 @@
mDisplayTopInset = combinedInsets.top;
mDisplayRightInset = combinedInsets.right;
mDisplayLeftInset = combinedInsets.left;
+ mQsController.setDisplayInsets(mDisplayRightInset, mDisplayLeftInset);
mNavigationBarBottomHeight = insets.getStableInsetBottom();
updateMaxHeadsUpTranslation();
@@ -5994,10 +4496,10 @@
}
private void onPanelStateChanged(@PanelState int state) {
- updateQSExpansionEnabledAmbient();
+ mQsController.updateExpansionEnabledAmbient();
if (state == STATE_OPEN && mCurrentPanelState != state) {
- setQsExpandImmediate(false);
+ mQsController.setExpandImmediate(false);
mView.sendAccessibilityEvent(AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED);
}
if (state == STATE_OPENING) {
@@ -6005,12 +4507,12 @@
// to locked will trigger this event and we're not actually in the process of opening
// the shade, lockscreen is just always expanded
if (mSplitShadeEnabled && !isOnKeyguard()) {
- setQsExpandImmediate(true);
+ mQsController.setExpandImmediate(true);
}
mOpenCloseListener.onOpenStarted();
}
if (state == STATE_CLOSED) {
- setQsExpandImmediate(false);
+ mQsController.setExpandImmediate(false);
// Close the status bar in the next frame so we can show the end of the
// animation.
mView.post(mMaybeHideExpandedRunnable);
@@ -6076,7 +4578,7 @@
/** @see ViewGroup#onInterceptTouchEvent(MotionEvent) */
public boolean onInterceptTouchEvent(MotionEvent event) {
mShadeLog.logMotionEvent(event, "NPVC onInterceptTouchEvent");
- if (mQs.disallowPanelTouches()) {
+ if (mQsController.disallowTouches()) {
mShadeLog.logMotionEvent(event,
"NPVC not intercepting touch, panel touches disallowed");
return false;
@@ -6098,14 +4600,14 @@
+ "HeadsUpTouchHelper");
return true;
}
- if (!shouldQuickSettingsIntercept(mDownX, mDownY, 0)
+ if (!mQsController.shouldQuickSettingsIntercept(mDownX, mDownY, 0)
&& mPulseExpansionHandler.onInterceptTouchEvent(event)) {
mShadeLog.v("NotificationPanelViewController MotionEvent intercepted: "
+ "PulseExpansionHandler");
return true;
}
- if (!isFullyCollapsed() && onQsIntercept(event)) {
+ if (!isFullyCollapsed() && mQsController.onIntercept(event)) {
debugLog("onQsIntercept true");
mShadeLog.v("NotificationPanelViewController MotionEvent intercepted: "
+ "QsIntercept");
@@ -6139,11 +4641,6 @@
switch (event.getActionMasked()) {
case MotionEvent.ACTION_DOWN:
- if (mTracking) {
- // TODO(b/247126247) fix underlying issue. Should be ACTION_POINTER_DOWN.
- mShadeLog.d("Don't intercept down event while already tracking");
- return false;
- }
mCentralSurfaces.userActivity();
mAnimatingOnDown = mHeightAnimator != null && !mIsSpringBackAnimation;
mMinExpandHeight = 0.0f;
@@ -6231,16 +4728,10 @@
"onTouch: duplicate down event detected... ignoring");
return true;
}
- if (mTracking) {
- // TODO(b/247126247) fix underlying issue. Should be ACTION_POINTER_DOWN.
- mShadeLog.d("Don't handle down event while already tracking");
- return true;
- }
mLastTouchDownTime = event.getDownTime();
}
-
- if (mQsFullyExpanded && mQs != null && mQs.disallowPanelTouches()) {
+ if (mQsController.isFullyExpandedAndTouchesDisallowed()) {
mShadeLog.logMotionEvent(event,
"onTouch: ignore touch, panel touches disallowed and qs fully expanded");
return false;
@@ -6271,7 +4762,7 @@
// If pulse is expanding already, let's give it the touch. There are situations
// where the panel starts expanding even though we're also pulsing
boolean pulseShouldGetTouch = (!mIsExpanding
- && !shouldQuickSettingsIntercept(mDownX, mDownY, 0))
+ && !mQsController.shouldQuickSettingsIntercept(mDownX, mDownY, 0))
|| mPulseExpansionHandler.isExpanding();
if (pulseShouldGetTouch && mPulseExpansionHandler.onTouchEvent(event)) {
// We're expanding all the other ones shouldn't get this anymore
@@ -6289,7 +4780,8 @@
}
boolean handled = mHeadsUpTouchHelper.onTouchEvent(event);
- if (!mHeadsUpTouchHelper.isTrackingHeadsUp() && handleQsTouch(event)) {
+ if (!mHeadsUpTouchHelper.isTrackingHeadsUp() && mQsController.handleTouch(
+ event, isFullyCollapsed(), isShadeOrQsHeightAnimationRunning())) {
mShadeLog.logMotionEvent(event, "onTouch: handleQsTouch handled event");
return true;
}
@@ -6444,7 +4936,9 @@
mTouchAboveFalsingThreshold = true;
mUpwardsWhenThresholdReached = isDirectionUpwards(x, y);
}
- if ((!mGestureWaitForTouchSlop || mTracking) && !isTrackingBlocked()) {
+ if ((!mGestureWaitForTouchSlop || mTracking)
+ && !(mBlockingExpansionForCurrentTouch
+ || mQsController.isTrackingBlocked())) {
// Count h==0 as part of swipe-up,
// otherwise {@link NotificationStackScrollLayout}
// wrongly enables stack height updates at the start of lockscreen swipe-up
@@ -6462,9 +4956,9 @@
// mHeightAnimator is null, there is no remaining frame, ends instrumenting.
if (mHeightAnimator == null) {
if (event.getActionMasked() == MotionEvent.ACTION_UP) {
- endJankMonitoring();
+ mQsController.endJankMonitoring();
} else {
- cancelJankMonitoring();
+ mQsController.cancelJankMonitoring();
}
}
break;
diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowView.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowView.java
index 1f0cbf9..74a61a3 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowView.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowView.java
@@ -16,7 +16,7 @@
package com.android.systemui.shade;
-import static android.os.Trace.TRACE_TAG_ALWAYS;
+import static android.os.Trace.TRACE_TAG_APP;
import static android.view.WindowInsets.Type.systemBars;
import static com.android.systemui.statusbar.phone.CentralSurfaces.DEBUG;
@@ -328,7 +328,7 @@
@Override
public void requestLayout() {
- Trace.instant(TRACE_TAG_ALWAYS, "NotificationShadeWindowView#requestLayout");
+ Trace.instant(TRACE_TAG_APP, "NotificationShadeWindowView#requestLayout");
super.requestLayout();
}
diff --git a/packages/SystemUI/src/com/android/systemui/shade/QsBatteryModeController.kt b/packages/SystemUI/src/com/android/systemui/shade/QsBatteryModeController.kt
new file mode 100644
index 0000000..3eec7fa0e
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/shade/QsBatteryModeController.kt
@@ -0,0 +1,70 @@
+package com.android.systemui.shade
+
+import android.content.Context
+import android.view.DisplayCutout
+import com.android.systemui.R
+import com.android.systemui.battery.BatteryMeterView
+import com.android.systemui.statusbar.phone.StatusBarContentInsetsProvider
+import javax.inject.Inject
+
+/**
+ * Controls [BatteryMeterView.BatteryPercentMode]. It takes into account cutout and qs-qqs
+ * transition fraction when determining the mode.
+ */
+class QsBatteryModeController
+@Inject
+constructor(
+ private val context: Context,
+ private val insetsProvider: StatusBarContentInsetsProvider,
+) {
+
+ private companion object {
+ // MotionLayout frames are in [0, 100]. Where 0 and 100 are reserved for start and end
+ // frames.
+ const val MOTION_LAYOUT_MAX_FRAME = 100
+ // We add a single buffer frame to ensure that battery view faded out completely when we are
+ // about to change it's state
+ const val BUFFER_FRAME_COUNT = 1
+ }
+
+ private var fadeInStartFraction: Float = 0f
+ private var fadeOutCompleteFraction: Float = 0f
+
+ init {
+ updateResources()
+ }
+
+ /**
+ * Returns an appropriate [BatteryMeterView.BatteryPercentMode] for the [qsExpandedFraction] and
+ * [cutout]. We don't show battery estimation in qqs header on the devices with center cutout.
+ * The result might be null when the battery icon is invisible during the qs-qqs transition
+ * animation.
+ */
+ @BatteryMeterView.BatteryPercentMode
+ fun getBatteryMode(cutout: DisplayCutout?, qsExpandedFraction: Float): Int? =
+ when {
+ qsExpandedFraction > fadeInStartFraction -> BatteryMeterView.MODE_ESTIMATE
+ qsExpandedFraction < fadeOutCompleteFraction ->
+ if (hasCenterCutout(cutout)) {
+ BatteryMeterView.MODE_ON
+ } else {
+ BatteryMeterView.MODE_ESTIMATE
+ }
+ else -> null
+ }
+
+ fun updateResources() {
+ fadeInStartFraction =
+ (context.resources.getInteger(R.integer.fade_in_start_frame) - BUFFER_FRAME_COUNT) /
+ MOTION_LAYOUT_MAX_FRAME.toFloat()
+ fadeOutCompleteFraction =
+ (context.resources.getInteger(R.integer.fade_out_complete_frame) + BUFFER_FRAME_COUNT) /
+ MOTION_LAYOUT_MAX_FRAME.toFloat()
+ }
+
+ private fun hasCenterCutout(cutout: DisplayCutout?): Boolean =
+ cutout?.let {
+ !insetsProvider.currentRotationHasCornerCutout() && !it.boundingRectTop.isEmpty
+ }
+ ?: false
+}
diff --git a/packages/SystemUI/src/com/android/systemui/shade/QuickSettingsController.java b/packages/SystemUI/src/com/android/systemui/shade/QuickSettingsController.java
new file mode 100644
index 0000000..c0ef4c1
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/shade/QuickSettingsController.java
@@ -0,0 +1,2050 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+
+package com.android.systemui.shade;
+
+import static com.android.internal.jank.InteractionJankMonitor.CUJ_NOTIFICATION_SHADE_QS_EXPAND_COLLAPSE;
+import static com.android.systemui.classifier.Classifier.QS_COLLAPSE;
+import static com.android.systemui.shade.NotificationPanelViewController.COUNTER_PANEL_OPEN_QS;
+import static com.android.systemui.shade.NotificationPanelViewController.FLING_COLLAPSE;
+import static com.android.systemui.shade.NotificationPanelViewController.FLING_EXPAND;
+import static com.android.systemui.shade.NotificationPanelViewController.FLING_HIDE;
+import static com.android.systemui.shade.NotificationPanelViewController.QS_PARALLAX_AMOUNT;
+import static com.android.systemui.statusbar.StatusBarState.KEYGUARD;
+
+import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
+import android.animation.ValueAnimator;
+import android.app.Fragment;
+import android.content.res.Resources;
+import android.graphics.Rect;
+import android.graphics.Region;
+import android.util.Log;
+import android.util.MathUtils;
+import android.view.MotionEvent;
+import android.view.VelocityTracker;
+import android.view.View;
+import android.view.ViewConfiguration;
+import android.view.ViewGroup;
+import android.view.accessibility.AccessibilityManager;
+import android.widget.FrameLayout;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.jank.InteractionJankMonitor;
+import com.android.internal.logging.MetricsLogger;
+import com.android.internal.logging.nano.MetricsProto;
+import com.android.internal.policy.ScreenDecorationsUtils;
+import com.android.internal.policy.SystemBarUtils;
+import com.android.keyguard.FaceAuthApiRequestReason;
+import com.android.keyguard.KeyguardUpdateMonitor;
+import com.android.systemui.R;
+import com.android.systemui.animation.Interpolators;
+import com.android.systemui.classifier.Classifier;
+import com.android.systemui.classifier.FalsingCollector;
+import com.android.systemui.flags.FeatureFlags;
+import com.android.systemui.flags.Flags;
+import com.android.systemui.fragments.FragmentHostManager;
+import com.android.systemui.media.controls.pipeline.MediaDataManager;
+import com.android.systemui.media.controls.ui.MediaHierarchyManager;
+import com.android.systemui.plugins.FalsingManager;
+import com.android.systemui.plugins.qs.QS;
+import com.android.systemui.screenrecord.RecordingController;
+import com.android.systemui.shade.transition.ShadeTransitionController;
+import com.android.systemui.statusbar.LockscreenShadeTransitionController;
+import com.android.systemui.statusbar.NotificationRemoteInputManager;
+import com.android.systemui.statusbar.NotificationShadeDepthController;
+import com.android.systemui.statusbar.PulseExpansionHandler;
+import com.android.systemui.statusbar.QsFrameTranslateController;
+import com.android.systemui.statusbar.StatusBarState;
+import com.android.systemui.statusbar.notification.stack.AmbientState;
+import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout;
+import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController;
+import com.android.systemui.statusbar.notification.stack.StackStateAnimator;
+import com.android.systemui.statusbar.phone.KeyguardBypassController;
+import com.android.systemui.statusbar.phone.KeyguardStatusBarView;
+import com.android.systemui.statusbar.phone.LockscreenGestureLogger;
+import com.android.systemui.statusbar.phone.ScrimController;
+import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager;
+import com.android.systemui.statusbar.phone.StatusBarTouchableRegionManager;
+import com.android.systemui.statusbar.phone.dagger.CentralSurfacesComponent;
+import com.android.systemui.statusbar.policy.KeyguardStateController;
+import com.android.systemui.util.LargeScreenUtils;
+
+import javax.inject.Inject;
+
+import dagger.Lazy;
+
+/** Handles QuickSettings touch handling, expansion and animation state
+ * TODO (b/264460656) make this dumpable
+ */
+@CentralSurfacesComponent.CentralSurfacesScope
+public class QuickSettingsController {
+ public static final String TAG = "QuickSettingsController";
+
+ private QS mQs;
+ private final Lazy<NotificationPanelViewController> mPanelViewControllerLazy;
+
+ private final NotificationPanelView mPanelView;
+ private final KeyguardStatusBarView mKeyguardStatusBar;
+ private final FrameLayout mQsFrame;
+
+ private final QsFrameTranslateController mQsFrameTranslateController;
+ private final ShadeTransitionController mShadeTransitionController;
+ private final PulseExpansionHandler mPulseExpansionHandler;
+ private final ShadeExpansionStateManager mShadeExpansionStateManager;
+ private final StatusBarKeyguardViewManager mStatusBarKeyguardViewManager;
+ private final NotificationStackScrollLayoutController mNotificationStackScrollLayoutController;
+ private final LockscreenShadeTransitionController mLockscreenShadeTransitionController;
+ private final NotificationShadeDepthController mDepthController;
+ private final LargeScreenShadeHeaderController mLargeScreenShadeHeaderController;
+ private final StatusBarTouchableRegionManager mStatusBarTouchableRegionManager;
+ private final KeyguardStateController mKeyguardStateController;
+ private final KeyguardBypassController mKeyguardBypassController;
+ private final NotificationRemoteInputManager mRemoteInputManager;
+ private VelocityTracker mQsVelocityTracker;
+ private final KeyguardUpdateMonitor mKeyguardUpdateMonitor;
+ private final ScrimController mScrimController;
+ private final MediaDataManager mMediaDataManager;
+ private final MediaHierarchyManager mMediaHierarchyManager;
+ private final AmbientState mAmbientState;
+ private final RecordingController mRecordingController;
+ private final FalsingCollector mFalsingCollector;
+ private final LockscreenGestureLogger mLockscreenGestureLogger;
+ private final ShadeLogger mShadeLog;
+ private final FeatureFlags mFeatureFlags;
+ private final InteractionJankMonitor mInteractionJankMonitor;
+ private final FalsingManager mFalsingManager;
+ private final AccessibilityManager mAccessibilityManager;
+ private final MetricsLogger mMetricsLogger;
+ private final Resources mResources;
+
+ /** Whether the notifications are displayed full width (no margins on the side). */
+ private boolean mIsFullWidth;
+ private int mTouchSlop;
+ private float mSlopMultiplier;
+ /** the current {@link StatusBarState} */
+ private int mBarState;
+ private int mStatusBarMinHeight;
+ private boolean mScrimEnabled = true;
+ private int mScrimCornerRadius;
+ private int mScreenCornerRadius;
+ private boolean mUseLargeScreenShadeHeader;
+ private int mLargeScreenShadeHeaderHeight;
+ private int mDisplayRightInset = 0; // in pixels
+ private int mDisplayLeftInset = 0; // in pixels
+ private boolean mSplitShadeEnabled;
+ /**
+ * The padding between the start of notifications and the qs boundary on the lockscreen.
+ * On lockscreen, notifications aren't inset this extra amount, but we still want the
+ * qs boundary to be padded.
+ */
+ private int mLockscreenNotificationPadding;
+ private int mSplitShadeNotificationsScrimMarginBottom;
+ private boolean mDozing;
+ private boolean mEnableClipping;
+ private int mFalsingThreshold;
+ /**
+ * Position of the qs bottom during the full shade transition. This is needed as the toppadding
+ * can change during state changes, which makes it much harder to do animations
+ */
+ private int mTransitionToFullShadePosition;
+ private boolean mCollapsedOnDown;
+ private float mShadeExpandedHeight = 0;
+ private boolean mLastShadeFlingWasExpanding;
+
+ private float mInitialHeightOnTouch;
+ private float mInitialTouchX;
+ private float mInitialTouchY;
+ /** whether current touch Y delta is above falsing threshold */
+ private boolean mTouchAboveFalsingThreshold;
+ /** whether we are tracking a touch on QS container */
+ private boolean mTracking;
+ /** pointerId of the pointer we're currently tracking */
+ private int mTrackingPointer;
+
+ /**
+ * Indicates that QS is in expanded state which can happen by:
+ * - single pane shade: expanding shade and then expanding QS
+ * - split shade: just expanding shade (QS are expanded automatically)
+ */
+ private boolean mExpanded;
+ /** Indicates QS is at its max height */
+ private boolean mFullyExpanded;
+ /**
+ * Determines if QS should be already expanded when expanding shade.
+ * Used for split shade, two finger gesture as well as accessibility shortcut to QS.
+ * It needs to be set when movement starts as it resets at the end of expansion/collapse.
+ */
+ private boolean mExpandImmediate;
+ private boolean mExpandedWhenExpandingStarted;
+ private boolean mAnimatingHiddenFromCollapsed;
+ private boolean mVisible;
+ private float mExpansionHeight;
+ private int mMinExpansionHeight;
+ private int mMaxExpansionHeight;
+ /** Expansion fraction of the notification shade */
+ private float mShadeExpandedFraction;
+ private int mPeekHeight;
+ private float mLastOverscroll;
+ private boolean mExpansionFromOverscroll;
+ private boolean mExpansionEnabledPolicy = true;
+ private boolean mExpansionEnabledAmbient = true;
+ private float mQuickQsHeaderHeight;
+ /**
+ * Determines if QS should be already expanded when expanding shade.
+ * Used for split shade, two finger gesture as well as accessibility shortcut to QS.
+ * It needs to be set when movement starts as it resets at the end of expansion/collapse.
+ */
+ private boolean mTwoFingerExpandPossible;
+ /** Whether the ongoing gesture might both trigger the expansion in both the view and QS. */
+ private boolean mConflictingExpansionGesture;
+ /**
+ * If we are in a panel collapsing motion, we reset scrollY of our scroll view but still
+ * need to take this into account in our panel height calculation.
+ */
+ private boolean mAnimatorExpand;
+
+ /**
+ * The amount of progress we are currently in if we're transitioning to the full shade.
+ * 0.0f means we're not transitioning yet, while 1 means we're all the way in the full
+ * shade. This value can also go beyond 1.1 when we're overshooting!
+ */
+ private float mTransitioningToFullShadeProgress;
+ /** Distance a full shade transition takes in order for qs to fully transition to the shade. */
+ private int mDistanceForFullShadeTransition;
+ private boolean mStackScrollerOverscrolling;
+ /** Indicates QS is animating - set by flingQs */
+ private boolean mAnimating;
+ /** Whether the current animator is resetting the qs translation. */
+ private boolean mIsTranslationResettingAnimator;
+ /** Whether the current animator is resetting the pulse expansion after a drag down. */
+ private boolean mIsPulseExpansionResettingAnimator;
+ /** The translation amount for QS for the full shade transition. */
+ private float mTranslationForFullShadeTransition;
+ /** Should we animate the next bounds update. */
+ private boolean mAnimateNextNotificationBounds;
+ /** The delay for the next bounds animation. */
+ private long mNotificationBoundsAnimationDelay;
+ /** The duration of the notification bounds animation. */
+ private long mNotificationBoundsAnimationDuration;
+
+ private final Region mInterceptRegion = new Region();
+ /** The end bounds of a clipping animation. */
+ private final Rect mClippingAnimationEndBounds = new Rect();
+ private final Rect mLastClipBounds = new Rect();
+
+ /** The animator for the qs clipping bounds. */
+ private ValueAnimator mClippingAnimator = null;
+ /** The main animator for QS expansion */
+ private ValueAnimator mExpansionAnimator;
+ /** The animator for QS size change */
+ private ValueAnimator mSizeChangeAnimator;
+
+ private ExpansionHeightListener mExpansionHeightListener;
+ private QsStateUpdateListener mQsStateUpdateListener;
+ private ApplyClippingImmediatelyListener mApplyClippingImmediatelyListener;
+ private FlingQsWithoutClickListener mFlingQsWithoutClickListener;
+ private ExpansionHeightSetToMaxListener mExpansionHeightSetToMaxListener;
+ private final QS.HeightListener mQsHeightListener = this::onHeightChanged;
+ private final Runnable mQsCollapseExpandAction = this::collapseOrExpandQs;
+ private final QS.ScrollListener mQsScrollListener = this::onScroll;
+
+ @Inject
+ public QuickSettingsController(
+ Lazy<NotificationPanelViewController> panelViewControllerLazy,
+ NotificationPanelView panelView,
+ QsFrameTranslateController qsFrameTranslateController,
+ ShadeTransitionController shadeTransitionController,
+ PulseExpansionHandler pulseExpansionHandler,
+ NotificationRemoteInputManager remoteInputManager,
+ ShadeExpansionStateManager shadeExpansionStateManager,
+ StatusBarKeyguardViewManager statusBarKeyguardViewManager,
+ NotificationStackScrollLayoutController notificationStackScrollLayoutController,
+ LockscreenShadeTransitionController lockscreenShadeTransitionController,
+ NotificationShadeDepthController notificationShadeDepthController,
+ LargeScreenShadeHeaderController largeScreenShadeHeaderController,
+ StatusBarTouchableRegionManager statusBarTouchableRegionManager,
+ KeyguardStateController keyguardStateController,
+ KeyguardBypassController keyguardBypassController,
+ KeyguardUpdateMonitor keyguardUpdateMonitor,
+ ScrimController scrimController,
+ MediaDataManager mediaDataManager,
+ MediaHierarchyManager mediaHierarchyManager,
+ AmbientState ambientState,
+ RecordingController recordingController,
+ FalsingManager falsingManager,
+ FalsingCollector falsingCollector,
+ AccessibilityManager accessibilityManager,
+ LockscreenGestureLogger lockscreenGestureLogger,
+ MetricsLogger metricsLogger,
+ FeatureFlags featureFlags,
+ InteractionJankMonitor interactionJankMonitor,
+ ShadeLogger shadeLog
+ ) {
+ mPanelViewControllerLazy = panelViewControllerLazy;
+ mPanelView = panelView;
+ mQsFrame = mPanelView.findViewById(R.id.qs_frame);
+ mKeyguardStatusBar = mPanelView.findViewById(R.id.keyguard_header);
+ mResources = mPanelView.getResources();
+ mSplitShadeEnabled = LargeScreenUtils.shouldUseSplitNotificationShade(mResources);
+ mQsFrameTranslateController = qsFrameTranslateController;
+ mShadeTransitionController = shadeTransitionController;
+ mPulseExpansionHandler = pulseExpansionHandler;
+ pulseExpansionHandler.setPulseExpandAbortListener(() -> {
+ if (mQs != null) {
+ mQs.animateHeaderSlidingOut();
+ }
+ });
+ mRemoteInputManager = remoteInputManager;
+ mShadeExpansionStateManager = shadeExpansionStateManager;
+ mStatusBarKeyguardViewManager = statusBarKeyguardViewManager;
+ mNotificationStackScrollLayoutController = notificationStackScrollLayoutController;
+ mLockscreenShadeTransitionController = lockscreenShadeTransitionController;
+ mDepthController = notificationShadeDepthController;
+ mLargeScreenShadeHeaderController = largeScreenShadeHeaderController;
+ mStatusBarTouchableRegionManager = statusBarTouchableRegionManager;
+ mKeyguardStateController = keyguardStateController;
+ mKeyguardBypassController = keyguardBypassController;
+ mKeyguardUpdateMonitor = keyguardUpdateMonitor;
+ mScrimController = scrimController;
+ mMediaDataManager = mediaDataManager;
+ mMediaHierarchyManager = mediaHierarchyManager;
+ mAmbientState = ambientState;
+ mRecordingController = recordingController;
+ mFalsingManager = falsingManager;
+ mFalsingCollector = falsingCollector;
+ mAccessibilityManager = accessibilityManager;
+
+ mLockscreenGestureLogger = lockscreenGestureLogger;
+ mMetricsLogger = metricsLogger;
+ mShadeLog = shadeLog;
+ mFeatureFlags = featureFlags;
+ mInteractionJankMonitor = interactionJankMonitor;
+
+ mShadeExpansionStateManager.addExpansionListener(this::onPanelExpansionChanged);
+ mLockscreenShadeTransitionController.addCallback(new LockscreenShadeTransitionCallback());
+ }
+
+ @VisibleForTesting
+ void setQs(QS qs) {
+ mQs = qs;
+ }
+
+ public void setExpansionHeightListener(ExpansionHeightListener listener) {
+ mExpansionHeightListener = listener;
+ }
+
+ public void setQsStateUpdateListener(QsStateUpdateListener listener) {
+ mQsStateUpdateListener = listener;
+ }
+
+ public void setApplyClippingImmediatelyListener(ApplyClippingImmediatelyListener listener) {
+ mApplyClippingImmediatelyListener = listener;
+ }
+
+ public void setFlingQsWithoutClickListener(FlingQsWithoutClickListener listener) {
+ mFlingQsWithoutClickListener = listener;
+ }
+
+ public void setExpansionHeightSetToMaxListener(ExpansionHeightSetToMaxListener callback) {
+ mExpansionHeightSetToMaxListener = callback;
+ }
+
+ void loadDimens() {
+ final ViewConfiguration configuration = ViewConfiguration.get(this.mPanelView.getContext());
+ mTouchSlop = configuration.getScaledTouchSlop();
+ mSlopMultiplier = configuration.getScaledAmbiguousGestureMultiplier();
+ mPeekHeight = mResources.getDimensionPixelSize(R.dimen.qs_peek_height);
+ mStatusBarMinHeight = SystemBarUtils.getStatusBarHeight(mPanelView.getContext());
+ mScrimCornerRadius = mResources.getDimensionPixelSize(
+ R.dimen.notification_scrim_corner_radius);
+ mScreenCornerRadius = (int) ScreenDecorationsUtils.getWindowCornerRadius(
+ mPanelView.getContext());
+ mFalsingThreshold = mResources.getDimensionPixelSize(R.dimen.qs_falsing_threshold);
+ mLockscreenNotificationPadding = mResources.getDimensionPixelSize(
+ R.dimen.notification_side_paddings);
+ mDistanceForFullShadeTransition = mResources.getDimensionPixelSize(
+ R.dimen.lockscreen_shade_qs_transition_distance);
+ }
+
+ void updateResources() {
+ mSplitShadeEnabled = LargeScreenUtils.shouldUseSplitNotificationShade(mResources);
+ if (mQs != null) {
+ mQs.setInSplitShade(mSplitShadeEnabled);
+ }
+ mSplitShadeNotificationsScrimMarginBottom =
+ mResources.getDimensionPixelSize(
+ R.dimen.split_shade_notifications_scrim_margin_bottom);
+
+ mUseLargeScreenShadeHeader =
+ LargeScreenUtils.shouldUseLargeScreenShadeHeader(mPanelView.getResources());
+ mLargeScreenShadeHeaderHeight =
+ mResources.getDimensionPixelSize(R.dimen.large_screen_shade_header_height);
+ int topMargin = mUseLargeScreenShadeHeader ? mLargeScreenShadeHeaderHeight :
+ mResources.getDimensionPixelSize(R.dimen.notification_panel_margin_top);
+ mLargeScreenShadeHeaderController.setLargeScreenActive(mUseLargeScreenShadeHeader);
+ mAmbientState.setStackTopMargin(topMargin);
+
+ // TODO: When the flag is eventually removed, it means that we have a single view that is
+ // the same height in QQS and in Large Screen (large_screen_shade_header_height). Eventually
+ // the concept of largeScreenHeader or quickQsHeader will disappear outside of the class
+ // that controls the view as the offset needs to be the same regardless.
+ if (mUseLargeScreenShadeHeader || mFeatureFlags.isEnabled(Flags.COMBINED_QS_HEADERS)) {
+ mQuickQsHeaderHeight = mLargeScreenShadeHeaderHeight;
+ } else {
+ mQuickQsHeaderHeight = SystemBarUtils.getQuickQsOffsetHeight(mPanelView.getContext());
+ }
+
+ mEnableClipping = mResources.getBoolean(R.bool.qs_enable_clipping);
+ }
+
+ // TODO (b/265054088): move this and others to a CoreStartable
+ void initNotificationStackScrollLayoutController() {
+ mNotificationStackScrollLayoutController.setOverscrollTopChangedListener(
+ new NsslOverscrollTopChangedListener());
+ mNotificationStackScrollLayoutController.setOnStackYChanged(this::onStackYChanged);
+ mNotificationStackScrollLayoutController.setOnScrollListener(this::onNotificationScrolled);
+ }
+
+ private void onStackYChanged(boolean shouldAnimate) {
+ if (isQsFragmentCreated()) {
+ if (shouldAnimate) {
+ setAnimateNextNotificationBounds(StackStateAnimator.ANIMATION_DURATION_STANDARD,
+ 0 /* delay */);
+ }
+ setClippingBounds();
+ }
+ }
+
+ private void onNotificationScrolled(int newScrollPosition) {
+ updateExpansionEnabledAmbient();
+ }
+
+ int getHeaderHeight() {
+ return mQs.getHeader().getHeight();
+ }
+
+ /** Returns the padding of the stackscroller when unlocked */
+ int getUnlockedStackScrollerPadding() {
+ return (mQs != null ? mQs.getHeader().getHeight() : 0) + mPeekHeight;
+ }
+
+ public boolean isExpansionEnabled() {
+ return mExpansionEnabledPolicy && mExpansionEnabledAmbient
+ && !mRemoteInputManager.isRemoteInputActive();
+ }
+
+ public float getTransitioningToFullShadeProgress() {
+ return mTransitioningToFullShadeProgress;
+ }
+
+ /** */
+ @VisibleForTesting
+ boolean isExpandImmediate() {
+ return mExpandImmediate;
+ }
+
+ float getInitialTouchY() {
+ return mInitialTouchY;
+ }
+
+ /** Returns whether split shade is enabled and an x coordinate is outside of the QS frame. */
+ private boolean isSplitShadeAndTouchXOutsideQs(float touchX) {
+ return mSplitShadeEnabled && touchX < mQsFrame.getX()
+ || touchX > mQsFrame.getX() + mQsFrame.getWidth();
+ }
+
+ /** Returns whether touch is within QS area */
+ private boolean isTouchInQsArea(float x, float y) {
+ if (isSplitShadeAndTouchXOutsideQs(x)) {
+ return false;
+ }
+ // TODO (b/265193930): remove dependency on NPVC
+ // Let's reject anything at the very bottom around the home handle in gesture nav
+ if (mPanelViewControllerLazy.get().isInGestureNavHomeHandleArea(x, y)) {
+ return false;
+ }
+ return y <= mNotificationStackScrollLayoutController.getBottomMostNotificationBottom()
+ || y <= mQs.getView().getY() + mQs.getView().getHeight();
+ }
+
+ /** Returns whether or not event should open QS */
+ private boolean isOpenQsEvent(MotionEvent event) {
+ final int pointerCount = event.getPointerCount();
+ final int action = event.getActionMasked();
+
+ final boolean
+ twoFingerDrag =
+ action == MotionEvent.ACTION_POINTER_DOWN && pointerCount == 2;
+
+ final boolean
+ stylusButtonClickDrag =
+ action == MotionEvent.ACTION_DOWN && (event.isButtonPressed(
+ MotionEvent.BUTTON_STYLUS_PRIMARY) || event.isButtonPressed(
+ MotionEvent.BUTTON_STYLUS_SECONDARY));
+
+ final boolean
+ mouseButtonClickDrag =
+ action == MotionEvent.ACTION_DOWN && (event.isButtonPressed(
+ MotionEvent.BUTTON_SECONDARY) || event.isButtonPressed(
+ MotionEvent.BUTTON_TERTIARY));
+
+ return twoFingerDrag || stylusButtonClickDrag || mouseButtonClickDrag;
+ }
+
+
+ public boolean getExpanded() {
+ return mExpanded;
+ }
+
+ @VisibleForTesting
+ boolean isTracking() {
+ return mTracking;
+ }
+
+ public boolean getFullyExpanded() {
+ return mFullyExpanded;
+ }
+
+ boolean isGoingBetweenClosedShadeAndExpandedQs() {
+ // Below is true when QS are expanded and we swipe up from the same bottom of panel to
+ // close the whole shade with one motion. Also this will be always true when closing
+ // split shade as there QS are always expanded so every collapsing motion is motion from
+ // expanded QS to closed panel
+ return mExpandImmediate || (mExpanded
+ && !mTracking && !isExpansionAnimating()
+ && !mExpansionFromOverscroll);
+ }
+
+ private boolean isQsFragmentCreated() {
+ return mQs != null;
+ }
+
+ public boolean isCustomizing() {
+ return isQsFragmentCreated() && mQs.isCustomizing();
+ }
+
+ public float getExpansionHeight() {
+ return mExpansionHeight;
+ }
+
+ public boolean getExpandedWhenExpandingStarted() {
+ return mExpandedWhenExpandingStarted;
+ }
+
+ public int getMinExpansionHeight() {
+ return mMinExpansionHeight;
+ }
+
+ public boolean isFullyExpandedAndTouchesDisallowed() {
+ return isQsFragmentCreated() && getFullyExpanded() && disallowTouches();
+ }
+
+ public int getMaxExpansionHeight() {
+ return mMaxExpansionHeight;
+ }
+
+ private boolean isQsFalseTouch() {
+ if (mFalsingManager.isClassifierEnabled()) {
+ return mFalsingManager.isFalseTouch(Classifier.QUICK_SETTINGS);
+ }
+ return !mTouchAboveFalsingThreshold;
+ }
+
+ public int getFalsingThreshold() {
+ return mFalsingThreshold;
+ }
+
+ /**
+ * Returns Whether we should intercept a gesture to open Quick Settings.
+ */
+ public boolean shouldQuickSettingsIntercept(float x, float y, float yDiff) {
+ boolean keyguardShowing = mBarState == KEYGUARD;
+ if (!isExpansionEnabled() || mCollapsedOnDown || (keyguardShowing
+ && mKeyguardBypassController.getBypassEnabled()) || mSplitShadeEnabled) {
+ return false;
+ }
+ View header = keyguardShowing || mQs == null ? mKeyguardStatusBar : mQs.getHeader();
+ int frameTop = keyguardShowing
+ || mQs == null ? 0 : mQsFrame.getTop();
+ mInterceptRegion.set(
+ /* left= */ (int) mQsFrame.getX(),
+ /* top= */ header.getTop() + frameTop,
+ /* right= */ (int) mQsFrame.getX() + mQsFrame.getWidth(),
+ /* bottom= */ header.getBottom() + frameTop);
+ // Also allow QS to intercept if the touch is near the notch.
+ mStatusBarTouchableRegionManager.updateRegionForNotch(mInterceptRegion);
+ final boolean onHeader = mInterceptRegion.contains((int) x, (int) y);
+
+ if (getExpanded()) {
+ return onHeader || (yDiff < 0 && isTouchInQsArea(x, y));
+ } else {
+ return onHeader;
+ }
+ }
+
+ /** Returns amount header should be translated */
+ private float getHeaderTranslation() {
+ if (mSplitShadeEnabled) {
+ // in split shade QS don't translate, just (un)squish and overshoot
+ return 0;
+ }
+ if (mBarState == KEYGUARD && !mKeyguardBypassController.getBypassEnabled()) {
+ return -mQs.getQsMinExpansionHeight();
+ }
+ float appearAmount = mNotificationStackScrollLayoutController
+ .calculateAppearFraction(mShadeExpandedHeight);
+ float startHeight = -getExpansionHeight();
+ if (mBarState == StatusBarState.SHADE) {
+ // Small parallax as we pull down and clip QS
+ startHeight = -getExpansionHeight() * QS_PARALLAX_AMOUNT;
+ }
+ if (mKeyguardBypassController.getBypassEnabled() && mBarState == KEYGUARD) {
+ appearAmount = mNotificationStackScrollLayoutController.calculateAppearFractionBypass();
+ startHeight = -mQs.getQsMinExpansionHeight();
+ }
+ float translation = MathUtils.lerp(startHeight, 0, Math.min(1.0f, appearAmount));
+ return Math.min(0, translation);
+ }
+
+ /**
+ * Can the panel collapse in this motion because it was started on QQS?
+ *
+ * @param downX the x location where the touch started
+ * @param downY the y location where the touch started
+ * Returns true if the panel could be collapsed because it stared on QQS
+ */
+ public boolean canPanelCollapseOnQQS(float downX, float downY) {
+ if (mCollapsedOnDown || mBarState == KEYGUARD || getExpanded()) {
+ return false;
+ }
+ View header = mQs == null ? mKeyguardStatusBar : mQs.getHeader();
+ return downX >= mQsFrame.getX() && downX <= mQsFrame.getX() + mQsFrame.getWidth()
+ && downY <= header.getBottom();
+ }
+
+ /** Closes the Qs customizer. */
+ public void closeQsCustomizer() {
+ mQs.closeCustomizer();
+ }
+
+ /** Returns whether touches from the notification panel should be disallowed */
+ public boolean disallowTouches() {
+ return mQs.disallowPanelTouches();
+ }
+
+ void setListening(boolean listening) {
+ if (mQs != null) {
+ mQs.setListening(listening);
+ }
+ }
+
+ void hideQsImmediately() {
+ if (mQs != null) {
+ mQs.hideImmediately();
+ }
+ }
+
+ public void setDozing(boolean dozing) {
+ mDozing = dozing;
+ }
+
+ /** set QS state to closed */
+ public void closeQs() {
+ cancelExpansionAnimation();
+ setExpansionHeight(getMinExpansionHeight());
+ // qsExpandImmediate is a safety latch in case we're calling closeQS while we're in the
+ // middle of animation - we need to make sure that value is always false when shade if
+ // fully collapsed or expanded
+ setExpandImmediate(false);
+ }
+
+ @VisibleForTesting
+ void setExpanded(boolean expanded) {
+ boolean changed = mExpanded != expanded;
+ if (changed) {
+ mExpanded = expanded;
+ updateQsState();
+ mShadeExpansionStateManager.onQsExpansionChanged(expanded);
+ mShadeLog.logQsExpansionChanged("QS Expansion Changed.", expanded,
+ getMinExpansionHeight(), getMaxExpansionHeight(),
+ mStackScrollerOverscrolling, mAnimatorExpand, mAnimating);
+ }
+ }
+
+ void setLastShadeFlingWasExpanding(boolean expanding) {
+ mLastShadeFlingWasExpanding = expanding;
+ mShadeLog.logLastFlingWasExpanding(expanding);
+ }
+
+ /** update Qs height state */
+ public void setExpansionHeight(float height) {
+ int maxHeight = getMaxExpansionHeight();
+ height = Math.min(Math.max(
+ height, getMinExpansionHeight()), maxHeight);
+ mFullyExpanded = height == maxHeight && maxHeight != 0;
+ boolean qsAnimatingAway = !mAnimatorExpand && mAnimating;
+ if (height > getMinExpansionHeight() && !getExpanded()
+ && !mStackScrollerOverscrolling
+ && !mDozing && !qsAnimatingAway) {
+ setExpanded(true);
+ } else if (height <= getMinExpansionHeight()
+ && getExpanded()) {
+ setExpanded(false);
+ }
+ mExpansionHeight = height;
+ updateExpansion();
+
+ if (mExpansionHeightListener != null) {
+ mExpansionHeightListener.onQsSetExpansionHeightCalled(getFullyExpanded());
+ }
+ }
+
+ /** */
+ public void setHeightOverrideToDesiredHeight() {
+ if (isSizeChangeAnimationRunning() && isQsFragmentCreated()) {
+ mQs.setHeightOverride(mQs.getDesiredHeight());
+ }
+ }
+
+ /** Updates quick setting heights and returns old max height. */
+ int updateHeightsOnShadeLayoutChange() {
+ int oldMaxHeight = getMaxExpansionHeight();
+ if (isQsFragmentCreated()) {
+ updateMinHeight();
+ mMaxExpansionHeight = mQs.getDesiredHeight();
+ mNotificationStackScrollLayoutController.setMaxTopPadding(
+ getMaxExpansionHeight());
+ }
+ return oldMaxHeight;
+ }
+
+ /** Called when Shade view layout changed. Updates QS expansion or
+ * starts size change animation if height has changed. */
+ void handleShadeLayoutChanged(int oldMaxHeight) {
+ if (mExpanded && mFullyExpanded) {
+ mExpansionHeight = mMaxExpansionHeight;
+ if (mExpansionHeightSetToMaxListener != null) {
+ mExpansionHeightSetToMaxListener.onExpansionHeightSetToMax(true);
+ }
+
+ // Size has changed, start an animation.
+ if (getMaxExpansionHeight() != oldMaxHeight) {
+ startSizeChangeAnimation(oldMaxHeight,
+ getMaxExpansionHeight());
+ }
+ } else if (!getExpanded()
+ && !isExpansionAnimating()) {
+ setExpansionHeight(getMinExpansionHeight() + mLastOverscroll);
+ } else {
+ mShadeLog.v("onLayoutChange: qs expansion not set");
+ }
+ }
+
+ private boolean isSizeChangeAnimationRunning() {
+ return mSizeChangeAnimator != null;
+ }
+
+ private void startSizeChangeAnimation(int oldHeight, final int newHeight) {
+ if (mSizeChangeAnimator != null) {
+ oldHeight = (int) mSizeChangeAnimator.getAnimatedValue();
+ mSizeChangeAnimator.cancel();
+ }
+ mSizeChangeAnimator = ValueAnimator.ofInt(oldHeight, newHeight);
+ mSizeChangeAnimator.setDuration(300);
+ mSizeChangeAnimator.setInterpolator(Interpolators.FAST_OUT_SLOW_IN);
+ mSizeChangeAnimator.addUpdateListener(animation -> {
+ if (mExpansionHeightSetToMaxListener != null) {
+ mExpansionHeightSetToMaxListener.onExpansionHeightSetToMax(true);
+ }
+
+ int height = (int) mSizeChangeAnimator.getAnimatedValue();
+ mQs.setHeightOverride(height);
+ });
+ mSizeChangeAnimator.addListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ mSizeChangeAnimator = null;
+ }
+ });
+ mSizeChangeAnimator.start();
+ }
+
+ void setNotificationPanelFullWidth(boolean isFullWidth) {
+ mIsFullWidth = isFullWidth;
+ if (mQs != null) {
+ mQs.setIsNotificationPanelFullWidth(isFullWidth);
+ }
+ }
+
+ void setBarState(int barState) {
+ mBarState = barState;
+ }
+
+ /** */
+ public void setExpansionEnabledPolicy(boolean expansionEnabledPolicy) {
+ mExpansionEnabledPolicy = expansionEnabledPolicy;
+ if (mQs != null) {
+ mQs.setHeaderClickable(isExpansionEnabled());
+ }
+ }
+
+ private void setOverScrolling(boolean overscrolling) {
+ mStackScrollerOverscrolling = overscrolling;
+ if (mQs != null) {
+ mQs.setOverscrolling(overscrolling);
+ }
+ }
+
+ /** Sets Qs ScrimEnabled and updates QS state. */
+ public void setScrimEnabled(boolean scrimEnabled) {
+ boolean changed = mScrimEnabled != scrimEnabled;
+ mScrimEnabled = scrimEnabled;
+ if (changed) {
+ updateQsState();
+ }
+ }
+
+ void setCollapsedOnDown(boolean collapsedOnDown) {
+ mCollapsedOnDown = collapsedOnDown;
+ }
+
+ void setShadeExpandedHeight(float shadeExpandedHeight) {
+ mShadeExpandedHeight = shadeExpandedHeight;
+ }
+
+ @VisibleForTesting
+ float getShadeExpandedHeight() {
+ return mShadeExpandedHeight;
+ }
+
+ @VisibleForTesting
+ void setExpandImmediate(boolean expandImmediate) {
+ if (expandImmediate != mExpandImmediate) {
+ mExpandImmediate = expandImmediate;
+ mShadeExpansionStateManager.notifyExpandImmediateChange(expandImmediate);
+ }
+ }
+
+ void setTwoFingerExpandPossible(boolean expandPossible) {
+ mTwoFingerExpandPossible = expandPossible;
+ }
+
+ /** Called when Qs starts expanding */
+ private void onExpansionStarted() {
+ cancelExpansionAnimation();
+ // TODO (b/265193930): remove dependency on NPVC
+ mPanelViewControllerLazy.get().cancelHeightAnimator();
+ // end
+
+ // Reset scroll position and apply that position to the expanded height.
+ float height = mExpansionHeight;
+ setExpansionHeight(height);
+ mNotificationStackScrollLayoutController.checkSnoozeLeavebehind();
+
+ // When expanding QS, let's authenticate the user if possible,
+ // this will speed up notification actions.
+ if (height == 0 && !mKeyguardStateController.canDismissLockScreen()) {
+ mKeyguardUpdateMonitor.requestFaceAuth(FaceAuthApiRequestReason.QS_EXPANDED);
+ }
+ }
+
+ void updateQsState() {
+ boolean qsFullScreen = mExpanded && !mSplitShadeEnabled;
+ mNotificationStackScrollLayoutController.setQsFullScreen(qsFullScreen);
+ mNotificationStackScrollLayoutController.setScrollingEnabled(
+ mBarState != KEYGUARD && (!qsFullScreen || mExpansionFromOverscroll));
+
+ if (mQsStateUpdateListener != null) {
+ mQsStateUpdateListener.onQsStateUpdated(mExpanded, mStackScrollerOverscrolling);
+ }
+
+ if (mQs == null) return;
+ mQs.setExpanded(mExpanded);
+ }
+
+ /** update expanded state of QS */
+ public void updateExpansion() {
+ if (mQs == null) return;
+ final float squishiness;
+ if ((mExpandImmediate || mExpanded) && !mSplitShadeEnabled) {
+ squishiness = 1;
+ } else if (mTransitioningToFullShadeProgress > 0.0f) {
+ squishiness = mLockscreenShadeTransitionController.getQsSquishTransitionFraction();
+ } else {
+ squishiness = mNotificationStackScrollLayoutController
+ .getNotificationSquishinessFraction();
+ }
+ final float qsExpansionFraction = computeExpansionFraction();
+ final float adjustedExpansionFraction = mSplitShadeEnabled
+ ? 1f : computeExpansionFraction();
+ mQs.setQsExpansion(
+ adjustedExpansionFraction,
+ mShadeExpandedFraction,
+ getHeaderTranslation(),
+ squishiness
+ );
+ mMediaHierarchyManager.setQsExpansion(qsExpansionFraction);
+ int qsPanelBottomY = calculateBottomPosition(qsExpansionFraction);
+ mScrimController.setQsPosition(qsExpansionFraction, qsPanelBottomY);
+ setClippingBounds();
+
+ if (mSplitShadeEnabled) {
+ // In split shade we want to pretend that QS are always collapsed so their behaviour and
+ // interactions don't influence notifications as they do in portrait. But we want to set
+ // 0 explicitly in case we're rotating from non-split shade with QS expansion of 1.
+ mNotificationStackScrollLayoutController.setQsExpansionFraction(0);
+ } else {
+ mNotificationStackScrollLayoutController.setQsExpansionFraction(qsExpansionFraction);
+ }
+
+ mDepthController.setQsPanelExpansion(qsExpansionFraction);
+ mStatusBarKeyguardViewManager.setQsExpansion(qsExpansionFraction);
+
+ // TODO (b/265193930): remove dependency on NPVC
+ float shadeExpandedFraction = mBarState == KEYGUARD
+ ? mPanelViewControllerLazy.get().getLockscreenShadeDragProgress()
+ : mShadeExpandedFraction;
+ mLargeScreenShadeHeaderController.setShadeExpandedFraction(shadeExpandedFraction);
+ mLargeScreenShadeHeaderController.setQsExpandedFraction(qsExpansionFraction);
+ mLargeScreenShadeHeaderController.setQsVisible(mVisible);
+ }
+
+ /** */
+ public void updateExpansionEnabledAmbient() {
+ final float scrollRangeToTop = mAmbientState.getTopPadding() - mQuickQsHeaderHeight;
+ mExpansionEnabledAmbient = mSplitShadeEnabled
+ || (mAmbientState.getScrollY() <= scrollRangeToTop);
+ if (mQs != null) {
+ mQs.setHeaderClickable(isExpansionEnabled());
+ }
+ }
+
+ /** Calculate y value of bottom of QS */
+ private int calculateBottomPosition(float qsExpansionFraction) {
+ if (mTransitioningToFullShadeProgress > 0.0f) {
+ return mTransitionToFullShadePosition;
+ } else {
+ int qsBottomYFrom = (int) getHeaderTranslation() + mQs.getQsMinExpansionHeight();
+ int expandedTopMargin = mUseLargeScreenShadeHeader ? mLargeScreenShadeHeaderHeight : 0;
+ int qsBottomYTo = mQs.getDesiredHeight() + expandedTopMargin;
+ return (int) MathUtils.lerp(qsBottomYFrom, qsBottomYTo, qsExpansionFraction);
+ }
+ }
+
+ /** Calculate fraction of current QS expansion state */
+ public float computeExpansionFraction() {
+ if (mAnimatingHiddenFromCollapsed) {
+ // When hiding QS from collapsed state, the expansion can sometimes temporarily
+ // be larger than 0 because of the timing, leading to flickers.
+ return 0.0f;
+ }
+ return Math.min(
+ 1f, (mExpansionHeight - mMinExpansionHeight) / (mMaxExpansionHeight
+ - mMinExpansionHeight));
+ }
+
+ void updateMinHeight() {
+ float previousMin = mMinExpansionHeight;
+ if (mBarState == KEYGUARD || mSplitShadeEnabled) {
+ mMinExpansionHeight = 0;
+ } else {
+ mMinExpansionHeight = mQs.getQsMinExpansionHeight();
+ }
+ if (mExpansionHeight == previousMin) {
+ mExpansionHeight = mMinExpansionHeight;
+ }
+ }
+
+ void updateQsFrameTranslation() {
+ // TODO (b/265193930): remove dependency on NPVC
+ mQsFrameTranslateController.translateQsFrame(mQsFrame, mQs,
+ mPanelViewControllerLazy.get().getNavigationBarBottomHeight()
+ + mAmbientState.getStackTopMargin());
+ }
+
+ /** Called when shade starts expanding. */
+ public void onExpandingStarted(boolean qsFullyExpanded) {
+ mNotificationStackScrollLayoutController.onExpansionStarted();
+ mExpandedWhenExpandingStarted = qsFullyExpanded;
+ mMediaHierarchyManager.setCollapsingShadeFromQS(mExpandedWhenExpandingStarted
+ /* We also start expanding when flinging closed Qs. Let's exclude that */
+ && !mAnimating);
+ if (mExpanded) {
+ onExpansionStarted();
+ }
+ // Since there are QS tiles in the header now, we need to make sure we start listening
+ // immediately so they can be up to date.
+ if (mQs == null) return;
+ mQs.setHeaderListening(true);
+ }
+
+ /** Set animate next notification bounds. */
+ private void setAnimateNextNotificationBounds(long duration, long delay) {
+ mAnimateNextNotificationBounds = true;
+ mNotificationBoundsAnimationDuration = duration;
+ mNotificationBoundsAnimationDelay = delay;
+ }
+
+ /**
+ * Updates scrim bounds, QS clipping, notifications clipping and keyguard status view clipping
+ * as well based on the bounds of the shade and QS state.
+ */
+ private void setClippingBounds() {
+ float qsExpansionFraction = computeExpansionFraction();
+ final int qsPanelBottomY = calculateBottomPosition(qsExpansionFraction);
+ final boolean qsVisible = (qsExpansionFraction > 0 || qsPanelBottomY > 0);
+ checkCorrectScrimVisibility(qsExpansionFraction);
+
+ int top = calculateTopClippingBound(qsPanelBottomY);
+ int bottom = calculateBottomClippingBound(top);
+ int left = calculateLeftClippingBound();
+ int right = calculateRightClippingBound();
+ // top should never be lower than bottom, otherwise it will be invisible.
+ top = Math.min(top, bottom);
+ applyClippingBounds(left, top, right, bottom, qsVisible);
+ }
+
+ /**
+ * Applies clipping to quick settings, notifications layout and
+ * updates bounds of the notifications background (notifications scrim).
+ *
+ * The parameters are bounds of the notifications area rectangle, this function
+ * calculates bounds for the QS clipping based on the notifications bounds.
+ */
+ private void applyClippingBounds(int left, int top, int right, int bottom,
+ boolean qsVisible) {
+ if (!mAnimateNextNotificationBounds || mLastClipBounds.isEmpty()) {
+ if (mClippingAnimator != null) {
+ // update the end position of the animator
+ mClippingAnimationEndBounds.set(left, top, right, bottom);
+ } else {
+ applyClippingImmediately(left, top, right, bottom, qsVisible);
+ }
+ } else {
+ mClippingAnimationEndBounds.set(left, top, right, bottom);
+ final int startLeft = mLastClipBounds.left;
+ final int startTop = mLastClipBounds.top;
+ final int startRight = mLastClipBounds.right;
+ final int startBottom = mLastClipBounds.bottom;
+ if (mClippingAnimator != null) {
+ mClippingAnimator.cancel();
+ }
+ mClippingAnimator = ValueAnimator.ofFloat(0.0f, 1.0f);
+ mClippingAnimator.setInterpolator(Interpolators.FAST_OUT_SLOW_IN);
+ mClippingAnimator.setDuration(mNotificationBoundsAnimationDuration);
+ mClippingAnimator.setStartDelay(mNotificationBoundsAnimationDelay);
+ mClippingAnimator.addUpdateListener(animation -> {
+ float fraction = animation.getAnimatedFraction();
+ int animLeft = (int) MathUtils.lerp(startLeft,
+ mClippingAnimationEndBounds.left, fraction);
+ int animTop = (int) MathUtils.lerp(startTop,
+ mClippingAnimationEndBounds.top, fraction);
+ int animRight = (int) MathUtils.lerp(startRight,
+ mClippingAnimationEndBounds.right, fraction);
+ int animBottom = (int) MathUtils.lerp(startBottom,
+ mClippingAnimationEndBounds.bottom, fraction);
+ applyClippingImmediately(animLeft, animTop, animRight, animBottom,
+ qsVisible /* qsVisible */);
+ });
+ mClippingAnimator.addListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ mClippingAnimator = null;
+ mIsTranslationResettingAnimator = false;
+ mIsPulseExpansionResettingAnimator = false;
+ }
+ });
+ mClippingAnimator.start();
+ }
+ mAnimateNextNotificationBounds = false;
+ mNotificationBoundsAnimationDelay = 0;
+ }
+
+ private void applyClippingImmediately(int left, int top, int right, int bottom,
+ boolean qsVisible) {
+ int radius = mScrimCornerRadius;
+ boolean clipStatusView = false;
+ mLastClipBounds.set(left, top, right, bottom);
+ if (mIsFullWidth) {
+ clipStatusView = qsVisible;
+ float screenCornerRadius = mRecordingController.isRecording() ? 0 : mScreenCornerRadius;
+ radius = (int) MathUtils.lerp(screenCornerRadius, mScrimCornerRadius,
+ Math.min(top / (float) mScrimCornerRadius, 1f));
+ }
+ if (isQsFragmentCreated()) {
+ float qsTranslation = 0;
+ boolean pulseExpanding = mPulseExpansionHandler.isExpanding();
+ if (mTransitioningToFullShadeProgress > 0.0f
+ || pulseExpanding || (mClippingAnimator != null
+ && (mIsTranslationResettingAnimator || mIsPulseExpansionResettingAnimator))) {
+ if (pulseExpanding || mIsPulseExpansionResettingAnimator) {
+ // qsTranslation should only be positive during pulse expansion because it's
+ // already translating in from the top
+ qsTranslation = Math.max(0, (top - getHeaderHeight()) / 2.0f);
+ } else if (!mSplitShadeEnabled) {
+ qsTranslation = (top - getHeaderHeight()) * QS_PARALLAX_AMOUNT;
+ }
+ }
+ mTranslationForFullShadeTransition = qsTranslation;
+ updateQsFrameTranslation();
+ float currentTranslation = mQsFrame.getTranslationY();
+ int clipTop = mEnableClipping
+ ? (int) (top - currentTranslation - mQsFrame.getTop()) : 0;
+ int clipBottom = mEnableClipping
+ ? (int) (bottom - currentTranslation - mQsFrame.getTop()) : 0;
+ mVisible = qsVisible;
+ mQs.setQsVisible(qsVisible);
+ mQs.setFancyClipping(
+ clipTop,
+ clipBottom,
+ radius,
+ qsVisible && !mSplitShadeEnabled);
+
+ }
+
+ // Increase the height of the notifications scrim when not in split shade
+ // (e.g. portrait tablet) so the rounded corners are not visible at the bottom,
+ // in this case they are rendered off-screen
+ final int notificationsScrimBottom = mSplitShadeEnabled ? bottom : bottom + radius;
+ mScrimController.setNotificationsBounds(left, top, right, notificationsScrimBottom);
+
+ if (mApplyClippingImmediatelyListener != null) {
+ mApplyClippingImmediatelyListener.onQsClippingImmediatelyApplied(clipStatusView,
+ mLastClipBounds, top, isQsFragmentCreated(), mVisible);
+ }
+
+ mScrimController.setScrimCornerRadius(radius);
+
+ // Convert global clipping coordinates to local ones,
+ // relative to NotificationStackScrollLayout
+ int nsslLeft = calculateNsslLeft(left);
+ int nsslRight = calculateNsslRight(right);
+ int nsslTop = getNotificationsClippingTopBounds(top);
+ int nsslBottom = bottom - mNotificationStackScrollLayoutController.getTop();
+ int bottomRadius = mSplitShadeEnabled ? radius : 0;
+ // TODO (b/265193930): remove dependency on NPVC
+ int topRadius = mSplitShadeEnabled
+ && mPanelViewControllerLazy.get().isExpandingFromHeadsUp() ? 0 : radius;
+ mNotificationStackScrollLayoutController.setRoundedClippingBounds(
+ nsslLeft, nsslTop, nsslRight, nsslBottom, topRadius, bottomRadius);
+ }
+
+ void setDisplayInsets(int leftInset, int rightInset) {
+ mDisplayLeftInset = leftInset;
+ mDisplayRightInset = rightInset;
+ }
+
+ private int calculateNsslLeft(int nsslLeftAbsolute) {
+ int left = nsslLeftAbsolute - mNotificationStackScrollLayoutController.getLeft();
+ if (mIsFullWidth) {
+ return left;
+ }
+ return left - mDisplayLeftInset;
+ }
+
+ private int calculateNsslRight(int nsslRightAbsolute) {
+ int right = nsslRightAbsolute - mNotificationStackScrollLayoutController.getLeft();
+ if (mIsFullWidth) {
+ return right;
+ }
+ return right - mDisplayLeftInset;
+ }
+
+ private int getNotificationsClippingTopBounds(int qsTop) {
+ // TODO (b/265193930): remove dependency on NPVC
+ if (mSplitShadeEnabled && mPanelViewControllerLazy.get().isExpandingFromHeadsUp()) {
+ // in split shade nssl has extra top margin so clipping at top 0 is not enough, we need
+ // to set top clipping bound to negative value to allow HUN to go up to the top edge of
+ // the screen without clipping.
+ return -mAmbientState.getStackTopMargin();
+ } else {
+ return qsTop - mNotificationStackScrollLayoutController.getTop();
+ }
+ }
+
+ private void checkCorrectScrimVisibility(float expansionFraction) {
+ // issues with scrims visible on keyguard occur only in split shade
+ if (mSplitShadeEnabled) {
+ // TODO (b/265193930): remove dependency on NPVC
+ boolean keyguardViewsVisible = mBarState == KEYGUARD
+ && mPanelViewControllerLazy.get().getKeyguardOnlyContentAlpha() == 1;
+ // expansionFraction == 1 means scrims are fully visible as their size/visibility depend
+ // on QS expansion
+ if (expansionFraction == 1 && keyguardViewsVisible) {
+ Log.wtf(TAG,
+ "Incorrect state, scrim is visible at the same time when clock is visible");
+ }
+ }
+ }
+
+ /** Calculate top padding for notifications */
+ public float calculateNotificationsTopPadding(boolean isShadeExpanding,
+ int keyguardNotificationStaticPadding, float expandedFraction) {
+ boolean keyguardShowing = mBarState == KEYGUARD;
+ if (mSplitShadeEnabled) {
+ return keyguardShowing
+ ? keyguardNotificationStaticPadding : 0;
+ }
+ if (keyguardShowing && (isExpandImmediate()
+ || isShadeExpanding && getExpandedWhenExpandingStarted())) {
+
+ // Either QS pushes the notifications down when fully expanded, or QS is fully above the
+ // notifications (mostly on tablets). maxNotificationPadding denotes the normal top
+ // padding on Keyguard, maxQsPadding denotes the top padding from the quick settings
+ // panel. We need to take the maximum and linearly interpolate with the panel expansion
+ // for a nice motion.
+ int maxQsPadding = getMaxExpansionHeight();
+ int max = keyguardShowing ? Math.max(
+ keyguardNotificationStaticPadding, maxQsPadding) : maxQsPadding;
+ return (int) MathUtils.lerp((float) getMinExpansionHeight(),
+ (float) max, expandedFraction);
+ } else if (isSizeChangeAnimationRunning()) {
+ return Math.max((int) mSizeChangeAnimator.getAnimatedValue(),
+ keyguardNotificationStaticPadding);
+ } else if (keyguardShowing) {
+ // We can only do the smoother transition on Keyguard when we also are not collapsing
+ // from a scrolled quick settings.
+ return MathUtils.lerp((float) keyguardNotificationStaticPadding,
+ (float) (getMaxExpansionHeight()), computeExpansionFraction());
+ } else {
+ return mQsFrameTranslateController.getNotificationsTopPadding(
+ mExpansionHeight, mNotificationStackScrollLayoutController);
+ }
+ }
+
+ /** Calculate height of QS panel */
+ public int calculatePanelHeightExpanded(int stackScrollerPadding) {
+ float
+ notificationHeight =
+ mNotificationStackScrollLayoutController.getHeight()
+ - mNotificationStackScrollLayoutController.getEmptyBottomMargin()
+ - mNotificationStackScrollLayoutController.getTopPadding();
+
+ // When only empty shade view is visible in QS collapsed state, simulate that we would have
+ // it in expanded QS state as well so we don't run into troubles when fading the view in/out
+ // and expanding/collapsing the whole panel from/to quick settings.
+ if (mNotificationStackScrollLayoutController.getNotGoneChildCount() == 0
+ && mNotificationStackScrollLayoutController.isShowingEmptyShadeView()) {
+ notificationHeight = mNotificationStackScrollLayoutController.getEmptyShadeViewHeight();
+ }
+ int maxQsHeight = mMaxExpansionHeight;
+
+ // If an animation is changing the size of the QS panel, take the animated value.
+ if (mSizeChangeAnimator != null) {
+ maxQsHeight = (int) mSizeChangeAnimator.getAnimatedValue();
+ }
+ float totalHeight = Math.max(maxQsHeight, mBarState == KEYGUARD ? stackScrollerPadding : 0)
+ + notificationHeight
+ + mNotificationStackScrollLayoutController.getTopPaddingOverflow();
+ if (totalHeight > mNotificationStackScrollLayoutController.getHeight()) {
+ float
+ fullyCollapsedHeight =
+ maxQsHeight + mNotificationStackScrollLayoutController.getLayoutMinHeight();
+ totalHeight = Math.max(fullyCollapsedHeight,
+ mNotificationStackScrollLayoutController.getHeight());
+ }
+ return (int) totalHeight;
+ }
+
+ private float getEdgePosition() {
+ // TODO: replace StackY with unified calculation
+ return Math.max(mQuickQsHeaderHeight * mAmbientState.getExpansionFraction(),
+ mAmbientState.getStackY()
+ // need to adjust for extra margin introduced by large screen shade header
+ + mAmbientState.getStackTopMargin() * mAmbientState.getExpansionFraction()
+ - mAmbientState.getScrollY());
+ }
+
+ private int calculateTopClippingBound(int qsPanelBottomY) {
+ int top;
+ if (mSplitShadeEnabled) {
+ top = Math.min(qsPanelBottomY, mLargeScreenShadeHeaderHeight);
+ } else {
+ if (mTransitioningToFullShadeProgress > 0.0f) {
+ // If we're transitioning, let's use the actual value. The else case
+ // can be wrong during transitions when waiting for the keyguard to unlock
+ top = mTransitionToFullShadePosition;
+ } else {
+ final float notificationTop = getEdgePosition();
+ if (mBarState == KEYGUARD) {
+ if (mKeyguardBypassController.getBypassEnabled()) {
+ // When bypassing on the keyguard, let's use the panel bottom.
+ // this should go away once we unify the stackY position and don't have
+ // to do this min anymore below.
+ top = qsPanelBottomY;
+ } else {
+ top = (int) Math.min(qsPanelBottomY, notificationTop);
+ }
+ } else {
+ top = (int) notificationTop;
+ }
+ }
+ // TODO (b/265193930): remove dependency on NPVC
+ top += mPanelViewControllerLazy.get().getOverStretchAmount();
+ // Correction for instant expansion caused by HUN pull down/
+ float minFraction = mPanelViewControllerLazy.get().getMinFraction();
+ if (minFraction > 0f && minFraction < 1f) {
+ float realFraction = (mShadeExpandedFraction
+ - minFraction) / (1f - minFraction);
+ top *= MathUtils.saturate(realFraction / minFraction);
+ }
+ }
+ return top;
+ }
+
+ private int calculateBottomClippingBound(int top) {
+ if (mSplitShadeEnabled) {
+ return top + mNotificationStackScrollLayoutController.getHeight()
+ + mSplitShadeNotificationsScrimMarginBottom;
+ } else {
+ return mPanelView.getBottom();
+ }
+ }
+
+ private int calculateLeftClippingBound() {
+ if (mIsFullWidth) {
+ // left bounds can ignore insets, it should always reach the edge of the screen
+ return 0;
+ } else {
+ return mNotificationStackScrollLayoutController.getLeft()
+ + mDisplayLeftInset;
+ }
+ }
+
+ private int calculateRightClippingBound() {
+ if (mIsFullWidth) {
+ return mPanelView.getRight()
+ + mDisplayRightInset;
+ } else {
+ return mNotificationStackScrollLayoutController.getRight()
+ + mDisplayLeftInset;
+ }
+ }
+
+ private void trackMovement(MotionEvent event) {
+ if (mQsVelocityTracker != null) mQsVelocityTracker.addMovement(event);
+ }
+
+ private void initVelocityTracker() {
+ if (mQsVelocityTracker != null) {
+ mQsVelocityTracker.recycle();
+ }
+ mQsVelocityTracker = VelocityTracker.obtain();
+ }
+
+ private float getCurrentVelocity() {
+ if (mQsVelocityTracker == null) {
+ return 0;
+ }
+ mQsVelocityTracker.computeCurrentVelocity(1000);
+ return mQsVelocityTracker.getYVelocity();
+ }
+
+ boolean updateAndGetTouchAboveFalsingThreshold() {
+ mTouchAboveFalsingThreshold = mFullyExpanded;
+ return mTouchAboveFalsingThreshold;
+ }
+
+ private void onHeightChanged() {
+ mMaxExpansionHeight = isQsFragmentCreated() ? mQs.getDesiredHeight() : 0;
+ if (mExpanded && mFullyExpanded) {
+ mExpansionHeight = mMaxExpansionHeight;
+ if (mExpansionHeightSetToMaxListener != null) {
+ mExpansionHeightSetToMaxListener.onExpansionHeightSetToMax(true);
+ }
+ }
+ if (mAccessibilityManager.isEnabled()) {
+ // TODO (b/265193930): remove dependency on NPVC
+ mPanelView.setAccessibilityPaneTitle(
+ mPanelViewControllerLazy.get().determineAccessibilityPaneTitle());
+ }
+ mNotificationStackScrollLayoutController.setMaxTopPadding(mMaxExpansionHeight);
+ }
+
+ private void collapseOrExpandQs() {
+ onExpansionStarted();
+ if (getExpanded()) {
+ flingQs(0, FLING_COLLAPSE, null, true);
+ } else if (isExpansionEnabled()) {
+ mLockscreenGestureLogger.write(MetricsProto.MetricsEvent.ACTION_SHADE_QS_TAP, 0, 0);
+ flingQs(0, FLING_EXPAND, null, true);
+ }
+ }
+
+ private void onScroll(int scrollY) {
+ mLargeScreenShadeHeaderController.setQsScrollY(scrollY);
+ if (scrollY > 0 && !mFullyExpanded) {
+ // TODO (b/265193930): remove dependency on NPVC
+ // If we are scrolling QS, we should be fully expanded.
+ mPanelViewControllerLazy.get().expandWithQs();
+ }
+ }
+
+ @VisibleForTesting
+ boolean isTrackingBlocked() {
+ return mConflictingExpansionGesture && getExpanded();
+ }
+
+ boolean isExpansionAnimating() {
+ return mExpansionAnimator != null;
+ }
+
+ @VisibleForTesting
+ boolean isConflictingExpansionGesture() {
+ return mConflictingExpansionGesture;
+ }
+
+ /** handles touches in Qs panel area */
+ public boolean handleTouch(MotionEvent event, boolean isFullyCollapsed,
+ boolean isShadeOrQsHeightAnimationRunning) {
+ if (isSplitShadeAndTouchXOutsideQs(event.getX())) {
+ return false;
+ }
+ final int action = event.getActionMasked();
+ boolean collapsedQs = !getExpanded() && !mSplitShadeEnabled;
+ boolean expandedShadeCollapsedQs = mShadeExpandedFraction == 1f
+ && mBarState != KEYGUARD && collapsedQs && isExpansionEnabled();
+ if (action == MotionEvent.ACTION_DOWN && expandedShadeCollapsedQs) {
+ // Down in the empty area while fully expanded - go to QS.
+ mShadeLog.logMotionEvent(event, "handleQsTouch: down action, QS tracking enabled");
+ mTracking = true;
+ traceQsJank(true, false);
+ mConflictingExpansionGesture = true;
+ onExpansionStarted();
+ mInitialHeightOnTouch = mExpansionHeight;
+ mInitialTouchY = event.getY();
+ mInitialTouchX = event.getX();
+ }
+ if (!isFullyCollapsed && !isShadeOrQsHeightAnimationRunning) {
+ handleDown(event);
+ }
+ // defer touches on QQS to shade while shade is collapsing. Added margin for error
+ // as sometimes the qsExpansionFraction can be a tiny value instead of 0 when in QQS.
+ if (!mSplitShadeEnabled && !mLastShadeFlingWasExpanding
+ && computeExpansionFraction() <= 0.01 && mShadeExpandedFraction < 1.0) {
+ mShadeLog.logMotionEvent(event,
+ "handleQsTouch: shade touched while shade collapsing, QS tracking disabled");
+ mTracking = false;
+ }
+ if (!isExpandImmediate() && mTracking) {
+ onTouch(event);
+ if (!mConflictingExpansionGesture && !mSplitShadeEnabled) {
+ mShadeLog.logMotionEvent(event,
+ "handleQsTouch: not immediate expand or conflicting gesture");
+ return true;
+ }
+ }
+ if (action == MotionEvent.ACTION_CANCEL || action == MotionEvent.ACTION_UP) {
+ mConflictingExpansionGesture = false;
+ }
+ if (action == MotionEvent.ACTION_DOWN && isFullyCollapsed && isExpansionEnabled()) {
+ mTwoFingerExpandPossible = true;
+ }
+ if (mTwoFingerExpandPossible && isOpenQsEvent(event)
+ && event.getY(event.getActionIndex())
+ < mStatusBarMinHeight) {
+ mMetricsLogger.count(COUNTER_PANEL_OPEN_QS, 1);
+ setExpandImmediate(true);
+ mNotificationStackScrollLayoutController.setShouldShowShelfOnly(!mSplitShadeEnabled);
+ if (mExpansionHeightSetToMaxListener != null) {
+ mExpansionHeightSetToMaxListener.onExpansionHeightSetToMax(false);
+ }
+
+ // Normally, we start listening when the panel is expanded, but here we need to start
+ // earlier so the state is already up to date when dragging down.
+ setListening(true);
+ }
+ return false;
+ }
+
+ private void handleDown(MotionEvent event) {
+ if (event.getActionMasked() == MotionEvent.ACTION_DOWN
+ && shouldQuickSettingsIntercept(event.getX(), event.getY(), -1)) {
+ mFalsingCollector.onQsDown();
+ mShadeLog.logMotionEvent(event, "handleQsDown: down action, QS tracking enabled");
+ mTracking = true;
+ onExpansionStarted();
+ mInitialHeightOnTouch = mExpansionHeight;
+ mInitialTouchY = event.getY();
+ mInitialTouchX = event.getX();
+ // TODO (b/265193930): remove dependency on NPVC
+ // If we interrupt an expansion gesture here, make sure to update the state correctly.
+ mPanelViewControllerLazy.get().notifyExpandingFinished();
+ }
+ }
+
+ private void onTouch(MotionEvent event) {
+ int pointerIndex = event.findPointerIndex(mTrackingPointer);
+ if (pointerIndex < 0) {
+ pointerIndex = 0;
+ mTrackingPointer = event.getPointerId(pointerIndex);
+ }
+ final float y = event.getY(pointerIndex);
+ final float x = event.getX(pointerIndex);
+ final float h = y - mInitialTouchY;
+
+ switch (event.getActionMasked()) {
+ case MotionEvent.ACTION_DOWN:
+ mShadeLog.logMotionEvent(event, "onQsTouch: down action, QS tracking enabled");
+ mTracking = true;
+ traceQsJank(true, false);
+ mInitialTouchY = y;
+ mInitialTouchX = x;
+ onExpansionStarted();
+ mInitialHeightOnTouch = mExpansionHeight;
+ initVelocityTracker();
+ trackMovement(event);
+ break;
+
+ case MotionEvent.ACTION_POINTER_UP:
+ final int upPointer = event.getPointerId(event.getActionIndex());
+ if (mTrackingPointer == upPointer) {
+ // gesture is ongoing, find a new pointer to track
+ final int newIndex = event.getPointerId(0) != upPointer ? 0 : 1;
+ final float newY = event.getY(newIndex);
+ final float newX = event.getX(newIndex);
+ mTrackingPointer = event.getPointerId(newIndex);
+ mInitialHeightOnTouch = mExpansionHeight;
+ mInitialTouchY = newY;
+ mInitialTouchX = newX;
+ }
+ break;
+
+ case MotionEvent.ACTION_MOVE:
+ mShadeLog.logMotionEvent(event, "onQsTouch: move action, setting QS expansion");
+ setExpansionHeight(h + mInitialHeightOnTouch);
+ // TODO (b/265193930): remove dependency on NPVC
+ if (h >= mPanelViewControllerLazy.get().getFalsingThreshold()) {
+ mTouchAboveFalsingThreshold = true;
+ }
+ trackMovement(event);
+ break;
+
+ case MotionEvent.ACTION_UP:
+ case MotionEvent.ACTION_CANCEL:
+ mShadeLog.logMotionEvent(event,
+ "onQsTouch: up/cancel action, QS tracking disabled");
+ mTracking = false;
+ mTrackingPointer = -1;
+ trackMovement(event);
+ float fraction = computeExpansionFraction();
+ if (fraction != 0f || y >= mInitialTouchY) {
+ flingQsWithCurrentVelocity(y,
+ event.getActionMasked() == MotionEvent.ACTION_CANCEL);
+ } else {
+ traceQsJank(false,
+ event.getActionMasked() == MotionEvent.ACTION_CANCEL);
+ }
+ if (mQsVelocityTracker != null) {
+ mQsVelocityTracker.recycle();
+ mQsVelocityTracker = null;
+ }
+ break;
+ }
+ }
+
+ /** intercepts touches on Qs panel area. */
+ public boolean onIntercept(MotionEvent event) {
+ int pointerIndex = event.findPointerIndex(mTrackingPointer);
+ if (pointerIndex < 0) {
+ pointerIndex = 0;
+ mTrackingPointer = event.getPointerId(pointerIndex);
+ }
+ final float x = event.getX(pointerIndex);
+ final float y = event.getY(pointerIndex);
+
+ switch (event.getActionMasked()) {
+ case MotionEvent.ACTION_DOWN:
+ mInitialTouchY = y;
+ mInitialTouchX = x;
+ initVelocityTracker();
+ trackMovement(event);
+ float qsExpansionFraction = computeExpansionFraction();
+ // Intercept the touch if QS is between fully collapsed and fully expanded state
+ if (!mSplitShadeEnabled
+ && qsExpansionFraction > 0.0 && qsExpansionFraction < 1.0) {
+ mShadeLog.logMotionEvent(event,
+ "onQsIntercept: down action, QS partially expanded/collapsed");
+ return true;
+ }
+ // TODO (b/265193930): remove dependency on NPVC
+ if (mPanelViewControllerLazy.get().getKeyguardShowing()
+ && shouldQuickSettingsIntercept(mInitialTouchX, mInitialTouchY, 0)) {
+ // Dragging down on the lockscreen statusbar should prohibit other interactions
+ // immediately, otherwise we'll wait on the touchslop. This is to allow
+ // dragging down to expanded quick settings directly on the lockscreen.
+ mPanelView.getParent().requestDisallowInterceptTouchEvent(true);
+ }
+ if (mExpansionAnimator != null) {
+ mInitialHeightOnTouch = mExpansionHeight;
+ mShadeLog.logMotionEvent(event,
+ "onQsIntercept: down action, QS tracking enabled");
+ mTracking = true;
+ traceQsJank(true, false);
+ mNotificationStackScrollLayoutController.cancelLongPress();
+ }
+ break;
+ case MotionEvent.ACTION_POINTER_UP:
+ final int upPointer = event.getPointerId(event.getActionIndex());
+ if (mTrackingPointer == upPointer) {
+ // gesture is ongoing, find a new pointer to track
+ final int newIndex = event.getPointerId(0) != upPointer ? 0 : 1;
+ mTrackingPointer = event.getPointerId(newIndex);
+ mInitialTouchX = event.getX(newIndex);
+ mInitialTouchY = event.getY(newIndex);
+ }
+ break;
+
+ case MotionEvent.ACTION_MOVE:
+ final float h = y - mInitialTouchY;
+ trackMovement(event);
+ if (mTracking) {
+
+ // Already tracking because onOverscrolled was called. We need to update here
+ // so we don't stop for a frame until the next touch event gets handled in
+ // onTouchEvent.
+ setExpansionHeight(h + mInitialHeightOnTouch);
+ trackMovement(event);
+ return true;
+ } else {
+ mShadeLog.logMotionEvent(event,
+ "onQsIntercept: move ignored because qs tracking disabled");
+ }
+ // TODO (b/265193930): remove dependency on NPVC
+ float touchSlop = event.getClassification()
+ == MotionEvent.CLASSIFICATION_AMBIGUOUS_GESTURE
+ ? mTouchSlop * mSlopMultiplier
+ : mTouchSlop;
+ if ((h > touchSlop || (h < -touchSlop && getExpanded()))
+ && Math.abs(h) > Math.abs(x - mInitialTouchX)
+ && shouldQuickSettingsIntercept(
+ mInitialTouchX, mInitialTouchY, h)) {
+ mPanelView.getParent().requestDisallowInterceptTouchEvent(true);
+ mShadeLog.onQsInterceptMoveQsTrackingEnabled(h);
+ mTracking = true;
+ traceQsJank(true, false);
+ onExpansionStarted();
+ mPanelViewControllerLazy.get().notifyExpandingFinished();
+ mInitialHeightOnTouch = mExpansionHeight;
+ mInitialTouchY = y;
+ mInitialTouchX = x;
+ mNotificationStackScrollLayoutController.cancelLongPress();
+ return true;
+ } else {
+ mShadeLog.logQsTrackingNotStarted(mInitialTouchY, y, h, touchSlop,
+ getExpanded(), mPanelViewControllerLazy.get().getKeyguardShowing(),
+ isExpansionEnabled());
+ }
+ break;
+
+ case MotionEvent.ACTION_CANCEL:
+ case MotionEvent.ACTION_UP:
+ trackMovement(event);
+ mShadeLog.logMotionEvent(event, "onQsIntercept: up action, QS tracking disabled");
+ mTracking = false;
+ break;
+ }
+ return false;
+ }
+
+ private void onPanelExpansionChanged(ShadeExpansionChangeEvent event) {
+ mShadeExpandedFraction = event.getFraction();
+ }
+
+ /**
+ * Animate QS closing by flinging it.
+ * If QS is expanded, it will collapse into QQS and stop.
+ * If in split shade, it will collapse the whole shade.
+ *
+ * @param animateAway Do not stop when QS becomes QQS. Fling until QS isn't visible anymore.
+ */
+ public void animateCloseQs(boolean animateAway) {
+ if (mExpansionAnimator != null) {
+ if (!mAnimatorExpand) {
+ return;
+ }
+ float height = mExpansionHeight;
+ mExpansionAnimator.cancel();
+ setExpansionHeight(height);
+ }
+ flingQs(0 /* vel */, animateAway ? FLING_HIDE : FLING_COLLAPSE);
+ }
+
+ private void cancelExpansionAnimation() {
+ if (mExpansionAnimator != null) {
+ mExpansionAnimator.cancel();
+ }
+ }
+
+ /** @see #flingQs(float, int, Runnable, boolean) */
+ public void flingQs(float vel, int type) {
+ flingQs(vel, type, null /* onFinishRunnable */, false /* isClick */);
+ }
+
+ /**
+ * Animates QS or QQS as if the user had swiped up or down.
+ *
+ * @param vel Finger velocity or 0 when not initiated by touch events.
+ * @param type Either FLING_EXPAND, FLING_COLLAPSE or FLING_HIDE.
+ * @param onFinishRunnable Runnable to be executed at the end of animation.
+ * @param isClick If originated by click (different interpolator and duration.)
+ */
+ private void flingQs(float vel, int type, final Runnable onFinishRunnable,
+ boolean isClick) {
+ float target;
+ switch (type) {
+ case FLING_EXPAND:
+ target = getMaxExpansionHeight();
+ break;
+ case FLING_COLLAPSE:
+ target = getMinExpansionHeight();
+ break;
+ case FLING_HIDE:
+ default:
+ if (isQsFragmentCreated()) {
+ mQs.closeDetail();
+ }
+ target = 0;
+ }
+ if (target == mExpansionHeight) {
+ if (onFinishRunnable != null) {
+ onFinishRunnable.run();
+ }
+ traceQsJank(false, type != FLING_EXPAND);
+ return;
+ }
+
+ // If we move in the opposite direction, reset velocity and use a different duration.
+ boolean oppositeDirection = false;
+ boolean expanding = type == FLING_EXPAND;
+ if (vel > 0 && !expanding || vel < 0 && expanding) {
+ vel = 0;
+ oppositeDirection = true;
+ }
+ ValueAnimator animator = ValueAnimator.ofFloat(
+ mExpansionHeight, target);
+ if (isClick) {
+ animator.setInterpolator(Interpolators.TOUCH_RESPONSE);
+ animator.setDuration(368);
+ } else {
+ if (mFlingQsWithoutClickListener != null) {
+ mFlingQsWithoutClickListener.onFlingQsWithoutClick(animator, mExpansionHeight,
+ target, vel);
+ }
+ }
+ if (oppositeDirection) {
+ animator.setDuration(350);
+ }
+ animator.addUpdateListener(
+ animation -> setExpansionHeight((Float) animation.getAnimatedValue()));
+ animator.addListener(new AnimatorListenerAdapter() {
+ private boolean mIsCanceled;
+
+ @Override
+ public void onAnimationStart(Animator animation) {
+ mPanelViewControllerLazy.get().notifyExpandingStarted();
+ }
+
+ @Override
+ public void onAnimationCancel(Animator animation) {
+ mIsCanceled = true;
+ }
+
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ mAnimatingHiddenFromCollapsed = false;
+ mAnimating = false;
+ mPanelViewControllerLazy.get().notifyExpandingFinished();
+ mNotificationStackScrollLayoutController.resetCheckSnoozeLeavebehind();
+ mExpansionAnimator = null;
+ if (onFinishRunnable != null) {
+ onFinishRunnable.run();
+ }
+ traceQsJank(false, mIsCanceled);
+ }
+ });
+ // Let's note that we're animating QS. Moving the animator here will cancel it immediately,
+ // so we need a separate flag.
+ mAnimating = true;
+ animator.start();
+ mExpansionAnimator = animator;
+ mAnimatorExpand = expanding;
+ mAnimatingHiddenFromCollapsed =
+ computeExpansionFraction() == 0.0f && target == 0;
+ }
+
+ private void flingQsWithCurrentVelocity(float y, boolean isCancelMotionEvent) {
+ float vel = getCurrentVelocity();
+ // TODO (b/265193930): remove dependency on NPVC
+ boolean expandsQs = mPanelViewControllerLazy.get().flingExpandsQs(vel);
+ if (expandsQs) {
+ if (mFalsingManager.isUnlockingDisabled() || isQsFalseTouch()) {
+ expandsQs = false;
+ } else {
+ logQsSwipeDown(y);
+ }
+ } else if (vel < 0) {
+ mFalsingManager.isFalseTouch(QS_COLLAPSE);
+ }
+
+ int flingType;
+ if (expandsQs && !isCancelMotionEvent) {
+ flingType = FLING_EXPAND;
+ } else if (mSplitShadeEnabled) {
+ flingType = FLING_HIDE;
+ } else {
+ flingType = FLING_COLLAPSE;
+ }
+ flingQs(vel, flingType);
+ }
+
+ private void logQsSwipeDown(float y) {
+ float vel = getCurrentVelocity();
+ final int gesture = mBarState == KEYGUARD ? MetricsProto.MetricsEvent.ACTION_LS_QS
+ : MetricsProto.MetricsEvent.ACTION_SHADE_QS_PULL;
+ // TODO (b/265193930): remove dependency on NPVC
+ float displayDensity = mPanelViewControllerLazy.get().getDisplayDensity();
+ mLockscreenGestureLogger.write(gesture,
+ (int) ((y - getInitialTouchY()) / displayDensity), (int) (vel / displayDensity));
+ }
+
+ /** */
+ public FragmentHostManager.FragmentListener getQsFragmentListener() {
+ return new QsFragmentListener();
+ }
+
+ /** */
+ public final class QsFragmentListener implements FragmentHostManager.FragmentListener {
+ /** */
+ @Override
+ public void onFragmentViewCreated(String tag, Fragment fragment) {
+ mQs = (QS) fragment;
+ mQs.setPanelView(mQsHeightListener);
+ mQs.setCollapseExpandAction(mQsCollapseExpandAction);
+ mQs.setHeaderClickable(isExpansionEnabled());
+ mQs.setOverscrolling(mStackScrollerOverscrolling);
+ mQs.setInSplitShade(mSplitShadeEnabled);
+ mQs.setIsNotificationPanelFullWidth(mIsFullWidth);
+
+ // recompute internal state when qspanel height changes
+ mQs.getView().addOnLayoutChangeListener(
+ (v, left, top, right, bottom, oldLeft, oldTop, oldRight, oldBottom) -> {
+ final int height = bottom - top;
+ final int oldHeight = oldBottom - oldTop;
+ if (height != oldHeight) {
+ onHeightChanged();
+ }
+ });
+ mQs.setCollapsedMediaVisibilityChangedListener((visible) -> {
+ if (mQs.getHeader().isShown()) {
+ setAnimateNextNotificationBounds(
+ StackStateAnimator.ANIMATION_DURATION_STANDARD, 0);
+ mNotificationStackScrollLayoutController.animateNextTopPaddingChange();
+ }
+ });
+ mLockscreenShadeTransitionController.setQS(mQs);
+ mShadeTransitionController.setQs(mQs);
+ mNotificationStackScrollLayoutController.setQsHeader((ViewGroup) mQs.getHeader());
+ mQs.setScrollListener(mQsScrollListener);
+ updateExpansion();
+ }
+
+ /** */
+ @Override
+ public void onFragmentViewDestroyed(String tag, Fragment fragment) {
+ // Manual handling of fragment lifecycle is only required because this bridges
+ // non-fragment and fragment code. Once we are using a fragment for the notification
+ // panel, mQs will not need to be null cause it will be tied to the same lifecycle.
+ if (fragment == mQs) {
+ mQs = null;
+ }
+ }
+ }
+
+ private final class LockscreenShadeTransitionCallback
+ implements LockscreenShadeTransitionController.Callback {
+ /** Called when pulse expansion has finished and this is going to the full shade. */
+ @Override
+ public void onPulseExpansionFinished() {
+ setAnimateNextNotificationBounds(
+ StackStateAnimator.ANIMATION_DURATION_GO_TO_FULL_SHADE, 0);
+ mIsPulseExpansionResettingAnimator = true;
+ }
+
+ @Override
+ public void setTransitionToFullShadeAmount(float pxAmount, boolean animate, long delay) {
+ if (animate && mIsFullWidth) {
+ setAnimateNextNotificationBounds(
+ StackStateAnimator.ANIMATION_DURATION_GO_TO_FULL_SHADE, delay);
+ mIsTranslationResettingAnimator = mTranslationForFullShadeTransition > 0.0f;
+ }
+ float endPosition = 0;
+ if (pxAmount > 0.0f) {
+ if (mSplitShadeEnabled) {
+ float qsHeight = MathUtils.lerp(getMinExpansionHeight(),
+ getMaxExpansionHeight(),
+ mLockscreenShadeTransitionController.getQSDragProgress());
+ setExpansionHeight(qsHeight);
+ }
+ if (mNotificationStackScrollLayoutController.getVisibleNotificationCount() == 0
+ && !mMediaDataManager.hasActiveMediaOrRecommendation()) {
+ // No notifications are visible, let's animate to the height of qs instead
+ if (isQsFragmentCreated()) {
+ // Let's interpolate to the header height instead of the top padding,
+ // because the toppadding is way too low because of the large clock.
+ // we still want to take into account the edgePosition though as that nicely
+ // overshoots in the stackscroller
+ endPosition = getEdgePosition()
+ - mNotificationStackScrollLayoutController.getTopPadding()
+ + getHeaderHeight();
+ }
+ } else {
+ // Interpolating to the new bottom edge position!
+ endPosition = getEdgePosition() + mNotificationStackScrollLayoutController
+ .getFullShadeTransitionInset();
+ if (mBarState == KEYGUARD) {
+ endPosition -= mLockscreenNotificationPadding;
+ }
+ }
+ }
+
+ // Calculate the overshoot amount such that we're reaching the target after our desired
+ // distance, but only reach it fully once we drag a full shade length.
+ mTransitioningToFullShadeProgress = Interpolators.FAST_OUT_SLOW_IN.getInterpolation(
+ MathUtils.saturate(pxAmount / mDistanceForFullShadeTransition));
+
+ int position = (int) MathUtils.lerp((float) 0, endPosition,
+ mTransitioningToFullShadeProgress);
+ if (mTransitioningToFullShadeProgress > 0.0f) {
+ // we want at least 1 pixel otherwise the panel won't be clipped
+ position = Math.max(1, position);
+ }
+ mTransitionToFullShadePosition = position;
+ updateExpansion();
+ }
+ }
+
+ private final class NsslOverscrollTopChangedListener implements
+ NotificationStackScrollLayout.OnOverscrollTopChangedListener {
+ @Override
+ public void onOverscrollTopChanged(float amount, boolean isRubberbanded) {
+ // When in split shade, overscroll shouldn't carry through to QS
+ if (mSplitShadeEnabled) {
+ return;
+ }
+ cancelExpansionAnimation();
+ if (!isExpansionEnabled()) {
+ amount = 0f;
+ }
+ float rounded = amount >= 1f ? amount : 0f;
+ setOverScrolling(rounded != 0f && isRubberbanded);
+ mExpansionFromOverscroll = rounded != 0f;
+ mLastOverscroll = rounded;
+ updateQsState();
+ setExpansionHeight(getMinExpansionHeight() + rounded);
+ }
+
+ @Override
+ public void flingTopOverscroll(float velocity, boolean open) {
+ // in split shade mode we want to expand/collapse QS only when touch happens within QS
+ if (isSplitShadeAndTouchXOutsideQs(mInitialTouchX)) {
+ return;
+ }
+ mLastOverscroll = 0f;
+ mExpansionFromOverscroll = false;
+ if (open) {
+ // During overscrolling, qsExpansion doesn't actually change that the qs is
+ // becoming expanded. Any layout could therefore reset the position again. Let's
+ // make sure we can expand
+ setOverScrolling(false);
+ }
+ setExpansionHeight(getExpansionHeight());
+ boolean canExpand = isExpansionEnabled();
+ flingQs(!canExpand && open ? 0f : velocity,
+ open && canExpand ? FLING_EXPAND : FLING_COLLAPSE, () -> {
+ setOverScrolling(false);
+ updateQsState();
+ }, false);
+ }
+ }
+
+ void beginJankMonitoring(boolean isFullyCollapsed) {
+ if (mInteractionJankMonitor == null) {
+ return;
+ }
+ // TODO (b/265193930): remove dependency on NPVC
+ InteractionJankMonitor.Configuration.Builder builder =
+ InteractionJankMonitor.Configuration.Builder.withView(
+ InteractionJankMonitor.CUJ_NOTIFICATION_SHADE_EXPAND_COLLAPSE,
+ mPanelView).setTag(isFullyCollapsed ? "Expand" : "Collapse");
+ mInteractionJankMonitor.begin(builder);
+ }
+
+ void endJankMonitoring() {
+ if (mInteractionJankMonitor == null) {
+ return;
+ }
+ InteractionJankMonitor.getInstance().end(
+ InteractionJankMonitor.CUJ_NOTIFICATION_SHADE_EXPAND_COLLAPSE);
+ }
+
+ void cancelJankMonitoring() {
+ if (mInteractionJankMonitor == null) {
+ return;
+ }
+ InteractionJankMonitor.getInstance().cancel(
+ InteractionJankMonitor.CUJ_NOTIFICATION_SHADE_EXPAND_COLLAPSE);
+ }
+
+ void traceQsJank(boolean startTracing, boolean wasCancelled) {
+ if (mInteractionJankMonitor == null) {
+ return;
+ }
+ if (startTracing) {
+ mInteractionJankMonitor.begin(mPanelView, CUJ_NOTIFICATION_SHADE_QS_EXPAND_COLLAPSE);
+ } else {
+ if (wasCancelled) {
+ mInteractionJankMonitor.cancel(CUJ_NOTIFICATION_SHADE_QS_EXPAND_COLLAPSE);
+ } else {
+ mInteractionJankMonitor.end(CUJ_NOTIFICATION_SHADE_QS_EXPAND_COLLAPSE);
+ }
+ }
+ }
+
+ interface ExpansionHeightSetToMaxListener {
+ void onExpansionHeightSetToMax(boolean requestPaddingUpdate);
+ }
+
+ interface ExpansionHeightListener {
+ void onQsSetExpansionHeightCalled(boolean qsFullyExpanded);
+ }
+
+ interface QsStateUpdateListener {
+ void onQsStateUpdated(boolean qsExpanded, boolean isStackScrollerOverscrolling);
+ }
+
+ interface ApplyClippingImmediatelyListener {
+ void onQsClippingImmediatelyApplied(boolean clipStatusView, Rect lastQsClipBounds,
+ int top, boolean qsFragmentCreated, boolean qsVisible);
+ }
+
+ interface FlingQsWithoutClickListener {
+ void onFlingQsWithoutClick(ValueAnimator animator, float qsExpansionHeight,
+ float target, float vel);
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/shade/ShadeLogger.kt b/packages/SystemUI/src/com/android/systemui/shade/ShadeLogger.kt
index 26c839de..b28509e 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/ShadeLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/ShadeLogger.kt
@@ -50,7 +50,6 @@
h: Float,
touchSlop: Float,
qsExpanded: Boolean,
- collapsedOnDown: Boolean,
keyguardShowing: Boolean,
qsExpansionEnabled: Boolean
) {
@@ -63,13 +62,12 @@
long1 = h.toLong()
double1 = touchSlop.toDouble()
bool1 = qsExpanded
- bool2 = collapsedOnDown
- bool3 = keyguardShowing
- bool4 = qsExpansionEnabled
+ bool2 = keyguardShowing
+ bool3 = qsExpansionEnabled
},
{
"QsTrackingNotStarted: initTouchY=$int1,y=$int2,h=$long1,slop=$double1,qsExpanded" +
- "=$bool1,collapsedDown=$bool2,keyguardShowing=$bool3,qsExpansion=$bool4"
+ "=$bool1,keyguardShowing=$bool2,qsExpansion=$bool3"
}
)
}
@@ -158,7 +156,6 @@
qsMinExpansionHeight: Int,
qsMaxExpansionHeight: Int,
stackScrollerOverscrolling: Boolean,
- dozing: Boolean,
qsAnimatorExpand: Boolean,
animatingQs: Boolean
) {
@@ -171,14 +168,13 @@
int1 = qsMinExpansionHeight
int2 = qsMaxExpansionHeight
bool2 = stackScrollerOverscrolling
- bool3 = dozing
- bool4 = qsAnimatorExpand
+ bool3 = qsAnimatorExpand
// 0 = false, 1 = true
long1 = animatingQs.compareTo(false).toLong()
},
{
"$str1 qsExpanded=$bool1,qsMinExpansionHeight=$int1,qsMaxExpansionHeight=$int2," +
- "stackScrollerOverscrolling=$bool2,dozing=$bool3,qsAnimatorExpand=$bool4," +
+ "stackScrollerOverscrolling=$bool2,qsAnimatorExpand=$bool3," +
"animatingQs=$long1"
}
)
diff --git a/packages/SystemUI/src/com/android/systemui/smartspace/dagger/SmartspaceViewComponent.kt b/packages/SystemUI/src/com/android/systemui/smartspace/dagger/SmartspaceViewComponent.kt
index 236ba1f..5736a5c 100644
--- a/packages/SystemUI/src/com/android/systemui/smartspace/dagger/SmartspaceViewComponent.kt
+++ b/packages/SystemUI/src/com/android/systemui/smartspace/dagger/SmartspaceViewComponent.kt
@@ -21,6 +21,7 @@
import android.view.ViewGroup
import com.android.systemui.plugins.ActivityStarter
import com.android.systemui.plugins.BcSmartspaceDataPlugin
+import com.android.systemui.plugins.BcSmartspaceDataPlugin.UI_SURFACE_DREAM
import com.android.systemui.plugins.FalsingManager
import com.android.systemui.smartspace.dagger.SmartspaceViewComponent.SmartspaceViewModule.PLUGIN
import dagger.BindsInstance
@@ -57,7 +58,7 @@
BcSmartspaceDataPlugin.SmartspaceView {
val ssView = plugin.getView(parent)
// Currently, this is only used to provide SmartspaceView on Dream surface.
- ssView.setIsDreaming(true)
+ ssView.setUiSurface(UI_SURFACE_DREAM)
ssView.registerDataProvider(plugin)
ssView.setIntentStarter(object : BcSmartspaceDataPlugin.IntentStarter {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/LockscreenShadeTransitionController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/LockscreenShadeTransitionController.kt
index afa60fb..f0d064b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/LockscreenShadeTransitionController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/LockscreenShadeTransitionController.kt
@@ -187,6 +187,8 @@
private val qsTransitionController = qsTransitionControllerFactory.create { qS }
+ private val callbacks = mutableListOf<Callback>()
+
/** See [LockscreenShadeQsTransitionController.qsTransitionFraction].*/
@get:FloatRange(from = 0.0, to = 1.0)
val qSDragProgress: Float
@@ -319,8 +321,8 @@
true /* drag down is always an open */)
}
notificationPanelController.animateToFullShade(delay)
- notificationPanelController.setTransitionToFullShadeAmount(0f,
- true /* animated */, delay)
+ callbacks.forEach { it.setTransitionToFullShadeAmount(0f,
+ true /* animated */, delay) }
// Let's reset ourselves, ready for the next animation
@@ -424,8 +426,8 @@
qsTransitionController.dragDownAmount = value
- notificationPanelController.setTransitionToFullShadeAmount(field,
- false /* animate */, 0 /* delay */)
+ callbacks.forEach { it.setTransitionToFullShadeAmount(field,
+ false /* animate */, 0 /* delay */) }
mediaHierarchyManager.setTransitionToFullShadeAmount(field)
scrimTransitionController.dragDownAmount = value
@@ -688,7 +690,7 @@
if (cancelled) {
setPulseHeight(0f, animate = true)
} else {
- notificationPanelController.onPulseExpansionFinished()
+ callbacks.forEach { it.onPulseExpansionFinished() }
setPulseHeight(0f, animate = false)
}
}
@@ -720,6 +722,27 @@
"${animationHandlerOnKeyguardDismiss != null}")
}
}
+
+
+ fun addCallback(callback: Callback) {
+ if (!callbacks.contains(callback)) {
+ callbacks.add(callback)
+ }
+ }
+
+ /**
+ * Callback for authentication events.
+ */
+ interface Callback {
+ /** TODO: comment here */
+ fun onPulseExpansionFinished() {}
+
+ /**
+ * Sets the amount of pixels we have currently dragged down if we're transitioning
+ * to the full shade. 0.0f means we're not transitioning yet.
+ */
+ fun setTransitionToFullShadeAmount(pxAmount: Float, animate: Boolean, delay: Long) {}
+ }
}
/**
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifCollection.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifCollection.java
index df35c9e..aa9a6c2 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifCollection.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifCollection.java
@@ -164,6 +164,11 @@
private Queue<NotifEvent> mEventQueue = new ArrayDeque<>();
+ private final Runnable mRebuildListRunnable = () -> {
+ if (mBuildListener != null) {
+ mBuildListener.onBuildList(mReadOnlyNotificationSet, "asynchronousUpdate");
+ }
+ };
private boolean mAttached = false;
private boolean mAmDispatchingToOtherCode;
@@ -458,7 +463,7 @@
int modificationType) {
Assert.isMainThread();
mEventQueue.add(new ChannelChangedEvent(pkgName, user, channel, modificationType));
- dispatchEventsAndRebuildList("onNotificationChannelModified");
+ dispatchEventsAndAsynchronouslyRebuildList();
}
private void onNotificationsInitialized() {
@@ -613,15 +618,39 @@
private void dispatchEventsAndRebuildList(String reason) {
Trace.beginSection("NotifCollection.dispatchEventsAndRebuildList");
+ if (mMainHandler.hasCallbacks(mRebuildListRunnable)) {
+ mMainHandler.removeCallbacks(mRebuildListRunnable);
+ }
+
+ dispatchEvents();
+
+ if (mBuildListener != null) {
+ mBuildListener.onBuildList(mReadOnlyNotificationSet, reason);
+ }
+ Trace.endSection();
+ }
+
+ private void dispatchEventsAndAsynchronouslyRebuildList() {
+ Trace.beginSection("NotifCollection.dispatchEventsAndAsynchronouslyRebuildList");
+
+ dispatchEvents();
+
+ if (!mMainHandler.hasCallbacks(mRebuildListRunnable)) {
+ mMainHandler.postDelayed(mRebuildListRunnable, 1000L);
+ }
+
+ Trace.endSection();
+ }
+
+ private void dispatchEvents() {
+ Trace.beginSection("NotifCollection.dispatchEvents");
+
mAmDispatchingToOtherCode = true;
while (!mEventQueue.isEmpty()) {
mEventQueue.remove().dispatchTo(mNotifCollectionListeners);
}
mAmDispatchingToOtherCode = false;
- if (mBuildListener != null) {
- mBuildListener.onBuildList(mReadOnlyNotificationSet, reason);
- }
Trace.endSection();
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/FooterView.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/FooterView.java
index 21f4cb5..49f17b6 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/FooterView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/FooterView.java
@@ -88,6 +88,7 @@
mSeenNotifsFooterTextView = findViewById(R.id.unlock_prompt_footer);
updateResources();
updateText();
+ updateColors();
}
public void setFooterLabelTextAndIcon(@StringRes int text, @DrawableRes int icon) {
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 1fb7eb5..d2087ba6 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
@@ -16,7 +16,7 @@
package com.android.systemui.statusbar.notification.stack;
-import static android.os.Trace.TRACE_TAG_ALWAYS;
+import static android.os.Trace.TRACE_TAG_APP;
import static com.android.internal.jank.InteractionJankMonitor.CUJ_NOTIFICATION_SHADE_SCROLL_FLING;
import static com.android.internal.jank.InteractionJankMonitor.CUJ_SHADE_CLEAR_ALL;
@@ -1121,7 +1121,7 @@
@Override
public void requestLayout() {
- Trace.instant(TRACE_TAG_ALWAYS, "NotificationStackScrollLayout#requestLayout");
+ Trace.instant(TRACE_TAG_APP, "NotificationStackScrollLayout#requestLayout");
super.requestLayout();
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithm.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithm.java
index 3170f34..a425792 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithm.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithm.java
@@ -894,20 +894,19 @@
ExpandableViewState childViewState = child.getViewState();
float baseZ = ambientState.getBaseZHeight();
- // Handles HUN shadow when Shade is opened
-
if (child.mustStayOnScreen() && !childViewState.headsUpIsVisible
&& !ambientState.isDozingAndNotPulsing(child)
&& childViewState.getYTranslation() < ambientState.getTopPadding()
+ ambientState.getStackTranslation()) {
- // Handles HUN shadow when Shade is opened, and AmbientState.mScrollY > 0
- // Calculate the HUN's z-value based on its overlapping fraction with QQS Panel.
- // When scrolling down shade to make HUN back to in-position in Notification Panel,
- // The over-lapping fraction goes to 0, and shadows hides gradually.
+
if (childrenOnTop != 0.0f) {
- // To elevate the later HUN over previous HUN
+ // To elevate the later HUN over previous HUN when multiple HUNs exist
childrenOnTop++;
} else {
+ // Handles HUN shadow when Shade is opened, and AmbientState.mScrollY > 0
+ // Calculate the HUN's z-value based on its overlapping fraction with QQS Panel.
+ // When scrolling down shade to make HUN back to in-position in Notification Panel,
+ // The overlapping fraction goes to 0, and shadows hides gradually.
float overlap = ambientState.getTopPadding()
+ ambientState.getStackTranslation() - childViewState.getYTranslation();
// To prevent over-shadow during HUN entry
@@ -915,7 +914,6 @@
1.0f,
overlap / childViewState.height
);
- MathUtils.saturate(childrenOnTop);
}
childViewState.setZTranslation(baseZ
+ childrenOnTop * mPinnedZTranslationExtra);
@@ -945,7 +943,6 @@
childViewState.setZTranslation(baseZ);
}
- // Handles HUN shadow when shade is closed.
// While HUN is showing and Shade is closed: headerVisibleAmount stays 0, shadow stays.
// During HUN-to-Shade (eg. dragging down HUN to open Shade): headerVisibleAmount goes
// gradually from 0 to 1, shadow hides gradually.
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesCommandQueueCallbacks.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesCommandQueueCallbacks.java
index ccde3c2..b8ab956 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesCommandQueueCallbacks.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesCommandQueueCallbacks.java
@@ -57,6 +57,7 @@
import com.android.systemui.settings.UserTracker;
import com.android.systemui.shade.CameraLauncher;
import com.android.systemui.shade.NotificationPanelViewController;
+import com.android.systemui.shade.QuickSettingsController;
import com.android.systemui.shade.ShadeController;
import com.android.systemui.statusbar.CommandQueue;
import com.android.systemui.statusbar.DisableFlagsLogger;
@@ -104,6 +105,7 @@
private final VibrationEffect mCameraLaunchGestureVibrationEffect;
private final SystemBarAttributesListener mSystemBarAttributesListener;
private final Lazy<CameraLauncher> mCameraLauncherLazy;
+ private final QuickSettingsController mQsController;
private static final VibrationAttributes HARDWARE_FEEDBACK_VIBRATION_ATTRIBUTES =
VibrationAttributes.createForUsage(VibrationAttributes.USAGE_HARDWARE_FEEDBACK);
@@ -111,6 +113,7 @@
@Inject
CentralSurfacesCommandQueueCallbacks(
CentralSurfaces centralSurfaces,
+ QuickSettingsController quickSettingsController,
Context context,
@Main Resources resources,
ShadeController shadeController,
@@ -137,6 +140,7 @@
Lazy<CameraLauncher> cameraLauncherLazy,
UserTracker userTracker) {
mCentralSurfaces = centralSurfaces;
+ mQsController = quickSettingsController;
mContext = context;
mShadeController = shadeController;
mCommandQueue = commandQueue;
@@ -334,9 +338,9 @@
mNotificationStackScrollLayoutController.setWillExpand(true);
mHeadsUpManager.unpinAll(true /* userUnpinned */);
mMetricsLogger.count("panel_open", 1);
- } else if (!mNotificationPanelViewController.isInSettings()
+ } else if (!mQsController.getExpanded()
&& !mNotificationPanelViewController.isExpanding()) {
- mNotificationPanelViewController.flingSettings(0 /* velocity */,
+ mQsController.flingQs(0 /* velocity */,
NotificationPanelViewController.FLING_EXPAND);
mMetricsLogger.count("panel_open_qs", 1);
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
index 85399ca..378b74a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
@@ -185,6 +185,7 @@
import com.android.systemui.shade.NotificationPanelViewController;
import com.android.systemui.shade.NotificationShadeWindowView;
import com.android.systemui.shade.NotificationShadeWindowViewController;
+import com.android.systemui.shade.QuickSettingsController;
import com.android.systemui.shade.ShadeController;
import com.android.systemui.shade.ShadeExpansionChangeEvent;
import com.android.systemui.shade.ShadeExpansionStateManager;
@@ -488,6 +489,8 @@
// settings
private QSPanelController mQSPanelController;
+ @VisibleForTesting
+ QuickSettingsController mQsController;
KeyguardIndicationController mKeyguardIndicationController;
@@ -1419,7 +1422,7 @@
|| isOccluded()
|| !mKeyguardStateController.canDismissLockScreen()
|| mKeyguardViewMediator.isAnySimPinSecure()
- || (mNotificationPanelViewController.isQsExpanded() && trackingTouch)
+ || (mQsController.getExpanded() && trackingTouch)
|| mNotificationPanelViewController.getBarState() == StatusBarState.SHADE_LOCKED) {
return;
}
@@ -1580,6 +1583,7 @@
mCentralSurfacesComponent.getLockIconViewController().init();
mStackScrollerController =
mCentralSurfacesComponent.getNotificationStackScrollLayoutController();
+ mQsController = mCentralSurfacesComponent.getQuickSettingsController();
mStackScroller = mStackScrollerController.getView();
mNotifListContainer = mCentralSurfacesComponent.getNotificationListContainer();
mPresenter = mCentralSurfacesComponent.getNotificationPresenter();
@@ -1699,7 +1703,7 @@
&& !isShadeDisabled()
&& ((mDisabled2 & StatusBarManager.DISABLE2_QUICK_SETTINGS) == 0)
&& !mDozing;
- mNotificationPanelViewController.setQsExpansionEnabledPolicy(expandEnabled);
+ mQsController.setExpansionEnabledPolicy(expandEnabled);
Log.d(TAG, "updateQsExpansionEnabled - QS Expand enabled: " + expandEnabled);
}
@@ -3234,12 +3238,12 @@
mStatusBarKeyguardViewManager.onBackPressed();
return true;
}
- if (mNotificationPanelViewController.isQsCustomizing()) {
- mNotificationPanelViewController.closeQsCustomizer();
+ if (mQsController.isCustomizing()) {
+ mQsController.closeQsCustomizer();
return true;
}
- if (mNotificationPanelViewController.isQsExpanded()) {
- mNotificationPanelViewController.animateCloseQs(false /* animateAway */);
+ if (mQsController.getExpanded()) {
+ mNotificationPanelViewController.animateCloseQs(false);
return true;
}
if (mNotificationPanelViewController.closeUserSwitcherIfOpen()) {
@@ -3600,7 +3604,7 @@
mFalsingCollector.onScreenOff();
mScrimController.onScreenTurnedOff();
if (mCloseQsBeforeScreenOff) {
- mNotificationPanelViewController.closeQs();
+ mQsController.closeQs();
mCloseQsBeforeScreenOff = false;
}
updateIsKeyguard();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenter.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenter.java
index da1c361..4eed487 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenter.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenter.java
@@ -40,6 +40,7 @@
import com.android.systemui.plugins.ActivityStarter.OnDismissAction;
import com.android.systemui.shade.NotificationPanelViewController;
import com.android.systemui.shade.NotificationShadeWindowView;
+import com.android.systemui.shade.QuickSettingsController;
import com.android.systemui.statusbar.CommandQueue;
import com.android.systemui.statusbar.KeyguardIndicationController;
import com.android.systemui.statusbar.LockscreenShadeTransitionController;
@@ -102,6 +103,7 @@
private final IStatusBarService mBarService;
private final DynamicPrivacyController mDynamicPrivacyController;
private final NotificationListContainer mNotifListContainer;
+ private final QuickSettingsController mQsController;
protected boolean mVrMode;
@@ -109,6 +111,7 @@
StatusBarNotificationPresenter(
Context context,
NotificationPanelViewController panel,
+ QuickSettingsController quickSettingsController,
HeadsUpManagerPhone headsUp,
NotificationShadeWindowView statusBarWindow,
ActivityStarter activityStarter,
@@ -136,6 +139,7 @@
mActivityStarter = activityStarter;
mKeyguardStateController = keyguardStateController;
mNotificationPanel = panel;
+ mQsController = quickSettingsController;
mHeadsUpManager = headsUp;
mDynamicPrivacyController = dynamicPrivacyController;
mKeyguardIndicationController = keyguardIndicationController;
@@ -191,7 +195,7 @@
private void maybeClosePanelForShadeEmptied() {
if (CLOSE_PANEL_WHEN_EMPTIED
&& !mNotificationPanel.isTracking()
- && !mNotificationPanel.isQsExpanded()
+ && !mQsController.getExpanded()
&& mStatusBarStateController.getState() == StatusBarState.SHADE_LOCKED
&& !isCollapsing()) {
mStatusBarStateController.setState(StatusBarState.KEYGUARD);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/CentralSurfacesComponent.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/CentralSurfacesComponent.java
index 64b04e9..aec196f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/CentralSurfacesComponent.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/CentralSurfacesComponent.java
@@ -26,6 +26,7 @@
import com.android.systemui.shade.NotificationPanelViewController;
import com.android.systemui.shade.NotificationShadeWindowView;
import com.android.systemui.shade.NotificationShadeWindowViewController;
+import com.android.systemui.shade.QuickSettingsController;
import com.android.systemui.statusbar.NotificationPresenter;
import com.android.systemui.statusbar.NotificationShelfController;
import com.android.systemui.statusbar.core.StatusBarInitializer;
@@ -113,6 +114,9 @@
*/
NotificationPanelViewController getNotificationPanelViewController();
+ /** Creates a QuickSettingsController. */
+ QuickSettingsController getQuickSettingsController();
+
/**
* Creates a LockIconViewController. Must be init after creation.
*/
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java
index 6b80494..dc90e2d 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java
@@ -28,6 +28,7 @@
import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_USER_LOCKDOWN;
import static com.android.keyguard.FaceAuthApiRequestReason.NOTIFICATION_PANEL_CLICKED;
import static com.android.keyguard.KeyguardUpdateMonitor.BIOMETRIC_HELP_FACE_NOT_AVAILABLE;
+import static com.android.keyguard.KeyguardUpdateMonitor.BIOMETRIC_STATE_CANCELLING;
import static com.android.keyguard.KeyguardUpdateMonitor.BIOMETRIC_STATE_CANCELLING_RESTARTING;
import static com.android.keyguard.KeyguardUpdateMonitor.DEFAULT_CANCEL_SIGNAL_TIMEOUT;
import static com.android.keyguard.KeyguardUpdateMonitor.HAL_POWER_PRESS_TIMEOUT;
@@ -1166,10 +1167,11 @@
assertThat(mKeyguardUpdateMonitor.isFingerprintLockedOut()).isEqualTo(fpLocked);
assertThat(mKeyguardUpdateMonitor.isFaceLockedOut()).isEqualTo(faceLocked);
- // Fingerprint should be restarted once its cancelled bc on lockout, the device
- // can still detectFingerprint (and if it's not locked out, fingerprint can listen)
+ // Fingerprint should be cancelled on lockout if going to lockout state, else
+ // restarted if it's not
assertThat(mKeyguardUpdateMonitor.mFingerprintRunningState)
- .isEqualTo(BIOMETRIC_STATE_CANCELLING_RESTARTING);
+ .isEqualTo(fpLocked
+ ? BIOMETRIC_STATE_CANCELLING : BIOMETRIC_STATE_CANCELLING_RESTARTING);
}
@Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthControllerTest.java
index ace0ccb..489efd71 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthControllerTest.java
@@ -25,6 +25,7 @@
import static com.google.common.truth.Truth.assertThat;
import static junit.framework.Assert.assertEquals;
+import static junit.framework.Assert.assertNotSame;
import static junit.framework.Assert.assertNull;
import static org.mockito.ArgumentMatchers.any;
@@ -33,6 +34,7 @@
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.inOrder;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
@@ -49,6 +51,7 @@
import android.content.Intent;
import android.content.pm.PackageManager;
import android.content.res.Configuration;
+import android.content.res.Resources;
import android.graphics.Point;
import android.hardware.biometrics.BiometricAuthenticator;
import android.hardware.biometrics.BiometricConstants;
@@ -166,6 +169,8 @@
private ArgumentCaptor<StatusBarStateController.StateListener> mStatusBarStateListenerCaptor;
@Captor
private ArgumentCaptor<WakefulnessLifecycle.Observer> mWakefullnessObserverCaptor;
+ @Mock
+ private Resources mResources;
private TestableContext mContextSpy;
private Execution mExecution;
@@ -879,6 +884,25 @@
);
}
+ @Test
+ public void testUpdateFingerprintLocation_defaultPointChanges_whenConfigChanges() {
+ when(mContextSpy.getResources()).thenReturn(mResources);
+
+ doReturn(500).when(mResources)
+ .getDimensionPixelSize(eq(com.android.systemui.R.dimen
+ .physical_fingerprint_sensor_center_screen_location_y));
+ mAuthController.onConfigurationChanged(null /* newConfig */);
+
+ final Point firstFpLocation = mAuthController.getFingerprintSensorLocation();
+
+ doReturn(1000).when(mResources)
+ .getDimensionPixelSize(eq(com.android.systemui.R.dimen
+ .physical_fingerprint_sensor_center_screen_location_y));
+ mAuthController.onConfigurationChanged(null /* newConfig */);
+
+ assertNotSame(firstFpLocation, mAuthController.getFingerprintSensorLocation());
+ }
+
private void showDialog(int[] sensorIds, boolean credentialAllowed) {
mAuthController.showAuthenticationDialog(createTestPromptInfo(),
mReceiver /* receiver */,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/classifier/BrightLineClassifierTest.java b/packages/SystemUI/tests/src/com/android/systemui/classifier/BrightLineClassifierTest.java
index e4df754..8cb9130 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/classifier/BrightLineClassifierTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/classifier/BrightLineClassifierTest.java
@@ -106,7 +106,7 @@
mClassifiers.add(mClassifierB);
when(mFalsingDataProvider.getRecentMotionEvents()).thenReturn(mMotionEventList);
when(mKeyguardStateController.isShowing()).thenReturn(true);
- when(mFalsingDataProvider.isFolded()).thenReturn(true);
+ when(mFalsingDataProvider.isUnfolded()).thenReturn(false);
mBrightLineFalsingManager = new BrightLineFalsingManager(mFalsingDataProvider,
mMetricsLogger, mClassifiers, mSingleTapClassfier, mLongTapClassifier,
mDoubleTapClassifier, mHistoryTracker, mKeyguardStateController,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/classifier/BrightLineFalsingManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/classifier/BrightLineFalsingManagerTest.java
index ae38eb6..315774a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/classifier/BrightLineFalsingManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/classifier/BrightLineFalsingManagerTest.java
@@ -89,7 +89,7 @@
mClassifiers.add(mClassifierA);
when(mFalsingDataProvider.getRecentMotionEvents()).thenReturn(mMotionEventList);
when(mKeyguardStateController.isShowing()).thenReturn(true);
- when(mFalsingDataProvider.isFolded()).thenReturn(true);
+ when(mFalsingDataProvider.isUnfolded()).thenReturn(false);
mBrightLineFalsingManager = new BrightLineFalsingManager(mFalsingDataProvider,
mMetricsLogger, mClassifiers, mSingleTapClassifier, mLongTapClassifier,
mDoubleTapClassifier, mHistoryTracker, mKeyguardStateController,
@@ -185,7 +185,7 @@
@Test
public void testSkipUnfolded() {
assertThat(mBrightLineFalsingManager.isFalseTouch(Classifier.GENERIC)).isTrue();
- when(mFalsingDataProvider.isFolded()).thenReturn(false);
+ when(mFalsingDataProvider.isUnfolded()).thenReturn(true);
assertThat(mBrightLineFalsingManager.isFalseTouch(Classifier.GENERIC)).isFalse();
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/classifier/FalsingDataProviderTest.java b/packages/SystemUI/tests/src/com/android/systemui/classifier/FalsingDataProviderTest.java
index c451a1e7..2edc3d3 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/classifier/FalsingDataProviderTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/classifier/FalsingDataProviderTest.java
@@ -324,12 +324,18 @@
@Test
public void test_FoldedState_Folded() {
when(mFoldStateListener.getFolded()).thenReturn(true);
- assertThat(mDataProvider.isFolded()).isTrue();
+ assertThat(mDataProvider.isUnfolded()).isFalse();
}
@Test
public void test_FoldedState_Unfolded() {
when(mFoldStateListener.getFolded()).thenReturn(false);
- assertThat(mDataProvider.isFolded()).isFalse();
+ assertThat(mDataProvider.isUnfolded()).isTrue();
+ }
+
+ @Test
+ public void test_FoldedState_NotFoldable() {
+ when(mFoldStateListener.getFolded()).thenReturn(null);
+ assertThat(mDataProvider.isUnfolded()).isFalse();
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/controls/ui/ControlsUiControllerImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/controls/ui/ControlsUiControllerImplTest.kt
index 85f9961..aa90e2a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/controls/ui/ControlsUiControllerImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/controls/ui/ControlsUiControllerImplTest.kt
@@ -29,6 +29,7 @@
import android.util.AttributeSet
import android.view.LayoutInflater
import android.view.View
+import android.view.ViewGroup
import android.widget.FrameLayout
import androidx.test.filters.SmallTest
import com.android.systemui.R
@@ -328,7 +329,7 @@
)
.isTrue()
- underTest.hide()
+ underTest.hide(parent)
clearInvocations(controlsListingController, taskViewFactory)
controlsSettingsRepository.setAllowActionOnTrivialControlsInLockscreen(false)
@@ -387,6 +388,28 @@
assertThat(underTest.resolveActivity()).isEqualTo(ControlsActivity::class.java)
}
+ @Test
+ fun testRemoveViewsOnlyForParentPassedInHide() {
+ underTest.show(parent, {}, context)
+ parent.addView(View(context))
+
+ val mockParent: ViewGroup = mock()
+
+ underTest.hide(mockParent)
+
+ verify(mockParent).removeAllViews()
+ assertThat(parent.childCount).isGreaterThan(0)
+ }
+
+ @Test
+ fun testHideDifferentParentDoesntCancelListeners() {
+ underTest.show(parent, {}, context)
+ underTest.hide(mock())
+
+ verify(controlsController, never()).unsubscribe()
+ verify(controlsListingController, never()).removeCallback(any())
+ }
+
private fun setUpPanel(panel: SelectedItem.PanelItem): ControlsServiceInfo {
val activity = ComponentName("pkg", "activity")
sharedPreferences
diff --git a/packages/SystemUI/tests/src/com/android/systemui/flags/ServerFlagReaderImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/flags/ServerFlagReaderImplTest.kt
index a12315b..2e98006 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/flags/ServerFlagReaderImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/flags/ServerFlagReaderImplTest.kt
@@ -26,6 +26,7 @@
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.Mock
+import org.mockito.Mockito.never
import org.mockito.Mockito.verify
import org.mockito.MockitoAnnotations
@@ -58,4 +59,16 @@
verify(changeListener).onChange(flag)
}
+
+ @Test
+ fun testChange_ignoresListenersDuringTest() {
+ val serverFlagReader = ServerFlagReaderImpl(NAMESPACE, deviceConfig, executor, true)
+ val flag = ReleasedFlag(1, "1", "test")
+ serverFlagReader.listenForChanges(listOf(flag), changeListener)
+
+ deviceConfig.setProperty(NAMESPACE, "flag_override_1", "1", false)
+ executor.runAllReady()
+
+ verify(changeListener, never()).onChange(flag)
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/CustomizationProviderTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/CustomizationProviderTest.kt
index 15a454b..a4e5bca 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/CustomizationProviderTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/CustomizationProviderTest.kt
@@ -17,6 +17,7 @@
package com.android.systemui.keyguard
+import android.app.admin.DevicePolicyManager
import android.content.ContentValues
import android.content.pm.PackageManager
import android.content.pm.ProviderInfo
@@ -61,7 +62,6 @@
import com.android.systemui.util.settings.FakeSettings
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.ExperimentalCoroutinesApi
-import kotlinx.coroutines.test.StandardTestDispatcher
import kotlinx.coroutines.test.TestScope
import kotlinx.coroutines.test.UnconfinedTestDispatcher
import kotlinx.coroutines.test.runTest
@@ -90,6 +90,7 @@
@Mock private lateinit var previewSurfacePackage: SurfaceControlViewHost.SurfacePackage
@Mock private lateinit var launchAnimator: DialogLaunchAnimator
@Mock private lateinit var commandQueue: CommandQueue
+ @Mock private lateinit var devicePolicyManager: DevicePolicyManager
private lateinit var underTest: CustomizationProvider
private lateinit var testScope: TestScope
@@ -102,7 +103,7 @@
whenever(backgroundHandler.looper).thenReturn(TestableLooper.get(this).looper)
underTest = CustomizationProvider()
- val testDispatcher = StandardTestDispatcher()
+ val testDispatcher = UnconfinedTestDispatcher()
testScope = TestScope(testDispatcher)
val localUserSelectionManager =
KeyguardQuickAffordanceLocalUserSelectionManager(
@@ -183,6 +184,8 @@
featureFlags = featureFlags,
repository = { quickAffordanceRepository },
launchAnimator = launchAnimator,
+ devicePolicyManager = devicePolicyManager,
+ backgroundDispatcher = testDispatcher,
)
underTest.previewManager =
KeyguardRemotePreviewManager(
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardFaceAuthManagerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardFaceAuthManagerTest.kt
new file mode 100644
index 0000000..7c604f7
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardFaceAuthManagerTest.kt
@@ -0,0 +1,428 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.keyguard.data.repository
+
+import android.app.StatusBarManager.SESSION_KEYGUARD
+import android.content.pm.UserInfo
+import android.hardware.biometrics.BiometricFaceConstants.FACE_ERROR_CANCELED
+import android.hardware.biometrics.BiometricFaceConstants.FACE_ERROR_LOCKOUT_PERMANENT
+import android.hardware.biometrics.ComponentInfoInternal
+import android.hardware.face.FaceManager
+import android.hardware.face.FaceSensorProperties
+import android.hardware.face.FaceSensorPropertiesInternal
+import android.os.CancellationSignal
+import androidx.test.filters.SmallTest
+import com.android.internal.logging.InstanceId.fakeInstanceId
+import com.android.internal.logging.UiEventLogger
+import com.android.keyguard.FaceAuthUiEvent
+import com.android.keyguard.FaceAuthUiEvent.FACE_AUTH_TRIGGERED_ALTERNATE_BIOMETRIC_BOUNCER_SHOWN
+import com.android.keyguard.FaceAuthUiEvent.FACE_AUTH_TRIGGERED_SWIPE_UP_ON_BOUNCER
+import com.android.systemui.R
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.coroutines.FlowValue
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.dump.DumpManager
+import com.android.systemui.dump.logcatLogBuffer
+import com.android.systemui.keyguard.shared.model.AuthenticationStatus
+import com.android.systemui.keyguard.shared.model.DetectionStatus
+import com.android.systemui.keyguard.shared.model.ErrorAuthenticationStatus
+import com.android.systemui.keyguard.shared.model.HelpAuthenticationStatus
+import com.android.systemui.keyguard.shared.model.SuccessAuthenticationStatus
+import com.android.systemui.log.FaceAuthenticationLogger
+import com.android.systemui.log.SessionTracker
+import com.android.systemui.statusbar.phone.KeyguardBypassController
+import com.android.systemui.user.data.repository.FakeUserRepository
+import com.android.systemui.util.mockito.whenever
+import com.google.common.truth.Truth.assertThat
+import java.io.PrintWriter
+import java.io.StringWriter
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.StandardTestDispatcher
+import kotlinx.coroutines.test.TestDispatcher
+import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.advanceTimeBy
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+import org.mockito.ArgumentCaptor
+import org.mockito.ArgumentMatchers.any
+import org.mockito.ArgumentMatchers.anyInt
+import org.mockito.ArgumentMatchers.eq
+import org.mockito.Captor
+import org.mockito.Mock
+import org.mockito.Mockito.clearInvocations
+import org.mockito.Mockito.isNull
+import org.mockito.Mockito.never
+import org.mockito.Mockito.verify
+import org.mockito.Mockito.verifyNoMoreInteractions
+import org.mockito.MockitoAnnotations
+
+@OptIn(ExperimentalCoroutinesApi::class)
+@SmallTest
+@RunWith(JUnit4::class)
+class KeyguardFaceAuthManagerTest : SysuiTestCase() {
+ private lateinit var underTest: KeyguardFaceAuthManagerImpl
+
+ @Mock private lateinit var faceManager: FaceManager
+ @Mock private lateinit var bypassController: KeyguardBypassController
+ @Mock private lateinit var sessionTracker: SessionTracker
+ @Mock private lateinit var uiEventLogger: UiEventLogger
+ @Mock private lateinit var dumpManager: DumpManager
+
+ @Captor
+ private lateinit var authenticationCallback: ArgumentCaptor<FaceManager.AuthenticationCallback>
+ @Captor
+ private lateinit var detectionCallback: ArgumentCaptor<FaceManager.FaceDetectionCallback>
+ @Captor private lateinit var cancellationSignal: ArgumentCaptor<CancellationSignal>
+ @Captor
+ private lateinit var faceLockoutResetCallback: ArgumentCaptor<FaceManager.LockoutResetCallback>
+ private lateinit var testDispatcher: TestDispatcher
+
+ private lateinit var testScope: TestScope
+ private lateinit var fakeUserRepository: FakeUserRepository
+ private lateinit var authStatus: FlowValue<AuthenticationStatus?>
+ private lateinit var detectStatus: FlowValue<DetectionStatus?>
+ private lateinit var authRunning: FlowValue<Boolean?>
+ private lateinit var lockedOut: FlowValue<Boolean?>
+
+ @Before
+ fun setup() {
+ MockitoAnnotations.initMocks(this)
+ fakeUserRepository = FakeUserRepository()
+ fakeUserRepository.setUserInfos(listOf(currentUser))
+ testDispatcher = StandardTestDispatcher()
+ testScope = TestScope(testDispatcher)
+ whenever(sessionTracker.getSessionId(SESSION_KEYGUARD)).thenReturn(keyguardSessionId)
+ whenever(bypassController.bypassEnabled).thenReturn(true)
+ underTest = createFaceAuthManagerImpl(faceManager)
+ }
+
+ private fun createFaceAuthManagerImpl(
+ fmOverride: FaceManager? = faceManager,
+ bypassControllerOverride: KeyguardBypassController? = bypassController
+ ) =
+ KeyguardFaceAuthManagerImpl(
+ mContext,
+ fmOverride,
+ fakeUserRepository,
+ bypassControllerOverride,
+ testScope.backgroundScope,
+ testDispatcher,
+ sessionTracker,
+ uiEventLogger,
+ FaceAuthenticationLogger(logcatLogBuffer("KeyguardFaceAuthManagerLog")),
+ dumpManager,
+ )
+
+ @Test
+ fun faceAuthRunsAndProvidesAuthStatusUpdates() =
+ testScope.runTest {
+ testSetup(this)
+
+ FACE_AUTH_TRIGGERED_SWIPE_UP_ON_BOUNCER.extraInfo = 10
+ underTest.authenticate(FACE_AUTH_TRIGGERED_SWIPE_UP_ON_BOUNCER)
+ faceAuthenticateIsCalled()
+ uiEventIsLogged(FACE_AUTH_TRIGGERED_SWIPE_UP_ON_BOUNCER)
+
+ assertThat(authRunning()).isTrue()
+
+ val successResult = successResult()
+ authenticationCallback.value.onAuthenticationSucceeded(successResult)
+
+ assertThat(authStatus()).isEqualTo(SuccessAuthenticationStatus(successResult))
+
+ assertThat(authRunning()).isFalse()
+ }
+
+ private fun uiEventIsLogged(faceAuthUiEvent: FaceAuthUiEvent) {
+ verify(uiEventLogger)
+ .logWithInstanceIdAndPosition(
+ faceAuthUiEvent,
+ 0,
+ null,
+ keyguardSessionId,
+ faceAuthUiEvent.extraInfo
+ )
+ }
+
+ @Test
+ fun faceAuthDoesNotRunWhileItIsAlreadyRunning() =
+ testScope.runTest {
+ testSetup(this)
+
+ underTest.authenticate(FACE_AUTH_TRIGGERED_SWIPE_UP_ON_BOUNCER)
+ faceAuthenticateIsCalled()
+ clearInvocations(faceManager)
+ clearInvocations(uiEventLogger)
+
+ underTest.authenticate(FACE_AUTH_TRIGGERED_SWIPE_UP_ON_BOUNCER)
+ verifyNoMoreInteractions(faceManager)
+ verifyNoMoreInteractions(uiEventLogger)
+ }
+
+ @Test
+ fun faceLockoutStatusIsPropagated() =
+ testScope.runTest {
+ testSetup(this)
+ verify(faceManager).addLockoutResetCallback(faceLockoutResetCallback.capture())
+
+ underTest.authenticate(FACE_AUTH_TRIGGERED_SWIPE_UP_ON_BOUNCER)
+ faceAuthenticateIsCalled()
+
+ authenticationCallback.value.onAuthenticationError(
+ FACE_ERROR_LOCKOUT_PERMANENT,
+ "face locked out"
+ )
+
+ assertThat(lockedOut()).isTrue()
+
+ faceLockoutResetCallback.value.onLockoutReset(0)
+ assertThat(lockedOut()).isFalse()
+ }
+
+ @Test
+ fun faceDetectionSupportIsTheCorrectValue() =
+ testScope.runTest {
+ assertThat(createFaceAuthManagerImpl(fmOverride = null).isDetectionSupported).isFalse()
+
+ whenever(faceManager.sensorPropertiesInternal).thenReturn(null)
+ assertThat(createFaceAuthManagerImpl().isDetectionSupported).isFalse()
+
+ whenever(faceManager.sensorPropertiesInternal).thenReturn(listOf())
+ assertThat(createFaceAuthManagerImpl().isDetectionSupported).isFalse()
+
+ whenever(faceManager.sensorPropertiesInternal)
+ .thenReturn(listOf(createFaceSensorProperties(supportsFaceDetection = false)))
+ assertThat(createFaceAuthManagerImpl().isDetectionSupported).isFalse()
+
+ whenever(faceManager.sensorPropertiesInternal)
+ .thenReturn(
+ listOf(
+ createFaceSensorProperties(supportsFaceDetection = false),
+ createFaceSensorProperties(supportsFaceDetection = true)
+ )
+ )
+ assertThat(createFaceAuthManagerImpl().isDetectionSupported).isFalse()
+
+ whenever(faceManager.sensorPropertiesInternal)
+ .thenReturn(
+ listOf(
+ createFaceSensorProperties(supportsFaceDetection = true),
+ createFaceSensorProperties(supportsFaceDetection = false)
+ )
+ )
+ assertThat(createFaceAuthManagerImpl().isDetectionSupported).isTrue()
+ }
+
+ @Test
+ fun cancelStopsFaceAuthentication() =
+ testScope.runTest {
+ testSetup(this)
+
+ underTest.authenticate(FACE_AUTH_TRIGGERED_SWIPE_UP_ON_BOUNCER)
+ faceAuthenticateIsCalled()
+
+ var wasAuthCancelled = false
+ cancellationSignal.value.setOnCancelListener { wasAuthCancelled = true }
+
+ underTest.cancel()
+ assertThat(wasAuthCancelled).isTrue()
+ assertThat(authRunning()).isFalse()
+ }
+
+ @Test
+ fun cancelInvokedWithoutFaceAuthRunningIsANoop() = testScope.runTest { underTest.cancel() }
+
+ @Test
+ fun faceDetectionRunsAndPropagatesDetectionStatus() =
+ testScope.runTest {
+ whenever(faceManager.sensorPropertiesInternal)
+ .thenReturn(listOf(createFaceSensorProperties(supportsFaceDetection = true)))
+ underTest = createFaceAuthManagerImpl()
+ testSetup(this)
+
+ underTest.detect()
+ faceDetectIsCalled()
+
+ detectionCallback.value.onFaceDetected(1, 1, true)
+
+ assertThat(detectStatus()).isEqualTo(DetectionStatus(1, 1, true))
+ }
+
+ @Test
+ fun faceDetectDoesNotRunIfDetectionIsNotSupported() =
+ testScope.runTest {
+ whenever(faceManager.sensorPropertiesInternal)
+ .thenReturn(listOf(createFaceSensorProperties(supportsFaceDetection = false)))
+ underTest = createFaceAuthManagerImpl()
+ testSetup(this)
+ clearInvocations(faceManager)
+
+ underTest.detect()
+
+ verify(faceManager, never()).detectFace(any(), any(), anyInt())
+ }
+
+ @Test
+ fun faceAuthShouldWaitAndRunIfTriggeredWhileCancelling() =
+ testScope.runTest {
+ testSetup(this)
+
+ underTest.authenticate(FACE_AUTH_TRIGGERED_SWIPE_UP_ON_BOUNCER)
+ faceAuthenticateIsCalled()
+
+ // Enter cancelling state
+ underTest.cancel()
+ clearInvocations(faceManager)
+
+ // Auth is while cancelling.
+ underTest.authenticate(FACE_AUTH_TRIGGERED_ALTERNATE_BIOMETRIC_BOUNCER_SHOWN)
+ // Auth is not started
+ verifyNoMoreInteractions(faceManager)
+
+ // Auth is done cancelling.
+ authenticationCallback.value.onAuthenticationError(
+ FACE_ERROR_CANCELED,
+ "First auth attempt cancellation completed"
+ )
+ assertThat(authStatus())
+ .isEqualTo(
+ ErrorAuthenticationStatus(
+ FACE_ERROR_CANCELED,
+ "First auth attempt cancellation completed"
+ )
+ )
+
+ faceAuthenticateIsCalled()
+ uiEventIsLogged(FACE_AUTH_TRIGGERED_ALTERNATE_BIOMETRIC_BOUNCER_SHOWN)
+ }
+
+ @Test
+ fun faceAuthAutoCancelsAfterDefaultCancellationTimeout() =
+ testScope.runTest {
+ testSetup(this)
+
+ underTest.authenticate(FACE_AUTH_TRIGGERED_SWIPE_UP_ON_BOUNCER)
+ faceAuthenticateIsCalled()
+
+ clearInvocations(faceManager)
+ underTest.cancel()
+ advanceTimeBy(KeyguardFaceAuthManagerImpl.DEFAULT_CANCEL_SIGNAL_TIMEOUT + 1)
+
+ underTest.authenticate(FACE_AUTH_TRIGGERED_SWIPE_UP_ON_BOUNCER)
+ faceAuthenticateIsCalled()
+ }
+
+ @Test
+ fun faceHelpMessagesAreIgnoredBasedOnConfig() =
+ testScope.runTest {
+ overrideResource(
+ R.array.config_face_acquire_device_entry_ignorelist,
+ intArrayOf(10, 11)
+ )
+ underTest = createFaceAuthManagerImpl()
+ testSetup(this)
+
+ underTest.authenticate(FACE_AUTH_TRIGGERED_SWIPE_UP_ON_BOUNCER)
+ faceAuthenticateIsCalled()
+
+ authenticationCallback.value.onAuthenticationHelp(9, "help msg")
+ authenticationCallback.value.onAuthenticationHelp(10, "Ignored help msg")
+ authenticationCallback.value.onAuthenticationHelp(11, "Ignored help msg")
+
+ assertThat(authStatus()).isEqualTo(HelpAuthenticationStatus(9, "help msg"))
+ }
+
+ @Test
+ fun dumpDoesNotErrorOutWhenFaceManagerOrBypassControllerIsNull() =
+ testScope.runTest {
+ fakeUserRepository.setSelectedUserInfo(currentUser)
+ underTest.dump(PrintWriter(StringWriter()), emptyArray())
+
+ underTest =
+ createFaceAuthManagerImpl(fmOverride = null, bypassControllerOverride = null)
+ fakeUserRepository.setSelectedUserInfo(currentUser)
+
+ underTest.dump(PrintWriter(StringWriter()), emptyArray())
+ }
+
+ private suspend fun testSetup(testScope: TestScope) {
+ with(testScope) {
+ authStatus = collectLastValue(underTest.authenticationStatus)
+ detectStatus = collectLastValue(underTest.detectionStatus)
+ authRunning = collectLastValue(underTest.isAuthRunning)
+ lockedOut = collectLastValue(underTest.isLockedOut)
+ fakeUserRepository.setSelectedUserInfo(currentUser)
+ }
+ }
+
+ private fun successResult() = FaceManager.AuthenticationResult(null, null, currentUserId, false)
+
+ private fun faceDetectIsCalled() {
+ verify(faceManager)
+ .detectFace(
+ cancellationSignal.capture(),
+ detectionCallback.capture(),
+ eq(currentUserId)
+ )
+ }
+
+ private fun faceAuthenticateIsCalled() {
+ verify(faceManager)
+ .authenticate(
+ isNull(),
+ cancellationSignal.capture(),
+ authenticationCallback.capture(),
+ isNull(),
+ eq(currentUserId),
+ eq(true)
+ )
+ }
+
+ private fun createFaceSensorProperties(
+ supportsFaceDetection: Boolean
+ ): FaceSensorPropertiesInternal {
+ val componentInfo =
+ listOf(
+ ComponentInfoInternal(
+ "faceSensor" /* componentId */,
+ "vendor/model/revision" /* hardwareVersion */,
+ "1.01" /* firmwareVersion */,
+ "00000001" /* serialNumber */,
+ "" /* softwareVersion */
+ )
+ )
+ return FaceSensorPropertiesInternal(
+ 0 /* id */,
+ FaceSensorProperties.STRENGTH_STRONG,
+ 1 /* maxTemplatesAllowed */,
+ componentInfo,
+ FaceSensorProperties.TYPE_UNKNOWN,
+ supportsFaceDetection /* supportsFaceDetection */,
+ true /* supportsSelfIllumination */,
+ false /* resetLockoutRequiresChallenge */
+ )
+ }
+
+ companion object {
+ const val currentUserId = 1
+ val keyguardSessionId = fakeInstanceId(10)!!
+ val currentUser = UserInfo(currentUserId, "test user", 0)
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorParameterizedTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorParameterizedTest.kt
index 23e06ec..84ec125 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorParameterizedTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorParameterizedTest.kt
@@ -17,6 +17,7 @@
package com.android.systemui.keyguard.domain.interactor
+import android.app.admin.DevicePolicyManager
import android.content.Intent
import android.os.UserHandle
import androidx.test.filters.SmallTest
@@ -54,7 +55,10 @@
import com.android.systemui.util.settings.FakeSettings
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
-import kotlinx.coroutines.test.runBlockingTest
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.StandardTestDispatcher
+import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.runTest
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
@@ -70,6 +74,7 @@
import org.mockito.Mockito.verifyZeroInteractions
import org.mockito.MockitoAnnotations
+@OptIn(ExperimentalCoroutinesApi::class)
@SmallTest
@RunWith(Parameterized::class)
class KeyguardQuickAffordanceInteractorParameterizedTest : SysuiTestCase() {
@@ -219,8 +224,10 @@
@Mock private lateinit var expandable: Expandable
@Mock private lateinit var launchAnimator: DialogLaunchAnimator
@Mock private lateinit var commandQueue: CommandQueue
+ @Mock private lateinit var devicePolicyManager: DevicePolicyManager
private lateinit var underTest: KeyguardQuickAffordanceInteractor
+ private lateinit var testScope: TestScope
@JvmField @Parameter(0) var needStrongAuthAfterBoot: Boolean = false
@JvmField @Parameter(1) var canShowWhileLocked: Boolean = false
@@ -292,6 +299,8 @@
set(Flags.CUSTOMIZABLE_LOCK_SCREEN_QUICK_AFFORDANCES, false)
set(Flags.FACE_AUTH_REFACTOR, true)
}
+ val testDispatcher = StandardTestDispatcher()
+ testScope = TestScope(testDispatcher)
underTest =
KeyguardQuickAffordanceInteractor(
keyguardInteractor =
@@ -322,58 +331,61 @@
featureFlags = featureFlags,
repository = { quickAffordanceRepository },
launchAnimator = launchAnimator,
+ devicePolicyManager = devicePolicyManager,
+ backgroundDispatcher = testDispatcher,
)
}
@Test
- fun onQuickAffordanceTriggered() = runBlockingTest {
- setUpMocks(
- needStrongAuthAfterBoot = needStrongAuthAfterBoot,
- keyguardIsUnlocked = keyguardIsUnlocked,
- )
+ fun onQuickAffordanceTriggered() =
+ testScope.runTest {
+ setUpMocks(
+ needStrongAuthAfterBoot = needStrongAuthAfterBoot,
+ keyguardIsUnlocked = keyguardIsUnlocked,
+ )
- homeControls.setState(
- lockScreenState =
- KeyguardQuickAffordanceConfig.LockScreenState.Visible(
- icon = DRAWABLE,
- )
- )
- homeControls.onTriggeredResult =
+ homeControls.setState(
+ lockScreenState =
+ KeyguardQuickAffordanceConfig.LockScreenState.Visible(
+ icon = DRAWABLE,
+ )
+ )
+ homeControls.onTriggeredResult =
+ if (startActivity) {
+ KeyguardQuickAffordanceConfig.OnTriggeredResult.StartActivity(
+ intent = INTENT,
+ canShowWhileLocked = canShowWhileLocked,
+ )
+ } else {
+ KeyguardQuickAffordanceConfig.OnTriggeredResult.Handled
+ }
+
+ underTest.onQuickAffordanceTriggered(
+ configKey = BuiltInKeyguardQuickAffordanceKeys.HOME_CONTROLS,
+ expandable = expandable,
+ )
+
if (startActivity) {
- KeyguardQuickAffordanceConfig.OnTriggeredResult.StartActivity(
- intent = INTENT,
- canShowWhileLocked = canShowWhileLocked,
- )
+ if (needsToUnlockFirst) {
+ verify(activityStarter)
+ .postStartActivityDismissingKeyguard(
+ any(),
+ /* delay= */ eq(0),
+ same(animationController),
+ )
+ } else {
+ verify(activityStarter)
+ .startActivity(
+ any(),
+ /* dismissShade= */ eq(true),
+ same(animationController),
+ /* showOverLockscreenWhenLocked= */ eq(true),
+ )
+ }
} else {
- KeyguardQuickAffordanceConfig.OnTriggeredResult.Handled
+ verifyZeroInteractions(activityStarter)
}
-
- underTest.onQuickAffordanceTriggered(
- configKey = BuiltInKeyguardQuickAffordanceKeys.HOME_CONTROLS,
- expandable = expandable,
- )
-
- if (startActivity) {
- if (needsToUnlockFirst) {
- verify(activityStarter)
- .postStartActivityDismissingKeyguard(
- any(),
- /* delay= */ eq(0),
- same(animationController),
- )
- } else {
- verify(activityStarter)
- .startActivity(
- any(),
- /* dismissShade= */ eq(true),
- same(animationController),
- /* showOverLockscreenWhenLocked= */ eq(true),
- )
- }
- } else {
- verifyZeroInteractions(activityStarter)
}
- }
private fun setUpMocks(
needStrongAuthAfterBoot: Boolean = true,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorTest.kt
index 1b8c627..62c9e5f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorTest.kt
@@ -17,6 +17,7 @@
package com.android.systemui.keyguard.domain.interactor
+import android.app.admin.DevicePolicyManager
import android.os.UserHandle
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
@@ -78,6 +79,7 @@
@Mock private lateinit var activityStarter: ActivityStarter
@Mock private lateinit var launchAnimator: DialogLaunchAnimator
@Mock private lateinit var commandQueue: CommandQueue
+ @Mock private lateinit var devicePolicyManager: DevicePolicyManager
private lateinit var underTest: KeyguardQuickAffordanceInteractor
@@ -184,6 +186,8 @@
featureFlags = featureFlags,
repository = { quickAffordanceRepository },
launchAnimator = launchAnimator,
+ devicePolicyManager = devicePolicyManager,
+ backgroundDispatcher = testDispatcher,
)
}
@@ -239,6 +243,44 @@
}
@Test
+ fun `quickAffordance - hidden when all features are disabled by device policy`() =
+ testScope.runTest {
+ whenever(devicePolicyManager.getKeyguardDisabledFeatures(null, userTracker.userId))
+ .thenReturn(DevicePolicyManager.KEYGUARD_DISABLE_FEATURES_ALL)
+ quickAccessWallet.setState(
+ KeyguardQuickAffordanceConfig.LockScreenState.Visible(
+ icon = ICON,
+ )
+ )
+
+ val collectedValue by
+ collectLastValue(
+ underTest.quickAffordance(KeyguardQuickAffordancePosition.BOTTOM_END)
+ )
+
+ assertThat(collectedValue).isInstanceOf(KeyguardQuickAffordanceModel.Hidden::class.java)
+ }
+
+ @Test
+ fun `quickAffordance - hidden when shortcuts feature is disabled by device policy`() =
+ testScope.runTest {
+ whenever(devicePolicyManager.getKeyguardDisabledFeatures(null, userTracker.userId))
+ .thenReturn(DevicePolicyManager.KEYGUARD_DISABLE_SHORTCUTS_ALL)
+ quickAccessWallet.setState(
+ KeyguardQuickAffordanceConfig.LockScreenState.Visible(
+ icon = ICON,
+ )
+ )
+
+ val collectedValue by
+ collectLastValue(
+ underTest.quickAffordance(KeyguardQuickAffordancePosition.BOTTOM_END)
+ )
+
+ assertThat(collectedValue).isInstanceOf(KeyguardQuickAffordanceModel.Hidden::class.java)
+ }
+
+ @Test
fun `quickAffordance - bottom start affordance hidden while dozing`() =
testScope.runTest {
repository.setDozing(true)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBottomAreaViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBottomAreaViewModelTest.kt
index 6afeddd..8bd8be5 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBottomAreaViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBottomAreaViewModelTest.kt
@@ -16,6 +16,7 @@
package com.android.systemui.keyguard.ui.viewmodel
+import android.app.admin.DevicePolicyManager
import android.content.Intent
import android.os.UserHandle
import androidx.test.filters.SmallTest
@@ -87,6 +88,7 @@
@Mock private lateinit var activityStarter: ActivityStarter
@Mock private lateinit var launchAnimator: DialogLaunchAnimator
@Mock private lateinit var commandQueue: CommandQueue
+ @Mock private lateinit var devicePolicyManager: DevicePolicyManager
private lateinit var underTest: KeyguardBottomAreaViewModel
@@ -140,6 +142,7 @@
bouncerRepository = FakeKeyguardBouncerRepository(),
)
whenever(userTracker.userHandle).thenReturn(mock())
+ whenever(userTracker.userId).thenReturn(10)
whenever(lockPatternUtils.getStrongAuthForUser(anyInt()))
.thenReturn(LockPatternUtils.StrongAuthTracker.STRONG_AUTH_NOT_REQUIRED)
val testDispatcher = StandardTestDispatcher()
@@ -205,6 +208,8 @@
featureFlags = featureFlags,
repository = { quickAffordanceRepository },
launchAnimator = launchAnimator,
+ devicePolicyManager = devicePolicyManager,
+ backgroundDispatcher = testDispatcher,
),
bottomAreaInteractor = KeyguardBottomAreaInteractor(repository = repository),
burnInHelperWrapper = burnInHelperWrapper,
@@ -240,6 +245,39 @@
}
@Test
+ fun `startButton - hidden when device policy disables all keyguard features`() =
+ testScope.runTest {
+ whenever(devicePolicyManager.getKeyguardDisabledFeatures(null, userTracker.userId))
+ .thenReturn(DevicePolicyManager.KEYGUARD_DISABLE_FEATURES_ALL)
+ repository.setKeyguardShowing(true)
+ val latest by collectLastValue(underTest.startButton)
+
+ val testConfig =
+ TestConfig(
+ isVisible = true,
+ isClickable = true,
+ isActivated = true,
+ icon = mock(),
+ canShowWhileLocked = false,
+ intent = Intent("action"),
+ )
+ val configKey =
+ setUpQuickAffordanceModel(
+ position = KeyguardQuickAffordancePosition.BOTTOM_START,
+ testConfig = testConfig,
+ )
+
+ assertQuickAffordanceViewModel(
+ viewModel = latest,
+ testConfig =
+ TestConfig(
+ isVisible = false,
+ ),
+ configKey = configKey,
+ )
+ }
+
+ @Test
fun `startButton - in preview mode - visible even when keyguard not showing`() =
testScope.runTest {
underTest.enablePreviewMode(
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelControllerTest.kt
index 6cf642c..09156d6 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelControllerTest.kt
@@ -70,7 +70,7 @@
whenever(brightnessSliderFactory.create(any(), any())).thenReturn(brightnessSlider)
whenever(brightnessControllerFactory.create(any())).thenReturn(brightnessController)
- testableResources.addOverride(R.bool.config_use_split_notification_shade, false)
+ setShouldUseSplitShade(false)
whenever(qsPanel.resources).thenReturn(testableResources.resources)
whenever(qsPanel.getOrCreateTileLayout()).thenReturn(pagedTileLayout)
whenever(statusBarKeyguardViewManager.isPrimaryBouncerInTransit()).thenReturn(false)
@@ -133,12 +133,31 @@
@Test
fun configurationChange_onlySplitShadeConfigChanges_tileAreRedistributed() {
- testableResources.addOverride(R.bool.config_use_split_notification_shade, false)
+ setShouldUseSplitShade(false)
controller.mOnConfigurationChangedListener.onConfigurationChange(configuration)
verify(pagedTileLayout, never()).forceTilesRedistribution(any())
- testableResources.addOverride(R.bool.config_use_split_notification_shade, true)
+ setShouldUseSplitShade(true)
controller.mOnConfigurationChangedListener.onConfigurationChange(configuration)
verify(pagedTileLayout).forceTilesRedistribution("Split shade state changed")
}
+
+ @Test
+ fun configurationChange_onlySplitShadeConfigChanges_qsPanelCanBeCollapsed() {
+ setShouldUseSplitShade(false)
+ controller.mOnConfigurationChangedListener.onConfigurationChange(configuration)
+ verify(qsPanel, never()).setCanCollapse(anyBoolean())
+
+ setShouldUseSplitShade(true)
+ controller.mOnConfigurationChangedListener.onConfigurationChange(configuration)
+ verify(qsPanel).setCanCollapse(false)
+
+ setShouldUseSplitShade(false)
+ controller.mOnConfigurationChangedListener.onConfigurationChange(configuration)
+ verify(qsPanel).setCanCollapse(true)
+ }
+
+ private fun setShouldUseSplitShade(shouldUse: Boolean) {
+ testableResources.addOverride(R.bool.config_use_split_notification_shade, shouldUse)
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelTest.kt
index d52b296..a8cfb25 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelTest.kt
@@ -37,6 +37,7 @@
import org.junit.runner.RunWith
import org.mockito.Mock
import org.mockito.Mockito.mock
+import org.mockito.Mockito.never
import org.mockito.Mockito.verify
import org.mockito.MockitoAnnotations
@@ -196,6 +197,16 @@
qsPanel.setSquishinessFraction(0.5f)
}
+ @Test
+ fun testSplitShade_CollapseAccessibilityActionNotAnnounced() {
+ qsPanel.setCanCollapse(false)
+ val accessibilityInfo = mock(AccessibilityNodeInfo::class.java)
+ qsPanel.onInitializeAccessibilityNodeInfo(accessibilityInfo)
+
+ val actionCollapse = AccessibilityNodeInfo.AccessibilityAction.ACTION_COLLAPSE
+ verify(accessibilityInfo, never()).addAction(actionCollapse)
+ }
+
private infix fun View.isLeftOf(other: View): Boolean {
val rect = Rect()
getBoundsOnScreen(rect)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/FontScalingTileTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/FontScalingTileTest.kt
index 528978a..257d42a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/FontScalingTileTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/FontScalingTileTest.kt
@@ -18,21 +18,30 @@
import android.os.Handler
import android.testing.AndroidTestingRunner
import android.testing.TestableLooper
+import android.view.View
import androidx.test.filters.SmallTest
import com.android.internal.logging.MetricsLogger
+import com.android.internal.logging.UiEventLogger
import com.android.systemui.SysuiTestCase
import com.android.systemui.animation.DialogLaunchAnimator
import com.android.systemui.classifier.FalsingManagerFake
+import com.android.systemui.flags.FakeFeatureFlags
+import com.android.systemui.flags.Flags
import com.android.systemui.plugins.ActivityStarter
import com.android.systemui.plugins.statusbar.StatusBarStateController
import com.android.systemui.qs.QSTileHost
import com.android.systemui.qs.logging.QSLogger
+import com.android.systemui.util.mockito.any
+import com.android.systemui.util.mockito.eq
+import com.android.systemui.util.mockito.nullable
import com.android.systemui.util.settings.FakeSettings
import com.google.common.truth.Truth.assertThat
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.Mock
+import org.mockito.Mockito.anyBoolean
+import org.mockito.Mockito.verify
import org.mockito.Mockito.`when`
import org.mockito.MockitoAnnotations
@@ -46,15 +55,20 @@
@Mock private lateinit var activityStarter: ActivityStarter
@Mock private lateinit var qsLogger: QSLogger
@Mock private lateinit var dialogLaunchAnimator: DialogLaunchAnimator
+ @Mock private lateinit var uiEventLogger: UiEventLogger
private lateinit var testableLooper: TestableLooper
private lateinit var fontScalingTile: FontScalingTile
+ val featureFlags = FakeFeatureFlags()
+
@Before
fun setUp() {
MockitoAnnotations.initMocks(this)
testableLooper = TestableLooper.get(this)
`when`(qsHost.getContext()).thenReturn(mContext)
+ `when`(qsHost.uiEventLogger).thenReturn(uiEventLogger)
+
fontScalingTile =
FontScalingTile(
qsHost,
@@ -66,15 +80,37 @@
activityStarter,
qsLogger,
dialogLaunchAnimator,
- FakeSettings()
+ FakeSettings(),
+ featureFlags
)
fontScalingTile.initialize()
+ testableLooper.processAllMessages()
}
@Test
- fun isNotAvailable_whenNotSupportedDevice_returnsFalse() {
+ fun isAvailable_whenFlagIsFalse_returnsFalse() {
+ featureFlags.set(Flags.ENABLE_FONT_SCALING_TILE, false)
+
val isAvailable = fontScalingTile.isAvailable()
assertThat(isAvailable).isFalse()
}
+
+ @Test
+ fun isAvailable_whenFlagIsTrue_returnsTrue() {
+ featureFlags.set(Flags.ENABLE_FONT_SCALING_TILE, true)
+
+ val isAvailable = fontScalingTile.isAvailable()
+
+ assertThat(isAvailable).isTrue()
+ }
+
+ @Test
+ fun clickTile_showDialog() {
+ val view = View(context)
+ fontScalingTile.click(view)
+ testableLooper.processAllMessages()
+
+ verify(dialogLaunchAnimator).showFromView(any(), eq(view), nullable(), anyBoolean())
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/LargeScreenShadeHeaderControllerCombinedTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/LargeScreenShadeHeaderControllerCombinedTest.kt
index 91fef1d..ee5f61c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/LargeScreenShadeHeaderControllerCombinedTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/LargeScreenShadeHeaderControllerCombinedTest.kt
@@ -21,6 +21,7 @@
import android.content.res.XmlResourceParser
import android.graphics.Rect
import android.testing.AndroidTestingRunner
+import android.view.Display
import android.view.DisplayCutout
import android.view.View
import android.view.ViewPropertyAnimator
@@ -77,9 +78,11 @@
import org.mockito.Mockito.inOrder
import org.mockito.Mockito.never
import org.mockito.Mockito.reset
+import org.mockito.Mockito.same
+import org.mockito.Mockito.spy
import org.mockito.Mockito.verify
-import org.mockito.Mockito.`when` as whenever
import org.mockito.junit.MockitoJUnit
+import org.mockito.Mockito.`when` as whenever
private val EMPTY_CHANGES = ConstraintsChanges()
@@ -133,6 +136,7 @@
@Mock
private lateinit var mockedContext: Context
+ private lateinit var viewContext: Context
@Mock(answer = Answers.RETURNS_MOCKS)
private lateinit var view: MotionLayout
@@ -143,6 +147,7 @@
@Mock
private lateinit var largeScreenConstraints: ConstraintSet
@Mock private lateinit var demoModeController: DemoModeController
+ @Mock private lateinit var qsBatteryModeController: QsBatteryModeController
@JvmField @Rule
val mockitoRule = MockitoJUnit.rule()
@@ -175,7 +180,8 @@
.thenReturn(qsCarrierGroupControllerBuilder)
whenever(qsCarrierGroupControllerBuilder.build()).thenReturn(qsCarrierGroupController)
- whenever(view.context).thenReturn(context)
+ viewContext = spy(context)
+ whenever(view.context).thenReturn(viewContext)
whenever(view.resources).thenReturn(context.resources)
whenever(view.setVisibility(ArgumentMatchers.anyInt())).then {
viewVisibility = it.arguments[0] as Int
@@ -192,19 +198,20 @@
setUpMotionLayout(view)
controller = LargeScreenShadeHeaderController(
- view,
- statusBarIconController,
- iconManagerFactory,
- privacyIconsController,
- insetsProvider,
- configurationController,
- variableDateViewControllerFactory,
- batteryMeterViewController,
- dumpManager,
- featureFlags,
- qsCarrierGroupControllerBuilder,
- combinedShadeHeadersConstraintManager,
- demoModeController
+ view,
+ statusBarIconController,
+ iconManagerFactory,
+ privacyIconsController,
+ insetsProvider,
+ configurationController,
+ variableDateViewControllerFactory,
+ batteryMeterViewController,
+ dumpManager,
+ featureFlags,
+ qsCarrierGroupControllerBuilder,
+ combinedShadeHeadersConstraintManager,
+ demoModeController,
+ qsBatteryModeController,
)
whenever(view.isAttachedToWindow).thenReturn(true)
controller.init()
@@ -218,7 +225,6 @@
verify(batteryMeterViewController).init()
verify(batteryMeterViewController).ignoreTunerUpdates()
- verify(batteryMeterView).setPercentShowMode(BatteryMeterView.MODE_ESTIMATE)
val inOrder = inOrder(qsCarrierGroupControllerBuilder)
inOrder.verify(qsCarrierGroupControllerBuilder).setQSCarrierGroup(carrierGroup)
@@ -226,6 +232,23 @@
}
@Test
+ fun `battery mode controller called when qsExpandedFraction changes`() {
+ whenever(qsBatteryModeController.getBatteryMode(same(null), eq(0f)))
+ .thenReturn(BatteryMeterView.MODE_ON)
+ whenever(qsBatteryModeController.getBatteryMode(same(null), eq(1f)))
+ .thenReturn(BatteryMeterView.MODE_ESTIMATE)
+ controller.qsVisible = true
+
+ val times = 10
+ repeat(times) {
+ controller.qsExpandedFraction = it / (times - 1).toFloat()
+ }
+
+ verify(batteryMeterView).setPercentShowMode(BatteryMeterView.MODE_ON)
+ verify(batteryMeterView).setPercentShowMode(BatteryMeterView.MODE_ESTIMATE)
+ }
+
+ @Test
fun testClockPivotLtr() {
val width = 200
whenever(clock.width).thenReturn(width)
@@ -684,11 +707,11 @@
configurationController.notifyDensityOrFontScaleChanged()
val captor = ArgumentCaptor.forClass(XmlResourceParser::class.java)
- verify(qqsConstraints).load(eq(context), capture(captor))
+ verify(qqsConstraints).load(eq(viewContext), capture(captor))
assertThat(captor.value.getResId()).isEqualTo(R.xml.qqs_header)
- verify(qsConstraints).load(eq(context), capture(captor))
+ verify(qsConstraints).load(eq(viewContext), capture(captor))
assertThat(captor.value.getResId()).isEqualTo(R.xml.qs_header)
- verify(largeScreenConstraints).load(eq(context), capture(captor))
+ verify(largeScreenConstraints).load(eq(viewContext), capture(captor))
assertThat(captor.value.getResId()).isEqualTo(R.xml.large_screen_shade_header)
}
@@ -786,6 +809,13 @@
whenever(insetsProvider.getStatusBarContentInsetsForCurrentRotation())
.thenReturn(Pair(0, 0).toAndroidPair())
whenever(insetsProvider.currentRotationHasCornerCutout()).thenReturn(false)
+ setupCurrentInsets(null)
+ }
+
+ private fun setupCurrentInsets(cutout: DisplayCutout?) {
+ val mockedDisplay =
+ mock<Display>().also { display -> whenever(display.cutout).thenReturn(cutout) }
+ whenever(viewContext.display).thenReturn(mockedDisplay)
}
private fun<T, U> Pair<T, U>.toAndroidPair(): android.util.Pair<T, U> {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/LargeScreenShadeHeaderControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/LargeScreenShadeHeaderControllerTest.kt
index 2bf2a81..e684007 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/LargeScreenShadeHeaderControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/LargeScreenShadeHeaderControllerTest.kt
@@ -1,7 +1,6 @@
package com.android.systemui.shade
import android.animation.Animator
-import android.animation.ValueAnimator
import android.app.StatusBarManager
import android.content.Context
import android.testing.AndroidTestingRunner
@@ -44,6 +43,7 @@
import org.mockito.ArgumentMatchers.anyInt
import org.mockito.Mock
import org.mockito.Mockito.mock
+import org.mockito.Mockito.reset
import org.mockito.Mockito.verify
import org.mockito.Mockito.verifyZeroInteractions
import org.mockito.junit.MockitoJUnit
@@ -76,6 +76,7 @@
@Mock private lateinit var mockedContext: Context
@Mock private lateinit var demoModeController: DemoModeController
+ @Mock private lateinit var qsBatteryModeController: QsBatteryModeController
@JvmField @Rule val mockitoRule = MockitoJUnit.rule()
var viewVisibility = View.GONE
@@ -130,8 +131,9 @@
featureFlags,
qsCarrierGroupControllerBuilder,
combinedShadeHeadersConstraintManager,
- demoModeController
- )
+ demoModeController,
+ qsBatteryModeController,
+ )
whenever(view.isAttachedToWindow).thenReturn(true)
mLargeScreenShadeHeaderController.init()
carrierIconSlots = listOf(
@@ -156,10 +158,20 @@
fun updateListeners_registersWhenVisible() {
makeShadeVisible()
verify(qsCarrierGroupController).setListening(true)
+ }
+
+ @Test
+ fun statusIconsAddedWhenAttached() {
verify(statusBarIconController).addIconGroup(any())
}
@Test
+ fun statusIconsRemovedWhenDettached() {
+ mLargeScreenShadeHeaderController.simulateViewDetached()
+ verify(statusBarIconController).removeIconGroup(any())
+ }
+
+ @Test
fun shadeExpandedFraction_updatesAlpha() {
makeShadeVisible()
mLargeScreenShadeHeaderController.shadeExpandedFraction = 0.5f
@@ -167,16 +179,6 @@
}
@Test
- fun alphaChangesUpdateVisibility() {
- makeShadeVisible()
- mLargeScreenShadeHeaderController.shadeExpandedFraction = 0f
- assertThat(viewVisibility).isEqualTo(View.INVISIBLE)
-
- mLargeScreenShadeHeaderController.shadeExpandedFraction = 1f
- assertThat(viewVisibility).isEqualTo(View.VISIBLE)
- }
-
- @Test
fun singleCarrier_enablesCarrierIconsInStatusIcons() {
whenever(qsCarrierGroupController.isSingleCarrier).thenReturn(true)
@@ -261,40 +263,32 @@
}
@Test
- fun testShadeExpanded_true_alpha_zero_invisible() {
- view.alpha = 0f
- mLargeScreenShadeHeaderController.largeScreenActive = true
- mLargeScreenShadeHeaderController.qsVisible = true
+ fun customizerAnimatorChangesViewVisibility() {
+ makeShadeVisible()
- assertThat(viewVisibility).isEqualTo(View.INVISIBLE)
- }
-
- @Test
- fun animatorCallsUpdateVisibilityOnUpdate() {
val animator = mock(ViewPropertyAnimator::class.java, Answers.RETURNS_SELF)
+ val duration = 1000L
whenever(view.animate()).thenReturn(animator)
+ val listenerCaptor = argumentCaptor<Animator.AnimatorListener>()
- mLargeScreenShadeHeaderController.startCustomizingAnimation(show = false, 0L)
-
- val updateCaptor = argumentCaptor<ValueAnimator.AnimatorUpdateListener>()
- verify(animator).setUpdateListener(capture(updateCaptor))
-
- mLargeScreenShadeHeaderController.largeScreenActive = true
- mLargeScreenShadeHeaderController.qsVisible = true
-
- view.alpha = 1f
- updateCaptor.value.onAnimationUpdate(mock())
-
- assertThat(viewVisibility).isEqualTo(View.VISIBLE)
-
- view.alpha = 0f
- updateCaptor.value.onAnimationUpdate(mock())
-
+ mLargeScreenShadeHeaderController.startCustomizingAnimation(show = true, duration)
+ verify(animator).setListener(capture(listenerCaptor))
+ // Start and end the animation
+ listenerCaptor.value.onAnimationStart(mock())
+ listenerCaptor.value.onAnimationEnd(mock())
assertThat(viewVisibility).isEqualTo(View.INVISIBLE)
+
+ reset(animator)
+ mLargeScreenShadeHeaderController.startCustomizingAnimation(show = false, duration)
+ verify(animator).setListener(capture(listenerCaptor))
+ // Start and end the animation
+ listenerCaptor.value.onAnimationStart(mock())
+ listenerCaptor.value.onAnimationEnd(mock())
+ assertThat(viewVisibility).isEqualTo(View.VISIBLE)
}
@Test
- fun animatorListenersClearedAtEnd() {
+ fun animatorListenerClearedAtEnd() {
val animator = mock(ViewPropertyAnimator::class.java, Answers.RETURNS_SELF)
whenever(view.animate()).thenReturn(animator)
@@ -304,21 +298,6 @@
listenerCaptor.value.onAnimationEnd(mock())
verify(animator).setListener(null)
- verify(animator).setUpdateListener(null)
- }
-
- @Test
- fun animatorListenersClearedOnCancel() {
- val animator = mock(ViewPropertyAnimator::class.java, Answers.RETURNS_SELF)
- whenever(view.animate()).thenReturn(animator)
-
- mLargeScreenShadeHeaderController.startCustomizingAnimation(show = true, 0L)
- val listenerCaptor = argumentCaptor<Animator.AnimatorListener>()
- verify(animator).setListener(capture(listenerCaptor))
-
- listenerCaptor.value.onAnimationCancel(mock())
- verify(animator).setListener(null)
- verify(animator).setUpdateListener(null)
}
@Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerTest.java
index 28f7edf..996d9fb 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerTest.java
@@ -196,6 +196,7 @@
import java.util.List;
import java.util.Optional;
+import dagger.Lazy;
import kotlinx.coroutines.CoroutineDispatcher;
@SmallTest
@@ -270,6 +271,7 @@
@Mock private KeyguardMediaController mKeyguardMediaController;
@Mock private NavigationModeController mNavigationModeController;
@Mock private NavigationBarController mNavigationBarController;
+ @Mock private QuickSettingsController mQsController;
@Mock private LargeScreenShadeHeaderController mLargeScreenShadeHeaderController;
@Mock private ContentResolver mContentResolver;
@Mock private TapAgainViewController mTapAgainViewController;
@@ -329,6 +331,10 @@
private final DisplayMetrics mDisplayMetrics = new DisplayMetrics();
private final ShadeExpansionStateManager mShadeExpansionStateManager =
new ShadeExpansionStateManager();
+
+ private QuickSettingsController mQuickSettingsController;
+ @Mock private Lazy<NotificationPanelViewController> mNotificationPanelViewControllerLazy;
+
private FragmentHostManager.FragmentListener mFragmentListener;
@Before
@@ -506,6 +512,7 @@
mTapAgainViewController,
mNavigationModeController,
mNavigationBarController,
+ mQsController,
mFragmentService,
mContentResolver,
mRecordingController,
@@ -515,8 +522,6 @@
mShadeExpansionStateManager,
mNotificationRemoteInputManager,
mSysUIUnfoldComponent,
- mInteractionJankMonitor,
- mQsFrameTranslateController,
mSysUiState,
() -> mKeyguardBottomAreaViewController,
mKeyguardUnlockAnimationController,
@@ -572,6 +577,40 @@
.setOnEmptySpaceClickListener(mEmptySpaceClickListenerCaptor.capture());
verify(mKeyguardStatusViewController).displayClock(LARGE, /* animate */ true);
reset(mKeyguardStatusViewController);
+
+ when(mNotificationPanelViewControllerLazy.get())
+ .thenReturn(mNotificationPanelViewController);
+ mQuickSettingsController = new QuickSettingsController(
+ mNotificationPanelViewControllerLazy,
+ mView,
+ mQsFrameTranslateController,
+ mShadeTransitionController,
+ expansionHandler,
+ mNotificationRemoteInputManager,
+ mShadeExpansionStateManager,
+ mStatusBarKeyguardViewManager,
+ mNotificationStackScrollLayoutController,
+ mLockscreenShadeTransitionController,
+ mNotificationShadeDepthController,
+ mLargeScreenShadeHeaderController,
+ mStatusBarTouchableRegionManager,
+ mKeyguardStateController,
+ mKeyguardBypassController,
+ mUpdateMonitor,
+ mScrimController,
+ mMediaDataManager,
+ mMediaHierarchyManager,
+ mAmbientState,
+ mRecordingController,
+ mFalsingManager,
+ new FalsingCollectorFake(),
+ mAccessibilityManager,
+ mLockscreenGestureLogger,
+ mMetricsLogger,
+ mFeatureFlags,
+ mInteractionJankMonitor,
+ mShadeLog
+ );
}
@After
@@ -755,27 +794,14 @@
@Test
public void testOnTouchEvent_expansionCanBeBlocked() {
- onTouchEvent(MotionEvent.obtain(0L /* downTime */,
- 0L /* eventTime */, MotionEvent.ACTION_DOWN, 0f /* x */, 0f /* y */,
- 0 /* metaState */));
- onTouchEvent(MotionEvent.obtain(0L /* downTime */,
- 0L /* eventTime */, MotionEvent.ACTION_MOVE, 0f /* x */, 200f /* y */,
- 0 /* metaState */));
+ onTouchEvent(MotionEvent.obtain(0L, 0L, MotionEvent.ACTION_DOWN, 0f, 0f, 0));
+ onTouchEvent(MotionEvent.obtain(0L, 0L, MotionEvent.ACTION_MOVE, 0f, 200f, 0));
assertThat((int) mNotificationPanelViewController.getExpandedHeight()).isEqualTo(200);
- assertThat(mNotificationPanelViewController.isTrackingBlocked()).isFalse();
mNotificationPanelViewController.blockExpansionForCurrentTouch();
- onTouchEvent(MotionEvent.obtain(0L /* downTime */,
- 0L /* eventTime */, MotionEvent.ACTION_MOVE, 0f /* x */, 300f /* y */,
- 0 /* metaState */));
+ onTouchEvent(MotionEvent.obtain(0L, 0L, MotionEvent.ACTION_MOVE, 0f, 300f, 0));
// Expansion should not have changed because it was blocked
assertThat((int) mNotificationPanelViewController.getExpandedHeight()).isEqualTo(200);
- assertThat(mNotificationPanelViewController.isTrackingBlocked()).isTrue();
-
- onTouchEvent(MotionEvent.obtain(0L /* downTime */,
- 0L /* eventTime */, MotionEvent.ACTION_UP, 0f /* x */, 300f /* y */,
- 0 /* metaState */));
- assertThat(mNotificationPanelViewController.isTrackingBlocked()).isFalse();
}
@Test
@@ -1045,7 +1071,7 @@
@Test
public void testCanCollapsePanelOnTouch_trueWhenInSettings() {
mStatusBarStateController.setState(SHADE);
- mNotificationPanelViewController.setQsExpanded(true);
+ when(mQsController.getExpanded()).thenReturn(true);
assertThat(mNotificationPanelViewController.canCollapsePanelOnTouch()).isTrue();
}
@@ -1054,7 +1080,7 @@
public void testCanCollapsePanelOnTouch_falseInDualPaneShade() {
mStatusBarStateController.setState(SHADE);
enableSplitShade(/* enabled= */ true);
- mNotificationPanelViewController.setQsExpanded(true);
+ when(mQsController.getExpanded()).thenReturn(true);
assertThat(mNotificationPanelViewController.canCollapsePanelOnTouch()).isFalse();
}
@@ -1125,7 +1151,7 @@
@Test
public void testRotatingToSplitShadeWithQsExpanded_transitionsToShadeLocked() {
mStatusBarStateController.setState(KEYGUARD);
- mNotificationPanelViewController.setQsExpanded(true);
+ when(mQsController.getExpanded()).thenReturn(true);
enableSplitShade(true);
@@ -1136,24 +1162,18 @@
public void testUnlockedSplitShadeTransitioningToKeyguard_closesQS() {
enableSplitShade(true);
mStatusBarStateController.setState(SHADE);
- mNotificationPanelViewController.setQsExpanded(true);
-
mStatusBarStateController.setState(KEYGUARD);
- assertThat(mNotificationPanelViewController.isQsExpanded()).isEqualTo(false);
- assertThat(mNotificationPanelViewController.isQsExpandImmediate()).isEqualTo(false);
+ verify(mQsController).closeQs();
}
@Test
public void testLockedSplitShadeTransitioningToKeyguard_closesQS() {
enableSplitShade(true);
mStatusBarStateController.setState(SHADE_LOCKED);
- mNotificationPanelViewController.setQsExpanded(true);
-
mStatusBarStateController.setState(KEYGUARD);
- assertThat(mNotificationPanelViewController.isQsExpanded()).isEqualTo(false);
- assertThat(mNotificationPanelViewController.isQsExpandImmediate()).isEqualTo(false);
+ verify(mQsController).closeQs();
}
@Test
@@ -1165,7 +1185,7 @@
verify(mKeyguardStatusViewController).displayClock(LARGE, /* animate */ true);
when(mNotificationStackScrollLayoutController.getVisibleNotificationCount()).thenReturn(1);
- mNotificationPanelViewController.closeQs();
+ triggerPositionClockAndNotifications();
verify(mKeyguardStatusViewController).displayClock(SMALL, /* animate */ true);
}
@@ -1293,18 +1313,6 @@
}
@Test
- public void testLargeScreenHeaderMadeActiveForLargeScreen() {
- mStatusBarStateController.setState(SHADE);
- when(mResources.getBoolean(R.bool.config_use_large_screen_shade_header)).thenReturn(true);
- mNotificationPanelViewController.updateResources();
- verify(mLargeScreenShadeHeaderController).setLargeScreenActive(true);
-
- when(mResources.getBoolean(R.bool.config_use_large_screen_shade_header)).thenReturn(false);
- mNotificationPanelViewController.updateResources();
- verify(mLargeScreenShadeHeaderController).setLargeScreenActive(false);
- }
-
- @Test
public void testExpandWithQsMethodIsUsingLockscreenTransitionController() {
enableSplitShade(/* enabled= */ true);
mStatusBarStateController.setState(KEYGUARD);
@@ -1358,12 +1366,14 @@
@Test
public void testQsToBeImmediatelyExpandedWhenOpeningPanelInSplitShade() {
enableSplitShade(/* enabled= */ true);
+ mShadeExpansionStateManager.updateState(STATE_OPEN);
+ verify(mQsController).setExpandImmediate(false);
+
mShadeExpansionStateManager.updateState(STATE_CLOSED);
- assertThat(mNotificationPanelViewController.isQsExpandImmediate()).isFalse();
+ verify(mQsController, times(2)).setExpandImmediate(false);
mShadeExpansionStateManager.updateState(STATE_OPENING);
-
- assertThat(mNotificationPanelViewController.isQsExpandImmediate()).isTrue();
+ verify(mQsController).setExpandImmediate(true);
}
@Test
@@ -1375,33 +1385,28 @@
// going to lockscreen would trigger STATE_OPENING
mShadeExpansionStateManager.updateState(STATE_OPENING);
- assertThat(mNotificationPanelViewController.isQsExpandImmediate()).isFalse();
+ verify(mQsController, never()).setExpandImmediate(true);
}
@Test
public void testQsImmediateResetsWhenPanelOpensOrCloses() {
- mNotificationPanelViewController.setQsExpandImmediate(true);
mShadeExpansionStateManager.updateState(STATE_OPEN);
- assertThat(mNotificationPanelViewController.isQsExpandImmediate()).isFalse();
-
- mNotificationPanelViewController.setQsExpandImmediate(true);
mShadeExpansionStateManager.updateState(STATE_CLOSED);
- assertThat(mNotificationPanelViewController.isQsExpandImmediate()).isFalse();
+ verify(mQsController, times(2)).setExpandImmediate(false);
}
@Test
public void testQsExpansionChangedToDefaultWhenRotatingFromOrToSplitShade() {
// to make sure shade is in expanded state
mNotificationPanelViewController.startWaitingForOpenPanelGesture();
- assertThat(mNotificationPanelViewController.isQsExpanded()).isFalse();
// switch to split shade from portrait (default state)
enableSplitShade(/* enabled= */ true);
- assertThat(mNotificationPanelViewController.isQsExpanded()).isTrue();
+ verify(mQsController).setExpanded(true);
// switch to portrait from split shade
enableSplitShade(/* enabled= */ false);
- assertThat(mNotificationPanelViewController.isQsExpanded()).isFalse();
+ verify(mQsController).setExpanded(false);
}
@Test
@@ -1413,60 +1418,11 @@
assertThat(mNotificationPanelViewController.isClosing()).isFalse();
mNotificationPanelViewController.animateCloseQs(false);
+
assertThat(mNotificationPanelViewController.isClosing()).isTrue();
}
@Test
- public void testPanelStaysOpenWhenClosingQs() {
- mShadeExpansionStateManager.onPanelExpansionChanged(/* fraction= */ 1,
- /* expanded= */ true, /* tracking= */ false, /* dragDownPxAmount= */ 0);
- mNotificationPanelViewController.setExpandedFraction(1f);
-
- assertThat(mNotificationPanelViewController.isClosing()).isFalse();
- mNotificationPanelViewController.animateCloseQs(false);
- assertThat(mNotificationPanelViewController.isClosing()).isFalse();
- }
-
- @Test
- public void interceptTouchEvent_withinQs_shadeExpanded_startsQsTracking() {
- mNotificationPanelViewController.setQs(mQs);
- when(mQsFrame.getX()).thenReturn(0f);
- when(mQsFrame.getWidth()).thenReturn(1000);
- when(mQsHeader.getTop()).thenReturn(0);
- when(mQsHeader.getBottom()).thenReturn(1000);
- NotificationPanelViewController.TouchHandler touchHandler =
- mNotificationPanelViewController.createTouchHandler();
-
- mNotificationPanelViewController.setExpandedFraction(1f);
- touchHandler.onInterceptTouchEvent(
- createMotionEvent(/* x= */ 0, /* y= */ 0, MotionEvent.ACTION_DOWN));
- touchHandler.onInterceptTouchEvent(
- createMotionEvent(/* x= */ 0, /* y= */ 500, MotionEvent.ACTION_MOVE));
-
- assertThat(mNotificationPanelViewController.isQsTracking()).isTrue();
- }
-
- @Test
- public void interceptTouchEvent_withinQs_shadeExpanded_inSplitShade_doesNotStartQsTracking() {
- enableSplitShade(true);
- mNotificationPanelViewController.setQs(mQs);
- when(mQsFrame.getX()).thenReturn(0f);
- when(mQsFrame.getWidth()).thenReturn(1000);
- when(mQsHeader.getTop()).thenReturn(0);
- when(mQsHeader.getBottom()).thenReturn(1000);
- NotificationPanelViewController.TouchHandler touchHandler =
- mNotificationPanelViewController.createTouchHandler();
-
- mNotificationPanelViewController.setExpandedFraction(1f);
- touchHandler.onInterceptTouchEvent(
- createMotionEvent(/* x= */ 0, /* y= */ 0, MotionEvent.ACTION_DOWN));
- touchHandler.onInterceptTouchEvent(
- createMotionEvent(/* x= */ 0, /* y= */ 500, MotionEvent.ACTION_MOVE));
-
- assertThat(mNotificationPanelViewController.isQsTracking()).isFalse();
- }
-
- @Test
public void testOnAttachRefreshStatusBarState() {
mStatusBarStateController.setState(KEYGUARD);
when(mKeyguardStateController.isKeyguardFadingAway()).thenReturn(false);
@@ -1494,11 +1450,14 @@
enableSplitShade(true);
mNotificationPanelViewController.expandWithQs();
when(mHeadsUpManager.isTrackingHeadsUp()).thenReturn(true);
+ when(mQsController.calculatePanelHeightExpanded(anyInt())).thenReturn(10000);
mNotificationPanelViewController.setHeadsUpDraggingStartingHeight(
SPLIT_SHADE_FULL_TRANSITION_DISTANCE);
int maxDistance = mNotificationPanelViewController.getMaxPanelTransitionDistance();
+ // make sure we're ignoring the placeholder value for Qs max height
+ assertThat(maxDistance).isLessThan(10000);
assertThat(maxDistance).isGreaterThan(SPLIT_SHADE_FULL_TRANSITION_DISTANCE);
}
@@ -1525,95 +1484,26 @@
@Test
public void onLayoutChange_fullWidth_updatesQSWithFullWithTrue() {
- mNotificationPanelViewController.setQs(mQs);
-
setIsFullWidth(true);
- verify(mQs).setIsNotificationPanelFullWidth(true);
+ verify(mQsController).setNotificationPanelFullWidth(true);
}
@Test
public void onLayoutChange_notFullWidth_updatesQSWithFullWithFalse() {
- mNotificationPanelViewController.setQs(mQs);
-
setIsFullWidth(false);
- verify(mQs).setIsNotificationPanelFullWidth(false);
+ verify(mQsController).setNotificationPanelFullWidth(false);
}
@Test
public void onLayoutChange_qsNotSet_doesNotCrash() {
- mNotificationPanelViewController.setQs(null);
+ mQuickSettingsController.setQs(null);
triggerLayoutChange();
}
@Test
- public void onQsFragmentAttached_fullWidth_setsFullWidthTrueOnQS() {
- setIsFullWidth(true);
- givenViewAttached();
- mFragmentListener.onFragmentViewCreated(QS.TAG, mQSFragment);
-
- verify(mQSFragment).setIsNotificationPanelFullWidth(true);
- }
-
- @Test
- public void onQsFragmentAttached_notFullWidth_setsFullWidthFalseOnQS() {
- setIsFullWidth(false);
- givenViewAttached();
- mFragmentListener.onFragmentViewCreated(QS.TAG, mQSFragment);
-
- verify(mQSFragment).setIsNotificationPanelFullWidth(false);
- }
-
- @Test
- public void setQsExpansion_lockscreenShadeTransitionInProgress_usesLockscreenSquishiness() {
- float squishinessFraction = 0.456f;
- mNotificationPanelViewController.setQs(mQs);
- when(mLockscreenShadeTransitionController.getQsSquishTransitionFraction())
- .thenReturn(squishinessFraction);
- when(mNotificationStackScrollLayoutController.getNotificationSquishinessFraction())
- .thenReturn(0.987f);
- // Call setTransitionToFullShadeAmount to get into the full shade transition in progress
- // state.
- mNotificationPanelViewController.setTransitionToFullShadeAmount(
- /* pxAmount= */ 234,
- /* animate= */ false,
- /* delay= */ 0
- );
-
- mNotificationPanelViewController.setQsExpansionHeight(/* height= */ 123);
-
- // First for setTransitionToFullShadeAmount and then setQsExpansion
- verify(mQs, times(2)).setQsExpansion(
- /* expansion= */ anyFloat(),
- /* panelExpansionFraction= */ anyFloat(),
- /* proposedTranslation= */ anyFloat(),
- eq(squishinessFraction)
- );
- }
-
- @Test
- public void setQsExpansion_lockscreenShadeTransitionNotInProgress_usesStandardSquishiness() {
- float lsSquishinessFraction = 0.456f;
- float nsslSquishinessFraction = 0.987f;
- mNotificationPanelViewController.setQs(mQs);
- when(mLockscreenShadeTransitionController.getQsSquishTransitionFraction())
- .thenReturn(lsSquishinessFraction);
- when(mNotificationStackScrollLayoutController.getNotificationSquishinessFraction())
- .thenReturn(nsslSquishinessFraction);
-
- mNotificationPanelViewController.setQsExpansionHeight(/* height= */ 123);
-
- verify(mQs).setQsExpansion(
- /* expansion= */ anyFloat(),
- /* panelExpansionFraction= */ anyFloat(),
- /* proposedTranslation= */ anyFloat(),
- eq(nsslSquishinessFraction)
- );
- }
-
- @Test
public void onEmptySpaceClicked_notDozingAndOnKeyguard_requestsFaceAuth() {
StatusBarStateController.StateListener statusBarStateListener =
mNotificationPanelViewController.getStatusBarStateListener();
@@ -1738,15 +1628,6 @@
int transitionDistance = mNotificationPanelViewController.getMaxPanelTransitionDistance();
mNotificationPanelViewController.setExpandedHeight(transitionDistance);
assertThat(mNotificationPanelViewController.isShadeFullyOpen()).isFalse();
-
- // set maxQsExpansion in NPVC
- int maxQsExpansion = 123;
- mNotificationPanelViewController.setQs(mQs);
- when(mQs.getDesiredHeight()).thenReturn(maxQsExpansion);
- triggerLayoutChange();
-
- mNotificationPanelViewController.setQsExpansionHeight(maxQsExpansion);
- assertThat(mNotificationPanelViewController.isShadeFullyOpen()).isTrue();
}
@Test
@@ -1761,7 +1642,7 @@
}
private void triggerPositionClockAndNotifications() {
- mNotificationPanelViewController.closeQs();
+ mNotificationPanelViewController.onQsSetExpansionHeightCalled(false);
}
private FalsingManager.FalsingTapListener getFalsingTapListener() {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/QsBatteryModeControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/QsBatteryModeControllerTest.kt
new file mode 100644
index 0000000..b547318
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/QsBatteryModeControllerTest.kt
@@ -0,0 +1,100 @@
+package com.android.systemui.shade
+
+import android.content.Context
+import android.content.res.Resources
+import android.graphics.Rect
+import android.testing.AndroidTestingRunner
+import android.view.DisplayCutout
+import androidx.test.filters.SmallTest
+import com.android.systemui.R
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.battery.BatteryMeterView
+import com.android.systemui.statusbar.phone.StatusBarContentInsetsProvider
+import com.android.systemui.util.mockito.mock
+import com.android.systemui.util.mockito.whenever
+import com.google.common.truth.Truth.assertThat
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mock
+import org.mockito.junit.MockitoJUnit
+
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+class QsBatteryModeControllerTest : SysuiTestCase() {
+
+ private companion object {
+ val CENTER_TOP_CUTOUT: DisplayCutout =
+ mock<DisplayCutout>().also {
+ whenever(it.boundingRectTop).thenReturn(Rect(10, 0, 20, 10))
+ }
+
+ const val MOTION_LAYOUT_MAX_FRAME = 100
+ const val QQS_START_FRAME = 14
+ const val QS_END_FRAME = 58
+ }
+
+ @JvmField @Rule val mockitoRule = MockitoJUnit.rule()!!
+
+ @Mock private lateinit var insetsProvider: StatusBarContentInsetsProvider
+ @Mock private lateinit var mockedContext: Context
+ @Mock private lateinit var mockedResources: Resources
+
+ private lateinit var controller: QsBatteryModeController // under test
+
+ @Before
+ fun setup() {
+ whenever(mockedContext.resources).thenReturn(mockedResources)
+ whenever(mockedResources.getInteger(R.integer.fade_in_start_frame)).thenReturn(QS_END_FRAME)
+ whenever(mockedResources.getInteger(R.integer.fade_out_complete_frame))
+ .thenReturn(QQS_START_FRAME)
+
+ controller = QsBatteryModeController(mockedContext, insetsProvider)
+ }
+
+ @Test
+ fun `returns MODE_ON for qqs with center cutout`() {
+ assertThat(
+ controller.getBatteryMode(CENTER_TOP_CUTOUT, QQS_START_FRAME.prevFrameToFraction())
+ )
+ .isEqualTo(BatteryMeterView.MODE_ON)
+ }
+
+ @Test
+ fun `returns MODE_ESTIMATE for qs with center cutout`() {
+ assertThat(controller.getBatteryMode(CENTER_TOP_CUTOUT, QS_END_FRAME.nextFrameToFraction()))
+ .isEqualTo(BatteryMeterView.MODE_ESTIMATE)
+ }
+
+ @Test
+ fun `returns MODE_ON for qqs with corner cutout`() {
+ whenever(insetsProvider.currentRotationHasCornerCutout()).thenReturn(true)
+
+ assertThat(
+ controller.getBatteryMode(CENTER_TOP_CUTOUT, QQS_START_FRAME.prevFrameToFraction())
+ )
+ .isEqualTo(BatteryMeterView.MODE_ESTIMATE)
+ }
+
+ @Test
+ fun `returns MODE_ESTIMATE for qs with corner cutout`() {
+ whenever(insetsProvider.currentRotationHasCornerCutout()).thenReturn(true)
+
+ assertThat(controller.getBatteryMode(CENTER_TOP_CUTOUT, QS_END_FRAME.nextFrameToFraction()))
+ .isEqualTo(BatteryMeterView.MODE_ESTIMATE)
+ }
+
+ @Test
+ fun `returns null in-between`() {
+ assertThat(
+ controller.getBatteryMode(CENTER_TOP_CUTOUT, QQS_START_FRAME.nextFrameToFraction())
+ )
+ .isNull()
+ assertThat(controller.getBatteryMode(CENTER_TOP_CUTOUT, QS_END_FRAME.prevFrameToFraction()))
+ .isNull()
+ }
+
+ private fun Int.prevFrameToFraction(): Float = (this - 1) / MOTION_LAYOUT_MAX_FRAME.toFloat()
+ private fun Int.nextFrameToFraction(): Float = (this + 1) / MOTION_LAYOUT_MAX_FRAME.toFloat()
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/QuickSettingsControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/shade/QuickSettingsControllerTest.java
new file mode 100644
index 0000000..c2fca6f
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/QuickSettingsControllerTest.java
@@ -0,0 +1,415 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.shade;
+
+import static com.android.systemui.statusbar.StatusBarState.KEYGUARD;
+import static com.android.systemui.statusbar.StatusBarState.SHADE;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyFloat;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.content.res.Resources;
+import android.os.Handler;
+import android.os.Looper;
+import android.testing.AndroidTestingRunner;
+import android.testing.TestableLooper;
+import android.view.MotionEvent;
+import android.view.ViewGroup;
+import android.view.ViewParent;
+import android.view.accessibility.AccessibilityManager;
+
+import androidx.test.filters.SmallTest;
+
+import com.android.internal.jank.InteractionJankMonitor;
+import com.android.internal.logging.MetricsLogger;
+import com.android.internal.logging.UiEventLogger;
+import com.android.keyguard.KeyguardStatusView;
+import com.android.keyguard.KeyguardUpdateMonitor;
+import com.android.systemui.R;
+import com.android.systemui.SysuiTestCase;
+import com.android.systemui.classifier.FalsingCollector;
+import com.android.systemui.dump.DumpManager;
+import com.android.systemui.flags.FeatureFlags;
+import com.android.systemui.fragments.FragmentHostManager;
+import com.android.systemui.media.controls.pipeline.MediaDataManager;
+import com.android.systemui.media.controls.ui.MediaHierarchyManager;
+import com.android.systemui.plugins.FalsingManager;
+import com.android.systemui.plugins.qs.QS;
+import com.android.systemui.qs.QSFragment;
+import com.android.systemui.screenrecord.RecordingController;
+import com.android.systemui.shade.transition.ShadeTransitionController;
+import com.android.systemui.statusbar.LockscreenShadeTransitionController;
+import com.android.systemui.statusbar.NotificationRemoteInputManager;
+import com.android.systemui.statusbar.NotificationShadeDepthController;
+import com.android.systemui.statusbar.PulseExpansionHandler;
+import com.android.systemui.statusbar.QsFrameTranslateController;
+import com.android.systemui.statusbar.StatusBarStateControllerImpl;
+import com.android.systemui.statusbar.SysuiStatusBarStateController;
+import com.android.systemui.statusbar.notification.stack.AmbientState;
+import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController;
+import com.android.systemui.statusbar.phone.DozeParameters;
+import com.android.systemui.statusbar.phone.HeadsUpManagerPhone;
+import com.android.systemui.statusbar.phone.KeyguardBottomAreaView;
+import com.android.systemui.statusbar.phone.KeyguardBypassController;
+import com.android.systemui.statusbar.phone.KeyguardStatusBarView;
+import com.android.systemui.statusbar.phone.LockscreenGestureLogger;
+import com.android.systemui.statusbar.phone.ScreenOffAnimationController;
+import com.android.systemui.statusbar.phone.ScrimController;
+import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager;
+import com.android.systemui.statusbar.phone.StatusBarTouchableRegionManager;
+import com.android.systemui.statusbar.policy.KeyguardStateController;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+import dagger.Lazy;
+
+@SmallTest
+@RunWith(AndroidTestingRunner.class)
+@TestableLooper.RunWithLooper(setAsMainLooper = true)
+public class QuickSettingsControllerTest extends SysuiTestCase {
+
+ private static final int SPLIT_SHADE_FULL_TRANSITION_DISTANCE = 400;
+
+ private QuickSettingsController mQsController;
+
+ @Mock private Resources mResources;
+ @Mock private KeyguardBottomAreaView mQsFrame;
+ @Mock private KeyguardStatusBarView mKeyguardStatusBar;
+ @Mock private QS mQs;
+ @Mock private QSFragment mQSFragment;
+
+ @Mock private Lazy<NotificationPanelViewController> mPanelViewControllerLazy;
+ @Mock private NotificationPanelViewController mNotificationPanelViewController;
+ @Mock private NotificationPanelView mPanelView;
+ @Mock private ViewGroup mQsHeader;
+ @Mock private ViewParent mPanelViewParent;
+ @Mock private QsFrameTranslateController mQsFrameTranslateController;
+ @Mock private ShadeTransitionController mShadeTransitionController;
+ @Mock private PulseExpansionHandler mPulseExpansionHandler;
+ @Mock private NotificationRemoteInputManager mNotificationRemoteInputManager;
+ @Mock private StatusBarKeyguardViewManager mStatusBarKeyguardViewManager;
+ @Mock private NotificationStackScrollLayoutController mNotificationStackScrollLayoutController;
+ @Mock private LockscreenShadeTransitionController mLockscreenShadeTransitionController;
+ @Mock private NotificationShadeDepthController mNotificationShadeDepthController;
+ @Mock private LargeScreenShadeHeaderController mLargeScreenShadeHeaderController;
+ @Mock private StatusBarTouchableRegionManager mStatusBarTouchableRegionManager;
+ @Mock private KeyguardStateController mKeyguardStateController;
+ @Mock private KeyguardBypassController mKeyguardBypassController;
+ @Mock private KeyguardUpdateMonitor mKeyguardUpdateMonitor;
+ @Mock private ScrimController mScrimController;
+ @Mock private MediaDataManager mMediaDataManager;
+ @Mock private MediaHierarchyManager mMediaHierarchyManager;
+ @Mock private AmbientState mAmbientState;
+ @Mock private RecordingController mRecordingController;
+ @Mock private FalsingManager mFalsingManager;
+ @Mock private FalsingCollector mFalsingCollector;
+ @Mock private AccessibilityManager mAccessibilityManager;
+ @Mock private LockscreenGestureLogger mLockscreenGestureLogger;
+ @Mock private MetricsLogger mMetricsLogger;
+ @Mock private FeatureFlags mFeatureFlags;
+ @Mock private InteractionJankMonitor mInteractionJankMonitor;
+ @Mock private ShadeLogger mShadeLogger;
+
+ @Mock private DumpManager mDumpManager;
+ @Mock private DozeParameters mDozeParameters;
+ @Mock private ScreenOffAnimationController mScreenOffAnimationController;
+ @Mock private HeadsUpManagerPhone mHeadsUpManager;
+ @Mock private UiEventLogger mUiEventLogger;
+
+ private SysuiStatusBarStateController mStatusBarStateController;
+
+ private Handler mMainHandler;
+ private LockscreenShadeTransitionController.Callback mLockscreenShadeTransitionCallback;
+
+ private final ShadeExpansionStateManager mShadeExpansionStateManager =
+ new ShadeExpansionStateManager();
+
+ private FragmentHostManager.FragmentListener mFragmentListener;
+
+ @Before
+ public void setup() {
+ MockitoAnnotations.initMocks(this);
+ when(mPanelViewControllerLazy.get()).thenReturn(mNotificationPanelViewController);
+ mStatusBarStateController = new StatusBarStateControllerImpl(mUiEventLogger, mDumpManager,
+ mInteractionJankMonitor, mShadeExpansionStateManager);
+
+ KeyguardStatusView keyguardStatusView = new KeyguardStatusView(mContext);
+ keyguardStatusView.setId(R.id.keyguard_status_view);
+
+ when(mPanelView.getResources()).thenReturn(mResources);
+ when(mPanelView.getContext()).thenReturn(getContext());
+ when(mPanelView.findViewById(R.id.keyguard_header)).thenReturn(mKeyguardStatusBar);
+ when(mNotificationStackScrollLayoutController.getHeight()).thenReturn(1000);
+ when(mPanelView.findViewById(R.id.qs_frame)).thenReturn(mQsFrame);
+ when(mPanelView.findViewById(R.id.keyguard_status_view))
+ .thenReturn(mock(KeyguardStatusView.class));
+ when(mQs.getView()).thenReturn(mPanelView);
+ when(mQSFragment.getView()).thenReturn(mPanelView);
+
+ when(mNotificationRemoteInputManager.isRemoteInputActive())
+ .thenReturn(false);
+ when(mInteractionJankMonitor.begin(any(), anyInt()))
+ .thenReturn(true);
+ when(mInteractionJankMonitor.end(anyInt()))
+ .thenReturn(true);
+
+ when(mPanelView.getParent()).thenReturn(mPanelViewParent);
+ when(mQs.getHeader()).thenReturn(mQsHeader);
+
+ doAnswer(invocation -> {
+ mLockscreenShadeTransitionCallback = invocation.getArgument(0);
+ return null;
+ }).when(mLockscreenShadeTransitionController).addCallback(any());
+
+
+ mMainHandler = new Handler(Looper.getMainLooper());
+
+ mQsController = new QuickSettingsController(
+ mPanelViewControllerLazy,
+ mPanelView,
+ mQsFrameTranslateController,
+ mShadeTransitionController,
+ mPulseExpansionHandler,
+ mNotificationRemoteInputManager,
+ mShadeExpansionStateManager,
+ mStatusBarKeyguardViewManager,
+ mNotificationStackScrollLayoutController,
+ mLockscreenShadeTransitionController,
+ mNotificationShadeDepthController,
+ mLargeScreenShadeHeaderController,
+ mStatusBarTouchableRegionManager,
+ mKeyguardStateController,
+ mKeyguardBypassController,
+ mKeyguardUpdateMonitor,
+ mScrimController,
+ mMediaDataManager,
+ mMediaHierarchyManager,
+ mAmbientState,
+ mRecordingController,
+ mFalsingManager,
+ mFalsingCollector,
+ mAccessibilityManager,
+ mLockscreenGestureLogger,
+ mMetricsLogger,
+ mFeatureFlags,
+ mInteractionJankMonitor,
+ mShadeLogger
+ );
+
+ mFragmentListener = mQsController.getQsFragmentListener();
+ }
+
+ @After
+ public void tearDown() {
+ mNotificationPanelViewController.cancelHeightAnimator();
+ mMainHandler.removeCallbacksAndMessages(null);
+ }
+
+ @Test
+ public void testOnTouchEvent_isConflictingExpansionGestureSet() {
+ assertThat(mQsController.isConflictingExpansionGesture()).isFalse();
+ mShadeExpansionStateManager.onPanelExpansionChanged(1f, true, false, 0f);
+ mQsController.handleTouch(MotionEvent.obtain(0L /* downTime */,
+ 0L /* eventTime */, MotionEvent.ACTION_DOWN, 0f /* x */, 0f /* y */,
+ 0 /* metaState */), false, false);
+ assertThat(mQsController.isConflictingExpansionGesture()).isTrue();
+ }
+
+ @Test
+ public void testCloseQsSideEffects() {
+ enableSplitShade(true);
+ mQsController.setExpandImmediate(true);
+ mQsController.setExpanded(true);
+ mQsController.closeQs();
+
+ assertThat(mQsController.getExpanded()).isEqualTo(false);
+ assertThat(mQsController.isExpandImmediate()).isEqualTo(false);
+ }
+
+ @Test
+ public void testLargeScreenHeaderMadeActiveForLargeScreen() {
+ mStatusBarStateController.setState(SHADE);
+ when(mResources.getBoolean(R.bool.config_use_large_screen_shade_header)).thenReturn(true);
+ mQsController.updateResources();
+ verify(mLargeScreenShadeHeaderController).setLargeScreenActive(true);
+
+ when(mResources.getBoolean(R.bool.config_use_large_screen_shade_header)).thenReturn(false);
+ mQsController.updateResources();
+ verify(mLargeScreenShadeHeaderController).setLargeScreenActive(false);
+ }
+
+ @Test
+ public void testPanelStaysOpenWhenClosingQs() {
+ mShadeExpansionStateManager.onPanelExpansionChanged(/* fraction= */ 1,
+ /* expanded= */ true, /* tracking= */ false, /* dragDownPxAmount= */ 0);
+ mNotificationPanelViewController.setExpandedFraction(1f);
+
+ float shadeExpandedHeight = mQsController.getShadeExpandedHeight();
+ mQsController.animateCloseQs(false);
+
+ assertThat(mQsController.getShadeExpandedHeight()).isEqualTo(shadeExpandedHeight);
+ }
+
+ @Test
+ public void interceptTouchEvent_withinQs_shadeExpanded_startsQsTracking() {
+ mQsController.setQs(mQs);
+ when(mQsFrame.getX()).thenReturn(0f);
+ when(mQsFrame.getWidth()).thenReturn(1000);
+ when(mQsHeader.getTop()).thenReturn(0);
+ when(mQsHeader.getBottom()).thenReturn(1000);
+
+ mQsController.setShadeExpandedHeight(1f);
+ mQsController.onIntercept(
+ createMotionEvent(0, 0, MotionEvent.ACTION_DOWN));
+ mQsController.onIntercept(
+ createMotionEvent(0, 500, MotionEvent.ACTION_MOVE));
+
+ assertThat(mQsController.isTracking()).isTrue();
+ }
+
+ @Test
+ public void interceptTouchEvent_withinQs_shadeExpanded_inSplitShade_doesNotStartQsTracking() {
+ enableSplitShade(true);
+ mQsController.setQs(mQs);
+ when(mQsFrame.getX()).thenReturn(0f);
+ when(mQsFrame.getWidth()).thenReturn(1000);
+ when(mQsHeader.getTop()).thenReturn(0);
+ when(mQsHeader.getBottom()).thenReturn(1000);
+
+ mQsController.setShadeExpandedHeight(1f);
+ mQsController.onIntercept(
+ createMotionEvent(0, 0, MotionEvent.ACTION_DOWN));
+ mQsController.onIntercept(
+ createMotionEvent(0, 500, MotionEvent.ACTION_MOVE));
+
+ assertThat(mQsController.isTracking()).isFalse();
+ }
+
+ @Test
+ public void onQsFragmentAttached_fullWidth_setsFullWidthTrueOnQS() {
+ setIsFullWidth(true);
+ mFragmentListener.onFragmentViewCreated(QS.TAG, mQSFragment);
+
+ verify(mQSFragment).setIsNotificationPanelFullWidth(true);
+ }
+
+ @Test
+ public void onQsFragmentAttached_notFullWidth_setsFullWidthFalseOnQS() {
+ setIsFullWidth(false);
+ mFragmentListener.onFragmentViewCreated(QS.TAG, mQSFragment);
+
+ verify(mQSFragment).setIsNotificationPanelFullWidth(false);
+ }
+
+ @Test
+ public void getMaxPanelTransitionDistance_inSplitShade_withHeadsUp_returnsBiggerValue() {
+ enableSplitShade(true);
+ mNotificationPanelViewController.expandWithQs();
+ when(mHeadsUpManager.isTrackingHeadsUp()).thenReturn(true);
+
+ mNotificationPanelViewController.setHeadsUpDraggingStartingHeight(
+ SPLIT_SHADE_FULL_TRANSITION_DISTANCE);
+
+ assertThat(mQsController.calculatePanelHeightExpanded(0))
+ .isGreaterThan(SPLIT_SHADE_FULL_TRANSITION_DISTANCE);
+ }
+
+ @Test
+ public void setQsExpansion_lockscreenShadeTransitionInProgress_usesLockscreenSquishiness() {
+ float squishinessFraction = 0.456f;
+ mQsController.setQs(mQs);
+ when(mLockscreenShadeTransitionController.getQsSquishTransitionFraction())
+ .thenReturn(squishinessFraction);
+ when(mNotificationStackScrollLayoutController.getNotificationSquishinessFraction())
+ .thenReturn(0.987f);
+ // Call setTransitionToFullShadeAmount to get into the full shade transition in progress
+ // state.
+ mLockscreenShadeTransitionCallback.setTransitionToFullShadeAmount(234, false, 0);
+
+ mQsController.setExpansionHeight(123);
+
+ // First for setTransitionToFullShadeAmount and then setQsExpansion
+ verify(mQs, times(2)).setQsExpansion(anyFloat(), anyFloat(), anyFloat(),
+ eq(squishinessFraction)
+ );
+ }
+
+ @Test
+ public void setQsExpansion_lockscreenShadeTransitionNotInProgress_usesStandardSquishiness() {
+ float lsSquishinessFraction = 0.456f;
+ float nsslSquishinessFraction = 0.987f;
+ mQsController.setQs(mQs);
+ when(mLockscreenShadeTransitionController.getQsSquishTransitionFraction())
+ .thenReturn(lsSquishinessFraction);
+ when(mNotificationStackScrollLayoutController.getNotificationSquishinessFraction())
+ .thenReturn(nsslSquishinessFraction);
+
+ mQsController.setExpansionHeight(123);
+
+ verify(mQs).setQsExpansion(anyFloat(), anyFloat(), anyFloat(), eq(nsslSquishinessFraction)
+ );
+ }
+
+ @Test
+ public void shadeExpanded_onKeyguard() {
+ mStatusBarStateController.setState(KEYGUARD);
+ // set maxQsExpansion in NPVC
+ int maxQsExpansion = 123;
+ mQsController.setQs(mQs);
+ when(mQs.getDesiredHeight()).thenReturn(maxQsExpansion);
+
+ int oldMaxHeight = mQsController.updateHeightsOnShadeLayoutChange();
+ mQsController.handleShadeLayoutChanged(oldMaxHeight);
+
+ mQsController.setExpansionHeight(maxQsExpansion);
+ assertThat(mQsController.computeExpansionFraction()).isEqualTo(1f);
+ }
+
+ private static MotionEvent createMotionEvent(int x, int y, int action) {
+ return MotionEvent.obtain(
+ /* downTime= */ 0, /* eventTime= */ 0, action, x, y, /* metaState= */ 0);
+ }
+
+ private void enableSplitShade(boolean enabled) {
+ when(mResources.getBoolean(R.bool.config_use_split_notification_shade)).thenReturn(enabled);
+ mQsController.updateResources();
+ }
+
+ private void setIsFullWidth(boolean fullWidth) {
+ mQsController.setNotificationPanelFullWidth(fullWidth);
+ triggerLayoutChange();
+ }
+
+ private void triggerLayoutChange() {
+ int oldMaxHeight = mQsController.updateHeightsOnShadeLayoutChange();
+ mQsController.handleShadeLayoutChanged(oldMaxHeight);
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/smartspace/DreamSmartspaceControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/smartspace/DreamSmartspaceControllerTest.kt
index c5432c5..a280510 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/smartspace/DreamSmartspaceControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/smartspace/DreamSmartspaceControllerTest.kt
@@ -99,8 +99,6 @@
override fun setPrimaryTextColor(color: Int) {}
- override fun setIsDreaming(isDreaming: Boolean) {}
-
override fun setUiSurface(uiSurface: String) {}
override fun setDozeAmount(amount: Float) {}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/LockscreenShadeTransitionControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/LockscreenShadeTransitionControllerTest.kt
index 702f278..d99cdd51 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/LockscreenShadeTransitionControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/LockscreenShadeTransitionControllerTest.kt
@@ -79,6 +79,7 @@
@Mock lateinit var singleShadeOverScroller: SingleShadeLockScreenOverScroller
@Mock lateinit var splitShadeOverScroller: SplitShadeLockScreenOverScroller
@Mock lateinit var qsTransitionController: LockscreenShadeQsTransitionController
+ @Mock lateinit var transitionControllerCallback: LockscreenShadeTransitionController.Callback
@JvmField @Rule val mockito = MockitoJUnit.rule()
private val configurationController = FakeConfigurationController()
@@ -124,6 +125,7 @@
},
qsTransitionControllerFactory = { qsTransitionController },
)
+ transitionController.addCallback(transitionControllerCallback)
whenever(nsslController.view).thenReturn(stackscroller)
whenever(nsslController.expandHelperCallback).thenReturn(expandHelperCallback)
transitionController.notificationPanelController = notificationPanelController
@@ -258,7 +260,7 @@
verify(nsslController, never()).setTransitionToFullShadeAmount(anyFloat())
verify(mediaHierarchyManager, never()).setTransitionToFullShadeAmount(anyFloat())
verify(scrimController, never()).setTransitionToFullShadeProgress(anyFloat(), anyFloat())
- verify(notificationPanelController, never()).setTransitionToFullShadeAmount(anyFloat(),
+ verify(transitionControllerCallback, never()).setTransitionToFullShadeAmount(anyFloat(),
anyBoolean(), anyLong())
verify(qsTransitionController, never()).dragDownAmount = anyFloat()
}
@@ -269,7 +271,7 @@
verify(nsslController).setTransitionToFullShadeAmount(anyFloat())
verify(mediaHierarchyManager).setTransitionToFullShadeAmount(anyFloat())
verify(scrimController).setTransitionToFullShadeProgress(anyFloat(), anyFloat())
- verify(notificationPanelController).setTransitionToFullShadeAmount(anyFloat(),
+ verify(transitionControllerCallback).setTransitionToFullShadeAmount(anyFloat(),
anyBoolean(), anyLong())
verify(qsTransitionController).dragDownAmount = 10f
verify(depthController).transitionToFullShadeProgress = anyFloat()
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/lockscreen/LockscreenSmartspaceControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/lockscreen/LockscreenSmartspaceControllerTest.kt
index d6225c6..7fdcfb2 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/lockscreen/LockscreenSmartspaceControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/lockscreen/LockscreenSmartspaceControllerTest.kt
@@ -780,9 +780,6 @@
override fun setPrimaryTextColor(color: Int) {
}
- override fun setIsDreaming(isDreaming: Boolean) {
- }
-
override fun setUiSurface(uiSurface: String) {
}
@@ -811,9 +808,6 @@
override fun setPrimaryTextColor(color: Int) {
}
- override fun setIsDreaming(isDreaming: Boolean) {
- }
-
override fun setUiSurface(uiSurface: String) {
}
@@ -838,9 +832,6 @@
override fun setPrimaryTextColor(color: Int) {
}
- override fun setIsDreaming(isDreaming: Boolean) {
- }
-
override fun setUiSurface(uiSurface: String) {
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotifCollectionTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotifCollectionTest.java
index 94e3e6c..edb2965 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotifCollectionTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotifCollectionTest.java
@@ -105,6 +105,7 @@
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import org.mockito.Spy;
+import org.mockito.stubbing.Answer;
import java.util.Arrays;
import java.util.Collection;
@@ -376,6 +377,90 @@
}
@Test
+ public void testScheduleBuildNotificationListWhenChannelChanged() {
+ // GIVEN
+ final NotificationEntryBuilder neb = buildNotif(TEST_PACKAGE, 48);
+ final NotificationChannel channel = new NotificationChannel(
+ "channelId",
+ "channelName",
+ NotificationManager.IMPORTANCE_DEFAULT);
+ neb.setChannel(channel);
+
+ final NotifEvent notif = mNoMan.postNotif(neb);
+ final NotificationEntry entry = mCollectionListener.getEntry(notif.key);
+
+ when(mMainHandler.hasCallbacks(any())).thenReturn(false);
+
+ clearInvocations(mBuildListener);
+
+ // WHEN
+ mNotifHandler.onNotificationChannelModified(TEST_PACKAGE,
+ entry.getSbn().getUser(), channel, NOTIFICATION_CHANNEL_OR_GROUP_UPDATED);
+
+ // THEN
+ verify(mMainHandler).postDelayed(any(), eq(1000L));
+ }
+
+ @Test
+ public void testCancelScheduledBuildNotificationListEventWhenNotifUpdatedSynchronously() {
+ // GIVEN
+ final NotificationEntry entry1 = buildNotif(TEST_PACKAGE, 1)
+ .setGroup(mContext, "group_1")
+ .build();
+ final NotificationEntry entry2 = buildNotif(TEST_PACKAGE, 2)
+ .setGroup(mContext, "group_1")
+ .setContentTitle(mContext, "New version")
+ .build();
+ final NotificationEntry entry3 = buildNotif(TEST_PACKAGE, 3)
+ .setGroup(mContext, "group_1")
+ .build();
+
+ final List<CoalescedEvent> entriesToBePosted = Arrays.asList(
+ new CoalescedEvent(entry1.getKey(), 0, entry1.getSbn(), entry1.getRanking(), null),
+ new CoalescedEvent(entry2.getKey(), 1, entry2.getSbn(), entry2.getRanking(), null),
+ new CoalescedEvent(entry3.getKey(), 2, entry3.getSbn(), entry3.getRanking(), null)
+ );
+
+ when(mMainHandler.hasCallbacks(any())).thenReturn(true);
+
+ // WHEN
+ mNotifHandler.onNotificationBatchPosted(entriesToBePosted);
+
+ // THEN
+ verify(mMainHandler).removeCallbacks(any());
+ }
+
+ @Test
+ public void testBuildNotificationListWhenChannelChanged() {
+ // GIVEN
+ final NotificationEntryBuilder neb = buildNotif(TEST_PACKAGE, 48);
+ final NotificationChannel channel = new NotificationChannel(
+ "channelId",
+ "channelName",
+ NotificationManager.IMPORTANCE_DEFAULT);
+ neb.setChannel(channel);
+
+ final NotifEvent notif = mNoMan.postNotif(neb);
+ final NotificationEntry entry = mCollectionListener.getEntry(notif.key);
+
+ when(mMainHandler.hasCallbacks(any())).thenReturn(false);
+ when(mMainHandler.postDelayed(any(), eq(1000L))).thenAnswer((Answer) invocation -> {
+ final Runnable runnable = invocation.getArgument(0);
+ runnable.run();
+ return null;
+ });
+
+ clearInvocations(mBuildListener);
+
+ // WHEN
+ mNotifHandler.onNotificationChannelModified(TEST_PACKAGE,
+ entry.getSbn().getUser(), channel, NOTIFICATION_CHANNEL_OR_GROUP_UPDATED);
+
+ // THEN
+ verifyBuiltList(List.of(entry));
+ }
+
+ @Test
public void testRankingsAreUpdatedForOtherNotifs() {
// GIVEN a collection with one notif
NotifEvent notif1 = mNoMan.postNotif(buildNotif(TEST_PACKAGE, 3)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesCommandQueueCallbacksTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesCommandQueueCallbacksTest.java
index b879cf2..48573c6 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesCommandQueueCallbacksTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesCommandQueueCallbacksTest.java
@@ -46,6 +46,7 @@
import com.android.systemui.settings.UserTracker;
import com.android.systemui.shade.CameraLauncher;
import com.android.systemui.shade.NotificationPanelViewController;
+import com.android.systemui.shade.QuickSettingsController;
import com.android.systemui.shade.ShadeController;
import com.android.systemui.statusbar.CommandQueue;
import com.android.systemui.statusbar.DisableFlagsLogger;
@@ -73,6 +74,7 @@
@Mock private CentralSurfaces mCentralSurfaces;
@Mock private ShadeController mShadeController;
@Mock private CommandQueue mCommandQueue;
+ @Mock private QuickSettingsController mQuickSettingsController;
@Mock private NotificationPanelViewController mNotificationPanelViewController;
@Mock private RemoteInputQuickSettingsDisabler mRemoteInputQuickSettingsDisabler;
private final MetricsLogger mMetricsLogger = new FakeMetricsLogger();
@@ -101,6 +103,7 @@
mSbcqCallbacks = new CentralSurfacesCommandQueueCallbacks(
mCentralSurfaces,
+ mQuickSettingsController,
mContext,
mContext.getResources(),
mShadeController,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java
index 0605398..dbf416e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java
@@ -126,6 +126,7 @@
import com.android.systemui.shade.NotificationPanelViewController;
import com.android.systemui.shade.NotificationShadeWindowView;
import com.android.systemui.shade.NotificationShadeWindowViewController;
+import com.android.systemui.shade.QuickSettingsController;
import com.android.systemui.shade.ShadeController;
import com.android.systemui.shade.ShadeControllerImpl;
import com.android.systemui.shade.ShadeExpansionStateManager;
@@ -218,6 +219,7 @@
@Mock private HeadsUpManagerPhone mHeadsUpManager;
@Mock private NotificationPanelViewController mNotificationPanelViewController;
@Mock private NotificationPanelView mNotificationPanelView;
+ @Mock private QuickSettingsController mQuickSettingsController;
@Mock private IStatusBarService mBarService;
@Mock private IDreamManager mDreamManager;
@Mock private LightRevealScrimViewModel mLightRevealScrimViewModel;
@@ -546,6 +548,7 @@
// initialized automatically and make NPVC private.
mCentralSurfaces.mNotificationShadeWindowView = mNotificationShadeWindowView;
mCentralSurfaces.mNotificationPanelViewController = mNotificationPanelViewController;
+ mCentralSurfaces.mQsController = mQuickSettingsController;
mCentralSurfaces.mDozeScrimController = mDozeScrimController;
mCentralSurfaces.mPresenter = mNotificationPresenter;
mCentralSurfaces.mKeyguardIndicationController = mKeyguardIndicationController;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenterTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenterTest.java
index 8841521..5bb25f5 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenterTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenterTest.java
@@ -43,6 +43,7 @@
import com.android.systemui.settings.FakeDisplayTracker;
import com.android.systemui.shade.NotificationPanelViewController;
import com.android.systemui.shade.NotificationShadeWindowView;
+import com.android.systemui.shade.QuickSettingsController;
import com.android.systemui.shade.ShadeController;
import com.android.systemui.statusbar.CommandQueue;
import com.android.systemui.statusbar.KeyguardIndicationController;
@@ -112,6 +113,7 @@
mStatusBarNotificationPresenter = new StatusBarNotificationPresenter(
mContext,
mock(NotificationPanelViewController.class),
+ mock(QuickSettingsController.class),
mock(HeadsUpManagerPhone.class),
notificationShadeWindowView,
mock(ActivityStarter.class),
diff --git a/services/core/java/com/android/server/audio/AudioDeviceBroker.java b/services/core/java/com/android/server/audio/AudioDeviceBroker.java
index cf880eb..6410142 100644
--- a/services/core/java/com/android/server/audio/AudioDeviceBroker.java
+++ b/services/core/java/com/android/server/audio/AudioDeviceBroker.java
@@ -1218,6 +1218,10 @@
sendILMsg(MSG_IL_BTA2DP_TIMEOUT, SENDMSG_QUEUE, a2dpCodec, address, delayMs);
}
+ /*package*/ void setLeAudioTimeout(String address, int device, int delayMs) {
+ sendILMsg(MSG_IL_BTLEAUDIO_TIMEOUT, SENDMSG_QUEUE, device, address, delayMs);
+ }
+
/*package*/ void setAvrcpAbsoluteVolumeSupported(boolean supported) {
synchronized (mDeviceStateLock) {
mBtHelper.setAvrcpAbsoluteVolumeSupported(supported);
@@ -1422,6 +1426,13 @@
mDeviceInventory.onMakeA2dpDeviceUnavailableNow((String) msg.obj, msg.arg1);
}
break;
+ case MSG_IL_BTLEAUDIO_TIMEOUT:
+ // msg.obj == address of LE Audio device
+ synchronized (mDeviceStateLock) {
+ mDeviceInventory.onMakeLeAudioDeviceUnavailableNow(
+ (String) msg.obj, msg.arg1);
+ }
+ break;
case MSG_L_A2DP_DEVICE_CONFIG_CHANGE:
final BluetoothDevice btDevice = (BluetoothDevice) msg.obj;
synchronized (mDeviceStateLock) {
@@ -1649,11 +1660,14 @@
// process set volume for Le Audio, obj is BleVolumeInfo
private static final int MSG_II_SET_LE_AUDIO_OUT_VOLUME = 46;
+ private static final int MSG_IL_BTLEAUDIO_TIMEOUT = 49;
+
private static boolean isMessageHandledUnderWakelock(int msgId) {
switch(msgId) {
case MSG_L_SET_WIRED_DEVICE_CONNECTION_STATE:
case MSG_L_SET_BT_ACTIVE_DEVICE:
case MSG_IL_BTA2DP_TIMEOUT:
+ case MSG_IL_BTLEAUDIO_TIMEOUT:
case MSG_L_A2DP_DEVICE_CONFIG_CHANGE:
case MSG_TOGGLE_HDMI:
case MSG_L_A2DP_DEVICE_CONNECTION_CHANGE_EXT:
@@ -1744,6 +1758,7 @@
case MSG_L_SET_BT_ACTIVE_DEVICE:
case MSG_L_SET_WIRED_DEVICE_CONNECTION_STATE:
case MSG_IL_BTA2DP_TIMEOUT:
+ case MSG_IL_BTLEAUDIO_TIMEOUT:
case MSG_L_A2DP_DEVICE_CONFIG_CHANGE:
if (sLastDeviceConnectMsgTime >= time) {
// add a little delay to make sure messages are ordered as expected
diff --git a/services/core/java/com/android/server/audio/AudioDeviceInventory.java b/services/core/java/com/android/server/audio/AudioDeviceInventory.java
index 35da73e..a74f4154 100644
--- a/services/core/java/com/android/server/audio/AudioDeviceInventory.java
+++ b/services/core/java/com/android/server/audio/AudioDeviceInventory.java
@@ -374,7 +374,7 @@
case BluetoothProfile.LE_AUDIO:
case BluetoothProfile.LE_AUDIO_BROADCAST:
if (switchToUnavailable) {
- makeLeAudioDeviceUnavailable(address, btInfo.mAudioSystemDevice);
+ makeLeAudioDeviceUnavailableNow(address, btInfo.mAudioSystemDevice);
} else if (switchToAvailable) {
makeLeAudioDeviceAvailable(address, BtHelper.getName(btInfo.mDevice),
streamType, btInfo.mVolume == -1 ? -1 : btInfo.mVolume * 10,
@@ -486,6 +486,12 @@
}
}
+ /*package*/ void onMakeLeAudioDeviceUnavailableNow(String address, int device) {
+ synchronized (mDevicesLock) {
+ makeLeAudioDeviceUnavailableNow(address, device);
+ }
+ }
+
/*package*/ void onReportNewRoutes() {
int n = mRoutesObservers.beginBroadcast();
if (n > 0) {
@@ -883,10 +889,11 @@
new MediaMetrics.Item(mMetricsId + "disconnectLeAudio")
.record();
if (toRemove.size() > 0) {
- final int delay = checkSendBecomingNoisyIntentInt(device, 0,
+ final int delay = checkSendBecomingNoisyIntentInt(device,
+ AudioService.CONNECTION_STATE_DISCONNECTED,
AudioSystem.DEVICE_NONE);
toRemove.stream().forEach(deviceAddress ->
- makeLeAudioDeviceUnavailable(deviceAddress, device)
+ makeLeAudioDeviceUnavailableLater(deviceAddress, device, delay)
);
}
}
@@ -1187,9 +1194,21 @@
*/
mDeviceBroker.setBluetoothA2dpOnInt(true, false /*fromA2dp*/, eventSource);
- AudioSystem.setDeviceConnectionState(new AudioDeviceAttributes(device, address, name),
+ final int res = AudioSystem.setDeviceConnectionState(new AudioDeviceAttributes(
+ device, address, name),
AudioSystem.DEVICE_STATE_AVAILABLE,
AudioSystem.AUDIO_FORMAT_DEFAULT);
+ if (res != AudioSystem.AUDIO_STATUS_OK) {
+ AudioService.sDeviceLogger.log(new AudioEventLogger.StringEvent(
+ "APM failed to make available LE Audio device addr=" + address
+ + " error=" + res).printLog(TAG));
+ // TODO: connection failed, stop here
+ // TODO: return;
+ } else {
+ AudioService.sDeviceLogger.log(new AudioEventLogger.StringEvent(
+ "LE Audio device addr=" + address + " now available").printLog(TAG));
+ }
+
mConnectedDevices.put(DeviceInfo.makeDeviceListKey(device, address),
new DeviceInfo(device, name, address, AudioSystem.AUDIO_FORMAT_DEFAULT));
mDeviceBroker.postAccessoryPlugMediaUnmute(device);
@@ -1210,11 +1229,23 @@
}
@GuardedBy("mDevicesLock")
- private void makeLeAudioDeviceUnavailable(String address, int device) {
+ private void makeLeAudioDeviceUnavailableNow(String address, int device) {
if (device != AudioSystem.DEVICE_NONE) {
- AudioSystem.setDeviceConnectionState(new AudioDeviceAttributes(device, address),
+ final int res = AudioSystem.setDeviceConnectionState(new AudioDeviceAttributes(
+ device, address),
AudioSystem.DEVICE_STATE_UNAVAILABLE,
AudioSystem.AUDIO_FORMAT_DEFAULT);
+
+ if (res != AudioSystem.AUDIO_STATUS_OK) {
+ AudioService.sDeviceLogger.log(new AudioEventLogger.StringEvent(
+ "APM failed to make unavailable LE Audio device addr=" + address
+ + " error=" + res).printLog(TAG));
+ // TODO: failed to disconnect, stop here
+ // TODO: return;
+ } else {
+ AudioService.sDeviceLogger.log((new AudioEventLogger.StringEvent(
+ "LE Audio device addr=" + address + " made unavailable")).printLog(TAG));
+ }
mConnectedDevices.remove(DeviceInfo.makeDeviceListKey(device, address));
}
@@ -1222,6 +1253,14 @@
}
@GuardedBy("mDevicesLock")
+ private void makeLeAudioDeviceUnavailableLater(String address, int device, int delayMs) {
+ // the device will be made unavailable later, so consider it disconnected right away
+ mConnectedDevices.remove(DeviceInfo.makeDeviceListKey(device, address));
+ // send the delayed message to make the device unavailable later
+ mDeviceBroker.setLeAudioTimeout(address, device, delayMs);
+ }
+
+ @GuardedBy("mDevicesLock")
private void setCurrentAudioRouteNameIfPossible(String name, boolean fromA2dp) {
synchronized (mCurAudioRoutes) {
if (TextUtils.equals(mCurAudioRoutes.bluetoothName, name)) {
diff --git a/services/core/java/com/android/server/audio/BtHelper.java b/services/core/java/com/android/server/audio/BtHelper.java
index f959821..691ce93 100644
--- a/services/core/java/com/android/server/audio/BtHelper.java
+++ b/services/core/java/com/android/server/audio/BtHelper.java
@@ -291,9 +291,9 @@
if (mA2dp == null) {
return AudioSystem.AUDIO_FORMAT_DEFAULT;
}
- final BluetoothCodecStatus btCodecStatus = null;
+ BluetoothCodecStatus btCodecStatus = null;
try {
- mA2dp.getCodecStatus(device);
+ btCodecStatus = mA2dp.getCodecStatus(device);
} catch (Exception e) {
Log.e(TAG, "Exception while getting status of " + device, e);
}
@@ -489,7 +489,7 @@
}
// @GuardedBy("AudioDeviceBroker.mSetModeLock")
- @GuardedBy("AudioDeviceBroker.mDeviceStateLock")
+ //@GuardedBy("AudioDeviceBroker.mDeviceStateLock")
/*package*/ synchronized void resetBluetoothSco() {
mScoAudioState = SCO_STATE_INACTIVE;
broadcastScoConnectionState(AudioManager.SCO_AUDIO_STATE_DISCONNECTED);
@@ -532,7 +532,7 @@
}
}
- //@GuardedBy("AudioDeviceBroker.mDeviceStateLock")
+ @GuardedBy("AudioDeviceBroker.mDeviceStateLock")
/*package*/ synchronized void onBtProfileConnected(int profile, BluetoothProfile proxy) {
if (profile == BluetoothProfile.HEADSET) {
onHeadsetProfileConnected((BluetoothHeadset) proxy);
@@ -564,7 +564,7 @@
}
// @GuardedBy("AudioDeviceBroker.mSetModeLock")
- @GuardedBy("AudioDeviceBroker.mDeviceStateLock")
+ //@GuardedBy("AudioDeviceBroker.mDeviceStateLock")
/*package*/ synchronized void onHeadsetProfileConnected(BluetoothHeadset headset) {
// Discard timeout message
mDeviceBroker.handleCancelFailureToConnectToBtHeadsetService();
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintService.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintService.java
index 598e2b9..94b67ce 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintService.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintService.java
@@ -452,6 +452,13 @@
return -1;
}
+ if (!Utils.isUserEncryptedOrLockdown(mLockPatternUtils, userId)) {
+ // If this happens, something in KeyguardUpdateMonitor is wrong. This should only
+ // ever be invoked when the user is encrypted or lockdown.
+ Slog.e(TAG, "detectFingerprint invoked when user is not encrypted or lockdown");
+ return -1;
+ }
+
final Pair<Integer, ServiceProvider> provider = getSingleProvider();
if (provider == null) {
Slog.w(TAG, "Null provider for detectFingerprint");
diff --git a/services/core/java/com/android/server/devicestate/DeviceStatePolicy.java b/services/core/java/com/android/server/devicestate/DeviceStatePolicy.java
index 5c4e2f3..c876a8b 100644
--- a/services/core/java/com/android/server/devicestate/DeviceStatePolicy.java
+++ b/services/core/java/com/android/server/devicestate/DeviceStatePolicy.java
@@ -20,6 +20,7 @@
import android.content.Context;
import android.content.res.Resources;
import android.text.TextUtils;
+import android.util.Slog;
import com.android.server.policy.DeviceStatePolicyImpl;
@@ -92,11 +93,16 @@
try {
return (DeviceStatePolicy.Provider) Class.forName(name).newInstance();
- } catch (ReflectiveOperationException | ClassCastException e) {
+ } catch (ClassCastException e) {
throw new IllegalStateException("Couldn't instantiate class " + name
+ " for config_deviceSpecificDeviceStatePolicyProvider:"
+ " make sure it has a public zero-argument constructor"
- + " and implements DeviceStatePolicy.Provider", e);
+ + " and implements DeviceStatePolicy.Provider");
+ } catch (ReflectiveOperationException e) {
+ Slog.e("DeviceStatePolicy", "Couldn't instantiate class " + name
+ + " for config_deviceSpecificDeviceStatePolicyProvider:"
+ + " using default provider", e);
+ return new DeviceStatePolicy.DefaultProvider();
}
}
}
diff --git a/services/core/java/com/android/server/display/DisplayManagerService.java b/services/core/java/com/android/server/display/DisplayManagerService.java
index 4341634..909c531 100644
--- a/services/core/java/com/android/server/display/DisplayManagerService.java
+++ b/services/core/java/com/android/server/display/DisplayManagerService.java
@@ -434,6 +434,8 @@
private boolean mIsDocked;
private boolean mIsDreaming;
+ private boolean mBootCompleted = false;
+
private final BroadcastReceiver mIdleModeReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
@@ -573,6 +575,12 @@
}
}
} else if (phase == PHASE_BOOT_COMPLETED) {
+ synchronized (mSyncRoot) {
+ mBootCompleted = true;
+ for (int i = 0; i < mDisplayPowerControllers.size(); i++) {
+ mDisplayPowerControllers.valueAt(i).onBootCompleted();
+ }
+ }
mDisplayModeDirector.onBootCompleted();
mLogicalDisplayMapper.onBootCompleted();
}
@@ -2680,7 +2688,7 @@
final DisplayPowerController displayPowerController = new DisplayPowerController(
mContext, mDisplayPowerCallbacks, mPowerHandler, mSensorManager,
mDisplayBlanker, display, mBrightnessTracker, brightnessSetting,
- () -> handleBrightnessChange(display), hbmMetadata);
+ () -> handleBrightnessChange(display), hbmMetadata, mBootCompleted);
mDisplayPowerControllers.append(display.getDisplayIdLocked(), displayPowerController);
}
diff --git a/services/core/java/com/android/server/display/DisplayPowerController.java b/services/core/java/com/android/server/display/DisplayPowerController.java
index b431306..7957ed6 100644
--- a/services/core/java/com/android/server/display/DisplayPowerController.java
+++ b/services/core/java/com/android/server/display/DisplayPowerController.java
@@ -132,6 +132,8 @@
private static final int MSG_UPDATE_RBC = 11;
private static final int MSG_BRIGHTNESS_RAMP_DONE = 12;
private static final int MSG_STATSD_HBM_BRIGHTNESS = 13;
+ private static final int MSG_SWITCH_USER = 14;
+ private static final int MSG_BOOT_COMPLETED = 15;
private static final int PROXIMITY_UNKNOWN = -1;
private static final int PROXIMITY_NEGATIVE = 0;
@@ -505,6 +507,8 @@
private boolean mIsEnabled;
private boolean mIsInTransition;
+ private boolean mBootCompleted;
+
/**
* Creates the display power controller.
*/
@@ -512,7 +516,8 @@
DisplayPowerCallbacks callbacks, Handler handler,
SensorManager sensorManager, DisplayBlanker blanker, LogicalDisplay logicalDisplay,
BrightnessTracker brightnessTracker, BrightnessSetting brightnessSetting,
- Runnable onBrightnessChangeRunnable, HighBrightnessModeMetadata hbmMetadata) {
+ Runnable onBrightnessChangeRunnable, HighBrightnessModeMetadata hbmMetadata,
+ boolean bootCompleted) {
mLogicalDisplay = logicalDisplay;
mDisplayId = mLogicalDisplay.getDisplayIdLocked();
final String displayIdStr = "[" + mDisplayId + "]";
@@ -654,6 +659,7 @@
mTemporaryAutoBrightnessAdjustment = PowerManager.BRIGHTNESS_INVALID_FLOAT;
mPendingAutoBrightnessAdjustment = PowerManager.BRIGHTNESS_INVALID_FLOAT;
+ mBootCompleted = bootCompleted;
}
private void applyReduceBrightColorsSplineAdjustment() {
@@ -703,6 +709,11 @@
}
public void onSwitchUser(@UserIdInt int newUserId) {
+ Message msg = mHandler.obtainMessage(MSG_SWITCH_USER, newUserId);
+ mHandler.sendMessage(msg);
+ }
+
+ private void handleOnSwitchUser(@UserIdInt int newUserId) {
handleSettingsChange(true /* userSwitch */);
handleBrightnessModeChange();
if (mBrightnessTracker != null) {
@@ -1364,7 +1375,7 @@
// Initialize things the first time the power state is changed.
if (mustInitialize) {
- initialize(state);
+ initialize(readyToUpdateDisplayState() ? state : Display.STATE_UNKNOWN);
}
// Animate the screen state change unless already animating.
@@ -2044,7 +2055,8 @@
}
}
- if (!reportOnly && mPowerState.getScreenState() != state) {
+ if (!reportOnly && mPowerState.getScreenState() != state
+ && readyToUpdateDisplayState()) {
Trace.traceCounter(Trace.TRACE_TAG_POWER, "ScreenState", state);
// TODO(b/153319140) remove when we can get this from the above trace invocation
SystemProperties.set("debug.tracing.screen_state", String.valueOf(state));
@@ -2491,6 +2503,10 @@
mBrightnessSetting.setBrightness(brightnessValue);
}
+ void onBootCompleted() {
+ mHandler.obtainMessage(MSG_BOOT_COMPLETED).sendToTarget();
+ }
+
private void updateScreenBrightnessSetting(float brightnessValue) {
if (!isValidBrightnessValue(brightnessValue)
|| brightnessValue == mCurrentScreenBrightnessSetting) {
@@ -2636,6 +2652,17 @@
}
};
+ /**
+ * Indicates whether the display state is ready to update. If this is the default display, we
+ * want to update it right away so that we can draw the boot animation on it. If it is not
+ * the default display, drawing the boot animation on it would look incorrect, so we need
+ * to wait until boot is completed.
+ * @return True if the display state is ready to update
+ */
+ private boolean readyToUpdateDisplayState() {
+ return mDisplayId == Display.DEFAULT_DISPLAY || mBootCompleted;
+ }
+
public void dump(final PrintWriter pw) {
synchronized (mLock) {
pw.println();
@@ -3167,6 +3194,15 @@
case MSG_STATSD_HBM_BRIGHTNESS:
logHbmBrightnessStats(Float.intBitsToFloat(msg.arg1), msg.arg2);
break;
+
+ case MSG_SWITCH_USER:
+ handleOnSwitchUser(msg.arg1);
+ break;
+
+ case MSG_BOOT_COMPLETED:
+ mBootCompleted = true;
+ updatePowerState();
+ break;
}
}
}
diff --git a/services/core/java/com/android/server/locksettings/LockSettingsService.java b/services/core/java/com/android/server/locksettings/LockSettingsService.java
index 78cffa6..59794f4 100644
--- a/services/core/java/com/android/server/locksettings/LockSettingsService.java
+++ b/services/core/java/com/android/server/locksettings/LockSettingsService.java
@@ -80,6 +80,7 @@
import android.hardware.fingerprint.FingerprintManager;
import android.net.Uri;
import android.os.Binder;
+import android.os.Build;
import android.os.Bundle;
import android.os.Handler;
import android.os.IBinder;
@@ -117,6 +118,7 @@
import android.util.ArrayMap;
import android.util.ArraySet;
import android.util.EventLog;
+import android.util.Log;
import android.util.LongSparseArray;
import android.util.Slog;
import android.util.SparseArray;
@@ -203,7 +205,7 @@
private static final String TAG = "LockSettingsService";
private static final String PERMISSION = ACCESS_KEYGUARD_SECURE_STORAGE;
private static final String BIOMETRIC_PERMISSION = MANAGE_BIOMETRIC;
- private static final boolean DEBUG = false;
+ private static final boolean DEBUG = Build.IS_DEBUGGABLE && Log.isLoggable(TAG, Log.DEBUG);
private static final int PROFILE_KEY_IV_SIZE = 12;
private static final String SEPARATE_PROFILE_CHALLENGE_KEY = "lockscreen.profilechallenge";
diff --git a/services/core/java/com/android/server/locksettings/LockSettingsStrongAuth.java b/services/core/java/com/android/server/locksettings/LockSettingsStrongAuth.java
index 1203769..678698b 100644
--- a/services/core/java/com/android/server/locksettings/LockSettingsStrongAuth.java
+++ b/services/core/java/com/android/server/locksettings/LockSettingsStrongAuth.java
@@ -25,6 +25,7 @@
import android.app.admin.DevicePolicyManager;
import android.app.trust.IStrongAuthTracker;
import android.content.Context;
+import android.os.Build;
import android.os.Handler;
import android.os.Looper;
import android.os.Message;
@@ -33,6 +34,7 @@
import android.os.SystemClock;
import android.os.UserHandle;
import android.util.ArrayMap;
+import android.util.Log;
import android.util.Slog;
import android.util.SparseBooleanArray;
import android.util.SparseIntArray;
@@ -46,8 +48,8 @@
*/
public class LockSettingsStrongAuth {
- private static final String TAG = "LockSettings";
- private static final boolean DEBUG = false;
+ private static final String TAG = "LockSettingsStrongAuth";
+ private static final boolean DEBUG = Build.IS_DEBUGGABLE && Log.isLoggable(TAG, Log.DEBUG);
private static final int MSG_REQUIRE_STRONG_AUTH = 1;
private static final int MSG_REGISTER_TRACKER = 2;
@@ -267,6 +269,7 @@
}
private void handleScheduleStrongAuthTimeout(int userId) {
+ if (DEBUG) Slog.d(TAG, "handleScheduleStrongAuthTimeout for userId=" + userId);
rescheduleStrongAuthTimeoutAlarm(mInjector.getElapsedRealtimeMs(), userId);
// cancel current non-strong biometric alarm listener for the user (if there was one)
diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java
index b5fceb5..ff10cbc 100755
--- a/services/core/java/com/android/server/notification/NotificationManagerService.java
+++ b/services/core/java/com/android/server/notification/NotificationManagerService.java
@@ -8543,6 +8543,9 @@
if (interceptBefore && !record.isIntercepted()
&& record.isNewEnoughForAlerting(System.currentTimeMillis())) {
buzzBeepBlinkLocked(record);
+
+ // Log alert after change in intercepted state to Zen Log as well
+ ZenLog.traceAlertOnUpdatedIntercept(record);
}
}
if (changed) {
diff --git a/services/core/java/com/android/server/notification/NotificationRecord.java b/services/core/java/com/android/server/notification/NotificationRecord.java
index d344306..1501d69 100644
--- a/services/core/java/com/android/server/notification/NotificationRecord.java
+++ b/services/core/java/com/android/server/notification/NotificationRecord.java
@@ -114,6 +114,8 @@
// is this notification currently being intercepted by Zen Mode?
private boolean mIntercept;
+ // has the intercept value been set explicitly? we only want to log it if new or changed
+ private boolean mInterceptSet;
// is this notification hidden since the app pkg is suspended?
private boolean mHidden;
@@ -914,6 +916,7 @@
public boolean setIntercepted(boolean intercept) {
mIntercept = intercept;
+ mInterceptSet = true;
return mIntercept;
}
@@ -934,6 +937,10 @@
return mIntercept;
}
+ public boolean hasInterceptBeenSet() {
+ return mInterceptSet;
+ }
+
public boolean isNewEnoughForAlerting(long now) {
return getFreshnessMs(now) <= MAX_SOUND_DELAY_MS;
}
diff --git a/services/core/java/com/android/server/notification/ZenLog.java b/services/core/java/com/android/server/notification/ZenLog.java
index c0bc474..35b94e7 100644
--- a/services/core/java/com/android/server/notification/ZenLog.java
+++ b/services/core/java/com/android/server/notification/ZenLog.java
@@ -68,20 +68,23 @@
private static final int TYPE_MATCHES_CALL_FILTER = 18;
private static final int TYPE_RECORD_CALLER = 19;
private static final int TYPE_CHECK_REPEAT_CALLER = 20;
+ private static final int TYPE_ALERT_ON_UPDATED_INTERCEPT = 21;
private static int sNext;
private static int sSize;
public static void traceIntercepted(NotificationRecord record, String reason) {
- if (record != null && record.isIntercepted()) return; // already logged
append(TYPE_INTERCEPTED, record.getKey() + "," + reason);
}
public static void traceNotIntercepted(NotificationRecord record, String reason) {
- if (record != null && record.isUpdate) return; // already logged
append(TYPE_NOT_INTERCEPTED, record.getKey() + "," + reason);
}
+ public static void traceAlertOnUpdatedIntercept(NotificationRecord record) {
+ append(TYPE_ALERT_ON_UPDATED_INTERCEPT, record.getKey());
+ }
+
public static void traceSetRingerModeExternal(int ringerModeOld, int ringerModeNew,
String caller, int ringerModeInternalIn, int ringerModeInternalOut) {
append(TYPE_SET_RINGER_MODE_EXTERNAL, caller + ",e:" +
@@ -219,6 +222,7 @@
case TYPE_MATCHES_CALL_FILTER: return "matches_call_filter";
case TYPE_RECORD_CALLER: return "record_caller";
case TYPE_CHECK_REPEAT_CALLER: return "check_repeat_caller";
+ case TYPE_ALERT_ON_UPDATED_INTERCEPT: return "alert_on_updated_intercept";
default: return "unknown";
}
}
diff --git a/services/core/java/com/android/server/notification/ZenModeFiltering.java b/services/core/java/com/android/server/notification/ZenModeFiltering.java
index db0ce2e..5b7b0c1 100644
--- a/services/core/java/com/android/server/notification/ZenModeFiltering.java
+++ b/services/core/java/com/android/server/notification/ZenModeFiltering.java
@@ -155,85 +155,85 @@
if (isCritical(record)) {
// Zen mode is ignored for critical notifications.
- ZenLog.traceNotIntercepted(record, "criticalNotification");
+ maybeLogInterceptDecision(record, false, "criticalNotification");
return false;
}
// Make an exception to policy for the notification saying that policy has changed
if (NotificationManager.Policy.areAllVisualEffectsSuppressed(policy.suppressedVisualEffects)
&& "android".equals(record.getSbn().getPackageName())
&& SystemMessageProto.SystemMessage.NOTE_ZEN_UPGRADE == record.getSbn().getId()) {
- ZenLog.traceNotIntercepted(record, "systemDndChangedNotification");
+ maybeLogInterceptDecision(record, false, "systemDndChangedNotification");
return false;
}
switch (zen) {
case Global.ZEN_MODE_NO_INTERRUPTIONS:
// #notevenalarms
- ZenLog.traceIntercepted(record, "none");
+ maybeLogInterceptDecision(record, true, "none");
return true;
case Global.ZEN_MODE_ALARMS:
if (isAlarm(record)) {
// Alarms only
- ZenLog.traceNotIntercepted(record, "alarm");
+ maybeLogInterceptDecision(record, false, "alarm");
return false;
}
- ZenLog.traceIntercepted(record, "alarmsOnly");
+ maybeLogInterceptDecision(record, true, "alarmsOnly");
return true;
case Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS:
// allow user-prioritized packages through in priority mode
if (record.getPackagePriority() == Notification.PRIORITY_MAX) {
- ZenLog.traceNotIntercepted(record, "priorityApp");
+ maybeLogInterceptDecision(record, false, "priorityApp");
return false;
}
if (isAlarm(record)) {
if (!policy.allowAlarms()) {
- ZenLog.traceIntercepted(record, "!allowAlarms");
+ maybeLogInterceptDecision(record, true, "!allowAlarms");
return true;
}
- ZenLog.traceNotIntercepted(record, "allowedAlarm");
+ maybeLogInterceptDecision(record, false, "allowedAlarm");
return false;
}
if (isEvent(record)) {
if (!policy.allowEvents()) {
- ZenLog.traceIntercepted(record, "!allowEvents");
+ maybeLogInterceptDecision(record, true, "!allowEvents");
return true;
}
- ZenLog.traceNotIntercepted(record, "allowedEvent");
+ maybeLogInterceptDecision(record, false, "allowedEvent");
return false;
}
if (isReminder(record)) {
if (!policy.allowReminders()) {
- ZenLog.traceIntercepted(record, "!allowReminders");
+ maybeLogInterceptDecision(record, true, "!allowReminders");
return true;
}
- ZenLog.traceNotIntercepted(record, "allowedReminder");
+ maybeLogInterceptDecision(record, false, "allowedReminder");
return false;
}
if (isMedia(record)) {
if (!policy.allowMedia()) {
- ZenLog.traceIntercepted(record, "!allowMedia");
+ maybeLogInterceptDecision(record, true, "!allowMedia");
return true;
}
- ZenLog.traceNotIntercepted(record, "allowedMedia");
+ maybeLogInterceptDecision(record, false, "allowedMedia");
return false;
}
if (isSystem(record)) {
if (!policy.allowSystem()) {
- ZenLog.traceIntercepted(record, "!allowSystem");
+ maybeLogInterceptDecision(record, true, "!allowSystem");
return true;
}
- ZenLog.traceNotIntercepted(record, "allowedSystem");
+ maybeLogInterceptDecision(record, false, "allowedSystem");
return false;
}
if (isConversation(record)) {
if (policy.allowConversations()) {
if (policy.priorityConversationSenders == CONVERSATION_SENDERS_ANYONE) {
- ZenLog.traceNotIntercepted(record, "conversationAnyone");
+ maybeLogInterceptDecision(record, false, "conversationAnyone");
return false;
} else if (policy.priorityConversationSenders
== NotificationManager.Policy.CONVERSATION_SENDERS_IMPORTANT
&& record.getChannel().isImportantConversation()) {
- ZenLog.traceNotIntercepted(record, "conversationMatches");
+ maybeLogInterceptDecision(record, false, "conversationMatches");
return false;
}
}
@@ -244,31 +244,59 @@
if (policy.allowRepeatCallers()
&& REPEAT_CALLERS.isRepeat(
mContext, extras(record), record.getPhoneNumbers())) {
- ZenLog.traceNotIntercepted(record, "repeatCaller");
+ maybeLogInterceptDecision(record, false, "repeatCaller");
return false;
}
if (!policy.allowCalls()) {
- ZenLog.traceIntercepted(record, "!allowCalls");
+ maybeLogInterceptDecision(record, true, "!allowCalls");
return true;
}
return shouldInterceptAudience(policy.allowCallsFrom(), record);
}
if (isMessage(record)) {
if (!policy.allowMessages()) {
- ZenLog.traceIntercepted(record, "!allowMessages");
+ maybeLogInterceptDecision(record, true, "!allowMessages");
return true;
}
return shouldInterceptAudience(policy.allowMessagesFrom(), record);
}
- ZenLog.traceIntercepted(record, "!priority");
+ maybeLogInterceptDecision(record, true, "!priority");
return true;
default:
- ZenLog.traceNotIntercepted(record, "unknownZenMode");
+ maybeLogInterceptDecision(record, false, "unknownZenMode");
return false;
}
}
+ // Consider logging the decision of shouldIntercept for the given record.
+ // This will log the outcome if one of the following is true:
+ // - it's the first time the intercept decision is set for the record
+ // - OR it's not the first time, but the intercept decision changed
+ private static void maybeLogInterceptDecision(NotificationRecord record, boolean intercept,
+ String reason) {
+ boolean interceptBefore = record.isIntercepted();
+ if (record.hasInterceptBeenSet() && (interceptBefore == intercept)) {
+ // this record has already been evaluated for whether it should be intercepted, and
+ // the decision has not changed.
+ return;
+ }
+
+ // add a note to the reason indicating whether it's new or updated
+ String annotatedReason = reason;
+ if (!record.hasInterceptBeenSet()) {
+ annotatedReason = "new:" + reason;
+ } else if (interceptBefore != intercept) {
+ annotatedReason = "updated:" + reason;
+ }
+
+ if (intercept) {
+ ZenLog.traceIntercepted(record, annotatedReason);
+ } else {
+ ZenLog.traceNotIntercepted(record, annotatedReason);
+ }
+ }
+
/**
* Check if the notification is too critical to be suppressed.
*
@@ -285,10 +313,10 @@
private static boolean shouldInterceptAudience(int source, NotificationRecord record) {
float affinity = record.getContactAffinity();
if (!audienceMatches(source, affinity)) {
- ZenLog.traceIntercepted(record, "!audienceMatches,affinity=" + affinity);
+ maybeLogInterceptDecision(record, true, "!audienceMatches,affinity=" + affinity);
return true;
}
- ZenLog.traceNotIntercepted(record, "affinity=" + affinity);
+ maybeLogInterceptDecision(record, false, "affinity=" + affinity);
return false;
}
diff --git a/services/core/java/com/android/server/pm/permission/PermissionManagerServiceImpl.java b/services/core/java/com/android/server/pm/permission/PermissionManagerServiceImpl.java
index 20c9a21..9ed5aa7 100644
--- a/services/core/java/com/android/server/pm/permission/PermissionManagerServiceImpl.java
+++ b/services/core/java/com/android/server/pm/permission/PermissionManagerServiceImpl.java
@@ -3268,7 +3268,7 @@
if (Objects.equals(packageName, PLATFORM_PACKAGE_NAME)) {
return true;
}
- if (!pkg.isPrivileged()) {
+ if (!(pkg.isSystem() && pkg.isPrivileged())) {
return true;
}
if (!mPrivilegedPermissionAllowlistSourcePackageNames
diff --git a/services/core/java/com/android/server/wm/LetterboxUiController.java b/services/core/java/com/android/server/wm/LetterboxUiController.java
index 5a481f4..e343768 100644
--- a/services/core/java/com/android/server/wm/LetterboxUiController.java
+++ b/services/core/java/com/android/server/wm/LetterboxUiController.java
@@ -962,8 +962,8 @@
&& (parentConfiguration.orientation == ORIENTATION_LANDSCAPE
&& mActivityRecord.getOrientationForReachability() == ORIENTATION_PORTRAIT)
// Check whether the activity fills the parent vertically.
- && parentConfiguration.windowConfiguration.getBounds().height()
- == mActivityRecord.getBounds().height();
+ && parentConfiguration.windowConfiguration.getAppBounds().height()
+ <= mActivityRecord.getBounds().height();
}
@VisibleForTesting
diff --git a/services/tests/servicestests/src/com/android/server/devicestate/DeviceStatePolicyProviderTest.java b/services/tests/servicestests/src/com/android/server/devicestate/DeviceStatePolicyProviderTest.java
index 0bd81b7..18dc35c 100644
--- a/services/tests/servicestests/src/com/android/server/devicestate/DeviceStatePolicyProviderTest.java
+++ b/services/tests/servicestests/src/com/android/server/devicestate/DeviceStatePolicyProviderTest.java
@@ -16,6 +16,8 @@
package com.android.server.devicestate;
+import static org.hamcrest.Matchers.instanceOf;
+import static org.junit.Assert.assertThat;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
import static org.testng.Assert.assertThrows;
@@ -24,8 +26,6 @@
import android.content.res.Resources;
import android.platform.test.annotations.Presubmit;
-import org.hamcrest.Matchers;
-import org.junit.Assert;
import org.junit.Test;
/**
@@ -39,37 +39,35 @@
@Test
public void test_emptyPolicyProvider() {
- Assert.assertThat(DeviceStatePolicy.Provider.fromResources(resourcesWithProvider("")),
- Matchers.instanceOf(DeviceStatePolicy.DefaultProvider.class));
+ assertThat(DeviceStatePolicy.Provider.fromResources(resourcesWithProvider("")),
+ instanceOf(DeviceStatePolicy.DefaultProvider.class));
}
@Test
public void test_nullPolicyProvider() {
- Assert.assertThat(DeviceStatePolicy.Provider.fromResources(resourcesWithProvider(null)),
- Matchers.instanceOf(DeviceStatePolicy.DefaultProvider.class));
+ assertThat(DeviceStatePolicy.Provider.fromResources(resourcesWithProvider(null)),
+ instanceOf(DeviceStatePolicy.DefaultProvider.class));
}
@Test
public void test_customPolicyProvider() {
- Assert.assertThat(DeviceStatePolicy.Provider.fromResources(resourcesWithProvider(
- TestProvider.class.getName())),
- Matchers.instanceOf(TestProvider.class));
+ assertThat(DeviceStatePolicy.Provider.fromResources(resourcesWithProvider(
+ TestProvider.class.getName())),
+ instanceOf(TestProvider.class));
}
@Test
public void test_badPolicyProvider_notImplementingProviderInterface() {
- assertThrows(IllegalStateException.class, () -> {
- DeviceStatePolicy.Provider.fromResources(resourcesWithProvider(
- Object.class.getName()));
- });
+ assertThrows(IllegalStateException.class, () ->
+ DeviceStatePolicy.Provider.fromResources(resourcesWithProvider(
+ Object.class.getName())));
}
@Test
- public void test_badPolicyProvider_doesntExist() {
- assertThrows(IllegalStateException.class, () -> {
- DeviceStatePolicy.Provider.fromResources(resourcesWithProvider(
- "com.android.devicestate.nonexistent.policy"));
- });
+ public void test_badPolicyProvider_returnsDefault() {
+ assertThat(DeviceStatePolicy.Provider.fromResources(
+ resourcesWithProvider("com.android.devicestate.nonexistent.policy")),
+ instanceOf(DeviceStatePolicy.DefaultProvider.class));
}
private static Resources resourcesWithProvider(String provider) {
diff --git a/tools/aapt2/SdkConstants.cpp b/tools/aapt2/SdkConstants.cpp
index 8ea43abf..34e8edb 100644
--- a/tools/aapt2/SdkConstants.cpp
+++ b/tools/aapt2/SdkConstants.cpp
@@ -27,7 +27,7 @@
static ApiVersion sDevelopmentSdkLevel = 10000;
static const auto sDevelopmentSdkCodeNames =
- std::unordered_set<StringPiece>({"Q", "R", "S", "Sv2", "Tiramisu"});
+ std::unordered_set<StringPiece>({"Q", "R", "S", "Sv2", "Tiramisu", "UpsideDownCake"});
static const std::vector<std::pair<uint16_t, ApiVersion>> sAttrIdMap = {
{0x021c, 1},