diff options
92 files changed, 2087 insertions, 835 deletions
diff --git a/core/java/android/app/NotificationChannelGroup.java b/core/java/android/app/NotificationChannelGroup.java index 1769993e0e07..f97415ca20c8 100644 --- a/core/java/android/app/NotificationChannelGroup.java +++ b/core/java/android/app/NotificationChannelGroup.java @@ -20,7 +20,6 @@ import android.annotation.SystemApi; import android.annotation.TestApi; import android.compat.annotation.UnsupportedAppUsage; import android.content.Intent; -import android.content.pm.ParceledListSlice; import android.os.Parcel; import android.os.Parcelable; import android.text.TextUtils; @@ -67,7 +66,7 @@ public final class NotificationChannelGroup implements Parcelable { private CharSequence mName; private String mDescription; private boolean mBlocked; - private ParceledListSlice<NotificationChannel> mChannels; + private List<NotificationChannel> mChannels = new ArrayList<>(); // Bitwise representation of fields that have been changed by the user private int mUserLockedFields; @@ -101,8 +100,7 @@ public final class NotificationChannelGroup implements Parcelable { } else { mDescription = null; } - mChannels = in.readParcelable( - NotificationChannelGroup.class.getClassLoader(), ParceledListSlice.class); + in.readParcelableList(mChannels, NotificationChannel.class.getClassLoader(), android.app.NotificationChannel.class); mBlocked = in.readBoolean(); mUserLockedFields = in.readInt(); } @@ -129,7 +127,7 @@ public final class NotificationChannelGroup implements Parcelable { } else { dest.writeByte((byte) 0); } - dest.writeParcelable(mChannels, flags); + dest.writeParcelableList(mChannels, flags); dest.writeBoolean(mBlocked); dest.writeInt(mUserLockedFields); } @@ -159,7 +157,7 @@ public final class NotificationChannelGroup implements Parcelable { * Returns the list of channels that belong to this group */ public List<NotificationChannel> getChannels() { - return mChannels == null ? new ArrayList<>() : mChannels.getList(); + return mChannels; } /** @@ -193,8 +191,15 @@ public final class NotificationChannelGroup implements Parcelable { /** * @hide */ + public void addChannel(NotificationChannel channel) { + mChannels.add(channel); + } + + /** + * @hide + */ public void setChannels(List<NotificationChannel> channels) { - mChannels = new ParceledListSlice<>(channels); + mChannels = channels; } /** @@ -329,7 +334,7 @@ public final class NotificationChannelGroup implements Parcelable { proto.write(NotificationChannelGroupProto.NAME, mName.toString()); proto.write(NotificationChannelGroupProto.DESCRIPTION, mDescription); proto.write(NotificationChannelGroupProto.IS_BLOCKED, mBlocked); - for (NotificationChannel channel : mChannels.getList()) { + for (NotificationChannel channel : mChannels) { channel.dumpDebug(proto, NotificationChannelGroupProto.CHANNELS); } proto.end(token); diff --git a/core/java/android/hardware/biometrics/BiometricConstants.java b/core/java/android/hardware/biometrics/BiometricConstants.java index 99e4febe205d..943eee461809 100644 --- a/core/java/android/hardware/biometrics/BiometricConstants.java +++ b/core/java/android/hardware/biometrics/BiometricConstants.java @@ -157,6 +157,11 @@ public interface BiometricConstants { int BIOMETRIC_ERROR_SENSOR_PRIVACY_ENABLED = 18; /** + * A power press stopped this biometric operation. + * @hide + */ + int BIOMETRIC_ERROR_POWER_PRESSED = 19; + /** * This constant is only used by SystemUI. It notifies SystemUI that authentication was paused * because the authentication attempt was unsuccessful. * @hide diff --git a/core/java/android/hardware/biometrics/BiometricFaceConstants.java b/core/java/android/hardware/biometrics/BiometricFaceConstants.java index fd46f243874b..2b62b98529a9 100644 --- a/core/java/android/hardware/biometrics/BiometricFaceConstants.java +++ b/core/java/android/hardware/biometrics/BiometricFaceConstants.java @@ -70,6 +70,7 @@ public interface BiometricFaceConstants { BIOMETRIC_ERROR_SECURITY_UPDATE_REQUIRED, BIOMETRIC_ERROR_RE_ENROLL, FACE_ERROR_UNKNOWN, + BIOMETRIC_ERROR_POWER_PRESSED, }) @Retention(RetentionPolicy.SOURCE) @interface FaceError {} @@ -184,6 +185,12 @@ public interface BiometricFaceConstants { int FACE_ERROR_UNKNOWN = 17; /** + * A power press stopped this biometric operation. + * @hide + */ + int BIOMETRIC_ERROR_POWER_PRESSED = 19; + + /** * Vendor codes received from the HAL start at 0. Codes that the framework exposes to keyguard * append this value for some reason. We should probably remove this and just send the actual * vendor code. diff --git a/core/java/android/hardware/biometrics/BiometricFingerprintConstants.java b/core/java/android/hardware/biometrics/BiometricFingerprintConstants.java index d8ebb628452a..98f571b80949 100644 --- a/core/java/android/hardware/biometrics/BiometricFingerprintConstants.java +++ b/core/java/android/hardware/biometrics/BiometricFingerprintConstants.java @@ -61,7 +61,8 @@ public interface BiometricFingerprintConstants { BIOMETRIC_ERROR_RE_ENROLL, BIOMETRIC_ERROR_SECURITY_UPDATE_REQUIRED, FINGERPRINT_ERROR_UNKNOWN, - FINGERPRINT_ERROR_BAD_CALIBRATION}) + FINGERPRINT_ERROR_BAD_CALIBRATION, + BIOMETRIC_ERROR_POWER_PRESSED}) @Retention(RetentionPolicy.SOURCE) @interface FingerprintError {} @@ -188,6 +189,12 @@ public interface BiometricFingerprintConstants { int FINGERPRINT_ERROR_BAD_CALIBRATION = 18; /** + * A power press stopped this biometric operation. + * @hide + */ + int BIOMETRIC_ERROR_POWER_PRESSED = 19; + + /** * @hide */ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) diff --git a/core/java/android/hardware/biometrics/BiometricStateListener.java b/core/java/android/hardware/biometrics/BiometricStateListener.java index 2ac0c1edaef0..b167cc6323d3 100644 --- a/core/java/android/hardware/biometrics/BiometricStateListener.java +++ b/core/java/android/hardware/biometrics/BiometricStateListener.java @@ -46,6 +46,14 @@ public abstract class BiometricStateListener extends IBiometricStateListener.Stu public @interface State { } + // The sensor received a touch. + public static final int ACTION_SENSOR_TOUCH = 0; + + @IntDef({ACTION_SENSOR_TOUCH}) + @Retention(RetentionPolicy.SOURCE) + public @interface Action { + } + /** * Defines behavior in response to state update * @param newState new state of the biometric sensor @@ -53,6 +61,13 @@ public abstract class BiometricStateListener extends IBiometricStateListener.Stu public void onStateChanged(@BiometricStateListener.State int newState) { } + + /** + * Invoked when a biometric action has occurred. + */ + public void onBiometricAction(@BiometricStateListener.Action int action) { + } + /** * Invoked when enrollment state changes for the specified user */ diff --git a/core/java/android/hardware/biometrics/IBiometricStateListener.aidl b/core/java/android/hardware/biometrics/IBiometricStateListener.aidl index 5bdced034017..6bb170d1cb62 100644 --- a/core/java/android/hardware/biometrics/IBiometricStateListener.aidl +++ b/core/java/android/hardware/biometrics/IBiometricStateListener.aidl @@ -22,5 +22,6 @@ package android.hardware.biometrics; */ oneway interface IBiometricStateListener { void onStateChanged(int newState); + void onBiometricAction(int action); void onEnrollmentsChanged(int userId, int sensorId, boolean hasEnrollments); } diff --git a/core/java/android/hardware/fingerprint/FingerprintManager.java b/core/java/android/hardware/fingerprint/FingerprintManager.java index 28f1f02b7a93..c614cdbc3199 100644 --- a/core/java/android/hardware/fingerprint/FingerprintManager.java +++ b/core/java/android/hardware/fingerprint/FingerprintManager.java @@ -101,6 +101,7 @@ public class FingerprintManager implements BiometricAuthenticator, BiometricFing private static final int MSG_FINGERPRINT_DETECTED = 107; private static final int MSG_UDFPS_POINTER_DOWN = 108; private static final int MSG_UDFPS_POINTER_UP = 109; + private static final int MSG_POWER_BUTTON_PRESSED = 110; /** * @hide @@ -984,6 +985,16 @@ public class FingerprintManager implements BiometricAuthenticator, BiometricFing } /** + * This is triggered by SideFpsEventHandler + * @hide + */ + @RequiresPermission(USE_BIOMETRIC_INTERNAL) + public void onPowerPressed() { + Slog.i(TAG, "onPowerPressed"); + mHandler.obtainMessage(MSG_POWER_BUTTON_PRESSED).sendToTarget(); + } + + /** * Determine if there is at least one fingerprint enrolled. * * @return true if at least one fingerprint is enrolled, false otherwise @@ -1196,6 +1207,9 @@ public class FingerprintManager implements BiometricAuthenticator, BiometricFing case MSG_UDFPS_POINTER_UP: sendUdfpsPointerUp(msg.arg1 /* sensorId */); break; + case MSG_POWER_BUTTON_PRESSED: + sendPowerPressed(); + break; default: Slog.w(TAG, "Unknown message: " + msg.what); @@ -1325,6 +1339,14 @@ public class FingerprintManager implements BiometricAuthenticator, BiometricFing mAuthenticationCallback.onUdfpsPointerUp(sensorId); } + private void sendPowerPressed() { + try { + mService.onPowerPressed(); + } catch (RemoteException e) { + Slog.e(TAG, "Error sending power press", e); + } + } + /** * @hide */ @@ -1461,6 +1483,9 @@ public class FingerprintManager implements BiometricAuthenticator, BiometricFing case FINGERPRINT_ERROR_BAD_CALIBRATION: return context.getString( com.android.internal.R.string.fingerprint_error_bad_calibration); + case BIOMETRIC_ERROR_POWER_PRESSED: + return context.getString( + com.android.internal.R.string.fingerprint_error_power_pressed); case FINGERPRINT_ERROR_VENDOR: { String[] msgArray = context.getResources().getStringArray( com.android.internal.R.array.fingerprint_error_vendor); diff --git a/core/java/android/hardware/fingerprint/IFingerprintService.aidl b/core/java/android/hardware/fingerprint/IFingerprintService.aidl index 0b6344617663..20cc58cc78a7 100644 --- a/core/java/android/hardware/fingerprint/IFingerprintService.aidl +++ b/core/java/android/hardware/fingerprint/IFingerprintService.aidl @@ -171,4 +171,7 @@ interface IFingerprintService { // Registers BiometricStateListener. void registerBiometricStateListener(IBiometricStateListener listener); + + // Sends a power button pressed event to all listeners. + oneway void onPowerPressed(); } diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java index a63beecaaf91..ce354610433c 100644 --- a/core/java/android/provider/Settings.java +++ b/core/java/android/provider/Settings.java @@ -9704,6 +9704,43 @@ public final class Settings { public static final String FACE_UNLOCK_RE_ENROLL = "face_unlock_re_enroll"; /** + * The time (in millis) to wait for a power button before sending a + * successful auth in to keyguard(for side fingerprint) + * @hide + */ + @Readable + public static final String FINGERPRINT_SIDE_FPS_KG_POWER_WINDOW = + "fingerprint_side_fps_kg_power_window"; + + /** + * The time (in millis) to wait for a power button before sending + * a successful auth in biometric prompt(for side fingerprint) + * @hide + */ + @Readable + public static final String FINGERPRINT_SIDE_FPS_BP_POWER_WINDOW = + "fingerprint_side_fps_bp_power_window"; + + /** + * The time (in millis) that a finger tap will wait for a power button + * before dismissing the power dialog during enrollment(for side + * fingerprint) + * @hide + */ + @Readable + public static final String FINGERPRINT_SIDE_FPS_ENROLL_TAP_WINDOW = + "fingerprint_side_fps_enroll_tap_window"; + + /** + * The time (in millis) that a power event will ignore future authentications + * (for side fingerprint) + * @hide + */ + @Readable + public static final String FINGERPRINT_SIDE_FPS_AUTH_DOWNTIME = + "fingerprint_side_fps_auth_downtime"; + + /** * Whether or not debugging is enabled. * @hide */ @@ -11309,6 +11346,7 @@ public final class Settings { * <li>{@link BatteryManager#BATTERY_PLUGGED_AC} to stay on for AC charger</li> * <li>{@link BatteryManager#BATTERY_PLUGGED_USB} to stay on for USB charger</li> * <li>{@link BatteryManager#BATTERY_PLUGGED_WIRELESS} to stay on for wireless charger</li> + * <li>{@link BatteryManager#BATTERY_PLUGGED_DOCK} to stay on for dock charger</li> * </ul> * These values can be OR-ed together. */ diff --git a/core/proto/android/server/powermanagerservice.proto b/core/proto/android/server/powermanagerservice.proto index 04f4d7b09d82..bd4f99044b17 100644 --- a/core/proto/android/server/powermanagerservice.proto +++ b/core/proto/android/server/powermanagerservice.proto @@ -238,6 +238,7 @@ message PowerServiceSettingsAndConfigurationDumpProto { optional bool is_stay_on_while_plugged_in_ac = 1; optional bool is_stay_on_while_plugged_in_usb = 2; optional bool is_stay_on_while_plugged_in_wireless = 3; + optional bool is_stay_on_while_plugged_in_dock = 4; } message ScreenBrightnessSettingLimitsProto { option (.android.msg_privacy).dest = DEST_AUTOMATIC; diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml index a8c7bf249ada..75034c7c2d2e 100644 --- a/core/res/res/values/config.xml +++ b/core/res/res/values/config.xml @@ -3498,6 +3498,22 @@ <!-- Specify if the fingerprint hardware support gestures--> <bool name="config_fingerprintSupportsGestures">false</bool> + <!-- The time (in millis) to wait for a power button before sending + a successful auth in biometric prompt(for side fingerprint) --> + <integer name="config_sidefpsBpPowerPressWindow">300</integer> + + <!-- The time (in millis) to wait for a power button before sending a + successful auth in to keyguard(for side fingerprint) --> + <integer name="config_sidefpsKeyguardPowerPressWindow">300</integer> + + <!-- The time (in millis) that a power event will ignore future authentications + (for side fingerprint) --> + <integer name="config_sidefpsPostAuthDowntime">400</integer> + + <!-- The time (in millis) that a finger tap will wait for a power button + before dismissing the power dialog during enrollment(for side fingerprint) --> + <integer name="config_sidefpsEnrollPowerPressWindow">300</integer> + <!-- This config is used to force VoiceInteractionService to start on certain low ram devices. It declares the package name of VoiceInteractionService that should be started. --> <string translatable="false" name="config_forceVoiceInteractionServicePackage"></string> diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml index 3787ff99383f..d5283852c295 100644 --- a/core/res/res/values/strings.xml +++ b/core/res/res/values/strings.xml @@ -1751,6 +1751,8 @@ <string name="fingerprint_error_security_update_required">Sensor temporarily disabled.</string> <!-- Generic error message shown when fingerprint needs calibration [CHAR LIMIT=150] --> <string name="fingerprint_error_bad_calibration">Can\u2019t use fingerprint sensor. Visit a repair provider</string> + <!-- Generic error message shown when the power button has been pressed. [CHAR LIMIT=150] --> + <string name="fingerprint_error_power_pressed">Power button pressed</string> <!-- Template to be used to name enrolled fingerprints by default. --> <string name="fingerprint_name_template">Finger <xliff:g id="fingerId" example="1">%d</xliff:g></string> diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml index 4a8b3a75b6ee..b2fd28fc27e6 100644 --- a/core/res/res/values/symbols.xml +++ b/core/res/res/values/symbols.xml @@ -2622,10 +2622,15 @@ <java-symbol type="string" name="fingerprint_recalibrate_notification_name" /> <java-symbol type="string" name="fingerprint_recalibrate_notification_title" /> <java-symbol type="string" name="fingerprint_recalibrate_notification_content" /> + <java-symbol type="string" name="fingerprint_error_power_pressed" /> <!-- Fingerprint config --> <java-symbol type="integer" name="config_fingerprintMaxTemplatesPerUser"/> <java-symbol type="bool" name="config_fingerprintSupportsGestures"/> + <java-symbol type="integer" name="config_sidefpsBpPowerPressWindow"/> + <java-symbol type="integer" name="config_sidefpsKeyguardPowerPressWindow"/> + <java-symbol type="integer" name="config_sidefpsPostAuthDowntime"/> + <java-symbol type="integer" name="config_sidefpsEnrollPowerPressWindow"/> <!-- Face authentication messages --> <java-symbol type="string" name="face_recalibrate_notification_name" /> diff --git a/core/tests/coretests/src/android/graphics/drawable/AdaptiveIconDrawableTest.java b/core/tests/coretests/src/android/graphics/drawable/AdaptiveIconDrawableTest.java index 7338c3a3ea8a..ddc05e09c395 100644 --- a/core/tests/coretests/src/android/graphics/drawable/AdaptiveIconDrawableTest.java +++ b/core/tests/coretests/src/android/graphics/drawable/AdaptiveIconDrawableTest.java @@ -204,6 +204,19 @@ public class AdaptiveIconDrawableTest extends AndroidTestCase { assertEquals(100, Color.alpha(bitmap.getPixel(50, 50))); } + @Test + public void testSetBounds() throws Exception { + mIconDrawable = new AdaptiveIconDrawable(mBackgroundDrawable, mForegroundDrawable); + mIconDrawable.setBounds(0, 0, 100, 100); + + assertEquals(new Rect(-25, -25, 125, 125), mBackgroundDrawable.getBounds()); + assertEquals(new Rect(-25, -25, 125, 125), mForegroundDrawable.getBounds()); + + mIconDrawable.setBounds(10, 10, 110, 110); + assertEquals(new Rect(-15, -15, 135, 135), mBackgroundDrawable.getBounds()); + assertEquals(new Rect(-15, -15, 135, 135), mForegroundDrawable.getBounds()); + } + // // Utils // diff --git a/graphics/java/android/graphics/drawable/AdaptiveIconDrawable.java b/graphics/java/android/graphics/drawable/AdaptiveIconDrawable.java index 2f56b181f455..6939a72019e8 100644 --- a/graphics/java/android/graphics/drawable/AdaptiveIconDrawable.java +++ b/graphics/java/android/graphics/drawable/AdaptiveIconDrawable.java @@ -26,8 +26,6 @@ import android.content.res.ColorStateList; import android.content.res.Resources; import android.content.res.Resources.Theme; import android.content.res.TypedArray; -import android.graphics.Bitmap; -import android.graphics.BitmapShader; import android.graphics.BlendMode; import android.graphics.Canvas; import android.graphics.Color; @@ -39,8 +37,6 @@ import android.graphics.Path; import android.graphics.PixelFormat; import android.graphics.Rect; import android.graphics.Region; -import android.graphics.Shader; -import android.graphics.Shader.TileMode; import android.util.AttributeSet; import android.util.DisplayMetrics; import android.util.PathParser; @@ -106,7 +102,8 @@ public class AdaptiveIconDrawable extends Drawable implements Drawable.Callback private static final float DEFAULT_VIEW_PORT_SCALE = 1f / (1 + 2 * EXTRA_INSET_PERCENTAGE); /** - * Clip path defined in R.string.config_icon_mask. + * Unused path. + * TODO: Remove once the layoutLib is updated */ private static Path sMask; @@ -114,9 +111,10 @@ public class AdaptiveIconDrawable extends Drawable implements Drawable.Callback * Scaled mask based on the view bounds. */ private final Path mMask; - private final Path mMaskScaleOnly; + private final Path mMaskTransformed; private final Matrix mMaskMatrix; private final Region mTransparentRegion; + private final Paint mPaint = new Paint(Paint.ANTI_ALIAS_FLAG); /** * Indices used to access {@link #mLayerState.mChildDrawable} array for foreground and @@ -131,19 +129,10 @@ public class AdaptiveIconDrawable extends Drawable implements Drawable.Callback */ LayerState mLayerState; - private Shader mLayersShader; - private Bitmap mLayersBitmap; - private final Rect mTmpOutRect = new Rect(); private Rect mHotspotBounds; private boolean mMutated; - private boolean mSuspendChildInvalidation; - private boolean mChildRequestedInvalidation; - private final Canvas mCanvas; - private Paint mPaint = new Paint(Paint.ANTI_ALIAS_FLAG | Paint.DITHER_FLAG | - Paint.FILTER_BITMAP_FLAG); - /** * Constructor used for xml inflation. */ @@ -157,19 +146,16 @@ public class AdaptiveIconDrawable extends Drawable implements Drawable.Callback */ AdaptiveIconDrawable(@Nullable LayerState state, @Nullable Resources res) { mLayerState = createConstantState(state, res); - // config_icon_mask from context bound resource may have been chaged using + // config_icon_mask from context bound resource may have been changed using // OverlayManager. Read that one first. Resources r = ActivityThread.currentActivityThread() == null ? Resources.getSystem() : ActivityThread.currentActivityThread().getApplication().getResources(); - // TODO: either make sMask update only when config_icon_mask changes OR - // get rid of it all-together in layoutlib - sMask = PathParser.createPathFromPathData(r.getString(R.string.config_icon_mask)); - mMask = new Path(sMask); - mMaskScaleOnly = new Path(mMask); + mMask = PathParser.createPathFromPathData(r.getString(R.string.config_icon_mask)); + mMaskTransformed = new Path(); mMaskMatrix = new Matrix(); - mCanvas = new Canvas(); mTransparentRegion = new Region(); + mPaint.setColor(Color.BLACK); } private ChildDrawable createChildDrawable(Drawable drawable) { @@ -280,7 +266,7 @@ public class AdaptiveIconDrawable extends Drawable implements Drawable.Callback * @return the mask path object used to clip the drawable */ public Path getIconMask() { - return mMask; + return mMaskTransformed; } /** @@ -322,92 +308,47 @@ public class AdaptiveIconDrawable extends Drawable implements Drawable.Callback if (bounds.isEmpty()) { return; } - updateLayerBounds(bounds); - } - - private void updateLayerBounds(Rect bounds) { - if (bounds.isEmpty()) { - return; - } - try { - suspendChildInvalidation(); - updateLayerBoundsInternal(bounds); - updateMaskBoundsInternal(bounds); - } finally { - resumeChildInvalidation(); - } - } - - /** - * Set the child layer bounds bigger than the view port size by {@link #DEFAULT_VIEW_PORT_SCALE} - */ - private void updateLayerBoundsInternal(Rect bounds) { - int cX = bounds.width() / 2; - int cY = bounds.height() / 2; + // Set the child layer bounds bigger than the view port size + // by {@link #DEFAULT_VIEW_PORT_SCALE} + float cX = bounds.exactCenterX(); + float cY = bounds.exactCenterY(); + float insetWidth = bounds.width() / (DEFAULT_VIEW_PORT_SCALE * 2); + float insetHeight = bounds.height() / (DEFAULT_VIEW_PORT_SCALE * 2); + final Rect outRect = mTmpOutRect; + outRect.set( + (int) (cX - insetWidth), + (int) (cY - insetHeight), + (int) (cX + insetWidth), + (int) (cY + insetHeight)); for (int i = 0, count = mLayerState.N_CHILDREN; i < count; i++) { final ChildDrawable r = mLayerState.mChildren[i]; - final Drawable d = r.mDrawable; - if (d == null) { - continue; + if (r.mDrawable != null) { + r.mDrawable.setBounds(outRect); } - - int insetWidth = (int) (bounds.width() / (DEFAULT_VIEW_PORT_SCALE * 2)); - int insetHeight = (int) (bounds.height() / (DEFAULT_VIEW_PORT_SCALE * 2)); - final Rect outRect = mTmpOutRect; - outRect.set(cX - insetWidth, cY - insetHeight, cX + insetWidth, cY + insetHeight); - - d.setBounds(outRect); } - } - - private void updateMaskBoundsInternal(Rect b) { - // reset everything that depends on the view bounds - mMaskMatrix.setScale(b.width() / MASK_SIZE, b.height() / MASK_SIZE); - sMask.transform(mMaskMatrix, mMaskScaleOnly); - mMaskMatrix.postTranslate(b.left, b.top); - sMask.transform(mMaskMatrix, mMask); + // Update the clipping mask + mMaskMatrix.setScale(bounds.width() / MASK_SIZE, bounds.height() / MASK_SIZE); + mMaskMatrix.postTranslate(bounds.left, bounds.top); + mMask.transform(mMaskMatrix, mMaskTransformed); - if (mLayersBitmap == null || mLayersBitmap.getWidth() != b.width() - || mLayersBitmap.getHeight() != b.height()) { - mLayersBitmap = Bitmap.createBitmap(b.width(), b.height(), Bitmap.Config.ARGB_8888); - } - - mPaint.setShader(null); + // Clear the transparent region, it is calculated lazily mTransparentRegion.setEmpty(); - mLayersShader = null; } @Override public void draw(Canvas canvas) { - if (mLayersBitmap == null) { - return; + int saveCount = canvas.save(); + canvas.clipPath(mMaskTransformed); + canvas.drawPaint(mPaint); + if (mLayerState.mChildren[BACKGROUND_ID].mDrawable != null) { + mLayerState.mChildren[BACKGROUND_ID].mDrawable.draw(canvas); } - if (mLayersShader == null) { - mCanvas.setBitmap(mLayersBitmap); - mCanvas.drawColor(Color.BLACK); - if (mLayerState.mChildren[BACKGROUND_ID].mDrawable != null) { - mLayerState.mChildren[BACKGROUND_ID].mDrawable.draw(mCanvas); - } - if (mLayerState.mChildren[FOREGROUND_ID].mDrawable != null) { - mLayerState.mChildren[FOREGROUND_ID].mDrawable.draw(mCanvas); - } - mLayersShader = new BitmapShader(mLayersBitmap, TileMode.CLAMP, TileMode.CLAMP); - mPaint.setShader(mLayersShader); - } - if (mMaskScaleOnly != null) { - Rect bounds = getBounds(); - canvas.translate(bounds.left, bounds.top); - canvas.drawPath(mMaskScaleOnly, mPaint); - canvas.translate(-bounds.left, -bounds.top); + if (mLayerState.mChildren[FOREGROUND_ID].mDrawable != null) { + mLayerState.mChildren[FOREGROUND_ID].mDrawable.draw(canvas); } - } - - @Override - public void invalidateSelf() { - mLayersShader = null; - super.invalidateSelf(); + canvas.restoreToCount(saveCount); } @Override @@ -600,37 +541,9 @@ public class AdaptiveIconDrawable extends Drawable implements Drawable.Callback return false; } - /** - * Temporarily suspends child invalidation. - * - * @see #resumeChildInvalidation() - */ - private void suspendChildInvalidation() { - mSuspendChildInvalidation = true; - } - - /** - * Resumes child invalidation after suspension, immediately performing an - * invalidation if one was requested by a child during suspension. - * - * @see #suspendChildInvalidation() - */ - private void resumeChildInvalidation() { - mSuspendChildInvalidation = false; - - if (mChildRequestedInvalidation) { - mChildRequestedInvalidation = false; - invalidateSelf(); - } - } - @Override public void invalidateDrawable(@NonNull Drawable who) { - if (mSuspendChildInvalidation) { - mChildRequestedInvalidation = true; - } else { - invalidateSelf(); - } + invalidateSelf(); } @Override @@ -714,6 +627,13 @@ public class AdaptiveIconDrawable extends Drawable implements Drawable.Callback @Override public void setAlpha(int alpha) { mPaint.setAlpha(alpha); + final ChildDrawable[] array = mLayerState.mChildren; + for (int i = 0; i < mLayerState.N_CHILDREN; i++) { + final Drawable dr = array[i].mDrawable; + if (dr != null) { + dr.setAlpha(alpha); + } + } } @Override @@ -816,10 +736,6 @@ public class AdaptiveIconDrawable extends Drawable implements Drawable.Callback } } - if (changed) { - updateLayerBounds(getBounds()); - } - return changed; } @@ -835,10 +751,6 @@ public class AdaptiveIconDrawable extends Drawable implements Drawable.Callback } } - if (changed) { - updateLayerBounds(getBounds()); - } - return changed; } @@ -979,6 +891,7 @@ public class AdaptiveIconDrawable extends Drawable implements Drawable.Callback int mDensity; // The density to use when inflating/looking up the children drawables. A value of 0 means + // use the system's density. int mSrcDensityOverride = 0; diff --git a/identity/java/android/security/identity/Util.java b/identity/java/android/security/identity/Util.java index e56bd5167906..789ff06f064b 100644 --- a/identity/java/android/security/identity/Util.java +++ b/identity/java/android/security/identity/Util.java @@ -20,12 +20,12 @@ import android.annotation.NonNull; import java.io.ByteArrayOutputStream; import java.io.IOException; +import java.math.BigInteger; import java.security.InvalidKeyException; import java.security.NoSuchAlgorithmException; import java.security.PublicKey; import java.security.interfaces.ECPublicKey; import java.security.spec.ECPoint; -import java.util.Collection; import javax.crypto.Mac; import javax.crypto.spec.SecretKeySpec; @@ -36,15 +36,6 @@ import javax.crypto.spec.SecretKeySpec; public class Util { private static final String TAG = "Util"; - static int[] integerCollectionToArray(Collection<Integer> collection) { - int[] result = new int[collection.size()]; - int n = 0; - for (int item : collection) { - result[n++] = item; - } - return result; - } - static byte[] stripLeadingZeroes(byte[] value) { int n = 0; while (n < value.length && value[n] == 0) { @@ -61,15 +52,47 @@ public class Util { static byte[] publicKeyEncodeUncompressedForm(PublicKey publicKey) { ECPoint w = ((ECPublicKey) publicKey).getW(); - // X and Y are always positive so for interop we remove any leading zeroes - // inserted by the BigInteger encoder. - byte[] x = stripLeadingZeroes(w.getAffineX().toByteArray()); - byte[] y = stripLeadingZeroes(w.getAffineY().toByteArray()); + BigInteger x = w.getAffineX(); + BigInteger y = w.getAffineY(); + if (x.compareTo(BigInteger.ZERO) < 0) { + throw new RuntimeException("X is negative"); + } + if (y.compareTo(BigInteger.ZERO) < 0) { + throw new RuntimeException("Y is negative"); + } try { ByteArrayOutputStream baos = new ByteArrayOutputStream(); baos.write(0x04); - baos.write(x); - baos.write(y); + + // Each coordinate may be encoded in 33*, 32, or fewer bytes. + // + // * : it can be 33 bytes because toByteArray() guarantees "The array will contain the + // minimum number of bytes required to represent this BigInteger, including at + // least one sign bit, which is (ceil((this.bitLength() + 1)/8))" which means that + // the MSB is always 0x00. This is taken care of by calling calling + // stripLeadingZeroes(). + // + // We need the encoding to be exactly 32 bytes since according to RFC 5480 section 2.2 + // and SEC 1: Elliptic Curve Cryptography section 2.3.3 the encoding is 0x04 | X | Y + // where X and Y are encoded in exactly 32 byte, big endian integer values each. + // + byte[] xBytes = stripLeadingZeroes(x.toByteArray()); + if (xBytes.length > 32) { + throw new RuntimeException("xBytes is " + xBytes.length + " which is unexpected"); + } + for (int n = 0; n < 32 - xBytes.length; n++) { + baos.write(0x00); + } + baos.write(xBytes); + + byte[] yBytes = stripLeadingZeroes(y.toByteArray()); + if (yBytes.length > 32) { + throw new RuntimeException("yBytes is " + yBytes.length + " which is unexpected"); + } + for (int n = 0; n < 32 - yBytes.length; n++) { + baos.write(0x00); + } + baos.write(yBytes); return baos.toByteArray(); } catch (IOException e) { throw new RuntimeException("Unexpected IOException", e); diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java index b3de18308c1f..1155ea174ed1 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java @@ -62,6 +62,7 @@ import android.content.res.Configuration; import android.graphics.Rect; import android.os.RemoteException; import android.os.SystemClock; +import android.util.Log; import android.view.Display; import android.view.Surface; import android.view.SurfaceControl; @@ -930,6 +931,12 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener, /** Called when exiting PIP transition is finished to do the state cleanup. */ void onExitPipFinished(TaskInfo info) { + if (mLeash == null) { + // TODO(239461594): Remove once the double call to onExitPipFinished() is fixed + Log.w(TAG, "Warning, onExitPipFinished() called multiple times in the same sessino"); + return; + } + clearWaitForFixedRotation(); if (mSwipePipToHomeOverlay != null) { removeContentOverlay(mSwipePipToHomeOverlay, null /* callback */); diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java index 53ec39d954c4..63f55c49851b 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java @@ -352,41 +352,31 @@ public class SplitScreenController implements DragAndDropPolicy.Starter, public void startIntent(PendingIntent intent, @Nullable Intent fillInIntent, @SplitPosition int position, @Nullable Bundle options) { + if (fillInIntent == null) { + fillInIntent = new Intent(); + } + // Flag this as a no-user-action launch to prevent sending user leaving event to the + // current top activity since it's going to be put into another side of the split. This + // prevents the current top activity from going into pip mode due to user leaving event. + fillInIntent.addFlags(FLAG_ACTIVITY_NO_USER_ACTION); + + // Flag with MULTIPLE_TASK if this is launching the same activity into both sides of the + // split. + if (isLaunchingAdjacently(intent.getIntent(), position)) { + fillInIntent.addFlags(FLAG_ACTIVITY_MULTIPLE_TASK); + ProtoLog.v(ShellProtoLogGroup.WM_SHELL_SPLIT_SCREEN, "Adding MULTIPLE_TASK"); + } + if (!ENABLE_SHELL_TRANSITIONS) { startIntentLegacy(intent, fillInIntent, position, options); return; } - try { - options = mStageCoordinator.resolveStartStage(STAGE_TYPE_UNDEFINED, position, options, - null /* wct */); - - if (fillInIntent == null) { - fillInIntent = new Intent(); - } - // Flag this as a no-user-action launch to prevent sending user leaving event to the - // current top activity since it's going to be put into another side of the split. This - // prevents the current top activity from going into pip mode due to user leaving event. - fillInIntent.addFlags(FLAG_ACTIVITY_NO_USER_ACTION); - - // Flag with MULTIPLE_TASK if this is launching the same activity into both sides of the - // split. - if (isLaunchingAdjacently(intent.getIntent(), position)) { - fillInIntent.addFlags(FLAG_ACTIVITY_MULTIPLE_TASK); - ProtoLog.v(ShellProtoLogGroup.WM_SHELL_SPLIT_SCREEN, "Adding MULTIPLE_TASK"); - } - - intent.send(mContext, 0, fillInIntent, null /* onFinished */, null /* handler */, - null /* requiredPermission */, options); - } catch (PendingIntent.CanceledException e) { - Slog.e(TAG, "Failed to launch task", e); - } + mStageCoordinator.startIntent(intent, fillInIntent, position, options); } - private void startIntentLegacy(PendingIntent intent, @Nullable Intent fillInIntent, + private void startIntentLegacy(PendingIntent intent, Intent fillInIntent, @SplitPosition int position, @Nullable Bundle options) { - boolean startSameActivityAdjacently = isLaunchingAdjacently(intent.getIntent(), position); - final WindowContainerTransaction evictWct = new WindowContainerTransaction(); mStageCoordinator.prepareEvictChildTasks(position, evictWct); @@ -397,8 +387,8 @@ public class SplitScreenController implements DragAndDropPolicy.Starter, IRemoteAnimationFinishedCallback finishedCallback, SurfaceControl.Transaction t) { if (apps == null || apps.length == 0) { - if (startSameActivityAdjacently) { - // Switch split position if dragging the same activity to another side. + // Switch the split position if launching as MULTIPLE_TASK failed. + if ((fillInIntent.getFlags() & FLAG_ACTIVITY_MULTIPLE_TASK) != 0) { setSideStagePosition(SplitLayout.reversePosition( mStageCoordinator.getSideStagePosition())); } @@ -432,18 +422,6 @@ public class SplitScreenController implements DragAndDropPolicy.Starter, final WindowContainerTransaction wct = new WindowContainerTransaction(); options = mStageCoordinator.resolveStartStage(STAGE_TYPE_UNDEFINED, position, options, wct); - // Flag this as a no-user-action launch to prevent sending user leaving event to the current - // top activity since it's going to be put into another side of the split. This prevents the - // current top activity from going into pip mode due to user leaving event. - if (fillInIntent == null) { - fillInIntent = new Intent(); - } - fillInIntent.addFlags(FLAG_ACTIVITY_NO_USER_ACTION); - if (startSameActivityAdjacently) { - fillInIntent.addFlags(FLAG_ACTIVITY_MULTIPLE_TASK); - ProtoLog.v(ShellProtoLogGroup.WM_SHELL_SPLIT_SCREEN, "Adding MULTIPLE_TASK"); - } - wct.sendPendingIntent(intent, fillInIntent, options); mSyncQueue.queue(transition, WindowManager.TRANSIT_OPEN, wct); } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenTransitions.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenTransitions.java index 056cd5813861..83bdf8bfb727 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenTransitions.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenTransitions.java @@ -62,13 +62,12 @@ class SplitScreenTransitions { private final Runnable mOnFinish; DismissTransition mPendingDismiss = null; - IBinder mPendingEnter = null; - IBinder mPendingRecent = null; + TransitSession mPendingEnter = null; + TransitSession mPendingRecent = null; private IBinder mAnimatingTransition = null; OneShotRemoteHandler mPendingRemoteHandler = null; private OneShotRemoteHandler mActiveRemoteHandler = null; - private boolean mEnterTransitionMerged; private final Transitions.TransitionFinishCallback mRemoteFinishCB = this::onFinish; @@ -145,7 +144,7 @@ class SplitScreenTransitions { continue; } - if (transition == mPendingEnter && (mainRoot.equals(change.getContainer()) + if (isPendingEnter(transition) && (mainRoot.equals(change.getContainer()) || sideRoot.equals(change.getContainer()))) { t.setPosition(leash, change.getEndAbsBounds().left, change.getEndAbsBounds().top); t.setWindowCrop(leash, change.getEndAbsBounds().width(), @@ -171,12 +170,40 @@ class SplitScreenTransitions { onFinish(null /* wct */, null /* wctCB */); } + boolean isPendingTransition(IBinder transition) { + return isPendingEnter(transition) + || isPendingDismiss(transition) + || isPendingRecent(transition); + } + + boolean isPendingEnter(IBinder transition) { + return mPendingEnter != null && mPendingEnter.mTransition == transition; + } + + boolean isPendingRecent(IBinder transition) { + return mPendingRecent != null && mPendingRecent.mTransition == transition; + } + + boolean isPendingDismiss(IBinder transition) { + return mPendingDismiss != null && mPendingDismiss.mTransition == transition; + } + /** Starts a transition to enter split with a remote transition animator. */ - IBinder startEnterTransition(@WindowManager.TransitionType int transitType, - @NonNull WindowContainerTransaction wct, @Nullable RemoteTransition remoteTransition, - @NonNull Transitions.TransitionHandler handler) { + IBinder startEnterTransition( + @WindowManager.TransitionType int transitType, + WindowContainerTransaction wct, + @Nullable RemoteTransition remoteTransition, + Transitions.TransitionHandler handler, + @Nullable TransitionCallback callback) { final IBinder transition = mTransitions.startTransition(transitType, wct, handler); - mPendingEnter = transition; + setEnterTransition(transition, remoteTransition, callback); + return transition; + } + + /** Sets a transition to enter split. */ + void setEnterTransition(@NonNull IBinder transition, + @Nullable RemoteTransition remoteTransition, @Nullable TransitionCallback callback) { + mPendingEnter = new TransitSession(transition, callback); if (remoteTransition != null) { // Wrapping it for ease-of-use (OneShot handles all the binder linking/death stuff) @@ -184,7 +211,9 @@ class SplitScreenTransitions { mTransitions.getMainExecutor(), remoteTransition); mPendingRemoteHandler.setTransition(transition); } - return transition; + + ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, " splitTransition " + + " deduced Enter split screen"); } /** Starts a transition to dismiss split. */ @@ -209,8 +238,8 @@ class SplitScreenTransitions { } void setRecentTransition(@NonNull IBinder transition, - @Nullable RemoteTransition remoteTransition) { - mPendingRecent = transition; + @Nullable RemoteTransition remoteTransition, @Nullable TransitionCallback callback) { + mPendingRecent = new TransitSession(transition, callback); if (remoteTransition != null) { // Wrapping it for ease-of-use (OneShot handles all the binder linking/death stuff) @@ -226,6 +255,18 @@ class SplitScreenTransitions { void mergeAnimation(IBinder transition, TransitionInfo info, SurfaceControl.Transaction t, IBinder mergeTarget, Transitions.TransitionFinishCallback finishCallback) { if (mergeTarget != mAnimatingTransition) return; + + if (isPendingEnter(transition) && isPendingRecent(mergeTarget)) { + mPendingRecent.mCallback = new TransitionCallback() { + @Override + public void onTransitionFinished(WindowContainerTransaction finishWct, + SurfaceControl.Transaction finishT) { + // Since there's an entering transition merged, recent transition no longer + // need to handle entering split screen after the transition finished. + } + }; + } + if (mActiveRemoteHandler != null) { mActiveRemoteHandler.mergeAnimation(transition, info, t, mergeTarget, finishCallback); } else { @@ -247,38 +288,55 @@ class SplitScreenTransitions { } void onTransitionConsumed(@NonNull IBinder transition, boolean aborted) { - if (aborted) return; + if (isPendingEnter(transition)) { + if (!aborted) { + // An enter transition got merged, appends the rest operations to finish entering + // split screen. + // TODO (b/238856352): Passed-in the proper finish transition to merge instead. + if (mFinishTransaction == null) { + mFinishTransaction = mTransactionPool.acquire(); + } + mStageCoordinator.finishEnterSplitScreen(mFinishTransaction); + } - // Once a pending enter transition got merged, make sure to append the reset of finishing - // operations to the finish transition. - if (transition == mPendingEnter) { - mFinishTransaction = mTransactionPool.acquire(); - mStageCoordinator.finishEnterSplitScreen(mFinishTransaction); + mPendingEnter.mCallback.onTransitionConsumed(aborted); mPendingEnter = null; mPendingRemoteHandler = null; - mEnterTransitionMerged = true; + } else if (isPendingDismiss(transition)) { + mPendingDismiss.mCallback.onTransitionConsumed(aborted); + mPendingDismiss = null; + } else if (isPendingRecent(transition)) { + mPendingRecent.mCallback.onTransitionConsumed(aborted); + mPendingRecent = null; + mPendingRemoteHandler = null; } } void onFinish(WindowContainerTransaction wct, WindowContainerTransactionCallback wctCB) { if (!mAnimations.isEmpty()) return; - if (mAnimatingTransition == mPendingEnter) { + + TransitionCallback callback = null; + if (isPendingEnter(mAnimatingTransition)) { + callback = mPendingEnter.mCallback; mPendingEnter = null; } - if (mPendingDismiss != null && mPendingDismiss.mTransition == mAnimatingTransition) { + if (isPendingDismiss(mAnimatingTransition)) { + callback = mPendingDismiss.mCallback; mPendingDismiss = null; } - if (mAnimatingTransition == mPendingRecent) { - if (!mEnterTransitionMerged) { - if (wct == null) wct = new WindowContainerTransaction(); - mStageCoordinator.onRecentTransitionFinished(wct, mFinishTransaction); - } + if (isPendingRecent(mAnimatingTransition)) { + callback = mPendingRecent.mCallback; mPendingRecent = null; } + + if (callback != null) { + if (wct == null) wct = new WindowContainerTransaction(); + callback.onTransitionFinished(wct, mFinishTransaction); + } + mPendingRemoteHandler = null; mActiveRemoteHandler = null; mAnimatingTransition = null; - mEnterTransitionMerged = false; mOnFinish.run(); if (mFinishTransaction != null) { @@ -382,17 +440,34 @@ class SplitScreenTransitions { || info.getType() == TRANSIT_SPLIT_SCREEN_PAIR_OPEN; } - /** Bundled information of dismiss transition. */ - static class DismissTransition { - IBinder mTransition; + /** Clean-up callbacks for transition. */ + interface TransitionCallback { + /** Calls when the transition got consumed. */ + default void onTransitionConsumed(boolean aborted) {} + + /** Calls when the transition finished. */ + default void onTransitionFinished(WindowContainerTransaction finishWct, + SurfaceControl.Transaction finishT) {} + } - int mReason; + /** Session for a transition and its clean-up callback. */ + static class TransitSession { + final IBinder mTransition; + TransitionCallback mCallback; - @SplitScreen.StageType - int mDismissTop; + TransitSession(IBinder transition, @Nullable TransitionCallback callback) { + mTransition = transition; + mCallback = callback != null ? callback : new TransitionCallback() {}; + } + } + + /** Bundled information of dismiss transition. */ + static class DismissTransition extends TransitSession { + final int mReason; + final @SplitScreen.StageType int mDismissTop; DismissTransition(IBinder transition, int reason, int dismissTop) { - this.mTransition = transition; + super(transition, null /* callback */); this.mReason = reason; this.mDismissTop = dismissTop; } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java index 2229e26b9343..b33a2829bee4 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java @@ -24,6 +24,7 @@ import static android.app.WindowConfiguration.ACTIVITY_TYPE_ASSISTANT; import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME; import static android.app.WindowConfiguration.ACTIVITY_TYPE_RECENTS; import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN; +import static android.content.Intent.FLAG_ACTIVITY_MULTIPLE_TASK; import static android.view.Display.DEFAULT_DISPLAY; import static android.view.WindowManager.LayoutParams.TYPE_DOCK_DIVIDER; import static android.view.WindowManager.TRANSIT_CHANGE; @@ -214,6 +215,33 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, } }; + private final SplitScreenTransitions.TransitionCallback mRecentTransitionCallback = + new SplitScreenTransitions.TransitionCallback() { + @Override + public void onTransitionFinished(WindowContainerTransaction finishWct, + SurfaceControl.Transaction finishT) { + // Check if the recent transition is finished by returning to the current split, so we + // can restore the divider bar. + for (int i = 0; i < finishWct.getHierarchyOps().size(); ++i) { + final WindowContainerTransaction.HierarchyOp op = + finishWct.getHierarchyOps().get(i); + final IBinder container = op.getContainer(); + if (op.getType() == HIERARCHY_OP_TYPE_REORDER && op.getToTop() + && (mMainStage.containsContainer(container) + || mSideStage.containsContainer(container))) { + setDividerVisibility(true, finishT); + return; + } + } + + // Dismiss the split screen if it's not returning to split. + prepareExitSplitScreen(STAGE_TYPE_UNDEFINED, finishWct); + setSplitsVisible(false); + setDividerVisibility(false, finishT); + logExit(EXIT_REASON_UNKNOWN); + } + }; + StageCoordinator(Context context, int displayId, SyncTransactionQueue syncQueue, ShellTaskOrganizer taskOrganizer, DisplayController displayController, DisplayImeController displayImeController, @@ -337,15 +365,23 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, final WindowContainerTransaction evictWct = new WindowContainerTransaction(); targetStage.evictAllChildren(evictWct); targetStage.addTask(task, wct); - if (!evictWct.isEmpty()) { - wct.merge(evictWct, true /* transfer */); - } if (ENABLE_SHELL_TRANSITIONS) { prepareEnterSplitScreen(wct); - mSplitTransitions.startEnterTransition(TRANSIT_SPLIT_SCREEN_OPEN_TO_SIDE, - wct, null, this); + mSplitTransitions.startEnterTransition(TRANSIT_SPLIT_SCREEN_OPEN_TO_SIDE, wct, + null, this, new SplitScreenTransitions.TransitionCallback() { + @Override + public void onTransitionFinished(WindowContainerTransaction finishWct, + SurfaceControl.Transaction finishT) { + if (!evictWct.isEmpty()) { + finishWct.merge(evictWct, true); + } + } + }); } else { + if (!evictWct.isEmpty()) { + wct.merge(evictWct, true /* transfer */); + } mTaskOrganizer.applyTransaction(wct); } return true; @@ -365,6 +401,39 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, return result; } + /** Launches an activity into split. */ + void startIntent(PendingIntent intent, Intent fillInIntent, @SplitPosition int position, + @Nullable Bundle options) { + final WindowContainerTransaction wct = new WindowContainerTransaction(); + final WindowContainerTransaction evictWct = new WindowContainerTransaction(); + prepareEvictChildTasks(position, evictWct); + + options = resolveStartStage(STAGE_TYPE_UNDEFINED, position, options, null /* wct */); + wct.sendPendingIntent(intent, fillInIntent, options); + prepareEnterSplitScreen(wct, null /* taskInfo */, position); + + mSplitTransitions.startEnterTransition(TRANSIT_SPLIT_SCREEN_OPEN_TO_SIDE, wct, null, this, + new SplitScreenTransitions.TransitionCallback() { + @Override + public void onTransitionConsumed(boolean aborted) { + // Switch the split position if launching as MULTIPLE_TASK failed. + if (aborted + && (fillInIntent.getFlags() & FLAG_ACTIVITY_MULTIPLE_TASK) != 0) { + setSideStagePositionAnimated( + SplitLayout.reversePosition(mSideStagePosition)); + } + } + + @Override + public void onTransitionFinished(WindowContainerTransaction finishWct, + SurfaceControl.Transaction finishT) { + if (!evictWct.isEmpty()) { + finishWct.merge(evictWct, true); + } + } + }); + } + /** Starts 2 tasks in one transition. */ void startTasks(int mainTaskId, @Nullable Bundle mainOptions, int sideTaskId, @Nullable Bundle sideOptions, @SplitPosition int sidePosition, float splitRatio, @@ -395,7 +464,7 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, wct.startTask(sideTaskId, sideOptions); mSplitTransitions.startEnterTransition( - TRANSIT_SPLIT_SCREEN_PAIR_OPEN, wct, remoteTransition, this); + TRANSIT_SPLIT_SCREEN_PAIR_OPEN, wct, remoteTransition, this, null); } /** Starts 2 tasks in one legacy transition. */ @@ -617,11 +686,13 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, } int getTaskId(@SplitPosition int splitPosition) { - if (mSideStagePosition == splitPosition) { - return mSideStage.getTopVisibleChildTaskId(); - } else { - return mMainStage.getTopVisibleChildTaskId(); + if (splitPosition == SPLIT_POSITION_UNDEFINED) { + return INVALID_TASK_ID; } + + return mSideStagePosition == splitPosition + ? mSideStage.getTopVisibleChildTaskId() + : mMainStage.getTopVisibleChildTaskId(); } void setSideStagePositionAnimated(@SplitPosition int sideStagePosition) { @@ -861,6 +932,7 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, mSplitLayout.init(); setDividerVisibility(true, t); updateSurfaceBounds(mSplitLayout, t, false /* applyResizingOffset */); + t.show(mRootTaskLeash); setSplitsVisible(true); mShouldUpdateRecents = true; updateRecentTasksSplitPair(); @@ -1543,14 +1615,14 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, } else if (activityType == ACTIVITY_TYPE_HOME || activityType == ACTIVITY_TYPE_RECENTS) { // Enter overview panel, so start recent transition. - mSplitTransitions.setRecentTransition(transition, - request.getRemoteTransition()); + mSplitTransitions.setRecentTransition(transition, request.getRemoteTransition(), + mRecentTransitionCallback); } else if (mSplitTransitions.mPendingRecent == null) { // If split-task is not controlled by recents animation // and occluded by the other fullscreen task, dismiss both. prepareExitSplitScreen(STAGE_TYPE_UNDEFINED, out); - mSplitTransitions.setDismissTransition(transition, - STAGE_TYPE_UNDEFINED, EXIT_REASON_UNKNOWN); + mSplitTransitions.setDismissTransition( + transition, STAGE_TYPE_UNDEFINED, EXIT_REASON_UNKNOWN); } } } else { @@ -1558,7 +1630,8 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, // One task is appearing into split, prepare to enter split screen. out = new WindowContainerTransaction(); prepareEnterSplitScreen(out); - mSplitTransitions.mPendingEnter = transition; + mSplitTransitions.setEnterTransition( + transition, request.getRemoteTransition(), null /* callback */); } } return out; @@ -1614,10 +1687,7 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, @NonNull SurfaceControl.Transaction startTransaction, @NonNull SurfaceControl.Transaction finishTransaction, @NonNull Transitions.TransitionFinishCallback finishCallback) { - if (transition != mSplitTransitions.mPendingEnter - && transition != mSplitTransitions.mPendingRecent - && (mSplitTransitions.mPendingDismiss == null - || mSplitTransitions.mPendingDismiss.mTransition != transition)) { + if (!mSplitTransitions.isPendingTransition(transition)) { // Not entering or exiting, so just do some house-keeping and validation. // If we're not in split-mode, just abort so something else can handle it. @@ -1664,12 +1734,11 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, } boolean shouldAnimate = true; - if (mSplitTransitions.mPendingEnter == transition) { + if (mSplitTransitions.isPendingEnter(transition)) { shouldAnimate = startPendingEnterAnimation(transition, info, startTransaction); - } else if (mSplitTransitions.mPendingRecent == transition) { + } else if (mSplitTransitions.isPendingRecent(transition)) { shouldAnimate = startPendingRecentAnimation(transition, info, startTransaction); - } else if (mSplitTransitions.mPendingDismiss != null - && mSplitTransitions.mPendingDismiss.mTransition == transition) { + } else if (mSplitTransitions.isPendingDismiss(transition)) { shouldAnimate = startPendingDismissAnimation( mSplitTransitions.mPendingDismiss, info, startTransaction, finishTransaction); } @@ -1837,28 +1906,6 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, return true; } - void onRecentTransitionFinished(WindowContainerTransaction wct, - SurfaceControl.Transaction finishT) { - // Check if the recent transition is finished by returning to the current split so we can - // restore the divider bar. - for (int i = 0; i < wct.getHierarchyOps().size(); ++i) { - final WindowContainerTransaction.HierarchyOp op = wct.getHierarchyOps().get(i); - final IBinder container = op.getContainer(); - if (op.getType() == HIERARCHY_OP_TYPE_REORDER && op.getToTop() - && (mMainStage.containsContainer(container) - || mSideStage.containsContainer(container))) { - setDividerVisibility(true, finishT); - return; - } - } - - // Dismiss the split screen is it's not returning to split. - prepareExitSplitScreen(STAGE_TYPE_UNDEFINED, wct); - setSplitsVisible(false); - setDividerVisibility(false, finishT); - logExit(EXIT_REASON_UNKNOWN); - } - private void addDividerBarToTransition(@NonNull TransitionInfo info, @NonNull SurfaceControl.Transaction t, boolean show) { final SurfaceControl leash = mSplitLayout.getDividerLeash(); diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipTaskOrganizerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipTaskOrganizerTest.java index b351f8fcf838..857f578fd8ed 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipTaskOrganizerTest.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipTaskOrganizerTest.java @@ -42,6 +42,7 @@ import android.testing.TestableLooper; import android.util.Rational; import android.util.Size; import android.view.DisplayInfo; +import android.view.SurfaceControl; import android.window.WindowContainerToken; import com.android.wm.shell.MockSurfaceControlHelper; @@ -150,7 +151,7 @@ public class PipTaskOrganizerTest extends ShellTestCase { final Rational aspectRatio = new Rational(2, 1); mSpiedPipTaskOrganizer.onTaskAppeared(createTaskInfo(mComponent1, - createPipParams(aspectRatio)), null /* leash */); + createPipParams(aspectRatio)), mock(SurfaceControl.class)); assertEquals(aspectRatio.floatValue(), mPipBoundsState.getAspectRatio(), 0.01f); } @@ -158,7 +159,7 @@ public class PipTaskOrganizerTest extends ShellTestCase { @Test public void onTaskAppeared_updatesLastPipComponentName() { mSpiedPipTaskOrganizer.onTaskAppeared(createTaskInfo(mComponent1, createPipParams(null)), - null /* leash */); + mock(SurfaceControl.class)); assertEquals(mComponent1, mPipBoundsState.getLastPipComponentName()); } @@ -169,7 +170,7 @@ public class PipTaskOrganizerTest extends ShellTestCase { mSpiedPipTaskOrganizer.onTaskAppeared( createTaskInfo(mComponent1, createPipParams(null), minSize), - null /* leash */); + mock(SurfaceControl.class)); assertEquals(minSize, mPipBoundsState.getOverrideMinSize()); } @@ -179,7 +180,7 @@ public class PipTaskOrganizerTest extends ShellTestCase { final Rational startAspectRatio = new Rational(2, 1); final Rational newAspectRatio = new Rational(1, 2); mSpiedPipTaskOrganizer.onTaskAppeared(createTaskInfo(mComponent1, - createPipParams(startAspectRatio)), null /* leash */); + createPipParams(startAspectRatio)), mock(SurfaceControl.class)); // It is in entering transition, should defer onTaskInfoChanged callback in this case. mSpiedPipTaskOrganizer.onTaskInfoChanged(createTaskInfo(mComponent1, @@ -197,7 +198,7 @@ public class PipTaskOrganizerTest extends ShellTestCase { final Rational startAspectRatio = new Rational(2, 1); final Rational newAspectRatio = new Rational(1, 2); mSpiedPipTaskOrganizer.onTaskAppeared(createTaskInfo(mComponent1, - createPipParams(startAspectRatio)), null /* leash */); + createPipParams(startAspectRatio)), mock(SurfaceControl.class)); mSpiedPipTaskOrganizer.sendOnPipTransitionFinished(TRANSITION_DIRECTION_TO_PIP); mSpiedPipTaskOrganizer.onTaskInfoChanged(createTaskInfo(mComponent1, @@ -210,7 +211,7 @@ public class PipTaskOrganizerTest extends ShellTestCase { @Test public void onTaskInfoChanged_inPip_updatesLastPipComponentName() { mSpiedPipTaskOrganizer.onTaskAppeared(createTaskInfo(mComponent1, - createPipParams(null)), null /* leash */); + createPipParams(null)), mock(SurfaceControl.class)); mSpiedPipTaskOrganizer.sendOnPipTransitionFinished(TRANSITION_DIRECTION_TO_PIP); mSpiedPipTaskOrganizer.onTaskInfoChanged(createTaskInfo(mComponent2, @@ -222,7 +223,7 @@ public class PipTaskOrganizerTest extends ShellTestCase { @Test public void onTaskInfoChanged_inPip_updatesOverrideMinSize() { mSpiedPipTaskOrganizer.onTaskAppeared(createTaskInfo(mComponent1, - createPipParams(null)), null /* leash */); + createPipParams(null)), mock(SurfaceControl.class)); mSpiedPipTaskOrganizer.sendOnPipTransitionFinished(TRANSITION_DIRECTION_TO_PIP); final Size minSize = new Size(400, 320); @@ -235,7 +236,7 @@ public class PipTaskOrganizerTest extends ShellTestCase { @Test public void onTaskVanished_clearsPipBounds() { mSpiedPipTaskOrganizer.onTaskAppeared(createTaskInfo(mComponent1, - createPipParams(null)), null /* leash */); + createPipParams(null)), mock(SurfaceControl.class)); mPipBoundsState.setBounds(new Rect(100, 100, 200, 150)); mSpiedPipTaskOrganizer.onTaskVanished(createTaskInfo(mComponent1, createPipParams(null))); diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitTransitionTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitTransitionTests.java index 304ca66dd3bb..1d038f4ee377 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitTransitionTests.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitTransitionTests.java @@ -182,7 +182,7 @@ public class SplitTransitionTests extends ShellTestCase { IBinder transition = mSplitScreenTransitions.startEnterTransition( TRANSIT_SPLIT_SCREEN_PAIR_OPEN, new WindowContainerTransaction(), - new RemoteTransition(testRemote), mStageCoordinator); + new RemoteTransition(testRemote), mStageCoordinator, null); mMainStage.onTaskAppeared(mMainChild, createMockSurface()); mSideStage.onTaskAppeared(mSideChild, createMockSurface()); boolean accepted = mStageCoordinator.startAnimation(transition, info, @@ -422,7 +422,7 @@ public class SplitTransitionTests extends ShellTestCase { TransitionInfo enterInfo = createEnterPairInfo(); IBinder enterTransit = mSplitScreenTransitions.startEnterTransition( TRANSIT_SPLIT_SCREEN_PAIR_OPEN, new WindowContainerTransaction(), - new RemoteTransition(new TestRemoteTransition()), mStageCoordinator); + new RemoteTransition(new TestRemoteTransition()), mStageCoordinator, null); mMainStage.onTaskAppeared(mMainChild, createMockSurface()); mSideStage.onTaskAppeared(mSideChild, createMockSurface()); mStageCoordinator.startAnimation(enterTransit, enterInfo, diff --git a/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java b/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java index ce331607c531..d1f10a640dbf 100644 --- a/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java +++ b/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java @@ -118,6 +118,10 @@ public class SecureSettings { Settings.Secure.FACE_UNLOCK_DISMISSES_KEYGUARD, Settings.Secure.FACE_UNLOCK_APP_ENABLED, Settings.Secure.FACE_UNLOCK_ALWAYS_REQUIRE_CONFIRMATION, + Settings.Secure.FINGERPRINT_SIDE_FPS_KG_POWER_WINDOW, + Settings.Secure.FINGERPRINT_SIDE_FPS_BP_POWER_WINDOW, + Settings.Secure.FINGERPRINT_SIDE_FPS_ENROLL_TAP_WINDOW, + Settings.Secure.FINGERPRINT_SIDE_FPS_AUTH_DOWNTIME, Settings.Secure.ACTIVE_UNLOCK_ON_WAKE, Settings.Secure.ACTIVE_UNLOCK_ON_UNLOCK_INTENT, Settings.Secure.ACTIVE_UNLOCK_ON_BIOMETRIC_FAIL, diff --git a/packages/SettingsProvider/src/android/provider/settings/validators/GlobalSettingsValidators.java b/packages/SettingsProvider/src/android/provider/settings/validators/GlobalSettingsValidators.java index e82bf0427509..fbbdd32c5886 100644 --- a/packages/SettingsProvider/src/android/provider/settings/validators/GlobalSettingsValidators.java +++ b/packages/SettingsProvider/src/android/provider/settings/validators/GlobalSettingsValidators.java @@ -51,6 +51,7 @@ public class GlobalSettingsValidators { || (val == BatteryManager.BATTERY_PLUGGED_AC) || (val == BatteryManager.BATTERY_PLUGGED_USB) || (val == BatteryManager.BATTERY_PLUGGED_WIRELESS) + || (val == BatteryManager.BATTERY_PLUGGED_DOCK) || (val == (BatteryManager.BATTERY_PLUGGED_AC | BatteryManager.BATTERY_PLUGGED_USB)) @@ -58,12 +59,29 @@ public class GlobalSettingsValidators { == (BatteryManager.BATTERY_PLUGGED_AC | BatteryManager.BATTERY_PLUGGED_WIRELESS)) || (val + == (BatteryManager.BATTERY_PLUGGED_AC + | BatteryManager.BATTERY_PLUGGED_DOCK)) + || (val == (BatteryManager.BATTERY_PLUGGED_USB | BatteryManager.BATTERY_PLUGGED_WIRELESS)) || (val + == (BatteryManager.BATTERY_PLUGGED_USB + | BatteryManager.BATTERY_PLUGGED_DOCK)) + || (val + == (BatteryManager.BATTERY_PLUGGED_WIRELESS + | BatteryManager.BATTERY_PLUGGED_DOCK)) + || (val == (BatteryManager.BATTERY_PLUGGED_AC | BatteryManager.BATTERY_PLUGGED_USB - | BatteryManager.BATTERY_PLUGGED_WIRELESS)); + | BatteryManager.BATTERY_PLUGGED_WIRELESS)) + || (val + == (BatteryManager.BATTERY_PLUGGED_AC + | BatteryManager.BATTERY_PLUGGED_USB + | BatteryManager.BATTERY_PLUGGED_DOCK)) + || (val + == (BatteryManager.BATTERY_PLUGGED_USB + | BatteryManager.BATTERY_PLUGGED_WIRELESS + | BatteryManager.BATTERY_PLUGGED_DOCK)); } catch (NumberFormatException e) { return false; } diff --git a/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java b/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java index 5d773789b206..4aadf72930aa 100644 --- a/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java +++ b/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java @@ -172,6 +172,11 @@ public class SecureSettingsValidators { Secure.AUTOMATIC_STORAGE_MANAGER_DAYS_TO_RETAIN, NON_NEGATIVE_INTEGER_VALIDATOR); VALIDATORS.put(Secure.FACE_UNLOCK_KEYGUARD_ENABLED, BOOLEAN_VALIDATOR); VALIDATORS.put(Secure.FACE_UNLOCK_DISMISSES_KEYGUARD, BOOLEAN_VALIDATOR); + VALIDATORS.put(Secure.FINGERPRINT_SIDE_FPS_KG_POWER_WINDOW, NON_NEGATIVE_INTEGER_VALIDATOR); + VALIDATORS.put(Secure.FINGERPRINT_SIDE_FPS_BP_POWER_WINDOW, NON_NEGATIVE_INTEGER_VALIDATOR); + VALIDATORS.put(Secure.FINGERPRINT_SIDE_FPS_ENROLL_TAP_WINDOW, + NON_NEGATIVE_INTEGER_VALIDATOR); + VALIDATORS.put(Secure.FINGERPRINT_SIDE_FPS_AUTH_DOWNTIME, NON_NEGATIVE_INTEGER_VALIDATOR); VALIDATORS.put(Secure.SHOW_MEDIA_WHEN_BYPASSING, BOOLEAN_VALIDATOR); VALIDATORS.put(Secure.FACE_UNLOCK_APP_ENABLED, BOOLEAN_VALIDATOR); VALIDATORS.put(Secure.FACE_UNLOCK_ALWAYS_REQUIRE_CONFIRMATION, BOOLEAN_VALIDATOR); diff --git a/packages/SystemUI/src/com/android/keyguard/FontInterpolator.kt b/packages/SystemUI/animation/src/com/android/systemui/animation/FontInterpolator.kt index 962c0023cf3b..3d341af3b397 100644 --- a/packages/SystemUI/src/com/android/keyguard/FontInterpolator.kt +++ b/packages/SystemUI/animation/src/com/android/systemui/animation/FontInterpolator.kt @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.android.keyguard +package com.android.systemui.animation import android.graphics.fonts.Font import android.graphics.fonts.FontVariationAxis @@ -61,7 +61,7 @@ class FontInterpolator { var index: Int, val sortedAxes: MutableList<FontVariationAxis> ) { - constructor(font: Font, axes: List<FontVariationAxis>): + constructor(font: Font, axes: List<FontVariationAxis>) : this(font.sourceIdentifier, font.ttcIndex, axes.toMutableList().apply { sortBy { it.tag } } diff --git a/packages/SystemUI/src/com/android/keyguard/TextAnimator.kt b/packages/SystemUI/animation/src/com/android/systemui/animation/TextAnimator.kt index ade89af81bd7..f79b328190dd 100644 --- a/packages/SystemUI/src/com/android/keyguard/TextAnimator.kt +++ b/packages/SystemUI/animation/src/com/android/systemui/animation/TextAnimator.kt @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.android.keyguard +package com.android.systemui.animation import android.animation.Animator import android.animation.AnimatorListenerAdapter @@ -61,8 +61,8 @@ class TextAnimator( private val invalidateCallback: () -> Unit ) { // Following two members are for mutable for testing purposes. - internal var textInterpolator: TextInterpolator = TextInterpolator(layout) - internal var animator: ValueAnimator = ValueAnimator.ofFloat(1f).apply { + public var textInterpolator: TextInterpolator = TextInterpolator(layout) + public var animator: ValueAnimator = ValueAnimator.ofFloat(1f).apply { duration = DEFAULT_ANIMATION_DURATION addUpdateListener { textInterpolator.progress = it.animatedValue as Float @@ -279,4 +279,4 @@ private fun <V> SparseArray<V>.getOrElse(key: Int, defaultValue: () -> V): V { put(key, v) } return v -}
\ No newline at end of file +} diff --git a/packages/SystemUI/src/com/android/keyguard/TextInterpolator.kt b/packages/SystemUI/animation/src/com/android/systemui/animation/TextInterpolator.kt index 20dbe29efb35..ff64c7891128 100644 --- a/packages/SystemUI/src/com/android/keyguard/TextInterpolator.kt +++ b/packages/SystemUI/animation/src/com/android/systemui/animation/TextInterpolator.kt @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.android.keyguard +package com.android.systemui.animation import android.graphics.Canvas import android.graphics.Paint diff --git a/packages/SystemUI/res-keyguard/layout/keyguard_clock_switch.xml b/packages/SystemUI/res-keyguard/layout/keyguard_clock_switch.xml index 87a9825af1cb..6a38507b2ad7 100644 --- a/packages/SystemUI/res-keyguard/layout/keyguard_clock_switch.xml +++ b/packages/SystemUI/res-keyguard/layout/keyguard_clock_switch.xml @@ -31,7 +31,7 @@ android:layout_alignParentStart="true" android:layout_alignParentTop="true" android:paddingStart="@dimen/clock_padding_start"> - <com.android.keyguard.AnimatableClockView + <com.android.systemui.shared.clocks.AnimatableClockView android:id="@+id/animatable_clock_view" android:layout_width="wrap_content" android:layout_height="wrap_content" @@ -53,7 +53,7 @@ android:layout_height="wrap_content" android:layout_below="@id/keyguard_slice_view" android:visibility="gone"> - <com.android.keyguard.AnimatableClockView + <com.android.systemui.shared.clocks.AnimatableClockView android:id="@+id/animatable_clock_view_large" android:layout_width="wrap_content" android:layout_height="wrap_content" diff --git a/packages/SystemUI/res-keyguard/values/attrs.xml b/packages/SystemUI/res-keyguard/values/attrs.xml index 25be37adac5b..f2a1e26dc9ce 100644 --- a/packages/SystemUI/res-keyguard/values/attrs.xml +++ b/packages/SystemUI/res-keyguard/values/attrs.xml @@ -41,10 +41,4 @@ <attr name="passwordStyle" format="reference" /> <attr name="numPadKeyStyle" format="reference" /> - - <declare-styleable name="AnimatableClockView"> - <attr name="dozeWeight" format="integer" /> - <attr name="lockScreenWeight" format="integer" /> - <attr name="chargeAnimationDelay" format="integer" /> - </declare-styleable> </resources> diff --git a/packages/SystemUI/res-keyguard/values/donottranslate.xml b/packages/SystemUI/res-keyguard/values/donottranslate.xml index e677797dc4a7..f9872f8da192 100644 --- a/packages/SystemUI/res-keyguard/values/donottranslate.xml +++ b/packages/SystemUI/res-keyguard/values/donottranslate.xml @@ -23,11 +23,5 @@ <!-- Skeleton string format for displaying the date shorter. --> <string name="abbrev_month_day_no_year">MMMd</string> - <!-- Skeleton string format for displaying the time in 12-hour format. --> - <string name="clock_12hr_format">hm</string> - - <!-- Skeleton string format for displaying the time in 24-hour format. --> - <string name="clock_24hr_format">Hm</string> - <string name="num_pad_key_ratio">1</string> </resources> diff --git a/packages/SystemUI/res/layout/people_space_activity_no_conversations.xml b/packages/SystemUI/res/layout/people_space_activity_no_conversations.xml index e929169cfe3d..a97c90c5e8ac 100644 --- a/packages/SystemUI/res/layout/people_space_activity_no_conversations.xml +++ b/packages/SystemUI/res/layout/people_space_activity_no_conversations.xml @@ -54,7 +54,6 @@ android:layout_height="wrap_content" android:layout_gravity="center" android:background="@drawable/rounded_bg_full_large_radius" - android:onClick="dismissActivity" android:text="@string/got_it" android:textColor="?androidprv:attr/textColorOnAccent" android:layout_marginBottom="60dp" diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml index 0cafa355d847..0a4c24d0eaa6 100644 --- a/packages/SystemUI/res/values/dimens.xml +++ b/packages/SystemUI/res/values/dimens.xml @@ -59,8 +59,8 @@ <dimen name="navigation_edge_entry_margin">4dp</dimen> <dimen name="navigation_edge_entry_background_width">8dp</dimen> <dimen name="navigation_edge_entry_background_height">60dp</dimen> - <dimen name="navigation_edge_entry_edge_corners">6dp</dimen> - <dimen name="navigation_edge_entry_far_corners">6dp</dimen> + <dimen name="navigation_edge_entry_edge_corners">30dp</dimen> + <dimen name="navigation_edge_entry_far_corners">30dp</dimen> <dimen name="navigation_edge_entry_arrow_length">10dp</dimen> <dimen name="navigation_edge_entry_arrow_height">7dp</dimen> @@ -90,6 +90,7 @@ <dimen name="navigation_edge_cancelled_arrow_length">12dp</dimen> <dimen name="navigation_edge_cancelled_arrow_height">0dp</dimen> + <dimen name="navigation_edge_cancelled_edge_corners">6dp</dimen> <!-- Height of notification icons in the status bar --> <dimen name="status_bar_icon_size">@*android:dimen/status_bar_icon_size</dimen> @@ -898,7 +899,7 @@ <!-- The maximum offset for the under-display fingerprint sensor (UDFPS) icon in either direction that elements are moved to prevent burn-in on AOD--> <dimen name="udfps_burn_in_offset_x">7px</dimen> - <dimen name="udfps_burn_in_offset_y">28px</dimen> + <dimen name="udfps_burn_in_offset_y">20px</dimen> <!-- The absolute side margins of quick settings --> <dimen name="quick_settings_bottom_margin_media">8dp</dimen> diff --git a/packages/SystemUI/shared/Android.bp b/packages/SystemUI/shared/Android.bp index 114ea657a758..165f9eba6f4c 100644 --- a/packages/SystemUI/shared/Android.bp +++ b/packages/SystemUI/shared/Android.bp @@ -47,6 +47,7 @@ android_library { ], static_libs: [ "PluginCoreLib", + "SystemUIAnimationLib", "SystemUIUnfoldLib", "androidx.dynamicanimation_dynamicanimation", "androidx.concurrent_concurrent-futures", diff --git a/packages/SystemUI/shared/res/values/attrs.xml b/packages/SystemUI/shared/res/values/attrs.xml new file mode 100644 index 000000000000..f9d66ee583da --- /dev/null +++ b/packages/SystemUI/shared/res/values/attrs.xml @@ -0,0 +1,28 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- Copyright (C) 2022 The Android Open Source Project + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +--> + +<!-- Formatting note: terminate all comments with a period, to avoid breaking + the documentation output. To suppress comment lines from the documentation + output, insert an eat-comment element after the comment lines. +--> + +<resources> + <declare-styleable name="AnimatableClockView"> + <attr name="dozeWeight" format="integer" /> + <attr name="lockScreenWeight" format="integer" /> + <attr name="chargeAnimationDelay" format="integer" /> + </declare-styleable> +</resources> diff --git a/packages/SystemUI/shared/res/values/donottranslate.xml b/packages/SystemUI/shared/res/values/donottranslate.xml new file mode 100644 index 000000000000..383d5521f156 --- /dev/null +++ b/packages/SystemUI/shared/res/values/donottranslate.xml @@ -0,0 +1,23 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- Copyright (C) 2022 The Android Open Source Project + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +--> + +<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> + <!-- Skeleton string format for displaying the time in 12-hour format. --> + <string name="clock_12hr_format">hm</string> + + <!-- Skeleton string format for displaying the time in 24-hour format. --> + <string name="clock_24hr_format">Hm</string> +</resources> diff --git a/packages/SystemUI/src/com/android/keyguard/AnimatableClockView.kt b/packages/SystemUI/shared/src/com/android/systemui/shared/clocks/AnimatableClockView.kt index b4955d28f84b..5b1a23d4f364 100644 --- a/packages/SystemUI/src/com/android/keyguard/AnimatableClockView.kt +++ b/packages/SystemUI/shared/src/com/android/systemui/shared/clocks/AnimatableClockView.kt @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.android.keyguard +package com.android.systemui.shared.clocks import android.animation.TimeInterpolator import android.annotation.ColorInt @@ -26,9 +26,10 @@ import android.text.TextUtils import android.text.format.DateFormat import android.util.AttributeSet import android.widget.TextView -import com.android.systemui.R +import com.android.systemui.animation.GlyphCallback import com.android.systemui.animation.Interpolators -import com.android.systemui.statusbar.notification.stack.StackStateAnimator +import com.android.systemui.animation.TextAnimator +import com.android.systemui.shared.R import java.io.PrintWriter import java.util.Calendar import java.util.Locale @@ -38,13 +39,13 @@ import java.util.TimeZone * Displays the time with the hour positioned above the minutes. (ie: 09 above 30 is 9:30) * The time's text color is a gradient that changes its colors based on its controller. */ +@SuppressLint("AppCompatCustomView") class AnimatableClockView @JvmOverloads constructor( context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0, defStyleRes: Int = 0 ) : TextView(context, attrs, defStyleAttr, defStyleRes) { - private val tag = "AnimatableClockView" private var lastMeasureCall: CharSequence = "" @@ -193,7 +194,7 @@ class AnimatableClockView @JvmOverloads constructor( ) } - fun animateFoldAppear() { + fun animateFoldAppear(animate: Boolean = true) { if (textAnimator == null) { return } @@ -210,22 +211,22 @@ class AnimatableClockView @JvmOverloads constructor( weight = dozingWeightInternal, textSize = -1f, color = dozingColor, - animate = true, + animate = animate, interpolator = Interpolators.EMPHASIZED_DECELERATE, - duration = StackStateAnimator.ANIMATION_DURATION_FOLD_TO_AOD.toLong(), + duration = ANIMATION_DURATION_FOLD_TO_AOD.toLong(), delay = 0, onAnimationEnd = null ) } - fun animateCharge(dozeStateGetter: DozeStateGetter) { + fun animateCharge(isDozing: () -> Boolean) { if (textAnimator == null || textAnimator!!.isRunning()) { // Skip charge animation if dozing animation is already playing. return } val startAnimPhase2 = Runnable { setTextStyle( - weight = if (dozeStateGetter.isDozing) dozingWeight else lockScreenWeight, + weight = if (isDozing()) dozingWeight else lockScreenWeight, textSize = -1f, color = null, animate = true, @@ -235,7 +236,7 @@ class AnimatableClockView @JvmOverloads constructor( ) } setTextStyle( - weight = if (dozeStateGetter.isDozing) lockScreenWeight else dozingWeight, + weight = if (isDozing()) lockScreenWeight else dozingWeight, textSize = -1f, color = null, animate = true, @@ -385,14 +386,14 @@ class AnimatableClockView @JvmOverloads constructor( } } - interface DozeStateGetter { - val isDozing: Boolean + companion object { + private val TAG = AnimatableClockView::class.simpleName + const val ANIMATION_DURATION_FOLD_TO_AOD: Int = 600 + private const val DOUBLE_LINE_FORMAT_12_HOUR = "hh\nmm" + private const val DOUBLE_LINE_FORMAT_24_HOUR = "HH\nmm" + private const val DOZE_ANIM_DURATION: Long = 300 + private const val APPEAR_ANIM_DURATION: Long = 350 + private const val CHARGE_ANIM_DURATION_PHASE_0: Long = 500 + private const val CHARGE_ANIM_DURATION_PHASE_1: Long = 1000 } } - -private const val DOUBLE_LINE_FORMAT_12_HOUR = "hh\nmm" -private const val DOUBLE_LINE_FORMAT_24_HOUR = "HH\nmm" -private const val DOZE_ANIM_DURATION: Long = 300 -private const val APPEAR_ANIM_DURATION: Long = 350 -private const val CHARGE_ANIM_DURATION_PHASE_0: Long = 500 -private const val CHARGE_ANIM_DURATION_PHASE_1: Long = 1000 diff --git a/packages/SystemUI/src/com/android/keyguard/AnimatableClockController.java b/packages/SystemUI/src/com/android/keyguard/AnimatableClockController.java index 487e1a48044c..c69ff7ee1cd8 100644 --- a/packages/SystemUI/src/com/android/keyguard/AnimatableClockController.java +++ b/packages/SystemUI/src/com/android/keyguard/AnimatableClockController.java @@ -32,6 +32,7 @@ import com.android.systemui.R; import com.android.systemui.broadcast.BroadcastDispatcher; import com.android.systemui.dagger.qualifiers.Main; import com.android.systemui.plugins.statusbar.StatusBarStateController; +import com.android.systemui.shared.clocks.AnimatableClockView; import com.android.systemui.statusbar.policy.BatteryController; import com.android.systemui.util.ViewController; @@ -134,6 +135,21 @@ public class AnimatableClockController extends ViewController<AnimatableClockVie reset(); } } + + @Override + public void onTimeFormatChanged(String timeFormat) { + mView.refreshFormat(); + } + + @Override + public void onTimeZoneChanged(TimeZone timeZone) { + mView.onTimeZoneChanged(timeZone); + } + + @Override + public void onUserSwitchComplete(int userId) { + mView.refreshFormat(); + } }; @Override @@ -186,7 +202,7 @@ public class AnimatableClockController extends ViewController<AnimatableClockVie /** Animate the clock appearance when a foldable device goes from fully-open/half-open state to * fully folded state and it goes to sleep (always on display screen) */ public void animateFoldAppear() { - mView.animateFoldAppear(); + mView.animateFoldAppear(true); } /** @@ -197,20 +213,6 @@ public class AnimatableClockController extends ViewController<AnimatableClockVie } /** - * Updates the timezone for the view. - */ - public void onTimeZoneChanged(TimeZone timeZone) { - mView.onTimeZoneChanged(timeZone); - } - - /** - * Trigger a time format update - */ - public void refreshFormat() { - mView.refreshFormat(); - } - - /** * Return locallly stored dozing state. */ @VisibleForTesting diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitch.java b/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitch.java index 40edfe50d907..206b8bee323c 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitch.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitch.java @@ -22,6 +22,7 @@ import com.android.keyguard.dagger.KeyguardStatusViewScope; import com.android.systemui.R; import com.android.systemui.animation.Interpolators; import com.android.systemui.plugins.ClockPlugin; +import com.android.systemui.shared.clocks.AnimatableClockView; import java.io.PrintWriter; import java.lang.annotation.Retention; diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java index 5c9dd5ec26e7..6c32a4910c56 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java @@ -398,17 +398,6 @@ public class KeyguardClockSwitchController extends ViewController<KeyguardClockS void updateTimeZone(TimeZone timeZone) { mView.onTimeZoneChanged(timeZone); - if (mClockViewController != null) { - mClockViewController.onTimeZoneChanged(timeZone); - mLargeClockViewController.onTimeZoneChanged(timeZone); - } - } - - void refreshFormat() { - if (mClockViewController != null) { - mClockViewController.refreshFormat(); - mLargeClockViewController.refreshFormat(); - } } /** diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardStatusViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardStatusViewController.java index 8921780fdacf..014d08288158 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardStatusViewController.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardStatusViewController.java @@ -240,11 +240,6 @@ public class KeyguardStatusViewController extends ViewController<KeyguardStatusV } @Override - public void onTimeFormatChanged(String timeFormat) { - mKeyguardClockSwitchController.refreshFormat(); - } - - @Override public void onTimeZoneChanged(TimeZone timeZone) { mKeyguardClockSwitchController.updateTimeZone(timeZone); } @@ -256,11 +251,6 @@ public class KeyguardStatusViewController extends ViewController<KeyguardStatusV refreshTime(); } } - - @Override - public void onUserSwitchComplete(int userId) { - mKeyguardClockSwitchController.refreshFormat(); - } }; /** diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java index c19175742ce5..4ae2cad4a01f 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java @@ -805,8 +805,8 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab private Runnable mRetryFingerprintAuthentication = new Runnable() { @Override public void run() { - Log.w(TAG, "Retrying fingerprint after HW unavailable, attempt " + - mHardwareFingerprintUnavailableRetryCount); + Log.w(TAG, + "Retrying fingerprint attempt: " + mHardwareFingerprintUnavailableRetryCount); if (mFpm.isHardwareDetected()) { updateFingerprintListeningState(BIOMETRIC_ACTION_UPDATE); } else if (mHardwareFingerprintUnavailableRetryCount < HAL_ERROR_RETRY_MAX) { @@ -833,7 +833,9 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab setFingerprintRunningState(BIOMETRIC_STATE_STOPPED); } - if (msgId == FingerprintManager.FINGERPRINT_ERROR_HW_UNAVAILABLE) { + if (msgId == FingerprintManager.FINGERPRINT_ERROR_HW_UNAVAILABLE + || msgId == FingerprintManager.BIOMETRIC_ERROR_POWER_PRESSED) { + Log.d(TAG, "Fingerprint retrying auth due to(" + msgId + ") -> " + errString); mHandler.postDelayed(mRetryFingerprintAuthentication, HAL_ERROR_RETRY_TIMEOUT); } diff --git a/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java b/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java index f99293a9d148..edcaf492874d 100644 --- a/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java +++ b/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java @@ -842,7 +842,8 @@ public class ScreenDecorations extends CoreStartable implements Tunable , Dumpab | WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL | WindowManager.LayoutParams.FLAG_SPLIT_TOUCH | WindowManager.LayoutParams.FLAG_SLIPPERY - | WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN, + | WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN + | WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE, PixelFormat.TRANSLUCENT); lp.privateFlags |= WindowManager.LayoutParams.SYSTEM_FLAG_SHOW_FOR_ALL_USERS | WindowManager.LayoutParams.PRIVATE_FLAG_NO_MOVE_ANIMATION; diff --git a/packages/SystemUI/src/com/android/systemui/dagger/SysUIComponent.java b/packages/SystemUI/src/com/android/systemui/dagger/SysUIComponent.java index 029cabb0bc0b..718befadd2b5 100644 --- a/packages/SystemUI/src/com/android/systemui/dagger/SysUIComponent.java +++ b/packages/SystemUI/src/com/android/systemui/dagger/SysUIComponent.java @@ -31,6 +31,7 @@ import com.android.systemui.media.taptotransfer.MediaTttCommandLineHelper; import com.android.systemui.media.taptotransfer.receiver.MediaTttChipControllerReceiver; import com.android.systemui.media.taptotransfer.sender.MediaTttChipControllerSender; import com.android.systemui.people.PeopleProvider; +import com.android.systemui.statusbar.pipeline.ConnectivityInfoProcessor; import com.android.systemui.statusbar.policy.ConfigurationController; import com.android.systemui.unfold.FoldStateLogger; import com.android.systemui.unfold.FoldStateLoggingProvider; @@ -130,6 +131,7 @@ public interface SysUIComponent { getMediaTttCommandLineHelper(); getMediaMuteAwaitConnectionCli(); getNearbyMediaDevicesManager(); + getConnectivityInfoProcessor(); getUnfoldLatencyTracker().init(); getFoldStateLoggingProvider().ifPresent(FoldStateLoggingProvider::init); getFoldStateLogger().ifPresent(FoldStateLogger::init); @@ -212,6 +214,9 @@ public interface SysUIComponent { /** */ Optional<NearbyMediaDevicesManager> getNearbyMediaDevicesManager(); + /** */ + Optional<ConnectivityInfoProcessor> getConnectivityInfoProcessor(); + /** * Returns {@link CoreStartable}s that should be started with the application. */ diff --git a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java index fe9622250e67..ceb702eafe1e 100644 --- a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java +++ b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java @@ -70,7 +70,7 @@ import com.android.systemui.statusbar.notification.row.dagger.NotificationShelfC import com.android.systemui.statusbar.phone.CentralSurfaces; import com.android.systemui.statusbar.phone.ShadeController; import com.android.systemui.statusbar.phone.dagger.CentralSurfacesComponent; -import com.android.systemui.statusbar.policy.ConfigurationController; +import com.android.systemui.statusbar.pipeline.dagger.StatusBarPipelineModule; import com.android.systemui.statusbar.policy.HeadsUpManager; import com.android.systemui.statusbar.policy.KeyguardStateController; import com.android.systemui.statusbar.policy.ZenModeController; @@ -91,7 +91,6 @@ import com.android.systemui.wallet.dagger.WalletModule; import com.android.systemui.wmshell.BubblesManager; import com.android.wm.shell.bubbles.Bubbles; import com.android.wm.shell.dagger.DynamicOverride; -import com.android.wm.shell.sysui.ShellController; import java.util.Optional; import java.util.concurrent.Executor; @@ -135,6 +134,7 @@ import dagger.Provides; SettingsUtilModule.class, SmartRepliesInflationModule.class, SmartspaceModule.class, + StatusBarPipelineModule.class, StatusBarPolicyModule.class, StatusBarWindowModule.class, SysUIConcurrencyModule.class, diff --git a/packages/SystemUI/src/com/android/systemui/flags/Flags.java b/packages/SystemUI/src/com/android/systemui/flags/Flags.java index 65711edd6e24..845dab24be9c 100644 --- a/packages/SystemUI/src/com/android/systemui/flags/Flags.java +++ b/packages/SystemUI/src/com/android/systemui/flags/Flags.java @@ -148,6 +148,8 @@ public class Flags { public static final BooleanFlag STATUS_BAR_LETTERBOX_APPEARANCE = new BooleanFlag(603, false); + public static final BooleanFlag NEW_STATUS_BAR_PIPELINE = new BooleanFlag(604, false); + /***************************************/ // 700 - dialer/calls public static final BooleanFlag ONGOING_CALL_STATUS_BAR_CHIP = diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/BackPanelController.kt b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/BackPanelController.kt index 2006d5338c8c..b05e75ea1c97 100644 --- a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/BackPanelController.kt +++ b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/BackPanelController.kt @@ -601,7 +601,7 @@ class BackPanelController private constructor( if (currentState == GestureState.INACTIVE || currentState == GestureState.CANCELLED ) - params.entryIndicator.backgroundDimens.edgeCornerRadius + params.cancelledEdgeCornerRadius else params.activeIndicator.backgroundDimens.edgeCornerRadius ) diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgePanelParams.kt b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgePanelParams.kt index a3fb58d5b015..d56537b694da 100644 --- a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgePanelParams.kt +++ b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgePanelParams.kt @@ -33,6 +33,8 @@ data class EdgePanelParams(private var resources: Resources) { private set var fullyStretchedIndicator = BackIndicatorDimens() private set + var cancelledEdgeCornerRadius: Float = 0f + private set var cancelledArrowDimens = ArrowDimens() // navigation bar edge constants @@ -132,6 +134,8 @@ data class EdgePanelParams(private var resources: Resources) { ) ) + cancelledEdgeCornerRadius = getDimen(R.dimen.navigation_edge_cancelled_edge_corners) + cancelledArrowDimens = ArrowDimens( length = getDimen(R.dimen.navigation_edge_cancelled_arrow_length), height = getDimen(R.dimen.navigation_edge_cancelled_arrow_height) diff --git a/packages/SystemUI/src/com/android/systemui/people/PeopleSpaceActivity.java b/packages/SystemUI/src/com/android/systemui/people/PeopleSpaceActivity.java index e845aa85a121..7cc95a158a14 100644 --- a/packages/SystemUI/src/com/android/systemui/people/PeopleSpaceActivity.java +++ b/packages/SystemUI/src/com/android/systemui/people/PeopleSpaceActivity.java @@ -22,7 +22,6 @@ import static android.appwidget.AppWidgetManager.INVALID_APPWIDGET_ID; import android.content.Intent; import android.os.Bundle; import android.util.Log; -import android.view.View; import android.view.ViewGroup; import androidx.activity.ComponentActivity; @@ -40,7 +39,6 @@ public class PeopleSpaceActivity extends ComponentActivity { private static final boolean DEBUG = PeopleSpaceUtils.DEBUG; private final PeopleViewModel.Factory mViewModelFactory; - private PeopleViewModel mViewModel; @Inject public PeopleSpaceActivity(PeopleViewModel.Factory viewModelFactory) { @@ -52,38 +50,32 @@ public class PeopleSpaceActivity extends ComponentActivity { protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setResult(RESULT_CANCELED); - mViewModel = new ViewModelProvider(this, mViewModelFactory).get(PeopleViewModel.class); + + PeopleViewModel viewModel = new ViewModelProvider(this, mViewModelFactory).get( + PeopleViewModel.class); // Update the widget ID coming from the intent. int widgetId = getIntent().getIntExtra(EXTRA_APPWIDGET_ID, INVALID_APPWIDGET_ID); - mViewModel.onWidgetIdChanged(widgetId); + viewModel.onWidgetIdChanged(widgetId); ViewGroup view = PeopleViewBinder.create(this); - PeopleViewBinder.bind(view, mViewModel, /* lifecycleOwner= */ this, - () -> { - finishActivity(); + PeopleViewBinder.bind(view, viewModel, /* lifecycleOwner= */ this, + (result) -> { + finishActivity(result); return null; }); setContentView(view); } - /** Finish activity with a successful widget configuration result. */ - private void finishActivity() { - if (DEBUG) Log.d(TAG, "Widget added!"); - setActivityResult(RESULT_OK); + private void finishActivity(PeopleViewModel.Result result) { + if (result instanceof PeopleViewModel.Result.Success) { + if (DEBUG) Log.d(TAG, "Widget added!"); + Intent data = ((PeopleViewModel.Result.Success) result).getData(); + setResult(RESULT_OK, data); + } else { + if (DEBUG) Log.d(TAG, "Activity dismissed with no widgets added!"); + setResult(RESULT_CANCELED); + } finish(); } - - /** Finish activity without choosing a widget. */ - public void dismissActivity(View v) { - if (DEBUG) Log.d(TAG, "Activity dismissed with no widgets added!"); - setResult(RESULT_CANCELED); - finish(); - } - - private void setActivityResult(int result) { - Intent resultValue = new Intent(); - resultValue.putExtra(EXTRA_APPWIDGET_ID, mViewModel.getAppWidgetId().getValue()); - setResult(result, resultValue); - } } diff --git a/packages/SystemUI/src/com/android/systemui/people/ui/view/PeopleViewBinder.kt b/packages/SystemUI/src/com/android/systemui/people/ui/view/PeopleViewBinder.kt index bc982cccaacd..d8a429e5bb1a 100644 --- a/packages/SystemUI/src/com/android/systemui/people/ui/view/PeopleViewBinder.kt +++ b/packages/SystemUI/src/com/android/systemui/people/ui/view/PeopleViewBinder.kt @@ -41,7 +41,7 @@ import kotlinx.coroutines.launch /** A ViewBinder for [PeopleViewModel]. */ object PeopleViewBinder { - private const val TAG = "PeopleSpaceViewBinder" + private const val TAG = "PeopleViewBinder" /** * The [ViewOutlineProvider] used to clip the corner radius of the recent and priority lists. @@ -72,15 +72,15 @@ object PeopleViewBinder { view: ViewGroup, viewModel: PeopleViewModel, lifecycleOwner: LifecycleOwner, - onFinish: () -> Unit, + onResult: (PeopleViewModel.Result) -> Unit, ) { - // Call [onFinish] this activity when the ViewModel tells us so. + // Call [onResult] as soon as a result is available. lifecycleOwner.lifecycleScope.launch { lifecycleOwner.repeatOnLifecycle(CREATED) { - viewModel.isFinished.collect { isFinished -> - if (isFinished) { - viewModel.clearIsFinished() - onFinish() + viewModel.result.collect { result -> + if (result != null) { + viewModel.clearResult() + onResult(result) } } } @@ -104,7 +104,7 @@ object PeopleViewBinder { viewModel::onTileClicked, ) } else { - setNoConversationsContent(view) + setNoConversationsContent(view, viewModel::onUserJourneyCancelled) } } } @@ -119,7 +119,7 @@ object PeopleViewBinder { } } - private fun setNoConversationsContent(view: ViewGroup) { + private fun setNoConversationsContent(view: ViewGroup, onGotItClicked: () -> Unit) { // This should never happen. if (view.childCount > 1) { error("view has ${view.childCount} children, it should have maximum 1") @@ -140,6 +140,10 @@ object PeopleViewBinder { LayoutInflater.from(context) .inflate(R.layout.people_space_activity_no_conversations, /* root= */ view) + noConversationsView.findViewById<View>(R.id.got_it_button).setOnClickListener { + onGotItClicked() + } + // The Tile preview has colorBackground as its background. Change it so it's different than // the activity's background. val item = noConversationsView.findViewById<LinearLayout>(android.R.id.background) diff --git a/packages/SystemUI/src/com/android/systemui/people/ui/viewmodel/PeopleViewModel.kt b/packages/SystemUI/src/com/android/systemui/people/ui/viewmodel/PeopleViewModel.kt index 17de991588b8..0834a5ae75f6 100644 --- a/packages/SystemUI/src/com/android/systemui/people/ui/viewmodel/PeopleViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/people/ui/viewmodel/PeopleViewModel.kt @@ -16,8 +16,10 @@ package com.android.systemui.people.ui.viewmodel +import android.appwidget.AppWidgetManager.EXTRA_APPWIDGET_ID import android.appwidget.AppWidgetManager.INVALID_APPWIDGET_ID import android.content.Context +import android.content.Intent import android.util.Log import androidx.lifecycle.ViewModel import androidx.lifecycle.ViewModelProvider @@ -32,6 +34,7 @@ import javax.inject.Inject import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.flow.asStateFlow /** * Models UI state for the people space, allowing the user to select which conversation should be @@ -49,7 +52,7 @@ class PeopleViewModel( * reactive and you have to manually call [onTileRefreshRequested] to refresh the tiles. */ private val _priorityTiles = MutableStateFlow(priorityTiles()) - val priorityTiles: Flow<List<PeopleTileViewModel>> = _priorityTiles + val priorityTiles: Flow<List<PeopleTileViewModel>> = _priorityTiles.asStateFlow() /** * The list of the priority tiles/conversations. @@ -58,15 +61,15 @@ class PeopleViewModel( * reactive and you have to manually call [onTileRefreshRequested] to refresh the tiles. */ private val _recentTiles = MutableStateFlow(recentTiles()) - val recentTiles: Flow<List<PeopleTileViewModel>> = _recentTiles + val recentTiles: Flow<List<PeopleTileViewModel>> = _recentTiles.asStateFlow() /** The ID of the widget currently being edited/added. */ private val _appWidgetId = MutableStateFlow(INVALID_APPWIDGET_ID) - val appWidgetId: StateFlow<Int> = _appWidgetId + val appWidgetId: StateFlow<Int> = _appWidgetId.asStateFlow() - /** Whether the user journey is complete. */ - private val _isFinished = MutableStateFlow(false) - val isFinished: StateFlow<Boolean> = _isFinished + /** The result of this user journey. */ + private val _result = MutableStateFlow<Result?>(null) + val result: StateFlow<Result?> = _result.asStateFlow() /** Refresh the [priorityTiles] and [recentTiles]. */ fun onTileRefreshRequested() { @@ -79,22 +82,28 @@ class PeopleViewModel( _appWidgetId.value = widgetId } - /** Clear [isFinished], setting it to false. */ - fun clearIsFinished() { - _isFinished.value = false + /** Clear [result], setting it to null. */ + fun clearResult() { + _result.value = null } /** Called when a tile is clicked. */ fun onTileClicked(tile: PeopleTileViewModel) { + val widgetId = _appWidgetId.value if (PeopleSpaceUtils.DEBUG) { Log.d( TAG, - "Put ${tile.username}'s shortcut ID: ${tile.key.shortcutId} for widget ID: " + - _appWidgetId.value + "Put ${tile.username}'s shortcut ID: ${tile.key.shortcutId} for widget ID $widgetId" ) } - widgetRepository.setWidgetTile(_appWidgetId.value, tile.key) - _isFinished.value = true + widgetRepository.setWidgetTile(widgetId, tile.key) + _result.value = + Result.Success(Intent().apply { putExtra(EXTRA_APPWIDGET_ID, appWidgetId.value) }) + } + + /** Called when this user journey is cancelled. */ + fun onUserJourneyCancelled() { + _result.value = Result.Cancelled } private fun priorityTiles(): List<PeopleTileViewModel> { @@ -143,7 +152,12 @@ class PeopleViewModel( } } + sealed class Result { + class Success(val data: Intent) : Result() + object Cancelled : Result() + } + companion object { - private const val TAG = "PeopleSpaceViewModel" + private const val TAG = "PeopleViewModel" } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackStateAnimator.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackStateAnimator.java index 7c9df426aba6..174bf4c27de8 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackStateAnimator.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackStateAnimator.java @@ -25,6 +25,7 @@ import android.view.View; import com.android.keyguard.KeyguardSliceView; import com.android.systemui.R; import com.android.systemui.animation.Interpolators; +import com.android.systemui.shared.clocks.AnimatableClockView; import com.android.systemui.statusbar.NotificationShelf; import com.android.systemui.statusbar.StatusBarIconView; import com.android.systemui.statusbar.notification.collection.NotificationEntry; @@ -52,7 +53,8 @@ public class StackStateAnimator { public static final int ANIMATION_DURATION_CLOSE_REMOTE_INPUT = 150; public static final int ANIMATION_DURATION_HEADS_UP_APPEAR = 400; public static final int ANIMATION_DURATION_HEADS_UP_DISAPPEAR = 400; - public static final int ANIMATION_DURATION_FOLD_TO_AOD = 600; + public static final int ANIMATION_DURATION_FOLD_TO_AOD = + AnimatableClockView.ANIMATION_DURATION_FOLD_TO_AOD; public static final int ANIMATION_DURATION_PULSE_APPEAR = KeyguardSliceView.DEFAULT_ANIM_DURATION; public static final int ANIMATION_DURATION_BLOCKING_HELPER_FADE = 240; diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesCommandQueueCallbacks.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesCommandQueueCallbacks.java index eda3446c8cb6..f4b7772a1453 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesCommandQueueCallbacks.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesCommandQueueCallbacks.java @@ -57,10 +57,8 @@ import com.android.systemui.keyguard.WakefulnessLifecycle; import com.android.systemui.qs.QSPanelController; import com.android.systemui.shade.NotificationPanelView; import com.android.systemui.shade.NotificationPanelViewController; -import com.android.systemui.shade.NotificationShadeWindowView; import com.android.systemui.statusbar.CommandQueue; import com.android.systemui.statusbar.DisableFlagsLogger; -import com.android.systemui.statusbar.SysuiStatusBarStateController; import com.android.systemui.statusbar.VibratorHelper; import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController; import com.android.systemui.statusbar.phone.dagger.CentralSurfacesComponent; @@ -91,19 +89,16 @@ public class CentralSurfacesCommandQueueCallbacks implements CommandQueue.Callba private final StatusBarKeyguardViewManager mStatusBarKeyguardViewManager; private final AssistManager mAssistManager; private final DozeServiceHost mDozeServiceHost; - private final SysuiStatusBarStateController mStatusBarStateController; - private final NotificationShadeWindowView mNotificationShadeWindowView; private final NotificationStackScrollLayoutController mNotificationStackScrollLayoutController; private final StatusBarHideIconsForBouncerManager mStatusBarHideIconsForBouncerManager; private final PowerManager mPowerManager; private final VibratorHelper mVibratorHelper; private final Optional<Vibrator> mVibratorOptional; - private final LightBarController mLightBarController; private final DisableFlagsLogger mDisableFlagsLogger; private final int mDisplayId; private final boolean mVibrateOnOpening; private final VibrationEffect mCameraLaunchGestureVibrationEffect; - + private final SystemBarAttributesListener mSystemBarAttributesListener; private static final VibrationAttributes HARDWARE_FEEDBACK_VIBRATION_ATTRIBUTES = VibrationAttributes.createForUsage(VibrationAttributes.USAGE_HARDWARE_FEEDBACK); @@ -126,16 +121,14 @@ public class CentralSurfacesCommandQueueCallbacks implements CommandQueue.Callba StatusBarKeyguardViewManager statusBarKeyguardViewManager, AssistManager assistManager, DozeServiceHost dozeServiceHost, - SysuiStatusBarStateController statusBarStateController, - NotificationShadeWindowView notificationShadeWindowView, NotificationStackScrollLayoutController notificationStackScrollLayoutController, StatusBarHideIconsForBouncerManager statusBarHideIconsForBouncerManager, PowerManager powerManager, VibratorHelper vibratorHelper, Optional<Vibrator> vibratorOptional, - LightBarController lightBarController, DisableFlagsLogger disableFlagsLogger, - @DisplayId int displayId) { + @DisplayId int displayId, + SystemBarAttributesListener systemBarAttributesListener) { mCentralSurfaces = centralSurfaces; mContext = context; @@ -152,20 +145,18 @@ public class CentralSurfacesCommandQueueCallbacks implements CommandQueue.Callba mStatusBarKeyguardViewManager = statusBarKeyguardViewManager; mAssistManager = assistManager; mDozeServiceHost = dozeServiceHost; - mStatusBarStateController = statusBarStateController; - mNotificationShadeWindowView = notificationShadeWindowView; mNotificationStackScrollLayoutController = notificationStackScrollLayoutController; mStatusBarHideIconsForBouncerManager = statusBarHideIconsForBouncerManager; mPowerManager = powerManager; mVibratorHelper = vibratorHelper; mVibratorOptional = vibratorOptional; - mLightBarController = lightBarController; mDisableFlagsLogger = disableFlagsLogger; mDisplayId = displayId; mVibrateOnOpening = resources.getBoolean(R.bool.config_vibrateOnIconAnimation); mCameraLaunchGestureVibrationEffect = getCameraGestureVibrationEffect( mVibratorOptional, resources); + mSystemBarAttributesListener = systemBarAttributesListener; } @Override @@ -472,14 +463,18 @@ public class CentralSurfacesCommandQueueCallbacks implements CommandQueue.Callba if (displayId != mDisplayId) { return; } - boolean barModeChanged = mCentralSurfaces.setAppearance(appearance); - - mLightBarController.onStatusBarAppearanceChanged(appearanceRegions, barModeChanged, - mCentralSurfaces.getBarMode(), navbarColorManagedByIme); - - mCentralSurfaces.updateBubblesVisibility(); - mStatusBarStateController.setSystemBarAttributes( - appearance, behavior, requestedVisibilities, packageName); + // SystemBarAttributesListener should __always__ be the top-level listener for system bar + // attributes changed. + mSystemBarAttributesListener.onSystemBarAttributesChanged( + displayId, + appearance, + appearanceRegions, + navbarColorManagedByIme, + behavior, + requestedVisibilities, + packageName, + letterboxDetails + ); } @Override diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/SystemBarAttributesListener.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/SystemBarAttributesListener.kt new file mode 100644 index 000000000000..a0415f2f3d7c --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/SystemBarAttributesListener.kt @@ -0,0 +1,158 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.statusbar.phone + +import android.view.InsetsVisibilities +import android.view.WindowInsetsController.Appearance +import android.view.WindowInsetsController.Behavior +import com.android.internal.statusbar.LetterboxDetails +import com.android.internal.view.AppearanceRegion +import com.android.systemui.dump.DumpManager +import com.android.systemui.flags.FeatureFlags +import com.android.systemui.flags.Flags +import com.android.systemui.statusbar.SysuiStatusBarStateController +import com.android.systemui.statusbar.phone.dagger.CentralSurfacesComponent +import com.android.systemui.statusbar.phone.dagger.CentralSurfacesComponent.CentralSurfacesScope +import java.io.PrintWriter +import javax.inject.Inject + +/** + * Top-level listener of system attributes changed. This class is __always the first__ one to be + * notified about changes. + * + * It is responsible for modifying any attributes if necessary, and then notifying the other + * downstream listeners. + */ +@CentralSurfacesScope +class SystemBarAttributesListener +@Inject +internal constructor( + private val centralSurfaces: CentralSurfaces, + private val featureFlags: FeatureFlags, + private val letterboxAppearanceCalculator: LetterboxAppearanceCalculator, + private val statusBarStateController: SysuiStatusBarStateController, + private val lightBarController: LightBarController, + private val dumpManager: DumpManager, +) : CentralSurfacesComponent.Startable, StatusBarBoundsProvider.BoundsChangeListener { + + private var lastLetterboxAppearance: LetterboxAppearance? = null + private var lastSystemBarAttributesParams: SystemBarAttributesParams? = null + + override fun start() { + dumpManager.registerDumpable(javaClass.simpleName, this::dump) + } + + override fun stop() { + dumpManager.unregisterDumpable(javaClass.simpleName) + } + + override fun onStatusBarBoundsChanged() { + val params = lastSystemBarAttributesParams + if (params != null && shouldUseLetterboxAppearance(params.letterboxesArray)) { + onSystemBarAttributesChanged( + params.displayId, + params.appearance, + params.appearanceRegionsArray, + params.navbarColorManagedByIme, + params.behavior, + params.requestedVisibilities, + params.packageName, + params.letterboxesArray) + } + } + + fun onSystemBarAttributesChanged( + displayId: Int, + @Appearance originalAppearance: Int, + originalAppearanceRegions: Array<AppearanceRegion>, + navbarColorManagedByIme: Boolean, + @Behavior behavior: Int, + requestedVisibilities: InsetsVisibilities, + packageName: String, + letterboxDetails: Array<LetterboxDetails> + ) { + lastSystemBarAttributesParams = + SystemBarAttributesParams( + displayId, + originalAppearance, + originalAppearanceRegions.toList(), + navbarColorManagedByIme, + behavior, + requestedVisibilities, + packageName, + letterboxDetails.toList()) + + val (appearance, appearanceRegions) = + modifyAppearanceIfNeeded( + originalAppearance, originalAppearanceRegions, letterboxDetails) + + val barModeChanged = centralSurfaces.setAppearance(appearance) + + lightBarController.onStatusBarAppearanceChanged( + appearanceRegions, barModeChanged, centralSurfaces.barMode, navbarColorManagedByIme) + + centralSurfaces.updateBubblesVisibility() + statusBarStateController.setSystemBarAttributes( + appearance, behavior, requestedVisibilities, packageName) + } + + private fun modifyAppearanceIfNeeded( + appearance: Int, + appearanceRegions: Array<AppearanceRegion>, + letterboxDetails: Array<LetterboxDetails> + ): Pair<Int, Array<AppearanceRegion>> = + if (shouldUseLetterboxAppearance(letterboxDetails)) { + val letterboxAppearance = + letterboxAppearanceCalculator.getLetterboxAppearance( + appearance, appearanceRegions, letterboxDetails) + lastLetterboxAppearance = letterboxAppearance + Pair(letterboxAppearance.appearance, letterboxAppearance.appearanceRegions) + } else { + lastLetterboxAppearance = null + Pair(appearance, appearanceRegions) + } + + private fun shouldUseLetterboxAppearance(letterboxDetails: Array<LetterboxDetails>) = + isLetterboxAppearanceFlagEnabled() && letterboxDetails.isNotEmpty() + + private fun isLetterboxAppearanceFlagEnabled() = + featureFlags.isEnabled(Flags.STATUS_BAR_LETTERBOX_APPEARANCE) + + private fun dump(printWriter: PrintWriter, strings: Array<String>) { + printWriter.println("lastSystemBarAttributesParams: $lastSystemBarAttributesParams") + printWriter.println("lastLetterboxAppearance: $lastLetterboxAppearance") + printWriter.println("letterbox appearance flag: ${isLetterboxAppearanceFlagEnabled()}") + } +} + +/** + * Keeps track of the parameters passed in + * [SystemBarAttributesListener.onSystemBarAttributesChanged]. + */ +private data class SystemBarAttributesParams( + val displayId: Int, + @Appearance val appearance: Int, + val appearanceRegions: List<AppearanceRegion>, + val navbarColorManagedByIme: Boolean, + @Behavior val behavior: Int, + val requestedVisibilities: InsetsVisibilities, + val packageName: String, + val letterboxes: List<LetterboxDetails>, +) { + val letterboxesArray = letterboxes.toTypedArray() + val appearanceRegionsArray = appearanceRegions.toTypedArray() +} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/CentralSurfacesStartableModule.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/CentralSurfacesStartableModule.java index 590522fc8751..d57e6a791489 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/CentralSurfacesStartableModule.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/CentralSurfacesStartableModule.java @@ -17,6 +17,7 @@ package com.android.systemui.statusbar.phone.dagger; import com.android.systemui.statusbar.phone.LetterboxAppearanceCalculator; +import com.android.systemui.statusbar.phone.SystemBarAttributesListener; import java.util.Set; @@ -34,4 +35,9 @@ interface CentralSurfacesStartableModule { @IntoSet CentralSurfacesComponent.Startable letterboxAppearanceCalculator( LetterboxAppearanceCalculator letterboxAppearanceCalculator); + + @Binds + @IntoSet + CentralSurfacesComponent.Startable sysBarAttrsListener( + SystemBarAttributesListener systemBarAttributesListener); } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarViewModule.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarViewModule.java index b3bef07cb820..c4e7a8a2c17b 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarViewModule.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarViewModule.java @@ -53,10 +53,12 @@ import com.android.systemui.statusbar.notification.stack.NotificationStackScroll import com.android.systemui.statusbar.phone.KeyguardBottomAreaView; import com.android.systemui.statusbar.phone.LetterboxAppearanceCalculator; import com.android.systemui.statusbar.phone.NotificationIconAreaController; +import com.android.systemui.statusbar.phone.StatusBarBoundsProvider; import com.android.systemui.statusbar.phone.StatusBarHideIconsForBouncerManager; import com.android.systemui.statusbar.phone.StatusBarIconController; import com.android.systemui.statusbar.phone.StatusBarLocationPublisher; import com.android.systemui.statusbar.phone.StatusIconContainer; +import com.android.systemui.statusbar.phone.SystemBarAttributesListener; import com.android.systemui.statusbar.phone.TapAgainView; import com.android.systemui.statusbar.phone.fragment.CollapsedStatusBarFragment; import com.android.systemui.statusbar.phone.fragment.CollapsedStatusBarFragmentLogger; @@ -258,6 +260,11 @@ public abstract class StatusBarViewModule { LetterboxAppearanceCalculator letterboxAppearanceCalculator ); + @Binds + @IntoSet + abstract StatusBarBoundsProvider.BoundsChangeListener sysBarAttrsListenerAsBoundsListener( + SystemBarAttributesListener systemBarAttributesListener); + /** * Creates a new {@link CollapsedStatusBarFragment}. * diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/ConnectivityInfoProcessor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/ConnectivityInfoProcessor.kt new file mode 100644 index 000000000000..bd6cf9a1d101 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/ConnectivityInfoProcessor.kt @@ -0,0 +1,30 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.statusbar.pipeline + +import com.android.systemui.dagger.SysUISingleton +import javax.inject.Inject + +/** + * A processor that transforms raw connectivity information that we get from callbacks and turns it + * into a list of displayable connectivity information. + * + * This will be used for the new status bar pipeline to calculate the list of icons that should be + * displayed in the RHS of the status bar. + */ +@SysUISingleton +class ConnectivityInfoProcessor @Inject constructor() diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/dagger/StatusBarPipelineModule.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/dagger/StatusBarPipelineModule.kt new file mode 100644 index 000000000000..734bd2d8e5b3 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/dagger/StatusBarPipelineModule.kt @@ -0,0 +1,42 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.statusbar.pipeline.dagger + +import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.flags.FeatureFlags +import com.android.systemui.flags.Flags +import com.android.systemui.statusbar.pipeline.ConnectivityInfoProcessor +import dagger.Lazy +import dagger.Module +import dagger.Provides +import java.util.Optional + +@Module +class StatusBarPipelineModule { + @Provides + @SysUISingleton + fun provideConnectivityInfoProcessor( + featureFlags: FeatureFlags, + processorLazy: Lazy<ConnectivityInfoProcessor> + ): Optional<ConnectivityInfoProcessor> { + return if (featureFlags.isEnabled(Flags.NEW_STATUS_BAR_PIPELINE)) { + Optional.of(processorLazy.get()) + } else { + Optional.empty() + } + } +} diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchControllerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchControllerTest.java index 5db2cf49636c..8b9a1e022d26 100644 --- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchControllerTest.java @@ -51,6 +51,7 @@ import com.android.systemui.dump.DumpManager; import com.android.systemui.keyguard.KeyguardUnlockAnimationController; import com.android.systemui.plugins.ClockPlugin; import com.android.systemui.plugins.statusbar.StatusBarStateController; +import com.android.systemui.shared.clocks.AnimatableClockView; import com.android.systemui.statusbar.StatusBarState; import com.android.systemui.statusbar.lockscreen.LockscreenSmartspaceController; import com.android.systemui.statusbar.phone.KeyguardBypassController; diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchTest.java index 8717a0eaf57f..6c6f0acd7085 100644 --- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchTest.java +++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchTest.java @@ -43,6 +43,7 @@ import android.widget.TextClock; import com.android.systemui.R; import com.android.systemui.SysuiTestCase; import com.android.systemui.plugins.ClockPlugin; +import com.android.systemui.shared.clocks.AnimatableClockView; import com.android.systemui.statusbar.StatusBarState; import org.junit.Before; diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardStatusViewControllerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardStatusViewControllerTest.java index 70025230fa83..4dcaa7cf8c09 100644 --- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardStatusViewControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardStatusViewControllerTest.java @@ -89,28 +89,6 @@ public class KeyguardStatusViewControllerTest extends SysuiTestCase { } @Test - public void timeFormatUpdateNotifiesClockSwitchController() { - mController.onViewAttached(); - - verify(mKeyguardUpdateMonitor).registerCallback( - mKeyguardUpdateMonitorCallbackCaptor.capture()); - - mKeyguardUpdateMonitorCallbackCaptor.getValue().onTimeFormatChanged(""); - verify(mKeyguardClockSwitchController).refreshFormat(); - } - - @Test - public void userChangeNotifiesClockSwitchController() { - mController.onViewAttached(); - - verify(mKeyguardUpdateMonitor).registerCallback( - mKeyguardUpdateMonitorCallbackCaptor.capture()); - - mKeyguardUpdateMonitorCallbackCaptor.getValue().onUserSwitchComplete(0); - verify(mKeyguardClockSwitchController).refreshFormat(); - } - - @Test public void setTranslationYExcludingMedia_forwardsCallToView() { float translationY = 123f; diff --git a/packages/SystemUI/tests/src/com/android/keyguard/FontInterpolatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/animation/FontInterpolatorTest.kt index 95fa3b9c594b..f01da2dd3a6b 100644 --- a/packages/SystemUI/tests/src/com/android/keyguard/FontInterpolatorTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/animation/FontInterpolatorTest.kt @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.android.keyguard +package com.android.systemui.animation import android.graphics.Paint import android.graphics.fonts.Font diff --git a/packages/SystemUI/tests/src/com/android/keyguard/TextAnimatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/animation/TextAnimatorTest.kt index 3322414e9e7a..ed0cd7ed9b24 100644 --- a/packages/SystemUI/tests/src/com/android/keyguard/TextAnimatorTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/animation/TextAnimatorTest.kt @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.android.keyguard +package com.android.systemui.animation import android.animation.AnimatorListenerAdapter import android.animation.ValueAnimator @@ -26,18 +26,17 @@ import android.text.TextPaint import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.google.common.truth.Truth.assertThat +import kotlin.math.ceil import org.junit.Test import org.junit.runner.RunWith import org.mockito.ArgumentCaptor -import org.mockito.Mockito.`when` import org.mockito.Mockito.eq import org.mockito.Mockito.inOrder import org.mockito.Mockito.mock import org.mockito.Mockito.never import org.mockito.Mockito.times import org.mockito.Mockito.verify - -import kotlin.math.ceil +import org.mockito.Mockito.`when` private val PAINT = TextPaint().apply { textSize = 32f diff --git a/packages/SystemUI/tests/src/com/android/keyguard/TextInterpolatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/animation/TextInterpolatorTest.kt index 603cf3bcef74..2a183bd43f83 100644 --- a/packages/SystemUI/tests/src/com/android/keyguard/TextInterpolatorTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/animation/TextInterpolatorTest.kt @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.android.keyguard +package com.android.systemui.animation import android.graphics.Bitmap import android.graphics.Canvas @@ -31,10 +31,10 @@ import android.text.TextPaint import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.google.common.truth.Truth.assertThat -import org.junit.Test -import org.junit.runner.RunWith import java.io.File import kotlin.math.ceil +import org.junit.Test +import org.junit.runner.RunWith private const val TEXT = "Hello, World." private const val BIDI_TEXT = "abc\u05D0\u05D1\u05D2" @@ -323,4 +323,4 @@ private fun Layout.toBitmap(width: Int, height: Int) = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888).also { draw(Canvas(it)) }!! private fun TextInterpolator.toBitmap(width: Int, height: Int) = - Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888).also { draw(Canvas(it)) }
\ No newline at end of file + Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888).also { draw(Canvas(it)) } diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/AnimatableClockControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/keyguard/AnimatableClockControllerTest.java index 5a4bb86dba45..df506b479b88 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/AnimatableClockControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/AnimatableClockControllerTest.java @@ -34,12 +34,12 @@ import android.view.View; import androidx.test.filters.SmallTest; import com.android.keyguard.AnimatableClockController; -import com.android.keyguard.AnimatableClockView; import com.android.keyguard.KeyguardUpdateMonitor; import com.android.settingslib.Utils; import com.android.systemui.SysuiTestCase; import com.android.systemui.broadcast.BroadcastDispatcher; import com.android.systemui.plugins.statusbar.StatusBarStateController; +import com.android.systemui.shared.clocks.AnimatableClockView; import com.android.systemui.statusbar.policy.BatteryController; import org.junit.After; diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ChannelEditorDialogControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ChannelEditorDialogControllerTest.kt index 214ba16dfc44..64d025628754 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ChannelEditorDialogControllerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ChannelEditorDialogControllerTest.kt @@ -98,7 +98,7 @@ class ChannelEditorDialogControllerTest : SysuiTestCase() { @Test fun testPrepareDialogForApp_onlyDefaultChannel() { - group.channels = listOf(channelDefault) + group.addChannel(channelDefault) controller.prepareDialogForApp(TEST_APP_NAME, TEST_PACKAGE_NAME, TEST_UID, setOf(channelDefault), appIcon, clickListener) diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesCommandQueueCallbacksTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesCommandQueueCallbacksTest.java index d79f3361408b..7b7a48be5b17 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesCommandQueueCallbacksTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesCommandQueueCallbacksTest.java @@ -22,26 +22,28 @@ import static org.mockito.ArgumentMatchers.anyBoolean; import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.Mockito.never; import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyZeroInteractions; import static org.mockito.Mockito.when; import android.app.StatusBarManager; import android.os.PowerManager; import android.os.Vibrator; import android.testing.AndroidTestingRunner; +import android.view.InsetsVisibilities; import androidx.test.filters.SmallTest; import com.android.internal.logging.MetricsLogger; import com.android.internal.logging.testing.FakeMetricsLogger; +import com.android.internal.statusbar.LetterboxDetails; +import com.android.internal.view.AppearanceRegion; import com.android.keyguard.KeyguardUpdateMonitor; import com.android.systemui.SysuiTestCase; import com.android.systemui.assist.AssistManager; import com.android.systemui.keyguard.WakefulnessLifecycle; import com.android.systemui.shade.NotificationPanelViewController; -import com.android.systemui.shade.NotificationShadeWindowView; import com.android.systemui.statusbar.CommandQueue; import com.android.systemui.statusbar.DisableFlagsLogger; -import com.android.systemui.statusbar.StatusBarStateControllerImpl; import com.android.systemui.statusbar.VibratorHelper; import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController; import com.android.systemui.statusbar.policy.DeviceProvisionedController; @@ -60,6 +62,7 @@ import java.util.Optional; @SmallTest @RunWith(AndroidTestingRunner.class) public class CentralSurfacesCommandQueueCallbacksTest extends SysuiTestCase { + @Mock private CentralSurfaces mCentralSurfaces; @Mock private ShadeController mShadeController; @Mock private CommandQueue mCommandQueue; @@ -74,14 +77,12 @@ public class CentralSurfacesCommandQueueCallbacksTest extends SysuiTestCase { @Mock private StatusBarKeyguardViewManager mStatusBarKeyguardViewManager; @Mock private AssistManager mAssistManager; @Mock private DozeServiceHost mDozeServiceHost; - @Mock private StatusBarStateControllerImpl mStatusBarStateController; - @Mock private NotificationShadeWindowView mNotificationShadeWindowView; @Mock private NotificationStackScrollLayoutController mNotificationStackScrollLayoutController; @Mock private PowerManager mPowerManager; @Mock private VibratorHelper mVibratorHelper; @Mock private Vibrator mVibrator; - @Mock private LightBarController mLightBarController; @Mock private StatusBarHideIconsForBouncerManager mStatusBarHideIconsForBouncerManager; + @Mock private SystemBarAttributesListener mSystemBarAttributesListener; CentralSurfacesCommandQueueCallbacks mSbcqCallbacks; @@ -106,16 +107,14 @@ public class CentralSurfacesCommandQueueCallbacksTest extends SysuiTestCase { mStatusBarKeyguardViewManager, mAssistManager, mDozeServiceHost, - mStatusBarStateController, - mNotificationShadeWindowView, mNotificationStackScrollLayoutController, mStatusBarHideIconsForBouncerManager, mPowerManager, mVibratorHelper, Optional.of(mVibrator), - mLightBarController, new DisableFlagsLogger(), - DEFAULT_DISPLAY); + DEFAULT_DISPLAY, + mSystemBarAttributesListener); when(mDeviceProvisionedController.isCurrentUserSetup()).thenReturn(true); when(mRemoteInputQuickSettingsDisabler.adjustDisableFlags(anyInt())) @@ -170,5 +169,59 @@ public class CentralSurfacesCommandQueueCallbacksTest extends SysuiTestCase { verify(mDozeServiceHost).setAlwaysOnSuppressed(false); } + @Test + public void onSystemBarAttributesChanged_forwardsToSysBarAttrsListener() { + int displayId = DEFAULT_DISPLAY; + int appearance = 123; + AppearanceRegion[] appearanceRegions = new AppearanceRegion[]{}; + boolean navbarColorManagedByIme = true; + int behavior = 456; + InsetsVisibilities requestedVisibilities = new InsetsVisibilities(); + String packageName = "test package name"; + LetterboxDetails[] letterboxDetails = new LetterboxDetails[]{}; + + mSbcqCallbacks.onSystemBarAttributesChanged( + displayId, + appearance, + appearanceRegions, + navbarColorManagedByIme, + behavior, + requestedVisibilities, + packageName, + letterboxDetails); + + verify(mSystemBarAttributesListener).onSystemBarAttributesChanged( + displayId, + appearance, + appearanceRegions, + navbarColorManagedByIme, + behavior, + requestedVisibilities, + packageName, + letterboxDetails + ); + } + @Test + public void onSystemBarAttributesChanged_differentDisplayId_doesNotForwardToAttrsListener() { + int appearance = 123; + AppearanceRegion[] appearanceRegions = new AppearanceRegion[]{}; + boolean navbarColorManagedByIme = true; + int behavior = 456; + InsetsVisibilities requestedVisibilities = new InsetsVisibilities(); + String packageName = "test package name"; + LetterboxDetails[] letterboxDetails = new LetterboxDetails[]{}; + + mSbcqCallbacks.onSystemBarAttributesChanged( + DEFAULT_DISPLAY + 1, + appearance, + appearanceRegions, + navbarColorManagedByIme, + behavior, + requestedVisibilities, + packageName, + letterboxDetails); + + verifyZeroInteractions(mSystemBarAttributesListener); + } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/SystemBarAttributesListenerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/SystemBarAttributesListenerTest.kt new file mode 100644 index 000000000000..fa7b2599c108 --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/SystemBarAttributesListenerTest.kt @@ -0,0 +1,250 @@ +package com.android.systemui.statusbar.phone + +import android.graphics.Rect +import android.testing.AndroidTestingRunner +import android.view.Display +import android.view.InsetsVisibilities +import android.view.WindowInsetsController +import android.view.WindowInsetsController.APPEARANCE_LIGHT_NAVIGATION_BARS +import android.view.WindowInsetsController.APPEARANCE_LIGHT_STATUS_BARS +import android.view.WindowInsetsController.APPEARANCE_LOW_PROFILE_BARS +import android.view.WindowInsetsController.Appearance +import androidx.test.filters.SmallTest +import com.android.internal.statusbar.LetterboxDetails +import com.android.internal.view.AppearanceRegion +import com.android.systemui.SysuiTestCase +import com.android.systemui.dump.DumpManager +import com.android.systemui.flags.FeatureFlags +import com.android.systemui.flags.Flags +import com.android.systemui.statusbar.SysuiStatusBarStateController +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith +import org.mockito.ArgumentMatchers.any +import org.mockito.ArgumentMatchers.anyBoolean +import org.mockito.ArgumentMatchers.anyInt +import org.mockito.ArgumentMatchers.eq +import org.mockito.Mock +import org.mockito.Mockito +import org.mockito.Mockito.reset +import org.mockito.Mockito.verify +import org.mockito.Mockito.verifyZeroInteractions +import org.mockito.Mockito.`when` as whenever +import org.mockito.MockitoAnnotations + +@SmallTest +@RunWith(AndroidTestingRunner::class) +class SystemBarAttributesListenerTest : SysuiTestCase() { + + @Mock private lateinit var dumpManager: DumpManager + @Mock private lateinit var lightBarController: LightBarController + @Mock private lateinit var statusBarStateController: SysuiStatusBarStateController + @Mock private lateinit var letterboxAppearanceCalculator: LetterboxAppearanceCalculator + @Mock private lateinit var featureFlags: FeatureFlags + @Mock private lateinit var centralSurfaces: CentralSurfaces + + private lateinit var sysBarAttrsListener: SystemBarAttributesListener + + @Before + fun setUp() { + MockitoAnnotations.initMocks(this) + + whenever( + letterboxAppearanceCalculator.getLetterboxAppearance( + anyInt(), anyObject(), anyObject())) + .thenReturn(TEST_LETTERBOX_APPEARANCE) + + sysBarAttrsListener = + SystemBarAttributesListener( + centralSurfaces, + featureFlags, + letterboxAppearanceCalculator, + statusBarStateController, + lightBarController, + dumpManager) + } + + @Test + fun onSysBarAttrsChanged_forwardsAppearanceToCentralSurfaces() { + val appearance = APPEARANCE_LIGHT_STATUS_BARS or APPEARANCE_LIGHT_NAVIGATION_BARS + + changeSysBarAttrs(appearance) + + verify(centralSurfaces).setAppearance(appearance) + } + + @Test + fun onSysBarAttrsChanged_flagTrue_forwardsLetterboxAppearanceToCentralSurfaces() { + whenever(featureFlags.isEnabled(Flags.STATUS_BAR_LETTERBOX_APPEARANCE)).thenReturn(true) + + changeSysBarAttrs(TEST_APPEARANCE, TEST_LETTERBOX_DETAILS) + + verify(centralSurfaces).setAppearance(TEST_LETTERBOX_APPEARANCE.appearance) + } + + @Test + fun onSysBarAttrsChanged_flagTrue_noLetterbox_forwardsOriginalAppearanceToCtrlSrfcs() { + whenever(featureFlags.isEnabled(Flags.STATUS_BAR_LETTERBOX_APPEARANCE)).thenReturn(true) + + changeSysBarAttrs(TEST_APPEARANCE, arrayOf<LetterboxDetails>()) + + verify(centralSurfaces).setAppearance(TEST_APPEARANCE) + } + + @Test + fun onSysBarAttrsChanged_forwardsAppearanceToStatusBarStateController() { + changeSysBarAttrs(TEST_APPEARANCE) + + verify(statusBarStateController) + .setSystemBarAttributes(eq(TEST_APPEARANCE), anyInt(), any(), any()) + } + + @Test + fun onSysBarAttrsChanged_flagTrue_forwardsLetterboxAppearanceToStatusBarStateCtrl() { + whenever(featureFlags.isEnabled(Flags.STATUS_BAR_LETTERBOX_APPEARANCE)).thenReturn(true) + + changeSysBarAttrs(TEST_APPEARANCE, TEST_LETTERBOX_DETAILS) + + verify(statusBarStateController) + .setSystemBarAttributes( + eq(TEST_LETTERBOX_APPEARANCE.appearance), anyInt(), any(), any()) + } + + @Test + fun onSysBarAttrsChanged_forwardsAppearanceToLightBarController() { + changeSysBarAttrs(TEST_APPEARANCE, TEST_APPEARANCE_REGIONS) + + verify(lightBarController) + .onStatusBarAppearanceChanged( + eq(TEST_APPEARANCE_REGIONS), anyBoolean(), anyInt(), anyBoolean()) + } + + @Test + fun onSysBarAttrsChanged_flagTrue_forwardsLetterboxAppearanceToLightBarController() { + whenever(featureFlags.isEnabled(Flags.STATUS_BAR_LETTERBOX_APPEARANCE)).thenReturn(true) + + changeSysBarAttrs(TEST_APPEARANCE, TEST_APPEARANCE_REGIONS, TEST_LETTERBOX_DETAILS) + + verify(lightBarController) + .onStatusBarAppearanceChanged( + eq(TEST_LETTERBOX_APPEARANCE.appearanceRegions), + anyBoolean(), + anyInt(), + anyBoolean()) + } + + @Test + fun onStatusBarBoundsChanged_forwardsLetterboxAppearanceToStatusBarStateController() { + whenever(featureFlags.isEnabled(Flags.STATUS_BAR_LETTERBOX_APPEARANCE)).thenReturn(true) + changeSysBarAttrs(TEST_APPEARANCE, TEST_APPEARANCE_REGIONS, TEST_LETTERBOX_DETAILS) + reset(centralSurfaces, lightBarController, statusBarStateController) + + sysBarAttrsListener.onStatusBarBoundsChanged() + + verify(statusBarStateController) + .setSystemBarAttributes( + eq(TEST_LETTERBOX_APPEARANCE.appearance), anyInt(), any(), any()) + } + + @Test + fun onStatusBarBoundsChanged_forwardsLetterboxAppearanceToLightBarController() { + whenever(featureFlags.isEnabled(Flags.STATUS_BAR_LETTERBOX_APPEARANCE)).thenReturn(true) + changeSysBarAttrs(TEST_APPEARANCE, TEST_APPEARANCE_REGIONS, TEST_LETTERBOX_DETAILS) + reset(centralSurfaces, lightBarController, statusBarStateController) + + sysBarAttrsListener.onStatusBarBoundsChanged() + + verify(lightBarController) + .onStatusBarAppearanceChanged( + eq(TEST_LETTERBOX_APPEARANCE.appearanceRegions), + anyBoolean(), + anyInt(), + anyBoolean()) + } + + @Test + fun onStatusBarBoundsChanged_forwardsLetterboxAppearanceToCentralSurfaces() { + whenever(featureFlags.isEnabled(Flags.STATUS_BAR_LETTERBOX_APPEARANCE)).thenReturn(true) + changeSysBarAttrs(TEST_APPEARANCE, TEST_APPEARANCE_REGIONS, TEST_LETTERBOX_DETAILS) + reset(centralSurfaces, lightBarController, statusBarStateController) + + sysBarAttrsListener.onStatusBarBoundsChanged() + + verify(centralSurfaces).setAppearance(TEST_LETTERBOX_APPEARANCE.appearance) + } + + @Test + fun onStatusBarBoundsChanged_previousCallEmptyLetterbox_doesNothing() { + whenever(featureFlags.isEnabled(Flags.STATUS_BAR_LETTERBOX_APPEARANCE)).thenReturn(true) + changeSysBarAttrs(TEST_APPEARANCE, TEST_APPEARANCE_REGIONS, arrayOf()) + reset(centralSurfaces, lightBarController, statusBarStateController) + + sysBarAttrsListener.onStatusBarBoundsChanged() + + verifyZeroInteractions(centralSurfaces, lightBarController, statusBarStateController) + } + + @Test + fun onStatusBarBoundsChanged_flagFalse_doesNothing() { + whenever(featureFlags.isEnabled(Flags.STATUS_BAR_LETTERBOX_APPEARANCE)).thenReturn(false) + changeSysBarAttrs(TEST_APPEARANCE, TEST_APPEARANCE_REGIONS, TEST_LETTERBOX_DETAILS) + reset(centralSurfaces, lightBarController, statusBarStateController) + + sysBarAttrsListener.onStatusBarBoundsChanged() + + verifyZeroInteractions(centralSurfaces, lightBarController, statusBarStateController) + } + + private fun changeSysBarAttrs(@Appearance appearance: Int) { + changeSysBarAttrs(appearance, arrayOf<LetterboxDetails>()) + } + + private fun changeSysBarAttrs( + @Appearance appearance: Int, + letterboxDetails: Array<LetterboxDetails> + ) { + changeSysBarAttrs(appearance, arrayOf(), letterboxDetails) + } + + private fun changeSysBarAttrs( + @Appearance appearance: Int, + appearanceRegions: Array<AppearanceRegion> + ) { + changeSysBarAttrs(appearance, appearanceRegions, arrayOf()) + } + + private fun changeSysBarAttrs( + @Appearance appearance: Int, + appearanceRegions: Array<AppearanceRegion>, + letterboxDetails: Array<LetterboxDetails> + ) { + sysBarAttrsListener.onSystemBarAttributesChanged( + Display.DEFAULT_DISPLAY, + appearance, + appearanceRegions, + /* navbarColorManagedByIme= */ false, + WindowInsetsController.BEHAVIOR_DEFAULT, + InsetsVisibilities(), + "package name", + letterboxDetails) + } + + companion object { + private const val TEST_APPEARANCE = + APPEARANCE_LIGHT_STATUS_BARS or APPEARANCE_LIGHT_NAVIGATION_BARS + private val TEST_APPEARANCE_REGION = AppearanceRegion(TEST_APPEARANCE, Rect(0, 0, 150, 300)) + private val TEST_APPEARANCE_REGIONS = arrayOf(TEST_APPEARANCE_REGION) + private val TEST_LETTERBOX_DETAILS = + arrayOf( + LetterboxDetails( + /* letterboxInnerBounds= */ Rect(0, 0, 0, 0), + /* letterboxFullBounds= */ Rect(0, 0, 0, 0), + /* appAppearance= */ 0)) + private val TEST_LETTERBOX_APPEARANCE = + LetterboxAppearance(/* appearance= */ APPEARANCE_LOW_PROFILE_BARS, arrayOf()) + } +} + +private fun <T> anyObject(): T { + return Mockito.anyObject<T>() +} diff --git a/services/core/java/com/android/server/am/AppRestrictionController.java b/services/core/java/com/android/server/am/AppRestrictionController.java index 6076eb1f69ee..7a09ce722f94 100644 --- a/services/core/java/com/android/server/am/AppRestrictionController.java +++ b/services/core/java/com/android/server/am/AppRestrictionController.java @@ -2790,13 +2790,6 @@ public final class AppRestrictionController { if (isOnSystemDeviceIdleAllowlist(uid)) { return REASON_SYSTEM_ALLOW_LISTED; } - if (isOnDeviceIdleAllowlist(uid)) { - return REASON_ALLOWLISTED_PACKAGE; - } - final ActivityManagerInternal am = mInjector.getActivityManagerInternal(); - if (am.isAssociatedCompanionApp(UserHandle.getUserId(uid), uid)) { - return REASON_COMPANION_DEVICE_MANAGER; - } if (UserManager.isDeviceInDemoMode(mContext)) { return REASON_DEVICE_DEMO_MODE; } @@ -2805,6 +2798,7 @@ public final class AppRestrictionController { .hasUserRestriction(UserManager.DISALLOW_APPS_CONTROL, userId)) { return REASON_DISALLOW_APPS_CONTROL; } + final ActivityManagerInternal am = mInjector.getActivityManagerInternal(); if (am.isDeviceOwner(uid)) { return REASON_DEVICE_OWNER; } @@ -2822,14 +2816,9 @@ public final class AppRestrictionController { final AppOpsManager appOpsManager = mInjector.getAppOpsManager(); final PackageManagerInternal pm = mInjector.getPackageManagerInternal(); final AppStandbyInternal appStandbyInternal = mInjector.getAppStandbyInternal(); + // Check each packages to see if any of them is in the "fixed" exemption cases. for (String pkg : packages) { - if (appOpsManager.checkOpNoThrow(AppOpsManager.OP_ACTIVATE_VPN, - uid, pkg) == AppOpsManager.MODE_ALLOWED) { - return REASON_OP_ACTIVATE_VPN; - } else if (appOpsManager.checkOpNoThrow(AppOpsManager.OP_ACTIVATE_PLATFORM_VPN, - uid, pkg) == AppOpsManager.MODE_ALLOWED) { - return REASON_OP_ACTIVATE_PLATFORM_VPN; - } else if (isSystemModule(pkg)) { + if (isSystemModule(pkg)) { return REASON_SYSTEM_MODULE; } else if (isCarrierApp(pkg)) { return REASON_CARRIER_PRIVILEGED_APP; @@ -2843,6 +2832,16 @@ public final class AppRestrictionController { return REASON_ACTIVE_DEVICE_ADMIN; } } + // Loop the packages again, and check the user-configurable exemptions. + for (String pkg : packages) { + if (appOpsManager.checkOpNoThrow(AppOpsManager.OP_ACTIVATE_VPN, + uid, pkg) == AppOpsManager.MODE_ALLOWED) { + return REASON_OP_ACTIVATE_VPN; + } else if (appOpsManager.checkOpNoThrow(AppOpsManager.OP_ACTIVATE_PLATFORM_VPN, + uid, pkg) == AppOpsManager.MODE_ALLOWED) { + return REASON_OP_ACTIVATE_PLATFORM_VPN; + } + } } if (isRoleHeldByUid(RoleManager.ROLE_DIALER, uid)) { return REASON_ROLE_DIALER; @@ -2850,6 +2849,12 @@ public final class AppRestrictionController { if (isRoleHeldByUid(RoleManager.ROLE_EMERGENCY, uid)) { return REASON_ROLE_EMERGENCY; } + if (isOnDeviceIdleAllowlist(uid)) { + return REASON_ALLOWLISTED_PACKAGE; + } + if (am.isAssociatedCompanionApp(UserHandle.getUserId(uid), uid)) { + return REASON_COMPANION_DEVICE_MANAGER; + } return REASON_DENIED; } diff --git a/services/core/java/com/android/server/audio/SpatializerHelper.java b/services/core/java/com/android/server/audio/SpatializerHelper.java index cd5960ffbf32..1def72b2987f 100644 --- a/services/core/java/com/android/server/audio/SpatializerHelper.java +++ b/services/core/java/com/android/server/audio/SpatializerHelper.java @@ -46,6 +46,8 @@ import android.util.Log; import android.util.Pair; import android.util.SparseIntArray; +import com.android.internal.annotations.GuardedBy; + import java.io.PrintWriter; import java.util.ArrayList; import java.util.List; @@ -103,10 +105,6 @@ public class SpatializerHelper { AudioDeviceInfo.TYPE_BLE_BROADCAST }; - private static final int[] WIRELESS_SPEAKER_TYPES = { - AudioDeviceInfo.TYPE_BLE_SPEAKER, - }; - // Spatializer state machine private static final int STATE_UNINITIALIZED = 0; private static final int STATE_NOT_SUPPORTED = 1; @@ -166,6 +164,7 @@ public class SpatializerHelper { * List of devices where Spatial Audio is possible. Each device can be enabled or disabled * (== user choice to use or not) */ + @GuardedBy("this") private final ArrayList<SADeviceState> mSADevices = new ArrayList<>(0); //------------------------------------------------------ @@ -520,30 +519,30 @@ public class SpatializerHelper { * set to true if the device is added to the list, otherwise, if already * present, the setting is left untouched. */ + @GuardedBy("this") private void addCompatibleAudioDevice(@NonNull AudioDeviceAttributes ada, boolean forceEnable) { if (!isDeviceCompatibleWithSpatializationModes(ada)) { return; } loglogi("addCompatibleAudioDevice: dev=" + ada); - boolean isInList = false; + final SADeviceState deviceState = findDeviceStateForAudioDeviceAttributes(ada); SADeviceState deviceUpdated = null; // non-null on update. - - for (SADeviceState deviceState : mSADevices) { - if (deviceState.matchesAudioDeviceAttributes(ada)) { - isInList = true; - if (forceEnable) { - deviceState.mEnabled = true; - deviceUpdated = deviceState; - } - break; + if (deviceState != null) { + if (forceEnable && !deviceState.mEnabled) { + deviceUpdated = deviceState; + deviceUpdated.mEnabled = true; } - } - if (!isInList) { - final SADeviceState deviceState = new SADeviceState(ada.getType(), ada.getAddress()); - deviceState.mEnabled = true; - mSADevices.add(deviceState); - deviceUpdated = deviceState; + } else { + // When adding, force the device type to be a canonical one. + final int canonicalDeviceType = getCanonicalDeviceType(ada.getType()); + if (canonicalDeviceType == AudioDeviceInfo.TYPE_UNKNOWN) { + Log.e(TAG, "addCompatibleAudioDevice with incompatible AudioDeviceAttributes " + + ada); + return; + } + deviceUpdated = new SADeviceState(canonicalDeviceType, ada.getAddress()); + mSADevices.add(deviceUpdated); } if (deviceUpdated != null) { onRoutingUpdated(); @@ -574,90 +573,95 @@ public class SpatializerHelper { synchronized void removeCompatibleAudioDevice(@NonNull AudioDeviceAttributes ada) { loglogi("removeCompatibleAudioDevice: dev=" + ada); - SADeviceState deviceUpdated = null; // non-null on update. - for (SADeviceState deviceState : mSADevices) { - if (deviceState.matchesAudioDeviceAttributes(ada)) { - deviceState.mEnabled = false; - deviceUpdated = deviceState; - break; - } - } - if (deviceUpdated != null) { + final SADeviceState deviceState = findDeviceStateForAudioDeviceAttributes(ada); + if (deviceState != null && deviceState.mEnabled) { + deviceState.mEnabled = false; onRoutingUpdated(); mAudioService.persistSpatialAudioDeviceSettings(); - logDeviceState(deviceUpdated, "removeCompatibleAudioDevice"); + logDeviceState(deviceState, "removeCompatibleAudioDevice"); } } /** + * Returns a possibly aliased device type which is used + * for spatial audio settings (or TYPE_UNKNOWN if it doesn't exist). + */ + private static @AudioDeviceInfo.AudioDeviceType int getCanonicalDeviceType(int deviceType) { + if (isWireless(deviceType)) return deviceType; + + final int spatMode = SPAT_MODE_FOR_DEVICE_TYPE.get(deviceType, Integer.MIN_VALUE); + if (spatMode == SpatializationMode.SPATIALIZER_TRANSAURAL) { + return AudioDeviceInfo.TYPE_BUILTIN_SPEAKER; + } else if (spatMode == SpatializationMode.SPATIALIZER_BINAURAL) { + return AudioDeviceInfo.TYPE_WIRED_HEADPHONES; + } + return AudioDeviceInfo.TYPE_UNKNOWN; + } + + /** + * Returns the Spatial Audio device state for an audio device attributes + * or null if it does not exist. + */ + @GuardedBy("this") + @Nullable + private SADeviceState findDeviceStateForAudioDeviceAttributes(AudioDeviceAttributes ada) { + final int deviceType = ada.getType(); + final boolean isWireless = isWireless(deviceType); + final int canonicalDeviceType = getCanonicalDeviceType(deviceType); + + for (SADeviceState deviceState : mSADevices) { + if (deviceState.mDeviceType == canonicalDeviceType + && (!isWireless || ada.getAddress().equals(deviceState.mDeviceAddress))) { + return deviceState; + } + } + return null; + } + + /** * Return if Spatial Audio is enabled and available for the given device * @param ada * @return a pair of boolean, 1/ enabled? 2/ available? */ private synchronized Pair<Boolean, Boolean> evaluateState(AudioDeviceAttributes ada) { - // if not a wireless device, this value will be overwritten to map the type - // to TYPE_BUILTIN_SPEAKER or TYPE_WIRED_HEADPHONES - @AudioDeviceInfo.AudioDeviceType int deviceType = ada.getType(); - - // if not a wireless device: find if media device is in the speaker, wired headphones - if (!isWireless(deviceType)) { - // is the device type capable of doing SA? - if (!mSACapableDeviceTypes.contains(deviceType)) { - Log.i(TAG, "Device incompatible with Spatial Audio dev:" + ada); - return new Pair<>(false, false); - } - // what spatialization mode to use for this device? - final int spatMode = SPAT_MODE_FOR_DEVICE_TYPE.get(deviceType, Integer.MIN_VALUE); - if (spatMode == Integer.MIN_VALUE) { - // error case, device not found - Log.e(TAG, "no spatialization mode found for device type:" + deviceType); - return new Pair<>(false, false); - } - // map the spatialization mode to the SPEAKER or HEADPHONES device - if (spatMode == SpatializationMode.SPATIALIZER_TRANSAURAL) { - deviceType = AudioDeviceInfo.TYPE_BUILTIN_SPEAKER; - } else { - deviceType = AudioDeviceInfo.TYPE_WIRED_HEADPHONES; - } - } else { // wireless device - if (isWirelessSpeaker(deviceType) && !mTransauralSupported) { - Log.i(TAG, "Device incompatible with Spatial Audio (no transaural) dev:" - + ada); - return new Pair<>(false, false); - } - if (!mBinauralSupported) { - Log.i(TAG, "Device incompatible with Spatial Audio (no binaural) dev:" - + ada); - return new Pair<>(false, false); - } + final @AudioDeviceInfo.AudioDeviceType int deviceType = ada.getType(); + // is the device type capable of doing SA? + if (!mSACapableDeviceTypes.contains(deviceType)) { + Log.i(TAG, "Device incompatible with Spatial Audio dev:" + ada); + return new Pair<>(false, false); } - - boolean enabled = false; - boolean available = false; - for (SADeviceState deviceState : mSADevices) { - if (deviceState.matchesAudioDeviceAttributes(ada)) { - available = true; - enabled = deviceState.mEnabled; - break; - } + // what spatialization mode to use for this device? + final int spatMode = SPAT_MODE_FOR_DEVICE_TYPE.get(deviceType, Integer.MIN_VALUE); + if (spatMode == Integer.MIN_VALUE) { + // error case, device not found + Log.e(TAG, "no spatialization mode found for device type:" + deviceType); + return new Pair<>(false, false); + } + final SADeviceState deviceState = findDeviceStateForAudioDeviceAttributes(ada); + if (deviceState == null) { + // no matching device state? + Log.i(TAG, "no spatialization device state found for Spatial Audio device:" + ada); + return new Pair<>(false, false); } - return new Pair<>(enabled, available); + // found the matching device state. + return new Pair<>(deviceState.mEnabled, true /* available */); } private synchronized void addWirelessDeviceIfNew(@NonNull AudioDeviceAttributes ada) { if (!isDeviceCompatibleWithSpatializationModes(ada)) { return; } - boolean knownDevice = false; - for (SADeviceState deviceState : mSADevices) { - if (deviceState.matchesAudioDeviceAttributes(ada)) { - knownDevice = true; - break; + if (findDeviceStateForAudioDeviceAttributes(ada) == null) { + // wireless device types should be canonical, but we translate to be sure. + final int canonicalDeviceType = getCanonicalDeviceType((ada.getType())); + if (canonicalDeviceType == AudioDeviceInfo.TYPE_UNKNOWN) { + Log.e(TAG, "addWirelessDeviceIfNew with incompatible AudioDeviceAttributes " + + ada); + return; } - } - if (!knownDevice) { - final SADeviceState deviceState = new SADeviceState(ada.getType(), ada.getAddress()); + final SADeviceState deviceState = + new SADeviceState(canonicalDeviceType, ada.getAddress()); mSADevices.add(deviceState); mAudioService.persistSpatialAudioDeviceSettings(); logDeviceState(deviceState, "addWirelessDeviceIfNew"); // may be updated later. @@ -699,12 +703,7 @@ public class SpatializerHelper { if (ada.getRole() != AudioDeviceAttributes.ROLE_OUTPUT) { return false; } - for (SADeviceState deviceState : mSADevices) { - if (deviceState.matchesAudioDeviceAttributes(ada)) { - return true; - } - } - return false; + return findDeviceStateForAudioDeviceAttributes(ada) != null; } private synchronized boolean canBeSpatializedOnDevice(@NonNull AudioAttributes attributes, @@ -1086,20 +1085,18 @@ public class SpatializerHelper { Log.v(TAG, "no headtracking support, ignoring setHeadTrackerEnabled to " + enabled + " for " + ada); } - for (SADeviceState deviceState : mSADevices) { - if (deviceState.matchesAudioDeviceAttributes(ada)) { - if (!deviceState.mHasHeadTracker) { - Log.e(TAG, "Called setHeadTrackerEnabled enabled:" + enabled - + " device:" + ada + " on a device without headtracker"); - return; - } - Log.i(TAG, "setHeadTrackerEnabled enabled:" + enabled + " device:" + ada); - deviceState.mHeadTrackerEnabled = enabled; - mAudioService.persistSpatialAudioDeviceSettings(); - logDeviceState(deviceState, "setHeadTrackerEnabled"); - break; - } + final SADeviceState deviceState = findDeviceStateForAudioDeviceAttributes(ada); + if (deviceState == null) return; + if (!deviceState.mHasHeadTracker) { + Log.e(TAG, "Called setHeadTrackerEnabled enabled:" + enabled + + " device:" + ada + " on a device without headtracker"); + return; } + Log.i(TAG, "setHeadTrackerEnabled enabled:" + enabled + " device:" + ada); + deviceState.mHeadTrackerEnabled = enabled; + mAudioService.persistSpatialAudioDeviceSettings(); + logDeviceState(deviceState, "setHeadTrackerEnabled"); + // check current routing to see if it affects the headtracking mode if (ROUTING_DEVICES[0].getType() == ada.getType() && ROUTING_DEVICES[0].getAddress().equals(ada.getAddress())) { @@ -1113,12 +1110,8 @@ public class SpatializerHelper { Log.v(TAG, "no headtracking support, hasHeadTracker always false for " + ada); return false; } - for (SADeviceState deviceState : mSADevices) { - if (deviceState.matchesAudioDeviceAttributes(ada)) { - return deviceState.mHasHeadTracker; - } - } - return false; + final SADeviceState deviceState = findDeviceStateForAudioDeviceAttributes(ada); + return deviceState != null && deviceState.mHasHeadTracker; } /** @@ -1131,15 +1124,14 @@ public class SpatializerHelper { Log.v(TAG, "no headtracking support, setHasHeadTracker always false for " + ada); return false; } - for (SADeviceState deviceState : mSADevices) { - if (deviceState.matchesAudioDeviceAttributes(ada)) { - if (!deviceState.mHasHeadTracker) { - deviceState.mHasHeadTracker = true; - mAudioService.persistSpatialAudioDeviceSettings(); - logDeviceState(deviceState, "setHasHeadTracker"); - } - return deviceState.mHeadTrackerEnabled; + final SADeviceState deviceState = findDeviceStateForAudioDeviceAttributes(ada); + if (deviceState != null) { + if (!deviceState.mHasHeadTracker) { + deviceState.mHasHeadTracker = true; + mAudioService.persistSpatialAudioDeviceSettings(); + logDeviceState(deviceState, "setHasHeadTracker"); } + return deviceState.mHeadTrackerEnabled; } Log.e(TAG, "setHasHeadTracker: device not found for:" + ada); return false; @@ -1150,15 +1142,9 @@ public class SpatializerHelper { Log.v(TAG, "no headtracking support, isHeadTrackerEnabled always false for " + ada); return false; } - for (SADeviceState deviceState : mSADevices) { - if (deviceState.matchesAudioDeviceAttributes(ada)) { - if (!deviceState.mHasHeadTracker) { - return false; - } - return deviceState.mHeadTrackerEnabled; - } - } - return false; + final SADeviceState deviceState = findDeviceStateForAudioDeviceAttributes(ada); + return deviceState != null + && deviceState.mHasHeadTracker && deviceState.mHeadTrackerEnabled; } synchronized boolean isHeadTrackerAvailable() { @@ -1582,12 +1568,6 @@ public class SpatializerHelper { mDeviceType, mDeviceAddress == null ? "" : mDeviceAddress); } - public boolean matchesAudioDeviceAttributes(AudioDeviceAttributes ada) { - final int deviceType = ada.getType(); - final boolean wireless = isWireless(deviceType); - return (deviceType == mDeviceType) - && (!wireless || ada.getAddress().equals(mDeviceAddress)); - } } /*package*/ synchronized String getSADeviceSettings() { @@ -1608,7 +1588,10 @@ public class SpatializerHelper { // small list, not worth overhead of Arrays.stream(devSettings) for (String setting : devSettings) { SADeviceState devState = SADeviceState.fromPersistedString(setting); + // Note if the device is not compatible with spatialization mode + // or the device type is not canonical, it is ignored. if (devState != null + && devState.mDeviceType == getCanonicalDeviceType(devState.mDeviceType) && isDeviceCompatibleWithSpatializationModes( devState.getAudioDeviceAttributes())) { mSADevices.add(devState); @@ -1645,15 +1628,6 @@ public class SpatializerHelper { return false; } - private static boolean isWirelessSpeaker(@AudioDeviceInfo.AudioDeviceType int deviceType) { - for (int type : WIRELESS_SPEAKER_TYPES) { - if (type == deviceType) { - return true; - } - } - return false; - } - private int getHeadSensorHandleUpdateTracker() { int headHandle = -1; UUID routingDeviceUuid = mAudioService.getDeviceSensorUuid(ROUTING_DEVICES[0]); diff --git a/services/core/java/com/android/server/biometrics/sensors/BiometricStateCallback.java b/services/core/java/com/android/server/biometrics/sensors/BiometricStateCallback.java index 1b24aa896652..0d789f7a1840 100644 --- a/services/core/java/com/android/server/biometrics/sensors/BiometricStateCallback.java +++ b/services/core/java/com/android/server/biometrics/sensors/BiometricStateCallback.java @@ -108,6 +108,17 @@ public class BiometricStateCallback implements ClientMonitorCallback { } } + @Override + public void onBiometricAction(@BiometricStateListener.Action int action) { + for (IBiometricStateListener listener : mBiometricStateListeners) { + try { + listener.onBiometricAction(action); + } catch (RemoteException e) { + Slog.e(TAG, "Remote exception in onBiometricAction", e); + } + } + } + /** * This should be invoked when: * 1) Enrolled --> None-enrolled diff --git a/services/core/java/com/android/server/biometrics/sensors/ClientMonitorCallback.java b/services/core/java/com/android/server/biometrics/sensors/ClientMonitorCallback.java index 8ea4ee911cb1..4417f9270d9c 100644 --- a/services/core/java/com/android/server/biometrics/sensors/ClientMonitorCallback.java +++ b/services/core/java/com/android/server/biometrics/sensors/ClientMonitorCallback.java @@ -31,6 +31,11 @@ public interface ClientMonitorCallback { default void onClientStarted(@NonNull BaseClientMonitor clientMonitor) {} /** + * Invoked when a biometric action has occurred. + */ + default void onBiometricAction(int action) {} + + /** * Invoked when the ClientMonitor operation is complete. This abstracts away asynchronous * (i.e. Authenticate, Enroll, Enumerate, Remove) and synchronous (i.e. generateChallenge, * revokeChallenge) so that a scheduler can process ClientMonitors regardless of their diff --git a/services/core/java/com/android/server/biometrics/sensors/ClientMonitorCompositeCallback.java b/services/core/java/com/android/server/biometrics/sensors/ClientMonitorCompositeCallback.java index b82f5fa129d6..07041bf8ce3d 100644 --- a/services/core/java/com/android/server/biometrics/sensors/ClientMonitorCompositeCallback.java +++ b/services/core/java/com/android/server/biometrics/sensors/ClientMonitorCompositeCallback.java @@ -44,6 +44,13 @@ public class ClientMonitorCompositeCallback implements ClientMonitorCallback { } @Override + public final void onBiometricAction(int action) { + for (int i = 0; i < mCallbacks.size(); i++) { + mCallbacks.get(i).onBiometricAction(action); + } + } + + @Override public final void onClientFinished(@NonNull BaseClientMonitor clientMonitor, boolean success) { for (int i = mCallbacks.size() - 1; i >= 0; i--) { diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintService.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintService.java index 0c5b19b0fb2e..94b67cedf86c 100644 --- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintService.java +++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintService.java @@ -1020,9 +1020,18 @@ public class FingerprintService extends SystemService { @Override public void registerBiometricStateListener(@NonNull IBiometricStateListener listener) { + Utils.checkPermission(getContext(), USE_BIOMETRIC_INTERNAL); FingerprintService.this.registerBiometricStateListener(listener); } - } + + @Override + public void onPowerPressed() { + Utils.checkPermission(getContext(), USE_BIOMETRIC_INTERNAL); + for (ServiceProvider provider : mServiceProviders) { + provider.onPowerPressed(); + } + } + }; public FingerprintService(Context context) { super(context); diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/PowerPressHandler.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/PowerPressHandler.java new file mode 100644 index 000000000000..288c372a3f13 --- /dev/null +++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/PowerPressHandler.java @@ -0,0 +1,27 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.biometrics.sensors.fingerprint; + +/** + * Interface for handling power presses. + */ +public interface PowerPressHandler { + /** + * Indicates a power press has occurred. + */ + void onPowerPressed(); +} diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/ServiceProvider.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/ServiceProvider.java index 9cdbdc9158fb..24a47e081498 100644 --- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/ServiceProvider.java +++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/ServiceProvider.java @@ -150,6 +150,8 @@ public interface ServiceProvider { void setUdfpsOverlayController(@NonNull IUdfpsOverlayController controller); + void onPowerPressed(); + /** * Sets side-fps controller * @param controller side-fps controller 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 79e3bf53acd3..e1626f014d8b 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 @@ -21,6 +21,7 @@ import android.annotation.Nullable; import android.app.TaskStackListener; import android.content.Context; import android.hardware.biometrics.BiometricAuthenticator; +import android.hardware.biometrics.BiometricConstants; import android.hardware.biometrics.BiometricFingerprintConstants; import android.hardware.biometrics.BiometricFingerprintConstants.FingerprintAcquired; import android.hardware.biometrics.common.ICancellationSignal; @@ -29,10 +30,15 @@ import android.hardware.biometrics.fingerprint.PointerContext; import android.hardware.fingerprint.FingerprintSensorPropertiesInternal; import android.hardware.fingerprint.ISidefpsController; import android.hardware.fingerprint.IUdfpsOverlayController; +import android.os.Build; +import android.os.Handler; import android.os.IBinder; import android.os.RemoteException; +import android.os.UserHandle; +import android.provider.Settings; import android.util.Slog; +import com.android.internal.R; import com.android.server.biometrics.log.BiometricContext; import com.android.server.biometrics.log.BiometricLogger; import com.android.server.biometrics.log.CallbackWithProbe; @@ -46,17 +52,18 @@ import com.android.server.biometrics.sensors.LockoutCache; import com.android.server.biometrics.sensors.LockoutConsumer; import com.android.server.biometrics.sensors.LockoutTracker; import com.android.server.biometrics.sensors.SensorOverlays; +import com.android.server.biometrics.sensors.fingerprint.PowerPressHandler; import com.android.server.biometrics.sensors.fingerprint.Udfps; import java.util.ArrayList; import java.util.function.Supplier; /** - * Fingerprint-specific authentication client supporting the - * {@link android.hardware.biometrics.fingerprint.IFingerprint} AIDL interface. + * Fingerprint-specific authentication client supporting the {@link + * android.hardware.biometrics.fingerprint.IFingerprint} AIDL interface. */ -class FingerprintAuthenticationClient extends AuthenticationClient<AidlSession> implements - Udfps, LockoutConsumer { +class FingerprintAuthenticationClient extends AuthenticationClient<AidlSession> + implements Udfps, LockoutConsumer, PowerPressHandler { private static final String TAG = "FingerprintAuthenticationClient"; @NonNull private final LockoutCache mLockoutCache; @@ -64,33 +71,88 @@ class FingerprintAuthenticationClient extends AuthenticationClient<AidlSession> @NonNull private final FingerprintSensorPropertiesInternal mSensorProps; @NonNull private final CallbackWithProbe<Probe> mALSProbeCallback; - @Nullable private ICancellationSignal mCancellationSignal; + @Nullable + private ICancellationSignal mCancellationSignal; private boolean mIsPointerDown; + private final Handler mHandler; - FingerprintAuthenticationClient(@NonNull Context context, + private static final int MESSAGE_IGNORE_AUTH = 1; + private static final int MESSAGE_AUTH_SUCCESS = 2; + private long mWaitForAuthKeyguard; + private long mWaitForAuthBp; + private long mIgnoreAuthFor; + + FingerprintAuthenticationClient( + @NonNull Context context, @NonNull Supplier<AidlSession> lazyDaemon, - @NonNull IBinder token, long requestId, - @NonNull ClientMonitorCallbackConverter listener, int targetUserId, long operationId, - boolean restricted, @NonNull String owner, int cookie, boolean requireConfirmation, + @NonNull IBinder token, + long requestId, + @NonNull ClientMonitorCallbackConverter listener, + int targetUserId, + long operationId, + boolean restricted, + @NonNull String owner, + int cookie, + boolean requireConfirmation, int sensorId, - @NonNull BiometricLogger biometricLogger, @NonNull BiometricContext biometricContext, + @NonNull BiometricLogger biometricLogger, + @NonNull BiometricContext biometricContext, boolean isStrongBiometric, - @Nullable TaskStackListener taskStackListener, @NonNull LockoutCache lockoutCache, + @Nullable TaskStackListener taskStackListener, + @NonNull LockoutCache lockoutCache, @Nullable IUdfpsOverlayController udfpsOverlayController, @Nullable ISidefpsController sidefpsController, boolean allowBackgroundAuthentication, - @NonNull FingerprintSensorPropertiesInternal sensorProps) { - super(context, lazyDaemon, token, listener, targetUserId, operationId, restricted, owner, - cookie, requireConfirmation, sensorId, - biometricLogger, biometricContext, - isStrongBiometric, taskStackListener, - lockoutCache, allowBackgroundAuthentication, true /* shouldVibrate */, + @NonNull FingerprintSensorPropertiesInternal sensorProps, + @NonNull Handler handler) { + super( + context, + lazyDaemon, + token, + listener, + targetUserId, + operationId, + restricted, + owner, + cookie, + requireConfirmation, + sensorId, + biometricLogger, + biometricContext, + isStrongBiometric, + taskStackListener, + lockoutCache, + allowBackgroundAuthentication, + true /* shouldVibrate */, false /* isKeyguardBypassEnabled */); setRequestId(requestId); mLockoutCache = lockoutCache; mSensorOverlays = new SensorOverlays(udfpsOverlayController, sidefpsController); mSensorProps = sensorProps; mALSProbeCallback = getLogger().createALSCallback(false /* startWithClient */); + mHandler = handler; + + mWaitForAuthKeyguard = + context.getResources() + .getInteger(R.integer.config_sidefpsKeyguardPowerPressWindow); + mWaitForAuthBp = + context.getResources().getInteger(R.integer.config_sidefpsBpPowerPressWindow); + mIgnoreAuthFor = + context.getResources().getInteger(R.integer.config_sidefpsPostAuthDowntime); + + if (mSensorProps.isAnySidefpsType()) { + if (Build.isDebuggable()) { + mWaitForAuthKeyguard = Settings.Secure.getIntForUser(context.getContentResolver(), + Settings.Secure.FINGERPRINT_SIDE_FPS_KG_POWER_WINDOW, + (int) mWaitForAuthKeyguard, UserHandle.USER_CURRENT); + mWaitForAuthBp = Settings.Secure.getIntForUser(context.getContentResolver(), + Settings.Secure.FINGERPRINT_SIDE_FPS_BP_POWER_WINDOW, (int) mWaitForAuthBp, + UserHandle.USER_CURRENT); + mIgnoreAuthFor = Settings.Secure.getIntForUser(context.getContentResolver(), + Settings.Secure.FINGERPRINT_SIDE_FPS_AUTH_DOWNTIME, (int) mIgnoreAuthFor, + UserHandle.USER_CURRENT); + } + } } @Override @@ -108,8 +170,8 @@ class FingerprintAuthenticationClient extends AuthenticationClient<AidlSession> @NonNull @Override protected ClientMonitorCallback wrapCallbackForStart(@NonNull ClientMonitorCallback callback) { - return new ClientMonitorCompositeCallback(mALSProbeCallback, - getBiometricContextUnsubscriber(), callback); + return new ClientMonitorCompositeCallback( + mALSProbeCallback, getBiometricContextUnsubscriber(), callback); } @Override @@ -126,16 +188,37 @@ class FingerprintAuthenticationClient extends AuthenticationClient<AidlSession> } @Override - public void onAuthenticated(BiometricAuthenticator.Identifier identifier, - boolean authenticated, ArrayList<Byte> token) { - super.onAuthenticated(identifier, authenticated, token); - - if (authenticated) { - mState = STATE_STOPPED; - mSensorOverlays.hide(getSensorId()); - } else { - mState = STATE_STARTED_PAUSED_ATTEMPTED; + public void onAuthenticated( + BiometricAuthenticator.Identifier identifier, + boolean authenticated, + ArrayList<Byte> token) { + + long delay = 0; + if (authenticated && mSensorProps.isAnySidefpsType()) { + if (mHandler.hasMessages(MESSAGE_IGNORE_AUTH)) { + Slog.i(TAG, "(sideFPS) Ignoring auth due to recent power press"); + onErrorInternal(BiometricConstants.BIOMETRIC_ERROR_POWER_PRESSED, 0, true); + return; + } + delay = isKeyguard() ? mWaitForAuthKeyguard : mWaitForAuthBp; + Slog.i(TAG, "(sideFPS) Auth succeeded, sideFps waiting for power until: " + delay); } + + mHandler.postDelayed( + () -> { + if (authenticated && mSensorProps.isAnySidefpsType()) { + Slog.i(TAG, "(sideFPS): No power press detected, sending auth"); + } + super.onAuthenticated(identifier, authenticated, token); + if (authenticated) { + mState = STATE_STOPPED; + mSensorOverlays.hide(getSensorId()); + } else { + mState = STATE_STARTED_PAUSED_ATTEMPTED; + } + }, + MESSAGE_AUTH_SUCCESS, + delay); } @Override @@ -165,7 +248,8 @@ class FingerprintAuthenticationClient extends AuthenticationClient<AidlSession> mCancellationSignal = doAuthenticate(); } catch (RemoteException e) { Slog.e(TAG, "Remote exception", e); - onError(BiometricFingerprintConstants.FINGERPRINT_ERROR_HW_UNAVAILABLE, + onError( + BiometricFingerprintConstants.FINGERPRINT_ERROR_HW_UNAVAILABLE, 0 /* vendorCode */); mSensorOverlays.hide(getSensorId()); mCallback.onClientFinished(this, false /* success */); @@ -177,15 +261,18 @@ class FingerprintAuthenticationClient extends AuthenticationClient<AidlSession> if (session.hasContextMethods()) { final OperationContext opContext = getOperationContext(); - final ICancellationSignal cancel = session.getSession().authenticateWithContext( - mOperationId, opContext); - getBiometricContext().subscribe(opContext, ctx -> { - try { - session.getSession().onContextChanged(ctx); - } catch (RemoteException e) { - Slog.e(TAG, "Unable to notify context changed", e); - } - }); + final ICancellationSignal cancel = + session.getSession().authenticateWithContext(mOperationId, opContext); + getBiometricContext() + .subscribe( + opContext, + ctx -> { + try { + session.getSession().onContextChanged(ctx); + } catch (RemoteException e) { + Slog.e(TAG, "Unable to notify context changed", e); + } + }); return cancel; } else { return session.getSession().authenticate(mOperationId); @@ -202,7 +289,8 @@ class FingerprintAuthenticationClient extends AuthenticationClient<AidlSession> mCancellationSignal.cancel(); } catch (RemoteException e) { Slog.e(TAG, "Remote exception", e); - onError(BiometricFingerprintConstants.FINGERPRINT_ERROR_HW_UNAVAILABLE, + onError( + BiometricFingerprintConstants.FINGERPRINT_ERROR_HW_UNAVAILABLE, 0 /* vendorCode */); mCallback.onClientFinished(this, false /* success */); } @@ -284,8 +372,13 @@ class FingerprintAuthenticationClient extends AuthenticationClient<AidlSession> mLockoutCache.setLockoutModeForUser(getTargetUserId(), LockoutTracker.LOCKOUT_TIMED); // Lockout metrics are logged as an error code. final int error = BiometricFingerprintConstants.FINGERPRINT_ERROR_LOCKOUT; - getLogger().logOnError(getContext(), getOperationContext(), - error, 0 /* vendorCode */, getTargetUserId()); + getLogger() + .logOnError( + getContext(), + getOperationContext(), + error, + 0 /* vendorCode */, + getTargetUserId()); try { getListener().onError(getSensorId(), getCookie(), error, 0 /* vendorCode */); @@ -303,8 +396,13 @@ class FingerprintAuthenticationClient extends AuthenticationClient<AidlSession> mLockoutCache.setLockoutModeForUser(getTargetUserId(), LockoutTracker.LOCKOUT_PERMANENT); // Lockout metrics are logged as an error code. final int error = BiometricFingerprintConstants.FINGERPRINT_ERROR_LOCKOUT_PERMANENT; - getLogger().logOnError(getContext(), getOperationContext(), - error, 0 /* vendorCode */, getTargetUserId()); + getLogger() + .logOnError( + getContext(), + getOperationContext(), + error, + 0 /* vendorCode */, + getTargetUserId()); try { getListener().onError(getSensorId(), getCookie(), error, 0 /* vendorCode */); @@ -315,4 +413,19 @@ class FingerprintAuthenticationClient extends AuthenticationClient<AidlSession> mSensorOverlays.hide(getSensorId()); mCallback.onClientFinished(this, false /* success */); } + + @Override + public void onPowerPressed() { + if (mSensorProps.isAnySidefpsType()) { + Slog.i(TAG, "(sideFPS): onPowerPressed"); + if (mHandler.hasMessages(MESSAGE_AUTH_SUCCESS)) { + Slog.i(TAG, "(sideFPS): Ignoring auth in queue"); + mHandler.removeMessages(MESSAGE_AUTH_SUCCESS); + // Do not call onError() as that will send an additional callback to coex. + onErrorInternal(BiometricConstants.BIOMETRIC_ERROR_POWER_PRESSED, 0, true); + } + mHandler.removeMessages(MESSAGE_IGNORE_AUTH); + mHandler.postDelayed(() -> {}, MESSAGE_IGNORE_AUTH, mIgnoreAuthFor); + } + } } diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintEnrollClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintEnrollClient.java index f23659c94e49..f4f0a19d0db7 100644 --- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintEnrollClient.java +++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintEnrollClient.java @@ -22,6 +22,7 @@ import android.content.Context; import android.hardware.biometrics.BiometricAuthenticator; import android.hardware.biometrics.BiometricFingerprintConstants; import android.hardware.biometrics.BiometricFingerprintConstants.FingerprintAcquired; +import android.hardware.biometrics.BiometricStateListener; import android.hardware.biometrics.common.ICancellationSignal; import android.hardware.biometrics.common.OperationContext; import android.hardware.biometrics.fingerprint.PointerContext; @@ -139,7 +140,7 @@ class FingerprintEnrollClient extends EnrollClient<AidlSession> implements Udfps controller.onEnrollmentHelp(getSensorId()); } }); - + mCallback.onBiometricAction(BiometricStateListener.ACTION_SENSOR_TOUCH); super.onAcquired(acquiredInfo, vendorCode); } diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintProvider.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintProvider.java index f16af78364a2..6f6c09b91a66 100644 --- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintProvider.java +++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintProvider.java @@ -67,6 +67,7 @@ import com.android.server.biometrics.sensors.LockoutResetDispatcher; import com.android.server.biometrics.sensors.PerformanceTracker; import com.android.server.biometrics.sensors.fingerprint.FingerprintUtils; import com.android.server.biometrics.sensors.fingerprint.GestureAvailabilityDispatcher; +import com.android.server.biometrics.sensors.fingerprint.PowerPressHandler; import com.android.server.biometrics.sensors.fingerprint.ServiceProvider; import com.android.server.biometrics.sensors.fingerprint.Udfps; @@ -388,6 +389,11 @@ public class FingerprintProvider implements IBinder.DeathRecipient, ServiceProvi } @Override + public void onBiometricAction(int action) { + mBiometricStateCallback.onBiometricAction(action); + } + + @Override public void onClientFinished(@NonNull BaseClientMonitor clientMonitor, boolean success) { mBiometricStateCallback.onClientFinished(clientMonitor, success); @@ -441,7 +447,7 @@ public class FingerprintProvider implements IBinder.DeathRecipient, ServiceProvi mBiometricContext, isStrongBiometric, mTaskStackListener, mSensors.get(sensorId).getLockoutCache(), mUdfpsOverlayController, mSidefpsController, allowBackgroundAuthentication, - mSensors.get(sensorId).getSensorProperties()); + mSensors.get(sensorId).getSensorProperties(), mHandler); scheduleForSensor(sensorId, client, mBiometricStateCallback); }); } @@ -614,6 +620,21 @@ public class FingerprintProvider implements IBinder.DeathRecipient, ServiceProvi } @Override + public void onPowerPressed() { + for (int i = 0; i < mSensors.size(); i++) { + final Sensor sensor = mSensors.valueAt(i); + BaseClientMonitor client = sensor.getScheduler().getCurrentClient(); + if (client == null) { + return; + } + if (!(client instanceof PowerPressHandler)) { + continue; + } + ((PowerPressHandler) client).onPowerPressed(); + } + } + + @Override public void setSidefpsController(@NonNull ISidefpsController controller) { mSidefpsController = controller; } diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/Fingerprint21.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/Fingerprint21.java index 2a3f34ae3cd4..c1a86386dfd4 100644 --- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/Fingerprint21.java +++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/Fingerprint21.java @@ -603,6 +603,11 @@ public class Fingerprint21 implements IHwBinder.DeathRecipient, ServiceProvider } @Override + public void onBiometricAction(int action) { + mBiometricStateCallback.onBiometricAction(action); + } + + @Override public void onClientFinished(@NonNull BaseClientMonitor clientMonitor, boolean success) { mBiometricStateCallback.onClientFinished(clientMonitor, success); @@ -821,6 +826,11 @@ public class Fingerprint21 implements IHwBinder.DeathRecipient, ServiceProvider } @Override + public void onPowerPressed() { + Slog.e(TAG, "onPowerPressed not supported for HIDL clients"); + } + + @Override public void setUdfpsOverlayController(@NonNull IUdfpsOverlayController controller) { mUdfpsOverlayController = controller; } diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintEnrollClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintEnrollClient.java index 1d478e53fd38..2a59c8c0f204 100644 --- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintEnrollClient.java +++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintEnrollClient.java @@ -21,6 +21,7 @@ import android.annotation.Nullable; import android.content.Context; import android.hardware.biometrics.BiometricAuthenticator; import android.hardware.biometrics.BiometricFingerprintConstants; +import android.hardware.biometrics.BiometricStateListener; import android.hardware.biometrics.fingerprint.V2_1.IBiometricsFingerprint; import android.hardware.fingerprint.Fingerprint; import android.hardware.fingerprint.FingerprintManager; @@ -151,6 +152,8 @@ public class FingerprintEnrollClient extends EnrollClient<IBiometricsFingerprint controller.onEnrollmentHelp(getSensorId()); } }); + + mCallback.onBiometricAction(BiometricStateListener.ACTION_SENSOR_TOUCH); } @Override diff --git a/services/core/java/com/android/server/location/contexthub/ContextHubClientBroker.java b/services/core/java/com/android/server/location/contexthub/ContextHubClientBroker.java index 9fb1d8e744fb..625f1936e28e 100644 --- a/services/core/java/com/android/server/location/contexthub/ContextHubClientBroker.java +++ b/services/core/java/com/android/server/location/contexthub/ContextHubClientBroker.java @@ -871,10 +871,9 @@ public class ContextHubClientBroker extends IContextHubClient.Stub if (!mPendingIntentRequest.hasPendingIntent() && mRegistered) { mClientManager.unregisterClient(mHostEndPointId); mRegistered = false; + mAppOpsManager.stopWatchingMode(this); + mContextHubProxy.onHostEndpointDisconnected(mHostEndPointId); } - mAppOpsManager.stopWatchingMode(this); - - mContextHubProxy.onHostEndpointDisconnected(mHostEndPointId); } private String authStateToString(@ContextHubManager.AuthorizationState int state) { diff --git a/services/core/java/com/android/server/notification/PreferencesHelper.java b/services/core/java/com/android/server/notification/PreferencesHelper.java index 066692d374d0..477b8da61e0f 100644 --- a/services/core/java/com/android/server/notification/PreferencesHelper.java +++ b/services/core/java/com/android/server/notification/PreferencesHelper.java @@ -86,7 +86,6 @@ import java.io.PrintWriter; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; -import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Objects; @@ -1328,17 +1327,16 @@ public class PreferencesHelper implements RankingConfig { return null; } NotificationChannelGroup group = r.groups.get(groupId).clone(); - ArrayList channels = new ArrayList(); + group.setChannels(new ArrayList<>()); int N = r.channels.size(); for (int i = 0; i < N; i++) { final NotificationChannel nc = r.channels.valueAt(i); if (includeDeleted || !nc.isDeleted()) { if (groupId.equals(nc.getGroup())) { - channels.add(nc); + group.addChannel(nc); } } } - group.setChannels(channels); return group; } } @@ -1359,48 +1357,44 @@ public class PreferencesHelper implements RankingConfig { public ParceledListSlice<NotificationChannelGroup> getNotificationChannelGroups(String pkg, int uid, boolean includeDeleted, boolean includeNonGrouped, boolean includeEmpty) { Objects.requireNonNull(pkg); - List<NotificationChannelGroup> groups = new ArrayList<>(); + Map<String, NotificationChannelGroup> groups = new ArrayMap<>(); synchronized (mPackagePreferences) { PackagePreferences r = getPackagePreferencesLocked(pkg, uid); if (r == null) { return ParceledListSlice.emptyList(); } - Map<String, ArrayList<NotificationChannel>> groupedChannels = new HashMap(); + NotificationChannelGroup nonGrouped = new NotificationChannelGroup(null, null); int N = r.channels.size(); for (int i = 0; i < N; i++) { final NotificationChannel nc = r.channels.valueAt(i); if (includeDeleted || !nc.isDeleted()) { if (nc.getGroup() != null) { if (r.groups.get(nc.getGroup()) != null) { - ArrayList<NotificationChannel> channels = groupedChannels.getOrDefault( - nc.getGroup(), new ArrayList<>()); - channels.add(nc); - groupedChannels.put(nc.getGroup(), channels); + NotificationChannelGroup ncg = groups.get(nc.getGroup()); + if (ncg == null) { + ncg = r.groups.get(nc.getGroup()).clone(); + ncg.setChannels(new ArrayList<>()); + groups.put(nc.getGroup(), ncg); + + } + ncg.addChannel(nc); } } else { - ArrayList<NotificationChannel> channels = groupedChannels.getOrDefault( - null, new ArrayList<>()); - channels.add(nc); - groupedChannels.put(null, channels); + nonGrouped.addChannel(nc); } } } - for (NotificationChannelGroup group : r.groups.values()) { - ArrayList<NotificationChannel> channels = - groupedChannels.getOrDefault(group.getId(), new ArrayList<>()); - if (includeEmpty || !channels.isEmpty()) { - NotificationChannelGroup clone = group.clone(); - clone.setChannels(channels); - groups.add(clone); - } + if (includeNonGrouped && nonGrouped.getChannels().size() > 0) { + groups.put(null, nonGrouped); } - - if (includeNonGrouped && groupedChannels.containsKey(null)) { - NotificationChannelGroup nonGrouped = new NotificationChannelGroup(null, null); - nonGrouped.setChannels(groupedChannels.get(null)); - groups.add(nonGrouped); + if (includeEmpty) { + for (NotificationChannelGroup group : r.groups.values()) { + if (!groups.containsKey(group.getId())) { + groups.put(group.getId(), group); + } + } } - return new ParceledListSlice<>(groups); + return new ParceledListSlice<>(new ArrayList<>(groups.values())); } } diff --git a/services/core/java/com/android/server/policy/PhoneWindowManager.java b/services/core/java/com/android/server/policy/PhoneWindowManager.java index d88949bcc0ec..d645bb211bc3 100644 --- a/services/core/java/com/android/server/policy/PhoneWindowManager.java +++ b/services/core/java/com/android/server/policy/PhoneWindowManager.java @@ -949,6 +949,11 @@ public class PhoneWindowManager implements WindowManagerPolicy { } private void powerPress(long eventTime, int count, boolean beganFromNonInteractive) { + // SideFPS still needs to know about suppressed power buttons, in case it needs to block + // an auth attempt. + if (count == 1) { + mSideFpsEventHandler.notifyPowerPressed(); + } if (mDefaultDisplayPolicy.isScreenOnEarly() && !mDefaultDisplayPolicy.isScreenOnFully()) { Slog.i(TAG, "Suppressed redundant power key press while " + "already in the process of turning the screen on."); @@ -968,7 +973,7 @@ public class PhoneWindowManager implements WindowManagerPolicy { } else if (count > 3 && count <= getMaxMultiPressPowerCount()) { Slog.d(TAG, "No behavior defined for power press count " + count); } else if (count == 1 && interactive && !beganFromNonInteractive) { - if (mSideFpsEventHandler.onSinglePressDetected(eventTime)) { + if (mSideFpsEventHandler.shouldConsumeSinglePress(eventTime)) { Slog.i(TAG, "Suppressing power key because the user is interacting with the " + "fingerprint sensor"); return; diff --git a/services/core/java/com/android/server/policy/SideFpsEventHandler.java b/services/core/java/com/android/server/policy/SideFpsEventHandler.java index 41d02727d9eb..af2b902c97e8 100644 --- a/services/core/java/com/android/server/policy/SideFpsEventHandler.java +++ b/services/core/java/com/android/server/policy/SideFpsEventHandler.java @@ -19,6 +19,7 @@ package com.android.server.policy; import static android.hardware.biometrics.BiometricStateListener.STATE_BP_AUTH; import static android.hardware.biometrics.BiometricStateListener.STATE_ENROLLING; import static android.hardware.biometrics.BiometricStateListener.STATE_IDLE; +import static android.hardware.biometrics.BiometricStateListener.STATE_KEYGUARD_AUTH; import android.annotation.NonNull; import android.annotation.Nullable; @@ -34,8 +35,12 @@ import android.hardware.biometrics.BiometricStateListener; import android.hardware.fingerprint.FingerprintManager; import android.hardware.fingerprint.FingerprintSensorPropertiesInternal; import android.hardware.fingerprint.IFingerprintAuthenticatorsRegisteredCallback; +import android.os.Build; import android.os.Handler; import android.os.PowerManager; +import android.os.UserHandle; +import android.provider.Settings; +import android.util.Log; import android.view.WindowManager; import com.android.internal.R; @@ -46,14 +51,26 @@ import java.util.concurrent.atomic.AtomicBoolean; import java.util.function.Supplier; /** - * Defines behavior for handling interactions between power button events and - * fingerprint-related operations, for devices where the fingerprint sensor (side fps) - * lives on the power button. + * Defines behavior for handling interactions between power button events and fingerprint-related + * operations, for devices where the fingerprint sensor (side fps) lives on the power button. */ public class SideFpsEventHandler { private static final int DEBOUNCE_DELAY_MILLIS = 500; + private int getTapWaitForPowerDuration(Context context) { + int tap = context.getResources().getInteger( + R.integer.config_sidefpsEnrollPowerPressWindow); + if (Build.isDebuggable()) { + tap = Settings.Secure.getIntForUser(context.getContentResolver(), + Settings.Secure.FINGERPRINT_SIDE_FPS_ENROLL_TAP_WINDOW, tap, + UserHandle.USER_CURRENT); + } + return tap; + } + + private static final String TAG = "SideFpsEventHandler"; + @NonNull private final Context mContext; @NonNull private final Handler mHandler; @NonNull private final PowerManager mPowerManager; @@ -61,20 +78,26 @@ public class SideFpsEventHandler { @NonNull private final AtomicBoolean mSideFpsEventHandlerReady; @Nullable private Dialog mDialog; - @NonNull private final DialogInterface.OnDismissListener mDialogDismissListener = (dialog) -> { - if (mDialog == dialog) { - mDialog = null; - } - }; + private final Runnable mTurnOffDialog = + () -> { + dismissDialog("mTurnOffDialog"); + }; + + @NonNull private final DialogInterface.OnDismissListener mDialogDismissListener; private @BiometricStateListener.State int mBiometricState; + private final int mTapWaitForPowerDuration; + private FingerprintManager mFingerprintManager; SideFpsEventHandler(Context context, Handler handler, PowerManager powerManager) { this(context, handler, powerManager, () -> new AlertDialog.Builder(context)); } @VisibleForTesting - SideFpsEventHandler(Context context, Handler handler, PowerManager powerManager, + SideFpsEventHandler( + Context context, + Handler handler, + PowerManager powerManager, Supplier<AlertDialog.Builder> dialogSupplier) { mContext = context; mHandler = handler; @@ -82,91 +105,131 @@ public class SideFpsEventHandler { mDialogSupplier = dialogSupplier; mBiometricState = STATE_IDLE; mSideFpsEventHandlerReady = new AtomicBoolean(false); + mDialogDismissListener = + (dialog) -> { + if (mDialog == dialog) { + if (mHandler != null) { + mHandler.removeCallbacks(mTurnOffDialog); + } + mDialog = null; + } + }; // ensure dialog is dismissed if screen goes off for unrelated reasons - context.registerReceiver(new BroadcastReceiver() { - @Override - public void onReceive(Context context, Intent intent) { - if (mDialog != null) { - mDialog.dismiss(); - mDialog = null; - } - } - }, new IntentFilter(Intent.ACTION_SCREEN_OFF)); + context.registerReceiver( + new BroadcastReceiver() { + @Override + public void onReceive(Context context, Intent intent) { + if (mDialog != null) { + mDialog.dismiss(); + mDialog = null; + } + } + }, + new IntentFilter(Intent.ACTION_SCREEN_OFF)); + mTapWaitForPowerDuration = getTapWaitForPowerDuration(context); } /** - * Called from {@link PhoneWindowManager} after the power button is pressed and displays a - * dialog confirming the user's intent to turn screen off if a fingerprint operation is - * active. The device goes to sleep if confirmed otherwise the dialog is dismissed. + * Called from {@link PhoneWindowManager} to notify FingerprintManager that a single tap power + * button has been pressed. + */ + public void notifyPowerPressed() { + Log.i(TAG, "notifyPowerPressed"); + if (mFingerprintManager == null) { + mFingerprintManager = mContext.getSystemService(FingerprintManager.class); + } + if (mFingerprintManager == null) { + return; + } + mFingerprintManager.onPowerPressed(); + } + + /** + * Called from {@link PhoneWindowManager} and will dictate if the SideFpsEventHandler should + * handle the power press. * * @param eventTime powerPress event time * @return true if powerPress was consumed, false otherwise */ - public boolean onSinglePressDetected(long eventTime) { + public boolean shouldConsumeSinglePress(long eventTime) { if (!mSideFpsEventHandlerReady.get()) { return false; } switch (mBiometricState) { case STATE_ENROLLING: + mHandler.post( + () -> { + if (mHandler.hasCallbacks(mTurnOffDialog)) { + Log.v(TAG, "Detected a tap to turn off dialog, ignoring"); + mHandler.removeCallbacks(mTurnOffDialog); + } + }); + showDialog(eventTime, "Enroll Power Press"); + return true; case STATE_BP_AUTH: - mHandler.post(() -> { - if (mDialog != null) { - mDialog.dismiss(); - } - mDialog = showConfirmDialog(mDialogSupplier.get(), - mPowerManager, eventTime, mBiometricState, mDialogDismissListener); - }); return true; + case STATE_KEYGUARD_AUTH: default: return false; } } @NonNull - private static Dialog showConfirmDialog(@NonNull AlertDialog.Builder dialogBuilder, - @NonNull PowerManager powerManager, long eventTime, + private static Dialog showConfirmDialog( + @NonNull AlertDialog.Builder dialogBuilder, + @NonNull PowerManager powerManager, + long eventTime, @BiometricStateListener.State int biometricState, @NonNull DialogInterface.OnDismissListener dismissListener) { final boolean enrolling = biometricState == STATE_ENROLLING; - final int title = enrolling ? R.string.fp_power_button_enrollment_title - : R.string.fp_power_button_bp_title; - final int message = enrolling ? R.string.fp_power_button_enrollment_message - : R.string.fp_power_button_bp_message; - final int positiveText = enrolling ? R.string.fp_power_button_enrollment_positive_button - : R.string.fp_power_button_bp_positive_button; - final int negativeText = enrolling ? R.string.fp_power_button_enrollment_negative_button - : R.string.fp_power_button_bp_negative_button; - - final Dialog confirmScreenOffDialog = dialogBuilder - .setTitle(title) - .setMessage(message) - .setPositiveButton(positiveText, - (dialog, which) -> { - dialog.dismiss(); - powerManager.goToSleep( - eventTime, - PowerManager.GO_TO_SLEEP_REASON_POWER_BUTTON, - 0 /* flags */ - ); - }) - .setNegativeButton(negativeText, (dialog, which) -> dialog.dismiss()) - .setOnDismissListener(dismissListener) - .setCancelable(false) - .create(); - confirmScreenOffDialog.getWindow().setType( - WindowManager.LayoutParams.TYPE_STATUS_BAR_SUB_PANEL); + final int title = + enrolling + ? R.string.fp_power_button_enrollment_title + : R.string.fp_power_button_bp_title; + final int message = + enrolling + ? R.string.fp_power_button_enrollment_message + : R.string.fp_power_button_bp_message; + final int positiveText = + enrolling + ? R.string.fp_power_button_enrollment_positive_button + : R.string.fp_power_button_bp_positive_button; + final int negativeText = + enrolling + ? R.string.fp_power_button_enrollment_negative_button + : R.string.fp_power_button_bp_negative_button; + + final Dialog confirmScreenOffDialog = + dialogBuilder + .setTitle(title) + .setMessage(message) + .setPositiveButton( + positiveText, + (dialog, which) -> { + dialog.dismiss(); + powerManager.goToSleep( + eventTime, + PowerManager.GO_TO_SLEEP_REASON_POWER_BUTTON, + 0 /* flags */); + }) + .setNegativeButton(negativeText, (dialog, which) -> dialog.dismiss()) + .setOnDismissListener(dismissListener) + .setCancelable(false) + .create(); + confirmScreenOffDialog + .getWindow() + .setType(WindowManager.LayoutParams.TYPE_STATUS_BAR_SUB_PANEL); confirmScreenOffDialog.show(); return confirmScreenOffDialog; } /** - * Awaits notification from PhoneWindowManager that fingerprint service is ready - * to send updates about power button fps sensor state. Then configures a - * BiometricStateListener to receive and record updates to fps state, and - * registers the BiometricStateListener in FingerprintManager. + * Awaits notification from PhoneWindowManager that fingerprint service is ready to send updates + * about power button fps sensor state. Then configures a BiometricStateListener to receive and + * record updates to fps state, and registers the BiometricStateListener in FingerprintManager. */ public void onFingerprintSensorReady() { final PackageManager pm = mContext.getPackageManager(); @@ -184,12 +247,12 @@ public class SideFpsEventHandler { if (fingerprintManager.isPowerbuttonFps()) { fingerprintManager.registerBiometricStateListener( new BiometricStateListener() { - @Nullable - private Runnable mStateRunnable = null; + @Nullable private Runnable mStateRunnable = null; @Override public void onStateChanged( @BiometricStateListener.State int newState) { + Log.d(TAG, "onStateChanged : " + newState); if (mStateRunnable != null) { mHandler.removeCallbacks(mStateRunnable); mStateRunnable = null; @@ -200,16 +263,58 @@ public class SideFpsEventHandler { // damper when moving to idle in case auth is first if (newState == STATE_IDLE) { mStateRunnable = () -> mBiometricState = newState; - mHandler.postDelayed(mStateRunnable, - DEBOUNCE_DELAY_MILLIS); + // This is also useful in the case of biometric + // prompt. + // If a user has recently succeeded/failed auth, we + // want to disable the power button for a short + // period of time (so ethey are able to view the + // prompt) + mHandler.postDelayed( + mStateRunnable, DEBOUNCE_DELAY_MILLIS); + dismissDialog("STATE_IDLE"); } else { mBiometricState = newState; } } + + @Override + public void onBiometricAction( + @BiometricStateListener.Action int action) { + Log.d(TAG, "onBiometricAction " + action); + switch (action) { + case BiometricStateListener.ACTION_SENSOR_TOUCH: + mHandler.postDelayed( + mTurnOffDialog, + mTapWaitForPowerDuration); + break; + } + } }); mSideFpsEventHandlerReady.set(true); } } }); } + + private void dismissDialog(String reason) { + Log.d(TAG, "Dismissing dialog with reason: " + reason); + if (mDialog != null && mDialog.isShowing()) { + mDialog.dismiss(); + } + } + + private void showDialog(long time, String reason) { + Log.d(TAG, "Showing dialog with reason: " + reason); + if (mDialog != null && mDialog.isShowing()) { + Log.d(TAG, "Ignoring show dialog"); + return; + } + mDialog = + showConfirmDialog( + mDialogSupplier.get(), + mPowerManager, + time, + mBiometricState, + mDialogDismissListener); + } } diff --git a/services/core/java/com/android/server/power/PowerManagerService.java b/services/core/java/com/android/server/power/PowerManagerService.java index 50d1bd6c3e33..dbf05f1cd7c7 100644 --- a/services/core/java/com/android/server/power/PowerManagerService.java +++ b/services/core/java/com/android/server/power/PowerManagerService.java @@ -4801,6 +4801,11 @@ public final class PowerManagerService extends SystemService .IS_STAY_ON_WHILE_PLUGGED_IN_WIRELESS, ((mStayOnWhilePluggedInSetting & BatteryManager.BATTERY_PLUGGED_WIRELESS) != 0)); + proto.write( + PowerServiceSettingsAndConfigurationDumpProto.StayOnWhilePluggedInProto + .IS_STAY_ON_WHILE_PLUGGED_IN_DOCK, + ((mStayOnWhilePluggedInSetting & BatteryManager.BATTERY_PLUGGED_DOCK) + != 0)); proto.end(stayOnWhilePluggedInToken); proto.write( diff --git a/services/core/java/com/android/server/statusbar/StatusBarManagerService.java b/services/core/java/com/android/server/statusbar/StatusBarManagerService.java index 71b1bc2e24bc..bec3754536e2 100644 --- a/services/core/java/com/android/server/statusbar/StatusBarManagerService.java +++ b/services/core/java/com/android/server/statusbar/StatusBarManagerService.java @@ -1207,7 +1207,7 @@ public class StatusBarManagerService extends IStatusBarService.Stub implements D private int mImeBackDisposition = 0; private boolean mShowImeSwitcher = false; private IBinder mImeToken = null; - private LetterboxDetails[] mLetterboxDetails; + private LetterboxDetails[] mLetterboxDetails = new LetterboxDetails[0]; private void setBarAttributes(@Appearance int appearance, AppearanceRegion[] appearanceRegions, boolean navbarColorManagedByIme, diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java index f438e469577e..14898917caae 100644 --- a/services/core/java/com/android/server/wm/DisplayContent.java +++ b/services/core/java/com/android/server/wm/DisplayContent.java @@ -4252,6 +4252,17 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp void detach(Transaction t) { removeImeSurface(t); } + + @Override + public String toString() { + StringBuilder sb = new StringBuilder(64); + sb.append("ImeScreenshot{"); + sb.append(Integer.toHexString(System.identityHashCode(this))); + sb.append(" imeTarget=" + mImeTarget); + sb.append(" surface=" + mImeSurface); + sb.append('}'); + return sb.toString(); + } } private void attachAndShowImeScreenshotOnTarget() { @@ -4284,15 +4295,23 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp } /** - * Removes the IME screenshot when necessary. - * - * Used when app transition animation finished or obsoleted screenshot surface like size - * changed by rotation. + * Removes the IME screenshot when the caller is a part of the attached target window. */ - void removeImeScreenshotIfPossible() { - if (mImeLayeringTarget == null - || mImeLayeringTarget.mAttrs.type != TYPE_APPLICATION_STARTING - && !mImeLayeringTarget.inTransitionSelfOrParent()) { + void removeImeSurfaceByTarget(WindowContainer win) { + if (mImeScreenshot == null || win == null) { + return; + } + // The starting window shouldn't be the input target to attach the IME screenshot during + // transitioning. + if (win.asWindowState() != null + && win.asWindowState().mAttrs.type == TYPE_APPLICATION_STARTING) { + return; + } + + final WindowState screenshotTarget = mImeScreenshot.getImeTarget(); + final boolean winIsOrContainsScreenshotTarget = (win == screenshotTarget + || win.getWindow(w -> w == screenshotTarget) != null); + if (winIsOrContainsScreenshotTarget) { removeImeSurfaceImmediately(); } } @@ -4640,10 +4659,8 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp wc, SurfaceAnimator.animationTypeToString(type), mImeScreenshot, mImeScreenshot.getImeTarget()); } - if (mImeScreenshot != null && (wc == mImeScreenshot.getImeTarget() - || wc.getWindow(w -> w == mImeScreenshot.getImeTarget()) != null) - && (type & WindowState.EXIT_ANIMATING_TYPES) != 0) { - removeImeSurfaceImmediately(); + if ((type & WindowState.EXIT_ANIMATING_TYPES) != 0) { + removeImeSurfaceByTarget(wc); } } diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java index af8c4c8e9370..fa78a2b4aad8 100644 --- a/services/core/java/com/android/server/wm/WindowState.java +++ b/services/core/java/com/android/server/wm/WindowState.java @@ -2449,8 +2449,8 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP final DisplayContent dc = getDisplayContent(); if (isImeLayeringTarget()) { - // Remove the IME screenshot surface if the layering target is not animating. - dc.removeImeScreenshotIfPossible(); + // Remove the attached IME screenshot surface. + dc.removeImeSurfaceByTarget(this); // Make sure to set mImeLayeringTarget as null when the removed window is the // IME target, in case computeImeTarget may use the outdated target. dc.setImeLayeringTarget(null); @@ -3584,6 +3584,7 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP } else { logExclusionRestrictions(EXCLUSION_LEFT); logExclusionRestrictions(EXCLUSION_RIGHT); + getDisplayContent().removeImeSurfaceByTarget(this); } // Exclude toast because legacy apps may show toast window by themselves, so the misused // apps won't always be considered as foreground state. diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClientTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClientTest.java index 1a49f8aa2e6a..ea1e49d8748a 100644 --- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClientTest.java +++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClientTest.java @@ -43,8 +43,10 @@ import android.hardware.fingerprint.Fingerprint; import android.hardware.fingerprint.FingerprintSensorPropertiesInternal; import android.hardware.fingerprint.ISidefpsController; import android.hardware.fingerprint.IUdfpsOverlayController; +import android.os.Handler; import android.os.IBinder; import android.os.RemoteException; +import android.os.test.TestLooper; import android.platform.test.annotations.Presubmit; import android.testing.TestableContext; @@ -128,6 +130,8 @@ public class FingerprintAuthenticationClientTest { @Captor private ArgumentCaptor<Consumer<OperationContext>> mContextInjector; + private TestLooper mLooper = new TestLooper(); + @Rule public final MockitoRule mockito = MockitoJUnit.rule(); @@ -233,6 +237,9 @@ public class FingerprintAuthenticationClientTest { client.start(mCallback); client.onAuthenticated(new Fingerprint("name", 2 /* enrollmentId */, SENSOR_ID), true /* authenticated */, new ArrayList<>()); + + mLooper.moveTimeForward(10); + mLooper.dispatchAll(); verify(mLuxProbe).destroy(); client.onAcquired(2, 0); @@ -309,9 +316,58 @@ public class FingerprintAuthenticationClientTest { client.onAuthenticated(new Fingerprint("friendly", 1 /* fingerId */, 2 /* deviceId */), true /* authenticated */, new ArrayList<>()); + mLooper.moveTimeForward(10); + mLooper.dispatchAll(); verify(mCancellationSignal).cancel(); } + @Test + public void fingerprintPowerIgnoresAuthInWindow() throws Exception { + when(mSensorProps.isAnySidefpsType()).thenReturn(true); + + final FingerprintAuthenticationClient client = createClient(1); + client.start(mCallback); + client.onPowerPressed(); + client.onAuthenticated(new Fingerprint("friendly", 1 /* fingerId */, 2 /* deviceId */), + true /* authenticated */, new ArrayList<>()); + mLooper.moveTimeForward(1000); + mLooper.dispatchAll(); + + verify(mCallback).onClientFinished(any(), eq(false)); + } + + @Test + public void fingerprintAuthIgnoredWaitingForPower() throws Exception { + when(mSensorProps.isAnySidefpsType()).thenReturn(true); + + final FingerprintAuthenticationClient client = createClient(1); + client.start(mCallback); + client.onAuthenticated(new Fingerprint("friendly", 3 /* fingerId */, 4 /* deviceId */), + true /* authenticated */, new ArrayList<>()); + client.onPowerPressed(); + mLooper.moveTimeForward(1000); + mLooper.dispatchAll(); + + verify(mCallback).onClientFinished(any(), eq(false)); + } + + @Test + public void fingerprintAuthSucceedsAfterPowerWindow() throws Exception { + when(mSensorProps.isAnySidefpsType()).thenReturn(true); + + final FingerprintAuthenticationClient client = createClient(1); + client.start(mCallback); + client.onPowerPressed(); + mLooper.moveTimeForward(1000); + mLooper.dispatchAll(); + client.onAuthenticated(new Fingerprint("friendly", 4 /* fingerId */, 5 /* deviceId */), + true /* authenticated */, new ArrayList<>()); + mLooper.moveTimeForward(1000); + mLooper.dispatchAll(); + + verify(mCallback).onClientFinished(any(), eq(true)); + } + private FingerprintAuthenticationClient createClient() throws RemoteException { return createClient(100 /* version */, true /* allowBackgroundAuthentication */); } @@ -336,7 +392,8 @@ public class FingerprintAuthenticationClientTest { 9 /* sensorId */, mBiometricLogger, mBiometricContext, true /* isStrongBiometric */, null /* taskStackListener */, mLockoutCache, - mUdfpsOverlayController, mSideFpsController, allowBackgroundAuthentication, mSensorProps) { + mUdfpsOverlayController, mSideFpsController, allowBackgroundAuthentication, mSensorProps, + new Handler(mLooper.getLooper())) { @Override protected ActivityTaskManager getActivityTaskManager() { return mActivityTaskManager; diff --git a/services/tests/servicestests/src/com/android/server/policy/SideFpsEventHandlerTest.java b/services/tests/servicestests/src/com/android/server/policy/SideFpsEventHandlerTest.java index 371861f74871..7746bd6859d6 100644 --- a/services/tests/servicestests/src/com/android/server/policy/SideFpsEventHandlerTest.java +++ b/services/tests/servicestests/src/com/android/server/policy/SideFpsEventHandlerTest.java @@ -53,33 +53,30 @@ import java.util.List; /** * Unit tests for {@link SideFpsEventHandler}. - * <p/> - * Run with <code>atest SideFpsEventHandlerTest</code>. + * + * <p>Run with <code>atest SideFpsEventHandlerTest</code>. */ @SmallTest @RunWith(AndroidTestingRunner.class) public class SideFpsEventHandlerTest { - private static final List<Integer> sAllStates = List.of( - BiometricStateListener.STATE_IDLE, - BiometricStateListener.STATE_ENROLLING, - BiometricStateListener.STATE_KEYGUARD_AUTH, - BiometricStateListener.STATE_BP_AUTH, - BiometricStateListener.STATE_AUTH_OTHER); + private static final List<Integer> sAllStates = + List.of( + BiometricStateListener.STATE_IDLE, + BiometricStateListener.STATE_ENROLLING, + BiometricStateListener.STATE_KEYGUARD_AUTH, + BiometricStateListener.STATE_BP_AUTH, + BiometricStateListener.STATE_AUTH_OTHER); @Rule public TestableContext mContext = new TestableContext(InstrumentationRegistry.getContext(), null); - @Mock - private PackageManager mPackageManager; - @Mock - private FingerprintManager mFingerprintManager; - @Spy - private AlertDialog.Builder mDialogBuilder = new AlertDialog.Builder(mContext); - @Mock - private AlertDialog mAlertDialog; - @Mock - private Window mWindow; + + @Mock private PackageManager mPackageManager; + @Mock private FingerprintManager mFingerprintManager; + @Spy private AlertDialog.Builder mDialogBuilder = new AlertDialog.Builder(mContext); + @Mock private AlertDialog mAlertDialog; + @Mock private Window mWindow; private TestLooper mLooper = new TestLooper(); private SideFpsEventHandler mEventHandler; @@ -95,9 +92,12 @@ public class SideFpsEventHandlerTest { when(mDialogBuilder.create()).thenReturn(mAlertDialog); when(mAlertDialog.getWindow()).thenReturn(mWindow); - mEventHandler = new SideFpsEventHandler( - mContext, new Handler(mLooper.getLooper()), - mContext.getSystemService(PowerManager.class), () -> mDialogBuilder); + mEventHandler = + new SideFpsEventHandler( + mContext, + new Handler(mLooper.getLooper()), + mContext.getSystemService(PowerManager.class), + () -> mDialogBuilder); } @Test @@ -105,7 +105,7 @@ public class SideFpsEventHandlerTest { when(mPackageManager.hasSystemFeature(eq(PackageManager.FEATURE_FINGERPRINT))) .thenReturn(false); - assertThat(mEventHandler.onSinglePressDetected(60L)).isFalse(); + assertThat(mEventHandler.shouldConsumeSinglePress(60L)).isFalse(); mLooper.dispatchAll(); verify(mAlertDialog, never()).show(); @@ -117,7 +117,7 @@ public class SideFpsEventHandlerTest { for (int state : sAllStates) { setBiometricState(state); - assertThat(mEventHandler.onSinglePressDetected(200L)).isFalse(); + assertThat(mEventHandler.shouldConsumeSinglePress(200L)).isFalse(); mLooper.dispatchAll(); verify(mAlertDialog, never()).show(); @@ -130,7 +130,7 @@ public class SideFpsEventHandlerTest { for (int state : sAllStates) { setBiometricState(state); - assertThat(mEventHandler.onSinglePressDetected(400L)).isFalse(); + assertThat(mEventHandler.shouldConsumeSinglePress(400L)).isFalse(); mLooper.dispatchAll(); verify(mAlertDialog, never()).show(); @@ -139,13 +139,13 @@ public class SideFpsEventHandlerTest { @Test public void ignoresWhenIdleOrUnknown() throws Exception { - setupWithSensor(true /* hasSfps */, true /* initialized */); + setupWithSensor(true /* hasSidefps */, true /* initialized */); setBiometricState(BiometricStateListener.STATE_IDLE); - assertThat(mEventHandler.onSinglePressDetected(80000L)).isFalse(); + assertThat(mEventHandler.shouldConsumeSinglePress(80000L)).isFalse(); setBiometricState(BiometricStateListener.STATE_AUTH_OTHER); - assertThat(mEventHandler.onSinglePressDetected(90000L)).isFalse(); + assertThat(mEventHandler.shouldConsumeSinglePress(90000L)).isFalse(); mLooper.dispatchAll(); verify(mAlertDialog, never()).show(); @@ -156,7 +156,7 @@ public class SideFpsEventHandlerTest { setupWithSensor(true /* hasSfps */, true /* initialized */); setBiometricState(BiometricStateListener.STATE_KEYGUARD_AUTH); - assertThat(mEventHandler.onSinglePressDetected(80000L)).isFalse(); + assertThat(mEventHandler.shouldConsumeSinglePress(80000L)).isFalse(); mLooper.dispatchAll(); verify(mAlertDialog, never()).show(); @@ -164,13 +164,13 @@ public class SideFpsEventHandlerTest { @Test public void promptsWhenBPisActive() throws Exception { - setupWithSensor(true /* hasSfps */, true /* initialized */); + setupWithSensor(true /* hasSideFps */, true /* initialized */); setBiometricState(BiometricStateListener.STATE_BP_AUTH); - assertThat(mEventHandler.onSinglePressDetected(80000L)).isTrue(); + assertThat(mEventHandler.shouldConsumeSinglePress(80000L)).isTrue(); mLooper.dispatchAll(); - verify(mAlertDialog).show(); + verify(mAlertDialog, never()).show(); } @Test @@ -178,7 +178,57 @@ public class SideFpsEventHandlerTest { setupWithSensor(true /* hasSfps */, true /* initialized */); setBiometricState(BiometricStateListener.STATE_ENROLLING); - assertThat(mEventHandler.onSinglePressDetected(80000L)).isTrue(); + assertThat(mEventHandler.shouldConsumeSinglePress(80000L)).isTrue(); + + mLooper.dispatchAll(); + verify(mAlertDialog).show(); + verify(mAlertDialog, never()).dismiss(); + } + + @Test + public void dismissesDialogOnTouchWhenEnrolling() throws Exception { + setupWithSensor(true /* hasSfps */, true /* initialized */); + + setBiometricState(BiometricStateListener.STATE_ENROLLING); + when(mAlertDialog.isShowing()).thenReturn(true); + assertThat(mEventHandler.shouldConsumeSinglePress(80000L)).isTrue(); + + mLooper.dispatchAll(); + verify(mAlertDialog).show(); + + mBiometricStateListener.onBiometricAction(BiometricStateListener.ACTION_SENSOR_TOUCH); + mLooper.moveTimeForward(10000); + mLooper.dispatchAll(); + + verify(mAlertDialog).dismiss(); + } + + @Test + public void dismissesDialogFailsWhenPowerPressedAndDialogShowing() throws Exception { + setupWithSensor(true /* hasSfps */, true /* initialized */); + + setBiometricState(BiometricStateListener.STATE_ENROLLING); + when(mAlertDialog.isShowing()).thenReturn(true); + assertThat(mEventHandler.shouldConsumeSinglePress(80000L)).isTrue(); + + mLooper.dispatchAll(); + verify(mAlertDialog).show(); + + mBiometricStateListener.onBiometricAction(BiometricStateListener.ACTION_SENSOR_TOUCH); + assertThat(mEventHandler.shouldConsumeSinglePress(60L)).isTrue(); + + mLooper.dispatchAll(); + verify(mAlertDialog, never()).dismiss(); + } + + @Test + public void showDialogAfterTap() throws Exception { + setupWithSensor(true /* hasSfps */, true /* initialized */); + + setBiometricState(BiometricStateListener.STATE_ENROLLING); + when(mAlertDialog.isShowing()).thenReturn(true); + mBiometricStateListener.onBiometricAction(BiometricStateListener.ACTION_SENSOR_TOUCH); + assertThat(mEventHandler.shouldConsumeSinglePress(60L)).isTrue(); mLooper.dispatchAll(); verify(mAlertDialog).show(); @@ -201,11 +251,13 @@ public class SideFpsEventHandlerTest { ArgumentCaptor.forClass(IFingerprintAuthenticatorsRegisteredCallback.class); verify(mFingerprintManager).addAuthenticatorsRegisteredCallback(fpCallbackCaptor.capture()); if (initialized) { - fpCallbackCaptor.getValue().onAllAuthenticatorsRegistered( - List.of(mock(FingerprintSensorPropertiesInternal.class))); + fpCallbackCaptor + .getValue() + .onAllAuthenticatorsRegistered( + List.of(mock(FingerprintSensorPropertiesInternal.class))); if (hasSfps) { - ArgumentCaptor<BiometricStateListener> captor = ArgumentCaptor.forClass( - BiometricStateListener.class); + ArgumentCaptor<BiometricStateListener> captor = + ArgumentCaptor.forClass(BiometricStateListener.class); verify(mFingerprintManager).registerBiometricStateListener(captor.capture()); mBiometricStateListener = captor.getValue(); } diff --git a/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java b/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java index 1f07b20acc13..d6b807fc223e 100644 --- a/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java @@ -2207,6 +2207,52 @@ public class DisplayContentTests extends WindowTestsBase { assertNotEquals(curSnapshot, mDisplayContent.mImeScreenshot); } + @UseTestDisplay(addWindows = {W_INPUT_METHOD}) + @Test + public void testRemoveImeScreenshot_whenTargetSurfaceWasInvisible() { + final Task rootTask = createTask(mDisplayContent); + final Task task = createTaskInRootTask(rootTask, 0 /* userId */); + final ActivityRecord activity = createActivityRecord(mDisplayContent, task); + final WindowState win = createWindow(null, TYPE_BASE_APPLICATION, activity, "win"); + win.onSurfaceShownChanged(true); + makeWindowVisible(win, mDisplayContent.mInputMethodWindow); + task.getDisplayContent().prepareAppTransition(TRANSIT_CLOSE); + doReturn(true).when(task).okToAnimate(); + ArrayList<WindowContainer> sources = new ArrayList<>(); + sources.add(activity); + + mDisplayContent.setImeLayeringTarget(win); + mDisplayContent.setImeInputTarget(win); + mDisplayContent.getInsetsStateController().getImeSourceProvider().setImeShowing(true); + task.applyAnimation(null, TRANSIT_OLD_TASK_CLOSE, false /* enter */, + false /* isVoiceInteraction */, sources); + assertNotNull(mDisplayContent.mImeScreenshot); + + win.onSurfaceShownChanged(false); + assertNull(mDisplayContent.mImeScreenshot); + } + + @UseTestDisplay(addWindows = {W_INPUT_METHOD}) + @Test + public void testRemoveImeScreenshot_whenWindowRemoveImmediately() { + final Task rootTask = createTask(mDisplayContent); + final Task task = createTaskInRootTask(rootTask, 0 /* userId */); + final ActivityRecord activity = createActivityRecord(mDisplayContent, task); + final WindowState win = createWindow(null, TYPE_BASE_APPLICATION, activity, "win"); + makeWindowVisible(mDisplayContent.mInputMethodWindow); + + mDisplayContent.setImeLayeringTarget(win); + mDisplayContent.setImeInputTarget(win); + mDisplayContent.getInsetsStateController().getImeSourceProvider().setImeShowing(true); + mDisplayContent.showImeScreenshot(); + assertNotNull(mDisplayContent.mImeScreenshot); + + // Expect IME snapshot will be removed when the win is IME layering target and invoked + // removeImeSurfaceByTarget. + win.removeImmediately(); + assertNull(mDisplayContent.mImeScreenshot); + } + @Test public void testRotateBounds_keepSamePhysicalPosition() { final DisplayContent dc = |