diff options
52 files changed, 889 insertions, 178 deletions
diff --git a/apex/blobstore/service/java/com/android/server/blob/BlobStoreManagerService.java b/apex/blobstore/service/java/com/android/server/blob/BlobStoreManagerService.java index 9d363c806f5f..3af36ebb08ca 100644 --- a/apex/blobstore/service/java/com/android/server/blob/BlobStoreManagerService.java +++ b/apex/blobstore/service/java/com/android/server/blob/BlobStoreManagerService.java @@ -28,6 +28,7 @@ import static android.content.pm.PackageManager.MATCH_UNINSTALLED_PACKAGES; import static android.os.UserHandle.USER_CURRENT; import static android.os.UserHandle.USER_NULL; +import static com.android.internal.util.ConcurrentUtils.DIRECT_EXECUTOR; import static com.android.server.blob.BlobStoreConfig.INVALID_BLOB_ID; import static com.android.server.blob.BlobStoreConfig.INVALID_BLOB_SIZE; import static com.android.server.blob.BlobStoreConfig.LOGV; @@ -1915,7 +1916,7 @@ public class BlobStoreManagerService extends SystemService { mStatsManager.setPullAtomCallback( FrameworkStatsLog.BLOB_INFO, null, // use default PullAtomMetadata values - BackgroundThread.getExecutor(), + DIRECT_EXECUTOR, mStatsCallbackImpl ); } diff --git a/apex/jobscheduler/service/java/com/android/server/alarm/MetricsHelper.java b/apex/jobscheduler/service/java/com/android/server/alarm/MetricsHelper.java index eb1848d666f0..7e110eb741dd 100644 --- a/apex/jobscheduler/service/java/com/android/server/alarm/MetricsHelper.java +++ b/apex/jobscheduler/service/java/com/android/server/alarm/MetricsHelper.java @@ -16,6 +16,7 @@ package com.android.server.alarm; +import static com.android.internal.util.ConcurrentUtils.DIRECT_EXECUTOR; import static com.android.internal.util.FrameworkStatsLog.ALARM_SCHEDULED__EXACT_ALARM_ALLOWED_REASON__ALLOW_LIST; import static com.android.internal.util.FrameworkStatsLog.ALARM_SCHEDULED__EXACT_ALARM_ALLOWED_REASON__CHANGE_DISABLED; import static com.android.internal.util.FrameworkStatsLog.ALARM_SCHEDULED__EXACT_ALARM_ALLOWED_REASON__LISTENER; @@ -31,7 +32,6 @@ import android.app.StatsManager; import android.content.Context; import android.os.SystemClock; -import com.android.internal.os.BackgroundThread; import com.android.internal.util.FrameworkStatsLog; import java.util.function.Supplier; @@ -51,7 +51,7 @@ class MetricsHelper { void registerPuller(Supplier<AlarmStore> alarmStoreSupplier) { final StatsManager statsManager = mContext.getSystemService(StatsManager.class); statsManager.setPullAtomCallback(FrameworkStatsLog.PENDING_ALARM_INFO, null, - BackgroundThread.getExecutor(), (atomTag, data) -> { + DIRECT_EXECUTOR, (atomTag, data) -> { if (atomTag != FrameworkStatsLog.PENDING_ALARM_INFO) { throw new UnsupportedOperationException("Unknown tag" + atomTag); } diff --git a/core/java/android/content/pm/ServiceInfo.java b/core/java/android/content/pm/ServiceInfo.java index be8b2a20cfb1..65f56f68ed3f 100644 --- a/core/java/android/content/pm/ServiceInfo.java +++ b/core/java/android/content/pm/ServiceInfo.java @@ -486,7 +486,7 @@ public class ServiceInfo extends ComponentInfo * Here is an example: * <pre> * <uses-permission - * android:name="android.permissions.FOREGROUND_SERVICE_SPECIAL_USE" + * android:name="android.permission.FOREGROUND_SERVICE_SPECIAL_USE" * /> * <service * android:name=".MySpecialForegroundService" @@ -506,7 +506,7 @@ public class ServiceInfo extends ComponentInfo * in both platforms. * <pre> * <uses-permission - * android:name="android.permissions.FOREGROUND_SERVICE_SPECIAL_USE" + * android:name="android.permission.FOREGROUND_SERVICE_SPECIAL_USE" * android:maxSdkVersion="last_sdk_version_without_type_foo" * /> * <service diff --git a/core/java/android/view/ViewGroup.java b/core/java/android/view/ViewGroup.java index 7bdff8c5b858..c43962d5900c 100644 --- a/core/java/android/view/ViewGroup.java +++ b/core/java/android/view/ViewGroup.java @@ -7397,19 +7397,26 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager } target = next; } - if (!childIsHit) { + if (!childIsHit && mFirstHoverTarget != null) { target = mFirstHoverTarget; + final ArrayList<View> preorderedList = buildTouchDispatchChildList(); while (notEmpty && target != null) { final HoverTarget next = target.next; final View hoveredView = target.child; - rect.set(hoveredView.mLeft, hoveredView.mTop, hoveredView.mRight, - hoveredView.mBottom); - matrix.mapRect(rect); - notEmpty = region.op(Math.round(rect.left), Math.round(rect.top), - Math.round(rect.right), Math.round(rect.bottom), Region.Op.DIFFERENCE); + if (!isOnTop(child, hoveredView, preorderedList)) { + rect.set(hoveredView.mLeft, hoveredView.mTop, hoveredView.mRight, + hoveredView.mBottom); + matrix.mapRect(rect); + notEmpty = region.op(Math.round(rect.left), Math.round(rect.top), + Math.round(rect.right), Math.round(rect.bottom), + Region.Op.DIFFERENCE); + } target = next; } + if (preorderedList != null) { + preorderedList.clear(); + } } } else { TouchTarget target = mFirstTouchTarget; @@ -7422,19 +7429,26 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager } target = next; } - if (!childIsHit) { + if (!childIsHit && mFirstTouchTarget != null) { target = mFirstTouchTarget; + final ArrayList<View> preorderedList = buildOrderedChildList(); while (notEmpty && target != null) { final TouchTarget next = target.next; final View touchedView = target.child; - rect.set(touchedView.mLeft, touchedView.mTop, touchedView.mRight, - touchedView.mBottom); - matrix.mapRect(rect); - notEmpty = region.op(Math.round(rect.left), Math.round(rect.top), - Math.round(rect.right), Math.round(rect.bottom), Region.Op.DIFFERENCE); + if (!isOnTop(child, touchedView, preorderedList)) { + rect.set(touchedView.mLeft, touchedView.mTop, touchedView.mRight, + touchedView.mBottom); + matrix.mapRect(rect); + notEmpty = region.op(Math.round(rect.left), Math.round(rect.top), + Math.round(rect.right), Math.round(rect.bottom), + Region.Op.DIFFERENCE); + } target = next; } + if (preorderedList != null) { + preorderedList.clear(); + } } } @@ -7444,6 +7458,28 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager return notEmpty; } + /** + * Return true if the given {@code view} is drawn on top of the {@code otherView}. + * Both the {@code view} and {@code otherView} must be children of this ViewGroup. + * Otherwise, the returned value is meaningless. + */ + private boolean isOnTop(View view, View otherView, ArrayList<View> preorderedList) { + final int childrenCount = mChildrenCount; + final boolean customOrder = preorderedList == null && isChildrenDrawingOrderEnabled(); + final View[] children = mChildren; + for (int i = childrenCount - 1; i >= 0; i--) { + final int childIndex = getAndVerifyPreorderedIndex(childrenCount, i, customOrder); + final View child = getAndVerifyPreorderedView(preorderedList, children, childIndex); + if (child == view) { + return true; + } + if (child == otherView) { + return false; + } + } + // Can't find the view and otherView in the children list. Return value is meaningless. + return false; + } private static void applyOpToRegionByBounds(Region region, View view, Region.Op op) { final int[] locationInWindow = new int[2]; diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java index c2afb4bf5205..cd2d36c60ade 100644 --- a/core/java/android/view/ViewRootImpl.java +++ b/core/java/android/view/ViewRootImpl.java @@ -587,6 +587,9 @@ public final class ViewRootImpl implements ViewParent, @NonNull Display mDisplay; final String mBasePackageName; + // If we would like to keep a particular eye on the corresponding package. + final boolean mExtraDisplayListenerLogging; + final int[] mTmpLocation = new int[2]; final TypedValue mTmpValue = new TypedValue(); @@ -1136,6 +1139,8 @@ public final class ViewRootImpl implements ViewParent, mWindowLayout = windowLayout; mDisplay = display; mBasePackageName = context.getBasePackageName(); + final String name = DisplayProperties.debug_vri_package().orElse(null); + mExtraDisplayListenerLogging = !TextUtils.isEmpty(name) && name.equals(mBasePackageName); mThread = Thread.currentThread(); mLocation = new WindowLeaked(null); mLocation.fillInStackTrace(); @@ -1577,6 +1582,10 @@ public final class ViewRootImpl implements ViewParent, // We should update mAttachInfo.mDisplayState after registerDisplayListener // because displayState might be changed before registerDisplayListener. mAttachInfo.mDisplayState = mDisplay.getState(); + if (mExtraDisplayListenerLogging) { + Slog.i(mTag, "(" + mBasePackageName + ") Initial DisplayState: " + + mAttachInfo.mDisplayState, new Throwable()); + } if ((res & WindowManagerGlobal.ADD_FLAG_USE_BLAST) != 0) { mUseBLASTAdapter = true; } @@ -1661,6 +1670,9 @@ public final class ViewRootImpl implements ViewParent, * Register any kind of listeners if setView was success. */ private void registerListeners() { + if (mExtraDisplayListenerLogging) { + Slog.i(mTag, "Register listeners: " + mBasePackageName); + } mAccessibilityManager.addAccessibilityStateChangeListener( mAccessibilityInteractionConnectionManager, mHandler); mAccessibilityManager.addHighTextContrastStateChangeListener( @@ -1686,6 +1698,9 @@ public final class ViewRootImpl implements ViewParent, DisplayManagerGlobal .getInstance() .unregisterDisplayListener(mDisplayListener); + if (mExtraDisplayListenerLogging) { + Slog.w(mTag, "Unregister listeners: " + mBasePackageName, new Throwable()); + } } private void setTag() { @@ -2093,9 +2108,16 @@ public final class ViewRootImpl implements ViewParent, private final DisplayListener mDisplayListener = new DisplayListener() { @Override public void onDisplayChanged(int displayId) { + if (mExtraDisplayListenerLogging) { + Slog.i(mTag, "Received onDisplayChanged - " + mView); + } if (mView != null && mDisplay.getDisplayId() == displayId) { final int oldDisplayState = mAttachInfo.mDisplayState; final int newDisplayState = mDisplay.getState(); + if (mExtraDisplayListenerLogging) { + Slog.i(mTag, "DisplayState - old: " + oldDisplayState + + ", new: " + newDisplayState); + } if (oldDisplayState != newDisplayState) { mAttachInfo.mDisplayState = newDisplayState; pokeDrawLockIfNeeded(); diff --git a/core/java/android/widget/TextView.java b/core/java/android/widget/TextView.java index 63e882533a4c..c990e941a11b 100644 --- a/core/java/android/widget/TextView.java +++ b/core/java/android/widget/TextView.java @@ -14051,7 +14051,8 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener selectionStart, OffsetMapping.MAP_STRATEGY_CURSOR); final int line = layout.getLineForOffset(offsetTransformed); final float insertionMarkerX = - layout.getPrimaryHorizontal(offsetTransformed) + layout.getPrimaryHorizontal( + offsetTransformed, layout.shouldClampCursor(line)) + viewportToContentHorizontalOffset; final float insertionMarkerTop = layout.getLineTop(line) + viewportToContentVerticalOffset; diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml index f5b0711a37aa..24dbc5eaed41 100644 --- a/core/res/res/values/config.xml +++ b/core/res/res/values/config.xml @@ -2791,7 +2791,7 @@ <!-- Base "handwriting slop" value used by ViewConfiguration as a movement threshold where stylus handwriting should begin. --> - <dimen name="config_viewConfigurationHandwritingSlop">4dp</dimen> + <dimen name="config_viewConfigurationHandwritingSlop">2dp</dimen> <!-- Base "hover slop" value used by ViewConfiguration as a movement threshold under which hover is considered "stationary". --> @@ -5348,6 +5348,10 @@ to enroll the other eligible biometric. --> <fraction name="config_biometricNotificationFrrThreshold">25%</fraction> + <!-- Whether to enable the biometric notification for dual-modality device that enrolled a + single biometric and experiences high FRR. --> + <bool name="config_biometricFrrNotificationEnabled">false</bool> + <!-- The component name for the default profile supervisor, which can be set as a profile owner even after user setup is complete. The defined component should be used for supervision purposes only. The component must be part of a system app. --> diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml index c78af86f0f39..be43a4fc238d 100644 --- a/core/res/res/values/symbols.xml +++ b/core/res/res/values/symbols.xml @@ -2584,6 +2584,7 @@ <!-- Biometric FRR config --> <java-symbol type="fraction" name="config_biometricNotificationFrrThreshold" /> + <java-symbol type="bool" name="config_biometricFrrNotificationEnabled" /> <!-- Biometric FRR notification messages --> <java-symbol type="string" name="device_unlock_notification_name" /> diff --git a/core/tests/coretests/src/android/view/ViewGroupGetChildLocalHitRegionTest.java b/core/tests/coretests/src/android/view/ViewGroupGetChildLocalHitRegionTest.java index 60a0a2adbbbe..c210fd631f06 100644 --- a/core/tests/coretests/src/android/view/ViewGroupGetChildLocalHitRegionTest.java +++ b/core/tests/coretests/src/android/view/ViewGroupGetChildLocalHitRegionTest.java @@ -90,22 +90,73 @@ public class ViewGroupGetChildLocalHitRegionTest { assertGetChildLocalHitRegionEmpty(R.id.view_cover_top, R.id.view_cover_bottom); } + @Test + public void testGetChildLocalHitRegion_topViewIsNotBlockedByBottomView() { + // In this case, two views overlap with each other and the MotionEvent is injected to the + // bottom view. It verifies that the hit region of the top view won't be blocked by the + // bottom view. + testGetChildLocalHitRegion_topViewIsNotBlockedByBottomView(/* isHover= */ true); + testGetChildLocalHitRegion_topViewIsNotBlockedByBottomView(/* isHover= */ false); + } + + private void testGetChildLocalHitRegion_topViewIsNotBlockedByBottomView(boolean isHover) { + // In this case, two views overlap with each other and the MotionEvent is injected to the + // bottom view. It verifies that the hit region of the top view won't be blocked by the + // bottom view. + mScenarioRule.getScenario().onActivity(activity -> { + View viewTop = activity.findViewById(R.id.view_overlap_top); + View viewBottom = activity.findViewById(R.id.view_overlap_bottom); + + // The viewTop covers the left side of the viewBottom. To avoid the MotionEvent gets + // blocked by viewTop, we inject MotionEvents into viewBottom's right bottom corner. + float x = viewBottom.getWidth() - 1; + float y = viewBottom.getHeight() - 1; + injectMotionEvent(viewBottom, x, y, isHover); + + Matrix actualMatrix = new Matrix(); + Region actualRegion = new Region(0, 0, viewTop.getWidth(), viewTop.getHeight()); + boolean actualNotEmpty = viewTop.getParent() + .getChildLocalHitRegion(viewTop, actualRegion, actualMatrix, isHover); + + int[] windowLocation = new int[2]; + viewTop.getLocationInWindow(windowLocation); + Matrix expectMatrix = new Matrix(); + expectMatrix.preTranslate(-windowLocation[0], -windowLocation[1]); + // Though viewTop and viewBottom overlaps, viewTop's hit region won't be blocked by + // viewBottom. + Region expectRegion = new Region(0, 0, viewTop.getWidth(), viewTop.getHeight()); + + assertThat(actualNotEmpty).isTrue(); + assertThat(actualMatrix).isEqualTo(expectMatrix); + assertThat(actualRegion).isEqualTo(expectRegion); + }); + } + private void injectMotionEvent(View view, boolean isHover) { + float x = view.getWidth() / 2f; + float y = view.getHeight() / 2f; + injectMotionEvent(view, x, y, isHover); + } + + /** + * Inject MotionEvent into the given view, at the given location specified in the view's + * coordinates. + */ + private void injectMotionEvent(View view, float x, float y, boolean isHover) { int[] location = new int[2]; view.getLocationInWindow(location); - float x = location[0] + view.getWidth() / 2f; - float y = location[1] + view.getHeight() / 2f; + float globalX = location[0] + x; + float globalY = location[1] + y; int action = isHover ? MotionEvent.ACTION_HOVER_ENTER : MotionEvent.ACTION_DOWN; MotionEvent motionEvent = MotionEvent.obtain(/* downtime= */ 0, /* eventTime= */ 0, action, - x, y, /* pressure= */ 0, /* size= */ 0, /* metaState= */ 0, + globalX, globalY, /* pressure= */ 0, /* size= */ 0, /* metaState= */ 0, /* xPrecision= */ 1, /* yPrecision= */ 1, /* deviceId= */0, /* edgeFlags= */0); View rootView = view.getRootView(); rootView.dispatchPointerEvent(motionEvent); } - private void assertGetChildLocalHitRegion(int viewId) { assertGetChildLocalHitRegion(viewId, /* isHover= */ true); assertGetChildLocalHitRegion(viewId, /* isHover= */ false); diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIController.java index c111ce623c1a..0e6b20332a4f 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIController.java @@ -22,10 +22,13 @@ import android.annotation.NonNull; import android.annotation.Nullable; import android.app.TaskInfo; import android.app.TaskInfo.CameraCompatControlState; +import android.content.ComponentName; import android.content.Context; import android.content.Intent; import android.content.res.Configuration; import android.hardware.display.DisplayManager; +import android.net.Uri; +import android.os.UserHandle; import android.provider.Settings; import android.util.ArraySet; import android.util.Log; @@ -577,7 +580,13 @@ public class CompatUIController implements OnDisplaysChangedListener, final Intent intent = new Intent(Settings.ACTION_MANAGE_USER_ASPECT_RATIO_SETTINGS); intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK); - mContext.startActivity(intent); + final ComponentName appComponent = taskInfo.topActivity; + if (appComponent != null) { + final Uri packageUri = Uri.parse("package:" + appComponent.getPackageName()); + intent.setData(packageUri); + } + final UserHandle userHandle = UserHandle.of(taskInfo.userId); + mContext.startActivityAsUser(intent, userHandle); } private void removeLayouts(int taskId) { diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/ActivityLaunchAnimator.kt b/packages/SystemUI/animation/src/com/android/systemui/animation/ActivityLaunchAnimator.kt index a95ab5565ab9..9b85eb84e34f 100644 --- a/packages/SystemUI/animation/src/com/android/systemui/animation/ActivityLaunchAnimator.kt +++ b/packages/SystemUI/animation/src/com/android/systemui/animation/ActivityLaunchAnimator.kt @@ -606,12 +606,28 @@ class ActivityLaunchAnimator( object : Controller by delegate { override fun onLaunchAnimationStart(isExpandingFullyAbove: Boolean) { listener?.onLaunchAnimationStart() + + if (DEBUG_LAUNCH_ANIMATION) { + Log.d( + TAG, + "Calling controller.onLaunchAnimationStart(isExpandingFullyAbove=" + + "$isExpandingFullyAbove) [controller=$delegate]" + ) + } delegate.onLaunchAnimationStart(isExpandingFullyAbove) } override fun onLaunchAnimationEnd(isExpandingFullyAbove: Boolean) { listener?.onLaunchAnimationEnd() iCallback?.invoke() + + if (DEBUG_LAUNCH_ANIMATION) { + Log.d( + TAG, + "Calling controller.onLaunchAnimationEnd(isExpandingFullyAbove=" + + "$isExpandingFullyAbove) [controller=$delegate]" + ) + } delegate.onLaunchAnimationEnd(isExpandingFullyAbove) } diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitch.java b/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitch.java index 450010c1bcd4..8eab31e7f3f3 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitch.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitch.java @@ -108,6 +108,8 @@ public class KeyguardClockSwitch extends RelativeLayout { private int mWeatherClockSmartspaceTranslateY = 0; private int mDrawAlpha = 255; + private int mStatusBarHeight = 0; + /** * Maintain state so that a newly connected plugin can be initialized. */ @@ -150,6 +152,8 @@ public class KeyguardClockSwitch extends RelativeLayout { R.dimen.weather_clock_smartspace_translateX); mWeatherClockSmartspaceTranslateY = mContext.getResources().getDimensionPixelSize( R.dimen.weather_clock_smartspace_translateY); + mStatusBarHeight = mContext.getResources().getDimensionPixelSize( + R.dimen.status_bar_height); updateStatusArea(/* animate= */false); } @@ -295,6 +299,8 @@ public class KeyguardClockSwitch extends RelativeLayout { mStatusAreaAnim = null; View in, out; + // statusAreaYTranslation uses for the translation for both mStatusArea and mSmallClockFrame + // statusAreaClockTranslateY only uses for mStatusArea float statusAreaYTranslation, statusAreaClockScale = 1f; float statusAreaClockTranslateX = 0f, statusAreaClockTranslateY = 0f; float clockInYTranslation, clockOutYTranslation; @@ -309,10 +315,21 @@ public class KeyguardClockSwitch extends RelativeLayout { && mClock.getLargeClock().getConfig().getHasCustomWeatherDataDisplay()) { statusAreaClockScale = mWeatherClockSmartspaceScaling; statusAreaClockTranslateX = mWeatherClockSmartspaceTranslateX; - statusAreaClockTranslateY = mWeatherClockSmartspaceTranslateY - mSmartspaceTop; if (mSplitShadeCentered) { statusAreaClockTranslateX *= SMARTSPACE_TRANSLATION_CENTER_MULTIPLIER; } + + // On large weather clock, + // top padding for time is status bar height from top of the screen. + // On small one, + // it's screenOffsetYPadding (translationY for KeyguardStatusView), + // Cause smartspace is positioned according to the smallClockFrame + // we need to translate the difference between bottom of large clock and small clock + // Also, we need to counter offset the empty date weather view, mSmartspaceTop + // mWeatherClockSmartspaceTranslateY is only for Felix + statusAreaClockTranslateY = mStatusBarHeight - 0.6F * mSmallClockFrame.getHeight() + - mSmartspaceTop - screenOffsetYPadding + - statusAreaYTranslation + mWeatherClockSmartspaceTranslateY; } clockInYTranslation = 0; clockOutYTranslation = 0; // Small clock translation is handled with statusArea diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardFaceListenModel.kt b/packages/SystemUI/src/com/android/keyguard/KeyguardFaceListenModel.kt index bb799fc55171..d7019b5c5d04 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardFaceListenModel.kt +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardFaceListenModel.kt @@ -27,7 +27,7 @@ data class KeyguardFaceListenModel( override var userId: Int = 0, override var listening: Boolean = false, // keep sorted - var allowedDisplayState: Boolean = false, + var allowedDisplayStateWhileAwake: Boolean = false, var alternateBouncerShowing: Boolean = false, var authInterruptActive: Boolean = false, var biometricSettingEnabledForUser: Boolean = false, @@ -58,7 +58,7 @@ data class KeyguardFaceListenModel( userId.toString(), listening.toString(), // keep sorted - allowedDisplayState.toString(), + allowedDisplayStateWhileAwake.toString(), alternateBouncerShowing.toString(), authInterruptActive.toString(), biometricSettingEnabledForUser.toString(), @@ -98,7 +98,7 @@ data class KeyguardFaceListenModel( userId = model.userId listening = model.listening // keep sorted - allowedDisplayState = model.allowedDisplayState + allowedDisplayStateWhileAwake = model.allowedDisplayStateWhileAwake alternateBouncerShowing = model.alternateBouncerShowing authInterruptActive = model.authInterruptActive biometricSettingEnabledForUser = model.biometricSettingEnabledForUser @@ -143,7 +143,7 @@ data class KeyguardFaceListenModel( "userId", "listening", // keep sorted - "allowedDisplayState", + "allowedDisplayStateWhileAwake", "alternateBouncerShowing", "authInterruptActive", "biometricSettingEnabledForUser", diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java index 5b9b53e65ff4..7d2043d7614d 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java @@ -75,6 +75,7 @@ import static com.android.keyguard.FaceAuthUiEvent.FACE_AUTH_UPDATED_STARTED_WAK import static com.android.keyguard.FaceAuthUiEvent.FACE_AUTH_UPDATED_STRONG_AUTH_CHANGED; import static com.android.keyguard.FaceAuthUiEvent.FACE_AUTH_UPDATED_USER_SWITCHING; import static com.android.systemui.DejankUtils.whitelistIpcs; +import static com.android.systemui.keyguard.WakefulnessLifecycle.WAKEFULNESS_AWAKE; import static com.android.systemui.statusbar.policy.DevicePostureController.DEVICE_POSTURE_OPENED; import static com.android.systemui.statusbar.policy.DevicePostureController.DEVICE_POSTURE_UNKNOWN; @@ -163,6 +164,7 @@ import com.android.systemui.dump.DumpManager; import com.android.systemui.dump.DumpsysTableLogger; import com.android.systemui.flags.FeatureFlags; import com.android.systemui.flags.Flags; +import com.android.systemui.keyguard.WakefulnessLifecycle; import com.android.systemui.keyguard.domain.interactor.FaceAuthenticationListener; import com.android.systemui.keyguard.domain.interactor.KeyguardFaceAuthInteractor; import com.android.systemui.keyguard.shared.constants.TrustAgentUiEvent; @@ -345,15 +347,16 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab return; } - if (mDisplayTracker.getDisplay(mDisplayTracker.getDefaultDisplayId()).getState() + if (mWakefulness.getWakefulness() == WAKEFULNESS_AWAKE + && mDisplayTracker.getDisplay(mDisplayTracker.getDefaultDisplayId()).getState() == Display.STATE_OFF) { - mAllowedDisplayStateForFaceAuth = false; + mAllowedDisplayStateWhileAwakeForFaceAuth = false; updateFaceListeningState( BIOMETRIC_ACTION_STOP, FACE_AUTH_DISPLAY_OFF ); } else { - mAllowedDisplayStateForFaceAuth = true; + mAllowedDisplayStateWhileAwakeForFaceAuth = true; } } }; @@ -377,7 +380,7 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab private boolean mOccludingAppRequestingFp; private boolean mOccludingAppRequestingFace; private boolean mSecureCameraLaunched; - private boolean mAllowedDisplayStateForFaceAuth = true; + private boolean mAllowedDisplayStateWhileAwakeForFaceAuth = true; @VisibleForTesting protected boolean mTelephonyCapable; private boolean mAllowFingerprintOnCurrentOccludingActivity; @@ -426,6 +429,7 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab private KeyguardFaceAuthInteractor mFaceAuthInteractor; private final TaskStackChangeListeners mTaskStackChangeListeners; private final IActivityTaskManager mActivityTaskManager; + private final WakefulnessLifecycle mWakefulness; private final DisplayTracker mDisplayTracker; private final LockPatternUtils mLockPatternUtils; @VisibleForTesting @@ -2211,7 +2215,7 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab Trace.beginSection("KeyguardUpdateMonitor#handleStartedWakingUp"); Assert.isMainThread(); - mAllowedDisplayStateForFaceAuth = true; + mAllowedDisplayStateWhileAwakeForFaceAuth = true; updateFingerprintListeningState(BIOMETRIC_ACTION_UPDATE); if (mFaceWakeUpTriggersConfig.shouldTriggerFaceAuthOnWakeUpFrom(pmWakeReason)) { FACE_AUTH_UPDATED_STARTED_WAKING_UP.setExtraInfo(pmWakeReason); @@ -2368,7 +2372,8 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab FeatureFlags featureFlags, TaskStackChangeListeners taskStackChangeListeners, IActivityTaskManager activityTaskManagerService, - DisplayTracker displayTracker) { + DisplayTracker displayTracker, + WakefulnessLifecycle wakefulness) { mContext = context; mSubscriptionManager = subscriptionManager; mUserTracker = userTracker; @@ -2416,6 +2421,7 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab .collect(Collectors.toSet()); mTaskStackChangeListeners = taskStackChangeListeners; mActivityTaskManager = activityTaskManagerService; + mWakefulness = wakefulness; mDisplayTracker = displayTracker; if (mFeatureFlags.isEnabled(Flags.STOP_FACE_AUTH_ON_DISPLAY_OFF)) { mDisplayTracker.addDisplayChangeCallback(mDisplayCallback, mainExecutor); @@ -3230,7 +3236,7 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab && faceAndFpNotAuthenticated && !mGoingToSleep && isPostureAllowedForFaceAuth - && mAllowedDisplayStateForFaceAuth; + && mAllowedDisplayStateWhileAwakeForFaceAuth; // Aggregate relevant fields for debug logging. logListenerModelData( @@ -3238,7 +3244,7 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab System.currentTimeMillis(), user, shouldListen, - mAllowedDisplayStateForFaceAuth, + mAllowedDisplayStateWhileAwakeForFaceAuth, mAlternateBouncerShowing, mAuthInterruptActive, biometricEnabledForUser, @@ -4214,7 +4220,7 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD); final boolean previousState = mAllowFingerprintOnCurrentOccludingActivity; mAllowFingerprintOnCurrentOccludingActivity = - standardTask.topActivity != null + standardTask != null && standardTask.topActivity != null && !TextUtils.isEmpty(standardTask.topActivity.getPackageName()) && mAllowFingerprintOnOccludingActivitiesFromPackage.contains( standardTask.topActivity.getPackageName()) diff --git a/packages/SystemUI/src/com/android/systemui/assist/AssistManager.java b/packages/SystemUI/src/com/android/systemui/assist/AssistManager.java index f26404cad02b..4416b1979524 100644 --- a/packages/SystemUI/src/com/android/systemui/assist/AssistManager.java +++ b/packages/SystemUI/src/com/android/systemui/assist/AssistManager.java @@ -309,8 +309,14 @@ public class AssistManager { } int invocationType = args.getInt(INVOCATION_TYPE_KEY); - return mAssistOverrideInvocationTypes != null && Arrays.stream( - mAssistOverrideInvocationTypes).anyMatch(override -> override == invocationType); + return shouldOverrideAssist(invocationType); + } + + /** @return true if the invocation type should be handled by OverviewProxy instead of SysUI. */ + public boolean shouldOverrideAssist(int invocationType) { + return mAssistOverrideInvocationTypes != null + && Arrays.stream(mAssistOverrideInvocationTypes).anyMatch( + override -> override == invocationType); } /** diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsAnimationViewController.kt b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsAnimationViewController.kt index 94b5fb2861b1..21451dc0ffdf 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsAnimationViewController.kt +++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsAnimationViewController.kt @@ -58,6 +58,7 @@ abstract class UdfpsAnimationViewController<T : UdfpsAnimationView>( // Notification shade can be expanded but not visible (fraction: 0.0), for example // when a heads-up notification (HUN) is showing. notificationShadeVisible = event.expanded && event.fraction > 0f + notificationShadeTracking = event.tracking view.onExpansionChanged(event.fraction) updatePauseAuth() } @@ -65,6 +66,9 @@ abstract class UdfpsAnimationViewController<T : UdfpsAnimationView>( /** If the notification shade is visible. */ var notificationShadeVisible: Boolean = false + /** If the notification shade is currently being dragged */ + var notificationShadeTracking: Boolean = false + /** * The amount of translation needed if the view currently requires the user to touch * somewhere other than the exact center of the sensor. For example, this can happen diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsBpViewController.kt b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsBpViewController.kt index 802eea300bd4..96354c2a99ff 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsBpViewController.kt +++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsBpViewController.kt @@ -15,6 +15,7 @@ */ package com.android.systemui.biometrics +import com.android.internal.annotations.VisibleForTesting import com.android.systemui.dump.DumpManager import com.android.systemui.plugins.statusbar.StatusBarStateController import com.android.systemui.shade.ShadeExpansionStateManager @@ -39,6 +40,12 @@ class UdfpsBpViewController( override val tag = "UdfpsBpViewController" override fun shouldPauseAuth(): Boolean { - return false + // Do not auth while notification shade is being dragged + return notificationShadeTracking + } + + @VisibleForTesting + public override fun onViewAttached() { + super.onViewAttached() } } diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java index a36870346b9a..c29f884c848d 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java +++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java @@ -549,8 +549,12 @@ public class UdfpsController implements DozeReceiver, Dumpable { Log.e(TAG, "ignoring the touch injected from outside of UdfpsView"); return false; } - if (mOverlay == null) { - Log.w(TAG, "ignoring onTouch with null overlay"); + if (mOverlay == null || mOverlay.getAnimationViewController() == null) { + Log.w(TAG, "ignoring onTouch with null overlay or animation view controller"); + return false; + } + if (mOverlay.getAnimationViewController().shouldPauseAuth()) { + Log.w(TAG, "ignoring onTouch with shouldPauseAuth = true"); return false; } if (!mOverlay.matchesRequestId(requestId)) { diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/NavBarHelper.java b/packages/SystemUI/src/com/android/systemui/navigationbar/NavBarHelper.java index c6f73efdfdb1..d7e062fb926f 100644 --- a/packages/SystemUI/src/com/android/systemui/navigationbar/NavBarHelper.java +++ b/packages/SystemUI/src/com/android/systemui/navigationbar/NavBarHelper.java @@ -233,6 +233,9 @@ public final class NavBarHelper implements Settings.Secure.getUriFor(Settings.Secure.ASSIST_LONG_PRESS_HOME_ENABLED), false, mAssistContentObserver, UserHandle.USER_ALL); mContentResolver.registerContentObserver( + Settings.Secure.getUriFor(Secure.SEARCH_LONG_PRESS_HOME_ENABLED), + false, mAssistContentObserver, UserHandle.USER_ALL); + mContentResolver.registerContentObserver( Settings.Secure.getUriFor(Settings.Secure.ASSIST_TOUCH_GESTURE_ENABLED), false, mAssistContentObserver, UserHandle.USER_ALL); @@ -422,11 +425,17 @@ public final class NavBarHelper implements private void updateAssistantAvailability() { boolean assistantAvailableForUser = mAssistManagerLazy.get() .getAssistInfoForUser(mUserTracker.getUserId()) != null; - boolean longPressDefault = mContext.getResources().getBoolean( - com.android.internal.R.bool.config_assistLongPressHomeEnabledDefault); + + boolean overrideLongPressHome = mAssistManagerLazy.get() + .shouldOverrideAssist(AssistManager.INVOCATION_TYPE_HOME_BUTTON_LONG_PRESS); + boolean longPressDefault = mContext.getResources().getBoolean(overrideLongPressHome + ? com.android.internal.R.bool.config_searchLongPressHomeEnabledDefault + : com.android.internal.R.bool.config_assistLongPressHomeEnabledDefault); mLongPressHomeEnabled = Settings.Secure.getIntForUser(mContentResolver, - Settings.Secure.ASSIST_LONG_PRESS_HOME_ENABLED, longPressDefault ? 1 : 0, + overrideLongPressHome ? Secure.SEARCH_LONG_PRESS_HOME_ENABLED + : Settings.Secure.ASSIST_LONG_PRESS_HOME_ENABLED, longPressDefault ? 1 : 0, mUserTracker.getUserId()) != 0; + boolean gestureDefault = mContext.getResources().getBoolean( com.android.internal.R.bool.config_assistTouchGestureEnabledDefault); mAssistantTouchGestureEnabled = Settings.Secure.getIntForUser(mContentResolver, @@ -455,6 +464,7 @@ public final class NavBarHelper implements @Override public void setAssistantOverridesRequested(int[] invocationTypes) { mAssistManagerLazy.get().setAssistantOverridesRequested(invocationTypes); + updateAssistantAvailability(); } @Override diff --git a/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskInitializer.kt b/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskInitializer.kt index fe1034a6aa32..338d3ed42f95 100644 --- a/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskInitializer.kt +++ b/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskInitializer.kt @@ -23,6 +23,7 @@ import android.os.UserHandle import android.view.KeyEvent import android.view.KeyEvent.KEYCODE_N import android.view.KeyEvent.KEYCODE_STYLUS_BUTTON_TAIL +import android.view.ViewConfiguration import com.android.keyguard.KeyguardUpdateMonitor import com.android.keyguard.KeyguardUpdateMonitorCallback import com.android.systemui.dagger.qualifiers.Background @@ -65,12 +66,6 @@ constructor( * [NoteTaskController], ensure custom actions can be triggered (i.e., keyboard shortcut). */ private fun initializeHandleSystemKey() { - val callbacks = - object : CommandQueue.Callbacks { - override fun handleSystemKey(key: KeyEvent) { - key.toNoteTaskEntryPointOrNull()?.let(controller::showNoteTask) - } - } commandQueue.addCallback(callbacks) } @@ -134,15 +129,39 @@ constructor( controller.updateNoteTaskForCurrentUserAndManagedProfiles() } } -} -/** - * Maps a [KeyEvent] to a [NoteTaskEntryPoint]. If the [KeyEvent] does not represent a - * [NoteTaskEntryPoint], returns null. - */ -private fun KeyEvent.toNoteTaskEntryPointOrNull(): NoteTaskEntryPoint? = - when { - keyCode == KEYCODE_STYLUS_BUTTON_TAIL -> TAIL_BUTTON - keyCode == KEYCODE_N && isMetaPressed && isCtrlPressed -> KEYBOARD_SHORTCUT - else -> null + /** + * Tracks a [KeyEvent], and determines if it should trigger an action to show the note task. + * Returns a [NoteTaskEntryPoint] if an action should be taken, and null otherwise. + */ + private fun KeyEvent.toNoteTaskEntryPointOrNull(): NoteTaskEntryPoint? = + when { + keyCode == KEYCODE_STYLUS_BUTTON_TAIL && isTailButtonNotesGesture() -> TAIL_BUTTON + keyCode == KEYCODE_N && isMetaPressed && isCtrlPressed -> KEYBOARD_SHORTCUT + else -> null + } + + private var lastStylusButtonTailUpEventTime: Long = -MULTI_PRESS_TIMEOUT + + /** + * Perform gesture detection for the stylus tail button to make sure we only show the note task + * when there is a single press. Long presses and multi-presses are ignored for now. + */ + private fun KeyEvent.isTailButtonNotesGesture(): Boolean { + if (keyCode != KEYCODE_STYLUS_BUTTON_TAIL || action != KeyEvent.ACTION_UP) { + return false + } + + val isMultiPress = (downTime - lastStylusButtonTailUpEventTime) < MULTI_PRESS_TIMEOUT + val isLongPress = (eventTime - downTime) >= LONG_PRESS_TIMEOUT + lastStylusButtonTailUpEventTime = eventTime + // For now, trigger action immediately on UP of a single press, without waiting for + // the multi-press timeout to expire. + return !isMultiPress && !isLongPress } + + companion object { + val MULTI_PRESS_TIMEOUT = ViewConfiguration.getMultiPressTimeout().toLong() + val LONG_PRESS_TIMEOUT = ViewConfiguration.getLongPressTimeout().toLong() + } +} diff --git a/packages/SystemUI/src/com/android/systemui/shade/ShadeHeaderController.kt b/packages/SystemUI/src/com/android/systemui/shade/ShadeHeaderController.kt index 656411874de5..54eba34ed55c 100644 --- a/packages/SystemUI/src/com/android/systemui/shade/ShadeHeaderController.kt +++ b/packages/SystemUI/src/com/android/systemui/shade/ShadeHeaderController.kt @@ -479,11 +479,13 @@ constructor( if (largeScreenActive) { logInstantEvent("Large screen constraints set") header.setTransition(LARGE_SCREEN_HEADER_TRANSITION_ID) + systemIcons.isClickable = true systemIcons.setOnClickListener { shadeCollapseAction?.run() } } else { logInstantEvent("Small screen constraints set") header.setTransition(HEADER_TRANSITION_ID) systemIcons.setOnClickListener(null) + systemIcons.isClickable = false } header.jumpToState(header.startState) updatePosition() diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java index a243356c8690..e76bae5e955b 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java @@ -2368,10 +2368,16 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces { // * When phone is unlocked: we still don't want to execute hiding of the keyguard // as the animation could prepare 'fake AOD' interface (without actually // transitioning to keyguard state) and this might reset the view states + // Log for b/290627350 + Log.d(TAG, "!shouldBeKeyguard mStatusBarStateController.isKeyguardRequested() " + + mStatusBarStateController.isKeyguardRequested() + " keyguardForDozing " + + keyguardForDozing + " wakeAndUnlocking " + wakeAndUnlocking + + " isWakingAndOccluded " + isWakingAndOccluded); if (!mScreenOffAnimationController.isKeyguardHideDelayed() // If we're animating occluded, there's an activity launching over the keyguard // UI. Wait to hide it until after the animation concludes. && !mKeyguardViewMediator.isOccludeAnimationPlaying()) { + Log.d(TAG, "hideKeyguardImpl " + forceStateChange); return hideKeyguardImpl(forceStateChange); } } diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java index 6f3322ab1b85..a052f77eaff3 100644 --- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java +++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java @@ -41,6 +41,9 @@ import static com.android.keyguard.KeyguardUpdateMonitor.HAL_POWER_PRESS_TIMEOUT import static com.android.keyguard.KeyguardUpdateMonitor.getCurrentUser; import static com.android.systemui.flags.Flags.FP_LISTEN_OCCLUDING_APPS; import static com.android.systemui.flags.Flags.STOP_FACE_AUTH_ON_DISPLAY_OFF; +import static com.android.systemui.keyguard.WakefulnessLifecycle.WAKEFULNESS_ASLEEP; +import static com.android.systemui.keyguard.WakefulnessLifecycle.WAKEFULNESS_AWAKE; +import static com.android.systemui.keyguard.WakefulnessLifecycle.WAKEFULNESS_WAKING; import static com.android.systemui.statusbar.policy.DevicePostureController.DEVICE_POSTURE_CLOSED; import static com.android.systemui.statusbar.policy.DevicePostureController.DEVICE_POSTURE_OPENED; import static com.android.systemui.statusbar.policy.DevicePostureController.DEVICE_POSTURE_UNKNOWN; @@ -146,6 +149,7 @@ import com.android.systemui.biometrics.FingerprintInteractiveToAuthProvider; import com.android.systemui.broadcast.BroadcastDispatcher; import com.android.systemui.dump.DumpManager; import com.android.systemui.flags.FakeFeatureFlags; +import com.android.systemui.keyguard.WakefulnessLifecycle; import com.android.systemui.log.SessionTracker; import com.android.systemui.plugins.statusbar.StatusBarStateController; import com.android.systemui.settings.FakeDisplayTracker; @@ -283,6 +287,8 @@ public class KeyguardUpdateMonitorTest extends SysuiTestCase { private TaskStackChangeListeners mTaskStackChangeListeners; @Mock private IActivityTaskManager mActivityTaskManager; + @Mock + private WakefulnessLifecycle mWakefulness; private List<FaceSensorPropertiesInternal> mFaceSensorProperties; private List<FingerprintSensorPropertiesInternal> mFingerprintSensorProperties; @@ -3084,8 +3090,57 @@ public class KeyguardUpdateMonitorTest extends SysuiTestCase { // THEN face listening is stopped. verify(faceCancel).cancel(); verify(callback).onBiometricRunningStateChanged( - eq(false), eq(BiometricSourceType.FACE)); // beverlyt + eq(false), eq(BiometricSourceType.FACE)); + } + + @Test + public void onDisplayOff_whileAsleep_doesNotStopFaceAuth() throws RemoteException { + enableStopFaceAuthOnDisplayOff(); + when(mWakefulness.getWakefulness()).thenReturn(WAKEFULNESS_ASLEEP); + // GIVEN device is listening for face + mKeyguardUpdateMonitor.setKeyguardShowing(true, false); + mKeyguardUpdateMonitor.dispatchStartedWakingUp(PowerManager.WAKE_REASON_POWER_BUTTON); + mTestableLooper.processAllMessages(); + verifyFaceAuthenticateCall(); + + final CancellationSignal faceCancel = spy(mKeyguardUpdateMonitor.mFaceCancelSignal); + mKeyguardUpdateMonitor.mFaceCancelSignal = faceCancel; + KeyguardUpdateMonitorCallback callback = mock(KeyguardUpdateMonitorCallback.class); + mKeyguardUpdateMonitor.registerCallback(callback); + + // WHEN the default display state changes to OFF + triggerDefaultDisplayStateChangeToOff(); + + // THEN face listening is NOT stopped. + verify(faceCancel, never()).cancel(); + verify(callback, never()).onBiometricRunningStateChanged( + eq(false), eq(BiometricSourceType.FACE)); + } + + @Test + public void onDisplayOff_whileWaking_doesNotStopFaceAuth() throws RemoteException { + enableStopFaceAuthOnDisplayOff(); + when(mWakefulness.getWakefulness()).thenReturn(WAKEFULNESS_WAKING); + + // GIVEN device is listening for face + mKeyguardUpdateMonitor.setKeyguardShowing(true, false); + mKeyguardUpdateMonitor.dispatchStartedWakingUp(PowerManager.WAKE_REASON_POWER_BUTTON); + mTestableLooper.processAllMessages(); + verifyFaceAuthenticateCall(); + + final CancellationSignal faceCancel = spy(mKeyguardUpdateMonitor.mFaceCancelSignal); + mKeyguardUpdateMonitor.mFaceCancelSignal = faceCancel; + KeyguardUpdateMonitorCallback callback = mock(KeyguardUpdateMonitorCallback.class); + mKeyguardUpdateMonitor.registerCallback(callback); + + // WHEN the default display state changes to OFF + triggerDefaultDisplayStateChangeToOff(); + + // THEN face listening is NOT stopped. + verify(faceCancel, never()).cancel(); + verify(callback, never()).onBiometricRunningStateChanged( + eq(false), eq(BiometricSourceType.FACE)); } private void triggerDefaultDisplayStateChangeToOn() { @@ -3393,6 +3448,7 @@ public class KeyguardUpdateMonitorTest extends SysuiTestCase { mFeatureFlags.set(STOP_FACE_AUTH_ON_DISPLAY_OFF, true); mKeyguardUpdateMonitor = new TestableKeyguardUpdateMonitor(mContext); setupBiometrics(mKeyguardUpdateMonitor); + when(mWakefulness.getWakefulness()).thenReturn(WAKEFULNESS_AWAKE); assertThat(mDisplayTracker.getDisplayCallbacks().size()).isEqualTo(1); } @@ -3473,7 +3529,8 @@ public class KeyguardUpdateMonitorTest extends SysuiTestCase { mPackageManager, mFaceManager, mFingerprintManager, mBiometricManager, mFaceWakeUpTriggersConfig, mDevicePostureController, Optional.of(mInteractiveToAuthProvider), mFeatureFlags, - mTaskStackChangeListeners, mActivityTaskManager, mDisplayTracker); + mTaskStackChangeListeners, mActivityTaskManager, mDisplayTracker, + mWakefulness); setStrongAuthTracker(KeyguardUpdateMonitorTest.this.mStrongAuthTracker); } diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsBpViewControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsBpViewControllerTest.kt index 7de78a60b73e..9be3d8201053 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsBpViewControllerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsBpViewControllerTest.kt @@ -23,14 +23,19 @@ import com.android.systemui.RoboPilotTest import com.android.systemui.SysuiTestCase import com.android.systemui.dump.DumpManager import com.android.systemui.plugins.statusbar.StatusBarStateController +import com.android.systemui.shade.ShadeExpansionChangeEvent import com.android.systemui.shade.ShadeExpansionStateManager import com.android.systemui.statusbar.phone.SystemUIDialogManager +import com.android.systemui.util.mockito.any +import com.android.systemui.util.mockito.whenever +import com.android.systemui.util.mockito.withArgCaptor import org.junit.Assert.assertFalse import org.junit.Before import org.junit.Rule import org.junit.Test import org.junit.runner.RunWith import org.mockito.Mock +import org.mockito.Mockito.verify import org.mockito.junit.MockitoJUnit @SmallTest @@ -51,6 +56,8 @@ class UdfpsBpViewControllerTest : SysuiTestCase() { @Before fun setup() { + whenever(shadeExpansionStateManager.addExpansionListener(any())) + .thenReturn(ShadeExpansionChangeEvent(0f, false, false, 0f)) udfpsBpViewController = UdfpsBpViewController( udfpsBpView, @@ -62,7 +69,32 @@ class UdfpsBpViewControllerTest : SysuiTestCase() { } @Test - fun testShouldNeverPauseAuth() { + fun testPauseAuthWhenNotificationShadeDragging() { + udfpsBpViewController.onViewAttached() + val shadeExpansionListener = withArgCaptor { + verify(shadeExpansionStateManager).addExpansionListener(capture()) + } + + // When shade is tracking, should pause auth + shadeExpansionListener.onPanelExpansionChanged( + ShadeExpansionChangeEvent( + fraction = 0f, + expanded = false, + tracking = true, + dragDownPxAmount = 10f + ) + ) + assert(udfpsBpViewController.shouldPauseAuth()) + + // When shade is not tracking, don't pause auth even if expanded + shadeExpansionListener.onPanelExpansionChanged( + ShadeExpansionChangeEvent( + fraction = 0f, + expanded = true, + tracking = false, + dragDownPxAmount = 10f + ) + ) assertFalse(udfpsBpViewController.shouldPauseAuth()) } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerTest.java index e56b5c7406b6..7dd88b437f17 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerTest.java @@ -207,6 +207,8 @@ public class UdfpsControllerTest extends SysuiTestCase { private final UdfpsAnimationViewController mUdfpsKeyguardViewController = mock(UdfpsKeyguardViewControllerLegacy.class); @Mock + private UdfpsAnimationViewController mUdfpsAnimationViewController; + @Mock private SystemUIDialogManager mSystemUIDialogManager; @Mock private ActivityLaunchAnimator mActivityLaunchAnimator; @@ -267,6 +269,7 @@ public class UdfpsControllerTest extends SysuiTestCase { when(mSessionTracker.getSessionId(anyInt())).thenReturn( (new InstanceIdSequence(1 << 20)).newInstanceId()); when(mUdfpsView.getViewRootImpl()).thenReturn(mViewRootImpl); + when(mUdfpsView.getAnimationViewController()).thenReturn(mUdfpsAnimationViewController); final List<ComponentInfoInternal> componentInfo = new ArrayList<>(); componentInfo.add(new ComponentInfoInternal("faceSensor" /* componentId */, @@ -1380,6 +1383,50 @@ public class UdfpsControllerTest extends SysuiTestCase { } @Test + public void onTouch_withNewTouchDetection_ignoreIfAuthPaused() throws RemoteException { + final NormalizedTouchData touchData = new NormalizedTouchData(0, 0f, 0f, 0f, 0f, 0f, 0L, + 0L); + final TouchProcessorResult processorResultDown = + new TouchProcessorResult.ProcessedTouch(InteractionEvent.DOWN, + 1 /* pointerId */, touchData); + + // Enable new touch detection. + when(mFeatureFlags.isEnabled(Flags.UDFPS_NEW_TOUCH_DETECTION)).thenReturn(true); + + // Configure UdfpsController to use FingerprintManager as opposed to AlternateTouchProvider. + initUdfpsController(mOpticalProps, false /* hasAlternateTouchProvider */); + + // Configure UdfpsView to not accept the ACTION_DOWN event + when(mUdfpsView.isDisplayConfigured()).thenReturn(true); + when(mUdfpsView.isWithinSensorArea(anyFloat(), anyFloat())).thenReturn(true); + + // GIVEN that auth is paused + when(mUdfpsAnimationViewController.shouldPauseAuth()).thenReturn(true); + + // GIVEN that the overlay is showing and a11y touch exploration NOT enabled + when(mAccessibilityManager.isTouchExplorationEnabled()).thenReturn(false); + mOverlayController.showUdfpsOverlay(TEST_REQUEST_ID, mOpticalProps.sensorId, + BiometricOverlayConstants.REASON_AUTH_KEYGUARD, mUdfpsOverlayControllerCallback); + mFgExecutor.runAllReady(); + + verify(mUdfpsView).setOnTouchListener(mTouchListenerCaptor.capture()); + + // WHEN ACTION_DOWN is received and touch is within sensor + when(mSinglePointerTouchProcessor.processTouch(any(), anyInt(), any())).thenReturn( + processorResultDown); + MotionEvent downEvent = MotionEvent.obtain(0, 0, ACTION_DOWN, 0, 0, 0); + mTouchListenerCaptor.getValue().onTouch(mUdfpsView, downEvent); + mBiometricExecutor.runAllReady(); + downEvent.recycle(); + + // THEN the touch is ignored + verify(mInputManager, never()).pilferPointers(any()); + verify(mFingerprintManager, never()).onPointerDown(anyLong(), anyInt(), anyInt(), + anyFloat(), anyFloat(), anyFloat(), anyFloat(), anyFloat(), anyLong(), anyLong(), + anyBoolean()); + } + + @Test public void onTouch_withNewTouchDetection_pilferPointer() throws RemoteException { final NormalizedTouchData touchData = new NormalizedTouchData(0, 0f, 0f, 0f, 0f, 0f, 0L, 0L); diff --git a/packages/SystemUI/tests/src/com/android/systemui/notetask/NoteTaskInitializerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/notetask/NoteTaskInitializerTest.kt index 95bb3e0a4538..78330078076c 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/notetask/NoteTaskInitializerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/notetask/NoteTaskInitializerTest.kt @@ -22,6 +22,8 @@ import android.os.UserManager import android.testing.AndroidTestingRunner import android.view.KeyEvent import android.view.KeyEvent.ACTION_DOWN +import android.view.KeyEvent.ACTION_UP +import android.view.KeyEvent.KEYCODE_N import android.view.KeyEvent.KEYCODE_STYLUS_BUTTON_TAIL import androidx.test.filters.SmallTest import com.android.keyguard.KeyguardUpdateMonitor @@ -30,7 +32,6 @@ import com.android.systemui.settings.FakeUserTracker import com.android.systemui.statusbar.CommandQueue import com.android.systemui.util.concurrency.FakeExecutor import com.android.systemui.util.mockito.any -import com.android.systemui.util.mockito.argumentCaptor import com.android.systemui.util.mockito.eq import com.android.systemui.util.mockito.mock import com.android.systemui.util.mockito.whenever @@ -43,7 +44,6 @@ import kotlinx.coroutines.ExperimentalCoroutinesApi import org.junit.Before import org.junit.Test import org.junit.runner.RunWith -import org.mockito.ArgumentCaptor import org.mockito.Mock import org.mockito.Mockito.never import org.mockito.Mockito.times @@ -66,6 +66,7 @@ internal class NoteTaskInitializerTest : SysuiTestCase() { private val executor = FakeExecutor(FakeSystemClock()) private val userTracker = FakeUserTracker() + private val handlerCallbacks = mutableListOf<Runnable>() @Before fun setUp() { @@ -74,19 +75,27 @@ internal class NoteTaskInitializerTest : SysuiTestCase() { } private fun createUnderTest( - isEnabled: Boolean, - bubbles: Bubbles?, + isEnabled: Boolean, + bubbles: Bubbles?, ): NoteTaskInitializer = - NoteTaskInitializer( - controller = controller, - commandQueue = commandQueue, - optionalBubbles = Optional.ofNullable(bubbles), - isEnabled = isEnabled, - roleManager = roleManager, - userTracker = userTracker, - keyguardUpdateMonitor = keyguardMonitor, - backgroundExecutor = executor, - ) + NoteTaskInitializer( + controller = controller, + commandQueue = commandQueue, + optionalBubbles = Optional.ofNullable(bubbles), + isEnabled = isEnabled, + roleManager = roleManager, + userTracker = userTracker, + keyguardUpdateMonitor = keyguardMonitor, + backgroundExecutor = executor, + ) + + private fun createKeyEvent( + action: Int, + code: Int, + downTime: Long = 0L, + eventTime: Long = 0L, + metaState: Int = 0 + ): KeyEvent = KeyEvent(downTime, eventTime, action, code, 0 /*repeat*/, metaState) @Test fun initialize_withUserUnlocked() { @@ -120,12 +129,12 @@ internal class NoteTaskInitializerTest : SysuiTestCase() { underTest.initialize() verifyZeroInteractions( - commandQueue, - bubbles, - controller, - roleManager, - userManager, - keyguardMonitor, + commandQueue, + bubbles, + controller, + roleManager, + userManager, + keyguardMonitor, ) } @@ -136,18 +145,23 @@ internal class NoteTaskInitializerTest : SysuiTestCase() { underTest.initialize() verifyZeroInteractions( - commandQueue, - bubbles, - controller, - roleManager, - userManager, - keyguardMonitor, + commandQueue, + bubbles, + controller, + roleManager, + userManager, + keyguardMonitor, ) } @Test fun initialize_handleSystemKey() { - val expectedKeyEvent = KeyEvent(ACTION_DOWN, KEYCODE_STYLUS_BUTTON_TAIL) + val expectedKeyEvent = + createKeyEvent( + ACTION_DOWN, + KEYCODE_N, + metaState = KeyEvent.META_META_ON or KeyEvent.META_CTRL_ON + ) val underTest = createUnderTest(isEnabled = true, bubbles = bubbles) underTest.initialize() val callback = withArgCaptor { verify(commandQueue).addCallback(capture()) } @@ -176,7 +190,7 @@ internal class NoteTaskInitializerTest : SysuiTestCase() { underTest.initialize() val callback = withArgCaptor { verify(roleManager) - .addOnRoleHoldersChangedListenerAsUser(any(), capture(), eq(UserHandle.ALL)) + .addOnRoleHoldersChangedListenerAsUser(any(), capture(), eq(UserHandle.ALL)) } callback.onRoleHoldersChanged(ROLE_NOTES, userTracker.userHandle) @@ -203,4 +217,60 @@ internal class NoteTaskInitializerTest : SysuiTestCase() { verify(controller, times(2)).updateNoteTaskForCurrentUserAndManagedProfiles() } + + @Test + fun tailButtonGestureDetection_singlePress_shouldShowNoteTaskOnUp() { + val underTest = createUnderTest(isEnabled = true, bubbles = bubbles) + underTest.initialize() + val callback = withArgCaptor { verify(commandQueue).addCallback(capture()) } + + callback.handleSystemKey( + createKeyEvent(ACTION_DOWN, KEYCODE_STYLUS_BUTTON_TAIL, downTime = 0, eventTime = 0) + ) + verify(controller, never()).showNoteTask(any()) + + callback.handleSystemKey( + createKeyEvent(ACTION_UP, KEYCODE_STYLUS_BUTTON_TAIL, downTime = 0, eventTime = 50) + ) + + verify(controller).showNoteTask(any()) + } + + @Test + fun tailButtonGestureDetection_doublePress_shouldNotShowNoteTaskTwice() { + val underTest = createUnderTest(isEnabled = true, bubbles = bubbles) + underTest.initialize() + val callback = withArgCaptor { verify(commandQueue).addCallback(capture()) } + + callback.handleSystemKey( + createKeyEvent(ACTION_DOWN, KEYCODE_STYLUS_BUTTON_TAIL, downTime = 0, eventTime = 0) + ) + callback.handleSystemKey( + createKeyEvent(ACTION_UP, KEYCODE_STYLUS_BUTTON_TAIL, downTime = 0, eventTime = 50) + ) + callback.handleSystemKey( + createKeyEvent(ACTION_DOWN, KEYCODE_STYLUS_BUTTON_TAIL, downTime = 99, eventTime = 99) + ) + callback.handleSystemKey( + createKeyEvent(ACTION_UP, KEYCODE_STYLUS_BUTTON_TAIL, downTime = 99, eventTime = 150) + ) + + verify(controller, times(1)).showNoteTask(any()) + } + + @Test + fun tailButtonGestureDetection_longPress_shouldNotShowNoteTask() { + val underTest = createUnderTest(isEnabled = true, bubbles = bubbles) + underTest.initialize() + val callback = withArgCaptor { verify(commandQueue).addCallback(capture()) } + + callback.handleSystemKey( + createKeyEvent(ACTION_DOWN, KEYCODE_STYLUS_BUTTON_TAIL, downTime = 0, eventTime = 0) + ) + callback.handleSystemKey( + createKeyEvent(ACTION_UP, KEYCODE_STYLUS_BUTTON_TAIL, downTime = 0, eventTime = 1000) + ) + + verify(controller, never()).showNoteTask(any()) + } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogImplTest.java index 2f563ddfae9c..33deb6546d15 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogImplTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogImplTest.java @@ -41,6 +41,7 @@ import static org.mockito.Mockito.when; import android.app.KeyguardManager; import android.content.res.Configuration; import android.media.AudioManager; +import android.os.Handler; import android.os.SystemClock; import android.testing.AndroidTestingRunner; import android.testing.TestableLooper; @@ -146,6 +147,10 @@ public class VolumeDialogImplTest extends SysuiTestCase { mTestableLooper = TestableLooper.get(this); allowTestableLooperAsMainThread(); + // Ensure previous tests have not left messages on main looper + Handler localHandler = new Handler(mTestableLooper.getLooper()); + localHandler.removeCallbacksAndMessages(null); + when(mPostureController.getDevicePosture()) .thenReturn(DevicePostureController.DEVICE_POSTURE_CLOSED); diff --git a/services/core/java/com/android/server/LogMteState.java b/services/core/java/com/android/server/LogMteState.java index 410dd8339b30..ec0492b19f89 100644 --- a/services/core/java/com/android/server/LogMteState.java +++ b/services/core/java/com/android/server/LogMteState.java @@ -16,11 +16,12 @@ package com.android.server; +import static com.android.internal.util.ConcurrentUtils.DIRECT_EXECUTOR; + import android.app.StatsManager; import android.content.Context; import android.util.StatsEvent; -import com.android.internal.os.BackgroundThread; import com.android.internal.os.Zygote; import com.android.internal.util.FrameworkStatsLog; @@ -32,7 +33,7 @@ public class LogMteState { .setPullAtomCallback( FrameworkStatsLog.MTE_STATE, null, // use default PullAtomMetadata values - BackgroundThread.getExecutor(), + DIRECT_EXECUTOR, new StatsManager.StatsPullAtomCallback() { @Override public int onPullAtom(int atomTag, List<StatsEvent> data) { diff --git a/services/core/java/com/android/server/am/ActiveServices.java b/services/core/java/com/android/server/am/ActiveServices.java index 66ea4d06d2b7..592628af2a8d 100644 --- a/services/core/java/com/android/server/am/ActiveServices.java +++ b/services/core/java/com/android/server/am/ActiveServices.java @@ -97,6 +97,11 @@ import static android.os.Process.SYSTEM_UID; import static android.os.Process.ZYGOTE_POLICY_FLAG_EMPTY; import static com.android.internal.messages.nano.SystemMessageProto.SystemMessage.NOTE_FOREGROUND_SERVICE_BG_LAUNCH; +import static com.android.internal.util.FrameworkStatsLog.FOREGROUND_SERVICE_STATE_CHANGED__FGS_START_API__FGSSTARTAPI_DELEGATE; +import static com.android.internal.util.FrameworkStatsLog.FOREGROUND_SERVICE_STATE_CHANGED__FGS_START_API__FGSSTARTAPI_NA; +import static com.android.internal.util.FrameworkStatsLog.FOREGROUND_SERVICE_STATE_CHANGED__FGS_START_API__FGSSTARTAPI_NONE; +import static com.android.internal.util.FrameworkStatsLog.FOREGROUND_SERVICE_STATE_CHANGED__FGS_START_API__FGSSTARTAPI_START_FOREGROUND_SERVICE; +import static com.android.internal.util.FrameworkStatsLog.FOREGROUND_SERVICE_STATE_CHANGED__FGS_START_API__FGSSTARTAPI_START_SERVICE; import static com.android.internal.util.FrameworkStatsLog.FOREGROUND_SERVICE_STATE_CHANGED__STATE__DENIED; import static com.android.internal.util.FrameworkStatsLog.FOREGROUND_SERVICE_STATE_CHANGED__STATE__ENTER; import static com.android.internal.util.FrameworkStatsLog.FOREGROUND_SERVICE_STATE_CHANGED__STATE__EXIT; @@ -122,6 +127,7 @@ import static com.android.server.am.ActivityManagerDebugConfig.TAG_AM; import static com.android.server.am.ActivityManagerDebugConfig.TAG_WITH_CLASS_NAME; import android.Manifest; +import android.Manifest.permission; import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.Nullable; @@ -901,7 +907,10 @@ public final class ActiveServices { showFgsBgRestrictedNotificationLocked(r); logFGSStateChangeLocked(r, FOREGROUND_SERVICE_STATE_CHANGED__STATE__DENIED, - 0, FGS_STOP_REASON_UNKNOWN, FGS_TYPE_POLICY_CHECK_UNKNOWN); + 0, FGS_STOP_REASON_UNKNOWN, FGS_TYPE_POLICY_CHECK_UNKNOWN, + FOREGROUND_SERVICE_STATE_CHANGED__FGS_START_API__FGSSTARTAPI_NA, + false /* fgsRestrictionRecalculated */ + ); if (CompatChanges.isChangeEnabled(FGS_START_EXCEPTION_CHANGE_ID, callingUid)) { throw new ForegroundServiceStartNotAllowedException(msg); } @@ -2066,6 +2075,7 @@ public final class ActiveServices { boolean alreadyStartedOp = false; boolean stopProcStatsOp = false; + final boolean origFgRequired = r.fgRequired; if (r.fgRequired) { if (DEBUG_SERVICE || DEBUG_BACKGROUND_CHECK) { Slog.i(TAG, "Service called startForeground() as required: " + r); @@ -2117,6 +2127,9 @@ public final class ActiveServices { // Whether to extend the SHORT_SERVICE time out. boolean extendShortServiceTimeout = false; + // Whether setFgsRestrictionLocked() is called in here. Only used for logging. + boolean fgsRestrictionRecalculated = false; + int fgsTypeCheckCode = FGS_TYPE_POLICY_CHECK_UNKNOWN; if (!ignoreForeground) { if (foregroundServiceType == FOREGROUND_SERVICE_TYPE_SHORT_SERVICE @@ -2182,6 +2195,7 @@ public final class ActiveServices { r.appInfo.uid, r.intent.getIntent(), r, r.userId, BackgroundStartPrivileges.NONE, false /* isBindService */); + fgsRestrictionRecalculated = true; if (!r.isFgsAllowedStart()) { Slog.w(TAG_SERVICE, "FGS type change to/from SHORT_SERVICE: " + " BFSL DENIED."); @@ -2246,6 +2260,7 @@ public final class ActiveServices { r.appInfo.uid, r.intent.getIntent(), r, r.userId, BackgroundStartPrivileges.NONE, false /* isBindService */); + fgsRestrictionRecalculated = true; final String temp = "startForegroundDelayMs:" + delayMs; if (r.mInfoAllowStartForeground != null) { r.mInfoAllowStartForeground += "; " + temp; @@ -2266,6 +2281,25 @@ public final class ActiveServices { r.appInfo.uid, r.intent.getIntent(), r, r.userId, BackgroundStartPrivileges.NONE, false /* isBindService */); + fgsRestrictionRecalculated = true; + } + + // When startForeground() is called on a bound service, without having + // it started (i.e. no Context.startService() or startForegroundService() was + // called.) + // called on it, then we probably didn't call setFgsRestrictionLocked() + // in startService(). If fgsRestrictionRecalculated is false, then we + // didn't call setFgsRestrictionLocked() here either. + // + // In this situation, we call setFgsRestrictionLocked() with + // forBoundFgs = false, so we'd set the FGS allowed reason to the + // by-bindings fields, so we can put it in the log, without affecting the + // logic. + if (!fgsRestrictionRecalculated && !r.startRequested) { + setFgsRestrictionLocked(r.serviceInfo.packageName, r.app.getPid(), + r.appInfo.uid, r.intent.getIntent(), r, r.userId, + BackgroundStartPrivileges.NONE, + false /* isBindService */, true /* forBoundFgs */); } // If the foreground service is not started from TOP process, do not allow it to @@ -2291,7 +2325,10 @@ public final class ActiveServices { ignoreForeground = true; logFGSStateChangeLocked(r, FOREGROUND_SERVICE_STATE_CHANGED__STATE__DENIED, - 0, FGS_STOP_REASON_UNKNOWN, FGS_TYPE_POLICY_CHECK_UNKNOWN); + 0, FGS_STOP_REASON_UNKNOWN, FGS_TYPE_POLICY_CHECK_UNKNOWN, + FOREGROUND_SERVICE_STATE_CHANGED__FGS_START_API__FGSSTARTAPI_NA, + false /* fgsRestrictionRecalculated */ + ); if (CompatChanges.isChangeEnabled(FGS_START_EXCEPTION_CHANGE_ID, r.appInfo.uid)) { throw new ForegroundServiceStartNotAllowedException(msg); @@ -2331,7 +2368,10 @@ public final class ActiveServices { if (fgsTypeResult.second != null) { logFGSStateChangeLocked(r, FOREGROUND_SERVICE_STATE_CHANGED__STATE__DENIED, - 0, FGS_STOP_REASON_UNKNOWN, fgsTypeResult.first); + 0, FGS_STOP_REASON_UNKNOWN, fgsTypeResult.first, + FOREGROUND_SERVICE_STATE_CHANGED__FGS_START_API__FGSSTARTAPI_NA, + false /* fgsRestrictionRecalculated */ + ); throw fgsTypeResult.second; } } @@ -2403,9 +2443,24 @@ public final class ActiveServices { AppOpsManager.ATTRIBUTION_CHAIN_ID_NONE); registerAppOpCallbackLocked(r); mAm.updateForegroundServiceUsageStats(r.name, r.userId, true); + + int fgsStartApi = FOREGROUND_SERVICE_STATE_CHANGED__FGS_START_API__FGSSTARTAPI_NONE; + if (r.startRequested) { + if (origFgRequired) { + fgsStartApi = + FOREGROUND_SERVICE_STATE_CHANGED__FGS_START_API__FGSSTARTAPI_START_FOREGROUND_SERVICE; + } else { + fgsStartApi = + FOREGROUND_SERVICE_STATE_CHANGED__FGS_START_API__FGSSTARTAPI_START_SERVICE; + } + } + logFGSStateChangeLocked(r, FOREGROUND_SERVICE_STATE_CHANGED__STATE__ENTER, - 0, FGS_STOP_REASON_UNKNOWN, fgsTypeCheckCode); + 0, FGS_STOP_REASON_UNKNOWN, fgsTypeCheckCode, + fgsStartApi, + fgsRestrictionRecalculated + ); synchronized (mFGSLogger) { mFGSLogger.logForegroundServiceStart(r.appInfo.uid, 0, r); } @@ -2499,7 +2554,10 @@ public final class ActiveServices { r.mFgsExitTime > r.mFgsEnterTime ? (int) (r.mFgsExitTime - r.mFgsEnterTime) : 0, FGS_STOP_REASON_STOP_FOREGROUND, - FGS_TYPE_POLICY_CHECK_UNKNOWN); + FGS_TYPE_POLICY_CHECK_UNKNOWN, + FOREGROUND_SERVICE_STATE_CHANGED__FGS_START_API__FGSSTARTAPI_NA, + false /* fgsRestrictionRecalculated */ + ); synchronized (mFGSLogger) { mFGSLogger.logForegroundServiceStop(r.appInfo.uid, r); @@ -3338,7 +3396,10 @@ public final class ActiveServices { FOREGROUND_SERVICE_STATE_CHANGED__STATE__TIMED_OUT, nowUptime > sr.mFgsEnterTime ? (int) (nowUptime - sr.mFgsEnterTime) : 0, FGS_STOP_REASON_UNKNOWN, - FGS_TYPE_POLICY_CHECK_UNKNOWN); + FGS_TYPE_POLICY_CHECK_UNKNOWN, + FOREGROUND_SERVICE_STATE_CHANGED__FGS_START_API__FGSSTARTAPI_NA, + false /* fgsRestrictionRecalculated */ + ); try { sr.app.getThread().scheduleTimeoutService(sr, sr.getShortFgsInfo().getStartId()); } catch (RemoteException e) { @@ -5685,7 +5746,10 @@ public final class ActiveServices { r.mFgsExitTime > r.mFgsEnterTime ? (int) (r.mFgsExitTime - r.mFgsEnterTime) : 0, FGS_STOP_REASON_STOP_SERVICE, - FGS_TYPE_POLICY_CHECK_UNKNOWN); + FGS_TYPE_POLICY_CHECK_UNKNOWN, + FOREGROUND_SERVICE_STATE_CHANGED__FGS_START_API__FGSSTARTAPI_NA, + false /* fgsRestrictionRecalculated */ + ); synchronized (mFGSLogger) { mFGSLogger.logForegroundServiceStop(r.appInfo.uid, r); } @@ -7430,6 +7494,13 @@ public final class ActiveServices { } } + private void setFgsRestrictionLocked(String callingPackage, + int callingPid, int callingUid, Intent intent, ServiceRecord r, int userId, + BackgroundStartPrivileges backgroundStartPrivileges, boolean isBindService) { + setFgsRestrictionLocked(callingPackage, callingPid, callingUid, intent, r, userId, + backgroundStartPrivileges, isBindService, /*forBoundFgs*/ false); + } + /** * There are two FGS restrictions: * In R, mAllowWhileInUsePermissionInFgs is to allow while-in-use permissions in foreground @@ -7441,11 +7512,14 @@ public final class ActiveServices { * @param intent intent to start/bind service. * @param r the service to start. * @param isBindService True if it's called from bindService(). + * @param forBoundFgs set to true if it's called from Service.startForeground() for a + * service that's not started but bound. * @return true if allow, false otherwise. */ private void setFgsRestrictionLocked(String callingPackage, int callingPid, int callingUid, Intent intent, ServiceRecord r, int userId, - BackgroundStartPrivileges backgroundStartPrivileges, boolean isBindService) { + BackgroundStartPrivileges backgroundStartPrivileges, boolean isBindService, + boolean forBoundFgs) { @ReasonCode int allowWIU; @ReasonCode int allowStart; @@ -7489,9 +7563,19 @@ public final class ActiveServices { r.mAllowWIUInBindService = allowWIU; r.mAllowStartInBindService = allowStart; } else { - r.mAllowWhileInUsePermissionInFgsReasonNoBinding = allowWIU; - r.mAllowStartForegroundNoBinding = allowStart; - + if (!forBoundFgs) { + // This is for "normal" situation. + r.mAllowWhileInUsePermissionInFgsReasonNoBinding = allowWIU; + r.mAllowStartForegroundNoBinding = allowStart; + } else { + // This logic is only for logging, so we only update the "by-binding" fields. + if (r.mAllowWIUByBindings == REASON_DENIED) { + r.mAllowWIUByBindings = allowWIU; + } + if (r.mAllowStartByBindings == REASON_DENIED) { + r.mAllowStartByBindings = allowStart; + } + } // Also do a binding client check, unless called from bindService(). if (r.mAllowWIUByBindings == REASON_DENIED) { r.mAllowWIUByBindings = @@ -8115,7 +8199,10 @@ public final class ActiveServices { */ private void logFGSStateChangeLocked(ServiceRecord r, int state, int durationMs, @FgsStopReason int fgsStopReason, - @ForegroundServicePolicyCheckCode int fgsTypeCheckCode) { + @ForegroundServicePolicyCheckCode int fgsTypeCheckCode, + int fgsStartApi, // from ForegroundServiceStateChanged.FgsStartApi + boolean fgsRestrictionRecalculated + ) { if (!ActivityManagerUtils.shouldSamplePackageForAtom( r.packageName, mAm.mConstants.mFgsAtomSampleRate)) { return; @@ -8172,7 +8259,9 @@ public final class ActiveServices { r.mAllowWIUByBindings, r.mAllowStartForegroundNoBinding, r.mAllowStartInBindService, - r.mAllowStartByBindings); + r.mAllowStartByBindings, + fgsStartApi, + fgsRestrictionRecalculated); int event = 0; if (state == FOREGROUND_SERVICE_STATE_CHANGED__STATE__ENTER) { @@ -8351,7 +8440,10 @@ public final class ActiveServices { } logFGSStateChangeLocked(r, FrameworkStatsLog.FOREGROUND_SERVICE_STATE_CHANGED__STATE__ENTER, - 0, FGS_STOP_REASON_UNKNOWN, FGS_TYPE_POLICY_CHECK_UNKNOWN); + 0, FGS_STOP_REASON_UNKNOWN, FGS_TYPE_POLICY_CHECK_UNKNOWN, + FOREGROUND_SERVICE_STATE_CHANGED__FGS_START_API__FGSSTARTAPI_DELEGATE, + false /* fgsRestrictionRecalculated */ + ); // Notify the caller. if (connection != null) { mAm.mHandler.post(() -> { diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java index 1fa60fef401b..47abc1092c49 100644 --- a/services/core/java/com/android/server/am/ActivityManagerService.java +++ b/services/core/java/com/android/server/am/ActivityManagerService.java @@ -16767,7 +16767,7 @@ public class ActivityManagerService extends IActivityManager.Stub for (int i = 0; i < N; i++) { PendingTempAllowlist ptw = list[i]; mLocalDeviceIdleController.addPowerSaveTempWhitelistAppDirect(ptw.targetUid, - ptw.duration, ptw.type, true, ptw.reasonCode, ptw.tag, + ptw.duration, ptw.type, false, ptw.reasonCode, ptw.tag, ptw.callingUid); } } diff --git a/services/core/java/com/android/server/am/BatteryStatsService.java b/services/core/java/com/android/server/am/BatteryStatsService.java index 2c745ae3bf55..be123f36ebcc 100644 --- a/services/core/java/com/android/server/am/BatteryStatsService.java +++ b/services/core/java/com/android/server/am/BatteryStatsService.java @@ -27,6 +27,8 @@ import static android.net.NetworkCapabilities.TRANSPORT_WIFI; import static android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK; import static android.os.BatteryStats.POWER_DATA_UNAVAILABLE; +import static com.android.internal.util.ConcurrentUtils.DIRECT_EXECUTOR; + import android.annotation.EnforcePermission; import android.annotation.NonNull; import android.annotation.RequiresNoPermission; @@ -95,7 +97,6 @@ import android.util.StatsEvent; import com.android.internal.R; import com.android.internal.annotations.GuardedBy; import com.android.internal.app.IBatteryStats; -import com.android.internal.os.BackgroundThread; import com.android.internal.os.BinderCallsStats; import com.android.internal.os.PowerProfile; import com.android.internal.os.RailStats; @@ -839,15 +840,15 @@ public final class BatteryStatsService extends IBatteryStats.Stub statsManager.setPullAtomCallback( FrameworkStatsLog.BATTERY_USAGE_STATS_SINCE_RESET, null, // use default PullAtomMetadata values - BackgroundThread.getExecutor(), pullAtomCallback); + DIRECT_EXECUTOR, pullAtomCallback); statsManager.setPullAtomCallback( FrameworkStatsLog.BATTERY_USAGE_STATS_SINCE_RESET_USING_POWER_PROFILE_MODEL, null, // use default PullAtomMetadata values - BackgroundThread.getExecutor(), pullAtomCallback); + DIRECT_EXECUTOR, pullAtomCallback); statsManager.setPullAtomCallback( FrameworkStatsLog.BATTERY_USAGE_STATS_BEFORE_RESET, null, // use default PullAtomMetadata values - BackgroundThread.getExecutor(), pullAtomCallback); + DIRECT_EXECUTOR, pullAtomCallback); } /** StatsPullAtomCallback for pulling BatteryUsageStats data. */ diff --git a/services/core/java/com/android/server/am/ForegroundServiceTypeLoggerModule.java b/services/core/java/com/android/server/am/ForegroundServiceTypeLoggerModule.java index f6859d1f027e..caafb421a147 100644 --- a/services/core/java/com/android/server/am/ForegroundServiceTypeLoggerModule.java +++ b/services/core/java/com/android/server/am/ForegroundServiceTypeLoggerModule.java @@ -27,6 +27,8 @@ import static android.app.ActivityManager.FOREGROUND_SERVICE_API_TYPE_PHONE_CALL import static android.app.ActivityManager.FOREGROUND_SERVICE_API_TYPE_USB; import static android.os.Process.INVALID_UID; +import static com.android.internal.util.FrameworkStatsLog.FOREGROUND_SERVICE_STATE_CHANGED__FGS_START_API__FGSSTARTAPI_NA; + import android.annotation.IntDef; import android.app.ActivityManager; import android.app.ActivityManager.ForegroundServiceApiType; @@ -218,6 +220,24 @@ public class ForegroundServiceTypeLoggerModule { final ArrayList<Long> timestampsFound = new ArrayList<>(); for (int i = 0, size = apiTypes.size(); i < size; i++) { final int apiType = apiTypes.get(i); + + // remove the FGS record from the stack + final ArrayMap<ComponentName, ServiceRecord> runningFgsOfType = + uidState.mRunningFgs.get(apiType); + if (runningFgsOfType == null) { + Slog.w(TAG, "Could not find appropriate running FGS for FGS stop for UID " + uid + + " in package " + record.packageName); + continue; + } + + runningFgsOfType.remove(record.getComponentName()); + if (runningFgsOfType.size() == 0) { + // there's no more FGS running for this type, just get rid of it + uidState.mRunningFgs.remove(apiType); + // but we need to keep track of the timestamp in case an API stops + uidState.mLastFgsTimeStamp.put(apiType, System.currentTimeMillis()); + } + final int apiTypeIndex = uidState.mOpenWithFgsCount.indexOfKey(apiType); if (apiTypeIndex < 0) { Slog.w(TAG, "Logger should be tracking FGS types correctly for UID " + uid @@ -236,22 +256,6 @@ public class ForegroundServiceTypeLoggerModule { // remove the last API close call uidState.mApiClosedCalls.remove(apiType); } - // remove the FGS record from the stack - final ArrayMap<ComponentName, ServiceRecord> runningFgsOfType = - uidState.mRunningFgs.get(apiType); - if (runningFgsOfType == null) { - Slog.w(TAG, "Could not find appropriate running FGS for FGS stop for UID " + uid - + " in package " + record.packageName); - continue; - } - - runningFgsOfType.remove(record.getComponentName()); - if (runningFgsOfType.size() == 0) { - // there's no more FGS running for this type, just get rid of it - uidState.mRunningFgs.remove(apiType); - // but we need to keep track of the timestamp in case an API stops - uidState.mLastFgsTimeStamp.put(apiType, System.currentTimeMillis()); - } } if (!apisFound.isEmpty()) { // time to log the call @@ -381,9 +385,14 @@ public class ForegroundServiceTypeLoggerModule { // initialize if we don't contain uidState.mOpenedWithoutFgsCount.put(apiType, 0); } - if (uidState.mOpenedWithoutFgsCount.get(apiType) != 0) { + int apiOpenWithoutFgsCount = uidState.mOpenedWithoutFgsCount.get(apiType); + if (apiOpenWithoutFgsCount != 0) { + apiOpenWithoutFgsCount -= 1; + if (apiOpenWithoutFgsCount == 0) { + uidState.mApiOpenCalls.remove(apiType); + } uidState.mOpenedWithoutFgsCount - .put(apiType, uidState.mOpenedWithoutFgsCount.get(apiType) - 1); + .put(apiType, apiOpenWithoutFgsCount); return System.currentTimeMillis(); } // This is a part of a valid active FGS @@ -520,7 +529,10 @@ public class ForegroundServiceTypeLoggerModule { r.mAllowWIUByBindings, r.mAllowStartForegroundNoBinding, r.mAllowStartInBindService, - r.mAllowStartByBindings); + r.mAllowStartByBindings, + FOREGROUND_SERVICE_STATE_CHANGED__FGS_START_API__FGSSTARTAPI_NA, + false + ); } /** @@ -578,7 +590,10 @@ public class ForegroundServiceTypeLoggerModule { 0, 0, 0, - 0); + 0, + FOREGROUND_SERVICE_STATE_CHANGED__FGS_START_API__FGSSTARTAPI_NA, + false + ); } /** diff --git a/services/core/java/com/android/server/app/GameManagerService.java b/services/core/java/com/android/server/app/GameManagerService.java index 80d14a21cc7e..0c9cb3bd00f8 100644 --- a/services/core/java/com/android/server/app/GameManagerService.java +++ b/services/core/java/com/android/server/app/GameManagerService.java @@ -25,6 +25,7 @@ import static com.android.internal.R.styleable.GameModeConfig_allowGameDownscali import static com.android.internal.R.styleable.GameModeConfig_allowGameFpsOverride; import static com.android.internal.R.styleable.GameModeConfig_supportsBatteryGameMode; import static com.android.internal.R.styleable.GameModeConfig_supportsPerformanceGameMode; +import static com.android.internal.util.ConcurrentUtils.DIRECT_EXECUTOR; import android.Manifest; import android.annotation.NonNull; @@ -2092,17 +2093,17 @@ public final class GameManagerService extends IGameManagerService.Stub { statsManager.setPullAtomCallback( FrameworkStatsLog.GAME_MODE_INFO, null, // use default PullAtomMetadata values - BackgroundThread.getExecutor(), + DIRECT_EXECUTOR, this::onPullAtom); statsManager.setPullAtomCallback( FrameworkStatsLog.GAME_MODE_CONFIGURATION, null, // use default PullAtomMetadata values - BackgroundThread.getExecutor(), + DIRECT_EXECUTOR, this::onPullAtom); statsManager.setPullAtomCallback( FrameworkStatsLog.GAME_MODE_LISTENER, null, // use default PullAtomMetadata values - BackgroundThread.getExecutor(), + DIRECT_EXECUTOR, this::onPullAtom); } diff --git a/services/core/java/com/android/server/audio/SoundDoseHelper.java b/services/core/java/com/android/server/audio/SoundDoseHelper.java index 95b6c2cef963..81365bfbf2a6 100644 --- a/services/core/java/com/android/server/audio/SoundDoseHelper.java +++ b/services/core/java/com/android/server/audio/SoundDoseHelper.java @@ -719,7 +719,9 @@ public class SoundDoseHelper { /*package*/ void initSafeMediaVolumeIndex() { for (int i = 0; i < mSafeMediaVolumeDevices.size(); ++i) { int deviceType = mSafeMediaVolumeDevices.keyAt(i); - mSafeMediaVolumeDevices.put(deviceType, getSafeDeviceMediaVolumeIndex(deviceType)); + if (mSafeMediaVolumeDevices.valueAt(i) == SAFE_MEDIA_VOLUME_UNINITIALIZED) { + mSafeMediaVolumeDevices.put(deviceType, getSafeDeviceMediaVolumeIndex(deviceType)); + } } } @@ -743,7 +745,7 @@ public class SoundDoseHelper { } /*package*/ boolean safeDevicesContains(int device) { - return mSafeMediaVolumeDevices.indexOfKey(device) >= 0; + return mSafeMediaVolumeDevices.get(device, SAFE_MEDIA_VOLUME_UNINITIALIZED) >= 0; } /*package*/ void invalidatPendingVolumeCommand() { @@ -1014,6 +1016,7 @@ public class SoundDoseHelper { initCsd(); synchronized (mSafeMediaVolumeStateLock) { + initSafeMediaVolumeIndex(); updateSafeMediaVolume_l(caller); } } @@ -1065,11 +1068,18 @@ public class SoundDoseHelper { } private int getSafeDeviceMediaVolumeIndex(int deviceType) { - // legacy implementation uses mSafeMediaVolumeIndex for wired HS/HP - // instead of computing it from the volume curves - if ((deviceType == AudioSystem.DEVICE_OUT_WIRED_HEADPHONE - || deviceType == AudioSystem.DEVICE_OUT_WIRED_HEADSET) && !mEnableCsd.get()) { - return mSafeMediaVolumeIndex; + if (!mEnableCsd.get()) { + // legacy hearing safety only for wired and USB HS/HP + if (deviceType == AudioSystem.DEVICE_OUT_WIRED_HEADPHONE + || deviceType == AudioSystem.DEVICE_OUT_WIRED_HEADSET) { + // legacy hearing safety uses mSafeMediaVolumeIndex for wired HS/HP + // instead of computing it from the volume curves + return mSafeMediaVolumeIndex; + } + + if (deviceType != AudioSystem.DEVICE_OUT_USB_HEADSET) { + return SAFE_MEDIA_VOLUME_UNINITIALIZED; + } } // determine UI volume index corresponding to the wanted safe gain in dBFS diff --git a/services/core/java/com/android/server/biometrics/AuthenticationStatsCollector.java b/services/core/java/com/android/server/biometrics/AuthenticationStatsCollector.java index 64691e0b062b..b2b6ee65761e 100644 --- a/services/core/java/com/android/server/biometrics/AuthenticationStatsCollector.java +++ b/services/core/java/com/android/server/biometrics/AuthenticationStatsCollector.java @@ -57,6 +57,7 @@ public class AuthenticationStatsCollector { @NonNull private final FaceManager mFaceManager; @NonNull private final FingerprintManager mFingerprintManager; + private final boolean mEnabled; private final float mThreshold; private final int mModality; private boolean mPersisterInitialized = false; @@ -83,6 +84,7 @@ public class AuthenticationStatsCollector { public AuthenticationStatsCollector(@NonNull Context context, int modality, @NonNull BiometricNotification biometricNotification) { mContext = context; + mEnabled = context.getResources().getBoolean(R.bool.config_biometricFrrNotificationEnabled); mThreshold = context.getResources() .getFraction(R.fraction.config_biometricNotificationFrrThreshold, 1, 1); mUserAuthenticationStatsMap = new HashMap<>(); @@ -116,6 +118,11 @@ public class AuthenticationStatsCollector { /** Update total authentication and rejected attempts. */ public void authenticate(int userId, boolean authenticated) { + // Don't collect data if the feature is disabled. + if (!mEnabled) { + return; + } + // Don't collect data for single-modality devices or user has both biometrics enrolled. if (isSingleModalityDevice() || (hasEnrolledFace(userId) && hasEnrolledFingerprint(userId))) { diff --git a/services/core/java/com/android/server/display/DisplayManagerService.java b/services/core/java/com/android/server/display/DisplayManagerService.java index 53921d4d83b5..6466b440b3a0 100644 --- a/services/core/java/com/android/server/display/DisplayManagerService.java +++ b/services/core/java/com/android/server/display/DisplayManagerService.java @@ -116,6 +116,7 @@ import android.os.UserHandle; import android.os.UserManager; import android.provider.DeviceConfigInterface; import android.provider.Settings; +import android.sysprop.DisplayProperties; import android.text.TextUtils; import android.util.ArraySet; import android.util.EventLog; @@ -485,6 +486,9 @@ public final class DisplayManagerService extends SystemService { private boolean mBootCompleted = false; + // If we would like to keep a particular eye on a package, we can set the package name. + private boolean mExtraDisplayEventLogging; + private final BroadcastReceiver mIdleModeReceiver = new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { @@ -565,6 +569,8 @@ public final class DisplayManagerService extends SystemService { mOverlayProperties = SurfaceControl.getOverlaySupport(); mSystemReady = false; mConfigParameterProvider = new DeviceConfigParameterProvider(DeviceConfigInterface.REAL); + final String name = DisplayProperties.debug_vri_package().orElse(null); + mExtraDisplayEventLogging = !TextUtils.isEmpty(name); } public void setupSchedulerPolicies() { @@ -2874,9 +2880,10 @@ public final class DisplayManagerService extends SystemService { // Delivers display event notifications to callbacks. private void deliverDisplayEvent(int displayId, ArraySet<Integer> uids, @DisplayEvent int event) { - if (DEBUG) { + if (DEBUG || mExtraDisplayEventLogging) { Slog.d(TAG, "Delivering display event: displayId=" - + displayId + ", event=" + event); + + displayId + ", event=" + event + + (uids != null ? ", uids=" + uids : "")); } // Grab the lock and copy the callbacks. diff --git a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java index 7aea63255dc8..a1d28da5f65e 100644 --- a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java +++ b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java @@ -119,7 +119,6 @@ import android.util.SparseArray; import android.util.SparseBooleanArray; import android.util.proto.ProtoOutputStream; import android.view.DisplayInfo; -import android.view.IWindowManager; import android.view.InputChannel; import android.view.InputDevice; import android.view.MotionEvent; @@ -127,7 +126,6 @@ import android.view.WindowManager; import android.view.WindowManager.DisplayImePolicy; import android.view.WindowManager.LayoutParams; import android.view.WindowManager.LayoutParams.SoftInputModeFlags; -import android.view.WindowManagerGlobal; import android.view.inputmethod.EditorInfo; import android.view.inputmethod.ImeTracker; import android.view.inputmethod.InputBinding; @@ -3073,9 +3071,13 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub ConcurrentUtils.waitForFutureNoInterrupt(mImeDrawsImeNavBarResLazyInitFuture, "Waiting for the lazy init of mImeDrawsImeNavBarRes"); } + // Whether the current display has a navigation bar. When this is false (e.g. emulator), + // the IME should not draw the IME navigation bar. + final boolean hasNavigationBar = mWindowManagerInternal + .hasNavigationBar(mCurTokenDisplayId != INVALID_DISPLAY + ? mCurTokenDisplayId : DEFAULT_DISPLAY); final boolean canImeDrawsImeNavBar = - mImeDrawsImeNavBarRes != null && mImeDrawsImeNavBarRes.get() - && hasNavigationBarOnCurrentDisplay(); + mImeDrawsImeNavBarRes != null && mImeDrawsImeNavBarRes.get() && hasNavigationBar; final boolean shouldShowImeSwitcherWhenImeIsShown = shouldShowImeSwitcherLocked( InputMethodService.IME_ACTIVE | InputMethodService.IME_VISIBLE); return (canImeDrawsImeNavBar ? InputMethodNavButtonFlags.IME_DRAWS_IME_NAV_BAR : 0) @@ -3083,21 +3085,6 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub ? InputMethodNavButtonFlags.SHOW_IME_SWITCHER_WHEN_IME_IS_SHOWN : 0); } - /** - * Whether the current display has a navigation bar. When this is {@code false} (e.g. emulator), - * the IME should <em>not</em> draw the IME navigation bar. - */ - @GuardedBy("ImfLock.class") - private boolean hasNavigationBarOnCurrentDisplay() { - final IWindowManager wm = WindowManagerGlobal.getWindowManagerService(); - try { - return wm.hasNavigationBar(mCurTokenDisplayId != INVALID_DISPLAY - ? mCurTokenDisplayId : DEFAULT_DISPLAY); - } catch (RemoteException e) { - throw e.rethrowFromSystemServer(); - } - } - @GuardedBy("ImfLock.class") private boolean shouldShowImeSwitcherLocked(int visibility) { if (!mShowOngoingImeSwitcherForPhones) return false; diff --git a/services/core/java/com/android/server/locksettings/recoverablekeystore/RecoverableKeyStoreManager.java b/services/core/java/com/android/server/locksettings/recoverablekeystore/RecoverableKeyStoreManager.java index 24dbce49eace..390a3b2457cb 100644 --- a/services/core/java/com/android/server/locksettings/recoverablekeystore/RecoverableKeyStoreManager.java +++ b/services/core/java/com/android/server/locksettings/recoverablekeystore/RecoverableKeyStoreManager.java @@ -194,8 +194,12 @@ public class RecoverableKeyStoreManager { mApplicationKeyStorage = applicationKeyStorage; mTestCertHelper = testOnlyInsecureCertificateHelper; mCleanupManager = cleanupManager; - // Clears data for removed users. - mCleanupManager.verifyKnownUsers(); + try { + // Clears data for removed users. + mCleanupManager.verifyKnownUsers(); + } catch (Exception e) { + Log.e(TAG, "Failed to verify known users", e); + } try { mRecoverableKeyGenerator = RecoverableKeyGenerator.newInstance(mDatabase); } catch (NoSuchAlgorithmException e) { diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java index c24e729cbff5..e633ba6886d0 100644 --- a/services/core/java/com/android/server/notification/NotificationManagerService.java +++ b/services/core/java/com/android/server/notification/NotificationManagerService.java @@ -2632,7 +2632,7 @@ public class NotificationManagerService extends SystemService { mStatsManager.setPullAtomCallback( DND_MODE_RULE, null, // use default PullAtomMetadata values - BackgroundThread.getExecutor(), + ConcurrentUtils.DIRECT_EXECUTOR, mPullAtomCallback ); } diff --git a/services/core/java/com/android/server/notification/NotificationRecordLogger.java b/services/core/java/com/android/server/notification/NotificationRecordLogger.java index b015a72a653e..d2e980b7e355 100644 --- a/services/core/java/com/android/server/notification/NotificationRecordLogger.java +++ b/services/core/java/com/android/server/notification/NotificationRecordLogger.java @@ -39,6 +39,7 @@ import com.android.internal.logging.UiEvent; import com.android.internal.logging.UiEventLogger; import com.android.internal.util.FrameworkStatsLog; +import java.time.Duration; import java.util.ArrayList; import java.util.Objects; @@ -497,6 +498,7 @@ interface NotificationRecordLogger { final boolean is_non_dismissible; final int fsi_state; final boolean is_locked; + final int age_in_minutes; @DurationMillisLong long post_duration_millis; // Not final; calculated at the end. NotificationReported(NotificationRecordPair p, @@ -541,6 +543,9 @@ interface NotificationRecordLogger { hasFullScreenIntent, hasFsiRequestedButDeniedFlag, eventType); this.is_locked = p.r.isLocked(); + + this.age_in_minutes = NotificationRecordLogger.getAgeInMinutes( + p.r.getSbn().getPostTime(), p.r.getSbn().getNotification().when); } } @@ -601,4 +606,13 @@ interface NotificationRecordLogger { } return FrameworkStatsLog.NOTIFICATION_REPORTED__FSI_STATE__NO_FSI; } + + /** + * @param postTimeMs time (in {@link System#currentTimeMillis} time) the notification was posted + * @param whenMs A timestamp related to this notification, in milliseconds since the epoch. + * @return difference in duration as an integer in minutes + */ + static int getAgeInMinutes(long postTimeMs, long whenMs) { + return (int) Duration.ofMillis(postTimeMs - whenMs).toMinutes(); + } } diff --git a/services/core/java/com/android/server/notification/NotificationRecordLoggerImpl.java b/services/core/java/com/android/server/notification/NotificationRecordLoggerImpl.java index 9da0e98c1775..fc0a7764963e 100644 --- a/services/core/java/com/android/server/notification/NotificationRecordLoggerImpl.java +++ b/services/core/java/com/android/server/notification/NotificationRecordLoggerImpl.java @@ -77,7 +77,8 @@ class NotificationRecordLoggerImpl implements NotificationRecordLogger { notificationReported.is_non_dismissible, notificationReported.post_duration_millis, notificationReported.fsi_state, - notificationReported.is_locked); + notificationReported.is_locked, + notificationReported.age_in_minutes); } @Override diff --git a/services/core/java/com/android/server/pm/UserManagerService.java b/services/core/java/com/android/server/pm/UserManagerService.java index f5c5867edb53..ca0c1f98fe0d 100644 --- a/services/core/java/com/android/server/pm/UserManagerService.java +++ b/services/core/java/com/android/server/pm/UserManagerService.java @@ -23,6 +23,7 @@ import static android.os.UserManager.DISALLOW_USER_SWITCH; import static android.os.UserManager.SYSTEM_USER_MODE_EMULATION_PROPERTY; import static android.os.UserManager.USER_OPERATION_ERROR_UNKNOWN; +import static com.android.internal.util.ConcurrentUtils.DIRECT_EXECUTOR; import static com.android.server.pm.UserJourneyLogger.ERROR_CODE_ABORTED; import static com.android.server.pm.UserJourneyLogger.ERROR_CODE_UNSPECIFIED; import static com.android.server.pm.UserJourneyLogger.ERROR_CODE_USER_ALREADY_AN_ADMIN; @@ -5234,12 +5235,12 @@ public class UserManagerService extends IUserManager.Stub { statsManager.setPullAtomCallback( FrameworkStatsLog.USER_INFO, null, // use default PullAtomMetadata values - BackgroundThread.getExecutor(), + DIRECT_EXECUTOR, this::onPullAtom); statsManager.setPullAtomCallback( FrameworkStatsLog.MULTI_USER_INFO, null, // use default PullAtomMetadata values - BackgroundThread.getExecutor(), + DIRECT_EXECUTOR, this::onPullAtom); } diff --git a/services/core/java/com/android/server/policy/PhoneWindowManager.java b/services/core/java/com/android/server/policy/PhoneWindowManager.java index ca647929c271..d1a4e6008c2a 100644 --- a/services/core/java/com/android/server/policy/PhoneWindowManager.java +++ b/services/core/java/com/android/server/policy/PhoneWindowManager.java @@ -4559,7 +4559,7 @@ public class PhoneWindowManager implements WindowManagerPolicy { case KeyEvent.KEYCODE_STYLUS_BUTTON_SECONDARY: case KeyEvent.KEYCODE_STYLUS_BUTTON_TERTIARY: case KeyEvent.KEYCODE_STYLUS_BUTTON_TAIL: { - if (down && mStylusButtonsEnabled) { + if (mStylusButtonsEnabled) { sendSystemKeyToStatusBarAsync(event); } result &= ~ACTION_PASS_TO_USER; diff --git a/services/core/java/com/android/server/power/hint/HintManagerService.java b/services/core/java/com/android/server/power/hint/HintManagerService.java index 1a91d252c431..f425ba32ced0 100644 --- a/services/core/java/com/android/server/power/hint/HintManagerService.java +++ b/services/core/java/com/android/server/power/hint/HintManagerService.java @@ -16,6 +16,8 @@ package com.android.server.power.hint; +import static com.android.internal.util.ConcurrentUtils.DIRECT_EXECUTOR; + import android.annotation.NonNull; import android.app.ActivityManager; import android.app.ActivityManagerInternal; @@ -37,7 +39,6 @@ import android.util.StatsEvent; import com.android.internal.annotations.GuardedBy; import com.android.internal.annotations.VisibleForTesting; -import com.android.internal.os.BackgroundThread; import com.android.internal.util.DumpUtils; import com.android.internal.util.FrameworkStatsLog; import com.android.internal.util.Preconditions; @@ -142,7 +143,7 @@ public final class HintManagerService extends SystemService { statsManager.setPullAtomCallback( FrameworkStatsLog.ADPF_SYSTEM_COMPONENT_INFO, null, // use default PullAtomMetadata values - BackgroundThread.getExecutor(), + DIRECT_EXECUTOR, this::onPullAtom); } diff --git a/services/core/java/com/android/server/stats/pull/StatsPullAtomService.java b/services/core/java/com/android/server/stats/pull/StatsPullAtomService.java index 9128974fa9d3..79b2836e237d 100644 --- a/services/core/java/com/android/server/stats/pull/StatsPullAtomService.java +++ b/services/core/java/com/android/server/stats/pull/StatsPullAtomService.java @@ -1599,7 +1599,7 @@ public class StatsPullAtomService extends SystemService { mStatsManager.setPullAtomCallback( tagId, metadata, - BackgroundThread.getExecutor(), + DIRECT_EXECUTOR, mStatsCallbackImpl ); } @@ -1612,7 +1612,7 @@ public class StatsPullAtomService extends SystemService { mStatsManager.setPullAtomCallback( tagId, metadata, - BackgroundThread.getExecutor(), + DIRECT_EXECUTOR, mStatsCallbackImpl ); } @@ -1625,7 +1625,7 @@ public class StatsPullAtomService extends SystemService { mStatsManager.setPullAtomCallback( tagId, metadata, - BackgroundThread.getExecutor(), + DIRECT_EXECUTOR, mStatsCallbackImpl ); } diff --git a/services/core/java/com/android/server/wm/LetterboxUiController.java b/services/core/java/com/android/server/wm/LetterboxUiController.java index 3639e1b9cb47..735cbc4e4287 100644 --- a/services/core/java/com/android/server/wm/LetterboxUiController.java +++ b/services/core/java/com/android/server/wm/LetterboxUiController.java @@ -33,6 +33,7 @@ import static android.content.pm.ActivityInfo.OVERRIDE_RESPECT_REQUESTED_ORIENTA import static android.content.pm.ActivityInfo.OVERRIDE_UNDEFINED_ORIENTATION_TO_NOSENSOR; import static android.content.pm.ActivityInfo.OVERRIDE_UNDEFINED_ORIENTATION_TO_PORTRAIT; import static android.content.pm.ActivityInfo.OVERRIDE_USE_DISPLAY_LANDSCAPE_NATURAL_ORIENTATION; +import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_LOCKED; import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_NOSENSOR; import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_PORTRAIT; import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_REVERSE_LANDSCAPE; @@ -661,6 +662,10 @@ final class LetterboxUiController { @ScreenOrientation int overrideOrientationIfNeeded(@ScreenOrientation int candidate) { if (shouldApplyUserFullscreenOverride()) { + Slog.v(TAG, "Requested orientation " + screenOrientationToString(candidate) + " for " + + mActivityRecord + " is overridden to " + + screenOrientationToString(SCREEN_ORIENTATION_USER) + + " by user aspect ratio settings."); return SCREEN_ORIENTATION_USER; } @@ -668,6 +673,15 @@ final class LetterboxUiController { // orientation. candidate = mActivityRecord.mWmService.mapOrientationRequest(candidate); + if (shouldApplyUserMinAspectRatioOverride() && (!isFixedOrientation(candidate) + || candidate == SCREEN_ORIENTATION_LOCKED)) { + Slog.v(TAG, "Requested orientation " + screenOrientationToString(candidate) + " for " + + mActivityRecord + " is overridden to " + + screenOrientationToString(SCREEN_ORIENTATION_PORTRAIT) + + " by user aspect ratio settings."); + return SCREEN_ORIENTATION_PORTRAIT; + } + if (FALSE.equals(mBooleanPropertyAllowOrientationOverride)) { return candidate; } diff --git a/services/core/java/com/android/server/wm/WindowManagerInternal.java b/services/core/java/com/android/server/wm/WindowManagerInternal.java index 805e7ffe7d76..9f1bccb9a27a 100644 --- a/services/core/java/com/android/server/wm/WindowManagerInternal.java +++ b/services/core/java/com/android/server/wm/WindowManagerInternal.java @@ -954,4 +954,11 @@ public abstract class WindowManagerInternal { /** Returns the SurfaceControl accessibility services should use for accessibility overlays. */ public abstract SurfaceControl getA11yOverlayLayer(int displayId); + + /** + * Device has a software navigation bar (separate from the status bar) on specific display. + * + * @param displayId the id of display to check if there is a software navigation bar. + */ + public abstract boolean hasNavigationBar(int displayId); } diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java index b20be551c114..e6a341fe37e7 100644 --- a/services/core/java/com/android/server/wm/WindowManagerService.java +++ b/services/core/java/com/android/server/wm/WindowManagerService.java @@ -8380,6 +8380,11 @@ public class WindowManagerService extends IWindowManager.Stub } @Override + public boolean hasNavigationBar(int displayId) { + return WindowManagerService.this.hasNavigationBar(displayId); + } + + @Override public void setInputMethodTargetChangeListener(@NonNull ImeTargetChangeListener listener) { synchronized (mGlobalLock) { mImeTargetChangeListener = listener; diff --git a/services/tests/servicestests/src/com/android/server/am/FgsLoggerTest.java b/services/tests/servicestests/src/com/android/server/am/FgsLoggerTest.java index f5005fdf1459..38bb3dea6ee3 100644 --- a/services/tests/servicestests/src/com/android/server/am/FgsLoggerTest.java +++ b/services/tests/servicestests/src/com/android/server/am/FgsLoggerTest.java @@ -152,6 +152,50 @@ public class FgsLoggerTest { } @Test + public void testApiStartStopFgs() throws InterruptedException { + ServiceRecord record = ServiceRecord.newEmptyInstanceForTest(null); + record.foregroundServiceType = ServiceInfo.FOREGROUND_SERVICE_TYPE_CAMERA; + + mFgsLogger.logForegroundServiceApiEventBegin(FOREGROUND_SERVICE_API_TYPE_CAMERA, + 1, 1, "aPackageHasNoName"); + Thread.sleep(2000); + + resetAndVerifyZeroInteractions(); + + mFgsLogger.logForegroundServiceApiEventEnd(FOREGROUND_SERVICE_API_TYPE_CAMERA, 1, 1); + + resetAndVerifyZeroInteractions(); + + mFgsLogger.logForegroundServiceStart(1, 1, record); + + resetAndVerifyZeroInteractions(); + } + + @Test + public void testFgsStartStopApiStartStop() throws InterruptedException { + ServiceRecord record = ServiceRecord.newEmptyInstanceForTest(null); + record.foregroundServiceType = ServiceInfo.FOREGROUND_SERVICE_TYPE_CAMERA; + + mFgsLogger.logForegroundServiceStart(1, 1, record); + + resetAndVerifyZeroInteractions(); + + mFgsLogger.logForegroundServiceStop(1, record); + + resetAndVerifyZeroInteractions(); + + mFgsLogger.logForegroundServiceApiEventBegin(FOREGROUND_SERVICE_API_TYPE_CAMERA, + 1, 1, "aPackageHasNoName"); + Thread.sleep(2000); + + resetAndVerifyZeroInteractions(); + + mFgsLogger.logForegroundServiceApiEventEnd(FOREGROUND_SERVICE_API_TYPE_CAMERA, 1, 1); + + resetAndVerifyZeroInteractions(); + } + + @Test public void testMultipleStartStopApis() throws InterruptedException { ServiceRecord record = ServiceRecord.newEmptyInstanceForTest(null); record.foregroundServiceType = ServiceInfo.FOREGROUND_SERVICE_TYPE_CAMERA; diff --git a/services/tests/servicestests/src/com/android/server/biometrics/AuthenticationStatsCollectorTest.java b/services/tests/servicestests/src/com/android/server/biometrics/AuthenticationStatsCollectorTest.java index fa6e7f60c1b0..ba77390d5e3f 100644 --- a/services/tests/servicestests/src/com/android/server/biometrics/AuthenticationStatsCollectorTest.java +++ b/services/tests/servicestests/src/com/android/server/biometrics/AuthenticationStatsCollectorTest.java @@ -87,6 +87,8 @@ public class AuthenticationStatsCollectorTest { public void setUp() { when(mContext.getResources()).thenReturn(mResources); + when(mResources.getBoolean(eq(R.bool.config_biometricFrrNotificationEnabled))) + .thenReturn(true); when(mResources.getFraction(eq(R.fraction.config_biometricNotificationFrrThreshold), anyInt(), anyInt())).thenReturn(FRR_THRESHOLD); @@ -109,7 +111,6 @@ public class AuthenticationStatsCollectorTest { 0 /* modality */, mBiometricNotification); } - @Test public void authenticate_authenticationSucceeded_mapShouldBeUpdated() { // Assert that the user doesn't exist in the map initially. @@ -341,4 +342,32 @@ public class AuthenticationStatsCollectorTest { // Assert that notification count has been updated. assertThat(authenticationStats.getEnrollmentNotifications()).isEqualTo(1); } + + @Test + public void authenticate_featureDisabled_mapMustNotBeUpdated() { + // Disable the feature. + when(mResources.getBoolean(eq(R.bool.config_biometricFrrNotificationEnabled))) + .thenReturn(false); + AuthenticationStatsCollector authenticationStatsCollector = + new AuthenticationStatsCollector(mContext, 0 /* modality */, + mBiometricNotification); + + authenticationStatsCollector.setAuthenticationStatsForUser(USER_ID_1, + new AuthenticationStats(USER_ID_1, 500 /* totalAttempts */, + 400 /* rejectedAttempts */, 0 /* enrollmentNotifications */, + 0 /* modality */)); + + authenticationStatsCollector.authenticate(USER_ID_1, false /* authenticated */); + + // Assert that no notification should be sent. + verify(mBiometricNotification, never()).sendFaceEnrollNotification(any()); + verify(mBiometricNotification, never()).sendFpEnrollNotification(any()); + // Assert that data hasn't been updated. + AuthenticationStats authenticationStats = authenticationStatsCollector + .getAuthenticationStatsForUser(USER_ID_1); + assertThat(authenticationStats.getTotalAttempts()).isEqualTo(500); + assertThat(authenticationStats.getRejectedAttempts()).isEqualTo(400); + assertThat(authenticationStats.getEnrollmentNotifications()).isEqualTo(0); + assertThat(authenticationStats.getFrr()).isWithin(0f).of(0.8f); + } } diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationRecordLoggerTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationRecordLoggerTest.java index b522cab0801b..5147a08b5216 100644 --- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationRecordLoggerTest.java +++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationRecordLoggerTest.java @@ -28,6 +28,7 @@ import static com.android.server.notification.NotificationRecordLogger.Notificat import static com.android.server.notification.NotificationRecordLogger.NotificationCancelledEvent.NOTIFICATION_CANCEL_USER_OTHER; import static com.android.server.notification.NotificationRecordLogger.NotificationReportedEvent.NOTIFICATION_POSTED; import static com.android.server.notification.NotificationRecordLogger.NotificationReportedEvent.NOTIFICATION_UPDATED; +import static com.google.common.truth.Truth.assertThat; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; @@ -48,6 +49,8 @@ import com.android.internal.util.FrameworkStatsLog; import org.junit.Test; import org.junit.runner.RunWith; +import java.time.Duration; + @SmallTest @RunWith(AndroidJUnit4.class) @@ -230,4 +233,12 @@ public class NotificationRecordLoggerTest extends UiServiceTestCase { NotificationRecordLogger.NotificationCancelledEvent.fromCancelReason( REASON_CANCEL, DISMISSAL_OTHER)); } + + @Test + public void testGetAgeInMinutes() { + long postTimeMs = Duration.ofMinutes(5).toMillis(); + long whenMs = Duration.ofMinutes(2).toMillis(); + int age = NotificationRecordLogger.getAgeInMinutes(postTimeMs, whenMs); + assertThat(age).isEqualTo(3); + } } diff --git a/services/tests/wmtests/src/com/android/server/wm/LetterboxUiControllerTest.java b/services/tests/wmtests/src/com/android/server/wm/LetterboxUiControllerTest.java index 0566f460c655..5f92fd5f12e7 100644 --- a/services/tests/wmtests/src/com/android/server/wm/LetterboxUiControllerTest.java +++ b/services/tests/wmtests/src/com/android/server/wm/LetterboxUiControllerTest.java @@ -32,6 +32,7 @@ import static android.content.pm.ActivityInfo.OVERRIDE_UNDEFINED_ORIENTATION_TO_ import static android.content.pm.ActivityInfo.OVERRIDE_UNDEFINED_ORIENTATION_TO_PORTRAIT; import static android.content.pm.ActivityInfo.OVERRIDE_USE_DISPLAY_LANDSCAPE_NATURAL_ORIENTATION; import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE; +import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_LOCKED; import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_NOSENSOR; import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_PORTRAIT; import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_REVERSE_LANDSCAPE; @@ -811,6 +812,31 @@ public class LetterboxUiControllerTest extends WindowTestsBase { /* candidate */ SCREEN_ORIENTATION_PORTRAIT), SCREEN_ORIENTATION_PORTRAIT); } + @Test + public void testOverrideOrientationIfNeeded_userAspectRatioApplied_unspecifiedOverridden() { + spyOn(mController); + doReturn(true).when(mController).shouldApplyUserMinAspectRatioOverride(); + + assertEquals(mController.overrideOrientationIfNeeded( + /* candidate */ SCREEN_ORIENTATION_UNSPECIFIED), SCREEN_ORIENTATION_PORTRAIT); + + assertEquals(mController.overrideOrientationIfNeeded( + /* candidate */ SCREEN_ORIENTATION_LOCKED), SCREEN_ORIENTATION_PORTRAIT); + + // unchanged if orientation is specified + assertEquals(mController.overrideOrientationIfNeeded( + /* candidate */ SCREEN_ORIENTATION_LANDSCAPE), SCREEN_ORIENTATION_LANDSCAPE); + } + + @Test + public void testOverrideOrientationIfNeeded_userAspectRatioNotApplied_returnsUnchanged() { + spyOn(mController); + doReturn(false).when(mController).shouldApplyUserMinAspectRatioOverride(); + + assertEquals(mController.overrideOrientationIfNeeded( + /* candidate */ SCREEN_ORIENTATION_UNSPECIFIED), SCREEN_ORIENTATION_UNSPECIFIED); + } + // shouldApplyUser...Override @Test public void testShouldApplyUserFullscreenOverride_trueProperty_returnsFalse() throws Exception { |