diff options
35 files changed, 449 insertions, 233 deletions
diff --git a/apex/jobscheduler/service/java/com/android/server/alarm/AlarmManagerService.java b/apex/jobscheduler/service/java/com/android/server/alarm/AlarmManagerService.java index ed80ddbd2cd7..9f529548833d 100644 --- a/apex/jobscheduler/service/java/com/android/server/alarm/AlarmManagerService.java +++ b/apex/jobscheduler/service/java/com/android/server/alarm/AlarmManagerService.java @@ -256,6 +256,7 @@ public class AlarmManagerService extends SystemService { AlarmHandler mHandler; AppWakeupHistory mAppWakeupHistory; AppWakeupHistory mAllowWhileIdleHistory; + AppWakeupHistory mAllowWhileIdleCompatHistory; private final SparseLongArray mLastPriorityAlarmDispatch = new SparseLongArray(); private final SparseArray<RingBuffer<RemovedAlarm>> mRemovalHistory = new SparseArray<>(); ClockReceiver mClockReceiver; @@ -1633,6 +1634,7 @@ public class AlarmManagerService extends SystemService { mAppWakeupHistory = new AppWakeupHistory(Constants.DEFAULT_APP_STANDBY_WINDOW); mAllowWhileIdleHistory = new AppWakeupHistory(INTERVAL_HOUR); + mAllowWhileIdleCompatHistory = new AppWakeupHistory(INTERVAL_HOUR); mNextWakeup = mNextNonWakeup = 0; @@ -2142,20 +2144,23 @@ public class AlarmManagerService extends SystemService { final int userId = UserHandle.getUserId(alarm.creatorUid); final int quota; final long window; + final AppWakeupHistory history; if ((alarm.flags & FLAG_ALLOW_WHILE_IDLE) != 0) { quota = mConstants.ALLOW_WHILE_IDLE_QUOTA; window = mConstants.ALLOW_WHILE_IDLE_WINDOW; + history = mAllowWhileIdleHistory; } else { quota = mConstants.ALLOW_WHILE_IDLE_COMPAT_QUOTA; window = mConstants.ALLOW_WHILE_IDLE_COMPAT_WINDOW; + history = mAllowWhileIdleCompatHistory; } - final int dispatchesInWindow = mAllowWhileIdleHistory.getTotalWakeupsInWindow( + final int dispatchesInHistory = history.getTotalWakeupsInWindow( alarm.sourcePackage, userId); - if (dispatchesInWindow < quota) { + if (dispatchesInHistory < quota) { // fine to go out immediately. batterySaverPolicyElapsed = nowElapsed; } else { - batterySaverPolicyElapsed = mAllowWhileIdleHistory.getNthLastWakeupForPackage( + batterySaverPolicyElapsed = history.getNthLastWakeupForPackage( alarm.sourcePackage, userId, quota) + window; } } else if ((alarm.flags & FLAG_PRIORITIZE) != 0) { @@ -2201,20 +2206,23 @@ public class AlarmManagerService extends SystemService { final int userId = UserHandle.getUserId(alarm.creatorUid); final int quota; final long window; + final AppWakeupHistory history; if ((alarm.flags & FLAG_ALLOW_WHILE_IDLE) != 0) { quota = mConstants.ALLOW_WHILE_IDLE_QUOTA; window = mConstants.ALLOW_WHILE_IDLE_WINDOW; + history = mAllowWhileIdleHistory; } else { quota = mConstants.ALLOW_WHILE_IDLE_COMPAT_QUOTA; window = mConstants.ALLOW_WHILE_IDLE_COMPAT_WINDOW; + history = mAllowWhileIdleCompatHistory; } - final int dispatchesInWindow = mAllowWhileIdleHistory.getTotalWakeupsInWindow( + final int dispatchesInHistory = history.getTotalWakeupsInWindow( alarm.sourcePackage, userId); - if (dispatchesInWindow < quota) { + if (dispatchesInHistory < quota) { // fine to go out immediately. deviceIdlePolicyTime = nowElapsed; } else { - final long whenInQuota = mAllowWhileIdleHistory.getNthLastWakeupForPackage( + final long whenInQuota = history.getNthLastWakeupForPackage( alarm.sourcePackage, userId, quota) + window; deviceIdlePolicyTime = Math.min(whenInQuota, mPendingIdleUntil.getWhenElapsed()); } @@ -2502,6 +2510,7 @@ public class AlarmManagerService extends SystemService { Binder.getCallingPid(), callingUid, "AlarmManager.setPrioritized"); // The API doesn't allow using both together. flags &= ~FLAG_ALLOW_WHILE_IDLE; + // Prioritized alarms don't need any extra permission to be exact. } else if (exact || allowWhileIdle) { final boolean needsPermission; boolean lowerQuota; @@ -2992,6 +3001,10 @@ public class AlarmManagerService extends SystemService { mAllowWhileIdleHistory.dump(pw, nowELAPSED); pw.println(); + pw.println("Allow while idle compat history:"); + mAllowWhileIdleCompatHistory.dump(pw, nowELAPSED); + pw.println(); + if (mLastPriorityAlarmDispatch.size() > 0) { pw.println("Last priority alarm dispatches:"); pw.increaseIndent(); @@ -4553,6 +4566,7 @@ public class AlarmManagerService extends SystemService { removeUserLocked(userHandle); mAppWakeupHistory.removeForUser(userHandle); mAllowWhileIdleHistory.removeForUser(userHandle); + mAllowWhileIdleCompatHistory.removeForUser(userHandle); } return; case Intent.ACTION_UID_REMOVED: @@ -4588,6 +4602,8 @@ public class AlarmManagerService extends SystemService { // package-removed and package-restarted case mAppWakeupHistory.removeForPackage(pkg, UserHandle.getUserId(uid)); mAllowWhileIdleHistory.removeForPackage(pkg, UserHandle.getUserId(uid)); + mAllowWhileIdleCompatHistory.removeForPackage(pkg, + UserHandle.getUserId(uid)); removeLocked(uid, REMOVE_REASON_UNDEFINED); } else { // external-applications-unavailable case @@ -4965,7 +4981,10 @@ public class AlarmManagerService extends SystemService { if (isAllowedWhileIdleRestricted(alarm)) { // Record the last time this uid handled an ALLOW_WHILE_IDLE alarm while the // device was in doze or battery saver. - mAllowWhileIdleHistory.recordAlarmForPackage(alarm.sourcePackage, + final AppWakeupHistory history = ((alarm.flags & FLAG_ALLOW_WHILE_IDLE) != 0) + ? mAllowWhileIdleHistory + : mAllowWhileIdleCompatHistory; + history.recordAlarmForPackage(alarm.sourcePackage, UserHandle.getUserId(alarm.creatorUid), nowELAPSED); mAlarmStore.updateAlarmDeliveries(a -> { if (a.creatorUid != alarm.creatorUid || !isAllowedWhileIdleRestricted(a)) { diff --git a/core/java/android/hardware/location/ContextHubClientCallback.java b/core/java/android/hardware/location/ContextHubClientCallback.java index 35d00f03de67..9309c5b0d8e3 100644 --- a/core/java/android/hardware/location/ContextHubClientCallback.java +++ b/core/java/android/hardware/location/ContextHubClientCallback.java @@ -68,8 +68,11 @@ public class ContextHubClientCallback { /** * Callback invoked when a nanoapp is dynamically loaded at the attached Context Hub through - * the {@link android.hardware.location.ContextHubManager}. This callback is not invoked for a - * nanoapp that is loaded internally by CHRE (e.g. nanoapps that are preloaded by the system). + * the {@link android.hardware.location.ContextHubManager}. + * + * NOTE: This callback is <b>not</b> invoked for a nanoapp that is loaded internally by CHRE + * (e.g. nanoapps that are preloaded by the system). To check the availability of these + * nanoapps, use the {@link ContextHubManager#queryNanoApps(ContextHubInfo)} API. * * @param client the client that is associated with this callback * @param nanoAppId the ID of the nanoapp that had been loaded diff --git a/core/java/android/hardware/location/ContextHubManager.java b/core/java/android/hardware/location/ContextHubManager.java index f69a7d7e5f16..9af0e09ee97a 100644 --- a/core/java/android/hardware/location/ContextHubManager.java +++ b/core/java/android/hardware/location/ContextHubManager.java @@ -128,7 +128,8 @@ public final class ContextHubManager { public static final int AUTHORIZATION_GRANTED = 2; /** - * Constants describing the type of events from a Context Hub. + * Constants describing the type of events from a Context Hub, as defined in + * {@link ContextHubClientCallback}. * {@hide} */ @Retention(RetentionPolicy.SOURCE) diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java index 280685065aaf..3550a31f9038 100644 --- a/core/java/android/view/ViewRootImpl.java +++ b/core/java/android/view/ViewRootImpl.java @@ -1955,22 +1955,23 @@ public final class ViewRootImpl implements ViewParent, return mBoundsLayer; } - Surface getOrCreateBLASTSurface(int width, int height, - @Nullable WindowManager.LayoutParams params) { + Surface getOrCreateBLASTSurface() { if (!mSurfaceControl.isValid()) { return null; } - int format = params == null ? PixelFormat.TRANSLUCENT : params.format; Surface ret = null; if (mBlastBufferQueue == null) { - mBlastBufferQueue = new BLASTBufferQueue(mTag, mSurfaceControl, width, height, - format); + mBlastBufferQueue = new BLASTBufferQueue(mTag, mSurfaceControl, + mSurfaceSize.x, mSurfaceSize.y, + mWindowAttributes.format); // We only return the Surface the first time, as otherwise // it hasn't changed and there is no need to update. ret = mBlastBufferQueue.createSurface(); } else { - mBlastBufferQueue.update(mSurfaceControl, width, height, format); + mBlastBufferQueue.update(mSurfaceControl, + mSurfaceSize.x, mSurfaceSize.y, + mWindowAttributes.format); } return ret; @@ -7784,8 +7785,7 @@ public final class ViewRootImpl implements ViewParent, if (!useBLAST()) { mSurface.copyFrom(mSurfaceControl); } else { - final Surface blastSurface = getOrCreateBLASTSurface(mSurfaceSize.x, mSurfaceSize.y, - params); + final Surface blastSurface = getOrCreateBLASTSurface(); // If blastSurface == null that means it hasn't changed since the last time we // called. In this situation, avoid calling transferFrom as we would then // inc the generation ID and cause EGL resources to be recreated. diff --git a/core/java/android/widget/EdgeEffect.java b/core/java/android/widget/EdgeEffect.java index 472e3e72ab2f..c110ab956030 100644 --- a/core/java/android/widget/EdgeEffect.java +++ b/core/java/android/widget/EdgeEffect.java @@ -365,6 +365,10 @@ public class EdgeEffect { mDuration = PULL_TIME; mPullDistance += deltaDistance; + if (edgeEffectBehavior == TYPE_STRETCH) { + // Don't allow stretch beyond 1 + mPullDistance = Math.min(1f, mPullDistance); + } mDistance = Math.max(0f, mPullDistance); mVelocity = 0; @@ -783,6 +787,10 @@ public class EdgeEffect { + mDampedFreq * sinCoeff * Math.cos(mDampedFreq * deltaT)); mDistance = (float) distance / mHeight; mVelocity = (float) velocity; + if (mDistance > 1f) { + mDistance = 1f; + mVelocity = 0f; + } if (isAtEquilibrium()) { mDistance = 0; mVelocity = 0; diff --git a/core/res/res/values-is/strings.xml b/core/res/res/values-is/strings.xml index 667dd9c46055..93e25a6b014e 100644 --- a/core/res/res/values-is/strings.xml +++ b/core/res/res/values-is/strings.xml @@ -2287,10 +2287,10 @@ <string name="window_magnification_prompt_content" msgid="8159173903032344891">"Nú geturðu stækkað hluta skjásins"</string> <string name="turn_on_magnification_settings_action" msgid="8521433346684847700">"Kveikja á í stillingum"</string> <string name="dismiss_action" msgid="1728820550388704784">"Hunsa"</string> - <string name="sensor_privacy_start_use_mic_notification_content_title" msgid="2420858361276370367">"Opna á hljóðnema tækisins"</string> + <string name="sensor_privacy_start_use_mic_notification_content_title" msgid="2420858361276370367">"Opna fyrir hljóðnema tækisins"</string> <string name="sensor_privacy_start_use_camera_notification_content_title" msgid="7287720213963466672">"Opna fyrir myndavél tækisins"</string> <string name="sensor_privacy_start_use_notification_content_text" msgid="7595608891015777346">"Fyrir <b><xliff:g id="APP">%s</xliff:g></b> og öll forrit og þjónustur"</string> - <string name="sensor_privacy_start_use_dialog_turn_on_button" msgid="7089318886628390827">"Opna á"</string> + <string name="sensor_privacy_start_use_dialog_turn_on_button" msgid="7089318886628390827">"Opna fyrir"</string> <string name="sensor_privacy_notification_channel_label" msgid="936036783155261349">"Persónuvernd skynjara"</string> <string name="splash_screen_view_icon_description" msgid="180638751260598187">"Forritstákn"</string> <string name="splash_screen_view_branding_description" msgid="7911129347402728216">"Mynd af merki forrits"</string> diff --git a/core/res/res/values-zh-rCN/strings.xml b/core/res/res/values-zh-rCN/strings.xml index 51395222e24c..68a5ce7ee349 100644 --- a/core/res/res/values-zh-rCN/strings.xml +++ b/core/res/res/values-zh-rCN/strings.xml @@ -142,7 +142,7 @@ <string name="wfcSpnFormat_wifi" msgid="1376356951297043426">"WLAN"</string> <string name="wfcSpnFormat_wifi_calling_wo_hyphen" msgid="7178561009225028264">"WLAN 通话"</string> <string name="wfcSpnFormat_vowifi" msgid="8371335230890725606">"VoWifi"</string> - <string name="wifi_calling_off_summary" msgid="5626710010766902560">"关闭"</string> + <string name="wifi_calling_off_summary" msgid="5626710010766902560">"已关闭"</string> <string name="wfc_mode_wifi_preferred_summary" msgid="1035175836270943089">"通过 WLAN 进行通话"</string> <string name="wfc_mode_cellular_preferred_summary" msgid="4958965609212575619">"通过移动网络进行通话"</string> <string name="wfc_mode_wifi_only_summary" msgid="104951993894678665">"仅限 WLAN"</string> @@ -1696,8 +1696,8 @@ <string name="accessibility_shortcut_single_service_warning" msgid="6363127705112844257">"同时按住两个音量键几秒钟,即可开启<xliff:g id="SERVICE">%1$s</xliff:g>无障碍功能。这样做可能会改变您设备的工作方式。\n\n您可以在“设置”>“无障碍”中将此快捷方式更改为开启另一项功能。"</string> <string name="accessibility_shortcut_on" msgid="5463618449556111344">"开启"</string> <string name="accessibility_shortcut_off" msgid="3651336255403648739">"不开启"</string> - <string name="accessibility_shortcut_menu_item_status_on" msgid="6608392117189732543">"开启"</string> - <string name="accessibility_shortcut_menu_item_status_off" msgid="5531598275559472393">"关闭"</string> + <string name="accessibility_shortcut_menu_item_status_on" msgid="6608392117189732543">"已开启"</string> + <string name="accessibility_shortcut_menu_item_status_off" msgid="5531598275559472393">"已关闭"</string> <string name="accessibility_enable_service_title" msgid="3931558336268541484">"要允许<xliff:g id="SERVICE">%1$s</xliff:g>完全控制您的设备吗?"</string> <string name="accessibility_enable_service_encryption_warning" msgid="8603532708618236909">"如果您开启<xliff:g id="SERVICE">%1$s</xliff:g>,您的设备将无法使用屏幕锁定功能来增强数据加密效果。"</string> <string name="accessibility_service_warning_description" msgid="291674995220940133">"对于能满足您的无障碍功能需求的应用,可授予其完全控制权限;但对大部分应用来说,都不适合授予此权限。"</string> diff --git a/libs/hwui/Readback.cpp b/libs/hwui/Readback.cpp index d8735ce57b65..a743d30939d0 100644 --- a/libs/hwui/Readback.cpp +++ b/libs/hwui/Readback.cpp @@ -183,8 +183,10 @@ CopyResult Readback::copySurfaceInto(ANativeWindow* window, const Rect& inSrcRec SkPaint paint; paint.setAlpha(255); paint.setBlendMode(SkBlendMode::kSrc); - canvas->drawImageRect(image, imageSrcRect, imageDstRect, sampling, &paint, - SkCanvas::kFast_SrcRectConstraint); + const bool hasBufferCrop = cropRect.left < cropRect.right && cropRect.top < cropRect.bottom; + auto constraint = + hasBufferCrop ? SkCanvas::kStrict_SrcRectConstraint : SkCanvas::kFast_SrcRectConstraint; + canvas->drawImageRect(image, imageSrcRect, imageDstRect, sampling, &paint, constraint); canvas->restore(); if (!tmpSurface->readPixels(*bitmap, 0, 0)) { diff --git a/packages/CompanionDeviceManager/res/values-fr-rCA/strings.xml b/packages/CompanionDeviceManager/res/values-fr-rCA/strings.xml index 2cf872cc7c71..1dcd3375c1cf 100644 --- a/packages/CompanionDeviceManager/res/values-fr-rCA/strings.xml +++ b/packages/CompanionDeviceManager/res/values-fr-rCA/strings.xml @@ -20,7 +20,7 @@ <string name="chooser_title" msgid="2262294130493605839">"Choisissez un <xliff:g id="PROFILE_NAME">%1$s</xliff:g> qui sera géré par <strong><xliff:g id="APP_NAME">%2$s</xliff:g></strong>"</string> <string name="profile_name_generic" msgid="6851028682723034988">"appareil"</string> <string name="profile_name_watch" msgid="576290739483672360">"montre"</string> - <string name="confirmation_title" msgid="8455544820286920304">"Autoriser <strong><xliff:g id="APP_NAME">%1$s</xliff:g></strong> pour gérer votre <strong><xliff:g id="DEVICE_NAME">%2$s</xliff:g></strong>"</string> + <string name="confirmation_title" msgid="8455544820286920304">"Autoriser <strong><xliff:g id="APP_NAME">%1$s</xliff:g></strong> à gérer votre <strong><xliff:g id="DEVICE_NAME">%2$s</xliff:g></strong>"</string> <string name="profile_summary" msgid="2059360676631420073">"Cette application est nécessaire pour gérer votre <xliff:g id="PROFILE_NAME">%1$s</xliff:g>. <xliff:g id="PRIVILEGES_DISCPLAIMER">%2$s</xliff:g>"</string> <string name="consent_yes" msgid="8344487259618762872">"Autoriser"</string> <string name="consent_no" msgid="2640796915611404382">"Ne pas autoriser"</string> diff --git a/packages/SystemUI/plugin/src/com/android/systemui/plugins/qs/DetailAdapter.java b/packages/SystemUI/plugin/src/com/android/systemui/plugins/qs/DetailAdapter.java index 95c2d2efcd89..6d1408d5d212 100644 --- a/packages/SystemUI/plugin/src/com/android/systemui/plugins/qs/DetailAdapter.java +++ b/packages/SystemUI/plugin/src/com/android/systemui/plugins/qs/DetailAdapter.java @@ -72,7 +72,8 @@ public interface DetailAdapter { } /** - * @return if detail panel should animate when shown or closed + * Indicates whether the detail view wants to animate when shown. This has no affect over the + * closing animation. Detail panels will always animate when closed. */ default boolean shouldAnimate() { return true; diff --git a/packages/SystemUI/res/layout/auth_biometric_contents.xml b/packages/SystemUI/res/layout/auth_biometric_contents.xml index 99e8116cb681..3c9e44e2dba9 100644 --- a/packages/SystemUI/res/layout/auth_biometric_contents.xml +++ b/packages/SystemUI/res/layout/auth_biometric_contents.xml @@ -52,6 +52,7 @@ android:layout_width="@dimen/biometric_dialog_biometric_icon_size" android:layout_height="@dimen/biometric_dialog_biometric_icon_size" android:layout_gravity="center" + android:contentDescription="@null" android:scaleType="fitXY" /> </FrameLayout> diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java index bd000b2effa3..e6e2ac980889 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java @@ -285,7 +285,7 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab @VisibleForTesting protected boolean mTelephonyCapable; - private final boolean mAcquiredHapticEnabled; + private final boolean mAcquiredHapticEnabled = false; @Nullable private final Vibrator mVibrator; // Device provisioning state @@ -1413,11 +1413,7 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab @VisibleForTesting public void playAcquiredHaptic() { if (mAcquiredHapticEnabled && mVibrator != null) { - String effect = Settings.Global.getString( - mContext.getContentResolver(), - "udfps_acquired_type"); - mVibrator.vibrate(UdfpsController.getVibration(effect, - UdfpsController.EFFECT_TICK), + mVibrator.vibrate(UdfpsController.EFFECT_CLICK, UdfpsController.VIBRATION_SONIFICATION_ATTRIBUTES); } } @@ -1730,8 +1726,6 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab mLockPatternUtils = lockPatternUtils; mAuthController = authController; dumpManager.registerDumpable(getClass().getName(), this); - mAcquiredHapticEnabled = Settings.Global.getInt(mContext.getContentResolver(), - "udfps_acquired", 0) == 1; mVibrator = vibrator; mHandler = new Handler(mainLooper) { diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricFingerprintView.java b/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricFingerprintView.java index 45ee4ad9ae50..ee602bc9cb78 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricFingerprintView.java +++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricFingerprintView.java @@ -23,6 +23,8 @@ import android.graphics.drawable.Drawable; import android.util.AttributeSet; import android.util.Log; +import androidx.annotation.Nullable; + import com.android.systemui.R; public class AuthBiometricFingerprintView extends AuthBiometricView { @@ -94,12 +96,37 @@ public class AuthBiometricFingerprintView extends AuthBiometricView { mIconView.setImageDrawable(icon); + final CharSequence iconContentDescription = getIconContentDescription(newState); + if (iconContentDescription != null) { + mIconView.setContentDescription(iconContentDescription); + } + if (animation != null && shouldAnimateForTransition(lastState, newState)) { animation.forceAnimationOnUI(); animation.start(); } } + @Nullable + private CharSequence getIconContentDescription(int newState) { + switch (newState) { + case STATE_IDLE: + case STATE_AUTHENTICATING_ANIMATING_IN: + case STATE_AUTHENTICATING: + case STATE_PENDING_CONFIRMATION: + case STATE_AUTHENTICATED: + return mContext.getString( + R.string.accessibility_fingerprint_dialog_fingerprint_icon); + + case STATE_ERROR: + case STATE_HELP: + return mContext.getString(R.string.biometric_dialog_try_again); + + default: + return null; + } + } + private boolean shouldAnimateForTransition(int oldState, int newState) { switch (newState) { case STATE_HELP: diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricView.java b/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricView.java index bebf813e1833..60b06378a61a 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricView.java +++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricView.java @@ -636,7 +636,6 @@ public abstract class AuthBiometricView extends LinearLayout { mIndicatorView.setText(message); mIndicatorView.setTextColor(mTextColorError); mIndicatorView.setVisibility(View.VISIBLE); - mIndicatorView.setSelected(true); mHandler.postDelayed(resetMessageRunnable, mInjector.getDelayAfterError()); Utils.notifyAccessibilityContentChanged(mAccessibilityManager, this); diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java index e51baed065ef..ab3e042e9da7 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java +++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java @@ -17,7 +17,6 @@ package com.android.systemui.biometrics; import static android.hardware.fingerprint.IUdfpsOverlayController.REASON_AUTH_FPM_KEYGUARD; -import static android.os.VibrationEffect.Composition.PRIMITIVE_LOW_TICK; import static com.android.internal.util.Preconditions.checkArgument; import static com.android.internal.util.Preconditions.checkNotNull; @@ -27,7 +26,6 @@ import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.SuppressLint; import android.content.BroadcastReceiver; -import android.content.ContentResolver; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; @@ -47,8 +45,6 @@ import android.os.SystemClock; import android.os.Trace; import android.os.VibrationEffect; import android.os.Vibrator; -import android.provider.Settings; -import android.text.TextUtils; import android.util.Log; import android.view.Gravity; import android.view.LayoutInflater; @@ -160,16 +156,8 @@ public class UdfpsController implements DozeReceiver { .setUsage(AudioAttributes.USAGE_ASSISTANCE_SONIFICATION) .build(); - public static final VibrationEffect EFFECT_TICK = - VibrationEffect.get(VibrationEffect.EFFECT_TICK); - private static final VibrationEffect EFFECT_TEXTURE_TICK = - VibrationEffect.get(VibrationEffect.EFFECT_TEXTURE_TICK); - @VisibleForTesting - static final VibrationEffect EFFECT_CLICK = VibrationEffect.get(VibrationEffect.EFFECT_CLICK); - private static final VibrationEffect EFFECT_HEAVY = - VibrationEffect.get(VibrationEffect.EFFECT_HEAVY_CLICK); - private static final VibrationEffect EFFECT_DOUBLE_CLICK = - VibrationEffect.get(VibrationEffect.EFFECT_DOUBLE_CLICK); + public static final VibrationEffect EFFECT_CLICK = + VibrationEffect.get(VibrationEffect.EFFECT_CLICK); private final ScreenLifecycle.Observer mScreenObserver = new ScreenLifecycle.Observer() { @Override @@ -437,7 +425,6 @@ public class UdfpsController implements DozeReceiver { mTouchLogTime = SystemClock.elapsedRealtime(); mPowerManager.userActivity(SystemClock.uptimeMillis(), PowerManager.USER_ACTIVITY_EVENT_TOUCH, 0); - playStartHaptic(); handled = true; } else if (sinceLastLog >= MIN_TOUCH_LOG_INTERVAL) { Log.v(TAG, "onTouch | finger move: " + touchInfo); @@ -552,18 +539,7 @@ public class UdfpsController implements DozeReceiver { @VisibleForTesting public void playStartHaptic() { if (mVibrator != null) { - final ContentResolver contentResolver = - mContext.getContentResolver(); - // TODO: these settings checks should eventually be removed after ux testing - // (b/185124905) - int startEnabled = Settings.Global.getInt(contentResolver, - "udfps_start", 1); - if (startEnabled > 0) { - String startEffectSetting = Settings.Global.getString( - contentResolver, "udfps_start_type"); - mVibrator.vibrate(getVibration(startEffectSetting, - EFFECT_CLICK), VIBRATION_SONIFICATION_ATTRIBUTES); - } + mVibrator.vibrate(EFFECT_CLICK, VIBRATION_SONIFICATION_ATTRIBUTES); } } @@ -698,7 +674,8 @@ public class UdfpsController implements DozeReceiver { // This view overlaps the sensor area, so prevent it from being selectable // during a11y. if (reason == IUdfpsOverlayController.REASON_ENROLL_FIND_SENSOR - || reason == IUdfpsOverlayController.REASON_ENROLL_ENROLLING) { + || reason == IUdfpsOverlayController.REASON_ENROLL_ENROLLING + || reason == IUdfpsOverlayController.REASON_AUTH_BP) { mView.setImportantForAccessibility(View.IMPORTANT_FOR_ACCESSIBILITY_NO); } @@ -853,6 +830,9 @@ public class UdfpsController implements DozeReceiver { Log.w(TAG, "Null view in onFingerDown"); return; } + if (!mOnFingerDown) { + playStartHaptic(); + } mOnFingerDown = true; mFingerprintManager.onPointerDown(mSensorProps.sensorId, x, y, minor, major); Trace.endAsyncSection("UdfpsController.e2e.onPointerDown", 0); @@ -880,38 +860,6 @@ public class UdfpsController implements DozeReceiver { } } - /** - * get vibration to play given string - * used for testing purposes (b/185124905) - */ - public static VibrationEffect getVibration(String effect, VibrationEffect defaultEffect) { - if (TextUtils.isEmpty(effect)) { - return defaultEffect; - } - - switch (effect.toLowerCase()) { - case "click": - return EFFECT_CLICK; - case "heavy": - return EFFECT_HEAVY; - case "texture_tick": - return EFFECT_TEXTURE_TICK; - case "tick": - return EFFECT_TICK; - case "double_tap": - return EFFECT_DOUBLE_CLICK; - default: - try { - int primitive = Integer.parseInt(effect); - if (primitive <= PRIMITIVE_LOW_TICK && primitive > -1) { - return VibrationEffect.startComposition().addPrimitive(primitive).compose(); - } - } catch (NumberFormatException e) { - } - return defaultEffect; - } - } - private void updateTouchListener() { if (mView == null) { return; diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java index c33f4fa8dee4..c7c25903923a 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java +++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java @@ -2311,7 +2311,6 @@ public class KeyguardViewMediator extends SystemUI implements Dumpable, // only play "unlock" noises if not on a call (since the incall UI // disables the keyguard) if (TelephonyManager.EXTRA_STATE_IDLE.equals(mPhoneState)) { - Log.i("TEST", "playSounds: false"); playSounds(false); } diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSDetail.java b/packages/SystemUI/src/com/android/systemui/qs/QSDetail.java index 1d6c7c94dcc3..929927e5d4e4 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/QSDetail.java +++ b/packages/SystemUI/src/com/android/systemui/qs/QSDetail.java @@ -212,6 +212,11 @@ public class QSDetail extends LinearLayout { Dependency.get(CommandQueue.class).animateCollapsePanels(); mTriggeredExpand = false; } + // Always animate on close, even if the last opened detail adapter had shouldAnimate() + // return false. This is necessary to avoid a race condition which could leave the + // keyguard in a bad state where QS remains visible underneath the notifications, clock, + // and status area. + mShouldAnimate = true; } boolean visibleDiff = wasShowingDetail != showingDetail; @@ -245,10 +250,15 @@ public class QSDetail extends LinearLayout { mClosingDetail = true; mDetailAdapter = null; listener = mTeardownDetailWhenDone; - mHeader.setVisibility(View.VISIBLE); - mFooter.setVisibility(View.VISIBLE); - mQsPanelController.setGridContentVisibility(true); - mQsPanelCallback.onScanStateChanged(false); + // Only update visibility if already expanded. Otherwise, a race condition can cause the + // keyguard to enter a bad state where the QS tiles are displayed underneath the + // notifications, clock, and status area. + if (mQsPanelController.isExpanded()) { + mHeader.setVisibility(View.VISIBLE); + mFooter.setVisibility(View.VISIBLE); + mQsPanelController.setGridContentVisibility(true); + mQsPanelCallback.onScanStateChanged(false); + } } sendAccessibilityEvent(AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED); animateDetailVisibleDiff(x, y, visibleDiff, listener); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithm.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithm.java index e65038b32bf0..f460a132d65c 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithm.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithm.java @@ -148,8 +148,22 @@ public class StackScrollAlgorithm { AmbientState ambientState) { NotificationShelf shelf = ambientState.getShelf(); - if (shelf != null) { - shelf.updateState(algorithmState, ambientState); + if (shelf == null) { + return; + } + + shelf.updateState(algorithmState, ambientState); + + // After the shelf has updated its yTranslation, + // explicitly hide views below the shelf to skip rendering them in the hardware layer. + final float shelfTop = shelf.getViewState().yTranslation; + + for (ExpandableView view : algorithmState.visibleChildren) { + final float viewTop = view.getViewState().yTranslation; + + if (viewTop >= shelfTop) { + view.getViewState().hidden = true; + } } } @@ -411,8 +425,7 @@ public class StackScrollAlgorithm { } else { if (view != ambientState.getTrackedHeadsUpRow()) { if (ambientState.isExpansionChanging()) { - // Show all views. Views below the shelf will later be clipped (essentially - // hidden) in NotificationShelf. + // We later update shelf state, then hide views below the shelf. viewState.hidden = false; viewState.inShelf = algorithmState.firstViewInShelf != null && i >= algorithmState.visibleChildren.indexOf( diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBottomAreaView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBottomAreaView.java index 16f8319928bb..91d503bac4fe 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBottomAreaView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBottomAreaView.java @@ -74,6 +74,7 @@ import com.android.internal.annotations.VisibleForTesting; import com.android.internal.widget.LockPatternUtils; import com.android.keyguard.KeyguardUpdateMonitor; import com.android.keyguard.KeyguardUpdateMonitorCallback; +import com.android.settingslib.Utils; import com.android.systemui.ActivityIntentHelper; import com.android.systemui.Dependency; import com.android.systemui.R; @@ -459,6 +460,10 @@ public class KeyguardBottomAreaView extends FrameLayout implements View.OnClickL if (tileIcon != null) { mWalletButton.setImageDrawable(tileIcon); } + mWalletButton.getDrawable().setTint( + Utils.getColorAttr( + mContext, + com.android.internal.R.attr.textColorPrimary).getDefaultColor()); mWalletButton.setVisibility(VISIBLE); mWalletButton.setOnClickListener(this::onWalletClick); mIndicationArea.setPadding(mIndicationPadding, 0, mIndicationPadding, 0); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBouncer.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBouncer.java index d6ea4a828395..8c0dfc5f7ab4 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBouncer.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBouncer.java @@ -87,7 +87,7 @@ public class KeyguardBouncer { private final Runnable mResetRunnable = ()-> { if (mKeyguardViewController != null) { mKeyguardViewController.resetSecurityContainer(); - for (KeyguardResetCallback callback : mResetCallbacks) { + for (KeyguardResetCallback callback : new ArrayList<>(mResetCallbacks)) { callback.onKeyguardReset(); } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java index 13aa15e44279..3c1892d4b7ea 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java @@ -1575,7 +1575,10 @@ public class NotificationPanelViewController extends PanelViewController { public void expandWithQsDetail(DetailAdapter qsDetailAdapter) { traceQsJank(true /* startTracing */, false /* wasCancelled */); flingSettings(0 /* velocity */, FLING_EXPAND); - mQSDetailDisplayer.showDetailAdapter(qsDetailAdapter, 0, 0); + // When expanding with a panel, there's no meaningful touch point to correspond to. Set the + // origin to somewhere above the screen. This is used for animations. + int x = mQsFrame.getWidth() / 2; + mQSDetailDisplayer.showDetailAdapter(qsDetailAdapter, x, -getHeight()); if (mAccessibilityManager.isEnabled()) { mView.setAccessibilityPaneTitle(determineAccessibilityPaneTitle()); } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationShadeWindowControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationShadeWindowControllerImpl.java index 022faf78b946..5e105bb64350 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationShadeWindowControllerImpl.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationShadeWindowControllerImpl.java @@ -55,16 +55,18 @@ import com.android.systemui.statusbar.policy.ConfigurationController; import com.android.systemui.statusbar.policy.ConfigurationController.ConfigurationListener; import com.android.systemui.statusbar.policy.KeyguardStateController; -import com.google.android.collect.Lists; - import java.io.FileDescriptor; import java.io.PrintWriter; +import java.lang.ref.Reference; import java.lang.ref.WeakReference; import java.lang.reflect.Field; import java.util.ArrayList; import java.util.HashSet; +import java.util.List; +import java.util.Objects; import java.util.Set; import java.util.function.Consumer; +import java.util.stream.Collectors; import javax.inject.Inject; @@ -100,7 +102,7 @@ public class NotificationShadeWindowControllerImpl implements NotificationShadeW private ForcePluginOpenListener mForcePluginOpenListener; private Consumer<Integer> mScrimsVisibilityListener; private final ArrayList<WeakReference<StatusBarWindowCallback>> - mCallbacks = Lists.newArrayList(); + mCallbacks = new ArrayList<>(); private final SysuiColorExtractor mColorExtractor; private final UnlockedScreenOffAnimationController mUnlockedScreenOffAnimationController; @@ -464,13 +466,15 @@ public class NotificationShadeWindowControllerImpl implements NotificationShadeW @Override public void notifyStateChangedCallbacks() { - for (int i = 0; i < mCallbacks.size(); i++) { - StatusBarWindowCallback cb = mCallbacks.get(i).get(); - if (cb != null) { - cb.onStateChanged(mCurrentState.mKeyguardShowing, - mCurrentState.mKeyguardOccluded, - mCurrentState.mBouncerShowing); - } + // Copy callbacks to separate ArrayList to avoid concurrent modification + List<StatusBarWindowCallback> activeCallbacks = mCallbacks.stream() + .map(Reference::get) + .filter(Objects::nonNull) + .collect(Collectors.toList()); + for (StatusBarWindowCallback cb : activeCallbacks) { + cb.onStateChanged(mCurrentState.mKeyguardShowing, + mCurrentState.mKeyguardOccluded, + mCurrentState.mBouncerShowing); } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpManager.java index 081fe5a47626..a8097c4d74b0 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpManager.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpManager.java @@ -41,6 +41,7 @@ import com.android.systemui.statusbar.notification.row.NotificationRowContentBin import java.io.FileDescriptor; import java.io.PrintWriter; +import java.util.ArrayList; import java.util.HashSet; /** @@ -157,7 +158,7 @@ public abstract class HeadsUpManager extends AlertingNotificationManager { NotificationPeekEvent.NOTIFICATION_PEEK, entry.getSbn().getUid(), entry.getSbn().getPackageName(), entry.getSbn().getInstanceId()); } - for (OnHeadsUpChangedListener listener : mListeners) { + for (OnHeadsUpChangedListener listener : new ArrayList<>(mListeners)) { if (isPinned) { listener.onHeadsUpPinned(entry); } else { @@ -177,7 +178,7 @@ public abstract class HeadsUpManager extends AlertingNotificationManager { entry.setHeadsUp(true); setEntryPinned((HeadsUpEntry) alertEntry, shouldHeadsUpBecomePinned(entry)); EventLogTags.writeSysuiHeadsUpStatus(entry.getKey(), 1 /* visible */); - for (OnHeadsUpChangedListener listener : mListeners) { + for (OnHeadsUpChangedListener listener : new ArrayList<>(mListeners)) { listener.onHeadsUpStateChanged(entry, true); } } @@ -188,7 +189,7 @@ public abstract class HeadsUpManager extends AlertingNotificationManager { entry.setHeadsUp(false); setEntryPinned((HeadsUpEntry) alertEntry, false /* isPinned */); EventLogTags.writeSysuiHeadsUpStatus(entry.getKey(), 0 /* visible */); - for (OnHeadsUpChangedListener listener : mListeners) { + for (OnHeadsUpChangedListener listener : new ArrayList<>(mListeners)) { listener.onHeadsUpStateChanged(entry, false); } } @@ -206,7 +207,7 @@ public abstract class HeadsUpManager extends AlertingNotificationManager { if (mHasPinnedNotification) { MetricsLogger.count(mContext, "note_peek", 1); } - for (OnHeadsUpChangedListener listener : mListeners) { + for (OnHeadsUpChangedListener listener : new ArrayList<>(mListeners)) { listener.onHeadsUpPinnedModeChanged(hasPinnedNotification); } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputView.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputView.java index b18dfd2866c4..bbaf65a399a9 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputView.java @@ -807,7 +807,7 @@ public class RemoteInputView extends LinearLayout implements View.OnClickListene } private void onEditTextFocusChanged(RemoteEditText remoteEditText, boolean focused) { - for (View.OnFocusChangeListener listener : mEditTextFocusChangeListeners) { + for (View.OnFocusChangeListener listener : new ArrayList<>(mEditTextFocusChangeListeners)) { listener.onFocusChange(remoteEditText, focused); } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/animation/ActivityLaunchAnimatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/animation/ActivityLaunchAnimatorTest.kt index 33cc7821eba4..694b84a0b949 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/animation/ActivityLaunchAnimatorTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/animation/ActivityLaunchAnimatorTest.kt @@ -10,6 +10,7 @@ import android.graphics.Rect import android.os.Looper import android.testing.AndroidTestingRunner import android.testing.TestableLooper.RunWithLooper +import android.util.Log import android.view.IRemoteAnimationFinishedCallback import android.view.RemoteAnimationAdapter import android.view.RemoteAnimationTarget @@ -27,6 +28,7 @@ import junit.framework.Assert.assertNotNull import junit.framework.Assert.assertNull import junit.framework.Assert.assertTrue import junit.framework.AssertionFailedError +import kotlin.concurrent.thread import org.junit.Before import org.junit.Rule import org.junit.Test @@ -39,7 +41,6 @@ import org.mockito.Mockito.never import org.mockito.Mockito.verify import org.mockito.Spy import org.mockito.junit.MockitoJUnit -import kotlin.concurrent.thread @SmallTest @RunWith(AndroidTestingRunner::class) @@ -50,6 +51,7 @@ class ActivityLaunchAnimatorTest : SysuiTestCase() { @Spy private val controller = TestLaunchAnimatorController(launchContainer) @Mock lateinit var iCallback: IRemoteAnimationFinishedCallback @Mock lateinit var startingSurface: StartingSurface + @Mock lateinit var failHandler: Log.TerribleFailureHandler private lateinit var activityLaunchAnimator: ActivityLaunchAnimator @get:Rule val rule = MockitoJUnit.rule() @@ -179,8 +181,10 @@ class ActivityLaunchAnimatorTest : SysuiTestCase() { } @Test - fun controllerFromOrphanViewReturnsNull() { + fun controllerFromOrphanViewReturnsNullAndIsATerribleFailure() { + Log.setWtfHandler(failHandler) assertNull(ActivityLaunchAnimator.Controller.fromView(View(mContext))) + verify(failHandler).onTerribleFailure(any(), any(), anyBoolean()) } private fun fakeWindow(): RemoteAnimationTarget { diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/QSDetailTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/QSDetailTest.java index c2bd024f0375..84776c7eb18f 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/QSDetailTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/QSDetailTest.java @@ -148,8 +148,10 @@ public class QSDetailTest extends SysuiTestCase { eq(true) /* in */, any()); clearInvocations(mQsDetail.mClipper); + // Detail adapters should always animate on close. shouldAnimate() should only affect the + // open transition mQsDetail.handleShowingDetail(null, 0, 0, false); - verify(mQsDetail.mClipper).updateCircularClip(eq(false) /* animate */, anyInt(), anyInt(), + verify(mQsDetail.mClipper).updateCircularClip(eq(true) /* animate */, anyInt(), anyInt(), eq(false) /* in */, any()); } diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java index 4ec5559a061d..59ebbf1f0c4e 100644 --- a/services/core/java/com/android/server/am/ActivityManagerService.java +++ b/services/core/java/com/android/server/am/ActivityManagerService.java @@ -12425,6 +12425,15 @@ public class ActivityManagerService extends IActivityManager.Stub return sticky; } + // SafetyNet logging for b/177931370. If any process other than system_server tries to + // listen to this broadcast action, then log it. + if (callingPid != Process.myPid()) { + if (filter.hasAction("com.android.server.net.action.SNOOZE_WARNING") + || filter.hasAction("com.android.server.net.action.SNOOZE_RAPID")) { + EventLog.writeEvent(0x534e4554, "177931370", callingUid, ""); + } + } + synchronized (this) { IApplicationThread thread; if (callerApp != null && ((thread = callerApp.getThread()) == null diff --git a/services/core/java/com/android/server/biometrics/sensors/AcquisitionClient.java b/services/core/java/com/android/server/biometrics/sensors/AcquisitionClient.java index ca357b4c2cec..f11fe8aee64f 100644 --- a/services/core/java/com/android/server/biometrics/sensors/AcquisitionClient.java +++ b/services/core/java/com/android/server/biometrics/sensors/AcquisitionClient.java @@ -188,18 +188,7 @@ public abstract class AcquisitionClient<T> extends HalClientMonitor<T> implement mPowerManager.userActivity(now, PowerManager.USER_ACTIVITY_EVENT_TOUCH, 0); } - protected boolean successHapticsEnabled() { - return true; - } - - protected boolean errorHapticsEnabled() { - return true; - } - protected final void vibrateSuccess() { - if (!successHapticsEnabled()) { - return; - } Vibrator vibrator = getContext().getSystemService(Vibrator.class); if (vibrator != null) { vibrator.vibrate(SUCCESS_VIBRATION_EFFECT, VIBRATION_SONIFICATION_ATTRIBUTES); @@ -207,9 +196,6 @@ public abstract class AcquisitionClient<T> extends HalClientMonitor<T> implement } protected final void vibrateError() { - if (!errorHapticsEnabled()) { - return; - } Vibrator vibrator = getContext().getSystemService(Vibrator.class); if (vibrator != null) { vibrator.vibrate(ERROR_VIBRATION_EFFECT, VIBRATION_SONIFICATION_ATTRIBUTES); diff --git a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceAuthenticationClient.java b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceAuthenticationClient.java index db927b227d9a..3757404d226d 100644 --- a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceAuthenticationClient.java +++ b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceAuthenticationClient.java @@ -19,7 +19,6 @@ package com.android.server.biometrics.sensors.face.aidl; import android.annotation.NonNull; import android.annotation.Nullable; import android.app.NotificationManager; -import android.content.ContentResolver; import android.content.Context; import android.content.res.Resources; import android.hardware.biometrics.BiometricAuthenticator; @@ -33,7 +32,6 @@ import android.hardware.face.FaceAuthenticationFrame; import android.hardware.face.FaceManager; import android.os.IBinder; import android.os.RemoteException; -import android.provider.Settings; import android.util.Slog; import com.android.internal.R; @@ -59,9 +57,6 @@ class FaceAuthenticationClient extends AuthenticationClient<ISession> implements @Nullable private final NotificationManager mNotificationManager; @Nullable private ICancellationSignal mCancellationSignal; - @NonNull private final ContentResolver mContentResolver; - private final boolean mCustomHaptics; - private final int[] mBiometricPromptIgnoreList; private final int[] mBiometricPromptIgnoreListVendor; private final int[] mKeyguardIgnoreList; @@ -92,10 +87,6 @@ class FaceAuthenticationClient extends AuthenticationClient<ISession> implements R.array.config_face_acquire_keyguard_ignorelist); mKeyguardIgnoreListVendor = resources.getIntArray( R.array.config_face_acquire_vendor_keyguard_ignorelist); - - mContentResolver = context.getContentResolver(); - mCustomHaptics = Settings.Global.getInt(mContentResolver, - "face_custom_success_error", 0) == 1; } @NonNull @@ -261,18 +252,4 @@ class FaceAuthenticationClient extends AuthenticationClient<ISession> implements Slog.e(TAG, "Remote exception", e); } } - - @Override - protected boolean successHapticsEnabled() { - return mCustomHaptics - ? Settings.Global.getInt(mContentResolver, "face_success_enabled", 1) == 0 - : super.successHapticsEnabled(); - } - - @Override - protected boolean errorHapticsEnabled() { - return mCustomHaptics - ? Settings.Global.getInt(mContentResolver, "face_error_enabled", 1) == 0 - : super.errorHapticsEnabled(); - } } diff --git a/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceAuthenticationClient.java b/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceAuthenticationClient.java index 6c0adafcf2ee..c3de7aa74d15 100644 --- a/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceAuthenticationClient.java +++ b/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceAuthenticationClient.java @@ -17,7 +17,6 @@ package com.android.server.biometrics.sensors.face.hidl; import android.annotation.NonNull; -import android.content.ContentResolver; import android.content.Context; import android.content.res.Resources; import android.hardware.biometrics.BiometricAuthenticator; @@ -28,7 +27,6 @@ import android.hardware.biometrics.face.V1_0.IBiometricsFace; import android.hardware.face.FaceManager; import android.os.IBinder; import android.os.RemoteException; -import android.provider.Settings; import android.util.Slog; import com.android.internal.R; @@ -49,8 +47,6 @@ class FaceAuthenticationClient extends AuthenticationClient<IBiometricsFace> { private static final String TAG = "FaceAuthenticationClient"; - @NonNull private final ContentResolver mContentResolver; - private final boolean mCustomHaptics; private final UsageStats mUsageStats; private final int[] mBiometricPromptIgnoreList; @@ -81,10 +77,6 @@ class FaceAuthenticationClient extends AuthenticationClient<IBiometricsFace> { R.array.config_face_acquire_keyguard_ignorelist); mKeyguardIgnoreListVendor = resources.getIntArray( R.array.config_face_acquire_vendor_keyguard_ignorelist); - - mContentResolver = context.getContentResolver(); - mCustomHaptics = Settings.Global.getInt(mContentResolver, - "face_custom_success_error", 0) == 1; } @NonNull @@ -200,18 +192,4 @@ class FaceAuthenticationClient extends AuthenticationClient<IBiometricsFace> { final boolean shouldSend = shouldSend(acquireInfo, vendorCode); onAcquiredInternal(acquireInfo, vendorCode, shouldSend); } - - @Override - protected boolean successHapticsEnabled() { - return mCustomHaptics - ? Settings.Global.getInt(mContentResolver, "face_success_enabled", 1) == 0 - : super.successHapticsEnabled(); - } - - @Override - protected boolean errorHapticsEnabled() { - return mCustomHaptics - ? Settings.Global.getInt(mContentResolver, "face_error_enabled", 1) == 0 - : super.errorHapticsEnabled(); - } } diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClient.java index 6a05ed470123..19134e46f08f 100644 --- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClient.java +++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClient.java @@ -19,7 +19,6 @@ package com.android.server.biometrics.sensors.fingerprint.aidl; import android.annotation.NonNull; import android.annotation.Nullable; import android.app.TaskStackListener; -import android.content.ContentResolver; import android.content.Context; import android.hardware.biometrics.BiometricAuthenticator; import android.hardware.biometrics.BiometricFingerprintConstants; @@ -30,7 +29,6 @@ import android.hardware.biometrics.fingerprint.ISession; import android.hardware.fingerprint.IUdfpsOverlayController; import android.os.IBinder; import android.os.RemoteException; -import android.provider.Settings; import android.util.Slog; import com.android.server.biometrics.Utils; @@ -57,9 +55,6 @@ class FingerprintAuthenticationClient extends AuthenticationClient<ISession> imp @Nullable private final IUdfpsOverlayController mUdfpsOverlayController; @Nullable private ICancellationSignal mCancellationSignal; - @NonNull private final ContentResolver mContentResolver; - private final boolean mCustomHaptics; - FingerprintAuthenticationClient(@NonNull Context context, @NonNull LazyDaemon<ISession> lazyDaemon, @NonNull IBinder token, @NonNull ClientMonitorCallbackConverter listener, int targetUserId, long operationId, @@ -74,10 +69,6 @@ class FingerprintAuthenticationClient extends AuthenticationClient<ISession> imp lockoutCache, allowBackgroundAuthentication); mLockoutCache = lockoutCache; mUdfpsOverlayController = udfpsOverlayController; - - mContentResolver = context.getContentResolver(); - mCustomHaptics = Settings.Global.getInt(mContentResolver, - "fp_custom_success_error", 0) == 1; } @NonNull @@ -213,18 +204,4 @@ class FingerprintAuthenticationClient extends AuthenticationClient<ISession> imp UdfpsHelper.hideUdfpsOverlay(getSensorId(), mUdfpsOverlayController); mCallback.onClientFinished(this, false /* success */); } - - @Override - protected boolean successHapticsEnabled() { - return mCustomHaptics - ? Settings.Global.getInt(mContentResolver, "fp_success_enabled", 1) == 0 - : super.successHapticsEnabled(); - } - - @Override - protected boolean errorHapticsEnabled() { - return mCustomHaptics - ? Settings.Global.getInt(mContentResolver, "fp_error_enabled", 1) == 0 - : super.errorHapticsEnabled(); - } } diff --git a/services/core/java/com/android/server/policy/SoftRestrictedPermissionPolicy.java b/services/core/java/com/android/server/policy/SoftRestrictedPermissionPolicy.java index 9026262db897..ab7135526746 100644 --- a/services/core/java/com/android/server/policy/SoftRestrictedPermissionPolicy.java +++ b/services/core/java/com/android/server/policy/SoftRestrictedPermissionPolicy.java @@ -189,12 +189,16 @@ public abstract class SoftRestrictedPermissionPolicy { return false; } - // 3. The app has WRITE_MEDIA_STORAGE, OR - // the app already has legacy external storage or requested it, - // and is < R. - return hasWriteMediaStorageGrantedForUid - || ((hasLegacyExternalStorage || hasRequestedLegacyExternalStorage) - && targetSDK < Build.VERSION_CODES.R); + // 3. The app targetSDK should be less than R + if (targetSDK >= Build.VERSION_CODES.R) { + return false; + } + + // 4. The app has WRITE_MEDIA_STORAGE, + // OR the app already has legacy external storage + // OR the app requested legacy external storage + return hasWriteMediaStorageGrantedForUid || hasLegacyExternalStorage + || hasRequestedLegacyExternalStorage; } @Override public boolean mayDenyExtraAppOpIfGranted() { @@ -216,10 +220,8 @@ public abstract class SoftRestrictedPermissionPolicy { return true; } - // The package doesn't have WRITE_MEDIA_STORAGE, - // AND didn't request legacy storage to be preserved - if (!hasWriteMediaStorageGrantedForUid - && !hasRequestedPreserveLegacyExternalStorage) { + // The package doesn't request legacy storage to be preserved + if (!hasRequestedPreserveLegacyExternalStorage) { return true; } diff --git a/services/core/java/com/android/server/vibrator/VibrationThread.java b/services/core/java/com/android/server/vibrator/VibrationThread.java index 03863723a5e9..dd4e260c6d91 100644 --- a/services/core/java/com/android/server/vibrator/VibrationThread.java +++ b/services/core/java/com/android/server/vibrator/VibrationThread.java @@ -1294,21 +1294,32 @@ final class VibrationThread extends Thread implements IBinder.DeathRecipient { @Override public boolean shouldPlayWhenVibratorComplete(int vibratorId) { if (controller.getVibratorInfo().getId() == vibratorId) { + mVibratorCallbackReceived = true; mNextOffTime = SystemClock.uptimeMillis(); } - // Timings are tightly controlled here, so never anticipate when vibrator is complete. - return false; + // Timings are tightly controlled here, so only anticipate if the vibrator was supposed + // to be ON but has completed prematurely, to turn it back on as soon as possible. + return mNextOffTime < startTime && controller.getCurrentAmplitude() > 0; } @Override public List<Step> play() { Trace.traceBegin(Trace.TRACE_TAG_VIBRATOR, "AmplitudeStep"); try { + long now = SystemClock.uptimeMillis(); + long latency = now - startTime; if (DEBUG) { - long latency = SystemClock.uptimeMillis() - startTime; Slog.d(TAG, "Running amplitude step with " + latency + "ms latency."); } + if (mVibratorCallbackReceived && latency < 0) { + // This step was anticipated because the vibrator turned off prematurely. + // Turn it back on and return this same step to run at the exact right time. + mNextOffTime = turnVibratorBackOn(/* remainingDuration= */ -latency); + return Arrays.asList(new AmplitudeStep(startTime, controller, effect, + segmentIndex, mNextOffTime)); + } + VibrationEffectSegment segment = effect.getSegments().get(segmentIndex); if (!(segment instanceof StepSegment)) { Slog.w(TAG, "Ignoring wrong segment for a AmplitudeStep: " + segment); @@ -1321,17 +1332,16 @@ final class VibrationThread extends Thread implements IBinder.DeathRecipient { return skipToNextSteps(/* segmentsSkipped= */ 1); } - long now = SystemClock.uptimeMillis(); float amplitude = stepSegment.getAmplitude(); if (amplitude == 0) { - if (mNextOffTime > now) { + if (vibratorOffTimeout > now) { // Amplitude cannot be set to zero, so stop the vibrator. stopVibrating(); mNextOffTime = now; } } else { if (startTime >= mNextOffTime) { - // Vibrator has stopped. Turn vibrator back on for the duration of another + // Vibrator is OFF. Turn vibrator back on for the duration of another // cycle before setting the amplitude. long onDuration = getVibratorOnDuration(effect, segmentIndex); if (onDuration > 0) { @@ -1350,6 +1360,22 @@ final class VibrationThread extends Thread implements IBinder.DeathRecipient { } } + private long turnVibratorBackOn(long remainingDuration) { + long onDuration = getVibratorOnDuration(effect, segmentIndex); + if (onDuration <= 0) { + // Vibrator is supposed to go back off when this step starts, so just leave it off. + return vibratorOffTimeout; + } + onDuration += remainingDuration; + float expectedAmplitude = controller.getCurrentAmplitude(); + mVibratorOnResult = startVibrating(onDuration); + if (mVibratorOnResult > 0) { + // Set the amplitude back to the value it was supposed to be playing at. + changeAmplitude(expectedAmplitude); + } + return SystemClock.uptimeMillis() + onDuration + CALLBACKS_EXTRA_TIMEOUT; + } + private long startVibrating(long duration) { if (DEBUG) { Slog.d(TAG, "Turning on vibrator " + controller.getVibratorInfo().getId() + " for " @@ -1383,7 +1409,10 @@ final class VibrationThread extends Thread implements IBinder.DeathRecipient { repeatIndex = -1; } if (i == startIndex) { - return 1000; + // The repeating waveform keeps the vibrator ON all the time. Use a minimum + // of 1s duration to prevent short patterns from turning the vibrator ON too + // frequently. + return Math.max(timing, 1000); } } if (i == segmentCount && effect.getRepeatIndex() < 0) { diff --git a/services/tests/mockingservicestests/src/com/android/server/alarm/AlarmManagerServiceTest.java b/services/tests/mockingservicestests/src/com/android/server/alarm/AlarmManagerServiceTest.java index 583797e69995..a254f68e8bed 100644 --- a/services/tests/mockingservicestests/src/com/android/server/alarm/AlarmManagerServiceTest.java +++ b/services/tests/mockingservicestests/src/com/android/server/alarm/AlarmManagerServiceTest.java @@ -514,8 +514,16 @@ public class AlarmManagerServiceTest { } private void setAllowWhileIdleAlarm(int type, long triggerTime, PendingIntent pi, - boolean unrestricted) { - final int flags = unrestricted ? FLAG_ALLOW_WHILE_IDLE_UNRESTRICTED : FLAG_ALLOW_WHILE_IDLE; + boolean unrestricted, boolean compat) { + assertFalse("Alarm cannot be compat and unrestricted", unrestricted && compat); + final int flags; + if (unrestricted) { + flags = FLAG_ALLOW_WHILE_IDLE_UNRESTRICTED; + } else if (compat) { + flags = FLAG_ALLOW_WHILE_IDLE_COMPAT; + } else { + flags = FLAG_ALLOW_WHILE_IDLE; + } setTestAlarm(type, triggerTime, pi, 0, flags, TEST_CALLING_UID); } @@ -1600,13 +1608,13 @@ public class AlarmManagerServiceTest { final long firstTrigger = mNowElapsedTest + 10; for (int i = 0; i < quota; i++) { setAllowWhileIdleAlarm(ELAPSED_REALTIME_WAKEUP, firstTrigger + i, - getNewMockPendingIntent(), false); + getNewMockPendingIntent(), false, false); mNowElapsedTest = mTestTimer.getElapsed(); mTestTimer.expire(); } // This one should get deferred on set. setAllowWhileIdleAlarm(ELAPSED_REALTIME_WAKEUP, firstTrigger + quota, - getNewMockPendingIntent(), false); + getNewMockPendingIntent(), false, false); final long expectedNextTrigger = firstTrigger + mAllowWhileIdleWindow; assertEquals("Incorrect trigger when no quota left", expectedNextTrigger, mTestTimer.getElapsed()); @@ -1619,6 +1627,108 @@ public class AlarmManagerServiceTest { } @Test + public void allowWhileIdleCompatAlarmsWhileDeviceIdle() throws Exception { + setDeviceConfigLong(KEY_MAX_DEVICE_IDLE_FUZZ, 0); + + final long window = mService.mConstants.ALLOW_WHILE_IDLE_COMPAT_WINDOW; + setIdleUntilAlarm(ELAPSED_REALTIME_WAKEUP, mNowElapsedTest + window + 1000, + getNewMockPendingIntent()); + assertNotNull(mService.mPendingIdleUntil); + + final int quota = mService.mConstants.ALLOW_WHILE_IDLE_COMPAT_QUOTA; + final long firstTrigger = mNowElapsedTest + 10; + for (int i = 0; i < quota; i++) { + setAllowWhileIdleAlarm(ELAPSED_REALTIME_WAKEUP, firstTrigger + i, + getNewMockPendingIntent(), false, true); + mNowElapsedTest = mTestTimer.getElapsed(); + mTestTimer.expire(); + } + // This one should get deferred on set. + setAllowWhileIdleAlarm(ELAPSED_REALTIME_WAKEUP, firstTrigger + quota, + getNewMockPendingIntent(), false, true); + final long expectedNextTrigger = firstTrigger + window; + assertEquals("Incorrect trigger when no quota left", expectedNextTrigger, + mTestTimer.getElapsed()); + + // Bring the idle until alarm back. + setIdleUntilAlarm(ELAPSED_REALTIME_WAKEUP, expectedNextTrigger - 50, + getNewMockPendingIntent()); + assertEquals(expectedNextTrigger - 50, mService.mPendingIdleUntil.getWhenElapsed()); + assertEquals(expectedNextTrigger - 50, mTestTimer.getElapsed()); + } + + @Test + public void allowWhileIdleCompatHistorySeparate() throws Exception { + when(mAppStateTracker.areAlarmsRestrictedByBatterySaver(TEST_CALLING_UID, + TEST_CALLING_PACKAGE)).thenReturn(true); + when(mAppStateTracker.isForceAllAppsStandbyEnabled()).thenReturn(true); + + final int fullQuota = mService.mConstants.ALLOW_WHILE_IDLE_QUOTA; + final int compatQuota = mService.mConstants.ALLOW_WHILE_IDLE_COMPAT_QUOTA; + + final long fullWindow = mAllowWhileIdleWindow; + final long compatWindow = mService.mConstants.ALLOW_WHILE_IDLE_COMPAT_WINDOW; + + final long firstFullTrigger = mNowElapsedTest + 10; + for (int i = 0; i < fullQuota; i++) { + setAllowWhileIdleAlarm(ELAPSED_REALTIME_WAKEUP, firstFullTrigger + i, + getNewMockPendingIntent(), false, false); + mNowElapsedTest = mTestTimer.getElapsed(); + mTestTimer.expire(); + } + // This one should get deferred on set, as full quota is not available. + setAllowWhileIdleAlarm(ELAPSED_REALTIME_WAKEUP, firstFullTrigger + fullQuota, + getNewMockPendingIntent(), false, false); + final long expectedNextFullTrigger = firstFullTrigger + fullWindow; + assertEquals("Incorrect trigger when no quota left", expectedNextFullTrigger, + mTestTimer.getElapsed()); + mService.removeLocked(TEST_CALLING_UID, REMOVE_REASON_UNDEFINED); + + // The following should be allowed, as compat quota should be free. + for (int i = 0; i < compatQuota; i++) { + final long trigger = mNowElapsedTest + 1; + setAllowWhileIdleAlarm(ELAPSED_REALTIME_WAKEUP, trigger, getNewMockPendingIntent(), + false, true); + assertEquals(trigger, mTestTimer.getElapsed()); + mNowElapsedTest = mTestTimer.getElapsed(); + mTestTimer.expire(); + } + + // Refresh the state + mService.removeLocked(TEST_CALLING_UID, REMOVE_REASON_UNDEFINED); + mService.mAllowWhileIdleHistory.removeForPackage(TEST_CALLING_PACKAGE, TEST_CALLING_USER); + mService.mAllowWhileIdleCompatHistory.removeForPackage(TEST_CALLING_PACKAGE, + TEST_CALLING_USER); + + // Now test with flipped order + + final long firstCompatTrigger = mNowElapsedTest + 10; + for (int i = 0; i < compatQuota; i++) { + setAllowWhileIdleAlarm(ELAPSED_REALTIME_WAKEUP, firstCompatTrigger + i, + getNewMockPendingIntent(), false, true); + mNowElapsedTest = mTestTimer.getElapsed(); + mTestTimer.expire(); + } + // This one should get deferred on set, as full quota is not available. + setAllowWhileIdleAlarm(ELAPSED_REALTIME_WAKEUP, firstCompatTrigger + compatQuota, + getNewMockPendingIntent(), false, true); + final long expectedNextCompatTrigger = firstCompatTrigger + compatWindow; + assertEquals("Incorrect trigger when no quota left", expectedNextCompatTrigger, + mTestTimer.getElapsed()); + mService.removeLocked(TEST_CALLING_UID, REMOVE_REASON_UNDEFINED); + + // The following should be allowed, as full quota should be free. + for (int i = 0; i < fullQuota; i++) { + final long trigger = mNowElapsedTest + 1; + setAllowWhileIdleAlarm(ELAPSED_REALTIME_WAKEUP, trigger, getNewMockPendingIntent(), + false, false); + assertEquals(trigger, mTestTimer.getElapsed()); + mNowElapsedTest = mTestTimer.getElapsed(); + mTestTimer.expire(); + } + } + + @Test public void allowWhileIdleUnrestricted() throws Exception { setDeviceConfigLong(KEY_MAX_DEVICE_IDLE_FUZZ, 0); @@ -1634,7 +1744,7 @@ public class AlarmManagerServiceTest { final long firstTrigger = mNowElapsedTest + 10; for (int i = 0; i < numAlarms; i++) { setAllowWhileIdleAlarm(ELAPSED_REALTIME_WAKEUP, firstTrigger + i, - getNewMockPendingIntent(), true); + getNewMockPendingIntent(), true, false); } // All of them should fire as expected. for (int i = 0; i < numAlarms; i++) { @@ -1736,7 +1846,7 @@ public class AlarmManagerServiceTest { final int quota = mService.mConstants.ALLOW_WHILE_IDLE_QUOTA; testQuotasDeferralOnSet(trigger -> setAllowWhileIdleAlarm(ELAPSED_REALTIME_WAKEUP, trigger, - getNewMockPendingIntent(), false), quota, mAllowWhileIdleWindow); + getNewMockPendingIntent(), false, false), quota, mAllowWhileIdleWindow); // Refresh the state mService.removeLocked(TEST_CALLING_UID, @@ -1744,7 +1854,7 @@ public class AlarmManagerServiceTest { mService.mAllowWhileIdleHistory.removeForPackage(TEST_CALLING_PACKAGE, TEST_CALLING_USER); testQuotasDeferralOnExpiration(trigger -> setAllowWhileIdleAlarm(ELAPSED_REALTIME_WAKEUP, - trigger, getNewMockPendingIntent(), false), quota, mAllowWhileIdleWindow); + trigger, getNewMockPendingIntent(), false, false), quota, mAllowWhileIdleWindow); // Refresh the state mService.removeLocked(TEST_CALLING_UID, @@ -1752,7 +1862,36 @@ public class AlarmManagerServiceTest { mService.mAllowWhileIdleHistory.removeForPackage(TEST_CALLING_PACKAGE, TEST_CALLING_USER); testQuotasNoDeferral(trigger -> setAllowWhileIdleAlarm(ELAPSED_REALTIME_WAKEUP, trigger, - getNewMockPendingIntent(), false), quota, mAllowWhileIdleWindow); + getNewMockPendingIntent(), false, false), quota, mAllowWhileIdleWindow); + } + + @Test + public void allowWhileIdleCompatAlarmsInBatterySaver() throws Exception { + when(mAppStateTracker.areAlarmsRestrictedByBatterySaver(TEST_CALLING_UID, + TEST_CALLING_PACKAGE)).thenReturn(true); + when(mAppStateTracker.isForceAllAppsStandbyEnabled()).thenReturn(true); + + final int quota = mService.mConstants.ALLOW_WHILE_IDLE_COMPAT_QUOTA; + final long window = mService.mConstants.ALLOW_WHILE_IDLE_COMPAT_WINDOW; + + testQuotasDeferralOnSet(trigger -> setAllowWhileIdleAlarm(ELAPSED_REALTIME_WAKEUP, trigger, + getNewMockPendingIntent(), false, true), quota, window); + + // Refresh the state + mService.removeLocked(TEST_CALLING_UID, REMOVE_REASON_UNDEFINED); + mService.mAllowWhileIdleCompatHistory.removeForPackage(TEST_CALLING_PACKAGE, + TEST_CALLING_USER); + + testQuotasDeferralOnExpiration(trigger -> setAllowWhileIdleAlarm(ELAPSED_REALTIME_WAKEUP, + trigger, getNewMockPendingIntent(), false, true), quota, window); + + // Refresh the state + mService.removeLocked(TEST_CALLING_UID, REMOVE_REASON_UNDEFINED); + mService.mAllowWhileIdleCompatHistory.removeForPackage(TEST_CALLING_PACKAGE, + TEST_CALLING_USER); + + testQuotasNoDeferral(trigger -> setAllowWhileIdleAlarm(ELAPSED_REALTIME_WAKEUP, trigger, + getNewMockPendingIntent(), false, true), quota, window); } @Test @@ -2123,7 +2262,7 @@ public class AlarmManagerServiceTest { final PendingIntent alarmPi = getNewMockPendingIntent(); final AlarmManager.AlarmClockInfo alarmClock = mock(AlarmManager.AlarmClockInfo.class); mBinder.set(TEST_CALLING_PACKAGE, RTC_WAKEUP, 1234, WINDOW_EXACT, 0, 0, - alarmPi, null, null, null, alarmClock); + alarmPi, null, null, null, alarmClock); // Correct permission checks are invoked. verify(mService).hasScheduleExactAlarmInternal(TEST_CALLING_PACKAGE, TEST_CALLING_UID); diff --git a/services/tests/servicestests/src/com/android/server/vibrator/VibrationThreadTest.java b/services/tests/servicestests/src/com/android/server/vibrator/VibrationThreadTest.java index 1596483cdbe1..2e5c24c1a95c 100644 --- a/services/tests/servicestests/src/com/android/server/vibrator/VibrationThreadTest.java +++ b/services/tests/servicestests/src/com/android/server/vibrator/VibrationThreadTest.java @@ -246,6 +246,81 @@ public class VibrationThreadTest { } @Test + public void vibrate_singleVibratorRepeatingShortAlwaysOnWaveform_turnsVibratorOnForASecond() + throws Exception { + FakeVibratorControllerProvider fakeVibrator = mVibratorProviders.get(VIBRATOR_ID); + fakeVibrator.setCapabilities(IVibrator.CAP_AMPLITUDE_CONTROL); + + long vibrationId = 1; + int[] amplitudes = new int[]{1, 2, 3}; + VibrationEffect effect = VibrationEffect.createWaveform( + new long[]{1, 10, 100}, amplitudes, 0); + VibrationThread thread = startThreadAndDispatcher(vibrationId, effect); + + assertTrue(waitUntil(t -> !fakeVibrator.getAmplitudes().isEmpty(), thread, + TEST_TIMEOUT_MILLIS)); + thread.cancel(); + waitForCompletion(thread); + + verifyCallbacksTriggered(vibrationId, Vibration.Status.CANCELLED); + assertFalse(thread.getVibrators().get(VIBRATOR_ID).isVibrating()); + assertEquals(Arrays.asList(expectedOneShot(1000)), fakeVibrator.getEffectSegments()); + } + + @Test + public void vibrate_singleVibratorRepeatingLongAlwaysOnWaveform_turnsVibratorOnForACycle() + throws Exception { + FakeVibratorControllerProvider fakeVibrator = mVibratorProviders.get(VIBRATOR_ID); + fakeVibrator.setCapabilities(IVibrator.CAP_AMPLITUDE_CONTROL); + + long vibrationId = 1; + int[] amplitudes = new int[]{1, 2, 3}; + VibrationEffect effect = VibrationEffect.createWaveform( + new long[]{5000, 500, 50}, amplitudes, 0); + VibrationThread thread = startThreadAndDispatcher(vibrationId, effect); + + assertTrue(waitUntil(t -> !fakeVibrator.getAmplitudes().isEmpty(), thread, + TEST_TIMEOUT_MILLIS)); + thread.cancel(); + waitForCompletion(thread); + + verifyCallbacksTriggered(vibrationId, Vibration.Status.CANCELLED); + assertFalse(thread.getVibrators().get(VIBRATOR_ID).isVibrating()); + assertEquals(Arrays.asList(expectedOneShot(5550)), fakeVibrator.getEffectSegments()); + } + + + @Test + public void vibrate_singleVibratorRepeatingAlwaysOnWaveform_turnsVibratorBackOn() + throws Exception { + FakeVibratorControllerProvider fakeVibrator = mVibratorProviders.get(VIBRATOR_ID); + fakeVibrator.setCapabilities(IVibrator.CAP_AMPLITUDE_CONTROL); + + long vibrationId = 1; + int[] amplitudes = new int[]{1, 2}; + VibrationEffect effect = VibrationEffect.createWaveform( + new long[]{900, 50}, amplitudes, 0); + VibrationThread thread = startThreadAndDispatcher(vibrationId, effect); + + assertTrue(waitUntil(t -> fakeVibrator.getAmplitudes().size() > 2 * amplitudes.length, + thread, 1000 + TEST_TIMEOUT_MILLIS)); + thread.cancel(); + waitForCompletion(thread); + + verifyCallbacksTriggered(vibrationId, Vibration.Status.CANCELLED); + assertFalse(thread.getVibrators().get(VIBRATOR_ID).isVibrating()); + assertEquals(2, fakeVibrator.getEffectSegments().size()); + // First time turn vibrator ON for minimum of 1s. + assertEquals(1000L, fakeVibrator.getEffectSegments().get(0).getDuration()); + // Vibrator turns off in the middle of the second execution of first step, turn it back ON + // for another 1s + remaining of 850ms. + assertEquals(1850, fakeVibrator.getEffectSegments().get(1).getDuration(), /* delta= */ 20); + // Set amplitudes for a cycle {1, 2}, start second loop then turn it back on to same value. + assertEquals(expectedAmplitudes(1, 2, 1, 1), + mVibratorProviders.get(VIBRATOR_ID).getAmplitudes().subList(0, 4)); + } + + @Test public void vibrate_singleVibratorPredefinedCancel_cancelsVibrationImmediately() throws Exception { mVibratorProviders.get(VIBRATOR_ID).setCapabilities(IVibrator.CAP_COMPOSE_EFFECTS); |