diff options
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 |