summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--core/java/android/app/NotificationManager.java15
-rw-r--r--core/java/com/android/internal/policy/GestureNavigationSettingsObserver.java52
-rw-r--r--libs/WindowManager/Shell/res/values/dimen.xml24
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/ProtoLogController.java5
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/common/DevicePostureController.java147
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java11
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipAnimationController.java2
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java6
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/protolog/ShellProtoLogImpl.java3
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java14
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragResizeInputListener.java1
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/DevicePostureControllerTest.java109
-rw-r--r--media/tests/MediaFrameworkTest/AndroidManifest.xml1
-rw-r--r--packages/SystemUI/docs/device-entry/quickaffordance.md5
-rw-r--r--packages/SystemUI/res/values/strings.xml2
-rw-r--r--packages/SystemUI/shared/src/com/android/systemui/flags/Flag.kt2
-rw-r--r--packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java3
-rw-r--r--packages/SystemUI/src/com/android/systemui/classifier/BrightLineFalsingManager.java2
-rw-r--r--packages/SystemUI/src/com/android/systemui/classifier/FalsingDataProvider.java4
-rw-r--r--packages/SystemUI/src/com/android/systemui/flags/Flags.kt5
-rw-r--r--packages/SystemUI/src/com/android/systemui/flags/ServerFlagReader.kt1
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/CustomizationProvider.kt12
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractor.kt63
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/QSFragment.java24
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/logging/QSLogger.kt24
-rw-r--r--packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java7
-rw-r--r--packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowView.java4
-rw-r--r--packages/SystemUI/src/com/android/systemui/shade/QuickSettingsController.java1
-rw-r--r--packages/SystemUI/src/com/android/systemui/shade/ShadeLogger.kt11
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifCollection.java37
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotificationMemoryLogger.kt114
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/row/FooterView.java1
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java4
-rw-r--r--packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java8
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/classifier/BrightLineClassifierTest.java2
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/classifier/BrightLineFalsingManagerTest.java4
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/classifier/FalsingDataProviderTest.java10
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/flags/ServerFlagReaderImplTest.kt13
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/keyguard/CustomizationProviderTest.kt7
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorParameterizedTest.kt98
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorTest.kt42
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBottomAreaViewModelTest.kt38
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/qs/QSFragmentTest.java9
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotifCollectionTest.java85
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/logging/NotificationMemoryLoggerTest.kt300
-rw-r--r--services/core/java/com/android/server/audio/AudioDeviceBroker.java15
-rw-r--r--services/core/java/com/android/server/audio/AudioDeviceInventory.java51
-rw-r--r--services/core/java/com/android/server/audio/BtHelper.java10
-rw-r--r--services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintService.java7
-rw-r--r--services/core/java/com/android/server/display/DisplayManagerService.java10
-rw-r--r--services/core/java/com/android/server/display/DisplayPowerController.java32
-rwxr-xr-xservices/core/java/com/android/server/notification/NotificationManagerService.java3
-rw-r--r--services/core/java/com/android/server/notification/NotificationRecord.java7
-rw-r--r--services/core/java/com/android/server/notification/ZenLog.java8
-rw-r--r--services/core/java/com/android/server/notification/ZenModeFiltering.java78
-rw-r--r--services/core/java/com/android/server/wm/LetterboxUiController.java4
56 files changed, 1308 insertions, 249 deletions
diff --git a/core/java/android/app/NotificationManager.java b/core/java/android/app/NotificationManager.java
index f6d27ad08b00..37a90de8d600 100644
--- a/core/java/android/app/NotificationManager.java
+++ b/core/java/android/app/NotificationManager.java
@@ -317,7 +317,10 @@ public class NotificationManager {
/**
* Intent that is broadcast when the state of {@link #getEffectsSuppressor()} changes.
- * This broadcast is only sent to registered receivers.
+ *
+ * <p>This broadcast is only sent to registered receivers and (starting from
+ * {@link Build.VERSION_CODES#Q}) receivers in packages that have been granted Do Not
+ * Disturb access (see {@link #isNotificationPolicyAccessGranted()}).
*
* @hide
*/
@@ -337,7 +340,10 @@ public class NotificationManager {
/**
* Intent that is broadcast when the state of getNotificationPolicy() changes.
- * This broadcast is only sent to registered receivers.
+ *
+ * <p>This broadcast is only sent to registered receivers and (starting from
+ * {@link Build.VERSION_CODES#Q}) receivers in packages that have been granted Do Not
+ * Disturb access (see {@link #isNotificationPolicyAccessGranted()}).
*/
@SdkConstant(SdkConstant.SdkConstantType.BROADCAST_INTENT_ACTION)
public static final String ACTION_NOTIFICATION_POLICY_CHANGED
@@ -345,7 +351,10 @@ public class NotificationManager {
/**
* Intent that is broadcast when the state of getCurrentInterruptionFilter() changes.
- * This broadcast is only sent to registered receivers.
+ *
+ * <p>This broadcast is only sent to registered receivers and (starting from
+ * {@link Build.VERSION_CODES#Q}) receivers in packages that have been granted Do Not
+ * Disturb access (see {@link #isNotificationPolicyAccessGranted()}).
*/
@SdkConstant(SdkConstant.SdkConstantType.BROADCAST_INTENT_ACTION)
public static final String ACTION_INTERRUPTION_FILTER_CHANGED
diff --git a/core/java/com/android/internal/policy/GestureNavigationSettingsObserver.java b/core/java/com/android/internal/policy/GestureNavigationSettingsObserver.java
index d2b612a9e6f3..f1ed3bed5d89 100644
--- a/core/java/com/android/internal/policy/GestureNavigationSettingsObserver.java
+++ b/core/java/com/android/internal/policy/GestureNavigationSettingsObserver.java
@@ -56,6 +56,9 @@ public class GestureNavigationSettingsObserver extends ContentObserver {
}
};
+ /**
+ * Registers the observer for all users.
+ */
public void register() {
ContentResolver r = mContext.getContentResolver();
r.registerContentObserver(
@@ -73,7 +76,10 @@ public class GestureNavigationSettingsObserver extends ContentObserver {
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 @@ public class GestureNavigationSettingsObserver extends ContentObserver {
}
}
+ /**
+ * 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 @@ public class GestureNavigationSettingsObserver extends ContentObserver {
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 @@ public class GestureNavigationSettingsObserver extends ContentObserver {
: 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 6f31d0674246..1f9b6cf6c64f 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/ProtoLogController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/ProtoLogController.java
index 88525aabe53b..e2012b4e36dc 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/ProtoLogController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/ProtoLogController.java
@@ -16,6 +16,8 @@
package com.android.wm.shell;
+import android.os.Build;
+
import com.android.wm.shell.protolog.ShellProtoLogImpl;
import com.android.wm.shell.sysui.ShellCommandHandler;
import com.android.wm.shell.sysui.ShellInit;
@@ -41,6 +43,9 @@ public class ProtoLogController implements ShellCommandHandler.ShellCommandActio
void onInit() {
mShellCommandHandler.addCommandCallback("protolog", this, this);
+ if (Build.IS_DEBUGGABLE) {
+ mShellProtoLog.startProtoLog(null /* PrintWriter */);
+ }
}
@Override
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/DevicePostureController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/DevicePostureController.java
new file mode 100644
index 000000000000..22587f4c6456
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/DevicePostureController.java
@@ -0,0 +1,147 @@
+/*
+ * 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.wm.shell.common;
+
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.content.Context;
+import android.hardware.devicestate.DeviceStateManager;
+import android.util.SparseIntArray;
+
+import com.android.internal.R;
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.wm.shell.sysui.ShellInit;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Wrapper class to track the device posture change on Fold-ables.
+ * See also <a
+ * href="https://developer.android.com/guide/topics/large-screens/learn-about-foldables
+ * #foldable_postures">Foldable states and postures</a> for reference.
+ *
+ * Note that most of the implementation here inherits from
+ * {@link com.android.systemui.statusbar.policy.DevicePostureController}.
+ */
+public class DevicePostureController {
+ @IntDef(prefix = {"DEVICE_POSTURE_"}, value = {
+ DEVICE_POSTURE_UNKNOWN,
+ DEVICE_POSTURE_CLOSED,
+ DEVICE_POSTURE_HALF_OPENED,
+ DEVICE_POSTURE_OPENED,
+ DEVICE_POSTURE_FLIPPED
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface DevicePostureInt {}
+
+ // NOTE: These constants **must** match those defined for Jetpack Sidecar. This is because we
+ // use the Device State -> Jetpack Posture map to translate between the two.
+ public static final int DEVICE_POSTURE_UNKNOWN = 0;
+ public static final int DEVICE_POSTURE_CLOSED = 1;
+ public static final int DEVICE_POSTURE_HALF_OPENED = 2;
+ public static final int DEVICE_POSTURE_OPENED = 3;
+ public static final int DEVICE_POSTURE_FLIPPED = 4;
+
+ private final Context mContext;
+ private final ShellExecutor mMainExecutor;
+ private final List<OnDevicePostureChangedListener> mListeners = new ArrayList<>();
+ private final SparseIntArray mDeviceStateToPostureMap = new SparseIntArray();
+
+ private int mDevicePosture = DEVICE_POSTURE_UNKNOWN;
+
+ public DevicePostureController(
+ Context context, ShellInit shellInit, ShellExecutor mainExecutor) {
+ mContext = context;
+ mMainExecutor = mainExecutor;
+ shellInit.addInitCallback(this::onInit, this);
+ }
+
+ private void onInit() {
+ // Most of this is borrowed from WindowManager/Jetpack/DeviceStateManagerPostureProducer.
+ // Using the sidecar/extension libraries directly brings in a new dependency that it'd be
+ // good to avoid (along with the fact that sidecar is deprecated, and extensions isn't fully
+ // ready yet), and we'd have to make our own layer over the sidecar library anyway to easily
+ // allow the implementation to change, so it was easier to just interface with
+ // DeviceStateManager directly.
+ String[] deviceStatePosturePairs = mContext.getResources()
+ .getStringArray(R.array.config_device_state_postures);
+ for (String deviceStatePosturePair : deviceStatePosturePairs) {
+ String[] deviceStatePostureMapping = deviceStatePosturePair.split(":");
+ if (deviceStatePostureMapping.length != 2) {
+ continue;
+ }
+
+ int deviceState;
+ int posture;
+ try {
+ deviceState = Integer.parseInt(deviceStatePostureMapping[0]);
+ posture = Integer.parseInt(deviceStatePostureMapping[1]);
+ } catch (NumberFormatException e) {
+ continue;
+ }
+
+ mDeviceStateToPostureMap.put(deviceState, posture);
+ }
+
+ final DeviceStateManager deviceStateManager = mContext.getSystemService(
+ DeviceStateManager.class);
+ if (deviceStateManager != null) {
+ deviceStateManager.registerCallback(mMainExecutor, state -> onDevicePostureChanged(
+ mDeviceStateToPostureMap.get(state, DEVICE_POSTURE_UNKNOWN)));
+ }
+ }
+
+ @VisibleForTesting
+ void onDevicePostureChanged(int devicePosture) {
+ if (devicePosture == mDevicePosture) return;
+ mDevicePosture = devicePosture;
+ mListeners.forEach(l -> l.onDevicePostureChanged(mDevicePosture));
+ }
+
+ /**
+ * Register {@link OnDevicePostureChangedListener} for device posture changes.
+ * The listener will receive callback with current device posture upon registration.
+ */
+ public void registerOnDevicePostureChangedListener(
+ @NonNull OnDevicePostureChangedListener listener) {
+ if (mListeners.contains(listener)) return;
+ mListeners.add(listener);
+ listener.onDevicePostureChanged(mDevicePosture);
+ }
+
+ /**
+ * Unregister {@link OnDevicePostureChangedListener} for device posture changes.
+ */
+ public void unregisterOnDevicePostureChangedListener(
+ @NonNull OnDevicePostureChangedListener listener) {
+ mListeners.remove(listener);
+ }
+
+ /**
+ * Listener interface for device posture change.
+ */
+ public interface OnDevicePostureChangedListener {
+ /**
+ * Callback when device posture changes.
+ * See {@link DevicePostureInt} for callback values.
+ */
+ void onDevicePostureChanged(@DevicePostureInt int posture);
+ }
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java
index 25c430c27457..72dc771ee08c 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java
@@ -41,6 +41,7 @@ import com.android.wm.shell.back.BackAnimation;
import com.android.wm.shell.back.BackAnimationController;
import com.android.wm.shell.bubbles.BubbleController;
import com.android.wm.shell.bubbles.Bubbles;
+import com.android.wm.shell.common.DevicePostureController;
import com.android.wm.shell.common.DisplayController;
import com.android.wm.shell.common.DisplayImeController;
import com.android.wm.shell.common.DisplayInsetsController;
@@ -160,6 +161,16 @@ public abstract class WMShellBaseModule {
@WMSingleton
@Provides
+ static DevicePostureController provideDevicePostureController(
+ Context context,
+ ShellInit shellInit,
+ @ShellMainThread ShellExecutor mainExecutor
+ ) {
+ return new DevicePostureController(context, shellInit, mainExecutor);
+ }
+
+ @WMSingleton
+ @Provides
static DragAndDropController provideDragAndDropController(Context context,
ShellInit shellInit,
ShellController shellController,
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 d9ac76e833d8..23f73f614294 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 @@ public class PipAnimationController {
/**
* 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 ec34f73126fb..fa3efeb51bd0 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 @@ public class PipController implements PipTransitionController.PipTransitionCallb
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/protolog/ShellProtoLogImpl.java b/libs/WindowManager/Shell/src/com/android/wm/shell/protolog/ShellProtoLogImpl.java
index 93ffb3dc8115..c59c42dadb9d 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/protolog/ShellProtoLogImpl.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/protolog/ShellProtoLogImpl.java
@@ -31,8 +31,7 @@ import java.io.PrintWriter;
*/
public class ShellProtoLogImpl extends BaseProtoLogImpl {
private static final String TAG = "ProtoLogImpl";
- private static final int BUFFER_CAPACITY = 1024 * 1024;
- // TODO: find a proper location to save the protolog message file
+ private static final int BUFFER_CAPACITY = 128 * 1024;
private static final String LOG_FILENAME = "/data/misc/wmtrace/shell_log.winscope";
private static final String VIEWER_CONFIG_FILENAME = "/system_ext/etc/wmshell.protolog.json.gz";
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 0c3eaf0b904f..2a6fbd2cee8c 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 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
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 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
}
}
- if (!isEnteringSplit && openingToSide) {
+ if (!isEnteringSplit && apps != null) {
final WindowContainerTransaction evictWct = new WindowContainerTransaction();
prepareEvictNonOpeningChildTasks(position, apps, evictWct);
mSyncQueue.queue(evictWct);
@@ -598,6 +603,11 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
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 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
}
- 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 7d954ad92285..81c4176b0f39 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 @@ class DragResizeInputListener implements AutoCloseable {
@Override
public void close() {
+ mInputEventReceiver.dispose();
mInputChannel.dispose();
try {
mWindowSession.remove(mFakeWindow);
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/DevicePostureControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/DevicePostureControllerTest.java
new file mode 100644
index 000000000000..f8ee300e411c
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/DevicePostureControllerTest.java
@@ -0,0 +1,109 @@
+/*
+ * 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.wm.shell.common;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.clearInvocations;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyZeroInteractions;
+
+import android.content.Context;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.filters.SmallTest;
+
+import com.android.wm.shell.sysui.ShellInit;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Captor;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class DevicePostureControllerTest {
+ @Mock
+ private Context mContext;
+
+ @Mock
+ private ShellInit mShellInit;
+
+ @Mock
+ private ShellExecutor mMainExecutor;
+
+ @Captor
+ private ArgumentCaptor<Integer> mDevicePostureCaptor;
+
+ @Mock
+ private DevicePostureController.OnDevicePostureChangedListener mOnDevicePostureChangedListener;
+
+ private DevicePostureController mDevicePostureController;
+
+ @Before
+ public void setUp() {
+ MockitoAnnotations.initMocks(this);
+ mDevicePostureController = new DevicePostureController(mContext, mShellInit, mMainExecutor);
+ }
+
+ @Test
+ public void instantiateController_addInitCallback() {
+ verify(mShellInit, times(1)).addInitCallback(any(), eq(mDevicePostureController));
+ }
+
+ @Test
+ public void registerOnDevicePostureChangedListener_callbackCurrentPosture() {
+ mDevicePostureController.registerOnDevicePostureChangedListener(
+ mOnDevicePostureChangedListener);
+ verify(mOnDevicePostureChangedListener, times(1))
+ .onDevicePostureChanged(anyInt());
+ }
+
+ @Test
+ public void onDevicePostureChanged_differentPosture_callbackListener() {
+ mDevicePostureController.registerOnDevicePostureChangedListener(
+ mOnDevicePostureChangedListener);
+ verify(mOnDevicePostureChangedListener).onDevicePostureChanged(
+ mDevicePostureCaptor.capture());
+ clearInvocations(mOnDevicePostureChangedListener);
+
+ int differentDevicePosture = mDevicePostureCaptor.getValue() + 1;
+ mDevicePostureController.onDevicePostureChanged(differentDevicePosture);
+
+ verify(mOnDevicePostureChangedListener, times(1))
+ .onDevicePostureChanged(differentDevicePosture);
+ }
+
+ @Test
+ public void onDevicePostureChanged_samePosture_doesNotCallbackListener() {
+ mDevicePostureController.registerOnDevicePostureChangedListener(
+ mOnDevicePostureChangedListener);
+ verify(mOnDevicePostureChangedListener).onDevicePostureChanged(
+ mDevicePostureCaptor.capture());
+ clearInvocations(mOnDevicePostureChangedListener);
+
+ int sameDevicePosture = mDevicePostureCaptor.getValue();
+ mDevicePostureController.onDevicePostureChanged(sameDevicePosture);
+
+ verifyZeroInteractions(mOnDevicePostureChangedListener);
+ }
+}
diff --git a/media/tests/MediaFrameworkTest/AndroidManifest.xml b/media/tests/MediaFrameworkTest/AndroidManifest.xml
index 33872ed63b89..0da5de75548c 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/SystemUI/docs/device-entry/quickaffordance.md b/packages/SystemUI/docs/device-entry/quickaffordance.md
index 79d5718efe0d..d662649ac419 100644
--- a/packages/SystemUI/docs/device-entry/quickaffordance.md
+++ b/packages/SystemUI/docs/device-entry/quickaffordance.md
@@ -52,6 +52,11 @@ A picker experience may:
* 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/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index e6ac59e6b106..464ce0333fd1 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/shared/src/com/android/systemui/flags/Flag.kt b/packages/SystemUI/shared/src/com/android/systemui/flags/Flag.kt
index c9a25b067b94..b6aae69ebad6 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/flags/Flag.kt
+++ b/packages/SystemUI/shared/src/com/android/systemui/flags/Flag.kt
@@ -145,7 +145,7 @@ data class SysPropBooleanFlag constructor(
override val namespace: String,
override val default: Boolean = false,
) : SysPropFlag<Boolean> {
- // TODO(b/223379190): Teamfood not supported for sysprop flags yet.
+ // TODO(b/268520433): Teamfood not supported for sysprop flags yet.
override val teamfood: Boolean = false
}
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
index be9264dbfcf3..8e11a990a4dc 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
@@ -2765,7 +2765,8 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab
boolean shouldListen = shouldListenKeyguardState && shouldListenUserState
&& shouldListenBouncerState && shouldListenUdfpsState
- && shouldListenSideFpsState;
+ && shouldListenSideFpsState
+ && !isFingerprintLockedOut();
logListenerModelData(
new KeyguardFingerprintListenModel(
System.currentTimeMillis(),
diff --git a/packages/SystemUI/src/com/android/systemui/classifier/BrightLineFalsingManager.java b/packages/SystemUI/src/com/android/systemui/classifier/BrightLineFalsingManager.java
index e9ac840cf4f4..f6b71336675f 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 @@ public class BrightLineFalsingManager implements FalsingManager {
|| 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 5f347c158818..bc0f9950f865 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 @@ public class FalsingDataProvider {
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/flags/Flags.kt b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
index 537401f3ffd2..6bc1eddf26f7 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 @@ object Flags {
)
// 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)
@@ -600,7 +600,8 @@ object Flags {
@JvmField val UDFPS_ELLIPSE_DETECTION = unreleasedFlag(2202, "udfps_ellipse_detection")
// 2300 - stylus
- @JvmField val TRACK_STYLUS_EVER_USED = releasedFlag(2300, "track_stylus_ever_used")
+ @JvmField
+ val TRACK_STYLUS_EVER_USED = unreleasedFlag(2300, "track_stylus_ever_used", teamfood = true)
@JvmField val ENABLE_STYLUS_CHARGING_UI = unreleasedFlag(2301, "enable_stylus_charging_ui")
@JvmField
val ENABLE_USI_BATTERY_NOTIFICATIONS = unreleasedFlag(2302, "enable_usi_battery_notifications")
diff --git a/packages/SystemUI/src/com/android/systemui/flags/ServerFlagReader.kt b/packages/SystemUI/src/com/android/systemui/flags/ServerFlagReader.kt
index 7acd3f3447dd..9b748d0a0eb2 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 @@ class ServerFlagReaderImpl @Inject constructor(
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 680c504d50fc..27a5974e6299 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 @@ class CustomizationProvider :
throw UnsupportedOperationException()
}
- return insertSelection(values)
+ return runBlocking(mainDispatcher) { insertSelection(values) }
}
override fun query(
@@ -171,7 +171,7 @@ class CustomizationProvider :
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 @@ class CustomizationProvider :
}
}
- 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 @@ class CustomizationProvider :
}
}
- private fun querySlots(): Cursor {
+ private suspend fun querySlots(): Cursor {
return MatrixCursor(
arrayOf(
Contract.LockScreenQuickAffordances.SlotTable.Columns.ID,
@@ -330,7 +330,7 @@ class CustomizationProvider :
}
}
- private fun queryFlags(): Cursor {
+ private suspend fun queryFlags(): Cursor {
return MatrixCursor(
arrayOf(
Contract.FlagsTable.Columns.NAME,
@@ -353,7 +353,7 @@ class CustomizationProvider :
}
}
- private fun deleteSelection(
+ private suspend fun deleteSelection(
uri: Uri,
selectionArgs: Array<out String>?,
): Int {
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 b2da793bb8e0..dfbe1c216847 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.phone.SystemUIDialog
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 @@ constructor(
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 @@ constructor(
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 @@ constructor(
*
* @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 @@ constructor(
* @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 @@ constructor(
}
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 @@ constructor(
/** 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 @@ constructor(
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 @@ constructor(
),
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 @@ constructor(
)
}
+ 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/qs/QSFragment.java b/packages/SystemUI/src/com/android/systemui/qs/QSFragment.java
index 8ad102ece9b3..ae965d37557f 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSFragment.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSFragment.java
@@ -51,9 +51,7 @@ import com.android.systemui.animation.Interpolators;
import com.android.systemui.animation.ShadeInterpolation;
import com.android.systemui.compose.ComposeFacade;
import com.android.systemui.dump.DumpManager;
-import com.android.systemui.flags.FeatureFlags;
import com.android.systemui.media.controls.ui.MediaHost;
-import com.android.systemui.plugins.FalsingManager;
import com.android.systemui.plugins.qs.QS;
import com.android.systemui.plugins.qs.QSContainerController;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
@@ -61,6 +59,7 @@ import com.android.systemui.qs.customize.QSCustomizerController;
import com.android.systemui.qs.dagger.QSFragmentComponent;
import com.android.systemui.qs.footer.ui.binder.FooterActionsViewBinder;
import com.android.systemui.qs.footer.ui.viewmodel.FooterActionsViewModel;
+import com.android.systemui.qs.logging.QSLogger;
import com.android.systemui.statusbar.CommandQueue;
import com.android.systemui.statusbar.StatusBarState;
import com.android.systemui.statusbar.SysuiStatusBarStateController;
@@ -88,14 +87,11 @@ public class QSFragment extends LifecycleFragment implements QS, CommandQueue.Ca
private final Rect mQsBounds = new Rect();
private final SysuiStatusBarStateController mStatusBarStateController;
- private final FalsingManager mFalsingManager;
private final KeyguardBypassController mBypassController;
private boolean mQsExpanded;
private boolean mHeaderAnimating;
private boolean mStackScrollerOverscrolling;
- private long mDelay;
-
private QSAnimator mQSAnimator;
private HeightListener mPanelView;
private QSSquishinessController mQSSquishinessController;
@@ -116,8 +112,7 @@ public class QSFragment extends LifecycleFragment implements QS, CommandQueue.Ca
private final MediaHost mQqsMediaHost;
private final QSFragmentComponent.Factory mQsComponentFactory;
private final QSFragmentDisableFlagsLogger mQsFragmentDisableFlagsLogger;
- private final QSTileHost mHost;
- private final FeatureFlags mFeatureFlags;
+ private final QSLogger mLogger;
private final FooterActionsController mFooterActionsController;
private final FooterActionsViewModel.Factory mFooterActionsViewModelFactory;
private final ListeningAndVisibilityLifecycleOwner mListeningAndVisibilityLifecycleOwner;
@@ -150,11 +145,6 @@ public class QSFragment extends LifecycleFragment implements QS, CommandQueue.Ca
*/
private boolean mTransitioningToFullShade;
- /**
- * Whether the next Quick settings
- */
- private boolean mAnimateNextQsUpdate;
-
private final DumpManager mDumpManager;
/**
@@ -178,14 +168,13 @@ public class QSFragment extends LifecycleFragment implements QS, CommandQueue.Ca
@Inject
public QSFragment(RemoteInputQuickSettingsDisabler remoteInputQsDisabler,
- QSTileHost qsTileHost,
SysuiStatusBarStateController statusBarStateController, CommandQueue commandQueue,
@Named(QS_PANEL) MediaHost qsMediaHost,
@Named(QUICK_QS_PANEL) MediaHost qqsMediaHost,
KeyguardBypassController keyguardBypassController,
QSFragmentComponent.Factory qsComponentFactory,
QSFragmentDisableFlagsLogger qsFragmentDisableFlagsLogger,
- FalsingManager falsingManager, DumpManager dumpManager, FeatureFlags featureFlags,
+ DumpManager dumpManager, QSLogger qsLogger,
FooterActionsController footerActionsController,
FooterActionsViewModel.Factory footerActionsViewModelFactory) {
mRemoteInputQuickSettingsDisabler = remoteInputQsDisabler;
@@ -193,13 +182,11 @@ public class QSFragment extends LifecycleFragment implements QS, CommandQueue.Ca
mQqsMediaHost = qqsMediaHost;
mQsComponentFactory = qsComponentFactory;
mQsFragmentDisableFlagsLogger = qsFragmentDisableFlagsLogger;
+ mLogger = qsLogger;
commandQueue.observe(getLifecycle(), this);
- mHost = qsTileHost;
- mFalsingManager = falsingManager;
mBypassController = keyguardBypassController;
mStatusBarStateController = statusBarStateController;
mDumpManager = dumpManager;
- mFeatureFlags = featureFlags;
mFooterActionsController = footerActionsController;
mFooterActionsViewModelFactory = footerActionsViewModelFactory;
mListeningAndVisibilityLifecycleOwner = new ListeningAndVisibilityLifecycleOwner();
@@ -716,8 +703,10 @@ public class QSFragment extends LifecycleFragment implements QS, CommandQueue.Ca
private void setAlphaAnimationProgress(float progress) {
final View view = getView();
if (progress == 0 && view.getVisibility() != View.INVISIBLE) {
+ mLogger.logVisibility("QS fragment", View.INVISIBLE);
view.setVisibility(View.INVISIBLE);
} else if (progress > 0 && view.getVisibility() != View.VISIBLE) {
+ mLogger.logVisibility("QS fragment", View.VISIBLE);
view.setVisibility((View.VISIBLE));
}
view.setAlpha(interpolateAlphaAnimationProgress(progress));
@@ -914,7 +903,6 @@ public class QSFragment extends LifecycleFragment implements QS, CommandQueue.Ca
getView().getViewTreeObserver().removeOnPreDrawListener(this);
getView().animate()
.translationY(0f)
- .setStartDelay(mDelay)
.setDuration(StackStateAnimator.ANIMATION_DURATION_GO_TO_FULL_SHADE)
.setInterpolator(Interpolators.FAST_OUT_SLOW_IN)
.setListener(mAnimateHeaderSlidingInListener)
diff --git a/packages/SystemUI/src/com/android/systemui/qs/logging/QSLogger.kt b/packages/SystemUI/src/com/android/systemui/qs/logging/QSLogger.kt
index d32ef327e90e..23c41db6d5a6 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/logging/QSLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/logging/QSLogger.kt
@@ -17,15 +17,14 @@
package com.android.systemui.qs.logging
import android.service.quicksettings.Tile
+import android.view.View
import com.android.systemui.log.dagger.QSLog
import com.android.systemui.plugins.log.ConstantStringsLogger
import com.android.systemui.plugins.log.ConstantStringsLoggerImpl
import com.android.systemui.plugins.log.LogBuffer
-import com.android.systemui.plugins.log.LogLevel
import com.android.systemui.plugins.log.LogLevel.DEBUG
import com.android.systemui.plugins.log.LogLevel.ERROR
import com.android.systemui.plugins.log.LogLevel.VERBOSE
-import com.android.systemui.plugins.log.LogMessage
import com.android.systemui.plugins.qs.QSTile
import com.android.systemui.statusbar.StatusBarState
import com.google.errorprone.annotations.CompileTimeConstant
@@ -332,4 +331,25 @@ class QSLogger @Inject constructor(@QSLog private val buffer: LogBuffer) :
else -> "wrong state"
}
}
+
+ fun logVisibility(viewName: String, @View.Visibility visibility: Int) {
+ buffer.log(
+ TAG,
+ DEBUG,
+ {
+ str1 = viewName
+ str2 = toVisibilityString(visibility)
+ },
+ { "$str1 visibility: $str2" }
+ )
+ }
+
+ private fun toVisibilityString(visibility: Int): String {
+ return when (visibility) {
+ View.VISIBLE -> "VISIBLE"
+ View.INVISIBLE -> "INVISIBLE"
+ View.GONE -> "GONE"
+ else -> "undefined"
+ }
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
index 1259f5eed87a..2175a3358396 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
@@ -2206,7 +2206,12 @@ public final class NotificationPanelViewController implements Dumpable {
&& 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 {
diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowView.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowView.java
index 1f0cbf9af51c..74a61a3efebe 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 @@ public class NotificationShadeWindowView extends FrameLayout {
@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/QuickSettingsController.java b/packages/SystemUI/src/com/android/systemui/shade/QuickSettingsController.java
index 6bf73dcfaac0..d041212d24c7 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/QuickSettingsController.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/QuickSettingsController.java
@@ -835,6 +835,7 @@ public class QuickSettingsController {
@VisibleForTesting
void setExpandImmediate(boolean expandImmediate) {
if (expandImmediate != mExpandImmediate) {
+ mShadeLog.logQsExpandImmediateChanged(expandImmediate);
mExpandImmediate = expandImmediate;
mShadeExpansionStateManager.notifyExpandImmediateChange(expandImmediate);
}
diff --git a/packages/SystemUI/src/com/android/systemui/shade/ShadeLogger.kt b/packages/SystemUI/src/com/android/systemui/shade/ShadeLogger.kt
index b28509e8fbf5..aa8c5b65e0fe 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/ShadeLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/ShadeLogger.kt
@@ -150,6 +150,17 @@ class ShadeLogger @Inject constructor(@ShadeLog private val buffer: LogBuffer) {
)
}
+ fun logQsExpandImmediateChanged(newValue: Boolean) {
+ buffer.log(
+ TAG,
+ LogLevel.VERBOSE,
+ {
+ bool1 = newValue
+ },
+ { "qsExpandImmediate=$bool1" }
+ )
+ }
+
fun logQsExpansionChanged(
message: String,
qsExpanded: Boolean,
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 df35c9e6832a..aa9a6c2c4cc6 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 @@ public class NotifCollection implements Dumpable, PipelineDumpable {
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 @@ public class NotifCollection implements Dumpable, PipelineDumpable {
int modificationType) {
Assert.isMainThread();
mEventQueue.add(new ChannelChangedEvent(pkgName, user, channel, modificationType));
- dispatchEventsAndRebuildList("onNotificationChannelModified");
+ dispatchEventsAndAsynchronouslyRebuildList();
}
private void onNotificationsInitialized() {
@@ -613,15 +618,39 @@ public class NotifCollection implements Dumpable, PipelineDumpable {
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/logging/NotificationMemoryLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotificationMemoryLogger.kt
index cc1103de8ec0..abe067039cd9 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotificationMemoryLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotificationMemoryLogger.kt
@@ -20,6 +20,7 @@ package com.android.systemui.statusbar.notification.logging
import android.app.StatsManager
import android.util.Log
import android.util.StatsEvent
+import androidx.annotation.VisibleForTesting
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.dagger.qualifiers.Main
@@ -143,67 +144,70 @@ constructor(
runBlocking(mainDispatcher) {
traceSection("NML#getNotifications") { notificationPipeline.allNotifs }
}
+}
- /** Aggregates memory usage data by package and style, returning sums. */
- private fun aggregateMemoryUsageData(
- notificationMemoryUse: List<NotificationMemoryUsage>
- ): Map<Pair<String, Int>, NotificationMemoryUseAtomBuilder> {
- return notificationMemoryUse
- .groupingBy { Pair(it.packageName, it.objectUsage.style) }
- .aggregate {
- _,
- accumulator: NotificationMemoryUseAtomBuilder?,
- element: NotificationMemoryUsage,
- first ->
- val use =
- if (first) {
- NotificationMemoryUseAtomBuilder(element.uid, element.objectUsage.style)
- } else {
- accumulator!!
- }
-
- use.count++
- // If the views of the notification weren't inflated, the list of memory usage
- // parameters will be empty.
- if (element.viewUsage.isNotEmpty()) {
- use.countWithInflatedViews++
+/** Aggregates memory usage data by package and style, returning sums. */
+@VisibleForTesting
+internal fun aggregateMemoryUsageData(
+ notificationMemoryUse: List<NotificationMemoryUsage>
+): Map<Pair<String, Int>, NotificationMemoryLogger.NotificationMemoryUseAtomBuilder> {
+ return notificationMemoryUse
+ .groupingBy { Pair(it.packageName, it.objectUsage.style) }
+ .aggregate {
+ _,
+ accumulator: NotificationMemoryLogger.NotificationMemoryUseAtomBuilder?,
+ element: NotificationMemoryUsage,
+ first ->
+ val use =
+ if (first) {
+ NotificationMemoryLogger.NotificationMemoryUseAtomBuilder(
+ element.uid,
+ element.objectUsage.style
+ )
+ } else {
+ accumulator!!
}
- use.smallIconObject += element.objectUsage.smallIcon
- if (element.objectUsage.smallIcon > 0) {
- use.smallIconBitmapCount++
- }
+ use.count++
+ // If the views of the notification weren't inflated, the list of memory usage
+ // parameters will be empty.
+ if (element.viewUsage.isNotEmpty()) {
+ use.countWithInflatedViews++
+ }
- use.largeIconObject += element.objectUsage.largeIcon
- if (element.objectUsage.largeIcon > 0) {
- use.largeIconBitmapCount++
- }
+ use.smallIconObject += element.objectUsage.smallIcon
+ if (element.objectUsage.smallIcon > 0) {
+ use.smallIconBitmapCount++
+ }
- use.bigPictureObject += element.objectUsage.bigPicture
- if (element.objectUsage.bigPicture > 0) {
- use.bigPictureBitmapCount++
- }
+ use.largeIconObject += element.objectUsage.largeIcon
+ if (element.objectUsage.largeIcon > 0) {
+ use.largeIconBitmapCount++
+ }
- use.extras += element.objectUsage.extras
- use.extenders += element.objectUsage.extender
-
- // Use totals count which are more accurate when aggregated
- // in this manner.
- element.viewUsage
- .firstOrNull { vu -> vu.viewType == ViewType.TOTAL }
- ?.let {
- use.smallIconViews += it.smallIcon
- use.largeIconViews += it.largeIcon
- use.systemIconViews += it.systemIcons
- use.styleViews += it.style
- use.customViews += it.style
- use.softwareBitmaps += it.softwareBitmapsPenalty
- }
-
- return@aggregate use
+ use.bigPictureObject += element.objectUsage.bigPicture
+ if (element.objectUsage.bigPicture > 0) {
+ use.bigPictureBitmapCount++
}
- }
- /** Rounds the passed value to the nearest KB - e.g. 700B rounds to 1KB. */
- private fun toKb(value: Int): Int = (value.toFloat() / 1024f).roundToInt()
+ use.extras += element.objectUsage.extras
+ use.extenders += element.objectUsage.extender
+
+ // Use totals count which are more accurate when aggregated
+ // in this manner.
+ element.viewUsage
+ .firstOrNull { vu -> vu.viewType == ViewType.TOTAL }
+ ?.let {
+ use.smallIconViews += it.smallIcon
+ use.largeIconViews += it.largeIcon
+ use.systemIconViews += it.systemIcons
+ use.styleViews += it.style
+ use.customViews += it.customViews
+ use.softwareBitmaps += it.softwareBitmapsPenalty
+ }
+
+ return@aggregate use
+ }
}
+/** Rounds the passed value to the nearest KB - e.g. 700B rounds to 1KB. */
+private fun toKb(value: Int): Int = (value.toFloat() / 1024f).roundToInt()
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 21f4cb566e28..49f17b664a20 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 @@ public class FooterView extends StackScrollerDecorView {
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 1fb7eb5106e6..d2087ba6ca1c 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 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable
@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/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java
index 6b80494a0c30..dc90e2d0a656 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.STR
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 @@ public class KeyguardUpdateMonitorTest extends SysuiTestCase {
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/classifier/BrightLineClassifierTest.java b/packages/SystemUI/tests/src/com/android/systemui/classifier/BrightLineClassifierTest.java
index e4df754ec96a..8cb91304808d 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 @@ public class BrightLineClassifierTest extends SysuiTestCase {
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 ae38eb67c431..315774aad71a 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 @@ public class BrightLineFalsingManagerTest extends SysuiTestCase {
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 @@ public class BrightLineFalsingManagerTest extends SysuiTestCase {
@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 c451a1e754c9..2edc3d361316 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 @@ public class FalsingDataProviderTest extends ClassifierTest {
@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/flags/ServerFlagReaderImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/flags/ServerFlagReaderImplTest.kt
index a12315b63fa7..2e9800606edf 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.Before
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 @@ class ServerFlagReaderImplTest : SysuiTestCase() {
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 15a454b3a24e..a4e5bcaecde4 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.mockito.whenever
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 @@ class CustomizationProviderTest : SysuiTestCase() {
@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 @@ class CustomizationProviderTest : SysuiTestCase() {
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 @@ class CustomizationProviderTest : SysuiTestCase() {
featureFlags = featureFlags,
repository = { quickAffordanceRepository },
launchAnimator = launchAnimator,
+ devicePolicyManager = devicePolicyManager,
+ backgroundDispatcher = testDispatcher,
)
underTest.previewManager =
KeyguardRemotePreviewManager(
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 23e06ec181c0..84ec125bfa55 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.mockito.whenever
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.verify
import org.mockito.Mockito.verifyZeroInteractions
import org.mockito.MockitoAnnotations
+@OptIn(ExperimentalCoroutinesApi::class)
@SmallTest
@RunWith(Parameterized::class)
class KeyguardQuickAffordanceInteractorParameterizedTest : SysuiTestCase() {
@@ -219,8 +224,10 @@ class KeyguardQuickAffordanceInteractorParameterizedTest : SysuiTestCase() {
@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 @@ class KeyguardQuickAffordanceInteractorParameterizedTest : SysuiTestCase() {
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 @@ class KeyguardQuickAffordanceInteractorParameterizedTest : SysuiTestCase() {
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 =
- if (startActivity) {
- KeyguardQuickAffordanceConfig.OnTriggeredResult.StartActivity(
- intent = INTENT,
- canShowWhileLocked = canShowWhileLocked,
- )
- } else {
- KeyguardQuickAffordanceConfig.OnTriggeredResult.Handled
- }
+ 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,
- )
+ underTest.onQuickAffordanceTriggered(
+ configKey = BuiltInKeyguardQuickAffordanceKeys.HOME_CONTROLS,
+ expandable = expandable,
+ )
- if (startActivity) {
- if (needsToUnlockFirst) {
- verify(activityStarter)
- .postStartActivityDismissingKeyguard(
- any(),
- /* delay= */ eq(0),
- same(animationController),
- )
+ 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 {
- verify(activityStarter)
- .startActivity(
- any(),
- /* dismissShade= */ eq(true),
- same(animationController),
- /* showOverLockscreenWhenLocked= */ eq(true),
- )
+ verifyZeroInteractions(activityStarter)
}
- } 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 1b8c6273e2d8..62c9e5ffbb51 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 @@ class KeyguardQuickAffordanceInteractorTest : SysuiTestCase() {
@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 @@ class KeyguardQuickAffordanceInteractorTest : SysuiTestCase() {
featureFlags = featureFlags,
repository = { quickAffordanceRepository },
launchAnimator = launchAnimator,
+ devicePolicyManager = devicePolicyManager,
+ backgroundDispatcher = testDispatcher,
)
}
@@ -239,6 +243,44 @@ class KeyguardQuickAffordanceInteractorTest : SysuiTestCase() {
}
@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 6afeddda18ab..8bd8be565eee 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 @@ class KeyguardBottomAreaViewModelTest : SysuiTestCase() {
@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 @@ class KeyguardBottomAreaViewModelTest : SysuiTestCase() {
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 @@ class KeyguardBottomAreaViewModelTest : SysuiTestCase() {
featureFlags = featureFlags,
repository = { quickAffordanceRepository },
launchAnimator = launchAnimator,
+ devicePolicyManager = devicePolicyManager,
+ backgroundDispatcher = testDispatcher,
),
bottomAreaInteractor = KeyguardBottomAreaInteractor(repository = repository),
burnInHelperWrapper = burnInHelperWrapper,
@@ -240,6 +245,39 @@ class KeyguardBottomAreaViewModelTest : SysuiTestCase() {
}
@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/QSFragmentTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/QSFragmentTest.java
index 4caa50fa847d..89606bf6be3d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/QSFragmentTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/QSFragmentTest.java
@@ -52,14 +52,13 @@ import com.android.systemui.R;
import com.android.systemui.SysuiBaseFragmentTest;
import com.android.systemui.animation.ShadeInterpolation;
import com.android.systemui.dump.DumpManager;
-import com.android.systemui.flags.FakeFeatureFlags;
import com.android.systemui.media.controls.ui.MediaHost;
-import com.android.systemui.plugins.FalsingManager;
import com.android.systemui.qs.customize.QSCustomizerController;
import com.android.systemui.qs.dagger.QSFragmentComponent;
import com.android.systemui.qs.external.TileServiceRequestController;
import com.android.systemui.qs.footer.ui.binder.FooterActionsViewBinder;
import com.android.systemui.qs.footer.ui.viewmodel.FooterActionsViewModel;
+import com.android.systemui.qs.logging.QSLogger;
import com.android.systemui.settings.FakeDisplayTracker;
import com.android.systemui.statusbar.CommandQueue;
import com.android.systemui.statusbar.StatusBarState;
@@ -86,7 +85,6 @@ public class QSFragmentTest extends SysuiBaseFragmentTest {
@Mock private MediaHost mQSMediaHost;
@Mock private MediaHost mQQSMediaHost;
@Mock private KeyguardBypassController mBypassController;
- @Mock private FalsingManager mFalsingManager;
@Mock private TileServiceRequestController.Builder mTileServiceRequestControllerBuilder;
@Mock private TileServiceRequestController mTileServiceRequestController;
@Mock private QSCustomizerController mQsCustomizerController;
@@ -503,11 +501,9 @@ public class QSFragmentTest extends SysuiBaseFragmentTest {
setUpMedia();
setUpOther();
- FakeFeatureFlags featureFlags = new FakeFeatureFlags();
return new QSFragment(
new RemoteInputQuickSettingsDisabler(
context, commandQueue, mock(ConfigurationController.class)),
- mock(QSTileHost.class),
mStatusBarStateController,
commandQueue,
mQSMediaHost,
@@ -515,9 +511,8 @@ public class QSFragmentTest extends SysuiBaseFragmentTest {
mBypassController,
mQsComponentFactory,
mock(QSFragmentDisableFlagsLogger.class),
- mFalsingManager,
mock(DumpManager.class),
- featureFlags,
+ mock(QSLogger.class),
mock(FooterActionsController.class),
mFooterActionsViewModelFactory);
}
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 94e3e6cb5f8e..edb2965eabfa 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.InOrder;
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 @@ public class NotifCollectionTest extends SysuiTestCase {
}
@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/notification/logging/NotificationMemoryLoggerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/logging/NotificationMemoryLoggerTest.kt
index bd039031cecc..33a838ed5183 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/logging/NotificationMemoryLoggerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/logging/NotificationMemoryLoggerTest.kt
@@ -20,6 +20,7 @@ import android.app.Notification
import android.app.StatsManager
import android.graphics.Bitmap
import android.graphics.drawable.Icon
+import android.stats.sysui.NotificationEnums
import android.testing.AndroidTestingRunner
import android.util.StatsEvent
import androidx.test.filters.SmallTest
@@ -31,10 +32,12 @@ import com.android.systemui.util.concurrency.FakeExecutor
import com.android.systemui.util.mockito.mock
import com.android.systemui.util.mockito.whenever
import com.android.systemui.util.time.FakeSystemClock
+import com.google.common.truth.Expect
import com.google.common.truth.Truth.assertThat
import java.lang.RuntimeException
import kotlinx.coroutines.Dispatchers
import org.junit.Before
+import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.Mock
@@ -45,6 +48,8 @@ import org.mockito.MockitoAnnotations
@RunWith(AndroidTestingRunner::class)
class NotificationMemoryLoggerTest : SysuiTestCase() {
+ @Rule @JvmField val expect = Expect.create()
+
private val bgExecutor = FakeExecutor(FakeSystemClock())
private val immediate = Dispatchers.Main.immediate
@@ -132,6 +137,123 @@ class NotificationMemoryLoggerTest : SysuiTestCase() {
.isEqualTo(StatsManager.PULL_SKIP)
}
+ @Test
+ fun aggregateMemoryUsageData_returnsCorrectlyAggregatedSamePackageData() {
+ val usage = getPresetMemoryUsages()
+ val aggregateUsage = aggregateMemoryUsageData(usage)
+
+ assertThat(aggregateUsage).hasSize(3)
+ assertThat(aggregateUsage)
+ .containsKey(Pair("package 1", NotificationEnums.STYLE_BIG_PICTURE))
+
+ // Aggregated fields
+ val aggregatedData =
+ aggregateUsage[Pair("package 1", NotificationEnums.STYLE_BIG_PICTURE)]!!
+ val presetUsage1 = usage[0]
+ val presetUsage2 = usage[1]
+ assertAggregatedData(
+ aggregatedData,
+ 2,
+ 2,
+ smallIconObject =
+ presetUsage1.objectUsage.smallIcon + presetUsage2.objectUsage.smallIcon,
+ smallIconBitmapCount = 2,
+ largeIconObject =
+ presetUsage1.objectUsage.largeIcon + presetUsage2.objectUsage.largeIcon,
+ largeIconBitmapCount = 2,
+ bigPictureObject =
+ presetUsage1.objectUsage.bigPicture + presetUsage2.objectUsage.bigPicture,
+ bigPictureBitmapCount = 2,
+ extras = presetUsage1.objectUsage.extras + presetUsage2.objectUsage.extras,
+ extenders = presetUsage1.objectUsage.extender + presetUsage2.objectUsage.extender,
+ // Only totals need to be summarized.
+ smallIconViews =
+ presetUsage1.viewUsage[0].smallIcon + presetUsage2.viewUsage[0].smallIcon,
+ largeIconViews =
+ presetUsage1.viewUsage[0].largeIcon + presetUsage2.viewUsage[0].largeIcon,
+ systemIconViews =
+ presetUsage1.viewUsage[0].systemIcons + presetUsage2.viewUsage[0].systemIcons,
+ styleViews = presetUsage1.viewUsage[0].style + presetUsage2.viewUsage[0].style,
+ customViews =
+ presetUsage1.viewUsage[0].customViews + presetUsage2.viewUsage[0].customViews,
+ softwareBitmaps =
+ presetUsage1.viewUsage[0].softwareBitmapsPenalty +
+ presetUsage2.viewUsage[0].softwareBitmapsPenalty,
+ seenCount = 0
+ )
+ }
+
+ @Test
+ fun aggregateMemoryUsageData_correctlySeparatesDifferentStyles() {
+ val usage = getPresetMemoryUsages()
+ val aggregateUsage = aggregateMemoryUsageData(usage)
+
+ assertThat(aggregateUsage).hasSize(3)
+ assertThat(aggregateUsage)
+ .containsKey(Pair("package 1", NotificationEnums.STYLE_BIG_PICTURE))
+ assertThat(aggregateUsage).containsKey(Pair("package 1", NotificationEnums.STYLE_BIG_TEXT))
+
+ // Different style should be separate
+ val separateStyleData =
+ aggregateUsage[Pair("package 1", NotificationEnums.STYLE_BIG_TEXT)]!!
+ val presetUsage = usage[2]
+ assertAggregatedData(
+ separateStyleData,
+ 1,
+ 1,
+ presetUsage.objectUsage.smallIcon,
+ 1,
+ presetUsage.objectUsage.largeIcon,
+ 1,
+ presetUsage.objectUsage.bigPicture,
+ 1,
+ presetUsage.objectUsage.extras,
+ presetUsage.objectUsage.extender,
+ presetUsage.viewUsage[0].smallIcon,
+ presetUsage.viewUsage[0].largeIcon,
+ presetUsage.viewUsage[0].systemIcons,
+ presetUsage.viewUsage[0].style,
+ presetUsage.viewUsage[0].customViews,
+ presetUsage.viewUsage[0].softwareBitmapsPenalty,
+ 0
+ )
+ }
+
+ @Test
+ fun aggregateMemoryUsageData_correctlySeparatesDifferentProcess() {
+ val usage = getPresetMemoryUsages()
+ val aggregateUsage = aggregateMemoryUsageData(usage)
+
+ assertThat(aggregateUsage).hasSize(3)
+ assertThat(aggregateUsage)
+ .containsKey(Pair("package 2", NotificationEnums.STYLE_BIG_PICTURE))
+
+ // Different UID/package should also be separate
+ val separatePackageData =
+ aggregateUsage[Pair("package 2", NotificationEnums.STYLE_BIG_PICTURE)]!!
+ val presetUsage = usage[3]
+ assertAggregatedData(
+ separatePackageData,
+ 1,
+ 1,
+ presetUsage.objectUsage.smallIcon,
+ 1,
+ presetUsage.objectUsage.largeIcon,
+ 1,
+ presetUsage.objectUsage.bigPicture,
+ 1,
+ presetUsage.objectUsage.extras,
+ presetUsage.objectUsage.extender,
+ presetUsage.viewUsage[0].smallIcon,
+ presetUsage.viewUsage[0].largeIcon,
+ presetUsage.viewUsage[0].systemIcons,
+ presetUsage.viewUsage[0].style,
+ presetUsage.viewUsage[0].customViews,
+ presetUsage.viewUsage[0].softwareBitmapsPenalty,
+ 0
+ )
+ }
+
private fun createLoggerWithNotifications(
notifications: List<Notification>
): NotificationMemoryLogger {
@@ -143,4 +265,182 @@ class NotificationMemoryLoggerTest : SysuiTestCase() {
whenever(pipeline.allNotifs).thenReturn(notifications)
return NotificationMemoryLogger(pipeline, statsManager, immediate, bgExecutor)
}
+
+ /**
+ * Short hand for making sure the passed NotificationMemoryUseAtomBuilder object contains
+ * expected values.
+ */
+ private fun assertAggregatedData(
+ value: NotificationMemoryLogger.NotificationMemoryUseAtomBuilder,
+ count: Int,
+ countWithInflatedViews: Int,
+ smallIconObject: Int,
+ smallIconBitmapCount: Int,
+ largeIconObject: Int,
+ largeIconBitmapCount: Int,
+ bigPictureObject: Int,
+ bigPictureBitmapCount: Int,
+ extras: Int,
+ extenders: Int,
+ smallIconViews: Int,
+ largeIconViews: Int,
+ systemIconViews: Int,
+ styleViews: Int,
+ customViews: Int,
+ softwareBitmaps: Int,
+ seenCount: Int
+ ) {
+ expect.withMessage("count").that(value.count).isEqualTo(count)
+ expect
+ .withMessage("countWithInflatedViews")
+ .that(value.countWithInflatedViews)
+ .isEqualTo(countWithInflatedViews)
+ expect.withMessage("smallIconObject").that(value.smallIconObject).isEqualTo(smallIconObject)
+ expect
+ .withMessage("smallIconBitmapCount")
+ .that(value.smallIconBitmapCount)
+ .isEqualTo(smallIconBitmapCount)
+ expect.withMessage("largeIconObject").that(value.largeIconObject).isEqualTo(largeIconObject)
+ expect
+ .withMessage("largeIconBitmapCount")
+ .that(value.largeIconBitmapCount)
+ .isEqualTo(largeIconBitmapCount)
+ expect
+ .withMessage("bigPictureObject")
+ .that(value.bigPictureObject)
+ .isEqualTo(bigPictureObject)
+ expect
+ .withMessage("bigPictureBitmapCount")
+ .that(value.bigPictureBitmapCount)
+ .isEqualTo(bigPictureBitmapCount)
+ expect.withMessage("extras").that(value.extras).isEqualTo(extras)
+ expect.withMessage("extenders").that(value.extenders).isEqualTo(extenders)
+ expect.withMessage("smallIconViews").that(value.smallIconViews).isEqualTo(smallIconViews)
+ expect.withMessage("largeIconViews").that(value.largeIconViews).isEqualTo(largeIconViews)
+ expect.withMessage("systemIconViews").that(value.systemIconViews).isEqualTo(systemIconViews)
+ expect.withMessage("styleViews").that(value.styleViews).isEqualTo(styleViews)
+ expect.withMessage("customViews").that(value.customViews).isEqualTo(customViews)
+ expect.withMessage("softwareBitmaps").that(value.softwareBitmaps).isEqualTo(softwareBitmaps)
+ expect.withMessage("seenCount").that(value.seenCount).isEqualTo(seenCount)
+ }
+
+ /** Generates a static set of [NotificationMemoryUsage] objects. */
+ private fun getPresetMemoryUsages() =
+ listOf(
+ // A pair of notifications that have to be aggregated, same UID and style
+ NotificationMemoryUsage(
+ "package 1",
+ 384,
+ "key1",
+ Notification.Builder(context).setStyle(Notification.BigPictureStyle()).build(),
+ NotificationObjectUsage(
+ 23,
+ 45,
+ 67,
+ NotificationEnums.STYLE_BIG_PICTURE,
+ 12,
+ 483,
+ 4382,
+ true
+ ),
+ listOf(
+ NotificationViewUsage(ViewType.TOTAL, 493, 584, 4833, 584, 4888, 5843),
+ NotificationViewUsage(
+ ViewType.PRIVATE_CONTRACTED_VIEW,
+ 100,
+ 250,
+ 300,
+ 594,
+ 6000,
+ 5843
+ )
+ )
+ ),
+ NotificationMemoryUsage(
+ "package 1",
+ 384,
+ "key2",
+ Notification.Builder(context).setStyle(Notification.BigPictureStyle()).build(),
+ NotificationObjectUsage(
+ 77,
+ 54,
+ 34,
+ NotificationEnums.STYLE_BIG_PICTURE,
+ 77,
+ 432,
+ 2342,
+ true
+ ),
+ listOf(
+ NotificationViewUsage(ViewType.TOTAL, 3245, 1234, 7653, 543, 765, 7655),
+ NotificationViewUsage(
+ ViewType.PRIVATE_CONTRACTED_VIEW,
+ 160,
+ 350,
+ 300,
+ 5544,
+ 66500,
+ 5433
+ )
+ )
+ ),
+ // Different style is different aggregation
+ NotificationMemoryUsage(
+ "package 1",
+ 384,
+ "key2",
+ Notification.Builder(context).setStyle(Notification.BigTextStyle()).build(),
+ NotificationObjectUsage(
+ 77,
+ 54,
+ 34,
+ NotificationEnums.STYLE_BIG_TEXT,
+ 77,
+ 432,
+ 2342,
+ true
+ ),
+ listOf(
+ NotificationViewUsage(ViewType.TOTAL, 3245, 1234, 7653, 543, 765, 7655),
+ NotificationViewUsage(
+ ViewType.PRIVATE_CONTRACTED_VIEW,
+ 160,
+ 350,
+ 300,
+ 5544,
+ 66500,
+ 5433
+ )
+ )
+ ),
+ // Different package is also different aggregation
+ NotificationMemoryUsage(
+ "package 2",
+ 684,
+ "key2",
+ Notification.Builder(context).setStyle(Notification.BigPictureStyle()).build(),
+ NotificationObjectUsage(
+ 32,
+ 654,
+ 234,
+ NotificationEnums.STYLE_BIG_PICTURE,
+ 211,
+ 776,
+ 435,
+ true
+ ),
+ listOf(
+ NotificationViewUsage(ViewType.TOTAL, 4355, 6543, 4322, 5435, 6546, 65485),
+ NotificationViewUsage(
+ ViewType.PRIVATE_CONTRACTED_VIEW,
+ 6546,
+ 7657,
+ 4353,
+ 6546,
+ 76575,
+ 54654
+ )
+ )
+ )
+ )
}
diff --git a/services/core/java/com/android/server/audio/AudioDeviceBroker.java b/services/core/java/com/android/server/audio/AudioDeviceBroker.java
index cf880eba20f7..6410142278b5 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 @@ import java.util.concurrent.atomic.AtomicBoolean;
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 @@ import java.util.concurrent.atomic.AtomicBoolean;
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 @@ import java.util.concurrent.atomic.AtomicBoolean;
// 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 @@ import java.util.concurrent.atomic.AtomicBoolean;
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 35da73ef58c0..a74f4154eb85 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 @@ public class AudioDeviceInventory {
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 @@ public class AudioDeviceInventory {
}
}
+ /*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 @@ public class AudioDeviceInventory {
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 @@ public class AudioDeviceInventory {
*/
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 @@ public class AudioDeviceInventory {
}
@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 @@ public class AudioDeviceInventory {
}
@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 f95982138564..691ce93b3f7b 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 @@ public class BtHelper {
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 @@ public class BtHelper {
}
// @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 @@ public class BtHelper {
}
}
- //@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 @@ public class BtHelper {
}
// @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 598e2b990ea5..94b67cedf86c 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 @@ public class FingerprintService extends SystemService {
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/display/DisplayManagerService.java b/services/core/java/com/android/server/display/DisplayManagerService.java
index 4341634fb890..909c531c5e92 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 @@ public final class DisplayManagerService extends SystemService {
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 @@ public final class DisplayManagerService extends SystemService {
}
}
} 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 @@ public final class DisplayManagerService extends SystemService {
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 8025fa60ca2b..7957ed63a3e2 100644
--- a/services/core/java/com/android/server/display/DisplayPowerController.java
+++ b/services/core/java/com/android/server/display/DisplayPowerController.java
@@ -133,6 +133,7 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call
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;
@@ -506,6 +507,8 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call
private boolean mIsEnabled;
private boolean mIsInTransition;
+ private boolean mBootCompleted;
+
/**
* Creates the display power controller.
*/
@@ -513,7 +516,8 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call
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 + "]";
@@ -655,6 +659,7 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call
mTemporaryAutoBrightnessAdjustment = PowerManager.BRIGHTNESS_INVALID_FLOAT;
mPendingAutoBrightnessAdjustment = PowerManager.BRIGHTNESS_INVALID_FLOAT;
+ mBootCompleted = bootCompleted;
}
private void applyReduceBrightColorsSplineAdjustment() {
@@ -1370,7 +1375,7 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call
// 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.
@@ -2050,7 +2055,8 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call
}
}
- 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));
@@ -2497,6 +2503,10 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call
mBrightnessSetting.setBrightness(brightnessValue);
}
+ void onBootCompleted() {
+ mHandler.obtainMessage(MSG_BOOT_COMPLETED).sendToTarget();
+ }
+
private void updateScreenBrightnessSetting(float brightnessValue) {
if (!isValidBrightnessValue(brightnessValue)
|| brightnessValue == mCurrentScreenBrightnessSetting) {
@@ -2642,6 +2652,17 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call
}
};
+ /**
+ * 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();
@@ -3177,6 +3198,11 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call
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/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java
index b5fceb50f1dc..ff10cbc19d5f 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 @@ public class NotificationManagerService extends SystemService {
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 d3443066155f..1501d69cea3c 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 @@ public final class NotificationRecord {
// 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 final class NotificationRecord {
public boolean setIntercepted(boolean intercept) {
mIntercept = intercept;
+ mInterceptSet = true;
return mIntercept;
}
@@ -934,6 +937,10 @@ public final class NotificationRecord {
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 c0bc474cd8fa..35b94e7ccd63 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 @@ public class ZenLog {
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 @@ public class ZenLog {
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 db0ce2ef6fe2..5b7b0c12026b 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 @@ public class ZenModeFiltering {
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 @@ public class ZenModeFiltering {
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 @@ public class ZenModeFiltering {
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/wm/LetterboxUiController.java b/services/core/java/com/android/server/wm/LetterboxUiController.java
index 5a481f438827..e3437683e957 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 @@ final class LetterboxUiController {
&& (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