diff options
23 files changed, 849 insertions, 96 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/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/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/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/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/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/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 0589cfc0967b..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) { @@ -1512,6 +1523,7 @@ import java.util.concurrent.atomic.AtomicBoolean; case MSG_I_BT_SERVICE_DISCONNECTED_PROFILE: if (msg.arg1 != BluetoothProfile.HEADSET) { synchronized (mDeviceStateLock) { + mBtHelper.onBtProfileDisconnected(msg.arg1); mDeviceInventory.onBtProfileDisconnected(msg.arg1); } } else { @@ -1648,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: @@ -1743,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 6cd42f87aede..691ce93b3f7b 100644 --- a/services/core/java/com/android/server/audio/BtHelper.java +++ b/services/core/java/com/android/server/audio/BtHelper.java @@ -279,7 +279,11 @@ public class BtHelper { } AudioService.sVolumeLogger.log(new AudioServiceEvents.VolumeEvent( AudioServiceEvents.VolumeEvent.VOL_SET_AVRCP_VOL, index)); - mA2dp.setAvrcpAbsoluteVolume(index); + try { + mA2dp.setAvrcpAbsoluteVolume(index); + } catch (Exception e) { + Log.e(TAG, "Exception while changing abs volume", e); + } } /*package*/ synchronized @AudioSystem.AudioFormatNativeEnumForBtCodec int getA2dpCodec( @@ -287,7 +291,12 @@ public class BtHelper { if (mA2dp == null) { return AudioSystem.AUDIO_FORMAT_DEFAULT; } - final BluetoothCodecStatus btCodecStatus = mA2dp.getCodecStatus(device); + BluetoothCodecStatus btCodecStatus = null; + try { + btCodecStatus = mA2dp.getCodecStatus(device); + } catch (Exception e) { + Log.e(TAG, "Exception while getting status of " + device, e); + } if (btCodecStatus == null) { return AudioSystem.AUDIO_FORMAT_DEFAULT; } @@ -421,7 +430,11 @@ public class BtHelper { } AudioService.sVolumeLogger.log(new AudioServiceEvents.VolumeEvent( AudioServiceEvents.VolumeEvent.VOL_SET_LE_AUDIO_VOL, index, maxIndex)); - mLeAudio.setVolume(volume); + try { + mLeAudio.setVolume(volume); + } catch (Exception e) { + Log.e(TAG, "Exception while setting LE volume", e); + } } /*package*/ synchronized void setHearingAidVolume(int index, int streamType, @@ -447,7 +460,11 @@ public class BtHelper { AudioService.sVolumeLogger.log(new AudioServiceEvents.VolumeEvent( AudioServiceEvents.VolumeEvent.VOL_SET_HEARING_AID_VOL, index, gainDB)); } - mHearingAid.setVolume(gainDB); + try { + mHearingAid.setVolume(gainDB); + } catch (Exception e) { + Log.i(TAG, "Exception while setting hearing aid volume", e); + } } /*package*/ synchronized void onBroadcastScoConnectionState(int state) { @@ -472,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); @@ -487,6 +504,35 @@ public class BtHelper { mBluetoothHeadset = null; } + //@GuardedBy("AudioDeviceBroker.mDeviceStateLock") + /*package*/ synchronized void onBtProfileDisconnected(int profile) { + switch (profile) { + case BluetoothProfile.A2DP: + mA2dp = null; + break; + case BluetoothProfile.HEARING_AID: + mHearingAid = null; + break; + case BluetoothProfile.LE_AUDIO: + mLeAudio = null; + break; + + case BluetoothProfile.A2DP_SINK: + case BluetoothProfile.LE_AUDIO_BROADCAST: + // shouldn't be received here as profile doesn't involve BtHelper + Log.e(TAG, "onBtProfileDisconnected: Not a profile handled by BtHelper " + + BluetoothProfile.getProfileName(profile)); + break; + + default: + // Not a valid profile to disconnect + Log.e(TAG, "onBtProfileDisconnected: Not a valid profile to disconnect " + + BluetoothProfile.getProfileName(profile)); + break; + } + } + + @GuardedBy("AudioDeviceBroker.mDeviceStateLock") /*package*/ synchronized void onBtProfileConnected(int profile, BluetoothProfile proxy) { if (profile == BluetoothProfile.HEADSET) { onHeadsetProfileConnected((BluetoothHeadset) proxy); @@ -518,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(); @@ -672,7 +718,6 @@ public class BtHelper { public void onServiceConnected(int profile, BluetoothProfile proxy) { switch(profile) { case BluetoothProfile.A2DP: - case BluetoothProfile.A2DP_SINK: case BluetoothProfile.HEADSET: case BluetoothProfile.HEARING_AID: case BluetoothProfile.LE_AUDIO: @@ -682,6 +727,10 @@ public class BtHelper { mDeviceBroker.postBtProfileConnected(profile, proxy); break; + case BluetoothProfile.A2DP_SINK: + // no A2DP sink functionality handled by BtHelper + case BluetoothProfile.LE_AUDIO_BROADCAST: + // no broadcast functionality handled by BtHelper default: break; } @@ -690,14 +739,19 @@ public class BtHelper { switch (profile) { case BluetoothProfile.A2DP: - case BluetoothProfile.A2DP_SINK: case BluetoothProfile.HEADSET: case BluetoothProfile.HEARING_AID: case BluetoothProfile.LE_AUDIO: - case BluetoothProfile.LE_AUDIO_BROADCAST: + AudioService.sDeviceLogger.log(new AudioEventLogger.StringEvent( + "BT profile service: disconnecting " + + BluetoothProfile.getProfileName(profile) + " profile")); mDeviceBroker.postBtProfileDisconnected(profile); break; + case BluetoothProfile.A2DP_SINK: + // no A2DP sink functionality handled by BtHelper + case BluetoothProfile.LE_AUDIO_BROADCAST: + // no broadcast functionality handled by BtHelper default: break; } 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"); |