diff options
106 files changed, 2881 insertions, 1040 deletions
diff --git a/boot/preloaded-classes b/boot/preloaded-classes index 72322ef5ec9e..548fa2f00ef0 100644 --- a/boot/preloaded-classes +++ b/boot/preloaded-classes @@ -8924,9 +8924,9 @@ android.view.accessibility.IAccessibilityManager android.view.accessibility.IAccessibilityManagerClient$Stub$Proxy android.view.accessibility.IAccessibilityManagerClient$Stub android.view.accessibility.IAccessibilityManagerClient -android.view.accessibility.IWindowMagnificationConnection$Stub$Proxy -android.view.accessibility.IWindowMagnificationConnection$Stub -android.view.accessibility.IWindowMagnificationConnection +android.view.accessibility.IMagnificationConnection$Stub$Proxy +android.view.accessibility.IMagnificationConnection$Stub +android.view.accessibility.IMagnificationConnection android.view.accessibility.WeakSparseArray$WeakReferenceWithId android.view.accessibility.WeakSparseArray android.view.animation.AccelerateDecelerateInterpolator diff --git a/config/preloaded-classes b/config/preloaded-classes index cace87c76a15..c49971eb68ae 100644 --- a/config/preloaded-classes +++ b/config/preloaded-classes @@ -8955,9 +8955,9 @@ android.view.accessibility.IAccessibilityManager android.view.accessibility.IAccessibilityManagerClient$Stub$Proxy android.view.accessibility.IAccessibilityManagerClient$Stub android.view.accessibility.IAccessibilityManagerClient -android.view.accessibility.IWindowMagnificationConnection$Stub$Proxy -android.view.accessibility.IWindowMagnificationConnection$Stub -android.view.accessibility.IWindowMagnificationConnection +android.view.accessibility.IMagnificationConnection$Stub$Proxy +android.view.accessibility.IMagnificationConnection$Stub +android.view.accessibility.IMagnificationConnection android.view.accessibility.WeakSparseArray$WeakReferenceWithId android.view.accessibility.WeakSparseArray android.view.animation.AccelerateDecelerateInterpolator diff --git a/core/api/current.txt b/core/api/current.txt index 119a3de6835e..137982102bd1 100644 --- a/core/api/current.txt +++ b/core/api/current.txt @@ -40630,7 +40630,6 @@ package android.service.notification { method public static boolean isValidId(android.net.Uri, String); method public static android.net.Uri.Builder newId(android.content.Context); method public static String relevanceToString(int); - method @FlaggedApi("android.app.modes_api") @NonNull public static String sourceToString(int); method public static String stateToString(int); method public void writeToParcel(android.os.Parcel, int); field @NonNull public static final android.os.Parcelable.Creator<android.service.notification.Condition> CREATOR; @@ -49269,6 +49268,7 @@ package android.util { field public static final int DENSITY_300 = 300; // 0x12c field public static final int DENSITY_340 = 340; // 0x154 field public static final int DENSITY_360 = 360; // 0x168 + field @FlaggedApi("com.android.window.flags.density_390_api") public static final int DENSITY_390 = 390; // 0x186 field public static final int DENSITY_400 = 400; // 0x190 field public static final int DENSITY_420 = 420; // 0x1a4 field public static final int DENSITY_440 = 440; // 0x1b8 diff --git a/core/api/removed.txt b/core/api/removed.txt index 12b1f6a1fba3..b58c822e39bc 100644 --- a/core/api/removed.txt +++ b/core/api/removed.txt @@ -8,16 +8,6 @@ package android.app { method @Deprecated public void setLatestEventInfo(android.content.Context, CharSequence, CharSequence, android.app.PendingIntent); } - public static final class Notification.BubbleMetadata implements android.os.Parcelable { - method @Deprecated @Nullable public android.graphics.drawable.Icon getBubbleIcon(); - method @Deprecated @Nullable public android.app.PendingIntent getBubbleIntent(); - } - - public static final class Notification.BubbleMetadata.Builder { - method @Deprecated @NonNull public android.app.Notification.BubbleMetadata.Builder createIntentBubble(@NonNull android.app.PendingIntent, @NonNull android.graphics.drawable.Icon); - method @Deprecated @NonNull public android.app.Notification.BubbleMetadata.Builder createShortcutBubble(@NonNull String); - } - public static class Notification.Builder { method @Deprecated public android.app.Notification.Builder setChannel(String); method @Deprecated public android.app.Notification.Builder setTimeout(long); @@ -111,28 +101,6 @@ package android.graphics { field @Deprecated public static final int MATRIX_SAVE_FLAG = 1; // 0x1 } - public final class ImageDecoder implements java.lang.AutoCloseable { - method @Deprecated public boolean getAsAlphaMask(); - method @Deprecated public boolean getConserveMemory(); - method @Deprecated public boolean getDecodeAsAlphaMask(); - method @Deprecated public boolean getMutable(); - method @Deprecated public boolean getRequireUnpremultiplied(); - method @Deprecated public android.graphics.ImageDecoder setAsAlphaMask(boolean); - method @Deprecated public void setConserveMemory(boolean); - method @Deprecated public android.graphics.ImageDecoder setDecodeAsAlphaMask(boolean); - method @Deprecated public android.graphics.ImageDecoder setMutable(boolean); - method @Deprecated public android.graphics.ImageDecoder setRequireUnpremultiplied(boolean); - method @Deprecated public android.graphics.ImageDecoder setResize(int, int); - method @Deprecated public android.graphics.ImageDecoder setResize(int); - field @Deprecated public static final int ERROR_SOURCE_ERROR = 3; // 0x3 - field @Deprecated public static final int ERROR_SOURCE_EXCEPTION = 1; // 0x1 - field @Deprecated public static final int ERROR_SOURCE_INCOMPLETE = 2; // 0x2 - } - - @Deprecated public static class ImageDecoder.IncompleteException extends java.io.IOException { - ctor public ImageDecoder.IncompleteException(); - } - @Deprecated public class LayerRasterizer extends android.graphics.Rasterizer { ctor public LayerRasterizer(); method public void addLayer(android.graphics.Paint, float, float); diff --git a/core/api/test-current.txt b/core/api/test-current.txt index f4c8429619dd..71a05a909a09 100644 --- a/core/api/test-current.txt +++ b/core/api/test-current.txt @@ -3101,6 +3101,10 @@ package android.service.watchdog { package android.speech { + public abstract class RecognitionService extends android.app.Service { + method public void onBindInternal(); + } + public class SpeechRecognizer { method @MainThread @NonNull public static android.speech.SpeechRecognizer createOnDeviceTestingSpeechRecognizer(@NonNull android.content.Context); method @RequiresPermission(android.Manifest.permission.MANAGE_SPEECH_RECOGNITION) public void setTemporaryOnDeviceRecognizer(@Nullable android.content.ComponentName); diff --git a/core/java/android/accessibilityservice/AccessibilityTrace.java b/core/java/android/accessibilityservice/AccessibilityTrace.java index f28015ab4685..7700b33253fd 100644 --- a/core/java/android/accessibilityservice/AccessibilityTrace.java +++ b/core/java/android/accessibilityservice/AccessibilityTrace.java @@ -35,7 +35,7 @@ public interface AccessibilityTrace { String NAME_ACCESSIBILITY_INTERACTION_CONNECTION_CALLBACK = "IAccessibilityInteractionConnectionCallback"; String NAME_REMOTE_MAGNIFICATION_ANIMATION_CALLBACK = "IRemoteMagnificationAnimationCallback"; - String NAME_WINDOW_MAGNIFICATION_CONNECTION = "IWindowMagnificationConnection"; + String NAME_MAGNIFICATION_CONNECTION = "IMagnificationConnection"; String NAME_WINDOW_MAGNIFICATION_CONNECTION_CALLBACK = "IWindowMagnificationConnectionCallback"; String NAME_WINDOW_MANAGER_INTERNAL = "WindowManagerInternal"; String NAME_WINDOWS_FOR_ACCESSIBILITY_CALLBACK = "WindowsForAccessibilityCallback"; @@ -58,7 +58,7 @@ public interface AccessibilityTrace { long FLAGS_ACCESSIBILITY_INTERACTION_CONNECTION = 0x0000000000000010L; long FLAGS_ACCESSIBILITY_INTERACTION_CONNECTION_CALLBACK = 0x0000000000000020L; long FLAGS_REMOTE_MAGNIFICATION_ANIMATION_CALLBACK = 0x0000000000000040L; - long FLAGS_WINDOW_MAGNIFICATION_CONNECTION = 0x0000000000000080L; + long FLAGS_MAGNIFICATION_CONNECTION = 0x0000000000000080L; long FLAGS_WINDOW_MAGNIFICATION_CONNECTION_CALLBACK = 0x0000000000000100L; long FLAGS_WINDOW_MANAGER_INTERNAL = 0x0000000000000200L; long FLAGS_WINDOWS_FOR_ACCESSIBILITY_CALLBACK = 0x0000000000000400L; @@ -98,7 +98,7 @@ public interface AccessibilityTrace { NAME_REMOTE_MAGNIFICATION_ANIMATION_CALLBACK, FLAGS_REMOTE_MAGNIFICATION_ANIMATION_CALLBACK), new AbstractMap.SimpleEntry<String, Long>( - NAME_WINDOW_MAGNIFICATION_CONNECTION, FLAGS_WINDOW_MAGNIFICATION_CONNECTION), + NAME_MAGNIFICATION_CONNECTION, FLAGS_MAGNIFICATION_CONNECTION), new AbstractMap.SimpleEntry<String, Long>( NAME_WINDOW_MAGNIFICATION_CONNECTION_CALLBACK, FLAGS_WINDOW_MAGNIFICATION_CONNECTION_CALLBACK), diff --git a/core/java/android/app/AppOpsManager.java b/core/java/android/app/AppOpsManager.java index aec042739c0d..71fe47e7b949 100644 --- a/core/java/android/app/AppOpsManager.java +++ b/core/java/android/app/AppOpsManager.java @@ -8370,9 +8370,29 @@ public class AppOpsManager { * Does not throw a security exception, does not translate {@link #MODE_FOREGROUND}. * @hide */ + public int unsafeCheckOpRawNoThrow(int op, @NonNull AttributionSource attributionSource) { + return unsafeCheckOpRawNoThrow(op, attributionSource.getUid(), + attributionSource.getPackageName(), attributionSource.getDeviceId()); + } + + /** + * Returns the <em>raw</em> mode associated with the op. + * Does not throw a security exception, does not translate {@link #MODE_FOREGROUND}. + * @hide + */ public int unsafeCheckOpRawNoThrow(int op, int uid, @NonNull String packageName) { + return unsafeCheckOpRawNoThrow(op, uid, packageName, Context.DEVICE_ID_DEFAULT); + } + + private int unsafeCheckOpRawNoThrow(int op, int uid, @NonNull String packageName, + int virtualDeviceId) { try { - return mService.checkOperationRaw(op, uid, packageName, null); + if (virtualDeviceId == Context.DEVICE_ID_DEFAULT) { + return mService.checkOperationRaw(op, uid, packageName, null); + } else { + return mService.checkOperationRawForDevice(op, uid, packageName, null, + Context.DEVICE_ID_DEFAULT); + } } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } @@ -8517,12 +8537,29 @@ public class AppOpsManager { } /** + * @see #noteOp(String, int, String, String, String) + * + * @hide + */ + public int noteOpNoThrow(int op, @NonNull AttributionSource attributionSource, + @Nullable String message) { + return noteOpNoThrow(op, attributionSource.getUid(), attributionSource.getPackageName(), + attributionSource.getAttributionTag(), attributionSource.getDeviceId(), message); + } + + /** * @see #noteOpNoThrow(String, int, String, String, String) * * @hide */ public int noteOpNoThrow(int op, int uid, @Nullable String packageName, @Nullable String attributionTag, @Nullable String message) { + return noteOpNoThrow(op, uid, packageName, attributionTag, Context.DEVICE_ID_DEFAULT, + message); + } + + private int noteOpNoThrow(int op, int uid, @Nullable String packageName, + @Nullable String attributionTag, int virtualDeviceId, @Nullable String message) { try { collectNoteOpCallsForValidation(op); int collectionMode = getNotedOpCollectionMode(uid, packageName, op); @@ -8535,9 +8572,15 @@ public class AppOpsManager { } } - SyncNotedAppOp syncOp = mService.noteOperation(op, uid, packageName, attributionTag, + SyncNotedAppOp syncOp; + if (virtualDeviceId == Context.DEVICE_ID_DEFAULT) { + syncOp = mService.noteOperation(op, uid, packageName, attributionTag, collectionMode == COLLECT_ASYNC, message, shouldCollectMessage); - + } else { + syncOp = mService.noteOperationForDevice(op, uid, packageName, attributionTag, + virtualDeviceId, collectionMode == COLLECT_ASYNC, message, + shouldCollectMessage); + } if (syncOp.getOpMode() == MODE_ALLOWED) { if (collectionMode == COLLECT_SELF) { collectNotedOpForSelf(syncOp); @@ -8775,7 +8818,8 @@ public class AppOpsManager { @UnsupportedAppUsage public int checkOp(int op, int uid, String packageName) { try { - int mode = mService.checkOperation(op, uid, packageName); + int mode = mService.checkOperationForDevice(op, uid, packageName, + Context.DEVICE_ID_DEFAULT); if (mode == MODE_ERRORED) { throw new SecurityException(buildSecurityExceptionMsg(op, uid, packageName)); } @@ -8786,6 +8830,19 @@ public class AppOpsManager { } /** + * Like {@link #checkOp} but instead of throwing a {@link SecurityException}, it + * returns {@link #MODE_ERRORED}. + * + * @see #checkOp(int, int, String) + * + * @hide + */ + public int checkOpNoThrow(int op, AttributionSource attributionSource) { + return checkOpNoThrow(op, attributionSource.getUid(), attributionSource.getPackageName(), + attributionSource.getDeviceId()); + } + + /** * Like {@link #checkOp} but instead of throwing a {@link SecurityException} it * returns {@link #MODE_ERRORED}. * @@ -8795,8 +8852,18 @@ public class AppOpsManager { */ @UnsupportedAppUsage public int checkOpNoThrow(int op, int uid, String packageName) { + return checkOpNoThrow(op, uid, packageName, Context.DEVICE_ID_DEFAULT); + } + + private int checkOpNoThrow(int op, int uid, String packageName, int virtualDeviceId) { try { - int mode = mService.checkOperation(op, uid, packageName); + int mode; + if (virtualDeviceId == Context.DEVICE_ID_DEFAULT) { + mode = mService.checkOperation(op, uid, packageName); + } else { + mode = mService.checkOperationForDevice(op, uid, packageName, virtualDeviceId); + } + return mode == AppOpsManager.MODE_FOREGROUND ? AppOpsManager.MODE_ALLOWED : mode; } catch (RemoteException e) { throw e.rethrowFromSystemServer(); @@ -9026,9 +9093,32 @@ public class AppOpsManager { * * @hide */ + public int startOpNoThrow(@NonNull IBinder token, int op, + @NonNull AttributionSource attributionSource, + boolean startIfModeDefault, @Nullable String message, + @AttributionFlags int attributionFlags, int attributionChainId) { + return startOpNoThrow(token, op, attributionSource.getUid(), + attributionSource.getPackageName(), startIfModeDefault, + attributionSource.getAttributionTag(), attributionSource.getDeviceId(), + message, attributionFlags, attributionChainId); + } + + /** + * @see #startOpNoThrow(String, int, String, String, String) + * + * @hide + */ public int startOpNoThrow(@NonNull IBinder token, int op, int uid, @NonNull String packageName, boolean startIfModeDefault, @Nullable String attributionTag, @Nullable String message, @AttributionFlags int attributionFlags, int attributionChainId) { + return startOpNoThrow(token, op, uid, packageName, startIfModeDefault, attributionTag, + Context.DEVICE_ID_DEFAULT, message, attributionFlags, attributionChainId); + } + + private int startOpNoThrow(@NonNull IBinder token, int op, int uid, @NonNull String packageName, + boolean startIfModeDefault, @Nullable String attributionTag, int virtualDeviceId, + @Nullable String message, @AttributionFlags int attributionFlags, + int attributionChainId) { try { collectNoteOpCallsForValidation(op); int collectionMode = getNotedOpCollectionMode(uid, packageName, op); @@ -9041,10 +9131,17 @@ public class AppOpsManager { } } - SyncNotedAppOp syncOp = mService.startOperation(token, op, uid, packageName, + SyncNotedAppOp syncOp; + if (virtualDeviceId == Context.DEVICE_ID_DEFAULT) { + syncOp = mService.startOperation(token, op, uid, packageName, attributionTag, startIfModeDefault, collectionMode == COLLECT_ASYNC, message, shouldCollectMessage, attributionFlags, attributionChainId); - + } else { + syncOp = mService.startOperationForDevice(token, op, uid, packageName, + attributionTag, virtualDeviceId, startIfModeDefault, + collectionMode == COLLECT_ASYNC, message, shouldCollectMessage, + attributionFlags, attributionChainId); + } if (syncOp.getOpMode() == MODE_ALLOWED) { if (collectionMode == COLLECT_SELF) { collectNotedOpForSelf(syncOp); @@ -9252,10 +9349,31 @@ public class AppOpsManager { * * @hide */ + public void finishOp(IBinder token, int op, @NonNull AttributionSource attributionSource) { + finishOp(token, op, attributionSource.getUid(), + attributionSource.getPackageName(), attributionSource.getAttributionTag(), + attributionSource.getDeviceId()); + } + + /** + * @see #finishOp(String, int, String, String) + * + * @hide + */ public void finishOp(IBinder token, int op, int uid, @NonNull String packageName, @Nullable String attributionTag) { + finishOp(token, op, uid, packageName, attributionTag, Context.DEVICE_ID_DEFAULT); + } + + private void finishOp(IBinder token, int op, int uid, @NonNull String packageName, + @Nullable String attributionTag, int virtualDeviceId ) { try { - mService.finishOperation(token, op, uid, packageName, attributionTag); + if (virtualDeviceId == Context.DEVICE_ID_DEFAULT) { + mService.finishOperation(token, op, uid, packageName, attributionTag); + } else { + mService.finishOperationForDevice(token, op, uid, packageName, attributionTag, + virtualDeviceId); + } } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } diff --git a/core/java/android/app/AppOpsManagerInternal.java b/core/java/android/app/AppOpsManagerInternal.java index 43023fe9c2ab..8daee5867238 100644 --- a/core/java/android/app/AppOpsManagerInternal.java +++ b/core/java/android/app/AppOpsManagerInternal.java @@ -26,11 +26,11 @@ import android.util.SparseArray; import android.util.SparseIntArray; import com.android.internal.app.IAppOpsCallback; -import com.android.internal.util.function.HeptFunction; +import com.android.internal.util.function.DodecFunction; +import com.android.internal.util.function.HexConsumer; import com.android.internal.util.function.HexFunction; +import com.android.internal.util.function.OctFunction; import com.android.internal.util.function.QuadFunction; -import com.android.internal.util.function.QuintConsumer; -import com.android.internal.util.function.QuintFunction; import com.android.internal.util.function.UndecFunction; /** @@ -48,13 +48,14 @@ public abstract class AppOpsManagerInternal { * @param uid The UID for which to check. * @param packageName The package for which to check. * @param attributionTag The attribution tag for which to check. + * @param virtualDeviceId the device for which to check the op * @param raw Whether to check the raw op i.e. not interpret the mode based on UID state. * @param superImpl The super implementation. * @return The app op check result. */ int checkOperation(int code, int uid, String packageName, @Nullable String attributionTag, - boolean raw, QuintFunction<Integer, Integer, String, String, Boolean, Integer> - superImpl); + int virtualDeviceId, boolean raw, HexFunction<Integer, Integer, String, String, + Integer, Boolean, Integer> superImpl); /** * Allows overriding check audio operation behavior. @@ -76,16 +77,17 @@ public abstract class AppOpsManagerInternal { * @param uid The UID for which to note. * @param packageName The package for which to note. {@code null} for system package. * @param featureId Id of the feature in the package + * @param virtualDeviceId the device for which to note the op * @param shouldCollectAsyncNotedOp If an {@link AsyncNotedAppOp} should be collected * @param message The message in the async noted op * @param superImpl The super implementation. * @return The app op note result. */ SyncNotedAppOp noteOperation(int code, int uid, @Nullable String packageName, - @Nullable String featureId, boolean shouldCollectAsyncNotedOp, + @Nullable String featureId, int virtualDeviceId, boolean shouldCollectAsyncNotedOp, @Nullable String message, boolean shouldCollectMessage, - @NonNull HeptFunction<Integer, Integer, String, String, Boolean, String, Boolean, - SyncNotedAppOp> superImpl); + @NonNull OctFunction<Integer, Integer, String, String, Integer, Boolean, String, + Boolean, SyncNotedAppOp> superImpl); /** * Allows overriding note proxy operation behavior. @@ -113,6 +115,7 @@ public abstract class AppOpsManagerInternal { * @param uid The UID for which to note. * @param packageName The package for which to note. {@code null} for system package. * @param attributionTag the attribution tag. + * @param virtualDeviceId the device for which to start the op * @param startIfModeDefault Whether to start the op of the mode is default. * @param shouldCollectAsyncNotedOp If an {@link AsyncNotedAppOp} should be collected * @param message The message in the async noted op @@ -123,11 +126,11 @@ public abstract class AppOpsManagerInternal { * @return The app op note result. */ SyncNotedAppOp startOperation(IBinder token, int code, int uid, - @Nullable String packageName, @Nullable String attributionTag, + @Nullable String packageName, @Nullable String attributionTag, int virtualDeviceId, boolean startIfModeDefault, boolean shouldCollectAsyncNotedOp, @Nullable String message, boolean shouldCollectMessage, @AttributionFlags int attributionFlags, int attributionChainId, - @NonNull UndecFunction<IBinder, Integer, Integer, String, String, Boolean, + @NonNull DodecFunction<IBinder, Integer, Integer, String, String, Integer, Boolean, Boolean, String, Boolean, Integer, Integer, SyncNotedAppOp> superImpl); /** @@ -164,11 +167,13 @@ public abstract class AppOpsManagerInternal { * @param uid The UID for which the op was noted. * @param packageName The package for which it was noted. {@code null} for system package. * @param attributionTag the attribution tag. + * @param virtualDeviceId the device for which to finish the op + * @param superImpl */ default void finishOperation(IBinder clientId, int code, int uid, String packageName, - String attributionTag, - @NonNull QuintConsumer<IBinder, Integer, Integer, String, String> superImpl) { - superImpl.accept(clientId, code, uid, packageName, attributionTag); + String attributionTag, int virtualDeviceId, @NonNull HexConsumer<IBinder, Integer, + Integer, String, String, Integer> superImpl) { + superImpl.accept(clientId, code, uid, packageName, attributionTag, virtualDeviceId); } /** diff --git a/core/java/android/app/DownloadManager.java b/core/java/android/app/DownloadManager.java index fd13174a6a39..b781ce50c4db 100644 --- a/core/java/android/app/DownloadManager.java +++ b/core/java/android/app/DownloadManager.java @@ -1112,12 +1112,17 @@ public class DownloadManager { * ready to execute it and connectivity is available. * * @param request the parameters specifying this download - * @return an ID for the download, unique across the system. This ID is used to make future - * calls related to this download. + * @return an ID for the download, unique across the system. This ID is used to make + * future calls related to this download. Returns -1 if the operation fails. */ public long enqueue(Request request) { ContentValues values = request.toContentValues(mPackageName); Uri downloadUri = mResolver.insert(Downloads.Impl.CONTENT_URI, values); + if (downloadUri == null) { + // If insert fails due to RemoteException, it would return a null uri. + return -1; + } + long id = Long.parseLong(downloadUri.getLastPathSegment()); return id; } diff --git a/core/java/android/app/Notification.java b/core/java/android/app/Notification.java index 8c5773a05764..c003540100ae 100644 --- a/core/java/android/app/Notification.java +++ b/core/java/android/app/Notification.java @@ -10383,16 +10383,6 @@ public class Notification implements Parcelable } /** - * @deprecated use {@link #getIntent()} instead. - * @removed Removed from the R SDK but was never publicly stable. - */ - @Nullable - @Deprecated - public PendingIntent getBubbleIntent() { - return mPendingIntent; - } - - /** * @return the pending intent to send when the bubble is dismissed by a user, if one exists. */ @Nullable @@ -10411,16 +10401,6 @@ public class Notification implements Parcelable } /** - * @deprecated use {@link #getIcon()} instead. - * @removed Removed from the R SDK but was never publicly stable. - */ - @Nullable - @Deprecated - public Icon getBubbleIcon() { - return mIcon; - } - - /** * @return the ideal height, in DPs, for the floating window that app content defined by * {@link #getIntent()} for this bubble. A value of 0 indicates a desired height has * not been set. @@ -10677,48 +10657,6 @@ public class Notification implements Parcelable } /** - * @deprecated use {@link Builder#Builder(String)} instead. - * @removed Removed from the R SDK but was never publicly stable. - */ - @NonNull - @Deprecated - public BubbleMetadata.Builder createShortcutBubble(@NonNull String shortcutId) { - if (!TextUtils.isEmpty(shortcutId)) { - // If shortcut id is set, we don't use these if they were previously set. - mPendingIntent = null; - mIcon = null; - } - mShortcutId = shortcutId; - return this; - } - - /** - * @deprecated use {@link Builder#Builder(PendingIntent, Icon)} instead. - * @removed Removed from the R SDK but was never publicly stable. - */ - @NonNull - @Deprecated - public BubbleMetadata.Builder createIntentBubble(@NonNull PendingIntent intent, - @NonNull Icon icon) { - if (intent == null) { - throw new IllegalArgumentException("Bubble requires non-null pending intent"); - } - if (icon == null) { - throw new IllegalArgumentException("Bubbles require non-null icon"); - } - if (icon.getType() != TYPE_URI_ADAPTIVE_BITMAP - && icon.getType() != TYPE_URI) { - Log.w(TAG, "Bubbles work best with icons of TYPE_URI or " - + "TYPE_URI_ADAPTIVE_BITMAP. " - + "In the future, using an icon of this type will be required."); - } - mShortcutId = null; - mPendingIntent = intent; - mIcon = icon; - return this; - } - - /** * Sets the intent for the bubble. * * <p>The intent that will be used when the bubble is expanded. This will display the diff --git a/core/java/android/content/AttributionSource.java b/core/java/android/content/AttributionSource.java index 697c25c2a1ec..b2074a6e7309 100644 --- a/core/java/android/content/AttributionSource.java +++ b/core/java/android/content/AttributionSource.java @@ -107,6 +107,13 @@ public final class AttributionSource implements Parcelable { } /** @hide */ + public AttributionSource(int uid, @Nullable String packageName, + @Nullable String attributionTag, int virtualDeviceId) { + this(uid, Process.INVALID_PID, packageName, attributionTag, sDefaultToken, null, + virtualDeviceId, null); + } + + /** @hide */ public AttributionSource(int uid, int pid, @Nullable String packageName, @Nullable String attributionTag) { this(uid, pid, packageName, attributionTag, sDefaultToken); diff --git a/core/java/android/os/vibrator/flags.aconfig b/core/java/android/os/vibrator/flags.aconfig index 69d86a6604ad..437668c9a7de 100644 --- a/core/java/android/os/vibrator/flags.aconfig +++ b/core/java/android/os/vibrator/flags.aconfig @@ -44,3 +44,10 @@ flag { description: "Enables the independent keyboard vibration settings feature" bug: "289107579" } + +flag { + namespace: "haptics" + name: "adaptive_haptics_enabled" + description: "Enables the adaptive haptics feature" + bug: "305961689" +} diff --git a/core/java/android/service/notification/Condition.java b/core/java/android/service/notification/Condition.java index d76fa5be4940..531626b1e85f 100644 --- a/core/java/android/service/notification/Condition.java +++ b/core/java/android/service/notification/Condition.java @@ -257,7 +257,10 @@ public final class Condition implements Parcelable { throw new IllegalArgumentException("state is invalid: " + state); } - /** Provides a human-readable string version of the Source enum. */ + /** + * Provides a human-readable string version of the Source enum. + * @hide + */ @FlaggedApi(Flags.FLAG_MODES_API) public static @NonNull String sourceToString(@Source int source) { if (source == SOURCE_UNKNOWN) return "SOURCE_UNKNOWN"; diff --git a/core/java/android/speech/RecognitionService.java b/core/java/android/speech/RecognitionService.java index 7f313c177053..cd80a0b597e4 100644 --- a/core/java/android/speech/RecognitionService.java +++ b/core/java/android/speech/RecognitionService.java @@ -22,6 +22,7 @@ import android.annotation.Nullable; import android.annotation.SdkConstant; import android.annotation.SdkConstant.SdkConstantType; import android.annotation.SuppressLint; +import android.annotation.TestApi; import android.app.AppOpsManager; import android.app.Service; import android.content.AttributionSource; @@ -514,9 +515,15 @@ public abstract class RecognitionService extends Service { @Override public final IBinder onBind(final Intent intent) { if (DBG) Log.d(TAG, "#onBind, intent=" + intent); + onBindInternal(); return mBinder; } + /** @hide */ + @SuppressLint("UnflaggedApi") // @TestApi without associated feature. + @TestApi + public void onBindInternal() { } + @Override public void onDestroy() { if (DBG) Log.d(TAG, "#onDestroy"); diff --git a/core/java/android/speech/SpeechRecognizerImpl.java b/core/java/android/speech/SpeechRecognizerImpl.java index 6c008743f07a..f3f17defce5d 100644 --- a/core/java/android/speech/SpeechRecognizerImpl.java +++ b/core/java/android/speech/SpeechRecognizerImpl.java @@ -61,6 +61,7 @@ class SpeechRecognizerImpl extends SpeechRecognizer { private static final int MSG_SET_TEMPORARY_ON_DEVICE_COMPONENT = 5; private static final int MSG_CHECK_RECOGNITION_SUPPORT = 6; private static final int MSG_TRIGGER_MODEL_DOWNLOAD = 7; + private static final int MSG_DESTROY = 8; /** The actual RecognitionService endpoint */ private IRecognitionService mService; @@ -77,39 +78,31 @@ class SpeechRecognizerImpl extends SpeechRecognizer { private IRecognitionServiceManager mManagerService; /** Handler that will execute the main tasks */ - private Handler mHandler = new Handler(Looper.getMainLooper()) { + private final Handler mHandler = new Handler(Looper.getMainLooper()) { @Override public void handleMessage(Message msg) { switch (msg.what) { - case MSG_START: - handleStartListening((Intent) msg.obj); - break; - case MSG_STOP: - handleStopMessage(); - break; - case MSG_CANCEL: - handleCancelMessage(); - break; - case MSG_CHANGE_LISTENER: - handleChangeListener((RecognitionListener) msg.obj); - break; - case MSG_SET_TEMPORARY_ON_DEVICE_COMPONENT: - handleSetTemporaryComponent((ComponentName) msg.obj); - break; - case MSG_CHECK_RECOGNITION_SUPPORT: + case MSG_START -> handleStartListening((Intent) msg.obj); + case MSG_STOP -> handleStopMessage(); + case MSG_CANCEL -> handleCancelMessage(); + case MSG_CHANGE_LISTENER -> handleChangeListener((RecognitionListener) msg.obj); + case MSG_SET_TEMPORARY_ON_DEVICE_COMPONENT -> + handleSetTemporaryComponent((ComponentName) msg.obj); + case MSG_CHECK_RECOGNITION_SUPPORT -> { CheckRecognitionSupportArgs args = (CheckRecognitionSupportArgs) msg.obj; handleCheckRecognitionSupport( args.mIntent, args.mCallbackExecutor, args.mCallback); - break; - case MSG_TRIGGER_MODEL_DOWNLOAD: + } + case MSG_TRIGGER_MODEL_DOWNLOAD -> { ModelDownloadListenerArgs modelDownloadListenerArgs = (ModelDownloadListenerArgs) msg.obj; handleTriggerModelDownload( modelDownloadListenerArgs.mIntent, modelDownloadListenerArgs.mExecutor, modelDownloadListenerArgs.mModelDownloadListener); - break; + } + case MSG_DESTROY -> handleDestroy(); } } }; @@ -433,6 +426,10 @@ class SpeechRecognizerImpl extends SpeechRecognizer { @Override public void destroy() { + putMessage(mHandler.obtainMessage(MSG_DESTROY)); + } + + private void handleDestroy() { if (mService != null) { try { mService.cancel(mListener, /*isShutdown*/ true); diff --git a/core/java/android/util/DisplayMetrics.java b/core/java/android/util/DisplayMetrics.java index 9148c4a101d7..f14485b09424 100755 --- a/core/java/android/util/DisplayMetrics.java +++ b/core/java/android/util/DisplayMetrics.java @@ -16,6 +16,9 @@ package android.util; +import static com.android.window.flags.Flags.FLAG_DENSITY_390_API; + +import android.annotation.FlaggedApi; import android.annotation.IntDef; import android.annotation.Nullable; import android.compat.annotation.UnsupportedAppUsage; @@ -59,6 +62,7 @@ public class DisplayMetrics { DENSITY_XHIGH, DENSITY_340, DENSITY_360, + DENSITY_390, DENSITY_400, DENSITY_420, DENSITY_440, @@ -182,6 +186,15 @@ public class DisplayMetrics { * This is not a density that applications should target, instead relying * on the system to scale their {@link #DENSITY_XXHIGH} assets for them. */ + @FlaggedApi(FLAG_DENSITY_390_API) + public static final int DENSITY_390 = 390; + + /** + * Intermediate density for screens that sit somewhere between + * {@link #DENSITY_XHIGH} (320 dpi) and {@link #DENSITY_XXHIGH} (480 dpi). + * This is not a density that applications should target, instead relying + * on the system to scale their {@link #DENSITY_XXHIGH} assets for them. + */ public static final int DENSITY_400 = 400; /** diff --git a/core/java/android/view/accessibility/AccessibilityManager.java b/core/java/android/view/accessibility/AccessibilityManager.java index 07ae1345a688..3dbe65ef4180 100644 --- a/core/java/android/view/accessibility/AccessibilityManager.java +++ b/core/java/android/view/accessibility/AccessibilityManager.java @@ -1823,13 +1823,13 @@ public final class AccessibilityManager { /** * - * Sets an {@link IWindowMagnificationConnection} that manipulates window magnification. + * Sets an {@link IMagnificationConnection} that manipulates magnification in SystemUI. * - * @param connection The connection that manipulates window magnification. + * @param connection The connection that manipulates magnification in SystemUI. * @hide */ - public void setWindowMagnificationConnection(@Nullable - IWindowMagnificationConnection connection) { + public void setMagnificationConnection(@Nullable + IMagnificationConnection connection) { final IAccessibilityManager service; synchronized (mLock) { service = getServiceLocked(); @@ -1838,9 +1838,9 @@ public final class AccessibilityManager { } } try { - service.setWindowMagnificationConnection(connection); + service.setMagnificationConnection(connection); } catch (RemoteException re) { - Log.e(LOG_TAG, "Error setting window magnfication connection", re); + Log.e(LOG_TAG, "Error setting magnification connection", re); } } diff --git a/core/java/android/view/accessibility/IAccessibilityManager.aidl b/core/java/android/view/accessibility/IAccessibilityManager.aidl index 7a1112f1446f..f741080c57a8 100644 --- a/core/java/android/view/accessibility/IAccessibilityManager.aidl +++ b/core/java/android/view/accessibility/IAccessibilityManager.aidl @@ -27,7 +27,7 @@ import android.view.accessibility.AccessibilityNodeInfo; import android.view.accessibility.IAccessibilityInteractionConnection; import android.view.accessibility.IAccessibilityManagerClient; import android.view.accessibility.AccessibilityWindowAttributes; -import android.view.accessibility.IWindowMagnificationConnection; +import android.view.accessibility.IMagnificationConnection; import android.view.InputEvent; import android.view.IWindow; import android.view.MagnificationSpec; @@ -90,7 +90,7 @@ interface IAccessibilityManager { oneway void registerSystemAction(in RemoteAction action, int actionId); oneway void unregisterSystemAction(int actionId); - oneway void setWindowMagnificationConnection(in IWindowMagnificationConnection connection); + oneway void setMagnificationConnection(in IMagnificationConnection connection); void associateEmbeddedHierarchy(IBinder host, IBinder embedded); diff --git a/core/java/android/view/accessibility/IWindowMagnificationConnection.aidl b/core/java/android/view/accessibility/IMagnificationConnection.aidl index a404bd6f8c97..a5e8aaf97de4 100644 --- a/core/java/android/view/accessibility/IWindowMagnificationConnection.aidl +++ b/core/java/android/view/accessibility/IMagnificationConnection.aidl @@ -23,11 +23,11 @@ import android.view.accessibility.IRemoteMagnificationAnimationCallback; /** * Interface for interaction between {@link AccessibilityManagerService} - * and {@link WindowMagnification} in SystemUI. + * and {@link Magnification} in SystemUI. * * @hide */ -oneway interface IWindowMagnificationConnection { +oneway interface IMagnificationConnection { /** * Enables window magnification on specified display with given center and scale and animation. diff --git a/core/java/com/android/internal/app/IAppOpsService.aidl b/core/java/com/android/internal/app/IAppOpsService.aidl index 492e2ac7cc28..3a321e5c26f7 100644 --- a/core/java/com/android/internal/app/IAppOpsService.aidl +++ b/core/java/com/android/internal/app/IAppOpsService.aidl @@ -140,14 +140,26 @@ interface IAppOpsService { void collectNoteOpCallsForValidation(String stackTrace, int op, String packageName, long version); SyncNotedAppOp noteProxyOperationWithState(int code, - in AttributionSourceState attributionSourceStateState, - boolean shouldCollectAsyncNotedOp, String message, boolean shouldCollectMessage, - boolean skipProxyOperation); + in AttributionSourceState attributionSourceStateState, + boolean shouldCollectAsyncNotedOp, String message, boolean shouldCollectMessage, + boolean skipProxyOperation); SyncNotedAppOp startProxyOperationWithState(IBinder clientId, int code, - in AttributionSourceState attributionSourceStateState, boolean startIfModeDefault, - boolean shouldCollectAsyncNotedOp, String message, boolean shouldCollectMessage, - boolean skipProxyOperation, int proxyAttributionFlags, int proxiedAttributionFlags, - int attributionChainId); + in AttributionSourceState attributionSourceStateState, boolean startIfModeDefault, + boolean shouldCollectAsyncNotedOp, String message, boolean shouldCollectMessage, + boolean skipProxyOperation, int proxyAttributionFlags, int proxiedAttributionFlags, + int attributionChainId); void finishProxyOperationWithState(IBinder clientId, int code, - in AttributionSourceState attributionSourceStateState, boolean skipProxyOperation); + in AttributionSourceState attributionSourceStateState, boolean skipProxyOperation); + int checkOperationRawForDevice(int code, int uid, String packageName, + @nullable String attributionTag, int virtualDeviceId); + int checkOperationForDevice(int code, int uid, String packageName, int virtualDeviceId); + SyncNotedAppOp noteOperationForDevice(int code, int uid, String packageName, + @nullable String attributionTag, int virtualDeviceId, + boolean shouldCollectAsyncNotedOp, String message, boolean shouldCollectMessage); + SyncNotedAppOp startOperationForDevice(IBinder clientId, int code, int uid, String packageName, + @nullable String attributionTag, int virtualDeviceId, boolean startIfModeDefault, + boolean shouldCollectAsyncNotedOp, String message, boolean shouldCollectMessage, + int attributionFlags, int attributionChainId); + void finishOperationForDevice(IBinder clientId, int code, int uid, String packageName, + @nullable String attributionTag, int virtualDeviceId); } diff --git a/core/java/com/android/internal/statusbar/IStatusBar.aidl b/core/java/com/android/internal/statusbar/IStatusBar.aidl index 4d8eeac6d5ab..5351c6dbf94f 100644 --- a/core/java/com/android/internal/statusbar/IStatusBar.aidl +++ b/core/java/com/android/internal/statusbar/IStatusBar.aidl @@ -285,12 +285,12 @@ oneway interface IStatusBar void suppressAmbientDisplay(boolean suppress); /** - * Requests {@link WindowMagnification} to set window magnification connection through - * {@link AccessibilityManager#setWindowMagnificationConnection(IWindowMagnificationConnection)} + * Requests {@link Magnification} to set magnification connection to SystemUI through + * {@link AccessibilityManager#setMagnificationConnection(IMagnificationConnection)} * * @param connect {@code true} if needs connection, otherwise set the connection to null. */ - void requestWindowMagnificationConnection(boolean connect); + void requestMagnificationConnection(boolean connect); /** * Allow for pass-through arguments from `adb shell cmd statusbar <args>`, and write to the diff --git a/core/tests/coretests/src/android/view/accessibility/AccessibilityManagerTest.java b/core/tests/coretests/src/android/view/accessibility/AccessibilityManagerTest.java index 84252f97d8e5..e2f255407a37 100644 --- a/core/tests/coretests/src/android/view/accessibility/AccessibilityManagerTest.java +++ b/core/tests/coretests/src/android/view/accessibility/AccessibilityManagerTest.java @@ -208,14 +208,14 @@ public class AccessibilityManagerTest { } @Test - public void testSetWindowMagnificationConnection() throws Exception { + public void testSetMagnificationConnection() throws Exception { AccessibilityManager manager = createManager(WITH_A11Y_ENABLED); - IWindowMagnificationConnection connection = Mockito.mock( - IWindowMagnificationConnection.class); + IMagnificationConnection connection = Mockito.mock( + IMagnificationConnection.class); - manager.setWindowMagnificationConnection(connection); + manager.setMagnificationConnection(connection); - verify(mMockService).setWindowMagnificationConnection(connection); + verify(mMockService).setMagnificationConnection(connection); } @Test diff --git a/graphics/java/android/graphics/ImageDecoder.java b/graphics/java/android/graphics/ImageDecoder.java index b2da233fc21e..639517996724 100644 --- a/graphics/java/android/graphics/ImageDecoder.java +++ b/graphics/java/android/graphics/ImageDecoder.java @@ -681,12 +681,6 @@ public final class ImageDecoder implements AutoCloseable { } }; - /** @removed - * @deprecated Subsumed by {@link #DecodeException}. - */ - @Deprecated - public static class IncompleteException extends IOException {}; - /** * Interface for changing the default settings of a decode. * @@ -713,24 +707,6 @@ public final class ImageDecoder implements AutoCloseable { }; - /** @removed - * @deprecated Replaced by {@link #DecodeException#SOURCE_EXCEPTION}. - */ - @Deprecated - public static final int ERROR_SOURCE_EXCEPTION = 1; - - /** @removed - * @deprecated Replaced by {@link #DecodeException#SOURCE_INCOMPLETE}. - */ - @Deprecated - public static final int ERROR_SOURCE_INCOMPLETE = 2; - - /** @removed - * @deprecated Replaced by {@link #DecodeException#SOURCE_MALFORMED_DATA}. - */ - @Deprecated - public static final int ERROR_SOURCE_ERROR = 3; - /** * Information about an interrupted decode. */ @@ -1178,14 +1154,6 @@ public final class ImageDecoder implements AutoCloseable { } // Modifiers - /** @removed - * @deprecated Renamed to {@link #setTargetSize}. - */ - @Deprecated - public ImageDecoder setResize(int width, int height) { - this.setTargetSize(width, height); - return this; - } /** * Specify the size of the output {@link Drawable} or {@link Bitmap}. @@ -1217,15 +1185,6 @@ public final class ImageDecoder implements AutoCloseable { mDesiredHeight = height; } - /** @removed - * @deprecated Renamed to {@link #setTargetSampleSize}. - */ - @Deprecated - public ImageDecoder setResize(int sampleSize) { - this.setTargetSampleSize(sampleSize); - return this; - } - private int getTargetDimension(int original, int sampleSize, int computed) { // Sampling will never result in a smaller size than 1. if (sampleSize >= original) { @@ -1381,15 +1340,6 @@ public final class ImageDecoder implements AutoCloseable { mUnpremultipliedRequired = unpremultipliedRequired; } - /** @removed - * @deprecated Renamed to {@link #setUnpremultipliedRequired}. - */ - @Deprecated - public ImageDecoder setRequireUnpremultiplied(boolean unpremultipliedRequired) { - this.setUnpremultipliedRequired(unpremultipliedRequired); - return this; - } - /** * Return whether the {@link Bitmap} will have unpremultiplied pixels. */ @@ -1397,14 +1347,6 @@ public final class ImageDecoder implements AutoCloseable { return mUnpremultipliedRequired; } - /** @removed - * @deprecated Renamed to {@link #isUnpremultipliedRequired}. - */ - @Deprecated - public boolean getRequireUnpremultiplied() { - return this.isUnpremultipliedRequired(); - } - /** * Modify the image after decoding and scaling. * @@ -1528,15 +1470,6 @@ public final class ImageDecoder implements AutoCloseable { mMutable = mutable; } - /** @removed - * @deprecated Renamed to {@link #setMutableRequired}. - */ - @Deprecated - public ImageDecoder setMutable(boolean mutable) { - this.setMutableRequired(mutable); - return this; - } - /** * Return whether the decoded {@link Bitmap} will be mutable. */ @@ -1544,14 +1477,6 @@ public final class ImageDecoder implements AutoCloseable { return mMutable; } - /** @removed - * @deprecated Renamed to {@link #isMutableRequired}. - */ - @Deprecated - public boolean getMutable() { - return this.isMutableRequired(); - } - /** * Save memory if possible by using a denser {@link Bitmap.Config} at the * cost of some image quality. @@ -1597,22 +1522,6 @@ public final class ImageDecoder implements AutoCloseable { return mConserveMemory ? MEMORY_POLICY_LOW_RAM : MEMORY_POLICY_DEFAULT; } - /** @removed - * @deprecated Replaced by {@link #setMemorySizePolicy}. - */ - @Deprecated - public void setConserveMemory(boolean conserveMemory) { - mConserveMemory = conserveMemory; - } - - /** @removed - * @deprecated Replaced by {@link #getMemorySizePolicy}. - */ - @Deprecated - public boolean getConserveMemory() { - return mConserveMemory; - } - /** * Specify whether to potentially treat the output as an alpha mask. * @@ -1632,24 +1541,6 @@ public final class ImageDecoder implements AutoCloseable { mDecodeAsAlphaMask = enabled; } - /** @removed - * @deprecated Renamed to {@link #setDecodeAsAlphaMaskEnabled}. - */ - @Deprecated - public ImageDecoder setDecodeAsAlphaMask(boolean enabled) { - this.setDecodeAsAlphaMaskEnabled(enabled); - return this; - } - - /** @removed - * @deprecated Renamed to {@link #setDecodeAsAlphaMaskEnabled}. - */ - @Deprecated - public ImageDecoder setAsAlphaMask(boolean asAlphaMask) { - this.setDecodeAsAlphaMask(asAlphaMask); - return this; - } - /** * Return whether to treat single channel input as alpha. * @@ -1662,22 +1553,6 @@ public final class ImageDecoder implements AutoCloseable { return mDecodeAsAlphaMask; } - /** @removed - * @deprecated Renamed to {@link #isDecodeAsAlphaMaskEnabled}. - */ - @Deprecated - public boolean getDecodeAsAlphaMask() { - return mDecodeAsAlphaMask; - } - - /** @removed - * @deprecated Renamed to {@link #isDecodeAsAlphaMaskEnabled}. - */ - @Deprecated - public boolean getAsAlphaMask() { - return this.getDecodeAsAlphaMask(); - } - /** * Specify the desired {@link ColorSpace} for the output. * diff --git a/media/java/android/media/AudioHalVersionInfo.java b/media/java/android/media/AudioHalVersionInfo.java index c881a03c6732..0f48abeb6882 100644 --- a/media/java/android/media/AudioHalVersionInfo.java +++ b/media/java/android/media/AudioHalVersionInfo.java @@ -80,8 +80,9 @@ public final class AudioHalVersionInfo implements Parcelable, Comparable<AudioHa * List of all valid Audio HAL versions. This list need to be in sync with sAudioHALVersions * defined in frameworks/av/media/libaudiohal/FactoryHalHidl.cpp. */ + // TODO: add AIDL_1_0 with sAudioHALVersions. public static final @NonNull List<AudioHalVersionInfo> VERSIONS = - List.of(AIDL_1_0, HIDL_7_1, HIDL_7_0, HIDL_6_0, HIDL_5_0, HIDL_4_0); + List.of(HIDL_7_1, HIDL_7_0, HIDL_6_0, HIDL_5_0, HIDL_4_0); private static final String TAG = "AudioHalVersionInfo"; private AudioHalVersion mHalVersion = new AudioHalVersion(); diff --git a/media/java/android/media/AudioManager.java b/media/java/android/media/AudioManager.java index 1e3234902a45..627f73c750fe 100644 --- a/media/java/android/media/AudioManager.java +++ b/media/java/android/media/AudioManager.java @@ -20,6 +20,7 @@ import static android.companion.virtual.VirtualDeviceParams.DEVICE_POLICY_DEFAUL import static android.companion.virtual.VirtualDeviceParams.POLICY_TYPE_AUDIO; import static android.content.Context.DEVICE_ID_DEFAULT; import static android.media.audio.Flags.autoPublicVolumeApiHardening; +import static android.media.audio.Flags.automaticBtDeviceType; import static android.media.audio.Flags.FLAG_FOCUS_FREEZE_TEST_API; import android.Manifest; @@ -7170,10 +7171,52 @@ public class AudioManager { * Sets the audio device type of a Bluetooth device given its MAC address */ @RequiresPermission(Manifest.permission.MODIFY_AUDIO_SETTINGS_PRIVILEGED) - public void setBluetoothAudioDeviceCategory(@NonNull String address, boolean isBle, + public void setBluetoothAudioDeviceCategory_legacy(@NonNull String address, boolean isBle, @AudioDeviceCategory int btAudioDeviceType) { + if (automaticBtDeviceType()) { + // do nothing + return; + } + try { + getService().setBluetoothAudioDeviceCategory_legacy(address, isBle, btAudioDeviceType); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** + * @hide + * Gets the audio device type of a Bluetooth device given its MAC address + */ + @RequiresPermission(Manifest.permission.MODIFY_AUDIO_SETTINGS_PRIVILEGED) + @AudioDeviceCategory + public int getBluetoothAudioDeviceCategory_legacy(@NonNull String address, boolean isBle) { + if (automaticBtDeviceType()) { + return AUDIO_DEVICE_CATEGORY_UNKNOWN; + } + try { + return getService().getBluetoothAudioDeviceCategory_legacy(address, isBle); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** + * @hide + * Sets the audio device type of a Bluetooth device given its MAC address + * + * @return {@code true} if the device type was set successfully. If the + * audio device type was automatically identified this method will + * return {@code false}. + */ + @RequiresPermission(Manifest.permission.MODIFY_AUDIO_SETTINGS_PRIVILEGED) + public boolean setBluetoothAudioDeviceCategory(@NonNull String address, + @AudioDeviceCategory int btAudioDeviceCategory) { + if (!automaticBtDeviceType()) { + return false; + } try { - getService().setBluetoothAudioDeviceCategory(address, isBle, btAudioDeviceType); + return getService().setBluetoothAudioDeviceCategory(address, btAudioDeviceCategory); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } @@ -7185,9 +7228,29 @@ public class AudioManager { */ @RequiresPermission(Manifest.permission.MODIFY_AUDIO_SETTINGS_PRIVILEGED) @AudioDeviceCategory - public int getBluetoothAudioDeviceCategory(@NonNull String address, boolean isBle) { + public int getBluetoothAudioDeviceCategory(@NonNull String address) { + if (!automaticBtDeviceType()) { + return AUDIO_DEVICE_CATEGORY_UNKNOWN; + } + try { + return getService().getBluetoothAudioDeviceCategory(address); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** + * @hide + * Returns {@code true} if the audio device type of a Bluetooth device can + * be automatically identified + */ + @RequiresPermission(Manifest.permission.MODIFY_AUDIO_SETTINGS_PRIVILEGED) + public boolean isBluetoothAudioDeviceCategoryFixed(@NonNull String address) { + if (!automaticBtDeviceType()) { + return false; + } try { - return getService().getBluetoothAudioDeviceCategory(address, isBle); + return getService().isBluetoothAudioDeviceCategoryFixed(address); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } diff --git a/media/java/android/media/IAudioService.aidl b/media/java/android/media/IAudioService.aidl index 5c8758a6aa73..a52f0b08330d 100644 --- a/media/java/android/media/IAudioService.aidl +++ b/media/java/android/media/IAudioService.aidl @@ -338,10 +338,20 @@ interface IAudioService { oneway void setCsdAsAFeatureEnabled(boolean csdToggleValue); @EnforcePermission("MODIFY_AUDIO_SETTINGS_PRIVILEGED") - oneway void setBluetoothAudioDeviceCategory(in String address, boolean isBle, int deviceType); + oneway void setBluetoothAudioDeviceCategory_legacy(in String address, boolean isBle, + int deviceCategory); @EnforcePermission("MODIFY_AUDIO_SETTINGS_PRIVILEGED") - int getBluetoothAudioDeviceCategory(in String address, boolean isBle); + int getBluetoothAudioDeviceCategory_legacy(in String address, boolean isBle); + + @EnforcePermission("MODIFY_AUDIO_SETTINGS_PRIVILEGED") + boolean setBluetoothAudioDeviceCategory(in String address, int deviceCategory); + + @EnforcePermission("MODIFY_AUDIO_SETTINGS_PRIVILEGED") + int getBluetoothAudioDeviceCategory(in String address); + + @EnforcePermission("MODIFY_AUDIO_SETTINGS_PRIVILEGED") + boolean isBluetoothAudioDeviceCategoryFixed(in String address); int setHdmiSystemAudioSupported(boolean on); diff --git a/packages/SettingsLib/Spa/gradle/libs.versions.toml b/packages/SettingsLib/Spa/gradle/libs.versions.toml index b40e911bd8de..9703c347859c 100644 --- a/packages/SettingsLib/Spa/gradle/libs.versions.toml +++ b/packages/SettingsLib/Spa/gradle/libs.versions.toml @@ -15,7 +15,7 @@ # [versions] -agp = "8.1.4" +agp = "8.2.0" compose-compiler = "1.5.1" dexmaker-mockito = "2.28.3" jvm = "17" diff --git a/packages/SettingsLib/Spa/gradle/wrapper/gradle-wrapper.jar b/packages/SettingsLib/Spa/gradle/wrapper/gradle-wrapper.jar Binary files differindex 033e24c4cdf4..d64cd4917707 100644 --- a/packages/SettingsLib/Spa/gradle/wrapper/gradle-wrapper.jar +++ b/packages/SettingsLib/Spa/gradle/wrapper/gradle-wrapper.jar diff --git a/packages/SettingsLib/Spa/gradle/wrapper/gradle-wrapper.properties b/packages/SettingsLib/Spa/gradle/wrapper/gradle-wrapper.properties index ce89de6ffd65..516749de880a 100644 --- a/packages/SettingsLib/Spa/gradle/wrapper/gradle-wrapper.properties +++ b/packages/SettingsLib/Spa/gradle/wrapper/gradle-wrapper.properties @@ -16,7 +16,7 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-8.4-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.5-bin.zip networkTimeout=10000 validateDistributionUrl=true zipStoreBase=GRADLE_USER_HOME diff --git a/packages/SettingsLib/Spa/gradlew b/packages/SettingsLib/Spa/gradlew index fcb6fca147c0..1aa94a426907 100755 --- a/packages/SettingsLib/Spa/gradlew +++ b/packages/SettingsLib/Spa/gradlew @@ -83,7 +83,8 @@ done # This is normally unused # shellcheck disable=SC2034 APP_BASE_NAME=${0##*/} -APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit +# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036) +APP_HOME=$( cd "${APP_HOME:-./}" > /dev/null && pwd -P ) || exit # Use the maximum available, or set MAX_FD != -1 to use that value. MAX_FD=maximum @@ -144,7 +145,7 @@ if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then case $MAX_FD in #( max*) # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked. - # shellcheck disable=SC3045 + # shellcheck disable=SC2039,SC3045 MAX_FD=$( ulimit -H -n ) || warn "Could not query maximum file descriptor limit" esac @@ -152,7 +153,7 @@ if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then '' | soft) :;; #( *) # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked. - # shellcheck disable=SC3045 + # shellcheck disable=SC2039,SC3045 ulimit -n "$MAX_FD" || warn "Could not set maximum file descriptor limit to $MAX_FD" esac @@ -201,11 +202,11 @@ fi # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' -# Collect all arguments for the java command; -# * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of -# shell script including quotes and variable substitutions, so put them in -# double quotes to make sure that they get re-expanded; and -# * put everything else in single quotes, so that it's not re-expanded. +# Collect all arguments for the java command: +# * DEFAULT_JVM_OPTS, JAVA_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments, +# and any embedded shellness will be escaped. +# * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be +# treated as '${Hostname}' itself on the command line. set -- \ "-Dorg.gradle.appname=$APP_BASE_NAME" \ diff --git a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/app/AppRepository.kt b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/app/AppRepository.kt index de2cf1f5fdf6..81a8b324f70f 100644 --- a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/app/AppRepository.kt +++ b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/app/AppRepository.kt @@ -19,11 +19,11 @@ package com.android.settingslib.spaprivileged.model.app import android.content.Context import android.content.pm.ApplicationInfo import android.graphics.drawable.Drawable +import android.util.IconDrawableFactory import androidx.compose.runtime.Composable import androidx.compose.runtime.State import androidx.compose.runtime.produceState import androidx.compose.ui.platform.LocalContext -import com.android.settingslib.Utils import com.android.settingslib.spa.framework.compose.rememberContext import com.android.settingslib.spaprivileged.R import com.android.settingslib.spaprivileged.framework.common.userManager @@ -65,6 +65,7 @@ interface AppRepository { internal class AppRepositoryImpl(private val context: Context) : AppRepository { private val packageManager = context.packageManager + private val iconDrawableFactory = IconDrawableFactory.newInstance(context) override fun loadLabel(app: ApplicationInfo): String = app.loadLabel(packageManager).toString() @@ -72,7 +73,7 @@ internal class AppRepositoryImpl(private val context: Context) : AppRepository { override fun produceIcon(app: ApplicationInfo) = produceState<Drawable?>(initialValue = null, app) { withContext(Dispatchers.IO) { - value = Utils.getBadgedIcon(context, app) + value = iconDrawableFactory.getBadgedIcon(app) } } diff --git a/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/model/app/AppRepositoryTest.kt b/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/model/app/AppRepositoryTest.kt index 8f458d33c126..70e405557dc7 100644 --- a/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/model/app/AppRepositoryTest.kt +++ b/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/model/app/AppRepositoryTest.kt @@ -27,14 +27,12 @@ import com.android.settingslib.spa.framework.compose.stateOf import com.android.settingslib.spa.testutils.delay import com.android.settingslib.spaprivileged.framework.common.userManager import com.google.common.truth.Truth.assertThat -import org.junit.Before import org.junit.Rule import org.junit.Test import org.junit.runner.RunWith -import org.mockito.Mock -import org.mockito.Spy -import org.mockito.junit.MockitoJUnit -import org.mockito.junit.MockitoRule +import org.mockito.kotlin.doReturn +import org.mockito.kotlin.mock +import org.mockito.kotlin.spy import org.mockito.kotlin.whenever @RunWith(AndroidJUnit4::class) @@ -42,23 +40,14 @@ class AppRepositoryTest { @get:Rule val composeTestRule = createComposeRule() - @get:Rule - val mockito: MockitoRule = MockitoJUnit.rule() - - @Spy - private val context: Context = ApplicationProvider.getApplicationContext() - - @Mock - private lateinit var userManager: UserManager + private val userManager = mock<UserManager>() - private lateinit var appRepository: AppRepositoryImpl - - @Before - fun setUp() { - whenever(context.userManager).thenReturn(userManager) - appRepository = AppRepositoryImpl(context) + private val context: Context = spy(ApplicationProvider.getApplicationContext()) { + on { userManager } doReturn userManager } + private val appRepository = AppRepositoryImpl(context) + @Test fun produceIconContentDescription_workProfile() { whenever(userManager.isManagedProfile(APP.userId)).thenReturn(true) diff --git a/packages/SettingsLib/src/com/android/settingslib/applications/ApplicationsState.java b/packages/SettingsLib/src/com/android/settingslib/applications/ApplicationsState.java index 079cde08f9bb..8e1067f28ffb 100644 --- a/packages/SettingsLib/src/com/android/settingslib/applications/ApplicationsState.java +++ b/packages/SettingsLib/src/com/android/settingslib/applications/ApplicationsState.java @@ -81,6 +81,8 @@ import java.util.Objects; import java.util.UUID; import java.util.regex.Pattern; +import javax.annotation.Nullable; + /** * Keeps track of information about all installed applications, lazy-loading * as needed. @@ -492,7 +494,8 @@ public class ApplicationsState { ApplicationInfo info = getAppInfoLocked(packageName, userId); if (info == null) { try { - info = mIpm.getApplicationInfo(packageName, 0, userId); + info = mIpm.getApplicationInfo(packageName, + PackageManager.MATCH_ARCHIVED_PACKAGES, userId); } catch (RemoteException e) { Log.w(TAG, "getEntry couldn't reach PackageManager", e); return null; @@ -1612,7 +1615,7 @@ public class ApplicationsState { } public static class AppEntry extends SizeInfo { - public final File apkFile; + @Nullable public final File apkFile; public final long id; public String label; public long size; @@ -1671,7 +1674,7 @@ public class ApplicationsState { @VisibleForTesting(otherwise = VisibleForTesting.PRIVATE) public AppEntry(Context context, ApplicationInfo info, long id) { - apkFile = new File(info.sourceDir); + this.apkFile = info.sourceDir != null ? new File(info.sourceDir) : null; this.id = id; this.info = info; this.size = SIZE_UNKNOWN; @@ -1717,13 +1720,13 @@ public class ApplicationsState { public void ensureLabel(Context context) { if (this.label == null || !this.mounted) { - if (!this.apkFile.exists()) { - this.mounted = false; - this.label = info.packageName; - } else { + if (this.apkFile != null && this.apkFile.exists()) { this.mounted = true; CharSequence label = info.loadLabel(context.getPackageManager()); this.label = label != null ? label.toString() : info.packageName; + } else { + this.mounted = false; + this.label = info.packageName; } } } @@ -1738,7 +1741,7 @@ public class ApplicationsState { } if (this.icon == null) { - if (this.apkFile.exists()) { + if (this.apkFile != null && this.apkFile.exists()) { this.icon = Utils.getBadgedIcon(context, info); return true; } else { @@ -1748,7 +1751,7 @@ public class ApplicationsState { } else if (!this.mounted) { // If the app wasn't mounted but is now mounted, reload // its icon. - if (this.apkFile.exists()) { + if (this.apkFile != null && this.apkFile.exists()) { this.mounted = true; this.icon = Utils.getBadgedIcon(context, info); return true; diff --git a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/ClockRegistry.kt b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/ClockRegistry.kt index ca24d94070f9..cdd074d872c0 100644 --- a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/ClockRegistry.kt +++ b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/ClockRegistry.kt @@ -173,8 +173,10 @@ open class ClockRegistry( { "Skipping initial load of known clock package package: $str1" } ) + var isCurrentClock = false var isClockListChanged = false for (metadata in knownClocks) { + isCurrentClock = isCurrentClock || currentClockId == metadata.clockId val id = metadata.clockId val info = availableClocks.concurrentGetOrPut(id, ClockInfo(metadata, null, manager)) { @@ -207,8 +209,9 @@ open class ClockRegistry( } verifyLoadedProviders() - // Load executed via verifyLoadedProviders - return false + // Load immediately if it's the current clock, otherwise let verifyLoadedProviders + // load and unload clocks as necessary on the background thread. + return isCurrentClock } override fun onPluginLoaded( diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml index 7db21b2be04d..b9478018d7d3 100644 --- a/packages/SystemUI/res/values/dimens.xml +++ b/packages/SystemUI/res/values/dimens.xml @@ -125,6 +125,25 @@ <dimen name="navigation_edge_cancelled_arrow_height">0dp</dimen> <dimen name="navigation_edge_cancelled_edge_corners">6dp</dimen> + <!-- + NOTICE: STATUS BAR INTERNALS. DO NOT READ THESE OUTSIDE OF STATUS BAR. + + Below are the bottom margin values for each rotation [1]. + Only used when the value is >= 0. + A value of 0 means that the content has 0 bottom margin, and will be at the bottom of the + status bar. + When the value is < 0, the value is ignored, and content will be centered vertically. + + [1] Rotation defined as in android.view.Surface.Rotation. + Rotation 0 means natural orientation. If a device is naturally portrait (e.g. a phone), + rotation 0 is portrait. If a device is naturally landscape (e.g a tablet), rotation 0 is + landscape. + --> + <dimen name="status_bar_bottom_aligned_margin_rotation_0">-1px</dimen> + <dimen name="status_bar_bottom_aligned_margin_rotation_90">-1px</dimen> + <dimen name="status_bar_bottom_aligned_margin_rotation_180">-1px</dimen> + <dimen name="status_bar_bottom_aligned_margin_rotation_270">-1px</dimen> + <!-- Height of the system icons container view in the status bar --> <dimen name="status_bar_system_icons_height">@dimen/status_bar_icon_size_sp</dimen> diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/Magnification.java b/packages/SystemUI/src/com/android/systemui/accessibility/Magnification.java index 1edb551eb944..3cb6314639e8 100644 --- a/packages/SystemUI/src/com/android/systemui/accessibility/Magnification.java +++ b/packages/SystemUI/src/com/android/systemui/accessibility/Magnification.java @@ -34,8 +34,8 @@ import android.view.Display; import android.view.SurfaceControl; import android.view.WindowManagerGlobal; import android.view.accessibility.AccessibilityManager; +import android.view.accessibility.IMagnificationConnection; import android.view.accessibility.IRemoteMagnificationAnimationCallback; -import android.view.accessibility.IWindowMagnificationConnection; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.graphics.SfVsyncFrameCallbackProvider; @@ -55,7 +55,7 @@ import javax.inject.Inject; /** * Class to handle the interaction with * {@link com.android.server.accessibility.AccessibilityManagerService}. It invokes - * {@link AccessibilityManager#setWindowMagnificationConnection(IWindowMagnificationConnection)} + * {@link AccessibilityManager#setMagnificationConnection(IMagnificationConnection)} * when {@code IStatusBar#requestWindowMagnificationConnection(boolean)} is called. */ @SysUISingleton @@ -484,11 +484,11 @@ public class Magnification implements CoreStartable, CommandQueue.Callbacks { } @Override - public void requestWindowMagnificationConnection(boolean connect) { + public void requestMagnificationConnection(boolean connect) { if (connect) { - setWindowMagnificationConnection(); + setMagnificationConnection(); } else { - clearWindowMagnificationConnection(); + clearMagnificationConnection(); } } @@ -499,17 +499,17 @@ public class Magnification implements CoreStartable, CommandQueue.Callbacks { magnificationController -> magnificationController.dump(pw)); } - private void setWindowMagnificationConnection() { + private void setMagnificationConnection() { if (mMagnificationConnectionImpl == null) { mMagnificationConnectionImpl = new MagnificationConnectionImpl(this, mHandler); } - mAccessibilityManager.setWindowMagnificationConnection( + mAccessibilityManager.setMagnificationConnection( mMagnificationConnectionImpl); } - private void clearWindowMagnificationConnection() { - mAccessibilityManager.setWindowMagnificationConnection(null); + private void clearMagnificationConnection() { + mAccessibilityManager.setMagnificationConnection(null); //TODO: destroy controllers. } } diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/MagnificationConnectionImpl.java b/packages/SystemUI/src/com/android/systemui/accessibility/MagnificationConnectionImpl.java index 5f0d496dd5d1..4944531989d3 100644 --- a/packages/SystemUI/src/com/android/systemui/accessibility/MagnificationConnectionImpl.java +++ b/packages/SystemUI/src/com/android/systemui/accessibility/MagnificationConnectionImpl.java @@ -21,8 +21,8 @@ import android.graphics.Rect; import android.os.Handler; import android.os.RemoteException; import android.util.Log; +import android.view.accessibility.IMagnificationConnection; import android.view.accessibility.IRemoteMagnificationAnimationCallback; -import android.view.accessibility.IWindowMagnificationConnection; import android.view.accessibility.IWindowMagnificationConnectionCallback; import com.android.systemui.dagger.qualifiers.Main; @@ -30,9 +30,9 @@ import com.android.systemui.dagger.qualifiers.Main; /** * Implementation of window magnification connection. * - * @see IWindowMagnificationConnection + * @see IMagnificationConnection */ -class MagnificationConnectionImpl extends IWindowMagnificationConnection.Stub { +class MagnificationConnectionImpl extends IMagnificationConnection.Stub { private static final String TAG = "WindowMagnificationConnectionImpl"; diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/fontscaling/FontScalingDialog.kt b/packages/SystemUI/src/com/android/systemui/accessibility/fontscaling/FontScalingDialogDelegate.kt index 2d2f29564263..91bc0c144773 100644 --- a/packages/SystemUI/src/com/android/systemui/accessibility/fontscaling/FontScalingDialog.kt +++ b/packages/SystemUI/src/com/android/systemui/accessibility/fontscaling/FontScalingDialogDelegate.kt @@ -42,18 +42,21 @@ import com.android.systemui.util.settings.SecureSettings import com.android.systemui.util.settings.SystemSettings import com.android.systemui.util.time.SystemClock import java.util.concurrent.atomic.AtomicInteger +import javax.inject.Inject import kotlin.math.roundToInt /** The Dialog that contains a seekbar for changing the font size. */ -class FontScalingDialog( - context: Context, +class FontScalingDialogDelegate @Inject constructor( + private val context: Context, + private val systemUIDialogFactory: SystemUIDialog.Factory, + private val layoutInflater: LayoutInflater, private val systemSettings: SystemSettings, private val secureSettings: SecureSettings, private val systemClock: SystemClock, private val userTracker: UserTracker, @Main mainHandler: Handler, - @Background private val backgroundDelayableExecutor: DelayableExecutor -) : SystemUIDialog(context) { + @Background private val backgroundDelayableExecutor: DelayableExecutor, +) : SystemUIDialog.Delegate { private val MIN_UPDATE_INTERVAL_MS: Long = 800 private val CHANGE_BY_SEEKBAR_DELAY_MS: Long = 100 private val CHANGE_BY_BUTTON_DELAY_MS: Long = 300 @@ -75,19 +78,22 @@ class FontScalingDialog( } } - override fun onCreate(savedInstanceState: Bundle?) { - setTitle(R.string.font_scaling_dialog_title) - setView(LayoutInflater.from(context).inflate(R.layout.font_scaling_dialog, null)) - setPositiveButton( - R.string.quick_settings_done, - /* onClick = */ null, - /* dismissOnClick = */ true + override fun createDialog(): SystemUIDialog = systemUIDialogFactory.create(this) + + override fun beforeCreate(dialog: SystemUIDialog, savedInstanceState: Bundle?) { + dialog.setTitle(R.string.font_scaling_dialog_title) + dialog.setView(layoutInflater.inflate(R.layout.font_scaling_dialog, null)) + dialog.setPositiveButton( + R.string.quick_settings_done, + /* onClick = */ null, + /* dismissOnClick = */ true ) - super.onCreate(savedInstanceState) + } - title = requireViewById(com.android.internal.R.id.alertTitle) - doneButton = requireViewById(com.android.internal.R.id.button1) - seekBarWithIconButtonsView = requireViewById(R.id.font_scaling_slider) + override fun onCreate(dialog: SystemUIDialog, savedInstanceState: Bundle?) { + title = dialog.requireViewById(com.android.internal.R.id.alertTitle) + doneButton = dialog.requireViewById(com.android.internal.R.id.button1) + seekBarWithIconButtonsView = dialog.requireViewById(R.id.font_scaling_slider) val labelArray = arrayOfNulls<String>(strEntryValues.size) for (i in strEntryValues.indices) { @@ -135,7 +141,7 @@ class FontScalingDialog( } } ) - doneButton.setOnClickListener { dismiss() } + doneButton.setOnClickListener { dialog.dismiss() } systemSettings.registerContentObserver(Settings.System.FONT_SCALE, fontSizeObserver) } @@ -156,7 +162,7 @@ class FontScalingDialog( backgroundDelayableExecutor.executeDelayed({ updateFontScale() }, delayMs) } - override fun stop() { + override fun onStop(dialog: SystemUIDialog) { cancelUpdateFontScaleRunnable?.run() cancelUpdateFontScaleRunnable = null systemSettings.unregisterContentObserver(fontSizeObserver) @@ -189,9 +195,7 @@ class FontScalingDialog( return strEntryValues.size - 1 } - override fun onConfigurationChanged(configuration: Configuration) { - super.onConfigurationChanged(configuration) - + override fun onConfigurationChanged(dialog: SystemUIDialog, configuration: Configuration) { val configDiff = configuration.diff(this.configuration) this.configuration.setTo(configuration) diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/FontScalingTile.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/FontScalingTile.kt index 64e3f16abec3..14d365839417 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/FontScalingTile.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/FontScalingTile.kt @@ -23,7 +23,7 @@ import android.view.View import com.android.internal.jank.InteractionJankMonitor import com.android.internal.logging.MetricsLogger import com.android.systemui.res.R -import com.android.systemui.accessibility.fontscaling.FontScalingDialog +import com.android.systemui.accessibility.fontscaling.FontScalingDialogDelegate import com.android.systemui.animation.DialogCuj import com.android.systemui.animation.DialogLaunchAnimator import com.android.systemui.dagger.qualifiers.Background @@ -36,14 +36,10 @@ import com.android.systemui.qs.QSHost import com.android.systemui.qs.QsEventLogger import com.android.systemui.qs.logging.QSLogger import com.android.systemui.qs.tileimpl.QSTileImpl -import com.android.systemui.settings.UserTracker import com.android.systemui.statusbar.phone.SystemUIDialog import com.android.systemui.statusbar.policy.KeyguardStateController -import com.android.systemui.util.concurrency.DelayableExecutor -import com.android.systemui.util.settings.SecureSettings -import com.android.systemui.util.settings.SystemSettings -import com.android.systemui.util.time.SystemClock import javax.inject.Inject +import javax.inject.Provider class FontScalingTile @Inject @@ -59,11 +55,7 @@ constructor( qsLogger: QSLogger, private val keyguardStateController: KeyguardStateController, private val dialogLaunchAnimator: DialogLaunchAnimator, - private val systemSettings: SystemSettings, - private val secureSettings: SecureSettings, - private val systemClock: SystemClock, - private val userTracker: UserTracker, - @Background private val backgroundDelayableExecutor: DelayableExecutor + private val fontScalingDialogDelegateProvider: Provider<FontScalingDialogDelegate> ) : QSTileImpl<QSTile.State?>( host, @@ -87,16 +79,7 @@ constructor( val animateFromView: Boolean = view != null && !keyguardStateController.isShowing val runnable = Runnable { - val dialog: SystemUIDialog = - FontScalingDialog( - mContext, - systemSettings, - secureSettings, - systemClock, - userTracker, - mainHandler, - backgroundDelayableExecutor - ) + val dialog: SystemUIDialog = fontScalingDialogDelegateProvider.get().createDialog() if (animateFromView) { dialogLaunchAnimator.showFromView( dialog, diff --git a/packages/SystemUI/src/com/android/systemui/shade/ShadeHeaderController.kt b/packages/SystemUI/src/com/android/systemui/shade/ShadeHeaderController.kt index cb95b25ece80..2460a3314be5 100644 --- a/packages/SystemUI/src/com/android/systemui/shade/ShadeHeaderController.kt +++ b/packages/SystemUI/src/com/android/systemui/shade/ShadeHeaderController.kt @@ -23,11 +23,11 @@ import android.app.PendingIntent import android.app.StatusBarManager import android.content.Intent import android.content.res.Configuration +import android.graphics.Insets import android.os.Bundle import android.os.Trace import android.os.Trace.TRACE_TAG_APP import android.provider.AlarmClock -import android.util.Pair import android.view.DisplayCutout import android.view.View import android.view.WindowInsets @@ -402,9 +402,9 @@ constructor( private fun updateConstraintsForInsets(view: MotionLayout, insets: WindowInsets) { val cutout = insets.displayCutout.also { this.cutout = it } - val sbInsets: Pair<Int, Int> = insetsProvider.getStatusBarContentInsetsForCurrentRotation() - val cutoutLeft = sbInsets.first - val cutoutRight = sbInsets.second + val sbInsets: Insets = insetsProvider.getStatusBarContentInsetsForCurrentRotation() + val cutoutLeft = sbInsets.left + val cutoutRight = sbInsets.right val hasCornerCutout: Boolean = insetsProvider.currentRotationHasCornerCutout() updateQQSPaddings() // Set these guides as the left/right limits for content that lives in the top row, using diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java b/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java index d88fab0deaa3..ada7d3ea1698 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java @@ -153,7 +153,7 @@ public class CommandQueue extends IStatusBar.Stub implements private static final int MSG_HIDE_TOAST = 53 << MSG_SHIFT; private static final int MSG_TRACING_STATE_CHANGED = 54 << MSG_SHIFT; private static final int MSG_SUPPRESS_AMBIENT_DISPLAY = 55 << MSG_SHIFT; - private static final int MSG_REQUEST_WINDOW_MAGNIFICATION_CONNECTION = 56 << MSG_SHIFT; + private static final int MSG_REQUEST_MAGNIFICATION_CONNECTION = 56 << MSG_SHIFT; //TODO(b/169175022) Update name and when feature name is locked. private static final int MSG_EMERGENCY_ACTION_LAUNCH_GESTURE = 58 << MSG_SHIFT; private static final int MSG_SET_NAVIGATION_BAR_LUMA_SAMPLING_ENABLED = 59 << MSG_SHIFT; @@ -426,11 +426,11 @@ public class CommandQueue extends IStatusBar.Stub implements /** * Requests {@link com.android.systemui.accessibility.Magnification} to invoke * {@code android.view.accessibility.AccessibilityManager# - * setWindowMagnificationConnection(IWindowMagnificationConnection)} + * setMagnificationConnection(IMagnificationConnection)} * * @param connect {@code true} if needs connection, otherwise set the connection to null. */ - default void requestWindowMagnificationConnection(boolean connect) { } + default void requestMagnificationConnection(boolean connect) { } /** * @see IStatusBar#setNavigationBarLumaSamplingEnabled(int, boolean) @@ -1125,9 +1125,9 @@ public class CommandQueue extends IStatusBar.Stub implements } @Override - public void requestWindowMagnificationConnection(boolean connect) { + public void requestMagnificationConnection(boolean connect) { synchronized (mLock) { - mHandler.obtainMessage(MSG_REQUEST_WINDOW_MAGNIFICATION_CONNECTION, connect) + mHandler.obtainMessage(MSG_REQUEST_MAGNIFICATION_CONNECTION, connect) .sendToTarget(); } } @@ -1767,9 +1767,9 @@ public class CommandQueue extends IStatusBar.Stub implements callbacks.suppressAmbientDisplay((boolean) msg.obj); } break; - case MSG_REQUEST_WINDOW_MAGNIFICATION_CONNECTION: + case MSG_REQUEST_MAGNIFICATION_CONNECTION: for (int i = 0; i < mCallbacks.size(); i++) { - mCallbacks.get(i).requestWindowMagnificationConnection((Boolean) msg.obj); + mCallbacks.get(i).requestMagnificationConnection((Boolean) msg.obj); } break; case MSG_SET_NAVIGATION_BAR_LUMA_SAMPLING_ENABLED: diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/events/PrivacyDotViewController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/events/PrivacyDotViewController.kt index a36d36c3827b..618dec22b32c 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/events/PrivacyDotViewController.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/events/PrivacyDotViewController.kt @@ -278,6 +278,7 @@ open class PrivacyDotViewController @Inject constructor( var contentInsets = state.contentRectForRotation(rot) tl.setPadding(0, state.paddingTop, 0, 0) (tl.layoutParams as FrameLayout.LayoutParams).apply { + topMargin = contentInsets.top height = contentInsets.height() if (rtl) { width = contentInsets.left @@ -290,6 +291,7 @@ open class PrivacyDotViewController @Inject constructor( contentInsets = state.contentRectForRotation(rot) tr.setPadding(0, state.paddingTop, 0, 0) (tr.layoutParams as FrameLayout.LayoutParams).apply { + topMargin = contentInsets.top height = contentInsets.height() if (rtl) { width = contentInsets.left @@ -302,6 +304,7 @@ open class PrivacyDotViewController @Inject constructor( contentInsets = state.contentRectForRotation(rot) br.setPadding(0, state.paddingTop, 0, 0) (br.layoutParams as FrameLayout.LayoutParams).apply { + topMargin = contentInsets.top height = contentInsets.height() if (rtl) { width = contentInsets.left @@ -314,6 +317,7 @@ open class PrivacyDotViewController @Inject constructor( contentInsets = state.contentRectForRotation(rot) bl.setPadding(0, state.paddingTop, 0, 0) (bl.layoutParams as FrameLayout.LayoutParams).apply { + topMargin = contentInsets.top height = contentInsets.height() if (rtl) { width = contentInsets.left diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/events/SystemEventChipAnimationController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/events/SystemEventChipAnimationController.kt index fec176523b4c..118f5f0515be 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/events/SystemEventChipAnimationController.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/events/SystemEventChipAnimationController.kt @@ -87,8 +87,8 @@ class SystemEventChipAnimationController @Inject constructor( animationWindowView.addView( it.view, layoutParamsDefault( - if (animationWindowView.isLayoutRtl) insets.first - else insets.second)) + if (animationWindowView.isLayoutRtl) insets.left + else insets.right)) it.view.alpha = 0f // For some reason, the window view's measured width is always 0 here, so use the // parent (status bar) @@ -289,7 +289,7 @@ class SystemEventChipAnimationController @Inject constructor( */ private fun updateChipBounds(chip: BackgroundAnimatableView, contentArea: Rect) { // decide which direction we're animating from, and then set some screen coordinates - val chipTop = (contentArea.bottom - chip.view.measuredHeight) / 2 + val chipTop = contentArea.top + (contentArea.height() - chip.view.measuredHeight) / 2 val chipBottom = chipTop + chip.view.measuredHeight val chipRight: Int val chipLeft: Int diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarView.java index 8a64a509a0e9..145dbff81144 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarView.java @@ -24,11 +24,11 @@ import android.content.Context; import android.content.res.Configuration; import android.content.res.Resources; import android.graphics.Color; +import android.graphics.Insets; import android.graphics.Rect; import android.graphics.drawable.Drawable; import android.os.Trace; import android.util.AttributeSet; -import android.util.Pair; import android.util.TypedValue; import android.view.DisplayCutout; import android.view.Gravity; @@ -103,7 +103,7 @@ public class KeyguardStatusBarView extends RelativeLayout { private DisplayCutout mDisplayCutout; private int mRoundedCornerPadding = 0; // right and left padding applied to this view to account for cutouts and rounded corners - private Pair<Integer, Integer> mPadding = new Pair(0, 0); + private Insets mPadding = Insets.of(0, 0, 0, 0); /** * The clipping on the top @@ -184,7 +184,7 @@ public class KeyguardStatusBarView extends RelativeLayout { int marginStart = calculateMargin( getResources().getDimensionPixelSize(R.dimen.keyguard_carrier_text_margin), - mPadding.first); + mPadding.left); lp.setMarginStart(marginStart); mCarrierLabel.setLayoutParams(lp); @@ -303,9 +303,9 @@ public class KeyguardStatusBarView extends RelativeLayout { // consider privacy dot space final int minLeft = (isLayoutRtl() && mIsPrivacyDotEnabled) - ? Math.max(mMinDotWidth, mPadding.first) : mPadding.first; + ? Math.max(mMinDotWidth, mPadding.left) : mPadding.left; final int minRight = (!isLayoutRtl() && mIsPrivacyDotEnabled) - ? Math.max(mMinDotWidth, mPadding.second) : mPadding.second; + ? Math.max(mMinDotWidth, mPadding.right) : mPadding.right; setPadding(minLeft, waterfallTop, minRight, 0); } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarView.java index a27e67b965a5..cb7bc256504e 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarView.java @@ -20,10 +20,10 @@ package com.android.systemui.statusbar.phone; import android.annotation.Nullable; import android.content.Context; import android.content.res.Configuration; +import android.graphics.Insets; import android.graphics.Rect; import android.util.AttributeSet; import android.util.Log; -import android.util.Pair; import android.view.DisplayCutout; import android.view.MotionEvent; import android.view.View; @@ -271,13 +271,12 @@ public class PhoneStatusBarView extends FrameLayout { } private void updateSafeInsets() { - Pair<Integer, Integer> insets = mContentInsetsProvider + Insets insets = mContentInsetsProvider .getStatusBarContentInsetsForCurrentRotation(); - setPadding( - insets.first, - getPaddingTop(), - insets.second, + insets.left, + insets.top, + insets.right, getPaddingBottom()); } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarContentInsetsProvider.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarContentInsetsProvider.kt index cba72d08840f..3b96f5793fe8 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarContentInsetsProvider.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarContentInsetsProvider.kt @@ -16,13 +16,16 @@ package com.android.systemui.statusbar.phone +import android.annotation.Px import android.content.Context import android.content.res.Resources +import android.graphics.Insets import android.graphics.Point import android.graphics.Rect import android.util.LruCache import android.util.Pair import android.view.DisplayCutout +import android.view.Surface import androidx.annotation.VisibleForTesting import com.android.internal.policy.SystemBarUtils import com.android.systemui.Dumpable @@ -154,13 +157,13 @@ class StatusBarContentInsetsProvider @Inject constructor( } /** - * Calculate the distance from the left and right edges of the screen to the status bar + * Calculate the distance from the left, right and top edges of the screen to the status bar * content area. This differs from the content area rects in that these values can be used * directly as padding. * * @param rotation the target rotation for which to calculate insets */ - fun getStatusBarContentInsetsForRotation(@Rotation rotation: Int): Pair<Int, Int> = + fun getStatusBarContentInsetsForRotation(@Rotation rotation: Int): Insets = traceSection(tag = "StatusBarContentInsetsProvider.getStatusBarContentInsetsForRotation") { val displayCutout = checkNotNull(context.display).cutout val key = getCacheKey(rotation, displayCutout) @@ -175,15 +178,14 @@ class StatusBarContentInsetsProvider @Inject constructor( val area = insetsCache[key] ?: getAndSetCalculatedAreaForRotation( rotation, displayCutout, getResourcesForRotation(rotation, context), key) - Pair(area.left, width - area.right) + Insets.of(area.left, area.top, /* right= */ width - area.right, /* bottom= */ 0) } /** - * Calculate the left and right insets for the status bar content in the device's current - * rotation + * Calculate the insets for the status bar content in the device's current rotation * @see getStatusBarContentAreaForRotation */ - fun getStatusBarContentInsetsForCurrentRotation(): Pair<Int, Int> { + fun getStatusBarContentInsetsForCurrentRotation(): Insets { return getStatusBarContentInsetsForRotation(getExactRotation(context)) } @@ -251,6 +253,10 @@ class StatusBarContentInsetsProvider @Inject constructor( minRight = max(minDotPadding, roundedCornerPadding) } + val bottomAlignedMargin = getBottomAlignedMargin(targetRotation, rotatedResources) + val statusBarContentHeight = + rotatedResources.getDimensionPixelSize(R.dimen.status_bar_icon_size_sp) + return calculateInsetsForRotationWithRotatedResources( currentRotation, targetRotation, @@ -260,7 +266,22 @@ class StatusBarContentInsetsProvider @Inject constructor( minLeft, minRight, configurationController.isLayoutRtl, - dotWidth) + dotWidth, + bottomAlignedMargin, + statusBarContentHeight) + } + + @Px + private fun getBottomAlignedMargin(targetRotation: Int, resources: Resources): Int { + val dimenRes = + when (targetRotation) { + Surface.ROTATION_0 -> R.dimen.status_bar_bottom_aligned_margin_rotation_0 + Surface.ROTATION_90 -> R.dimen.status_bar_bottom_aligned_margin_rotation_90 + Surface.ROTATION_180 -> R.dimen.status_bar_bottom_aligned_margin_rotation_180 + Surface.ROTATION_270 -> R.dimen.status_bar_bottom_aligned_margin_rotation_270 + else -> throw IllegalStateException("Unknown rotation: $targetRotation") + } + return resources.getDimensionPixelSize(dimenRes) } fun getStatusBarPaddingTop(@Rotation rotation: Int? = null): Int { @@ -329,8 +350,7 @@ fun getPrivacyChipBoundingRectForInsets( } /** - * Calculates the exact left and right positions for the status bar contents for the given - * rotation + * Calculates the exact left and right positions for the status bar contents for the given rotation * * @param currentRotation current device rotation * @param targetRotation rotation for which to calculate the status bar content rect @@ -341,9 +361,12 @@ fun getPrivacyChipBoundingRectForInsets( * @param minRight the minimum padding to enforce on the right * @param isRtl current layout direction is Right-To-Left or not * @param dotWidth privacy dot image width (0 if privacy dot is disabled) - * + * @param bottomAlignedMargin the bottom margin that the status bar content should have. -1 if none, + * and content should be centered vertically. + * @param statusBarContentHeight the height of the status bar contents (icons, text, etc) * @see [RotationUtils#getResourcesForRotation] */ +@VisibleForTesting fun calculateInsetsForRotationWithRotatedResources( @Rotation currentRotation: Int, @Rotation targetRotation: Int, @@ -353,7 +376,9 @@ fun calculateInsetsForRotationWithRotatedResources( minLeft: Int, minRight: Int, isRtl: Boolean, - dotWidth: Int + dotWidth: Int, + bottomAlignedMargin: Int, + statusBarContentHeight: Int ): Rect { /* TODO: Check if this is ever used for devices with no rounded corners @@ -363,7 +388,7 @@ fun calculateInsetsForRotationWithRotatedResources( val rotZeroBounds = getRotationZeroDisplayBounds(maxBounds, currentRotation) - val sbLeftRight = getStatusBarLeftRight( + return getStatusBarContentBounds( displayCutout, statusBarHeight, rotZeroBounds.right, @@ -375,9 +400,9 @@ fun calculateInsetsForRotationWithRotatedResources( isRtl, dotWidth, targetRotation, - currentRotation) - - return sbLeftRight + currentRotation, + bottomAlignedMargin, + statusBarContentHeight) } /** @@ -399,26 +424,30 @@ fun calculateInsetsForRotationWithRotatedResources( * @return a Rect which exactly calculates the Status Bar's content rect relative to the target * rotation */ -private fun getStatusBarLeftRight( - displayCutout: DisplayCutout?, - sbHeight: Int, - width: Int, - height: Int, - cWidth: Int, - cHeight: Int, - minLeft: Int, - minRight: Int, - isRtl: Boolean, - dotWidth: Int, - @Rotation targetRotation: Int, - @Rotation currentRotation: Int +private fun getStatusBarContentBounds( + displayCutout: DisplayCutout?, + sbHeight: Int, + width: Int, + height: Int, + cWidth: Int, + cHeight: Int, + minLeft: Int, + minRight: Int, + isRtl: Boolean, + dotWidth: Int, + @Rotation targetRotation: Int, + @Rotation currentRotation: Int, + bottomAlignedMargin: Int, + statusBarContentHeight: Int ): Rect { + val insetTop = getInsetTop(bottomAlignedMargin, statusBarContentHeight, sbHeight) + val logicalDisplayWidth = if (targetRotation.isHorizontal()) height else width val cutoutRects = displayCutout?.boundingRects if (cutoutRects == null || cutoutRects.isEmpty()) { return Rect(minLeft, - 0, + insetTop, logicalDisplayWidth - minRight, sbHeight) } @@ -455,7 +484,48 @@ private fun getStatusBarLeftRight( // is very close to but not directly touch edges. } - return Rect(leftMargin, 0, logicalDisplayWidth - rightMargin, sbHeight) + return Rect(leftMargin, insetTop, logicalDisplayWidth - rightMargin, sbHeight) +} + +/* + * Returns the inset top of the status bar. + * + * Only greater than 0, when we want the content to be bottom aligned. + * + * Common case when we want content to be vertically centered within the status bar. + * Example dimensions: + * - Status bar height: 50dp + * - Content height: 20dp + * _______________________________________________ + * | | + * | | + * | 09:00 5G [] 74% | 20dp Content CENTER_VERTICAL gravity + * | | + * |_____________________________________________| + * + * Case when we want bottom alignment and a bottom margin of 10dp. + * We need to make the status bar height artificially smaller using top padding/inset. + * - Status bar height: 50dp + * - Content height: 20dp + * - Bottom margin: 10dp + * ______________________________________________ + * |_____________________________________________| 10dp top inset/padding + * | | 40dp new artificial status bar height + * | 09:00 5G [] 74% | 20dp Content CENTER_VERTICAL gravity + * |_____________________________________________| 10dp bottom margin + */ +@Px +private fun getInsetTop( + bottomAlignedMargin: Int, + statusBarContentHeight: Int, + statusBarHeight: Int +): Int { + val bottomAlignmentEnabled = bottomAlignedMargin >= 0 + if (!bottomAlignmentEnabled) { + return 0 + } + val newArtificialStatusBarHeight = bottomAlignedMargin * 2 + statusBarContentHeight + return statusBarHeight - newArtificialStatusBarHeight } private fun sbRect( diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/SystemUIDialog.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/SystemUIDialog.java index 2df30dccb13d..93bc96022292 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/SystemUIDialog.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/SystemUIDialog.java @@ -203,6 +203,28 @@ public class SystemUIDialog extends AlertDialog implements ViewRootImpl.ConfigCh SysUiState sysUiState, BroadcastDispatcher broadcastDispatcher, DialogLaunchAnimator dialogLaunchAnimator, + Delegate delegate) { + this( + context, + theme, + dismissOnDeviceLock, + featureFlags, + dialogManager, + sysUiState, + broadcastDispatcher, + dialogLaunchAnimator, + (DialogDelegate<SystemUIDialog>) delegate); + } + + public SystemUIDialog( + Context context, + int theme, + boolean dismissOnDeviceLock, + FeatureFlags featureFlags, + SystemUIDialogManager dialogManager, + SysUiState sysUiState, + BroadcastDispatcher broadcastDispatcher, + DialogLaunchAnimator dialogLaunchAnimator, DialogDelegate<SystemUIDialog> delegate) { super(context, theme); mContext = context; diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/IWindowMagnificationConnectionTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/IMagnificationConnectionTest.java index 43952824f9a7..235aa218715d 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/IWindowMagnificationConnectionTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/IMagnificationConnectionTest.java @@ -33,8 +33,8 @@ import android.testing.AndroidTestingRunner; import android.testing.TestableLooper; import android.view.Display; import android.view.accessibility.AccessibilityManager; +import android.view.accessibility.IMagnificationConnection; import android.view.accessibility.IRemoteMagnificationAnimationCallback; -import android.view.accessibility.IWindowMagnificationConnection; import android.view.accessibility.IWindowMagnificationConnectionCallback; import androidx.test.filters.SmallTest; @@ -53,13 +53,13 @@ import org.mockito.Mock; import org.mockito.MockitoAnnotations; /** - * Tests for {@link android.view.accessibility.IWindowMagnificationConnection} retrieved from + * Tests for {@link android.view.accessibility.IMagnificationConnection} retrieved from * {@link Magnification} */ @SmallTest @RunWith(AndroidTestingRunner.class) @TestableLooper.RunWithLooper -public class IWindowMagnificationConnectionTest extends SysuiTestCase { +public class IMagnificationConnectionTest extends SysuiTestCase { private static final int TEST_DISPLAY = Display.DEFAULT_DISPLAY; @Mock @@ -85,7 +85,7 @@ public class IWindowMagnificationConnectionTest extends SysuiTestCase { @Mock private AccessibilityLogger mA11yLogger; - private IWindowMagnificationConnection mIWindowMagnificationConnection; + private IMagnificationConnection mIMagnificationConnection; private Magnification mMagnification; private FakeDisplayTracker mDisplayTracker = new FakeDisplayTracker(mContext); @@ -94,10 +94,10 @@ public class IWindowMagnificationConnectionTest extends SysuiTestCase { MockitoAnnotations.initMocks(this); getContext().addMockSystemService(Context.ACCESSIBILITY_SERVICE, mAccessibilityManager); doAnswer(invocation -> { - mIWindowMagnificationConnection = invocation.getArgument(0); + mIMagnificationConnection = invocation.getArgument(0); return null; - }).when(mAccessibilityManager).setWindowMagnificationConnection( - any(IWindowMagnificationConnection.class)); + }).when(mAccessibilityManager).setMagnificationConnection( + any(IMagnificationConnection.class)); mMagnification = new Magnification(getContext(), getContext().getMainThreadHandler(), mCommandQueue, mModeSwitchesController, mSysUiState, mOverviewProxyService, mSecureSettings, @@ -107,14 +107,14 @@ public class IWindowMagnificationConnectionTest extends SysuiTestCase { mMagnification.mMagnificationSettingsSupplier = new FakeSettingsSupplier( mContext.getSystemService(DisplayManager.class)); - mMagnification.requestWindowMagnificationConnection(true); - assertNotNull(mIWindowMagnificationConnection); - mIWindowMagnificationConnection.setConnectionCallback(mConnectionCallback); + mMagnification.requestMagnificationConnection(true); + assertNotNull(mIMagnificationConnection); + mIMagnificationConnection.setConnectionCallback(mConnectionCallback); } @Test public void enableWindowMagnification_passThrough() throws RemoteException { - mIWindowMagnificationConnection.enableWindowMagnification(TEST_DISPLAY, 3.0f, Float.NaN, + mIMagnificationConnection.enableWindowMagnification(TEST_DISPLAY, 3.0f, Float.NaN, Float.NaN, 0f, 0f, mAnimationCallback); waitForIdleSync(); @@ -124,7 +124,7 @@ public class IWindowMagnificationConnectionTest extends SysuiTestCase { @Test public void disableWindowMagnification_deleteWindowMagnification() throws RemoteException { - mIWindowMagnificationConnection.disableWindowMagnification(TEST_DISPLAY, + mIMagnificationConnection.disableWindowMagnification(TEST_DISPLAY, mAnimationCallback); waitForIdleSync(); @@ -134,7 +134,7 @@ public class IWindowMagnificationConnectionTest extends SysuiTestCase { @Test public void setScaleForWindowMagnification() throws RemoteException { - mIWindowMagnificationConnection.setScaleForWindowMagnification(TEST_DISPLAY, 3.0f); + mIMagnificationConnection.setScaleForWindowMagnification(TEST_DISPLAY, 3.0f); waitForIdleSync(); verify(mWindowMagnificationController).setScale(3.0f); @@ -142,7 +142,7 @@ public class IWindowMagnificationConnectionTest extends SysuiTestCase { @Test public void moveWindowMagnifier() throws RemoteException { - mIWindowMagnificationConnection.moveWindowMagnifier(TEST_DISPLAY, 100f, 200f); + mIMagnificationConnection.moveWindowMagnifier(TEST_DISPLAY, 100f, 200f); waitForIdleSync(); verify(mWindowMagnificationController).moveWindowMagnifier(100f, 200f); @@ -150,7 +150,7 @@ public class IWindowMagnificationConnectionTest extends SysuiTestCase { @Test public void moveWindowMagnifierToPosition() throws RemoteException { - mIWindowMagnificationConnection.moveWindowMagnifierToPosition(TEST_DISPLAY, + mIMagnificationConnection.moveWindowMagnifierToPosition(TEST_DISPLAY, 100f, 200f, mAnimationCallback); waitForIdleSync(); @@ -163,7 +163,7 @@ public class IWindowMagnificationConnectionTest extends SysuiTestCase { // magnification settings panel should not be showing assertFalse(mMagnification.isMagnificationSettingsPanelShowing(TEST_DISPLAY)); - mIWindowMagnificationConnection.showMagnificationButton(TEST_DISPLAY, + mIMagnificationConnection.showMagnificationButton(TEST_DISPLAY, Settings.Secure.ACCESSIBILITY_MAGNIFICATION_MODE_FULLSCREEN); waitForIdleSync(); @@ -173,7 +173,7 @@ public class IWindowMagnificationConnectionTest extends SysuiTestCase { @Test public void removeMagnificationButton() throws RemoteException { - mIWindowMagnificationConnection.removeMagnificationButton(TEST_DISPLAY); + mIMagnificationConnection.removeMagnificationButton(TEST_DISPLAY); waitForIdleSync(); verify(mModeSwitchesController).removeButton(TEST_DISPLAY); @@ -181,7 +181,7 @@ public class IWindowMagnificationConnectionTest extends SysuiTestCase { @Test public void removeMagnificationSettingsPanel() throws RemoteException { - mIWindowMagnificationConnection.removeMagnificationSettingsPanel(TEST_DISPLAY); + mIMagnificationConnection.removeMagnificationSettingsPanel(TEST_DISPLAY); waitForIdleSync(); verify(mMagnificationSettingsController).closeMagnificationSettings(); @@ -191,7 +191,7 @@ public class IWindowMagnificationConnectionTest extends SysuiTestCase { public void onUserMagnificationScaleChanged() throws RemoteException { final int testUserId = 1; final float testScale = 3.0f; - mIWindowMagnificationConnection.onUserMagnificationScaleChanged( + mIMagnificationConnection.onUserMagnificationScaleChanged( testUserId, TEST_DISPLAY, testScale); waitForIdleSync(); diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/MagnificationTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/MagnificationTest.java index c972febf2c7e..39c8f5d724b0 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/MagnificationTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/MagnificationTest.java @@ -43,7 +43,7 @@ import android.testing.AndroidTestingRunner; import android.testing.TestableLooper; import android.view.Display; import android.view.accessibility.AccessibilityManager; -import android.view.accessibility.IWindowMagnificationConnection; +import android.view.accessibility.IMagnificationConnection; import android.view.accessibility.IWindowMagnificationConnectionCallback; import androidx.test.filters.SmallTest; @@ -98,11 +98,11 @@ public class MagnificationTest extends SysuiTestCase { MockitoAnnotations.initMocks(this); getContext().addMockSystemService(Context.ACCESSIBILITY_SERVICE, mAccessibilityManager); doAnswer(invocation -> { - IWindowMagnificationConnection connection = invocation.getArgument(0); + IMagnificationConnection connection = invocation.getArgument(0); connection.setConnectionCallback(mConnectionCallback); return null; - }).when(mAccessibilityManager).setWindowMagnificationConnection( - any(IWindowMagnificationConnection.class)); + }).when(mAccessibilityManager).setMagnificationConnection( + any(IMagnificationConnection.class)); when(mSysUiState.setFlag(anyInt(), anyBoolean())).thenReturn(mSysUiState); @@ -138,22 +138,22 @@ public class MagnificationTest extends SysuiTestCase { @Test public void requestWindowMagnificationConnection_setConnectionAndListener() { - mCommandQueue.requestWindowMagnificationConnection(true); + mCommandQueue.requestMagnificationConnection(true); waitForIdleSync(); - verify(mAccessibilityManager).setWindowMagnificationConnection(any( - IWindowMagnificationConnection.class)); + verify(mAccessibilityManager).setMagnificationConnection(any( + IMagnificationConnection.class)); - mCommandQueue.requestWindowMagnificationConnection(false); + mCommandQueue.requestMagnificationConnection(false); waitForIdleSync(); - verify(mAccessibilityManager).setWindowMagnificationConnection(isNull()); + verify(mAccessibilityManager).setMagnificationConnection(isNull()); } @Test public void onWindowMagnifierBoundsChanged() throws RemoteException { final Rect testBounds = new Rect(0, 0, 500, 600); - mCommandQueue.requestWindowMagnificationConnection(true); + mCommandQueue.requestMagnificationConnection(true); waitForIdleSync(); mMagnification.mWindowMagnifierCallback @@ -166,7 +166,7 @@ public class MagnificationTest extends SysuiTestCase { public void onPerformScaleAction_enabled_notifyCallback() throws RemoteException { final float newScale = 4.0f; final boolean updatePersistence = true; - mCommandQueue.requestWindowMagnificationConnection(true); + mCommandQueue.requestMagnificationConnection(true); waitForIdleSync(); mMagnification.mWindowMagnifierCallback @@ -178,7 +178,7 @@ public class MagnificationTest extends SysuiTestCase { @Test public void onAccessibilityActionPerformed_enabled_notifyCallback() throws RemoteException { - mCommandQueue.requestWindowMagnificationConnection(true); + mCommandQueue.requestMagnificationConnection(true); waitForIdleSync(); mMagnification.mWindowMagnifierCallback @@ -189,7 +189,7 @@ public class MagnificationTest extends SysuiTestCase { @Test public void onMove_enabled_notifyCallback() throws RemoteException { - mCommandQueue.requestWindowMagnificationConnection(true); + mCommandQueue.requestMagnificationConnection(true); waitForIdleSync(); mMagnification.mWindowMagnifierCallback.onMove(TEST_DISPLAY); @@ -254,7 +254,7 @@ public class MagnificationTest extends SysuiTestCase { @Test public void onMagnifierScale_notifyCallback() throws RemoteException { - mCommandQueue.requestWindowMagnificationConnection(true); + mCommandQueue.requestMagnificationConnection(true); waitForIdleSync(); final float scale = 3.0f; final boolean updatePersistence = false; @@ -271,7 +271,7 @@ public class MagnificationTest extends SysuiTestCase { public void onModeSwitch_windowEnabledAndSwitchToFullscreen_hidePanelAndNotifyCallback() throws RemoteException { when(mWindowMagnificationController.isActivated()).thenReturn(true); - mCommandQueue.requestWindowMagnificationConnection(true); + mCommandQueue.requestMagnificationConnection(true); waitForIdleSync(); mMagnification.mMagnificationSettingsControllerCallback.onModeSwitch( @@ -289,7 +289,7 @@ public class MagnificationTest extends SysuiTestCase { public void onModeSwitch_switchToSameMode_doNothing() throws RemoteException { when(mWindowMagnificationController.isActivated()).thenReturn(true); - mCommandQueue.requestWindowMagnificationConnection(true); + mCommandQueue.requestMagnificationConnection(true); waitForIdleSync(); mMagnification.mMagnificationSettingsControllerCallback.onModeSwitch( diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/fontscaling/FontScalingDialogTest.kt b/packages/SystemUI/tests/src/com/android/systemui/accessibility/fontscaling/FontScalingDialogDelegateTest.kt index 83bee932fc59..bfb5485e47b7 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/fontscaling/FontScalingDialogTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/fontscaling/FontScalingDialogDelegateTest.kt @@ -20,18 +20,24 @@ import android.os.Handler import android.provider.Settings import android.testing.AndroidTestingRunner import android.testing.TestableLooper +import android.view.LayoutInflater import android.view.ViewGroup import android.widget.Button import android.widget.SeekBar import androidx.test.filters.SmallTest import com.android.systemui.res.R import com.android.systemui.SysuiTestCase +import com.android.systemui.animation.DialogLaunchAnimator import com.android.systemui.common.ui.view.SeekBarWithIconButtonsView import com.android.systemui.common.ui.view.SeekBarWithIconButtonsView.OnSeekBarWithIconButtonsChangeListener +import com.android.systemui.flags.FakeFeatureFlags +import com.android.systemui.flags.Flags +import com.android.systemui.model.SysUiState import com.android.systemui.settings.UserTracker +import com.android.systemui.statusbar.phone.SystemUIDialog +import com.android.systemui.statusbar.phone.SystemUIDialog.DEFAULT_DISMISS_ON_DEVICE_LOCK +import com.android.systemui.statusbar.phone.SystemUIDialogManager import com.android.systemui.util.concurrency.FakeExecutor -import com.android.systemui.util.mockito.capture -import com.android.systemui.util.mockito.whenever import com.android.systemui.util.settings.FakeSettings import com.android.systemui.util.settings.SecureSettings import com.android.systemui.util.settings.SystemSettings @@ -40,25 +46,25 @@ import com.google.common.truth.Truth.assertThat import org.junit.Before import org.junit.Test import org.junit.runner.RunWith -import org.mockito.ArgumentCaptor -import org.mockito.Captor +import org.mockito.ArgumentMatchers.any +import org.mockito.ArgumentMatchers.anyBoolean +import org.mockito.ArgumentMatchers.anyInt import org.mockito.Mock import org.mockito.Mockito.spy import org.mockito.Mockito.verify +import org.mockito.Mockito.`when` as whenever import org.mockito.MockitoAnnotations private const val ON: Int = 1 private const val OFF: Int = 0 -/** Tests for [FontScalingDialog]. */ +/** Tests for [FontScalingDialogDelegate]. */ @SmallTest @RunWith(AndroidTestingRunner::class) @TestableLooper.RunWithLooper(setAsMainLooper = true) -class FontScalingDialogTest : SysuiTestCase() { - private val MIN_UPDATE_INTERVAL_MS: Long = 800 - private val CHANGE_BY_SEEKBAR_DELAY_MS: Long = 100 - private val CHANGE_BY_BUTTON_DELAY_MS: Long = 300 - private lateinit var fontScalingDialog: FontScalingDialog +class FontScalingDialogDelegateTest : SysuiTestCase() { + private lateinit var fontScalingDialogDelegate: FontScalingDialogDelegate + private lateinit var dialog: SystemUIDialog private lateinit var systemSettings: SystemSettings private lateinit var secureSettings: SecureSettings private lateinit var systemClock: FakeSystemClock @@ -69,9 +75,12 @@ class FontScalingDialogTest : SysuiTestCase() { .getResources() .getStringArray(com.android.settingslib.R.array.entryvalues_font_size) + @Mock private lateinit var dialogManager: SystemUIDialogManager + @Mock private lateinit var dialogFactory: SystemUIDialog.Factory @Mock private lateinit var userTracker: UserTracker - @Captor - private lateinit var seekBarChangeCaptor: ArgumentCaptor<OnSeekBarWithIconButtonsChangeListener> + private val featureFlags = FakeFeatureFlags() + @Mock private lateinit var sysuiState: SysUiState + @Mock private lateinit var dialogLaunchAnimator: DialogLaunchAnimator @Before fun setUp() { @@ -79,28 +88,46 @@ class FontScalingDialogTest : SysuiTestCase() { testableLooper = TestableLooper.get(this) val mainHandler = Handler(testableLooper.looper) systemSettings = FakeSettings() + featureFlags.set(Flags.WM_ENABLE_PREDICTIVE_BACK_QS_DIALOG_ANIM, true) // Guarantee that the systemSettings always starts with the default font scale. systemSettings.putFloatForUser(Settings.System.FONT_SCALE, 1.0f, userTracker.userId) secureSettings = FakeSettings() systemClock = FakeSystemClock() backgroundDelayableExecutor = FakeExecutor(systemClock) - fontScalingDialog = - FontScalingDialog( + whenever(sysuiState.setFlag(anyInt(), anyBoolean())).thenReturn(sysuiState) + + fontScalingDialogDelegate = spy(FontScalingDialogDelegate( mContext, + dialogFactory, + LayoutInflater.from(mContext), systemSettings, secureSettings, systemClock, userTracker, mainHandler, backgroundDelayableExecutor - ) + )) + + dialog = SystemUIDialog( + mContext, + 0, + DEFAULT_DISMISS_ON_DEVICE_LOCK, + featureFlags, + dialogManager, + sysuiState, + fakeBroadcastDispatcher, + dialogLaunchAnimator, + fontScalingDialogDelegate + ) + + whenever(dialogFactory.create(any())).thenReturn(dialog) } @Test fun showTheDialog_seekbarIsShowingCorrectProgress() { - fontScalingDialog.show() + dialog.show() - val seekBar: SeekBar = fontScalingDialog.findViewById<SeekBar>(R.id.seekbar)!! + val seekBar: SeekBar = dialog.findViewById<SeekBar>(R.id.seekbar)!! val progress: Int = seekBar.getProgress() val currentScale = systemSettings.getFloatForUser( @@ -111,17 +138,17 @@ class FontScalingDialogTest : SysuiTestCase() { assertThat(currentScale).isEqualTo(fontSizeValueArray[progress].toFloat()) - fontScalingDialog.dismiss() + dialog.dismiss() } @Test fun progressIsZero_clickIconEnd_seekBarProgressIncreaseOne_fontSizeScaled() { - fontScalingDialog.show() + dialog.show() - val iconEndFrame: ViewGroup = fontScalingDialog.findViewById(R.id.icon_end_frame)!! + val iconEndFrame: ViewGroup = dialog.findViewById(R.id.icon_end_frame)!! val seekBarWithIconButtonsView: SeekBarWithIconButtonsView = - fontScalingDialog.findViewById(R.id.font_scaling_slider)!! - val seekBar: SeekBar = fontScalingDialog.findViewById(R.id.seekbar)!! + dialog.findViewById(R.id.font_scaling_slider)!! + val seekBar: SeekBar = dialog.findViewById(R.id.seekbar)!! seekBarWithIconButtonsView.setProgress(0) backgroundDelayableExecutor.runAllReady() @@ -142,17 +169,17 @@ class FontScalingDialogTest : SysuiTestCase() { assertThat(seekBar.getProgress()).isEqualTo(1) assertThat(currentScale).isEqualTo(fontSizeValueArray[1].toFloat()) - fontScalingDialog.dismiss() + dialog.dismiss() } @Test fun progressIsMax_clickIconStart_seekBarProgressDecreaseOne_fontSizeScaled() { - fontScalingDialog.show() + dialog.show() - val iconStartFrame: ViewGroup = fontScalingDialog.findViewById(R.id.icon_start_frame)!! + val iconStartFrame: ViewGroup = dialog.findViewById(R.id.icon_start_frame)!! val seekBarWithIconButtonsView: SeekBarWithIconButtonsView = - fontScalingDialog.findViewById(R.id.font_scaling_slider)!! - val seekBar: SeekBar = fontScalingDialog.findViewById(R.id.seekbar)!! + dialog.findViewById(R.id.font_scaling_slider)!! + val seekBar: SeekBar = dialog.findViewById(R.id.seekbar)!! seekBarWithIconButtonsView.setProgress(fontSizeValueArray.size - 1) backgroundDelayableExecutor.runAllReady() @@ -174,14 +201,14 @@ class FontScalingDialogTest : SysuiTestCase() { assertThat(currentScale) .isEqualTo(fontSizeValueArray[fontSizeValueArray.size - 2].toFloat()) - fontScalingDialog.dismiss() + dialog.dismiss() } @Test fun progressChanged_keyWasNotSetBefore_fontScalingHasBeenChangedIsOn() { - fontScalingDialog.show() + dialog.show() - val iconStartFrame: ViewGroup = fontScalingDialog.findViewById(R.id.icon_start_frame)!! + val iconStartFrame: ViewGroup = dialog.findViewById(R.id.icon_start_frame)!! secureSettings.putIntForUser( Settings.Secure.ACCESSIBILITY_FONT_SCALING_HAS_BEEN_CHANGED, OFF, @@ -202,24 +229,21 @@ class FontScalingDialogTest : SysuiTestCase() { ) assertThat(currentSettings).isEqualTo(ON) - fontScalingDialog.dismiss() + dialog.dismiss() } @Test fun dragSeekbar_systemFontSizeSettingsDoesNotChange() { - fontScalingDialog = spy(fontScalingDialog) - val slider: SeekBarWithIconButtonsView = spy(SeekBarWithIconButtonsView(mContext)) - whenever( - fontScalingDialog.findViewById<SeekBarWithIconButtonsView>(R.id.font_scaling_slider) - ) - .thenReturn(slider) - fontScalingDialog.show() - verify(slider).setOnSeekBarWithIconButtonsChangeListener(capture(seekBarChangeCaptor)) + dialog.show() + + val slider = dialog.findViewById<SeekBarWithIconButtonsView>(R.id.font_scaling_slider)!! + val changeListener = slider.onSeekBarWithIconButtonsChangeListener + val seekBar: SeekBar = slider.findViewById(R.id.seekbar)!! // Default seekbar progress for font size is 1, simulate dragging to 0 without // releasing the finger. - seekBarChangeCaptor.value.onStartTrackingTouch(seekBar) + changeListener.onStartTrackingTouch(seekBar) // Update seekbar progress. This will trigger onProgressChanged in the // OnSeekBarChangeListener and the seekbar could get updated progress value // in onStopTrackingTouch. @@ -238,13 +262,13 @@ class FontScalingDialogTest : SysuiTestCase() { assertThat(systemScale).isEqualTo(1.0f) // Simulate releasing the finger from the seekbar. - seekBarChangeCaptor.value.onStopTrackingTouch(seekBar) + changeListener.onStopTrackingTouch(seekBar) backgroundDelayableExecutor.runAllReady() backgroundDelayableExecutor.advanceClockToNext() backgroundDelayableExecutor.runAllReady() // SeekBar interaction is finalized. - seekBarChangeCaptor.value.onUserInteractionFinalized( + changeListener.onUserInteractionFinalized( seekBar, OnSeekBarWithIconButtonsChangeListener.ControlUnitType.SLIDER ) @@ -261,25 +285,21 @@ class FontScalingDialogTest : SysuiTestCase() { ) assertThat(systemScale).isEqualTo(fontSizeValueArray[0].toFloat()) - fontScalingDialog.dismiss() + dialog.dismiss() } @Test fun dragSeekBar_createTextPreview() { - fontScalingDialog = spy(fontScalingDialog) - val slider: SeekBarWithIconButtonsView = spy(SeekBarWithIconButtonsView(mContext)) - whenever( - fontScalingDialog.findViewById<SeekBarWithIconButtonsView>(R.id.font_scaling_slider) - ) - .thenReturn(slider) - fontScalingDialog.show() - verify(slider).setOnSeekBarWithIconButtonsChangeListener(capture(seekBarChangeCaptor)) + dialog.show() + val slider = dialog.findViewById<SeekBarWithIconButtonsView>(R.id.font_scaling_slider)!! + val changeListener = slider.onSeekBarWithIconButtonsChangeListener + val seekBar: SeekBar = slider.findViewById(R.id.seekbar)!! // Default seekbar progress for font size is 1, simulate dragging to 0 without // releasing the finger - seekBarChangeCaptor.value.onStartTrackingTouch(seekBar) - seekBarChangeCaptor.value.onProgressChanged( + changeListener.onStartTrackingTouch(seekBar) + changeListener.onProgressChanged( seekBar, /* progress= */ 0, /* fromUser= */ false @@ -287,16 +307,16 @@ class FontScalingDialogTest : SysuiTestCase() { backgroundDelayableExecutor.advanceClockToNext() backgroundDelayableExecutor.runAllReady() - verify(fontScalingDialog).createTextPreview(/* index= */ 0) - fontScalingDialog.dismiss() + verify(fontScalingDialogDelegate).createTextPreview(/* index= */ 0) + dialog.dismiss() } @Test fun changeFontSize_buttonIsDisabledBeforeFontSizeChangeFinishes() { - fontScalingDialog.show() + dialog.show() - val iconEndFrame: ViewGroup = fontScalingDialog.findViewById(R.id.icon_end_frame)!! - val doneButton: Button = fontScalingDialog.findViewById(com.android.internal.R.id.button1)!! + val iconEndFrame: ViewGroup = dialog.findViewById(R.id.icon_end_frame)!! + val doneButton: Button = dialog.findViewById(com.android.internal.R.id.button1)!! iconEndFrame.performClick() backgroundDelayableExecutor.runAllReady() @@ -308,7 +328,7 @@ class FontScalingDialogTest : SysuiTestCase() { val config = Configuration() config.fontScale = 1.15f - fontScalingDialog.onConfigurationChanged(config) + dialog.onConfigurationChanged(config) testableLooper.processAllMessages() assertThat(doneButton.isEnabled).isTrue() } diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/FontScalingTileTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/FontScalingTileTest.kt index d1d3c17be67f..77964527eaf1 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/FontScalingTileTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/FontScalingTileTest.kt @@ -24,6 +24,7 @@ import android.view.View import androidx.test.filters.SmallTest import com.android.internal.logging.MetricsLogger import com.android.systemui.SysuiTestCase +import com.android.systemui.accessibility.fontscaling.FontScalingDialogDelegate import com.android.systemui.animation.DialogLaunchAnimator import com.android.systemui.classifier.FalsingManagerFake import com.android.systemui.plugins.ActivityStarter @@ -31,13 +32,12 @@ import com.android.systemui.plugins.statusbar.StatusBarStateController import com.android.systemui.qs.QSHost import com.android.systemui.qs.QsEventLogger import com.android.systemui.qs.logging.QSLogger -import com.android.systemui.settings.UserTracker +import com.android.systemui.statusbar.phone.SystemUIDialog import com.android.systemui.statusbar.policy.KeyguardStateController import com.android.systemui.util.concurrency.FakeExecutor import com.android.systemui.util.mockito.any import com.android.systemui.util.mockito.eq import com.android.systemui.util.mockito.nullable -import com.android.systemui.util.settings.FakeSettings import com.android.systemui.util.time.FakeSystemClock import com.google.common.truth.Truth.assertThat import org.junit.After @@ -45,9 +45,9 @@ import org.junit.Before import org.junit.Test import org.junit.runner.RunWith import org.mockito.ArgumentCaptor +import org.mockito.ArgumentMatchers.anyBoolean import org.mockito.Captor import org.mockito.Mock -import org.mockito.Mockito.anyBoolean import org.mockito.Mockito.never import org.mockito.Mockito.verify import org.mockito.Mockito.`when` @@ -64,8 +64,9 @@ class FontScalingTileTest : SysuiTestCase() { @Mock private lateinit var qsLogger: QSLogger @Mock private lateinit var dialogLaunchAnimator: DialogLaunchAnimator @Mock private lateinit var uiEventLogger: QsEventLogger - @Mock private lateinit var userTracker: UserTracker @Mock private lateinit var keyguardStateController: KeyguardStateController + @Mock private lateinit var fontScalingDialogDelegate: FontScalingDialogDelegate + @Mock private lateinit var dialog: SystemUIDialog private lateinit var testableLooper: TestableLooper private lateinit var systemClock: FakeSystemClock @@ -79,6 +80,7 @@ class FontScalingTileTest : SysuiTestCase() { MockitoAnnotations.initMocks(this) testableLooper = TestableLooper.get(this) `when`(qsHost.getContext()).thenReturn(mContext) + `when`(fontScalingDialogDelegate.createDialog()).thenReturn(dialog) systemClock = FakeSystemClock() backgroundDelayableExecutor = FakeExecutor(systemClock) @@ -95,11 +97,7 @@ class FontScalingTileTest : SysuiTestCase() { qsLogger, keyguardStateController, dialogLaunchAnimator, - FakeSettings(), - FakeSettings(), - FakeSystemClock(), - userTracker, - backgroundDelayableExecutor, + { fontScalingDialogDelegate }, ) fontScalingTile.initialize() testableLooper.processAllMessages() diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/ShadeHeaderControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/ShadeHeaderControllerTest.kt index 56061f64c315..9fa173ab040a 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/shade/ShadeHeaderControllerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/shade/ShadeHeaderControllerTest.kt @@ -83,6 +83,7 @@ import org.mockito.Mockito.reset import org.mockito.Mockito.times import org.mockito.Mockito.verify import org.mockito.Mockito.`when` as whenever +import android.graphics.Insets import org.mockito.junit.MockitoJUnit private val EMPTY_CHANGES = ConstraintsChanges() @@ -930,12 +931,16 @@ class ShadeHeaderControllerTest : SysuiTestCase() { return windowInsets } - private fun mockInsetsProvider( - insets: Pair<Int, Int> = 0 to 0, - cornerCutout: Boolean = false, - ) { + private fun mockInsetsProvider(insets: Pair<Int, Int> = 0 to 0, cornerCutout: Boolean = false) { whenever(insetsProvider.getStatusBarContentInsetsForCurrentRotation()) - .thenReturn(insets.toAndroidPair()) + .thenReturn( + Insets.of( + /* left= */ insets.first, + /* top= */ 0, + /* right= */ insets.second, + /* bottom= */ 0 + ) + ) whenever(insetsProvider.currentRotationHasCornerCutout()).thenReturn(cornerCutout) } @@ -980,7 +985,7 @@ class ShadeHeaderControllerTest : SysuiTestCase() { ) .thenReturn(EMPTY_CHANGES) whenever(insetsProvider.getStatusBarContentInsetsForCurrentRotation()) - .thenReturn(Pair(0, 0).toAndroidPair()) + .thenReturn(Insets.NONE) whenever(insetsProvider.currentRotationHasCornerCutout()).thenReturn(false) setupCurrentInsets(null) } diff --git a/packages/SystemUI/tests/src/com/android/systemui/shared/clocks/ClockRegistryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shared/clocks/ClockRegistryTest.kt index 186ae6b0c4f0..ee94cbbcfd79 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/shared/clocks/ClockRegistryTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/shared/clocks/ClockRegistryTest.kt @@ -381,12 +381,15 @@ class ClockRegistryTest : SysuiTestCase() { } @Test - fun knownPluginAttached_clockAndListChanged_notLoaded() { - val lifecycle1 = FakeLifecycle("Metro", null).apply { - mComponentName = ComponentName("com.android.systemui.clocks.metro", "MetroClock") + fun knownPluginAttached_clockAndListChanged_loadedCurrent() { + val metroLifecycle = FakeLifecycle("Metro", null).apply { + mComponentName = ComponentName("com.android.systemui.clocks.metro", "Metro") } - val lifecycle2 = FakeLifecycle("BigNum", null).apply { - mComponentName = ComponentName("com.android.systemui.clocks.bignum", "BigNumClock") + val bignumLifecycle = FakeLifecycle("BigNum", null).apply { + mComponentName = ComponentName("com.android.systemui.clocks.bignum", "BigNum") + } + val calligraphyLifecycle = FakeLifecycle("Calligraphy", null).apply { + mComponentName = ComponentName("com.android.systemui.clocks.calligraphy", "Calligraphy") } var changeCallCount = 0 @@ -401,15 +404,21 @@ class ClockRegistryTest : SysuiTestCase() { assertEquals(1, changeCallCount) assertEquals(0, listChangeCallCount) - assertEquals(false, pluginListener.onPluginAttached(lifecycle1)) + assertEquals(false, pluginListener.onPluginAttached(metroLifecycle)) scheduler.runCurrent() assertEquals(1, changeCallCount) assertEquals(1, listChangeCallCount) - assertEquals(false, pluginListener.onPluginAttached(lifecycle2)) + assertEquals(false, pluginListener.onPluginAttached(bignumLifecycle)) scheduler.runCurrent() assertEquals(1, changeCallCount) assertEquals(2, listChangeCallCount) + + // This returns true, but doesn't trigger onCurrentClockChanged yet + assertEquals(true, pluginListener.onPluginAttached(calligraphyLifecycle)) + scheduler.runCurrent() + assertEquals(1, changeCallCount) + assertEquals(3, listChangeCallCount) } @Test diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/CommandQueueTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/CommandQueueTest.java index b04d5d3d44e4..260bef8e98ce 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/CommandQueueTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/CommandQueueTest.java @@ -503,10 +503,10 @@ public class CommandQueueTest extends SysuiTestCase { } @Test - public void testRequestWindowMagnificationConnection() { - mCommandQueue.requestWindowMagnificationConnection(true); + public void testRequestMagnificationConnection() { + mCommandQueue.requestMagnificationConnection(true); waitForIdleSync(); - verify(mCallbacks).requestWindowMagnificationConnection(true); + verify(mCallbacks).requestMagnificationConnection(true); } @Test diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/events/PrivacyDotViewControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/events/PrivacyDotViewControllerTest.kt new file mode 100644 index 000000000000..2951fc0232b3 --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/events/PrivacyDotViewControllerTest.kt @@ -0,0 +1,258 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.statusbar.events + +import android.graphics.Point +import android.graphics.Rect +import android.testing.AndroidTestingRunner +import android.testing.TestableLooper.RunWithLooper +import android.view.Display +import android.view.DisplayAdjustments +import android.view.View +import android.widget.FrameLayout +import androidx.test.filters.SmallTest +import com.android.systemui.SysuiTestCase +import com.android.systemui.statusbar.FakeStatusBarStateController +import com.android.systemui.statusbar.phone.StatusBarContentInsetsProvider +import com.android.systemui.statusbar.policy.FakeConfigurationController +import com.android.systemui.util.concurrency.DelayableExecutor +import com.android.systemui.util.leak.RotationUtils.ROTATION_LANDSCAPE +import com.android.systemui.util.leak.RotationUtils.ROTATION_NONE +import com.android.systemui.util.leak.RotationUtils.ROTATION_SEASCAPE +import com.android.systemui.util.leak.RotationUtils.ROTATION_UPSIDE_DOWN +import com.android.systemui.util.mockito.any +import com.android.systemui.util.mockito.mock +import com.android.systemui.util.mockito.whenever +import com.google.common.truth.Truth.assertThat +import java.util.concurrent.TimeUnit +import kotlinx.coroutines.test.TestScope +import org.junit.Test +import org.junit.runner.RunWith + +@SmallTest +@RunWith(AndroidTestingRunner::class) +@RunWithLooper +class PrivacyDotViewControllerTest : SysuiTestCase() { + + private val context = getContext().createDisplayContext(createMockDisplay()) + + private val testScope = TestScope() + private val executor = InstantExecutor() + private val statusBarStateController = FakeStatusBarStateController() + private val configurationController = FakeConfigurationController() + private val contentInsetsProvider = createMockContentInsetsProvider() + + private val topLeftView = initDotView() + private val topRightView = initDotView() + private val bottomLeftView = initDotView() + private val bottomRightView = initDotView() + + private val controller = + PrivacyDotViewController( + executor, + testScope.backgroundScope, + statusBarStateController, + configurationController, + contentInsetsProvider, + animationScheduler = mock<SystemStatusAnimationScheduler>(), + shadeInteractor = null + ) + .also { + it.setUiExecutor(executor) + it.initialize(topLeftView, topRightView, bottomLeftView, bottomRightView) + } + + @Test + fun topMargin_topLeftView_basedOnSeascapeArea() { + controller.initialize(topLeftView, topRightView, bottomLeftView, bottomRightView) + + assertThat(topLeftView.frameLayoutParams.topMargin) + .isEqualTo(CONTENT_AREA_ROTATION_SEASCAPE.top) + } + + @Test + fun topMargin_topRightView_basedOnPortraitArea() { + controller.initialize(topLeftView, topRightView, bottomLeftView, bottomRightView) + + assertThat(topRightView.frameLayoutParams.topMargin) + .isEqualTo(CONTENT_AREA_ROTATION_NONE.top) + } + + @Test + fun topMargin_bottomLeftView_basedOnUpsideDownArea() { + controller.initialize(topLeftView, topRightView, bottomLeftView, bottomRightView) + + assertThat(bottomLeftView.frameLayoutParams.topMargin) + .isEqualTo(CONTENT_AREA_ROTATION_UPSIDE_DOWN.top) + } + + @Test + fun topMargin_bottomRightView_basedOnLandscapeArea() { + controller.initialize(topLeftView, topRightView, bottomLeftView, bottomRightView) + + assertThat(bottomRightView.frameLayoutParams.topMargin) + .isEqualTo(CONTENT_AREA_ROTATION_LANDSCAPE.top) + } + + @Test + fun height_topLeftView_basedOnSeascapeAreaHeight() { + controller.initialize(topLeftView, topRightView, bottomLeftView, bottomRightView) + + assertThat(topLeftView.layoutParams.height) + .isEqualTo(CONTENT_AREA_ROTATION_SEASCAPE.height()) + } + + @Test + fun height_topRightView_basedOnPortraitAreaHeight() { + controller.initialize(topLeftView, topRightView, bottomLeftView, bottomRightView) + + assertThat(topRightView.layoutParams.height).isEqualTo(CONTENT_AREA_ROTATION_NONE.height()) + } + + @Test + fun height_bottomLeftView_basedOnUpsidedownAreaHeight() { + controller.initialize(topLeftView, topRightView, bottomLeftView, bottomRightView) + + assertThat(bottomLeftView.layoutParams.height) + .isEqualTo(CONTENT_AREA_ROTATION_UPSIDE_DOWN.height()) + } + + @Test + fun height_bottomRightView_basedOnLandscapeAreaHeight() { + controller.initialize(topLeftView, topRightView, bottomLeftView, bottomRightView) + + assertThat(bottomRightView.layoutParams.height) + .isEqualTo(CONTENT_AREA_ROTATION_LANDSCAPE.height()) + } + + @Test + fun width_topLeftView_ltr_basedOnDisplayHeightAndSeascapeArea() { + controller.initialize(topLeftView, topRightView, bottomLeftView, bottomRightView) + + assertThat(topLeftView.layoutParams.width) + .isEqualTo(DISPLAY_HEIGHT - CONTENT_AREA_ROTATION_SEASCAPE.right) + } + + @Test + fun width_topLeftView_rtl_basedOnPortraitArea() { + controller.initialize(topLeftView, topRightView, bottomLeftView, bottomRightView) + configurationController.notifyLayoutDirectionChanged(isRtl = true) + + assertThat(topLeftView.layoutParams.width).isEqualTo(CONTENT_AREA_ROTATION_NONE.left) + } + + @Test + fun width_topRightView_ltr_basedOnPortraitArea() { + controller.initialize(topLeftView, topRightView, bottomLeftView, bottomRightView) + + assertThat(topRightView.layoutParams.width) + .isEqualTo(DISPLAY_WIDTH - CONTENT_AREA_ROTATION_NONE.right) + } + + @Test + fun width_topRightView_rtl_basedOnLandscapeArea() { + controller.initialize(topLeftView, topRightView, bottomLeftView, bottomRightView) + configurationController.notifyLayoutDirectionChanged(isRtl = true) + + assertThat(topRightView.layoutParams.width).isEqualTo(CONTENT_AREA_ROTATION_LANDSCAPE.left) + } + + @Test + fun width_bottomRightView_ltr_basedOnDisplayHeightAndLandscapeArea() { + controller.initialize(topLeftView, topRightView, bottomLeftView, bottomRightView) + + assertThat(bottomRightView.layoutParams.width) + .isEqualTo(DISPLAY_HEIGHT - CONTENT_AREA_ROTATION_LANDSCAPE.right) + } + + @Test + fun width_bottomRightView_rtl_basedOnUpsideDown() { + controller.initialize(topLeftView, topRightView, bottomLeftView, bottomRightView) + configurationController.notifyLayoutDirectionChanged(isRtl = true) + + assertThat(bottomRightView.layoutParams.width) + .isEqualTo(CONTENT_AREA_ROTATION_UPSIDE_DOWN.left) + } + + @Test + fun width_bottomLeftView_ltr_basedOnDisplayWidthAndUpsideDownArea() { + controller.initialize(topLeftView, topRightView, bottomLeftView, bottomRightView) + + assertThat(bottomLeftView.layoutParams.width) + .isEqualTo(DISPLAY_WIDTH - CONTENT_AREA_ROTATION_UPSIDE_DOWN.right) + } + + @Test + fun width_bottomLeftView_rtl_basedOnSeascapeArea() { + controller.initialize(topLeftView, topRightView, bottomLeftView, bottomRightView) + configurationController.notifyLayoutDirectionChanged(isRtl = true) + + assertThat(bottomLeftView.layoutParams.width).isEqualTo(CONTENT_AREA_ROTATION_SEASCAPE.left) + } + + private fun initDotView(): View = + View(context).also { + it.layoutParams = FrameLayout.LayoutParams(/* width = */ 0, /* height = */ 0) + } +} + +private const val DISPLAY_WIDTH = 1234 +private const val DISPLAY_HEIGHT = 2345 +private val CONTENT_AREA_ROTATION_SEASCAPE = Rect(left = 10, top = 40, right = 990, bottom = 100) +private val CONTENT_AREA_ROTATION_NONE = Rect(left = 20, top = 30, right = 980, bottom = 100) +private val CONTENT_AREA_ROTATION_LANDSCAPE = Rect(left = 30, top = 20, right = 970, bottom = 100) +private val CONTENT_AREA_ROTATION_UPSIDE_DOWN = Rect(left = 40, top = 10, right = 960, bottom = 100) + +private class InstantExecutor : DelayableExecutor { + override fun execute(runnable: Runnable) { + runnable.run() + } + + override fun executeDelayed(runnable: Runnable, delay: Long, unit: TimeUnit) = + runnable.apply { run() } + + override fun executeAtTime(runnable: Runnable, uptimeMillis: Long, unit: TimeUnit) = + runnable.apply { run() } +} + +private fun Rect(left: Int, top: Int, right: Int, bottom: Int) = Rect(left, top, right, bottom) + +private val View.frameLayoutParams + get() = layoutParams as FrameLayout.LayoutParams + +private fun createMockDisplay() = + mock<Display>().also { display -> + whenever(display.getRealSize(any(Point::class.java))).thenAnswer { invocation -> + val output = invocation.arguments[0] as Point + output.x = DISPLAY_WIDTH + output.y = DISPLAY_HEIGHT + return@thenAnswer Unit + } + whenever(display.displayAdjustments).thenReturn(DisplayAdjustments()) + } + +private fun createMockContentInsetsProvider() = + mock<StatusBarContentInsetsProvider>().also { + whenever(it.getStatusBarContentAreaForRotation(ROTATION_SEASCAPE)) + .thenReturn(CONTENT_AREA_ROTATION_SEASCAPE) + whenever(it.getStatusBarContentAreaForRotation(ROTATION_NONE)) + .thenReturn(CONTENT_AREA_ROTATION_NONE) + whenever(it.getStatusBarContentAreaForRotation(ROTATION_LANDSCAPE)) + .thenReturn(CONTENT_AREA_ROTATION_LANDSCAPE) + whenever(it.getStatusBarContentAreaForRotation(ROTATION_UPSIDE_DOWN)) + .thenReturn(CONTENT_AREA_ROTATION_UPSIDE_DOWN) + } diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/events/SystemEventChipAnimationControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/events/SystemEventChipAnimationControllerTest.kt index df257ab113b3..8be2ef008039 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/events/SystemEventChipAnimationControllerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/events/SystemEventChipAnimationControllerTest.kt @@ -17,10 +17,10 @@ package com.android.systemui.statusbar.events import android.content.Context +import android.graphics.Insets import android.graphics.Rect import android.testing.AndroidTestingRunner import android.testing.TestableLooper -import android.util.Pair import android.view.Gravity import android.view.View import android.widget.FrameLayout @@ -79,7 +79,14 @@ class SystemEventChipAnimationControllerTest : SysuiTestCase() { } whenever(insetsProvider.getStatusBarContentInsetsForCurrentRotation()) - .thenReturn(Pair(insets, insets)) + .thenReturn( + Insets.of( + /* left= */ insets, + /* top= */ insets, + /* right= */ insets, + /* bottom= */ 0 + ) + ) whenever(insetsProvider.getStatusBarContentAreaForCurrentRotation()) .thenReturn(portraitArea) @@ -105,18 +112,18 @@ class SystemEventChipAnimationControllerTest : SysuiTestCase() { controller.prepareChipAnimation(viewCreator) val chipRect = controller.chipBounds - // SB area = 10, 0, 990, 100 + // SB area = 10, 10, 990, 100 // chip size = 0, 0, 100, 50 - assertThat(chipRect).isEqualTo(Rect(890, 25, 990, 75)) + assertThat(chipRect).isEqualTo(Rect(890, 30, 990, 80)) } @Test fun prepareChipAnimation_rotation_repositionsChip() { controller.prepareChipAnimation(viewCreator) - // Chip has been prepared, and is located at (890, 25, 990, 75) + // Chip has been prepared, and is located at (890, 30, 990, 75) // Rotation should put it into its landscape location: - // SB area = 10, 0, 1990, 80 + // SB area = 10, 10, 1990, 80 // chip size = 0, 0, 100, 50 whenever(insetsProvider.getStatusBarContentAreaForCurrentRotation()) @@ -124,7 +131,7 @@ class SystemEventChipAnimationControllerTest : SysuiTestCase() { getInsetsListener().onStatusBarContentInsetsChanged() val chipRect = controller.chipBounds - assertThat(chipRect).isEqualTo(Rect(1890, 15, 1990, 65)) + assertThat(chipRect).isEqualTo(Rect(1890, 20, 1990, 70)) } /** regression test for (b/289378932) */ @@ -162,7 +169,7 @@ class SystemEventChipAnimationControllerTest : SysuiTestCase() { // THEN it still aligns the chip to the content area provided by the insets provider val chipRect = controller.chipBounds - assertThat(chipRect).isEqualTo(Rect(890, 25, 990, 75)) + assertThat(chipRect).isEqualTo(Rect(890, 30, 990, 80)) } private class TestView(context: Context) : View(context), BackgroundAnimatableView { @@ -185,9 +192,9 @@ class SystemEventChipAnimationControllerTest : SysuiTestCase() { } companion object { - private val portraitArea = Rect(10, 0, 990, 100) - private val landscapeArea = Rect(10, 0, 1990, 80) - private val fullScreenSb = Rect(10, 0, 990, 2000) + private val portraitArea = Rect(10, 10, 990, 100) + private val landscapeArea = Rect(10, 10, 1990, 80) + private val fullScreenSb = Rect(10, 10, 990, 2000) // 10px insets on both sides private const val insets = 10 diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/events/SystemStatusAnimationSchedulerImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/events/SystemStatusAnimationSchedulerImplTest.kt index 5f01b5a56e4c..875fe58e5b50 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/events/SystemStatusAnimationSchedulerImplTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/events/SystemStatusAnimationSchedulerImplTest.kt @@ -16,6 +16,7 @@ package com.android.systemui.statusbar.events +import android.graphics.Insets import android.graphics.Rect import android.os.Process import android.testing.AndroidTestingRunner @@ -91,15 +92,19 @@ class SystemStatusAnimationSchedulerImplTest : SysuiTestCase() { // StatusBarContentInsetProvider is mocked. Ensure that it returns some mocked values. whenever(statusBarContentInsetProvider.getStatusBarContentInsetsForCurrentRotation()) - .thenReturn(android.util.Pair(10, 10)) + .thenReturn( + Insets.of(/* left = */ 10, /* top = */ 10, /* right = */ 10, /* bottom = */ 0) + ) whenever(statusBarContentInsetProvider.getStatusBarContentAreaForCurrentRotation()) - .thenReturn(Rect(10, 0, 990, 100)) + .thenReturn( + Rect(/* left = */ 10, /* top = */ 10, /* right = */ 990, /* bottom = */ 100) + ) // StatusBarWindowController is mocked. The addViewToWindow function needs to be mocked to // ensure that the chip view is added to a parent view whenever(statusBarWindowController.addViewToWindow(any(), any())).then { val statusbarFake = FrameLayout(mContext) - statusbarFake.layout(0, 0, 1000, 100) + statusbarFake.layout(/* l = */ 0, /* t = */ 0, /* r = */ 1000, /* b = */ 100) statusbarFake.addView( it.arguments[0] as View, it.arguments[1] as FrameLayout.LayoutParams diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewTest.kt index 9c10131acae2..65d71f8b4540 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewTest.kt @@ -16,33 +16,47 @@ package com.android.systemui.statusbar.phone +import android.content.res.Configuration +import android.graphics.Insets +import android.graphics.Rect +import android.testing.TestableLooper.RunWithLooper +import android.view.DisplayCutout +import android.view.DisplayShape +import android.view.LayoutInflater import android.view.MotionEvent -import android.view.ViewGroup +import android.view.PrivacyIndicatorBounds +import android.view.RoundedCorners +import android.view.WindowInsets +import android.widget.FrameLayout import androidx.test.filters.SmallTest import com.android.systemui.Gefingerpoken import com.android.systemui.SysuiTestCase -import com.android.systemui.shade.ShadeViewController +import com.android.systemui.plugins.DarkIconDispatcher +import com.android.systemui.res.R +import com.android.systemui.util.mockito.mock +import com.android.systemui.util.mockito.whenever import com.google.common.truth.Truth.assertThat import org.junit.Before import org.junit.Test -import org.mockito.Mock -import org.mockito.MockitoAnnotations +import org.mockito.Mockito.spy @SmallTest +@RunWithLooper(setAsMainLooper = true) class PhoneStatusBarViewTest : SysuiTestCase() { - @Mock - private lateinit var shadeViewController: ShadeViewController - @Mock - private lateinit var panelView: ViewGroup - private lateinit var view: PhoneStatusBarView + private val contentInsetsProvider = mock<StatusBarContentInsetsProvider>() + @Before fun setUp() { - MockitoAnnotations.initMocks(this) - - view = PhoneStatusBarView(mContext, null) + mDependency.injectTestDependency( + StatusBarContentInsetsProvider::class.java, + contentInsetsProvider + ) + mDependency.injectTestDependency(DarkIconDispatcher::class.java, mock<DarkIconDispatcher>()) + view = spy(createStatusBarView()) + whenever(view.rootWindowInsets).thenReturn(emptyWindowInsets()) } @Test @@ -95,6 +109,48 @@ class PhoneStatusBarViewTest : SysuiTestCase() { // No assert needed, just testing no crash } + @Test + fun onAttachedToWindow_updatesLeftTopRightPaddingsBasedOnInsets() { + val insets = Insets.of(/* left = */ 10, /* top = */ 20, /* right = */ 30, /* bottom = */ 40) + whenever(contentInsetsProvider.getStatusBarContentInsetsForCurrentRotation()) + .thenReturn(insets) + + view.onAttachedToWindow() + + assertThat(view.paddingLeft).isEqualTo(insets.left) + assertThat(view.paddingTop).isEqualTo(insets.top) + assertThat(view.paddingRight).isEqualTo(insets.right) + assertThat(view.paddingBottom).isEqualTo(0) + } + + @Test + fun onConfigurationChanged_updatesLeftTopRightPaddingsBasedOnInsets() { + val insets = Insets.of(/* left = */ 40, /* top = */ 30, /* right = */ 20, /* bottom = */ 10) + whenever(contentInsetsProvider.getStatusBarContentInsetsForCurrentRotation()) + .thenReturn(insets) + + view.onConfigurationChanged(Configuration()) + + assertThat(view.paddingLeft).isEqualTo(insets.left) + assertThat(view.paddingTop).isEqualTo(insets.top) + assertThat(view.paddingRight).isEqualTo(insets.right) + assertThat(view.paddingBottom).isEqualTo(0) + } + + @Test + fun onApplyWindowInsets_updatesLeftTopRightPaddingsBasedOnInsets() { + val insets = Insets.of(/* left = */ 90, /* top = */ 10, /* right = */ 45, /* bottom = */ 50) + whenever(contentInsetsProvider.getStatusBarContentInsetsForCurrentRotation()) + .thenReturn(insets) + + view.onApplyWindowInsets(WindowInsets(Rect())) + + assertThat(view.paddingLeft).isEqualTo(insets.left) + assertThat(view.paddingTop).isEqualTo(insets.top) + assertThat(view.paddingRight).isEqualTo(insets.right) + assertThat(view.paddingBottom).isEqualTo(0) + } + private class TestTouchEventHandler : Gefingerpoken { var lastInterceptEvent: MotionEvent? = null var lastEvent: MotionEvent? = null @@ -110,4 +166,28 @@ class PhoneStatusBarViewTest : SysuiTestCase() { return handleTouchReturnValue } } + + private fun createStatusBarView() = + LayoutInflater.from(context) + .inflate( + R.layout.status_bar, + /* root= */ FrameLayout(context), + /* attachToRoot = */ false + ) as PhoneStatusBarView + + private fun emptyWindowInsets() = + WindowInsets( + /* typeInsetsMap = */ arrayOf(), + /* typeMaxInsetsMap = */ arrayOf(), + /* typeVisibilityMap = */ booleanArrayOf(), + /* isRound = */ false, + /* forceConsumingTypes = */ 0, + /* suppressScrimTypes = */ 0, + /* displayCutout = */ DisplayCutout.NO_CUTOUT, + /* roundedCorners = */ RoundedCorners.NO_ROUNDED_CORNERS, + /* privacyIndicatorBounds = */ PrivacyIndicatorBounds(), + /* displayShape = */ DisplayShape.NONE, + /* compatInsetsTypes = */ 0, + /* compatIgnoreVisibility = */ false + ) } diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarContentInsetsProviderTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarContentInsetsProviderTest.kt index 210c5ab28c42..5c5624692f07 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarContentInsetsProviderTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarContentInsetsProviderTest.kt @@ -31,6 +31,7 @@ import com.android.systemui.util.leak.RotationUtils.ROTATION_NONE import com.android.systemui.util.leak.RotationUtils.ROTATION_SEASCAPE import com.android.systemui.util.leak.RotationUtils.ROTATION_UPSIDE_DOWN import com.android.systemui.util.leak.RotationUtils.Rotation +import com.android.systemui.util.mockito.whenever import com.google.common.truth.Truth.assertThat import junit.framework.Assert.assertTrue import org.junit.Before @@ -62,7 +63,6 @@ class StatusBarContentInsetsProviderTest : SysuiTestCase() { `when`(contextMock.createConfigurationContext(any())).thenAnswer { context.createConfigurationContext(it.arguments[0] as Configuration) } - configurationController = ConfigurationControllerImpl(contextMock) } @@ -76,6 +76,7 @@ class StatusBarContentInsetsProviderTest : SysuiTestCase() { val currentRotation = ROTATION_NONE val chipWidth = 30 val dotWidth = 10 + val statusBarContentHeight = 15 var isRtl = false var targetRotation = ROTATION_NONE @@ -88,7 +89,9 @@ class StatusBarContentInsetsProviderTest : SysuiTestCase() { minLeftPadding, minRightPadding, isRtl, - dotWidth) + dotWidth, + BOTTOM_ALIGNED_MARGIN_NONE, + statusBarContentHeight) var chipBounds = getPrivacyChipBoundingRectForInsets(bounds, dotWidth, chipWidth, isRtl) /* 1080 - 20 (rounded corner) - 30 (chip), @@ -119,7 +122,9 @@ class StatusBarContentInsetsProviderTest : SysuiTestCase() { minLeftPadding, minRightPadding, isRtl, - dotWidth) + dotWidth, + BOTTOM_ALIGNED_MARGIN_NONE, + statusBarContentHeight) chipBounds = getPrivacyChipBoundingRectForInsets(bounds, dotWidth, chipWidth, isRtl) /* 2160 - 20 (rounded corner) - 30 (chip), @@ -141,6 +146,20 @@ class StatusBarContentInsetsProviderTest : SysuiTestCase() { } @Test + fun privacyChipBoundingRectForInsets_usesTopInset() { + val chipWidth = 30 + val dotWidth = 10 + val isRtl = false + val contentRect = + Rect(/* left = */ 0, /* top = */ 10, /* right = */ 1000, /* bottom = */ 100) + + val chipBounds = + getPrivacyChipBoundingRectForInsets(contentRect, dotWidth, chipWidth, isRtl) + + assertThat(chipBounds.top).isEqualTo(contentRect.top) + } + + @Test fun testCalculateInsetsForRotationWithRotatedResources_topLeftCutout() { // GIVEN a device in portrait mode with width < height and a display cutout in the top-left val screenBounds = Rect(0, 0, 1080, 2160) @@ -152,6 +171,7 @@ class StatusBarContentInsetsProviderTest : SysuiTestCase() { val currentRotation = ROTATION_NONE val isRtl = false val dotWidth = 10 + val statusBarContentHeight = 15 `when`(dc.boundingRects).thenReturn(listOf(dcBounds)) @@ -172,7 +192,9 @@ class StatusBarContentInsetsProviderTest : SysuiTestCase() { minLeftPadding, minRightPadding, isRtl, - dotWidth) + dotWidth, + BOTTOM_ALIGNED_MARGIN_NONE, + statusBarContentHeight) assertRects(expectedBounds, bounds, currentRotation, targetRotation) @@ -191,7 +213,9 @@ class StatusBarContentInsetsProviderTest : SysuiTestCase() { minLeftPadding, minRightPadding, isRtl, - dotWidth) + dotWidth, + BOTTOM_ALIGNED_MARGIN_NONE, + statusBarContentHeight) assertRects(expectedBounds, bounds, currentRotation, targetRotation) @@ -212,7 +236,9 @@ class StatusBarContentInsetsProviderTest : SysuiTestCase() { minLeftPadding, minRightPadding, isRtl, - dotWidth) + dotWidth, + BOTTOM_ALIGNED_MARGIN_NONE, + statusBarContentHeight) assertRects(expectedBounds, bounds, currentRotation, targetRotation) @@ -232,12 +258,60 @@ class StatusBarContentInsetsProviderTest : SysuiTestCase() { minLeftPadding, minRightPadding, isRtl, - dotWidth) + dotWidth, + BOTTOM_ALIGNED_MARGIN_NONE, + statusBarContentHeight) assertRects(expectedBounds, bounds, currentRotation, targetRotation) } @Test + fun calculateInsetsForRotationWithRotatedResources_bottomAlignedMarginDisabled_noTopInset() { + whenever(dc.boundingRects).thenReturn(emptyList()) + + val bounds = calculateInsetsForRotationWithRotatedResources( + currentRotation = ROTATION_NONE, + targetRotation = ROTATION_NONE, + displayCutout = dc, + maxBounds = Rect(0, 0, 1080, 2160), + statusBarHeight = 100, + minLeft = 0, + minRight = 0, + isRtl = false, + dotWidth = 10, + bottomAlignedMargin = BOTTOM_ALIGNED_MARGIN_NONE, + statusBarContentHeight = 15) + + assertThat(bounds.top).isEqualTo(0) + } + + @Test + fun calculateInsetsForRotationWithRotatedResources_bottomAlignedMargin_topBasedOnMargin() { + whenever(dc.boundingRects).thenReturn(emptyList()) + + val bounds = calculateInsetsForRotationWithRotatedResources( + currentRotation = ROTATION_NONE, + targetRotation = ROTATION_NONE, + displayCutout = dc, + maxBounds = Rect(0, 0, 1080, 2160), + statusBarHeight = 100, + minLeft = 0, + minRight = 0, + isRtl = false, + dotWidth = 10, + bottomAlignedMargin = 5, + statusBarContentHeight = 15) + + // Content in the status bar is centered vertically. To achieve the bottom margin we want, + // we need to "shrink" the height of the status bar until the centered content has the + // desired bottom margin. To achieve this shrinking, we use top inset/padding. + // "New" SB height = bottom margin * 2 + content height + // Top inset = SB height - "New" SB height + val expectedTopInset = 75 + assertThat(bounds.top).isEqualTo(expectedTopInset) + } + + @Test fun testCalculateInsetsForRotationWithRotatedResources_nonCornerCutout() { // GIVEN phone in portrait mode, where width < height and the cutout is not in the corner // the assumption here is that if the cutout does NOT touch the corner then we have room to @@ -253,6 +327,7 @@ class StatusBarContentInsetsProviderTest : SysuiTestCase() { val currentRotation = ROTATION_NONE val isRtl = false val dotWidth = 10 + val statusBarContentHeight = 15 `when`(dc.boundingRects).thenReturn(listOf(dcBounds)) @@ -273,7 +348,9 @@ class StatusBarContentInsetsProviderTest : SysuiTestCase() { minLeftPadding, minRightPadding, isRtl, - dotWidth) + dotWidth, + BOTTOM_ALIGNED_MARGIN_NONE, + statusBarContentHeight) assertRects(expectedBounds, bounds, currentRotation, targetRotation) @@ -292,7 +369,9 @@ class StatusBarContentInsetsProviderTest : SysuiTestCase() { minLeftPadding, minRightPadding, isRtl, - dotWidth) + dotWidth, + BOTTOM_ALIGNED_MARGIN_NONE, + statusBarContentHeight) assertRects(expectedBounds, bounds, currentRotation, targetRotation) @@ -311,7 +390,9 @@ class StatusBarContentInsetsProviderTest : SysuiTestCase() { minLeftPadding, minRightPadding, isRtl, - dotWidth) + dotWidth, + BOTTOM_ALIGNED_MARGIN_NONE, + statusBarContentHeight) assertRects(expectedBounds, bounds, currentRotation, targetRotation) @@ -330,7 +411,9 @@ class StatusBarContentInsetsProviderTest : SysuiTestCase() { minLeftPadding, minRightPadding, isRtl, - dotWidth) + dotWidth, + BOTTOM_ALIGNED_MARGIN_NONE, + statusBarContentHeight) assertRects(expectedBounds, bounds, currentRotation, targetRotation) } @@ -346,6 +429,7 @@ class StatusBarContentInsetsProviderTest : SysuiTestCase() { val sbHeightLandscape = 60 val isRtl = false val dotWidth = 10 + val statusBarContentHeight = 15 // THEN content insets should only use rounded corner padding var targetRotation = ROTATION_NONE @@ -363,7 +447,9 @@ class StatusBarContentInsetsProviderTest : SysuiTestCase() { minLeftPadding, minRightPadding, isRtl, - dotWidth) + dotWidth, + BOTTOM_ALIGNED_MARGIN_NONE, + statusBarContentHeight) assertRects(expectedBounds, bounds, currentRotation, targetRotation) targetRotation = ROTATION_LANDSCAPE @@ -381,7 +467,9 @@ class StatusBarContentInsetsProviderTest : SysuiTestCase() { minLeftPadding, minRightPadding, isRtl, - dotWidth) + dotWidth, + BOTTOM_ALIGNED_MARGIN_NONE, + statusBarContentHeight) assertRects(expectedBounds, bounds, currentRotation, targetRotation) targetRotation = ROTATION_UPSIDE_DOWN @@ -399,7 +487,9 @@ class StatusBarContentInsetsProviderTest : SysuiTestCase() { minLeftPadding, minRightPadding, isRtl, - dotWidth) + dotWidth, + BOTTOM_ALIGNED_MARGIN_NONE, + statusBarContentHeight) assertRects(expectedBounds, bounds, currentRotation, targetRotation) targetRotation = ROTATION_LANDSCAPE @@ -417,7 +507,9 @@ class StatusBarContentInsetsProviderTest : SysuiTestCase() { minLeftPadding, minRightPadding, isRtl, - dotWidth) + dotWidth, + BOTTOM_ALIGNED_MARGIN_NONE, + statusBarContentHeight) assertRects(expectedBounds, bounds, currentRotation, targetRotation) } @@ -433,17 +525,18 @@ class StatusBarContentInsetsProviderTest : SysuiTestCase() { val currentRotation = ROTATION_NONE val isRtl = false val dotWidth = 10 + val statusBarContentHeight = 15 `when`(dc.boundingRects).thenReturn(listOf(dcBounds)) // THEN left should be set to the display cutout width, and right should use the minRight - var targetRotation = ROTATION_NONE - var expectedBounds = Rect(dcBounds.right, + val targetRotation = ROTATION_NONE + val expectedBounds = Rect(dcBounds.right, 0, screenBounds.right - minRightPadding, sbHeightPortrait) - var bounds = calculateInsetsForRotationWithRotatedResources( + val bounds = calculateInsetsForRotationWithRotatedResources( currentRotation, targetRotation, dc, @@ -452,7 +545,9 @@ class StatusBarContentInsetsProviderTest : SysuiTestCase() { minLeftPadding, minRightPadding, isRtl, - dotWidth) + dotWidth, + BOTTOM_ALIGNED_MARGIN_NONE, + statusBarContentHeight) assertRects(expectedBounds, bounds, currentRotation, targetRotation) } @@ -577,4 +672,8 @@ class StatusBarContentInsetsProviderTest : SysuiTestCase() { " expected=$expected actual=$actual", expected.equals(actual)) } + + companion object { + private const val BOTTOM_ALIGNED_MARGIN_NONE = -1 + } } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/policy/FakeConfigurationController.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/policy/FakeConfigurationController.kt index 23477d86807c..c51de334c8ca 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/policy/FakeConfigurationController.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/policy/FakeConfigurationController.kt @@ -11,6 +11,7 @@ import javax.inject.Inject class FakeConfigurationController @Inject constructor() : ConfigurationController { private var listeners = mutableListOf<ConfigurationController.ConfigurationListener>() + private var isRtl = false override fun addCallback(listener: ConfigurationController.ConfigurationListener) { listeners += listener @@ -36,7 +37,12 @@ class FakeConfigurationController @Inject constructor() : ConfigurationControlle onConfigurationChanged(newConfiguration = null) } - override fun isLayoutRtl(): Boolean = false + fun notifyLayoutDirectionChanged(isRtl: Boolean) { + this.isRtl = isRtl + listeners.forEach { it.onLayoutDirectionChanged(isRtl) } + } + + override fun isLayoutRtl(): Boolean = isRtl } @Module diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java index 2eecb4d3a86c..5bffe80a5c69 100644 --- a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java +++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java @@ -24,7 +24,7 @@ import static android.accessibilityservice.AccessibilityTrace.FLAGS_FINGERPRINT; import static android.accessibilityservice.AccessibilityTrace.FLAGS_INPUT_FILTER; import static android.accessibilityservice.AccessibilityTrace.FLAGS_PACKAGE_BROADCAST_RECEIVER; import static android.accessibilityservice.AccessibilityTrace.FLAGS_USER_BROADCAST_RECEIVER; -import static android.accessibilityservice.AccessibilityTrace.FLAGS_WINDOW_MAGNIFICATION_CONNECTION; +import static android.accessibilityservice.AccessibilityTrace.FLAGS_MAGNIFICATION_CONNECTION; import static android.accessibilityservice.AccessibilityTrace.FLAGS_WINDOW_MANAGER_INTERNAL; import static android.companion.virtual.VirtualDeviceManager.ACTION_VIRTUAL_DEVICE_REMOVED; import static android.companion.virtual.VirtualDeviceManager.EXTRA_VIRTUAL_DEVICE_ID; @@ -135,7 +135,7 @@ import android.view.accessibility.IAccessibilityInteractionConnection; import android.view.accessibility.IAccessibilityInteractionConnectionCallback; import android.view.accessibility.IAccessibilityManager; import android.view.accessibility.IAccessibilityManagerClient; -import android.view.accessibility.IWindowMagnificationConnection; +import android.view.accessibility.IMagnificationConnection; import android.view.inputmethod.EditorInfo; import com.android.internal.R; @@ -3431,7 +3431,7 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub } } - private void updateWindowMagnificationConnectionIfNeeded(AccessibilityUserState userState) { + private void updateMagnificationConnectionIfNeeded(AccessibilityUserState userState) { if (!mMagnificationController.supportWindowMagnification()) { return; } @@ -4110,12 +4110,12 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub } @Override - public void setWindowMagnificationConnection( - IWindowMagnificationConnection connection) throws RemoteException { + public void setMagnificationConnection( + IMagnificationConnection connection) throws RemoteException { if (mTraceManager.isA11yTracingEnabledForTypes( - FLAGS_ACCESSIBILITY_MANAGER | FLAGS_WINDOW_MAGNIFICATION_CONNECTION)) { - mTraceManager.logTrace(LOG_TAG + ".setWindowMagnificationConnection", - FLAGS_ACCESSIBILITY_MANAGER | FLAGS_WINDOW_MAGNIFICATION_CONNECTION, + FLAGS_ACCESSIBILITY_MANAGER | FLAGS_MAGNIFICATION_CONNECTION)) { + mTraceManager.logTrace(LOG_TAG + ".setMagnificationConnection", + FLAGS_ACCESSIBILITY_MANAGER | FLAGS_MAGNIFICATION_CONNECTION, "connection=" + connection); } @@ -4422,7 +4422,7 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub pw.append("visibleBgUserIds=").append(mVisibleBgUserIds.toString()); pw.println(); } - pw.append("hasWindowMagnificationConnection=").append( + pw.append("hasMagnificationConnection=").append( String.valueOf(getMagnificationConnectionManager().isConnected())); pw.println(); mMagnificationProcessor.dump(pw, getValidDisplayList()); @@ -5132,7 +5132,7 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub updateMagnificationModeChangeSettingsLocked(userState, displayId); } } - updateWindowMagnificationConnectionIfNeeded(userState); + updateMagnificationConnectionIfNeeded(userState); // Remove magnification button UI when the magnification capability is not all mode or // magnification is disabled. if (!(userState.isMagnificationSingleFingerTripleTapEnabledLocked() diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityTraceManager.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityTraceManager.java index 6114213ef58b..307b555b3b99 100644 --- a/services/accessibility/java/com/android/server/accessibility/AccessibilityTraceManager.java +++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityTraceManager.java @@ -223,7 +223,7 @@ public class AccessibilityTraceManager implements AccessibilityTrace { pw.println(" IAccessibilityInteractionConnection"); pw.println(" IAccessibilityInteractionConnectionCallback"); pw.println(" IRemoteMagnificationAnimationCallback"); - pw.println(" IWindowMagnificationConnection"); + pw.println(" IMagnificationConnection"); pw.println(" IWindowMagnificationConnectionCallback"); pw.println(" WindowManagerInternal"); pw.println(" WindowsForAccessibilityCallback"); diff --git a/services/accessibility/java/com/android/server/accessibility/magnification/MagnificationConnectionManager.java b/services/accessibility/java/com/android/server/accessibility/magnification/MagnificationConnectionManager.java index 5a3c070819bd..eff6488bc032 100644 --- a/services/accessibility/java/com/android/server/accessibility/magnification/MagnificationConnectionManager.java +++ b/services/accessibility/java/com/android/server/accessibility/magnification/MagnificationConnectionManager.java @@ -16,7 +16,7 @@ package com.android.server.accessibility.magnification; -import static android.accessibilityservice.AccessibilityTrace.FLAGS_WINDOW_MAGNIFICATION_CONNECTION; +import static android.accessibilityservice.AccessibilityTrace.FLAGS_MAGNIFICATION_CONNECTION; import static android.accessibilityservice.AccessibilityTrace.FLAGS_WINDOW_MAGNIFICATION_CONNECTION_CALLBACK; import static android.view.accessibility.MagnificationAnimationCallback.STUB_ANIMATION_CALLBACK; @@ -42,7 +42,7 @@ import android.util.Slog; import android.util.SparseArray; import android.util.SparseBooleanArray; import android.view.MotionEvent; -import android.view.accessibility.IWindowMagnificationConnection; +import android.view.accessibility.IMagnificationConnection; import android.view.accessibility.IWindowMagnificationConnectionCallback; import android.view.accessibility.MagnificationAnimationCallback; @@ -61,8 +61,8 @@ import java.util.concurrent.atomic.AtomicLongFieldUpdater; /** * A class to manipulate magnification through {@link MagnificationConnectionWrapper} - * create by {@link #setConnection(IWindowMagnificationConnection)}. To set the connection with - * SysUI, call {@code StatusBarManagerInternal#requestWindowMagnificationConnection(boolean)}. + * create by {@link #setConnection(IMagnificationConnection)}. To set the connection with + * SysUI, call {@code StatusBarManagerInternal#requestMagnificationConnection(boolean)}. * The applied magnification scale is constrained by * {@link MagnificationScaleProvider#constrainScale(float)} */ @@ -93,13 +93,13 @@ public class MagnificationConnectionManager implements }) public @interface WindowPosition {} - /** Window magnification connection is connecting. */ + /** Magnification connection is connecting. */ private static final int CONNECTING = 0; - /** Window magnification connection is connected. */ + /** Magnification connection is connected. */ private static final int CONNECTED = 1; - /** Window magnification connection is disconnecting. */ + /** Magnification connection is disconnecting. */ private static final int DISCONNECTING = 2; - /** Window magnification connection is disconnected. */ + /** Magnification connection is disconnected. */ private static final int DISCONNECTED = 3; @Retention(RetentionPolicy.SOURCE) @@ -195,7 +195,7 @@ public class MagnificationConnectionManager implements void onSourceBoundsChanged(int displayId, Rect bounds); /** - * Called from {@link IWindowMagnificationConnection} to request changing the magnification + * Called from {@link IMagnificationConnection} to request changing the magnification * mode on the given display. * * @param displayId the logical display id @@ -218,11 +218,11 @@ public class MagnificationConnectionManager implements } /** - * Sets {@link IWindowMagnificationConnection}. + * Sets {@link IMagnificationConnection}. * - * @param connection {@link IWindowMagnificationConnection} + * @param connection {@link IMagnificationConnection} */ - public void setConnection(@Nullable IWindowMagnificationConnection connection) { + public void setConnection(@Nullable IMagnificationConnection connection) { if (DBG) { Slog.d(TAG, "setConnection :" + connection + ", mConnectionState=" + connectionStateToString(mConnectionState)); @@ -266,7 +266,7 @@ public class MagnificationConnectionManager implements } /** - * @return {@code true} if {@link IWindowMagnificationConnection} is available + * @return {@code true} if {@link IMagnificationConnection} is available */ public boolean isConnected() { synchronized (mLock) { @@ -275,21 +275,21 @@ public class MagnificationConnectionManager implements } /** - * Requests {@link IWindowMagnificationConnection} through - * {@link StatusBarManagerInternal#requestWindowMagnificationConnection(boolean)} and + * Requests {@link IMagnificationConnection} through + * {@link StatusBarManagerInternal#requestMagnificationConnection(boolean)} and * destroys all window magnifications if necessary. * * @param connect {@code true} if needs connection, otherwise set the connection to null and * destroy all window magnifications. - * @return {@code true} if {@link IWindowMagnificationConnection} state is going to change. + * @return {@code true} if {@link IMagnificationConnection} state is going to change. */ public boolean requestConnection(boolean connect) { if (DBG) { Slog.d(TAG, "requestConnection :" + connect); } - if (mTrace.isA11yTracingEnabledForTypes(FLAGS_WINDOW_MAGNIFICATION_CONNECTION)) { - mTrace.logTrace(TAG + ".requestWindowMagnificationConnection", - FLAGS_WINDOW_MAGNIFICATION_CONNECTION, "connect=" + connect); + if (mTrace.isA11yTracingEnabledForTypes(FLAGS_MAGNIFICATION_CONNECTION)) { + mTrace.logTrace(TAG + ".requestMagnificationConnection", + FLAGS_MAGNIFICATION_CONNECTION, "connect=" + connect); } synchronized (mLock) { if ((connect && (mConnectionState == CONNECTED || mConnectionState == CONNECTING)) @@ -329,7 +329,7 @@ public class MagnificationConnectionManager implements final StatusBarManagerInternal service = LocalServices.getService( StatusBarManagerInternal.class); if (service != null) { - return service.requestWindowMagnificationConnection(connect); + return service.requestMagnificationConnection(connect); } } finally { Binder.restoreCallingIdentity(identity); diff --git a/services/accessibility/java/com/android/server/accessibility/magnification/MagnificationConnectionWrapper.java b/services/accessibility/java/com/android/server/accessibility/magnification/MagnificationConnectionWrapper.java index 20538f167656..d7098a78d248 100644 --- a/services/accessibility/java/com/android/server/accessibility/magnification/MagnificationConnectionWrapper.java +++ b/services/accessibility/java/com/android/server/accessibility/magnification/MagnificationConnectionWrapper.java @@ -16,8 +16,8 @@ package com.android.server.accessibility.magnification; +import static android.accessibilityservice.AccessibilityTrace.FLAGS_MAGNIFICATION_CONNECTION; import static android.accessibilityservice.AccessibilityTrace.FLAGS_REMOTE_MAGNIFICATION_ANIMATION_CALLBACK; -import static android.accessibilityservice.AccessibilityTrace.FLAGS_WINDOW_MAGNIFICATION_CONNECTION; import static android.accessibilityservice.AccessibilityTrace.FLAGS_WINDOW_MAGNIFICATION_CONNECTION_CALLBACK; import static android.os.IBinder.DeathRecipient; @@ -25,25 +25,25 @@ import android.annotation.NonNull; import android.annotation.Nullable; import android.os.RemoteException; import android.util.Slog; +import android.view.accessibility.IMagnificationConnection; import android.view.accessibility.IRemoteMagnificationAnimationCallback; -import android.view.accessibility.IWindowMagnificationConnection; import android.view.accessibility.IWindowMagnificationConnectionCallback; import android.view.accessibility.MagnificationAnimationCallback; import com.android.server.accessibility.AccessibilityTraceManager; /** - * A wrapper of {@link IWindowMagnificationConnection}. + * A wrapper of {@link IMagnificationConnection}. */ class MagnificationConnectionWrapper { private static final boolean DBG = false; private static final String TAG = "MagnificationConnectionWrapper"; - private final @NonNull IWindowMagnificationConnection mConnection; + private final @NonNull IMagnificationConnection mConnection; private final @NonNull AccessibilityTraceManager mTrace; - MagnificationConnectionWrapper(@NonNull IWindowMagnificationConnection connection, + MagnificationConnectionWrapper(@NonNull IMagnificationConnection connection, @NonNull AccessibilityTraceManager trace) { mConnection = connection; mTrace = trace; @@ -61,9 +61,9 @@ class MagnificationConnectionWrapper { boolean enableWindowMagnification(int displayId, float scale, float centerX, float centerY, float magnificationFrameOffsetRatioX, float magnificationFrameOffsetRatioY, @Nullable MagnificationAnimationCallback callback) { - if (mTrace.isA11yTracingEnabledForTypes(FLAGS_WINDOW_MAGNIFICATION_CONNECTION)) { + if (mTrace.isA11yTracingEnabledForTypes(FLAGS_MAGNIFICATION_CONNECTION)) { mTrace.logTrace(TAG + ".enableWindowMagnification", - FLAGS_WINDOW_MAGNIFICATION_CONNECTION, + FLAGS_MAGNIFICATION_CONNECTION, "displayId=" + displayId + ";scale=" + scale + ";centerX=" + centerX + ";centerY=" + centerY + ";magnificationFrameOffsetRatioX=" + magnificationFrameOffsetRatioX + ";magnificationFrameOffsetRatioY=" @@ -83,8 +83,8 @@ class MagnificationConnectionWrapper { } boolean setScaleForWindowMagnification(int displayId, float scale) { - if (mTrace.isA11yTracingEnabledForTypes(FLAGS_WINDOW_MAGNIFICATION_CONNECTION)) { - mTrace.logTrace(TAG + ".setScale", FLAGS_WINDOW_MAGNIFICATION_CONNECTION, + if (mTrace.isA11yTracingEnabledForTypes(FLAGS_MAGNIFICATION_CONNECTION)) { + mTrace.logTrace(TAG + ".setScale", FLAGS_MAGNIFICATION_CONNECTION, "displayId=" + displayId + ";scale=" + scale); } try { @@ -100,9 +100,9 @@ class MagnificationConnectionWrapper { boolean disableWindowMagnification(int displayId, @Nullable MagnificationAnimationCallback callback) { - if (mTrace.isA11yTracingEnabledForTypes(FLAGS_WINDOW_MAGNIFICATION_CONNECTION)) { + if (mTrace.isA11yTracingEnabledForTypes(FLAGS_MAGNIFICATION_CONNECTION)) { mTrace.logTrace(TAG + ".disableWindowMagnification", - FLAGS_WINDOW_MAGNIFICATION_CONNECTION, + FLAGS_MAGNIFICATION_CONNECTION, "displayId=" + displayId + ";callback=" + callback); } try { @@ -118,8 +118,8 @@ class MagnificationConnectionWrapper { } boolean moveWindowMagnifier(int displayId, float offsetX, float offsetY) { - if (mTrace.isA11yTracingEnabledForTypes(FLAGS_WINDOW_MAGNIFICATION_CONNECTION)) { - mTrace.logTrace(TAG + ".moveWindowMagnifier", FLAGS_WINDOW_MAGNIFICATION_CONNECTION, + if (mTrace.isA11yTracingEnabledForTypes(FLAGS_MAGNIFICATION_CONNECTION)) { + mTrace.logTrace(TAG + ".moveWindowMagnifier", FLAGS_MAGNIFICATION_CONNECTION, "displayId=" + displayId + ";offsetX=" + offsetX + ";offsetY=" + offsetY); } try { @@ -135,9 +135,9 @@ class MagnificationConnectionWrapper { boolean moveWindowMagnifierToPosition(int displayId, float positionX, float positionY, @Nullable MagnificationAnimationCallback callback) { - if (mTrace.isA11yTracingEnabledForTypes(FLAGS_WINDOW_MAGNIFICATION_CONNECTION)) { + if (mTrace.isA11yTracingEnabledForTypes(FLAGS_MAGNIFICATION_CONNECTION)) { mTrace.logTrace(TAG + ".moveWindowMagnifierToPosition", - FLAGS_WINDOW_MAGNIFICATION_CONNECTION, "displayId=" + displayId + FLAGS_MAGNIFICATION_CONNECTION, "displayId=" + displayId + ";positionX=" + positionX + ";positionY=" + positionY); } try { @@ -153,9 +153,9 @@ class MagnificationConnectionWrapper { } boolean showMagnificationButton(int displayId, int magnificationMode) { - if (mTrace.isA11yTracingEnabledForTypes(FLAGS_WINDOW_MAGNIFICATION_CONNECTION)) { + if (mTrace.isA11yTracingEnabledForTypes(FLAGS_MAGNIFICATION_CONNECTION)) { mTrace.logTrace(TAG + ".showMagnificationButton", - FLAGS_WINDOW_MAGNIFICATION_CONNECTION, + FLAGS_MAGNIFICATION_CONNECTION, "displayId=" + displayId + ";mode=" + magnificationMode); } try { @@ -170,9 +170,9 @@ class MagnificationConnectionWrapper { } boolean removeMagnificationButton(int displayId) { - if (mTrace.isA11yTracingEnabledForTypes(FLAGS_WINDOW_MAGNIFICATION_CONNECTION)) { + if (mTrace.isA11yTracingEnabledForTypes(FLAGS_MAGNIFICATION_CONNECTION)) { mTrace.logTrace(TAG + ".removeMagnificationButton", - FLAGS_WINDOW_MAGNIFICATION_CONNECTION, "displayId=" + displayId); + FLAGS_MAGNIFICATION_CONNECTION, "displayId=" + displayId); } try { mConnection.removeMagnificationButton(displayId); @@ -186,9 +186,9 @@ class MagnificationConnectionWrapper { } boolean removeMagnificationSettingsPanel(int displayId) { - if (mTrace.isA11yTracingEnabledForTypes(FLAGS_WINDOW_MAGNIFICATION_CONNECTION)) { + if (mTrace.isA11yTracingEnabledForTypes(FLAGS_MAGNIFICATION_CONNECTION)) { mTrace.logTrace(TAG + ".removeMagnificationSettingsPanel", - FLAGS_WINDOW_MAGNIFICATION_CONNECTION, "displayId=" + displayId); + FLAGS_MAGNIFICATION_CONNECTION, "displayId=" + displayId); } try { mConnection.removeMagnificationSettingsPanel(displayId); @@ -202,9 +202,9 @@ class MagnificationConnectionWrapper { } boolean onUserMagnificationScaleChanged(int userId, int displayId, float scale) { - if (mTrace.isA11yTracingEnabledForTypes(FLAGS_WINDOW_MAGNIFICATION_CONNECTION)) { + if (mTrace.isA11yTracingEnabledForTypes(FLAGS_MAGNIFICATION_CONNECTION)) { mTrace.logTrace(TAG + ".onMagnificationScaleUpdated", - FLAGS_WINDOW_MAGNIFICATION_CONNECTION, "displayId=" + displayId); + FLAGS_MAGNIFICATION_CONNECTION, "displayId=" + displayId); } try { mConnection.onUserMagnificationScaleChanged(userId, displayId, scale); @@ -219,10 +219,10 @@ class MagnificationConnectionWrapper { boolean setConnectionCallback(IWindowMagnificationConnectionCallback connectionCallback) { if (mTrace.isA11yTracingEnabledForTypes( - FLAGS_WINDOW_MAGNIFICATION_CONNECTION + FLAGS_MAGNIFICATION_CONNECTION | FLAGS_WINDOW_MAGNIFICATION_CONNECTION_CALLBACK)) { mTrace.logTrace(TAG + ".setConnectionCallback", - FLAGS_WINDOW_MAGNIFICATION_CONNECTION + FLAGS_MAGNIFICATION_CONNECTION | FLAGS_WINDOW_MAGNIFICATION_CONNECTION_CALLBACK, "callback=" + connectionCallback); } diff --git a/services/core/Android.bp b/services/core/Android.bp index 659112e90203..8ed3fd696bda 100644 --- a/services/core/Android.bp +++ b/services/core/Android.bp @@ -154,6 +154,7 @@ java_library_static { static_libs: [ "android.frameworks.location.altitude-V1-java", // AIDL + "android.frameworks.vibrator-V1-java", // AIDL "android.hardware.authsecret-V1.0-java", "android.hardware.authsecret-V1-java", "android.hardware.boot-V1.0-java", // HIDL @@ -193,7 +194,6 @@ java_library_static { "overlayable_policy_aidl-java", "SurfaceFlingerProperties", "com.android.sysprop.watchdog", - "ImmutabilityAnnotation", "securebox", "apache-commons-math", "backstage_power_flags_lib", diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java index b87d02d86c22..6ec4fbc21626 100644 --- a/services/core/java/com/android/server/am/ActivityManagerService.java +++ b/services/core/java/com/android/server/am/ActivityManagerService.java @@ -429,10 +429,10 @@ import com.android.internal.util.FastPrintWriter; import com.android.internal.util.FrameworkStatsLog; import com.android.internal.util.MemInfoReader; import com.android.internal.util.Preconditions; -import com.android.internal.util.function.HeptFunction; +import com.android.internal.util.function.DodecFunction; import com.android.internal.util.function.HexFunction; +import com.android.internal.util.function.OctFunction; import com.android.internal.util.function.QuadFunction; -import com.android.internal.util.function.QuintFunction; import com.android.internal.util.function.UndecFunction; import com.android.server.AlarmManagerInternal; import com.android.server.BootReceiver; @@ -20149,20 +20149,21 @@ public class ActivityManagerService extends IActivityManager.Stub } @Override - public int checkOperation(int code, int uid, String packageName, - String attributionTag, boolean raw, - QuintFunction<Integer, Integer, String, String, Boolean, Integer> superImpl) { + public int checkOperation(int code, int uid, String packageName, String attributionTag, + int virtualDeviceId, boolean raw, HexFunction<Integer, Integer, String, String, + Integer, Boolean, Integer> superImpl) { if (uid == mTargetUid && isTargetOp(code)) { final int shellUid = UserHandle.getUid(UserHandle.getUserId(uid), Process.SHELL_UID); final long identity = Binder.clearCallingIdentity(); try { - return superImpl.apply(code, shellUid, "com.android.shell", null, raw); + return superImpl.apply(code, shellUid, "com.android.shell", null, + virtualDeviceId, raw); } finally { Binder.restoreCallingIdentity(identity); } } - return superImpl.apply(code, uid, packageName, attributionTag, raw); + return superImpl.apply(code, uid, packageName, attributionTag, virtualDeviceId, raw); } @Override @@ -20183,23 +20184,24 @@ public class ActivityManagerService extends IActivityManager.Stub @Override public SyncNotedAppOp noteOperation(int code, int uid, @Nullable String packageName, - @Nullable String featureId, boolean shouldCollectAsyncNotedOp, + @Nullable String featureId, int virtualDeviceId, boolean shouldCollectAsyncNotedOp, @Nullable String message, boolean shouldCollectMessage, - @NonNull HeptFunction<Integer, Integer, String, String, Boolean, String, Boolean, - SyncNotedAppOp> superImpl) { + @NonNull OctFunction<Integer, Integer, String, String, Integer, Boolean, String, + Boolean, SyncNotedAppOp> superImpl) { if (uid == mTargetUid && isTargetOp(code)) { final int shellUid = UserHandle.getUid(UserHandle.getUserId(uid), Process.SHELL_UID); final long identity = Binder.clearCallingIdentity(); try { return superImpl.apply(code, shellUid, "com.android.shell", featureId, - shouldCollectAsyncNotedOp, message, shouldCollectMessage); + virtualDeviceId, shouldCollectAsyncNotedOp, message, + shouldCollectMessage); } finally { Binder.restoreCallingIdentity(identity); } } - return superImpl.apply(code, uid, packageName, featureId, shouldCollectAsyncNotedOp, - message, shouldCollectMessage); + return superImpl.apply(code, uid, packageName, featureId, virtualDeviceId, + shouldCollectAsyncNotedOp, message, shouldCollectMessage); } @Override @@ -20230,11 +20232,11 @@ public class ActivityManagerService extends IActivityManager.Stub @Override public SyncNotedAppOp startOperation(IBinder token, int code, int uid, - @Nullable String packageName, @Nullable String attributionTag, + @Nullable String packageName, @Nullable String attributionTag, int virtualDeviceId, boolean startIfModeDefault, boolean shouldCollectAsyncNotedOp, @Nullable String message, boolean shouldCollectMessage, @AttributionFlags int attributionFlags, int attributionChainId, - @NonNull UndecFunction<IBinder, Integer, Integer, String, String, Boolean, + @NonNull DodecFunction<IBinder, Integer, Integer, String, String, Integer, Boolean, Boolean, String, Boolean, Integer, Integer, SyncNotedAppOp> superImpl) { if (uid == mTargetUid && isTargetOp(code)) { final int shellUid = UserHandle.getUid(UserHandle.getUserId(uid), @@ -20242,13 +20244,14 @@ public class ActivityManagerService extends IActivityManager.Stub final long identity = Binder.clearCallingIdentity(); try { return superImpl.apply(token, code, shellUid, "com.android.shell", - attributionTag, startIfModeDefault, shouldCollectAsyncNotedOp, message, - shouldCollectMessage, attributionFlags, attributionChainId); + attributionTag, virtualDeviceId, startIfModeDefault, + shouldCollectAsyncNotedOp, message, shouldCollectMessage, + attributionFlags, attributionChainId); } finally { Binder.restoreCallingIdentity(identity); } } - return superImpl.apply(token, code, uid, packageName, attributionTag, + return superImpl.apply(token, code, uid, packageName, attributionTag, virtualDeviceId, startIfModeDefault, shouldCollectAsyncNotedOp, message, shouldCollectMessage, attributionFlags, attributionChainId); } diff --git a/services/core/java/com/android/server/appop/AppOpsService.java b/services/core/java/com/android/server/appop/AppOpsService.java index 7780b3906b9f..d80638af697e 100644 --- a/services/core/java/com/android/server/appop/AppOpsService.java +++ b/services/core/java/com/android/server/appop/AppOpsService.java @@ -2580,17 +2580,30 @@ public class AppOpsService extends IAppOpsService.Stub { public int checkOperationRaw(int code, int uid, String packageName, @Nullable String attributionTag) { return mCheckOpsDelegateDispatcher.checkOperation(code, uid, packageName, attributionTag, - true /*raw*/); + Context.DEVICE_ID_DEFAULT, true /*raw*/); + } + + @Override + public int checkOperationRawForDevice(int code, int uid, @Nullable String packageName, + @Nullable String attributionTag, int virtualDeviceId) { + return mCheckOpsDelegateDispatcher.checkOperation(code, uid, packageName, attributionTag, + virtualDeviceId, true /*raw*/); } @Override public int checkOperation(int code, int uid, String packageName) { return mCheckOpsDelegateDispatcher.checkOperation(code, uid, packageName, null, - false /*raw*/); + Context.DEVICE_ID_DEFAULT, false /*raw*/); + } + + @Override + public int checkOperationForDevice(int code, int uid, String packageName, int virtualDeviceId) { + return mCheckOpsDelegateDispatcher.checkOperation(code, uid, packageName, null, + virtualDeviceId, false /*raw*/); } private int checkOperationImpl(int code, int uid, String packageName, - @Nullable String attributionTag, boolean raw) { + @Nullable String attributionTag, int virtualDeviceId, boolean raw) { verifyIncomingOp(code); if (!isIncomingPackageValid(packageName, UserHandle.getUserId(uid))) { return AppOpsManager.opToDefaultMode(code); @@ -2816,12 +2829,23 @@ public class AppOpsService extends IAppOpsService.Stub { String attributionTag, boolean shouldCollectAsyncNotedOp, String message, boolean shouldCollectMessage) { return mCheckOpsDelegateDispatcher.noteOperation(code, uid, packageName, - attributionTag, shouldCollectAsyncNotedOp, message, shouldCollectMessage); + attributionTag, Context.DEVICE_ID_DEFAULT, shouldCollectAsyncNotedOp, message, + shouldCollectMessage); + } + + @Override + public SyncNotedAppOp noteOperationForDevice(int code, int uid, @Nullable String packageName, + @Nullable String attributionTag, int virtualDeviceId, boolean shouldCollectAsyncNotedOp, + String message, boolean shouldCollectMessage) { + return mCheckOpsDelegateDispatcher.noteOperation(code, uid, packageName, + attributionTag, virtualDeviceId, shouldCollectAsyncNotedOp, message, + shouldCollectMessage); } private SyncNotedAppOp noteOperationImpl(int code, int uid, @Nullable String packageName, - @Nullable String attributionTag, boolean shouldCollectAsyncNotedOp, - @Nullable String message, boolean shouldCollectMessage) { + @Nullable String attributionTag, int virtualDeviceId, + boolean shouldCollectAsyncNotedOp, @Nullable String message, + boolean shouldCollectMessage) { verifyIncomingUid(uid); verifyIncomingOp(code); if (!isIncomingPackageValid(packageName, UserHandle.getUserId(uid))) { @@ -2840,10 +2864,10 @@ public class AppOpsService extends IAppOpsService.Stub { } private SyncNotedAppOp noteOperationUnchecked(int code, int uid, @NonNull String packageName, - @Nullable String attributionTag, int proxyUid, String proxyPackageName, - @Nullable String proxyAttributionTag, @OpFlags int flags, - boolean shouldCollectAsyncNotedOp, @Nullable String message, - boolean shouldCollectMessage) { + @Nullable String attributionTag, int proxyUid, String proxyPackageName, + @Nullable String proxyAttributionTag, @OpFlags int flags, + boolean shouldCollectAsyncNotedOp, @Nullable String message, + boolean shouldCollectMessage) { PackageVerificationResult pvr; try { pvr = verifyAndGetBypass(uid, packageName, attributionTag, proxyPackageName); @@ -3238,12 +3262,26 @@ public class AppOpsService extends IAppOpsService.Stub { String message, boolean shouldCollectMessage, @AttributionFlags int attributionFlags, int attributionChainId) { return mCheckOpsDelegateDispatcher.startOperation(token, code, uid, packageName, - attributionTag, startIfModeDefault, shouldCollectAsyncNotedOp, message, - shouldCollectMessage, attributionFlags, attributionChainId); + attributionTag, Context.DEVICE_ID_DEFAULT, startIfModeDefault, + shouldCollectAsyncNotedOp, message, shouldCollectMessage, attributionFlags, + attributionChainId + ); + } + + @Override + public SyncNotedAppOp startOperationForDevice(IBinder token, int code, int uid, + @Nullable String packageName, @Nullable String attributionTag, int virtualDeviceId, + boolean startIfModeDefault, boolean shouldCollectAsyncNotedOp, String message, + boolean shouldCollectMessage, @AttributionFlags int attributionFlags, + int attributionChainId) { + return mCheckOpsDelegateDispatcher.startOperation(token, code, uid, packageName, + attributionTag, virtualDeviceId, startIfModeDefault, shouldCollectAsyncNotedOp, + message, shouldCollectMessage, attributionFlags, attributionChainId + ); } private SyncNotedAppOp startOperationImpl(@NonNull IBinder clientId, int code, int uid, - @Nullable String packageName, @Nullable String attributionTag, + @Nullable String packageName, @Nullable String attributionTag, int virtualDeviceId, boolean startIfModeDefault, boolean shouldCollectAsyncNotedOp, @NonNull String message, boolean shouldCollectMessage, @AttributionFlags int attributionFlags, int attributionChainId) { @@ -3614,11 +3652,18 @@ public class AppOpsService extends IAppOpsService.Stub { public void finishOperation(IBinder clientId, int code, int uid, String packageName, String attributionTag) { mCheckOpsDelegateDispatcher.finishOperation(clientId, code, uid, packageName, - attributionTag); + attributionTag, Context.DEVICE_ID_DEFAULT); + } + + @Override + public void finishOperationForDevice(IBinder clientId, int code, int uid, + @Nullable String packageName, @Nullable String attributionTag, int virtualDeviceId) { + mCheckOpsDelegateDispatcher.finishOperation(clientId, code, uid, packageName, + attributionTag, virtualDeviceId); } private void finishOperationImpl(IBinder clientId, int code, int uid, String packageName, - String attributionTag) { + String attributionTag, int virtualDeviceId) { verifyIncomingUid(uid); verifyIncomingOp(code); if (!isIncomingPackageValid(packageName, UserHandle.getUserId(uid))) { @@ -6800,25 +6845,28 @@ public class AppOpsService extends IAppOpsService.Stub { } public int checkOperation(int code, int uid, String packageName, - @Nullable String attributionTag, boolean raw) { + @Nullable String attributionTag, int virtualDeviceId, boolean raw) { if (mPolicy != null) { if (mCheckOpsDelegate != null) { - return mPolicy.checkOperation(code, uid, packageName, attributionTag, raw, - this::checkDelegateOperationImpl); + return mPolicy.checkOperation(code, uid, packageName, attributionTag, + virtualDeviceId, raw, this::checkDelegateOperationImpl + ); } else { - return mPolicy.checkOperation(code, uid, packageName, attributionTag, raw, - AppOpsService.this::checkOperationImpl); + return mPolicy.checkOperation(code, uid, packageName, attributionTag, + virtualDeviceId, raw, AppOpsService.this::checkOperationImpl + ); } } else if (mCheckOpsDelegate != null) { - return checkDelegateOperationImpl(code, uid, packageName, attributionTag, raw); + return checkDelegateOperationImpl(code, uid, packageName, attributionTag, + virtualDeviceId, raw); } - return checkOperationImpl(code, uid, packageName, attributionTag, raw); + return checkOperationImpl(code, uid, packageName, attributionTag, virtualDeviceId, raw); } private int checkDelegateOperationImpl(int code, int uid, String packageName, - @Nullable String attributionTag, boolean raw) { - return mCheckOpsDelegate.checkOperation(code, uid, packageName, attributionTag, raw, - AppOpsService.this::checkOperationImpl); + @Nullable String attributionTag, int virtualDeviceId, boolean raw) { + return mCheckOpsDelegate.checkOperation(code, uid, packageName, attributionTag, + virtualDeviceId, raw, AppOpsService.this::checkOperationImpl); } public int checkAudioOperation(int code, int usage, int uid, String packageName) { @@ -6843,33 +6891,36 @@ public class AppOpsService extends IAppOpsService.Stub { } public SyncNotedAppOp noteOperation(int code, int uid, String packageName, - String attributionTag, boolean shouldCollectAsyncNotedOp, String message, - boolean shouldCollectMessage) { + String attributionTag, int virtualDeviceId, boolean shouldCollectAsyncNotedOp, + String message, boolean shouldCollectMessage) { if (mPolicy != null) { if (mCheckOpsDelegate != null) { return mPolicy.noteOperation(code, uid, packageName, attributionTag, - shouldCollectAsyncNotedOp, message, shouldCollectMessage, - this::noteDelegateOperationImpl); + virtualDeviceId, shouldCollectAsyncNotedOp, message, + shouldCollectMessage, this::noteDelegateOperationImpl + ); } else { return mPolicy.noteOperation(code, uid, packageName, attributionTag, - shouldCollectAsyncNotedOp, message, shouldCollectMessage, - AppOpsService.this::noteOperationImpl); + virtualDeviceId, shouldCollectAsyncNotedOp, message, + shouldCollectMessage, AppOpsService.this::noteOperationImpl + ); } } else if (mCheckOpsDelegate != null) { - return noteDelegateOperationImpl(code, uid, packageName, - attributionTag, shouldCollectAsyncNotedOp, message, shouldCollectMessage); + return noteDelegateOperationImpl(code, uid, packageName, attributionTag, + virtualDeviceId, shouldCollectAsyncNotedOp, message, shouldCollectMessage); } return noteOperationImpl(code, uid, packageName, attributionTag, - shouldCollectAsyncNotedOp, message, shouldCollectMessage); + virtualDeviceId, shouldCollectAsyncNotedOp, message, shouldCollectMessage); } private SyncNotedAppOp noteDelegateOperationImpl(int code, int uid, - @Nullable String packageName, @Nullable String featureId, + @Nullable String packageName, @Nullable String featureId, int virtualDeviceId, boolean shouldCollectAsyncNotedOp, @Nullable String message, boolean shouldCollectMessage) { return mCheckOpsDelegate.noteOperation(code, uid, packageName, featureId, - shouldCollectAsyncNotedOp, message, shouldCollectMessage, - AppOpsService.this::noteOperationImpl); + virtualDeviceId, shouldCollectAsyncNotedOp, message, shouldCollectMessage, + AppOpsService.this::noteOperationImpl + ); } public SyncNotedAppOp noteProxyOperation(int code, AttributionSource attributionSource, @@ -6904,40 +6955,45 @@ public class AppOpsService extends IAppOpsService.Stub { } public SyncNotedAppOp startOperation(IBinder token, int code, int uid, - @Nullable String packageName, @NonNull String attributionTag, + @Nullable String packageName, @NonNull String attributionTag, int virtualDeviceId, boolean startIfModeDefault, boolean shouldCollectAsyncNotedOp, @Nullable String message, boolean shouldCollectMessage, @AttributionFlags int attributionFlags, int attributionChainId) { if (mPolicy != null) { if (mCheckOpsDelegate != null) { - return mPolicy.startOperation(token, code, uid, packageName, - attributionTag, startIfModeDefault, shouldCollectAsyncNotedOp, message, + return mPolicy.startOperation(token, code, uid, packageName, attributionTag, + virtualDeviceId, startIfModeDefault, shouldCollectAsyncNotedOp, message, shouldCollectMessage, attributionFlags, attributionChainId, - this::startDelegateOperationImpl); + this::startDelegateOperationImpl + ); } else { return mPolicy.startOperation(token, code, uid, packageName, attributionTag, - startIfModeDefault, shouldCollectAsyncNotedOp, message, + virtualDeviceId, startIfModeDefault, shouldCollectAsyncNotedOp, message, shouldCollectMessage, attributionFlags, attributionChainId, - AppOpsService.this::startOperationImpl); + AppOpsService.this::startOperationImpl + ); } } else if (mCheckOpsDelegate != null) { return startDelegateOperationImpl(token, code, uid, packageName, attributionTag, - startIfModeDefault, shouldCollectAsyncNotedOp, message, - shouldCollectMessage, attributionFlags, attributionChainId); + virtualDeviceId, startIfModeDefault, shouldCollectAsyncNotedOp, message, + shouldCollectMessage, attributionFlags, attributionChainId + ); } return startOperationImpl(token, code, uid, packageName, attributionTag, - startIfModeDefault, shouldCollectAsyncNotedOp, message, shouldCollectMessage, - attributionFlags, attributionChainId); + virtualDeviceId, startIfModeDefault, shouldCollectAsyncNotedOp, message, + shouldCollectMessage, attributionFlags, attributionChainId + ); } private SyncNotedAppOp startDelegateOperationImpl(IBinder token, int code, int uid, @Nullable String packageName, @Nullable String attributionTag, - boolean startIfModeDefault, boolean shouldCollectAsyncNotedOp, String message, - boolean shouldCollectMessage, @AttributionFlags int attributionFlags, - int attributionChainId) { + int virtualDeviceId, boolean startIfModeDefault, boolean shouldCollectAsyncNotedOp, + String message, boolean shouldCollectMessage, + @AttributionFlags int attributionFlags, int attributionChainId) { return mCheckOpsDelegate.startOperation(token, code, uid, packageName, attributionTag, - startIfModeDefault, shouldCollectAsyncNotedOp, message, shouldCollectMessage, - attributionFlags, attributionChainId, AppOpsService.this::startOperationImpl); + virtualDeviceId, startIfModeDefault, shouldCollectAsyncNotedOp, message, + shouldCollectMessage, attributionFlags, attributionChainId, + AppOpsService.this::startOperationImpl); } public SyncNotedAppOp startProxyOperation(@NonNull IBinder clientId, int code, @@ -6982,26 +7038,28 @@ public class AppOpsService extends IAppOpsService.Stub { } public void finishOperation(IBinder clientId, int code, int uid, String packageName, - String attributionTag) { + String attributionTag, int virtualDeviceId) { if (mPolicy != null) { if (mCheckOpsDelegate != null) { mPolicy.finishOperation(clientId, code, uid, packageName, attributionTag, - this::finishDelegateOperationImpl); + virtualDeviceId, this::finishDelegateOperationImpl); } else { mPolicy.finishOperation(clientId, code, uid, packageName, attributionTag, - AppOpsService.this::finishOperationImpl); + virtualDeviceId, AppOpsService.this::finishOperationImpl); } } else if (mCheckOpsDelegate != null) { - finishDelegateOperationImpl(clientId, code, uid, packageName, attributionTag); + finishDelegateOperationImpl(clientId, code, uid, packageName, attributionTag, + virtualDeviceId); } else { - finishOperationImpl(clientId, code, uid, packageName, attributionTag); + finishOperationImpl(clientId, code, uid, packageName, attributionTag, + virtualDeviceId); } } private void finishDelegateOperationImpl(IBinder clientId, int code, int uid, - String packageName, String attributionTag) { + String packageName, String attributionTag, int virtualDeviceId) { mCheckOpsDelegate.finishOperation(clientId, code, uid, packageName, attributionTag, - AppOpsService.this::finishOperationImpl); + virtualDeviceId, AppOpsService.this::finishOperationImpl); } public void finishProxyOperation(@NonNull IBinder clientId, int code, diff --git a/services/core/java/com/android/server/audio/AdiDeviceState.java b/services/core/java/com/android/server/audio/AdiDeviceState.java index 5c8dd0d427f9..b91e633bd3de 100644 --- a/services/core/java/com/android/server/audio/AdiDeviceState.java +++ b/services/core/java/com/android/server/audio/AdiDeviceState.java @@ -19,6 +19,7 @@ package com.android.server.audio; import static android.media.AudioManager.AUDIO_DEVICE_CATEGORY_UNKNOWN; import static android.media.AudioSystem.DEVICE_NONE; import static android.media.AudioSystem.isBluetoothDevice; +import static android.media.audio.Flags.automaticBtDeviceType; import android.annotation.NonNull; import android.annotation.Nullable; @@ -55,6 +56,8 @@ import java.util.Objects; @AudioManager.AudioDeviceCategory private int mAudioDeviceCategory = AUDIO_DEVICE_CATEGORY_UNKNOWN; + private boolean mAutoBtCategorySet = false; + private boolean mSAEnabled; private boolean mHasHeadTracker = false; private boolean mHeadTrackerEnabled; @@ -84,58 +87,94 @@ import java.util.Objects; mDeviceId = new Pair<>(mInternalDeviceType, mDeviceAddress); } - public Pair<Integer, String> getDeviceId() { + public synchronized Pair<Integer, String> getDeviceId() { return mDeviceId; } @AudioDeviceInfo.AudioDeviceType - public int getDeviceType() { + public synchronized int getDeviceType() { return mDeviceType; } - public int getInternalDeviceType() { + public synchronized int getInternalDeviceType() { return mInternalDeviceType; } @NonNull - public String getDeviceAddress() { + public synchronized String getDeviceAddress() { return mDeviceAddress; } - public void setSAEnabled(boolean sAEnabled) { + public synchronized void setSAEnabled(boolean sAEnabled) { mSAEnabled = sAEnabled; } - public boolean isSAEnabled() { + public synchronized boolean isSAEnabled() { return mSAEnabled; } - public void setHeadTrackerEnabled(boolean headTrackerEnabled) { + public synchronized void setHeadTrackerEnabled(boolean headTrackerEnabled) { mHeadTrackerEnabled = headTrackerEnabled; } - public boolean isHeadTrackerEnabled() { + public synchronized boolean isHeadTrackerEnabled() { return mHeadTrackerEnabled; } - public void setHasHeadTracker(boolean hasHeadTracker) { + public synchronized void setHasHeadTracker(boolean hasHeadTracker) { mHasHeadTracker = hasHeadTracker; } - public boolean hasHeadTracker() { + public synchronized boolean hasHeadTracker() { return mHasHeadTracker; } @AudioDeviceInfo.AudioDeviceType - public int getAudioDeviceCategory() { + public synchronized int getAudioDeviceCategory() { return mAudioDeviceCategory; } - public void setAudioDeviceCategory(@AudioDeviceInfo.AudioDeviceType int audioDeviceCategory) { + public synchronized void setAudioDeviceCategory( + @AudioDeviceInfo.AudioDeviceType int audioDeviceCategory) { mAudioDeviceCategory = audioDeviceCategory; } + public synchronized boolean isBtDeviceCategoryFixed() { + if (!automaticBtDeviceType()) { + // do nothing + return false; + } + + updateAudioDeviceCategory(); + return mAutoBtCategorySet; + } + + public synchronized boolean updateAudioDeviceCategory() { + if (!automaticBtDeviceType()) { + // do nothing + return false; + } + if (!isBluetoothDevice(mInternalDeviceType)) { + return false; + } + if (mAutoBtCategorySet) { + // no need to update. The auto value is already set. + return false; + } + + int newAudioDeviceCategory = BtHelper.getBtDeviceCategory(mDeviceAddress); + if (newAudioDeviceCategory == AUDIO_DEVICE_CATEGORY_UNKNOWN) { + // no info provided by the BtDevice metadata + return false; + } + + mAudioDeviceCategory = newAudioDeviceCategory; + mAutoBtCategorySet = true; + return true; + + } + @Override public boolean equals(Object obj) { if (this == obj) { @@ -175,7 +214,7 @@ import java.util.Objects; + " HTenabled: " + mHeadTrackerEnabled; } - public String toPersistableString() { + public synchronized String toPersistableString() { return (new StringBuilder().append(mDeviceType) .append(SETTING_FIELD_SEPARATOR).append(mDeviceAddress) .append(SETTING_FIELD_SEPARATOR).append(mSAEnabled ? "1" : "0") @@ -228,6 +267,8 @@ import java.util.Objects; deviceState.setHasHeadTracker(Integer.parseInt(fields[3]) == 1); deviceState.setHeadTrackerEnabled(Integer.parseInt(fields[4]) == 1); deviceState.setAudioDeviceCategory(audioDeviceCategory); + // update in case we can automatically determine the category + deviceState.updateAudioDeviceCategory(); return deviceState; } catch (NumberFormatException e) { Log.e(TAG, "unable to parse setting for AdiDeviceState: " + persistedString, e); @@ -235,7 +276,7 @@ import java.util.Objects; } } - public AudioDeviceAttributes getAudioDeviceAttributes() { + public synchronized AudioDeviceAttributes getAudioDeviceAttributes() { return new AudioDeviceAttributes(AudioDeviceAttributes.ROLE_OUTPUT, mDeviceType, mDeviceAddress); } diff --git a/services/core/java/com/android/server/audio/AudioDeviceBroker.java b/services/core/java/com/android/server/audio/AudioDeviceBroker.java index 2f7d99fcbc4b..865c2ab762ff 100644 --- a/services/core/java/com/android/server/audio/AudioDeviceBroker.java +++ b/services/core/java/com/android/server/audio/AudioDeviceBroker.java @@ -30,6 +30,7 @@ import android.media.AudioAttributes; import android.media.AudioDeviceAttributes; import android.media.AudioDeviceInfo; import android.media.AudioManager; +import android.media.AudioManager.AudioDeviceCategory; import android.media.AudioPlaybackConfiguration; import android.media.AudioRecordingConfiguration; import android.media.AudioRoutesInfo; @@ -1483,8 +1484,12 @@ public class AudioDeviceBroker { MSG_I_UPDATE_LE_AUDIO_GROUP_ADDRESSES, SENDMSG_QUEUE, groupId); } - /*package*/ void postSynchronizeLeDevicesInInventory(AdiDeviceState deviceState) { - sendLMsgNoDelay(MSG_L_SYNCHRONIZE_LE_DEVICES_IN_INVENTORY, SENDMSG_QUEUE, deviceState); + /*package*/ void postSynchronizeAdiDevicesInInventory(AdiDeviceState deviceState) { + sendLMsgNoDelay(MSG_L_SYNCHRONIZE_ADI_DEVICES_IN_INVENTORY, SENDMSG_QUEUE, deviceState); + } + + /*package*/ void postUpdatedAdiDeviceState(AdiDeviceState deviceState) { + sendLMsgNoDelay(MSG_L_UPDATED_ADI_DEVICE_STATE, SENDMSG_QUEUE, deviceState); } /*package*/ static final class CommunicationDeviceInfo { @@ -2007,14 +2012,19 @@ public class AudioDeviceBroker { } } break; - case MSG_L_SYNCHRONIZE_LE_DEVICES_IN_INVENTORY: + case MSG_L_SYNCHRONIZE_ADI_DEVICES_IN_INVENTORY: synchronized (mSetModeLock) { synchronized (mDeviceStateLock) { - mDeviceInventory.onSynchronizeLeDevicesInInventory( + mDeviceInventory.onSynchronizeAdiDevicesInInventory( (AdiDeviceState) msg.obj); } } break; + case MSG_L_UPDATED_ADI_DEVICE_STATE: + synchronized (mDeviceStateLock) { + mAudioService.onUpdatedAdiDeviceState((AdiDeviceState) msg.obj); + } break; + default: Log.wtf(TAG, "Invalid message " + msg.what); } @@ -2098,7 +2108,8 @@ public class AudioDeviceBroker { private static final int MSG_CHECK_COMMUNICATION_ROUTE_CLIENT_STATE = 56; private static final int MSG_I_UPDATE_LE_AUDIO_GROUP_ADDRESSES = 57; - private static final int MSG_L_SYNCHRONIZE_LE_DEVICES_IN_INVENTORY = 58; + private static final int MSG_L_SYNCHRONIZE_ADI_DEVICES_IN_INVENTORY = 58; + private static final int MSG_L_UPDATED_ADI_DEVICE_STATE = 59; @@ -2745,6 +2756,21 @@ public class AudioDeviceBroker { return mDeviceInventory.findBtDeviceStateForAddress(address, deviceType); } + void addAudioDeviceWithCategoryInInventoryIfNeeded(@NonNull String address, + @AudioDeviceCategory int btAudioDeviceCategory) { + mDeviceInventory.addAudioDeviceWithCategoryInInventoryIfNeeded(address, + btAudioDeviceCategory); + } + + @AudioDeviceCategory + int getAndUpdateBtAdiDeviceStateCategoryForAddress(@NonNull String address) { + return mDeviceInventory.getAndUpdateBtAdiDeviceStateCategoryForAddress(address); + } + + boolean isBluetoothAudioDeviceCategoryFixed(@NonNull String address) { + return mDeviceInventory.isBluetoothAudioDeviceCategoryFixed(address); + } + //------------------------------------------------ // for testing purposes only void clearDeviceInventory() { diff --git a/services/core/java/com/android/server/audio/AudioDeviceInventory.java b/services/core/java/com/android/server/audio/AudioDeviceInventory.java index e503f1f2c8c2..5499fd556c0a 100644 --- a/services/core/java/com/android/server/audio/AudioDeviceInventory.java +++ b/services/core/java/com/android/server/audio/AudioDeviceInventory.java @@ -15,16 +15,20 @@ */ package com.android.server.audio; +import static android.media.AudioManager.AUDIO_DEVICE_CATEGORY_UNKNOWN; import static android.media.AudioSystem.DEVICE_IN_ALL_SCO_SET; import static android.media.AudioSystem.DEVICE_OUT_ALL_A2DP_SET; import static android.media.AudioSystem.DEVICE_OUT_ALL_BLE_SET; import static android.media.AudioSystem.DEVICE_OUT_ALL_SCO_SET; +import static android.media.AudioSystem.DEVICE_OUT_BLE_HEADSET; +import static android.media.AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP; import static android.media.AudioSystem.DEVICE_OUT_HEARING_AID; import static android.media.AudioSystem.isBluetoothA2dpOutDevice; import static android.media.AudioSystem.isBluetoothDevice; import static android.media.AudioSystem.isBluetoothLeOutDevice; import static android.media.AudioSystem.isBluetoothOutDevice; import static android.media.AudioSystem.isBluetoothScoOutDevice; +import static android.media.audio.Flags.automaticBtDeviceType; import android.annotation.NonNull; @@ -39,6 +43,7 @@ import android.media.AudioDeviceInfo; import android.media.AudioDevicePort; import android.media.AudioFormat; import android.media.AudioManager; +import android.media.AudioManager.AudioDeviceCategory; import android.media.AudioPort; import android.media.AudioRoutesInfo; import android.media.AudioSystem; @@ -82,6 +87,7 @@ import java.util.List; import java.util.Map.Entry; import java.util.Objects; import java.util.Set; +import java.util.concurrent.atomic.AtomicBoolean; import java.util.stream.Stream; /** @@ -108,9 +114,11 @@ public class AudioDeviceInventory { private final HashMap<Pair<Integer, String>, AdiDeviceState> mDeviceInventory = new HashMap<>(); Collection<AdiDeviceState> getImmutableDeviceInventory() { + final List<AdiDeviceState> newList; synchronized (mDeviceInventoryLock) { - return mDeviceInventory.values(); + newList = new ArrayList<>(mDeviceInventory.values()); } + return newList; } /** @@ -127,30 +135,43 @@ public class AudioDeviceInventory { return oldState; }); } - mDeviceBroker.postSynchronizeLeDevicesInInventory(deviceState); + mDeviceBroker.postSynchronizeAdiDevicesInInventory(deviceState); } /** - * Adds a new entry in mDeviceInventory if the AudioDeviceAttributes passed is an sink + * Adds a new entry in mDeviceInventory if the attributes passed represent a sink * Bluetooth device and no corresponding entry already exists. - * @param ada the device to add if needed + * + * <p>This method will reconcile all BT devices connected with different profiles + * that share the same MAC address and will also synchronize the devices to their + * corresponding peers in case of BLE */ - void addAudioDeviceInInventoryIfNeeded(int deviceType, String address, String peerAddres) { + void addAudioDeviceInInventoryIfNeeded(int deviceType, String address, String peerAddress, + @AudioDeviceCategory int category) { if (!isBluetoothOutDevice(deviceType)) { return; } synchronized (mDeviceInventoryLock) { AdiDeviceState ads = findBtDeviceStateForAddress(address, deviceType); - if (ads == null) { - ads = findBtDeviceStateForAddress(peerAddres, deviceType); + if (ads == null && peerAddress != null) { + ads = findBtDeviceStateForAddress(peerAddress, deviceType); } if (ads != null) { - mDeviceBroker.postSynchronizeLeDevicesInInventory(ads); + if (ads.getAudioDeviceCategory() != category + && category != AUDIO_DEVICE_CATEGORY_UNKNOWN) { + ads.setAudioDeviceCategory(category); + mDeviceBroker.postUpdatedAdiDeviceState(ads); + mDeviceBroker.postPersistAudioDeviceSettings(); + } + mDeviceBroker.postSynchronizeAdiDevicesInInventory(ads); return; } ads = new AdiDeviceState(AudioDeviceInfo.convertInternalDeviceToDeviceType(deviceType), deviceType, address); + ads.setAudioDeviceCategory(category); + mDeviceInventory.put(ads.getDeviceId(), ads); + mDeviceBroker.postUpdatedAdiDeviceState(ads); mDeviceBroker.postPersistAudioDeviceSettings(); } } @@ -161,69 +182,160 @@ public class AudioDeviceInventory { * @param deviceState the device to update */ void addOrUpdateAudioDeviceCategoryInInventory(AdiDeviceState deviceState) { + AtomicBoolean updatedCategory = new AtomicBoolean(false); synchronized (mDeviceInventoryLock) { - mDeviceInventory.merge(deviceState.getDeviceId(), deviceState, (oldState, newState) -> { - oldState.setAudioDeviceCategory(newState.getAudioDeviceCategory()); - return oldState; - }); + if (automaticBtDeviceType()) { + if (deviceState.updateAudioDeviceCategory()) { + updatedCategory.set(true); + } + } + deviceState = mDeviceInventory.merge(deviceState.getDeviceId(), + deviceState, (oldState, newState) -> { + if (oldState.getAudioDeviceCategory() + != newState.getAudioDeviceCategory()) { + oldState.setAudioDeviceCategory(newState.getAudioDeviceCategory()); + updatedCategory.set(true); + } + return oldState; + }); + } + if (updatedCategory.get()) { + mDeviceBroker.postUpdatedAdiDeviceState(deviceState); + } + mDeviceBroker.postSynchronizeAdiDevicesInInventory(deviceState); + } + + void addAudioDeviceWithCategoryInInventoryIfNeeded(@NonNull String address, + @AudioDeviceCategory int btAudioDeviceCategory) { + addAudioDeviceInInventoryIfNeeded(DEVICE_OUT_BLE_HEADSET, + address, "", btAudioDeviceCategory); + addAudioDeviceInInventoryIfNeeded(DEVICE_OUT_BLUETOOTH_A2DP, + address, "", btAudioDeviceCategory); + + } + @AudioDeviceCategory + int getAndUpdateBtAdiDeviceStateCategoryForAddress(@NonNull String address) { + int btCategory = AUDIO_DEVICE_CATEGORY_UNKNOWN; + boolean bleCategoryFound = false; + AdiDeviceState deviceState = findBtDeviceStateForAddress(address, DEVICE_OUT_BLE_HEADSET); + if (deviceState != null) { + addOrUpdateAudioDeviceCategoryInInventory(deviceState); + btCategory = deviceState.getAudioDeviceCategory(); + bleCategoryFound = true; } - mDeviceBroker.postSynchronizeLeDevicesInInventory(deviceState); + + deviceState = findBtDeviceStateForAddress(address, DEVICE_OUT_BLUETOOTH_A2DP); + if (deviceState != null) { + addOrUpdateAudioDeviceCategoryInInventory(deviceState); + int a2dpCategory = deviceState.getAudioDeviceCategory(); + if (bleCategoryFound && a2dpCategory != btCategory) { + Log.w(TAG, "Found different audio device category for A2DP and BLE profiles with " + + "address " + address); + } + btCategory = a2dpCategory; + } + + return btCategory; + } + + boolean isBluetoothAudioDeviceCategoryFixed(@NonNull String address) { + AdiDeviceState deviceState = findBtDeviceStateForAddress(address, DEVICE_OUT_BLE_HEADSET); + if (deviceState != null) { + return deviceState.isBtDeviceCategoryFixed(); + } + + deviceState = findBtDeviceStateForAddress(address, DEVICE_OUT_BLUETOOTH_A2DP); + if (deviceState != null) { + return deviceState.isBtDeviceCategoryFixed(); + } + + return false; } /** * synchronize AdiDeviceState for LE devices in the same group */ - void onSynchronizeLeDevicesInInventory(AdiDeviceState updatedDevice) { + void onSynchronizeAdiDevicesInInventory(AdiDeviceState updatedDevice) { synchronized (mDevicesLock) { synchronized (mDeviceInventoryLock) { boolean found = false; - for (DeviceInfo di : mConnectedDevices.values()) { - if (di.mDeviceType != updatedDevice.getInternalDeviceType()) { + found |= synchronizeBleDeviceInInventory(updatedDevice); + if (automaticBtDeviceType()) { + found |= synchronizeDeviceProfilesInInventory(updatedDevice); + } + if (found) { + mDeviceBroker.postPersistAudioDeviceSettings(); + } + } + } + } + + @GuardedBy({"mDevicesLock", "mDeviceInventoryLock"}) + private boolean synchronizeBleDeviceInInventory(AdiDeviceState updatedDevice) { + for (DeviceInfo di : mConnectedDevices.values()) { + if (di.mDeviceType != updatedDevice.getInternalDeviceType()) { + continue; + } + if (di.mDeviceAddress.equals(updatedDevice.getDeviceAddress())) { + for (AdiDeviceState ads2 : mDeviceInventory.values()) { + if (!(di.mDeviceType == ads2.getInternalDeviceType() + && di.mPeerDeviceAddress.equals(ads2.getDeviceAddress()))) { continue; } - if (di.mDeviceAddress.equals(updatedDevice.getDeviceAddress())) { - for (AdiDeviceState ads2 : mDeviceInventory.values()) { - if (!(di.mDeviceType == ads2.getInternalDeviceType() - && di.mPeerDeviceAddress.equals(ads2.getDeviceAddress()))) { - continue; - } - ads2.setHasHeadTracker(updatedDevice.hasHeadTracker()); - ads2.setHeadTrackerEnabled(updatedDevice.isHeadTrackerEnabled()); - ads2.setSAEnabled(updatedDevice.isSAEnabled()); - ads2.setAudioDeviceCategory(updatedDevice.getAudioDeviceCategory()); - found = true; - AudioService.sDeviceLogger.enqueue(new EventLogger.StringEvent( - "onSynchronizeLeDevicesInInventory synced device pair ads1=" + ads2.setHasHeadTracker(updatedDevice.hasHeadTracker()); + ads2.setHeadTrackerEnabled(updatedDevice.isHeadTrackerEnabled()); + ads2.setSAEnabled(updatedDevice.isSAEnabled()); + ads2.setAudioDeviceCategory(updatedDevice.getAudioDeviceCategory()); + + mDeviceBroker.postUpdatedAdiDeviceState(ads2); + AudioService.sDeviceLogger.enqueue(new EventLogger.StringEvent( + "synchronizeBleDeviceInInventory synced device pair ads1=" + updatedDevice + " ads2=" + ads2).printLog(TAG)); - break; - } + return true; + } + } + if (di.mPeerDeviceAddress.equals(updatedDevice.getDeviceAddress())) { + for (AdiDeviceState ads2 : mDeviceInventory.values()) { + if (!(di.mDeviceType == ads2.getInternalDeviceType() + && di.mDeviceAddress.equals(ads2.getDeviceAddress()))) { + continue; } - if (di.mPeerDeviceAddress.equals(updatedDevice.getDeviceAddress())) { - for (AdiDeviceState ads2 : mDeviceInventory.values()) { - if (!(di.mDeviceType == ads2.getInternalDeviceType() - && di.mDeviceAddress.equals(ads2.getDeviceAddress()))) { - continue; - } - ads2.setHasHeadTracker(updatedDevice.hasHeadTracker()); - ads2.setHeadTrackerEnabled(updatedDevice.isHeadTrackerEnabled()); - ads2.setSAEnabled(updatedDevice.isSAEnabled()); - ads2.setAudioDeviceCategory(updatedDevice.getAudioDeviceCategory()); - found = true; - AudioService.sDeviceLogger.enqueue(new EventLogger.StringEvent( - "onSynchronizeLeDevicesInInventory synced device pair ads1=" + ads2.setHasHeadTracker(updatedDevice.hasHeadTracker()); + ads2.setHeadTrackerEnabled(updatedDevice.isHeadTrackerEnabled()); + ads2.setSAEnabled(updatedDevice.isSAEnabled()); + ads2.setAudioDeviceCategory(updatedDevice.getAudioDeviceCategory()); + + mDeviceBroker.postUpdatedAdiDeviceState(ads2); + AudioService.sDeviceLogger.enqueue(new EventLogger.StringEvent( + "synchronizeBleDeviceInInventory synced device pair ads1=" + updatedDevice + " peer ads2=" + ads2).printLog(TAG)); - break; - } - } - if (found) { - break; - } - } - if (found) { - mDeviceBroker.postPersistAudioDeviceSettings(); + return true; } } } + return false; + } + + @GuardedBy("mDeviceInventoryLock") + private boolean synchronizeDeviceProfilesInInventory(AdiDeviceState updatedDevice) { + for (AdiDeviceState ads : mDeviceInventory.values()) { + if (updatedDevice.getInternalDeviceType() == ads.getInternalDeviceType() + || !updatedDevice.getDeviceAddress().equals(ads.getDeviceAddress())) { + continue; + } + + ads.setHasHeadTracker(updatedDevice.hasHeadTracker()); + ads.setHeadTrackerEnabled(updatedDevice.isHeadTrackerEnabled()); + ads.setSAEnabled(updatedDevice.isSAEnabled()); + ads.setAudioDeviceCategory(updatedDevice.getAudioDeviceCategory()); + + mDeviceBroker.postUpdatedAdiDeviceState(ads); + AudioService.sDeviceLogger.enqueue(new EventLogger.StringEvent( + "synchronizeDeviceProfilesInInventory synced device pair ads1=" + + updatedDevice + " ads2=" + ads).printLog(TAG)); + return true; + } + return false; } /** @@ -231,7 +343,7 @@ public class AudioDeviceInventory { * returns a valid device for A2DP and BLE devices. * * @param address MAC address of BT device - * @param isBle true if the device is BLE, false for A2DP + * @param deviceType internal device type to identify the BT device * @return the found {@link AdiDeviceState} or {@code null} otherwise. */ @Nullable @@ -1547,7 +1659,8 @@ public class AudioDeviceInventory { if (!connect) { purgeDevicesRoles_l(); } else { - addAudioDeviceInInventoryIfNeeded(device, address, ""); + addAudioDeviceInInventoryIfNeeded(device, address, "", + BtHelper.getBtDeviceCategory(address)); } } mmi.set(MediaMetrics.Property.STATE, MediaMetrics.Value.CONNECTED).record(); @@ -1829,7 +1942,9 @@ public class AudioDeviceInventory { setCurrentAudioRouteNameIfPossible(name, true /*fromA2dp*/); updateBluetoothPreferredModes_l(btInfo.mDevice /*connectedDevice*/); - addAudioDeviceInInventoryIfNeeded(AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP, address, ""); + + addAudioDeviceInInventoryIfNeeded(AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP, address, "", + BtHelper.getBtDeviceCategory(address)); } static final int[] CAPTURE_PRESETS = new int[] {AudioSource.MIC, AudioSource.CAMCORDER, @@ -2149,7 +2264,8 @@ public class AudioDeviceInventory { mDeviceBroker.postApplyVolumeOnDevice(streamType, DEVICE_OUT_HEARING_AID, "makeHearingAidDeviceAvailable"); setCurrentAudioRouteNameIfPossible(name, false /*fromA2dp*/); - addAudioDeviceInInventoryIfNeeded(DEVICE_OUT_HEARING_AID, address, ""); + addAudioDeviceInInventoryIfNeeded(DEVICE_OUT_HEARING_AID, address, "", + BtHelper.getBtDeviceCategory(address)); new MediaMetrics.Item(mMetricsId + "makeHearingAidDeviceAvailable") .set(MediaMetrics.Property.ADDRESS, address != null ? address : "") .set(MediaMetrics.Property.DEVICE, @@ -2274,7 +2390,8 @@ public class AudioDeviceInventory { peerAddress, groupId)); mDeviceBroker.postAccessoryPlugMediaUnmute(device); setCurrentAudioRouteNameIfPossible(name, /*fromA2dp=*/false); - addAudioDeviceInInventoryIfNeeded(device, address, peerAddress); + addAudioDeviceInInventoryIfNeeded(device, address, peerAddress, + BtHelper.getBtDeviceCategory(address)); } if (streamType == AudioSystem.STREAM_DEFAULT) { diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java index 4f6c6d61ee6f..f1496361fc60 100644 --- a/services/core/java/com/android/server/audio/AudioService.java +++ b/services/core/java/com/android/server/audio/AudioService.java @@ -19,6 +19,7 @@ package com.android.server.audio; import static android.Manifest.permission.MODIFY_AUDIO_SETTINGS_PRIVILEGED; import static android.app.BroadcastOptions.DELIVERY_GROUP_POLICY_MOST_RECENT; import static android.media.audio.Flags.autoPublicVolumeApiHardening; +import static android.media.audio.Flags.automaticBtDeviceType; import static android.media.audio.Flags.focusFreezeTestApi; import static android.media.AudioDeviceInfo.TYPE_BLE_HEADSET; import static android.media.AudioDeviceInfo.TYPE_BLE_SPEAKER; @@ -11148,9 +11149,13 @@ public class AudioService extends IAudioService.Stub @Override @android.annotation.EnforcePermission(MODIFY_AUDIO_SETTINGS_PRIVILEGED) - public void setBluetoothAudioDeviceCategory(@NonNull String address, boolean isBle, + public void setBluetoothAudioDeviceCategory_legacy(@NonNull String address, boolean isBle, @AudioDeviceCategory int btAudioDeviceCategory) { - super.setBluetoothAudioDeviceCategory_enforcePermission(); + super.setBluetoothAudioDeviceCategory_legacy_enforcePermission(); + if (automaticBtDeviceType()) { + // do nothing + return; + } final String addr = Objects.requireNonNull(address); @@ -11182,8 +11187,11 @@ public class AudioService extends IAudioService.Stub @Override @android.annotation.EnforcePermission(MODIFY_AUDIO_SETTINGS_PRIVILEGED) @AudioDeviceCategory - public int getBluetoothAudioDeviceCategory(@NonNull String address, boolean isBle) { - super.getBluetoothAudioDeviceCategory_enforcePermission(); + public int getBluetoothAudioDeviceCategory_legacy(@NonNull String address, boolean isBle) { + super.getBluetoothAudioDeviceCategory_legacy_enforcePermission(); + if (automaticBtDeviceType()) { + return AUDIO_DEVICE_CATEGORY_UNKNOWN; + } final AdiDeviceState deviceState = mDeviceBroker.findBtDeviceStateForAddress( Objects.requireNonNull(address), (isBle ? AudioSystem.DEVICE_OUT_BLE_HEADSET @@ -11195,6 +11203,61 @@ public class AudioService extends IAudioService.Stub return deviceState.getAudioDeviceCategory(); } + @Override + @android.annotation.EnforcePermission(MODIFY_AUDIO_SETTINGS_PRIVILEGED) + public boolean setBluetoothAudioDeviceCategory(@NonNull String address, + @AudioDeviceCategory int btAudioDeviceCategory) { + super.setBluetoothAudioDeviceCategory_enforcePermission(); + if (!automaticBtDeviceType()) { + return false; + } + + final String addr = Objects.requireNonNull(address); + if (isBluetoothAudioDeviceCategoryFixed(addr)) { + Log.w(TAG, "Cannot set fixed audio device type for address " + + Utils.anonymizeBluetoothAddress(address)); + return false; + } + + mDeviceBroker.addAudioDeviceWithCategoryInInventoryIfNeeded(address, btAudioDeviceCategory); + + return true; + } + + @Override + @android.annotation.EnforcePermission(MODIFY_AUDIO_SETTINGS_PRIVILEGED) + @AudioDeviceCategory + public int getBluetoothAudioDeviceCategory(@NonNull String address) { + super.getBluetoothAudioDeviceCategory_enforcePermission(); + if (!automaticBtDeviceType()) { + return AUDIO_DEVICE_CATEGORY_UNKNOWN; + } + + return mDeviceBroker.getAndUpdateBtAdiDeviceStateCategoryForAddress(address); + } + + @Override + @android.annotation.EnforcePermission(MODIFY_AUDIO_SETTINGS_PRIVILEGED) + @AudioDeviceCategory + public boolean isBluetoothAudioDeviceCategoryFixed(@NonNull String address) { + super.isBluetoothAudioDeviceCategoryFixed_enforcePermission(); + if (!automaticBtDeviceType()) { + return false; + } + + return mDeviceBroker.isBluetoothAudioDeviceCategoryFixed(address); + } + + /*package*/void onUpdatedAdiDeviceState(AdiDeviceState deviceState) { + if (deviceState == null) { + return; + } + mSpatializerHelper.refreshDevice(deviceState.getAudioDeviceAttributes()); + mSoundDoseHelper.setAudioDeviceCategory(deviceState.getDeviceAddress(), + deviceState.getInternalDeviceType(), + deviceState.getAudioDeviceCategory() == AUDIO_DEVICE_CATEGORY_HEADPHONES); + } + //========================================================================================== // Hdmi CEC: // - System audio mode: diff --git a/services/core/java/com/android/server/audio/BtHelper.java b/services/core/java/com/android/server/audio/BtHelper.java index a078d08a2c8f..401dc88669ec 100644 --- a/services/core/java/com/android/server/audio/BtHelper.java +++ b/services/core/java/com/android/server/audio/BtHelper.java @@ -15,6 +15,22 @@ */ package com.android.server.audio; +import static android.bluetooth.BluetoothDevice.DEVICE_TYPE_CARKIT; +import static android.bluetooth.BluetoothDevice.DEVICE_TYPE_DEFAULT; +import static android.bluetooth.BluetoothDevice.DEVICE_TYPE_HEADSET; +import static android.bluetooth.BluetoothDevice.DEVICE_TYPE_HEARING_AID; +import static android.bluetooth.BluetoothDevice.DEVICE_TYPE_SPEAKER; +import static android.bluetooth.BluetoothDevice.DEVICE_TYPE_UNTETHERED_HEADSET; +import static android.bluetooth.BluetoothDevice.DEVICE_TYPE_WATCH; +import static android.media.AudioManager.AUDIO_DEVICE_CATEGORY_CARKIT; +import static android.media.AudioManager.AUDIO_DEVICE_CATEGORY_HEADPHONES; +import static android.media.AudioManager.AUDIO_DEVICE_CATEGORY_HEARING_AID; +import static android.media.AudioManager.AUDIO_DEVICE_CATEGORY_RECEIVER; +import static android.media.AudioManager.AUDIO_DEVICE_CATEGORY_SPEAKER; +import static android.media.AudioManager.AUDIO_DEVICE_CATEGORY_UNKNOWN; +import static android.media.AudioManager.AUDIO_DEVICE_CATEGORY_WATCH; +import static android.media.audio.Flags.automaticBtDeviceType; + import android.annotation.NonNull; import android.annotation.Nullable; import android.bluetooth.BluetoothA2dp; @@ -33,6 +49,7 @@ import android.content.Context; import android.content.Intent; import android.media.AudioDeviceAttributes; import android.media.AudioManager; +import android.media.AudioManager.AudioDeviceCategory; import android.media.AudioSystem; import android.media.BluetoothProfileConnectionInfo; import android.os.Binder; @@ -1115,6 +1132,71 @@ public class BtHelper { return adapter.getPreferredAudioProfiles(adapter.getRemoteDevice(address)); } + @Nullable + /*package */ static BluetoothDevice getBluetoothDevice(String address) { + BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter(); + if (adapter == null || !BluetoothAdapter.checkBluetoothAddress(address)) { + return null; + } + + return adapter.getRemoteDevice(address); + } + + @AudioDeviceCategory + /*package*/ static int getBtDeviceCategory(String address) { + if (!automaticBtDeviceType()) { + return AUDIO_DEVICE_CATEGORY_UNKNOWN; + } + + BluetoothDevice device = BtHelper.getBluetoothDevice(address); + if (device == null) { + return AUDIO_DEVICE_CATEGORY_UNKNOWN; + } + + byte[] deviceType = device.getMetadata(BluetoothDevice.METADATA_DEVICE_TYPE); + if (deviceType == null) { + return AUDIO_DEVICE_CATEGORY_UNKNOWN; + } + String deviceCategory = new String(deviceType); + switch (deviceCategory) { + case DEVICE_TYPE_HEARING_AID: + return AUDIO_DEVICE_CATEGORY_HEARING_AID; + case DEVICE_TYPE_CARKIT: + return AUDIO_DEVICE_CATEGORY_CARKIT; + case DEVICE_TYPE_HEADSET: + case DEVICE_TYPE_UNTETHERED_HEADSET: + return AUDIO_DEVICE_CATEGORY_HEADPHONES; + case DEVICE_TYPE_SPEAKER: + return AUDIO_DEVICE_CATEGORY_SPEAKER; + case DEVICE_TYPE_WATCH: + return AUDIO_DEVICE_CATEGORY_WATCH; + case DEVICE_TYPE_DEFAULT: + default: + // fall through + } + + BluetoothClass deviceClass = device.getBluetoothClass(); + if (deviceClass == null) { + return AUDIO_DEVICE_CATEGORY_UNKNOWN; + } + + switch (deviceClass.getDeviceClass()) { + case BluetoothClass.Device.WEARABLE_WRIST_WATCH: + return AUDIO_DEVICE_CATEGORY_WATCH; + case BluetoothClass.Device.AUDIO_VIDEO_LOUDSPEAKER: + case BluetoothClass.Device.AUDIO_VIDEO_VIDEO_DISPLAY_AND_LOUDSPEAKER: + case BluetoothClass.Device.AUDIO_VIDEO_PORTABLE_AUDIO: + return AUDIO_DEVICE_CATEGORY_SPEAKER; + case BluetoothClass.Device.AUDIO_VIDEO_WEARABLE_HEADSET: + case BluetoothClass.Device.AUDIO_VIDEO_HEADPHONES: + return AUDIO_DEVICE_CATEGORY_HEADPHONES; + case BluetoothClass.Device.AUDIO_VIDEO_HIFI_AUDIO: + return AUDIO_DEVICE_CATEGORY_RECEIVER; + default: + return AUDIO_DEVICE_CATEGORY_UNKNOWN; + } + } + /** * Notifies Bluetooth framework that new preferred audio profiles for Bluetooth devices * have been applied. diff --git a/services/core/java/com/android/server/audio/LoudnessCodecHelper.java b/services/core/java/com/android/server/audio/LoudnessCodecHelper.java index bbe819f22e3a..9b0afc4282a2 100644 --- a/services/core/java/com/android/server/audio/LoudnessCodecHelper.java +++ b/services/core/java/com/android/server/audio/LoudnessCodecHelper.java @@ -26,10 +26,12 @@ import static android.media.LoudnessCodecInfo.CodecMetadataType.CODEC_METADATA_T import static android.media.MediaFormat.KEY_AAC_DRC_EFFECT_TYPE; import static android.media.MediaFormat.KEY_AAC_DRC_HEAVY_COMPRESSION; import static android.media.MediaFormat.KEY_AAC_DRC_TARGET_REFERENCE_LEVEL; +import static android.media.audio.Flags.automaticBtDeviceType; import android.annotation.IntDef; import android.annotation.NonNull; import android.media.AudioDeviceInfo; +import android.media.AudioManager.AudioDeviceCategory; import android.media.AudioPlaybackConfiguration; import android.media.AudioSystem; import android.media.ILoudnessCodecUpdatesDispatcher; @@ -552,6 +554,13 @@ public class LoudnessCodecHelper { @DeviceSplRange private int getDeviceSplRange(AudioDeviceInfo deviceInfo) { final int internalDeviceType = deviceInfo.getInternalType(); + final @AudioDeviceCategory int deviceCategory; + if (automaticBtDeviceType()) { + deviceCategory = mAudioService.getBluetoothAudioDeviceCategory(deviceInfo.getAddress()); + } else { + deviceCategory = mAudioService.getBluetoothAudioDeviceCategory_legacy( + deviceInfo.getAddress(), AudioSystem.isBluetoothLeDevice(internalDeviceType)); + } if (internalDeviceType == AudioSystem.DEVICE_OUT_SPEAKER) { final String splRange = SystemProperties.get( SYSTEM_PROPERTY_SPEAKER_SPL_RANGE_SIZE, "unknown"); @@ -569,18 +578,14 @@ public class LoudnessCodecHelper { || internalDeviceType == AudioSystem.DEVICE_OUT_WIRED_HEADPHONE || internalDeviceType == AudioSystem.DEVICE_OUT_WIRED_HEADSET || (AudioSystem.isBluetoothDevice(internalDeviceType) - && mAudioService.getBluetoothAudioDeviceCategory(deviceInfo.getAddress(), - AudioSystem.isBluetoothLeDevice(internalDeviceType)) - == AUDIO_DEVICE_CATEGORY_HEADPHONES)) { + && deviceCategory == AUDIO_DEVICE_CATEGORY_HEADPHONES)) { return SPL_RANGE_LARGE; } else if (AudioSystem.isBluetoothDevice(internalDeviceType)) { - final int audioDeviceType = mAudioService.getBluetoothAudioDeviceCategory( - deviceInfo.getAddress(), AudioSystem.isBluetoothLeDevice(internalDeviceType)); - if (audioDeviceType == AUDIO_DEVICE_CATEGORY_CARKIT) { + if (deviceCategory == AUDIO_DEVICE_CATEGORY_CARKIT) { return SPL_RANGE_MEDIUM; - } else if (audioDeviceType == AUDIO_DEVICE_CATEGORY_WATCH) { + } else if (deviceCategory == AUDIO_DEVICE_CATEGORY_WATCH) { return SPL_RANGE_SMALL; - } else if (audioDeviceType == AUDIO_DEVICE_CATEGORY_HEARING_AID) { + } else if (deviceCategory == AUDIO_DEVICE_CATEGORY_HEARING_AID) { return SPL_RANGE_SMALL; } } diff --git a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java index a0bc7c27ff4a..98f627ce3c1a 100644 --- a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java +++ b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java @@ -741,7 +741,6 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub */ int mImeWindowVis; - private LocaleList mLastSystemLocales; private final MyPackageMonitor mMyPackageMonitor = new MyPackageMonitor(); private final String mSlotIme; @@ -1199,9 +1198,6 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub if (Intent.ACTION_USER_ADDED.equals(action) || Intent.ACTION_USER_REMOVED.equals(action)) { updateCurrentProfileIds(); - return; - } else if (Intent.ACTION_LOCALE_CHANGED.equals(action)) { - onActionLocaleChanged(); } else { Slog.w(TAG, "Unexpected intent " + intent); } @@ -1240,20 +1236,19 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub * * <p>Note: For historical reasons, {@link Intent#ACTION_LOCALE_CHANGED} has been sent to all * the users. We should ignore this event if this is about any background user's locale.</p> - * - * <p>Caution: This method must not be called when system is not ready.</p> */ - void onActionLocaleChanged() { + void onActionLocaleChanged(@NonNull LocaleList prevLocales, @NonNull LocaleList newLocales) { + if (DEBUG) { + Slog.d(TAG, "onActionLocaleChanged prev=" + prevLocales + " new=" + newLocales); + } synchronized (ImfLock.class) { - final LocaleList possibleNewLocale = mRes.getConfiguration().getLocales(); - if (possibleNewLocale != null && possibleNewLocale.equals(mLastSystemLocales)) { + if (!mSystemReady) { return; } buildInputMethodListLocked(true); // If the locale is changed, needs to reset the default ime resetDefaultImeLocked(mContext); updateFromSettingsLocked(true); - mLastSystemLocales = possibleNewLocale; } } @@ -1681,6 +1676,7 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub true /* allowIo */); thread.start(); mHandler = Handler.createAsync(thread.getLooper(), this); + SystemLocaleWrapper.onStart(context, this::onActionLocaleChanged, mHandler); mImeTrackerService = new ImeTrackerService(serviceThreadForTesting != null ? serviceThreadForTesting.getLooper() : Looper.getMainLooper()); // Note: SettingsObserver doesn't register observers in its constructor. @@ -1838,7 +1834,6 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub // Even in such cases, IMMS works fine because it will find the most applicable // IME for that user. final boolean initialUserSwitch = TextUtils.isEmpty(defaultImiId); - mLastSystemLocales = mRes.getConfiguration().getLocales(); // The mSystemReady flag is set during boot phase, // and user switch would not happen at that time. @@ -1890,7 +1885,6 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub } if (!mSystemReady) { mSystemReady = true; - mLastSystemLocales = mRes.getConfiguration().getLocales(); final int currentUserId = mSettings.getCurrentUserId(); mSettings.switchCurrentUser(currentUserId, !mUserManagerInternal.isUserUnlockingOrUnlocked(currentUserId)); @@ -1930,7 +1924,6 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub final IntentFilter broadcastFilterForSystemUser = new IntentFilter(); broadcastFilterForSystemUser.addAction(Intent.ACTION_USER_ADDED); broadcastFilterForSystemUser.addAction(Intent.ACTION_USER_REMOVED); - broadcastFilterForSystemUser.addAction(Intent.ACTION_LOCALE_CHANGED); mContext.registerReceiver(new ImmsBroadcastReceiverForSystemUser(), broadcastFilterForSystemUser); @@ -4073,14 +4066,19 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub final List<InputMethodInfo> enabled = mSettings.getEnabledInputMethodListLocked(); if (enabled != null) { final int enabledCount = enabled.size(); - final String locale = mCurrentSubtype == null - ? mRes.getConfiguration().locale.toString() - : mCurrentSubtype.getLocale(); + final String locale; + if (mCurrentSubtype != null + && !TextUtils.isEmpty(mCurrentSubtype.getLocale())) { + locale = mCurrentSubtype.getLocale(); + } else { + locale = SystemLocaleWrapper.get(mSettings.getCurrentUserId()).get(0) + .toString(); + } for (int i = 0; i < enabledCount; ++i) { final InputMethodInfo imi = enabled.get(i); if (imi.getSubtypeCount() > 0 && imi.isSystem()) { InputMethodSubtype keyboardSubtype = - SubtypeUtils.findLastResortApplicableSubtypeLocked(mRes, + SubtypeUtils.findLastResortApplicableSubtypeLocked( SubtypeUtils.getSubtypes(imi), SubtypeUtils.SUBTYPE_MODE_KEYBOARD, locale, true); if (keyboardSubtype != null) { @@ -5430,12 +5428,14 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub if (explicitlyOrImplicitlyEnabledSubtypes.size() == 1) { mCurrentSubtype = explicitlyOrImplicitlyEnabledSubtypes.get(0); } else if (explicitlyOrImplicitlyEnabledSubtypes.size() > 1) { + final String locale = SystemLocaleWrapper.get(mSettings.getCurrentUserId()) + .get(0).toString(); mCurrentSubtype = SubtypeUtils.findLastResortApplicableSubtypeLocked( - mRes, explicitlyOrImplicitlyEnabledSubtypes, - SubtypeUtils.SUBTYPE_MODE_KEYBOARD, null, true); + explicitlyOrImplicitlyEnabledSubtypes, + SubtypeUtils.SUBTYPE_MODE_KEYBOARD, locale, true); if (mCurrentSubtype == null) { mCurrentSubtype = SubtypeUtils.findLastResortApplicableSubtypeLocked( - mRes, explicitlyOrImplicitlyEnabledSubtypes, null, null, true); + explicitlyOrImplicitlyEnabledSubtypes, null, locale, true); } } } else { diff --git a/services/core/java/com/android/server/inputmethod/InputMethodUtils.java b/services/core/java/com/android/server/inputmethod/InputMethodUtils.java index 984ae1f06711..c661c864b3ee 100644 --- a/services/core/java/com/android/server/inputmethod/InputMethodUtils.java +++ b/services/core/java/com/android/server/inputmethod/InputMethodUtils.java @@ -29,6 +29,7 @@ import android.content.pm.PackageManagerInternal; import android.content.pm.ResolveInfo; import android.content.res.Resources; import android.os.Build; +import android.os.LocaleList; import android.os.UserHandle; import android.provider.Settings; import android.text.TextUtils; @@ -218,7 +219,6 @@ final class InputMethodUtils { @NonNull private Context mUserAwareContext; - private Resources mRes; private ContentResolver mResolver; private final ArrayMap<String, InputMethodInfo> mMethodMap; @@ -281,7 +281,6 @@ final class InputMethodUtils { mUserAwareContext = context.getUserId() == userId ? context : context.createContextAsUser(UserHandle.of(userId), 0 /* flags */); - mRes = mUserAwareContext.getResources(); mResolver = mUserAwareContext.getContentResolver(); } @@ -397,7 +396,8 @@ final class InputMethodUtils { List<InputMethodSubtype> enabledSubtypes = getEnabledInputMethodSubtypeListLocked(imi); if (allowsImplicitlyEnabledSubtypes && enabledSubtypes.isEmpty()) { - enabledSubtypes = SubtypeUtils.getImplicitlyApplicableSubtypesLocked(mRes, imi); + enabledSubtypes = SubtypeUtils.getImplicitlyApplicableSubtypesLocked( + SystemLocaleWrapper.get(mCurrentUserId), imi); } return InputMethodSubtype.sort(imi, enabledSubtypes); } @@ -646,6 +646,7 @@ final class InputMethodUtils { private String getEnabledSubtypeHashCodeForInputMethodAndSubtypeLocked(List<Pair<String, ArrayList<String>>> enabledImes, String imeId, String subtypeHashCode) { + final LocaleList localeList = SystemLocaleWrapper.get(mCurrentUserId); for (Pair<String, ArrayList<String>> enabledIme: enabledImes) { if (enabledIme.first.equals(imeId)) { final ArrayList<String> explicitlyEnabledSubtypes = enabledIme.second; @@ -657,7 +658,8 @@ final class InputMethodUtils { // are enabled implicitly, so needs to treat them to be enabled. if (imi != null && imi.getSubtypeCount() > 0) { List<InputMethodSubtype> implicitlyEnabledSubtypes = - SubtypeUtils.getImplicitlyApplicableSubtypesLocked(mRes, imi); + SubtypeUtils.getImplicitlyApplicableSubtypesLocked(localeList, + imi); final int numSubtypes = implicitlyEnabledSubtypes.size(); for (int i = 0; i < numSubtypes; ++i) { final InputMethodSubtype st = implicitlyEnabledSubtypes.get(i); @@ -847,14 +849,15 @@ final class InputMethodUtils { if (explicitlyOrImplicitlyEnabledSubtypes.size() == 1) { return explicitlyOrImplicitlyEnabledSubtypes.get(0); } + final String locale = SystemLocaleWrapper.get(mCurrentUserId).get(0).toString(); final InputMethodSubtype subtype = SubtypeUtils.findLastResortApplicableSubtypeLocked( - mRes, explicitlyOrImplicitlyEnabledSubtypes, SubtypeUtils.SUBTYPE_MODE_KEYBOARD, - null, true); + explicitlyOrImplicitlyEnabledSubtypes, SubtypeUtils.SUBTYPE_MODE_KEYBOARD, + locale, true); if (subtype != null) { return subtype; } - return SubtypeUtils.findLastResortApplicableSubtypeLocked(mRes, - explicitlyOrImplicitlyEnabledSubtypes, null, null, true); + return SubtypeUtils.findLastResortApplicableSubtypeLocked( + explicitlyOrImplicitlyEnabledSubtypes, null, locale, true); } boolean setAdditionalInputMethodSubtypes(@NonNull String imeId, diff --git a/services/core/java/com/android/server/inputmethod/SubtypeUtils.java b/services/core/java/com/android/server/inputmethod/SubtypeUtils.java index 0185190521a3..95df99855dcf 100644 --- a/services/core/java/com/android/server/inputmethod/SubtypeUtils.java +++ b/services/core/java/com/android/server/inputmethod/SubtypeUtils.java @@ -18,7 +18,6 @@ package com.android.server.inputmethod; import android.annotation.NonNull; import android.annotation.Nullable; -import android.content.res.Resources; import android.os.LocaleList; import android.text.TextUtils; import android.util.ArrayMap; @@ -125,9 +124,7 @@ final class SubtypeUtils { @VisibleForTesting @NonNull static ArrayList<InputMethodSubtype> getImplicitlyApplicableSubtypesLocked( - Resources res, InputMethodInfo imi) { - final LocaleList systemLocales = res.getConfiguration().getLocales(); - + @NonNull LocaleList systemLocales, InputMethodInfo imi) { synchronized (sCacheLock) { // We intentionally do not use InputMethodInfo#equals(InputMethodInfo) here because // it does not check if subtypes are also identical. @@ -140,7 +137,7 @@ final class SubtypeUtils { // TODO: Refactor getImplicitlyApplicableSubtypesLockedImpl() so that it can receive // LocaleList rather than Resource. final ArrayList<InputMethodSubtype> result = - getImplicitlyApplicableSubtypesLockedImpl(res, imi); + getImplicitlyApplicableSubtypesLockedImpl(systemLocales, imi); synchronized (sCacheLock) { // Both LocaleList and InputMethodInfo are immutable. No need to copy them here. sCachedSystemLocales = systemLocales; @@ -151,9 +148,8 @@ final class SubtypeUtils { } private static ArrayList<InputMethodSubtype> getImplicitlyApplicableSubtypesLockedImpl( - Resources res, InputMethodInfo imi) { + @NonNull LocaleList systemLocales, InputMethodInfo imi) { final List<InputMethodSubtype> subtypes = getSubtypes(imi); - final LocaleList systemLocales = res.getConfiguration().getLocales(); final String systemLocale = systemLocales.get(0).toString(); if (TextUtils.isEmpty(systemLocale)) return new ArrayList<>(); final int numSubtypes = subtypes.size(); @@ -220,7 +216,7 @@ final class SubtypeUtils { if (applicableSubtypes.isEmpty()) { InputMethodSubtype lastResortKeyboardSubtype = findLastResortApplicableSubtypeLocked( - res, subtypes, SUBTYPE_MODE_KEYBOARD, systemLocale, true); + subtypes, SUBTYPE_MODE_KEYBOARD, systemLocale, true); if (lastResortKeyboardSubtype != null) { applicableSubtypes.add(lastResortKeyboardSubtype); } @@ -249,14 +245,11 @@ final class SubtypeUtils { * @return the most applicable subtypeId */ static InputMethodSubtype findLastResortApplicableSubtypeLocked( - Resources res, List<InputMethodSubtype> subtypes, String mode, String locale, + List<InputMethodSubtype> subtypes, String mode, @NonNull String locale, boolean canIgnoreLocaleAsLastResort) { if (subtypes == null || subtypes.isEmpty()) { return null; } - if (TextUtils.isEmpty(locale)) { - locale = res.getConfiguration().locale.toString(); - } final String language = LocaleUtils.getLanguageFromLocaleString(locale); boolean partialMatchFound = false; InputMethodSubtype applicableSubtype = null; diff --git a/services/core/java/com/android/server/inputmethod/SystemLocaleWrapper.java b/services/core/java/com/android/server/inputmethod/SystemLocaleWrapper.java new file mode 100644 index 000000000000..0f1b7119f8ce --- /dev/null +++ b/services/core/java/com/android/server/inputmethod/SystemLocaleWrapper.java @@ -0,0 +1,108 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.inputmethod; + +import android.annotation.AnyThread; +import android.annotation.NonNull; +import android.annotation.UserIdInt; +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; +import android.os.Handler; +import android.os.LocaleList; + +import java.util.Locale; +import java.util.Objects; +import java.util.concurrent.atomic.AtomicReference; + +/** + * A set of thread-safe utility methods for the system locals. + */ +final class SystemLocaleWrapper { + /** + * Not intended to be instantiated. + */ + private SystemLocaleWrapper() { + } + + private static final AtomicReference<LocaleList> sSystemLocale = + new AtomicReference<>(new LocaleList(Locale.getDefault())); + + /** + * Returns {@link LocaleList} for the specified user. + * + * <p>Note: If you call this method twice, it is possible that the second value is different + * from the first value. The caller is responsible for taking care of such cases.</p> + * + * @param userId the ID of the user to query about. + * @return {@link LocaleList} associated with the user. + */ + @AnyThread + @NonNull + static LocaleList get(@UserIdInt int userId) { + // Currently system locale is not per-user. + // TODO(b/30119489): Make this per-user. + return sSystemLocale.get(); + } + + /** + * Callback for the locale change event. When this gets filed, {@link #get(int)} is already + * updated to return the new value. + */ + interface Callback { + void onLocaleChanged(@NonNull LocaleList prevLocales, @NonNull LocaleList newLocales); + } + + /** + * Called when {@link InputMethodManagerService} is about to start. + * + * @param context {@link Context} to be used. + * @param callback {@link Callback} for the locale change events. + */ + @AnyThread + static void onStart(@NonNull Context context, @NonNull Callback callback, + @NonNull Handler handler) { + sSystemLocale.set(context.getResources().getConfiguration().getLocales()); + + context.registerReceiver(new LocaleChangeListener(context, callback), + new IntentFilter(Intent.ACTION_LOCALE_CHANGED), null, handler); + } + + private static final class LocaleChangeListener extends BroadcastReceiver { + @NonNull + private final Context mContext; + @NonNull + private final Callback mCallback; + LocaleChangeListener(@NonNull Context context, @NonNull Callback callback) { + mContext = context; + mCallback = callback; + } + + @Override + public void onReceive(Context context, Intent intent) { + if (!Intent.ACTION_LOCALE_CHANGED.equals(intent.getAction())) { + return; + } + final LocaleList newLocales = mContext.getResources().getConfiguration().getLocales(); + final LocaleList prevLocales = sSystemLocale.getAndSet(newLocales); + if (!Objects.equals(newLocales, prevLocales)) { + mCallback.onLocaleChanged(prevLocales, newLocales); + } + } + } +} diff --git a/services/core/java/com/android/server/pm/ComputerEngine.java b/services/core/java/com/android/server/pm/ComputerEngine.java index e5c4ccc73bc3..b2d4a2ca1102 100644 --- a/services/core/java/com/android/server/pm/ComputerEngine.java +++ b/services/core/java/com/android/server/pm/ComputerEngine.java @@ -1500,15 +1500,14 @@ public class ComputerEngine implements Computer { state.getFirstInstallTimeMillis(), ps.getLastUpdateTime(), installedPermissions, grantedPermissions, state, userId, ps); - if (packageInfo == null) { - return null; + if (packageInfo != null) { + packageInfo.packageName = packageInfo.applicationInfo.packageName = + resolveExternalPackageName(p); + return packageInfo; } - - packageInfo.packageName = packageInfo.applicationInfo.packageName = - resolveExternalPackageName(p); - - return packageInfo; - } else if ((flags & (MATCH_UNINSTALLED_PACKAGES | MATCH_ARCHIVED_PACKAGES)) != 0 + } + // TODO(b/314808978): Set ps.setPkg to null during install-archived. + if ((flags & (MATCH_UNINSTALLED_PACKAGES | MATCH_ARCHIVED_PACKAGES)) != 0 && PackageUserStateUtils.isAvailable(state, flags)) { PackageInfo pi = new PackageInfo(); pi.packageName = ps.getPackageName(); @@ -1540,9 +1539,8 @@ public class ComputerEngine implements Computer { + ps.getPackageName() + "]. Provides a minimum info."); } return pi; - } else { - return null; } + return null; } public final PackageInfo getPackageInfo(String packageName, diff --git a/services/core/java/com/android/server/pm/InstallPackageHelper.java b/services/core/java/com/android/server/pm/InstallPackageHelper.java index 1a6529732a02..3e7c8c405816 100644 --- a/services/core/java/com/android/server/pm/InstallPackageHelper.java +++ b/services/core/java/com/android/server/pm/InstallPackageHelper.java @@ -2158,7 +2158,11 @@ final class InstallPackageHelper { } } if (installRequest.getReturnCode() == PackageManager.INSTALL_SUCCEEDED) { - mPm.createArchiveStateIfNeeded(ps, + // If this is an archival installation then we'll initialize the archive status, + // while also marking package as not installed. + // Doing this at the very end of the install as we are using ps.getInstalled + // to figure out which users were changed. + mPm.markPackageAsArchivedIfNeeded(ps, installRequest.getArchivedPackage(), installRequest.getNewUsers()); mPm.updateSequenceNumberLP(ps, installRequest.getNewUsers()); diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java index 5daada94d815..c0c98dedfae3 100644 --- a/services/core/java/com/android/server/pm/PackageManagerService.java +++ b/services/core/java/com/android/server/pm/PackageManagerService.java @@ -1518,8 +1518,8 @@ public class PackageManagerService implements PackageSender, TestUtilityService return archPkg; } - void createArchiveStateIfNeeded(PackageSetting pkgSetting, ArchivedPackageParcel archivePackage, - int[] userIds) { + void markPackageAsArchivedIfNeeded(PackageSetting pkgSetting, + ArchivedPackageParcel archivePackage, int[] userIds) { if (pkgSetting == null || archivePackage == null || archivePackage.archivedActivities == null || userIds == null || userIds.length == 0) { @@ -1541,6 +1541,7 @@ public class PackageManagerService implements PackageSender, TestUtilityService } pkgSetting .modifyUserState(userId) + .setInstalled(false) .setArchiveState(archiveState); } } diff --git a/services/core/java/com/android/server/pm/UserManagerService.java b/services/core/java/com/android/server/pm/UserManagerService.java index 139312170c04..b53a21c9aa1c 100644 --- a/services/core/java/com/android/server/pm/UserManagerService.java +++ b/services/core/java/com/android/server/pm/UserManagerService.java @@ -5137,12 +5137,6 @@ public class UserManagerService extends IUserManager.Stub { mPm.createNewUser(userId, userTypeInstallablePackages, disallowedPackages); t.traceEnd(); - userInfo.partial = false; - synchronized (mPackagesLock) { - writeUserLP(userData); - } - updateUserIds(); - Bundle restrictions = new Bundle(); if (isGuest) { // Guest default restrictions can be modified via setDefaultGuestRestrictions. @@ -5160,6 +5154,12 @@ public class UserManagerService extends IUserManager.Stub { mBaseUserRestrictions.updateRestrictions(userId, restrictions); } + userInfo.partial = false; + synchronized (mPackagesLock) { + writeUserLP(userData); + } + updateUserIds(); + t.traceBegin("PM.onNewUserCreated-" + userId); mPm.onNewUserCreated(userId, /* convertedFromPreCreated= */ false); t.traceEnd(); diff --git a/services/core/java/com/android/server/pm/permission/PermissionManagerService.java b/services/core/java/com/android/server/pm/permission/PermissionManagerService.java index 9610d051db95..d3931a303d0d 100644 --- a/services/core/java/com/android/server/pm/permission/PermissionManagerService.java +++ b/services/core/java/com/android/server/pm/permission/PermissionManagerService.java @@ -278,8 +278,11 @@ public class PermissionManagerService extends IPermissionManager.Stub { private boolean setAutoRevokeExemptedInternal(@NonNull AndroidPackage pkg, boolean exempted, @UserIdInt int userId) { final int packageUid = UserHandle.getUid(userId, pkg.getUid()); + final AttributionSource attributionSource = + new AttributionSource(packageUid, pkg.getPackageName(), null); + if (mAppOpsManager.checkOpNoThrow(AppOpsManager.OP_AUTO_REVOKE_MANAGED_BY_INSTALLER, - packageUid, pkg.getPackageName()) != MODE_ALLOWED) { + attributionSource) != MODE_ALLOWED) { // Allowlist user set - don't override return false; } @@ -330,8 +333,10 @@ public class PermissionManagerService extends IPermissionManager.Stub { final long identity = Binder.clearCallingIdentity(); try { + final AttributionSource attributionSource = + new AttributionSource(packageUid, packageName, null); return mAppOpsManager.checkOpNoThrow( - AppOpsManager.OP_AUTO_REVOKE_PERMISSIONS_IF_UNUSED, packageUid, packageName) + AppOpsManager.OP_AUTO_REVOKE_PERMISSIONS_IF_UNUSED, attributionSource) == MODE_IGNORED; } finally { Binder.restoreCallingIdentity(identity); @@ -1157,9 +1162,11 @@ public class PermissionManagerService extends IPermissionManager.Stub { if (resolvedPackageName == null) { return; } + final AttributionSource resolvedAccessorSource = + accessorSource.withPackageName(resolvedPackageName); + appOpsManager.finishOp(attributionSourceState.token, op, - accessorSource.getUid(), resolvedPackageName, - accessorSource.getAttributionTag()); + resolvedAccessorSource); } else { final AttributionSource resolvedAttributionSource = resolveAttributionSource(context, accessorSource); @@ -1583,16 +1590,19 @@ public class PermissionManagerService extends IPermissionManager.Stub { if (resolvedAccessorPackageName == null) { return AppOpsManager.MODE_ERRORED; } + final AttributionSource resolvedAttributionSource = + accessorSource.withPackageName(resolvedAccessorPackageName); final int opMode = appOpsManager.unsafeCheckOpRawNoThrow(op, - accessorSource.getUid(), resolvedAccessorPackageName); + resolvedAttributionSource); final AttributionSource next = accessorSource.getNext(); if (!selfAccess && opMode == AppOpsManager.MODE_ALLOWED && next != null) { final String resolvedNextPackageName = resolvePackageName(context, next); if (resolvedNextPackageName == null) { return AppOpsManager.MODE_ERRORED; } - return appOpsManager.unsafeCheckOpRawNoThrow(op, next.getUid(), - resolvedNextPackageName); + final AttributionSource resolvedNextAttributionSource = + next.withPackageName(resolvedNextPackageName); + return appOpsManager.unsafeCheckOpRawNoThrow(op, resolvedNextAttributionSource); } return opMode; } else if (startDataDelivery) { @@ -1615,9 +1625,7 @@ public class PermissionManagerService extends IPermissionManager.Stub { // the operation. We return the less permissive of the two and check // the permission op while start the attributed op. if (attributedOp != AppOpsManager.OP_NONE && attributedOp != op) { - checkedOpResult = appOpsManager.checkOpNoThrow(op, - resolvedAttributionSource.getUid(), resolvedAttributionSource - .getPackageName()); + checkedOpResult = appOpsManager.checkOpNoThrow(op, resolvedAttributionSource); if (checkedOpResult == MODE_ERRORED) { return checkedOpResult; } @@ -1626,12 +1634,9 @@ public class PermissionManagerService extends IPermissionManager.Stub { if (selfAccess) { try { startedOpResult = appOpsManager.startOpNoThrow( - chainStartToken, startedOp, - resolvedAttributionSource.getUid(), - resolvedAttributionSource.getPackageName(), - /*startIfModeDefault*/ false, - resolvedAttributionSource.getAttributionTag(), - message, proxyAttributionFlags, attributionChainId); + chainStartToken, startedOp, resolvedAttributionSource, + /*startIfModeDefault*/ false, message, proxyAttributionFlags, + attributionChainId); } catch (SecurityException e) { Slog.w(LOG_TAG, "Datasource " + attributionSource + " protecting data with" + " platform defined runtime permission " @@ -1676,9 +1681,7 @@ public class PermissionManagerService extends IPermissionManager.Stub { // the operation. We return the less permissive of the two and check // the permission op while start the attributed op. if (attributedOp != AppOpsManager.OP_NONE && attributedOp != op) { - checkedOpResult = appOpsManager.checkOpNoThrow(op, - resolvedAttributionSource.getUid(), resolvedAttributionSource - .getPackageName()); + checkedOpResult = appOpsManager.checkOpNoThrow(op, resolvedAttributionSource); if (checkedOpResult == MODE_ERRORED) { return checkedOpResult; } @@ -1692,10 +1695,7 @@ public class PermissionManagerService extends IPermissionManager.Stub { // As a fallback we note a proxy op that blames the app and the datasource. try { notedOpResult = appOpsManager.noteOpNoThrow(notedOp, - resolvedAttributionSource.getUid(), - resolvedAttributionSource.getPackageName(), - resolvedAttributionSource.getAttributionTag(), - message); + resolvedAttributionSource, message); } catch (SecurityException e) { Slog.w(LOG_TAG, "Datasource " + attributionSource + " protecting data with" + " platform defined runtime permission " diff --git a/services/core/java/com/android/server/pm/pkg/PackageUserStateUtils.java b/services/core/java/com/android/server/pm/pkg/PackageUserStateUtils.java index fe80f743ffc3..4b3992e63202 100644 --- a/services/core/java/com/android/server/pm/pkg/PackageUserStateUtils.java +++ b/services/core/java/com/android/server/pm/pkg/PackageUserStateUtils.java @@ -93,8 +93,8 @@ public class PackageUserStateUtils { * this object exists means that the package must be installed or has data on at least one user; * <li> If it is not installed but still has data (i.e., it was previously uninstalled with * {@link PackageManager#DELETE_KEEP_DATA}), return true if the caller requested - * {@link PackageManager#MATCH_UNINSTALLED_PACKAGES} or - * {@link PackageManager#MATCH_ARCHIVED_PACKAGES}; + * {@link PackageManager#MATCH_UNINSTALLED_PACKAGES}. + * Always available for {@link PackageManager#MATCH_ARCHIVED_PACKAGES}. * </ul><p> */ public static boolean isAvailable(@NonNull PackageUserState state, long flags) { @@ -109,11 +109,19 @@ public class PackageUserStateUtils { if (state.isInstalled()) { if (!state.isHidden()) { return true; - } else return matchDataExists; - } else { - // not installed - return matchDataExists && state.dataExists(); + } else { + return matchDataExists; + } } + + // not installed + if (matchUninstalled) { + return state.dataExists(); + } + + // archived or installed as archived + // TODO(b/314808978): Create data folders during install-archived. + return matchArchived; } public static boolean reportIfDebug(boolean result, long flags) { diff --git a/services/core/java/com/android/server/policy/AppOpsPolicy.java b/services/core/java/com/android/server/policy/AppOpsPolicy.java index b83421fe78d7..ecffd382f542 100644 --- a/services/core/java/com/android/server/policy/AppOpsPolicy.java +++ b/services/core/java/com/android/server/policy/AppOpsPolicy.java @@ -50,11 +50,11 @@ import android.util.Log; import android.util.SparseArray; import com.android.internal.annotations.GuardedBy; -import com.android.internal.util.function.HeptFunction; +import com.android.internal.util.function.DodecFunction; +import com.android.internal.util.function.HexConsumer; import com.android.internal.util.function.HexFunction; +import com.android.internal.util.function.OctFunction; import com.android.internal.util.function.QuadFunction; -import com.android.internal.util.function.QuintConsumer; -import com.android.internal.util.function.QuintFunction; import com.android.internal.util.function.UndecFunction; import com.android.server.LocalServices; @@ -230,9 +230,10 @@ public final class AppOpsPolicy implements AppOpsManagerInternal.CheckOpsDelegat @Override public int checkOperation(int code, int uid, String packageName, - @Nullable String attributionTag, boolean raw, - QuintFunction<Integer, Integer, String, String, Boolean, Integer> superImpl) { - return superImpl.apply(code, resolveUid(code, uid), packageName, attributionTag, raw); + @Nullable String attributionTag, int virtualDeviceId, boolean raw, + HexFunction<Integer, Integer, String, String, Integer, Boolean, Integer> superImpl) { + return superImpl.apply(code, resolveUid(code, uid), packageName, attributionTag, + virtualDeviceId, raw); } @Override @@ -243,12 +244,13 @@ public final class AppOpsPolicy implements AppOpsManagerInternal.CheckOpsDelegat @Override public SyncNotedAppOp noteOperation(int code, int uid, @Nullable String packageName, - @Nullable String attributionTag, boolean shouldCollectAsyncNotedOp, @Nullable - String message, boolean shouldCollectMessage, @NonNull HeptFunction<Integer, Integer, - String, String, Boolean, String, Boolean, SyncNotedAppOp> superImpl) { + @Nullable String attributionTag, int virtualDeviceId, + boolean shouldCollectAsyncNotedOp, @Nullable String message, + boolean shouldCollectMessage, @NonNull OctFunction<Integer, Integer, String, String, + Integer, Boolean, String, Boolean, SyncNotedAppOp> superImpl) { return superImpl.apply(resolveDatasourceOp(code, uid, packageName, attributionTag), - resolveUid(code, uid), packageName, attributionTag, shouldCollectAsyncNotedOp, - message, shouldCollectMessage); + resolveUid(code, uid), packageName, attributionTag, virtualDeviceId, + shouldCollectAsyncNotedOp, message, shouldCollectMessage); } @Override @@ -265,16 +267,16 @@ public final class AppOpsPolicy implements AppOpsManagerInternal.CheckOpsDelegat @Override public SyncNotedAppOp startOperation(IBinder token, int code, int uid, - @Nullable String packageName, @Nullable String attributionTag, + @Nullable String packageName, @Nullable String attributionTag, int virtualDeviceId, boolean startIfModeDefault, boolean shouldCollectAsyncNotedOp, String message, boolean shouldCollectMessage, @AttributionFlags int attributionFlags, - int attributionChainId, @NonNull UndecFunction<IBinder, Integer, Integer, String, - String, Boolean, Boolean, String, Boolean, Integer, Integer, - SyncNotedAppOp> superImpl) { + int attributionChainId, @NonNull DodecFunction<IBinder, Integer, Integer, String, + String, Integer, Boolean, Boolean, String, Boolean, Integer, Integer, + SyncNotedAppOp> superImpl) { return superImpl.apply(token, resolveDatasourceOp(code, uid, packageName, attributionTag), - resolveUid(code, uid), packageName, attributionTag, startIfModeDefault, - shouldCollectAsyncNotedOp, message, shouldCollectMessage, attributionFlags, - attributionChainId); + resolveUid(code, uid), packageName, attributionTag, virtualDeviceId, + startIfModeDefault, shouldCollectAsyncNotedOp, message, shouldCollectMessage, + attributionFlags, attributionChainId); } @Override @@ -294,10 +296,10 @@ public final class AppOpsPolicy implements AppOpsManagerInternal.CheckOpsDelegat @Override public void finishOperation(IBinder clientId, int code, int uid, String packageName, - String attributionTag, - @NonNull QuintConsumer<IBinder, Integer, Integer, String, String> superImpl) { + String attributionTag, int virtualDeviceId, + @NonNull HexConsumer<IBinder, Integer, Integer, String, String, Integer> superImpl) { superImpl.accept(clientId, resolveDatasourceOp(code, uid, packageName, attributionTag), - resolveUid(code, uid), packageName, attributionTag); + resolveUid(code, uid), packageName, attributionTag, virtualDeviceId); } @Override diff --git a/services/core/java/com/android/server/policy/DeferredKeyActionExecutor.java b/services/core/java/com/android/server/policy/DeferredKeyActionExecutor.java new file mode 100644 index 000000000000..b531b0eff854 --- /dev/null +++ b/services/core/java/com/android/server/policy/DeferredKeyActionExecutor.java @@ -0,0 +1,163 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.server.policy; + +import android.util.Log; +import android.util.SparseArray; +import android.view.KeyEvent; + +import java.io.PrintWriter; +import java.util.ArrayList; +import java.util.List; + +/** + * A class that is responsible for queueing deferred key actions which can be triggered at a later + * time. + */ +class DeferredKeyActionExecutor { + private static final boolean DEBUG = PhoneWindowManager.DEBUG_INPUT; + private static final String TAG = "DeferredKeyAction"; + + private final SparseArray<TimedActionsBuffer> mBuffers = new SparseArray<>(); + + /** + * Queue a key action which can be triggered at a later time. Note that this method will also + * delete any outdated actions belong to the same key code. + * + * <p>Warning: the queued actions will only be cleaned up lazily when a new gesture downTime is + * recorded. If no new gesture downTime is recorded and the existing gesture is not executable, + * the actions will be kept in the buffer indefinitely. This may cause memory leak if the action + * itself holds references to temporary objects, or if too many actions are queued for the same + * gesture. The risk scales as you track more key codes. Please use this method with caution and + * ensure you only queue small amount of actions with limited size. + * + * <p>If you need to queue a large amount of actions with large size, there are several + * potential solutions to relief the memory leak risks: + * + * <p>1. Add a timeout (e.g. ANR timeout) based clean-up mechanism. + * + * <p>2. Clean-up queued actions when we know they won't be needed. E.g., add a callback when + * the gesture is handled by apps, and clean up queued actions associated with the handled + * gesture. + * + * @param keyCode the key code which triggers the action. + * @param downTime the down time of the key gesture. For multi-press actions, this is the down + * time of the last press. For long-press or very long-press actions, this is the initial + * down time. + * @param action the action that will be triggered at a later time. + */ + public void queueKeyAction(int keyCode, long downTime, Runnable action) { + getActionsBufferWithLazyCleanUp(keyCode, downTime).addAction(action); + } + + /** + * Make actions associated with the given key gesture executable. Actions already queued for the + * given gesture will be executed immediately. Any new actions belonging to this gesture will be + * executed as soon as they get queued. Note that this method will also delete any outdated + * actions belong to the same key code. + * + * @param keyCode the key code of the gesture. + * @param downTime the down time of the gesture. + */ + public void setActionsExecutable(int keyCode, long downTime) { + getActionsBufferWithLazyCleanUp(keyCode, downTime).setExecutable(); + } + + private TimedActionsBuffer getActionsBufferWithLazyCleanUp(int keyCode, long downTime) { + TimedActionsBuffer buffer = mBuffers.get(keyCode); + if (buffer == null || buffer.getDownTime() != downTime) { + if (DEBUG && buffer != null) { + Log.d( + TAG, + "getActionsBufferWithLazyCleanUp: cleaning up gesture actions for key " + + KeyEvent.keyCodeToString(keyCode)); + } + buffer = new TimedActionsBuffer(keyCode, downTime); + mBuffers.put(keyCode, buffer); + } + return buffer; + } + + public void dump(String prefix, PrintWriter pw) { + pw.println(prefix + "Deferred key action executor:"); + if (mBuffers.size() == 0) { + pw.println(prefix + " empty"); + return; + } + for (int i = 0; i < mBuffers.size(); i++) { + mBuffers.valueAt(i).dump(prefix, pw); + } + } + + /** A buffer holding a gesture down time and its corresponding actions. */ + private static class TimedActionsBuffer { + private final List<Runnable> mActions = new ArrayList<>(); + private final int mKeyCode; + private final long mDownTime; + private boolean mExecutable; + + TimedActionsBuffer(int keyCode, long downTime) { + mKeyCode = keyCode; + mDownTime = downTime; + } + + long getDownTime() { + return mDownTime; + } + + void addAction(Runnable action) { + if (mExecutable) { + if (DEBUG) { + Log.i( + TAG, + "addAction: execute action for key " + + KeyEvent.keyCodeToString(mKeyCode)); + } + action.run(); + return; + } + mActions.add(action); + } + + void setExecutable() { + mExecutable = true; + if (DEBUG && !mActions.isEmpty()) { + Log.i( + TAG, + "setExecutable: execute actions for key " + + KeyEvent.keyCodeToString(mKeyCode)); + } + for (Runnable action : mActions) { + action.run(); + } + mActions.clear(); + } + + void dump(String prefix, PrintWriter pw) { + if (mExecutable) { + pw.println(prefix + " " + KeyEvent.keyCodeToString(mKeyCode) + ": executable"); + } else { + pw.println( + prefix + + " " + + KeyEvent.keyCodeToString(mKeyCode) + + ": " + + mActions.size() + + " actions queued"); + } + } + } +} diff --git a/services/core/java/com/android/server/speech/RemoteSpeechRecognitionService.java b/services/core/java/com/android/server/speech/RemoteSpeechRecognitionService.java index c2666f63d7a6..bb5a697114d3 100644 --- a/services/core/java/com/android/server/speech/RemoteSpeechRecognitionService.java +++ b/services/core/java/com/android/server/speech/RemoteSpeechRecognitionService.java @@ -206,22 +206,16 @@ final class RemoteSpeechRecognitionService extends ServiceConnector.Impl<IRecogn synchronized (mLock) { ClientState clientState = mClients.get(listener.asBinder()); - if (clientState == null) { - if (DEBUG) { - Slog.w(TAG, "#cancel called with no preceding #startListening - ignoring."); - } - return; + if (clientState != null) { + clientState.mRecordingInProgress = false; + // Temporary reference to allow for resetting mDelegatingListener to null. + final IRecognitionListener delegatingListener = clientState.mDelegatingListener; + run(service -> service.cancel(delegatingListener, isShutdown)); } - clientState.mRecordingInProgress = false; - - // Temporary reference to allow for resetting the hard link mDelegatingListener to null. - final IRecognitionListener delegatingListener = clientState.mDelegatingListener; - run(service -> service.cancel(delegatingListener, isShutdown)); // If shutdown, remove the client info from the map. Unbind if that was the last client. if (isShutdown) { removeClient(listener); - if (mClients.isEmpty()) { if (DEBUG) { Slog.d(TAG, "Unbinding from the recognition service."); diff --git a/services/core/java/com/android/server/statusbar/StatusBarManagerInternal.java b/services/core/java/com/android/server/statusbar/StatusBarManagerInternal.java index a5123311d499..b271a03c109d 100644 --- a/services/core/java/com/android/server/statusbar/StatusBarManagerInternal.java +++ b/services/core/java/com/android/server/statusbar/StatusBarManagerInternal.java @@ -196,10 +196,10 @@ public interface StatusBarManagerInternal { void hideToast(String packageName, IBinder token); /** - * @see com.android.internal.statusbar.IStatusBar#requestWindowMagnificationConnection(boolean + * @see com.android.internal.statusbar.IStatusBar#requestMagnificationConnection(boolean * request) */ - boolean requestWindowMagnificationConnection(boolean request); + boolean requestMagnificationConnection(boolean request); /** * @see com.android.internal.statusbar.IStatusBar#setNavigationBarLumaSamplingEnabled(int, diff --git a/services/core/java/com/android/server/statusbar/StatusBarManagerService.java b/services/core/java/com/android/server/statusbar/StatusBarManagerService.java index 7c51e7b84132..b21721ad7b45 100644 --- a/services/core/java/com/android/server/statusbar/StatusBarManagerService.java +++ b/services/core/java/com/android/server/statusbar/StatusBarManagerService.java @@ -760,11 +760,11 @@ public class StatusBarManagerService extends IStatusBarService.Stub implements D } @Override - public boolean requestWindowMagnificationConnection(boolean request) { + public boolean requestMagnificationConnection(boolean request) { IStatusBar bar = mBar; if (bar != null) { try { - bar.requestWindowMagnificationConnection(request); + bar.requestMagnificationConnection(request); return true; } catch (RemoteException ex) { } } diff --git a/services/core/java/com/android/server/vibrator/VibratorControlService.java b/services/core/java/com/android/server/vibrator/VibratorControlService.java new file mode 100644 index 000000000000..2eeb903bb551 --- /dev/null +++ b/services/core/java/com/android/server/vibrator/VibratorControlService.java @@ -0,0 +1,105 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.vibrator; + +import android.annotation.NonNull; +import android.annotation.SuppressLint; +import android.frameworks.vibrator.IVibratorControlService; +import android.frameworks.vibrator.IVibratorController; +import android.frameworks.vibrator.VibrationParam; +import android.os.IBinder; +import android.os.RemoteException; +import android.util.Slog; + +import java.util.Objects; + +/** + * Implementation of {@link IVibratorControlService} which allows the registration of + * {@link IVibratorController} to set and receive vibration params. + * + * @hide + */ +public final class VibratorControlService extends IVibratorControlService.Stub { + private static final String TAG = "VibratorControlService"; + + private final VibratorControllerHolder mVibratorControllerHolder; + private final Object mLock; + + public VibratorControlService(VibratorControllerHolder vibratorControllerHolder, Object lock) { + mVibratorControllerHolder = vibratorControllerHolder; + mLock = lock; + } + + @Override + public void registerVibratorController(IVibratorController controller) + throws RemoteException { + synchronized (mLock) { + mVibratorControllerHolder.setVibratorController(controller); + } + } + + @Override + public void unregisterVibratorController(@NonNull IVibratorController controller) + throws RemoteException { + Objects.requireNonNull(controller); + + synchronized (mLock) { + if (mVibratorControllerHolder.getVibratorController() == null) { + Slog.w(TAG, "Received request to unregister IVibratorController = " + + controller + ", but no controller was previously registered. Request " + + "Ignored."); + return; + } + if (!Objects.equals(mVibratorControllerHolder.getVibratorController().asBinder(), + controller.asBinder())) { + Slog.wtf(TAG, "Failed to unregister IVibratorController. The provided " + + "controller doesn't match the registered one. " + this); + return; + } + mVibratorControllerHolder.setVibratorController(null); + } + } + + @Override + public void setVibrationParams( + @SuppressLint("ArrayReturn") VibrationParam[] params, IVibratorController token) + throws RemoteException { + // TODO(b/305939964): Add set vibration implementation. + } + + @Override + public void clearVibrationParams(int types, IVibratorController token) throws RemoteException { + // TODO(b/305939964): Add clear vibration implementation. + } + + @Override + public void onRequestVibrationParamsComplete( + IBinder requestToken, @SuppressLint("ArrayReturn") VibrationParam[] result) + throws RemoteException { + // TODO(305942827): Cache the vibration params in VibrationScaler + } + + @Override + public int getInterfaceVersion() throws RemoteException { + return this.VERSION; + } + + @Override + public String getInterfaceHash() throws RemoteException { + return this.HASH; + } +} diff --git a/services/core/java/com/android/server/vibrator/VibratorControllerHolder.java b/services/core/java/com/android/server/vibrator/VibratorControllerHolder.java new file mode 100644 index 000000000000..63e69db9480f --- /dev/null +++ b/services/core/java/com/android/server/vibrator/VibratorControllerHolder.java @@ -0,0 +1,70 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.vibrator; + +import android.annotation.NonNull; +import android.frameworks.vibrator.IVibratorController; +import android.os.IBinder; +import android.os.RemoteException; +import android.util.Slog; + +/** + * Holder class for {@link IVibratorController}. + * + * @hide + */ +public final class VibratorControllerHolder implements IBinder.DeathRecipient { + private static final String TAG = "VibratorControllerHolder"; + + private IVibratorController mVibratorController; + + public IVibratorController getVibratorController() { + return mVibratorController; + } + + /** + * Sets the {@link IVibratorController} in {@link VibratorControllerHolder} to the new + * controller. This will also take care of registering and unregistering death notifications + * for the cached {@link IVibratorController}. + */ + public void setVibratorController(IVibratorController controller) { + try { + if (mVibratorController != null) { + mVibratorController.asBinder().unlinkToDeath(this, 0); + } + mVibratorController = controller; + if (mVibratorController != null) { + mVibratorController.asBinder().linkToDeath(this, 0); + } + } catch (RemoteException e) { + Slog.wtf(TAG, "Failed to set IVibratorController: " + this, e); + } + } + + @Override + public void binderDied(@NonNull IBinder deadBinder) { + if (deadBinder == mVibratorController.asBinder()) { + setVibratorController(null); + } + } + + @Override + public void binderDied() { + // Should not be used as binderDied(IBinder who) is overridden. + Slog.wtf(TAG, "binderDied() called unexpectedly."); + } +} diff --git a/services/core/java/com/android/server/vibrator/VibratorManagerService.java b/services/core/java/com/android/server/vibrator/VibratorManagerService.java index cf33cc5f43bd..d5044d9bc660 100644 --- a/services/core/java/com/android/server/vibrator/VibratorManagerService.java +++ b/services/core/java/com/android/server/vibrator/VibratorManagerService.java @@ -53,6 +53,7 @@ import android.os.Trace; import android.os.VibrationAttributes; import android.os.VibrationEffect; import android.os.VibratorInfo; +import android.os.vibrator.Flags; import android.os.vibrator.PrebakedSegment; import android.os.vibrator.VibrationEffectSegment; import android.os.vibrator.VibratorInfoFactory; @@ -87,10 +88,13 @@ import java.util.Objects; import java.util.function.Consumer; import java.util.function.Function; + /** System implementation of {@link IVibratorManagerService}. */ public class VibratorManagerService extends IVibratorManagerService.Stub { private static final String TAG = "VibratorManagerService"; private static final String EXTERNAL_VIBRATOR_SERVICE = "external_vibrator_service"; + private static final String VIBRATOR_CONTROL_SERVICE = + "android.frameworks.vibrator.IVibratorControlService/default"; private static final boolean DEBUG = false; private static final VibrationAttributes DEFAULT_ATTRIBUTES = new VibrationAttributes.Builder().build(); @@ -269,6 +273,10 @@ public class VibratorManagerService extends IVibratorManagerService.Stub { context.registerReceiver(mIntentReceiver, filter, Context.RECEIVER_NOT_EXPORTED); injector.addService(EXTERNAL_VIBRATOR_SERVICE, new ExternalVibratorService()); + if (Flags.adaptiveHapticsEnabled()) { + injector.addService(VIBRATOR_CONTROL_SERVICE, + new VibratorControlService(new VibratorControllerHolder(), mLock)); + } } /** Finish initialization at boot phase {@link SystemService#PHASE_SYSTEM_SERVICES_READY}. */ diff --git a/services/manifest_services.xml b/services/manifest_services.xml index 76389154a885..e2fdfe9e8e47 100644 --- a/services/manifest_services.xml +++ b/services/manifest_services.xml @@ -4,4 +4,14 @@ <version>1</version> <fqname>IAltitudeService/default</fqname> </hal> + <hal format="aidl"> + <name>android.frameworks.vibrator</name> + <version>1</version> + <fqname>IVibratorController/default</fqname> + </hal> + <hal format="aidl"> + <name>android.frameworks.vibrator</name> + <version>1</version> + <fqname>IVibratorControlService/default</fqname> + </hal> </manifest> diff --git a/services/tests/mockingservicestests/src/com/android/server/pm/UserManagerServiceTest.java b/services/tests/mockingservicestests/src/com/android/server/pm/UserManagerServiceTest.java index 305569edd2fa..fd6aa0c1ffab 100644 --- a/services/tests/mockingservicestests/src/com/android/server/pm/UserManagerServiceTest.java +++ b/services/tests/mockingservicestests/src/com/android/server/pm/UserManagerServiceTest.java @@ -27,6 +27,7 @@ import static com.google.common.truth.Truth.assertThat; import static com.google.common.truth.Truth.assertWithMessage; import static org.junit.Assert.assertThrows; +import static org.junit.Assert.assertTrue; import static org.mockito.ArgumentMatchers.anyBoolean; import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.ArgumentMatchers.eq; @@ -47,10 +48,12 @@ import android.provider.Settings; import android.util.Log; import android.util.Pair; import android.util.SparseArray; +import android.util.Xml; import androidx.test.annotation.UiThreadTest; import com.android.internal.widget.LockSettingsInternal; +import com.android.modules.utils.TypedXmlPullParser; import com.android.modules.utils.testing.ExtendedMockitoRule; import com.android.server.LocalServices; import com.android.server.am.UserState; @@ -62,8 +65,12 @@ import org.junit.Before; import org.junit.Rule; import org.junit.Test; import org.mockito.Mock; +import org.xmlpull.v1.XmlPullParser; +import org.xmlpull.v1.XmlPullParserException; import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; /** * Run as {@code atest FrameworksMockingServicesTests:com.android.server.pm.UserManagerServiceTest} @@ -96,6 +103,12 @@ public final class UserManagerServiceTest { */ private static final int PROFILE_USER_ID = 643; + private static final String USER_INFO_DIR = "system" + File.separator + "users"; + + private static final String XML_SUFFIX = ".xml"; + + private static final String TAG_RESTRICTIONS = "restrictions"; + @Rule public final ExtendedMockitoRule mExtendedMockitoRule = new ExtendedMockitoRule.Builder(this) .spyStatic(UserManager.class) @@ -530,6 +543,48 @@ public final class UserManagerServiceTest { assertThat(user1.name.length()).isEqualTo(4); } + @Test + public void testDefaultRestrictionsArePersistedAfterCreateUser() + throws IOException, XmlPullParserException { + UserInfo user = mUms.createUserWithThrow("Test", USER_TYPE_FULL_SECONDARY, 0); + assertTrue(hasRestrictionsInUserXMLFile(user.id)); + } + + /** + * Returns true if the user's XML file has Default restrictions + * @param userId Id of the user. + */ + private boolean hasRestrictionsInUserXMLFile(int userId) + throws IOException, XmlPullParserException { + FileInputStream is = new FileInputStream(getUserXmlFile(userId)); + final TypedXmlPullParser parser = Xml.resolvePullParser(is); + + int type; + while ((type = parser.next()) != XmlPullParser.START_TAG + && type != XmlPullParser.END_DOCUMENT) { + // Skip + } + + if (type != XmlPullParser.START_TAG) { + return false; + } + + int outerDepth = parser.getDepth(); + while ((type = parser.next()) != XmlPullParser.END_DOCUMENT + && (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) { + if (TAG_RESTRICTIONS.equals(parser.getName())) { + return true; + } + } + + return false; + } + + private File getUserXmlFile(int userId) { + File file = new File(mTestDir, USER_INFO_DIR); + return new File(file, userId + XML_SUFFIX); + } + private String generateLongString() { String partialString = "Test Name Test Name Test Name Test Name Test Name Test Name Test " + "Name Test Name Test Name Test Name "; //String of length 100 diff --git a/services/tests/servicestests/src/com/android/server/accessibility/magnification/FullScreenMagnificationControllerTest.java b/services/tests/servicestests/src/com/android/server/accessibility/magnification/FullScreenMagnificationControllerTest.java index 1b024982c41f..52726caba2bf 100644 --- a/services/tests/servicestests/src/com/android/server/accessibility/magnification/FullScreenMagnificationControllerTest.java +++ b/services/tests/servicestests/src/com/android/server/accessibility/magnification/FullScreenMagnificationControllerTest.java @@ -19,7 +19,7 @@ package com.android.server.accessibility.magnification; import static android.accessibilityservice.MagnificationConfig.MAGNIFICATION_MODE_FULLSCREEN; import static com.android.server.accessibility.magnification.FullScreenMagnificationController.MagnificationInfoChangedCallback; -import static com.android.server.accessibility.magnification.MockWindowMagnificationConnection.TEST_DISPLAY; +import static com.android.server.accessibility.magnification.MockMagnificationConnection.TEST_DISPLAY; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; diff --git a/services/tests/servicestests/src/com/android/server/accessibility/magnification/MagnificationConnectionManagerTest.java b/services/tests/servicestests/src/com/android/server/accessibility/magnification/MagnificationConnectionManagerTest.java index 3843e2507df6..a7cf361c7bc1 100644 --- a/services/tests/servicestests/src/com/android/server/accessibility/magnification/MagnificationConnectionManagerTest.java +++ b/services/tests/servicestests/src/com/android/server/accessibility/magnification/MagnificationConnectionManagerTest.java @@ -16,8 +16,8 @@ package com.android.server.accessibility.magnification; -import static com.android.server.accessibility.magnification.MockWindowMagnificationConnection.TEST_DISPLAY; -import static com.android.server.accessibility.magnification.MockWindowMagnificationConnection.TEST_DISPLAY_2; +import static com.android.server.accessibility.magnification.MockMagnificationConnection.TEST_DISPLAY; +import static com.android.server.accessibility.magnification.MockMagnificationConnection.TEST_DISPLAY_2; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyBoolean; @@ -79,7 +79,7 @@ public class MagnificationConnectionManagerTest { private static final int CURRENT_USER_ID = UserHandle.USER_SYSTEM; private static final int SERVICE_ID = 1; - private MockWindowMagnificationConnection mMockConnection; + private MockMagnificationConnection mMockConnection; @Mock private Context mContext; @Mock @@ -99,7 +99,7 @@ public class MagnificationConnectionManagerTest { LocalServices.removeServiceForTest(StatusBarManagerInternal.class); LocalServices.addService(StatusBarManagerInternal.class, mMockStatusBarManagerInternal); mResolver = new MockContentResolver(); - mMockConnection = new MockWindowMagnificationConnection(); + mMockConnection = new MockMagnificationConnection(); mMagnificationConnectionManager = new MagnificationConnectionManager(mContext, new Object(), mMockCallback, mMockTrace, new MagnificationScaleProvider(mContext)); @@ -128,7 +128,7 @@ public class MagnificationConnectionManagerTest { connect ? mMockConnection.getConnection() : null); } return true; - }).when(mMockStatusBarManagerInternal).requestWindowMagnificationConnection(anyBoolean()); + }).when(mMockStatusBarManagerInternal).requestMagnificationConnection(anyBoolean()); } @Test @@ -169,8 +169,7 @@ public class MagnificationConnectionManagerTest { public void setSecondConnectionAndFormerConnectionBinderDead_hasWrapperAndNotCallUnlinkToDeath() throws RemoteException { mMagnificationConnectionManager.setConnection(mMockConnection.getConnection()); - MockWindowMagnificationConnection secondConnection = - new MockWindowMagnificationConnection(); + MockMagnificationConnection secondConnection = new MockMagnificationConnection(); mMagnificationConnectionManager.setConnection(secondConnection.getConnection()); mMockConnection.getDeathRecipient().binderDied(); @@ -620,13 +619,13 @@ public class MagnificationConnectionManagerTest { assertTrue(mMagnificationConnectionManager.requestConnection(false)); verify(mMockConnection.getConnection()).disableWindowMagnification(TEST_DISPLAY, null); - verify(mMockStatusBarManagerInternal).requestWindowMagnificationConnection(false); + verify(mMockStatusBarManagerInternal).requestMagnificationConnection(false); } @Test public void requestConnection_requestWindowMagnificationConnection() throws RemoteException { assertTrue(mMagnificationConnectionManager.requestConnection(true)); - verify(mMockStatusBarManagerInternal).requestWindowMagnificationConnection(true); + verify(mMockStatusBarManagerInternal).requestMagnificationConnection(true); } @Test diff --git a/services/tests/servicestests/src/com/android/server/accessibility/magnification/MagnificationConnectionWrapperTest.java b/services/tests/servicestests/src/com/android/server/accessibility/magnification/MagnificationConnectionWrapperTest.java index 8f85f11b7c49..8fdd884380d5 100644 --- a/services/tests/servicestests/src/com/android/server/accessibility/magnification/MagnificationConnectionWrapperTest.java +++ b/services/tests/servicestests/src/com/android/server/accessibility/magnification/MagnificationConnectionWrapperTest.java @@ -24,8 +24,8 @@ import static org.mockito.Mockito.verify; import android.os.RemoteException; import android.provider.Settings; import android.view.Display; +import android.view.accessibility.IMagnificationConnection; import android.view.accessibility.IRemoteMagnificationAnimationCallback; -import android.view.accessibility.IWindowMagnificationConnection; import android.view.accessibility.IWindowMagnificationConnectionCallback; import android.view.accessibility.MagnificationAnimationCallback; @@ -45,7 +45,7 @@ public class MagnificationConnectionWrapperTest { private static final int TEST_DISPLAY = Display.DEFAULT_DISPLAY; - private IWindowMagnificationConnection mConnection; + private IMagnificationConnection mConnection; @Mock private AccessibilityTraceManager mTrace; @Mock @@ -53,14 +53,14 @@ public class MagnificationConnectionWrapperTest { @Mock private MagnificationAnimationCallback mAnimationCallback; - private MockWindowMagnificationConnection mMockWindowMagnificationConnection; + private MockMagnificationConnection mMockMagnificationConnection; private MagnificationConnectionWrapper mConnectionWrapper; @Before public void setUp() throws RemoteException { MockitoAnnotations.initMocks(this); - mMockWindowMagnificationConnection = new MockWindowMagnificationConnection(); - mConnection = mMockWindowMagnificationConnection.getConnection(); + mMockMagnificationConnection = new MockMagnificationConnection(); + mConnection = mMockMagnificationConnection.getConnection(); mConnectionWrapper = new MagnificationConnectionWrapper(mConnection, mTrace); } diff --git a/services/tests/servicestests/src/com/android/server/accessibility/magnification/MagnificationControllerTest.java b/services/tests/servicestests/src/com/android/server/accessibility/magnification/MagnificationControllerTest.java index e8cdf35dee13..28d07f995ad3 100644 --- a/services/tests/servicestests/src/com/android/server/accessibility/magnification/MagnificationControllerTest.java +++ b/services/tests/servicestests/src/com/android/server/accessibility/magnification/MagnificationControllerTest.java @@ -130,7 +130,7 @@ public class MagnificationControllerTest { @Captor private ArgumentCaptor<MagnificationAnimationCallback> mCallbackArgumentCaptor; - private MockWindowMagnificationConnection mMockConnection; + private MockMagnificationConnection mMockConnection; private MagnificationConnectionManager mMagnificationConnectionManager; private MockContentResolver mMockResolver; private MagnificationController mMagnificationController; @@ -208,7 +208,7 @@ public class MagnificationControllerTest { mMagnificationConnectionManager = spy( new MagnificationConnectionManager(mContext, globalLock, mWindowMagnificationCallbackDelegate, mTraceManager, mScaleProvider)); - mMockConnection = new MockWindowMagnificationConnection(true); + mMockConnection = new MockMagnificationConnection(true); mMagnificationConnectionManager.setConnection(mMockConnection.getConnection()); mMagnificationController = spy(new MagnificationController(mService, globalLock, mContext, diff --git a/services/tests/servicestests/src/com/android/server/accessibility/magnification/MockWindowMagnificationConnection.java b/services/tests/servicestests/src/com/android/server/accessibility/magnification/MockMagnificationConnection.java index 4c03ec34f074..3d3d0b7aa07a 100644 --- a/services/tests/servicestests/src/com/android/server/accessibility/magnification/MockWindowMagnificationConnection.java +++ b/services/tests/servicestests/src/com/android/server/accessibility/magnification/MockMagnificationConnection.java @@ -31,8 +31,8 @@ import android.os.Binder; import android.os.IBinder; import android.os.RemoteException; import android.view.Display; +import android.view.accessibility.IMagnificationConnection; import android.view.accessibility.IRemoteMagnificationAnimationCallback; -import android.view.accessibility.IWindowMagnificationConnection; import android.view.accessibility.IWindowMagnificationConnectionCallback; import java.util.ArrayList; @@ -42,12 +42,12 @@ import java.util.List; * Mocks the basic logic of window magnification in System UI. We assume the screen size is * unlimited, so source bounds is always on the center of the mirror window bounds. */ -class MockWindowMagnificationConnection { +class MockMagnificationConnection { public static final int TEST_DISPLAY = Display.DEFAULT_DISPLAY; public static final int TEST_DISPLAY_2 = Display.DEFAULT_DISPLAY + 1; private final List mValidDisplayIds; - private final IWindowMagnificationConnection mConnection; + private final IMagnificationConnection mConnection; private final Binder mBinder; private final boolean mSuspendCallback; private boolean mHasPendingCallback = false; @@ -60,17 +60,17 @@ class MockWindowMagnificationConnection { private Rect mSourceBounds = new Rect(); private IRemoteMagnificationAnimationCallback mAnimationCallback; - MockWindowMagnificationConnection() throws RemoteException { + MockMagnificationConnection() throws RemoteException { this(false); } - MockWindowMagnificationConnection(boolean suspendCallback) throws RemoteException { + MockMagnificationConnection(boolean suspendCallback) throws RemoteException { mValidDisplayIds = new ArrayList(); mValidDisplayIds.add(TEST_DISPLAY); mValidDisplayIds.add(TEST_DISPLAY_2); mSuspendCallback = suspendCallback; - mConnection = mock(IWindowMagnificationConnection.class); + mConnection = mock(IMagnificationConnection.class); mBinder = mock(Binder.class); when(mConnection.asBinder()).thenReturn(mBinder); doAnswer((invocation) -> { @@ -154,7 +154,7 @@ class MockWindowMagnificationConnection { } } - IWindowMagnificationConnection getConnection() { + IMagnificationConnection getConnection() { return mConnection; } diff --git a/services/tests/servicestests/src/com/android/server/accessibility/magnification/WindowMagnificationGestureHandlerTest.java b/services/tests/servicestests/src/com/android/server/accessibility/magnification/WindowMagnificationGestureHandlerTest.java index c4be51f9ecbd..a3b67aef551a 100644 --- a/services/tests/servicestests/src/com/android/server/accessibility/magnification/WindowMagnificationGestureHandlerTest.java +++ b/services/tests/servicestests/src/com/android/server/accessibility/magnification/WindowMagnificationGestureHandlerTest.java @@ -86,14 +86,14 @@ public class WindowMagnificationGestureHandlerTest { public static final float DEFAULT_TAP_X = 301; public static final float DEFAULT_TAP_Y = 299; public static final PointF DEFAULT_POINT = new PointF(DEFAULT_TAP_X, DEFAULT_TAP_Y); - private static final int DISPLAY_0 = MockWindowMagnificationConnection.TEST_DISPLAY; + private static final int DISPLAY_0 = MockMagnificationConnection.TEST_DISPLAY; @Rule public final TestableContext mContext = new TestableContext( InstrumentationRegistry.getInstrumentation().getContext()); private MagnificationConnectionManager mMagnificationConnectionManager; - private MockWindowMagnificationConnection mMockConnection; + private MockMagnificationConnection mMockConnection; private SpyWindowMagnificationGestureHandler mWindowMagnificationGestureHandler; private WindowMagnificationGestureHandler mMockWindowMagnificationGestureHandler; @Mock @@ -107,7 +107,7 @@ public class WindowMagnificationGestureHandlerTest { mMagnificationConnectionManager = new MagnificationConnectionManager(mContext, new Object(), mock(MagnificationConnectionManager.Callback.class), mMockTrace, new MagnificationScaleProvider(mContext)); - mMockConnection = new MockWindowMagnificationConnection(); + mMockConnection = new MockMagnificationConnection(); mWindowMagnificationGestureHandler = new SpyWindowMagnificationGestureHandler( mContext, mMagnificationConnectionManager, mMockTrace, mMockCallback, /** detectSingleFingerTripleTap= */ true, /** detectTwoFingerTripleTap= */ true, diff --git a/services/tests/servicestests/src/com/android/server/companion/virtual/GenericWindowPolicyControllerTest.java b/services/tests/servicestests/src/com/android/server/companion/virtual/GenericWindowPolicyControllerTest.java index b732d38aefe7..33559107dfbb 100644 --- a/services/tests/servicestests/src/com/android/server/companion/virtual/GenericWindowPolicyControllerTest.java +++ b/services/tests/servicestests/src/com/android/server/companion/virtual/GenericWindowPolicyControllerTest.java @@ -26,10 +26,12 @@ import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.after; import static org.mockito.Mockito.doNothing; import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; +import static org.mockito.Mockito.timeout; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; @@ -70,6 +72,7 @@ import java.util.Set; @RunWith(AndroidJUnit4.class) public class GenericWindowPolicyControllerTest { + private static final int TIMEOUT_MILLIS = 500; private static final int DISPLAY_ID = Display.DEFAULT_DISPLAY + 1; private static final int TEST_UID = 1234567; private static final String DISPLAY_CATEGORY = "com.display.category"; @@ -134,7 +137,7 @@ public class GenericWindowPolicyControllerTest { GenericWindowPolicyController gwpc = createGwpc(); assertThat(gwpc.isEnteringPipAllowed(TEST_UID)).isFalse(); - verify(mPipBlockedCallback).onEnteringPipBlocked(TEST_UID); + verify(mPipBlockedCallback, timeout(TIMEOUT_MILLIS)).onEnteringPipBlocked(TEST_UID); } @Test @@ -144,7 +147,7 @@ public class GenericWindowPolicyControllerTest { Arrays.asList(WindowConfiguration.WINDOWING_MODE_FULLSCREEN, WindowConfiguration.WINDOWING_MODE_PINNED))); assertThat(gwpc.isEnteringPipAllowed(TEST_UID)).isTrue(); - verify(mPipBlockedCallback, never()).onEnteringPipBlocked(TEST_UID); + verify(mPipBlockedCallback, after(TIMEOUT_MILLIS).never()).onEnteringPipBlocked(TEST_UID); } @Test @@ -496,7 +499,7 @@ public class GenericWindowPolicyControllerTest { gwpc.onRunningAppsChanged(uids); assertThat(gwpc.getRunningAppsChangedListenersSizeForTesting()).isEqualTo(1); - verify(mRunningAppsChangedListener).onRunningAppsChanged(uids); + verify(mRunningAppsChangedListener, timeout(TIMEOUT_MILLIS)).onRunningAppsChanged(uids); } @Test @@ -508,7 +511,7 @@ public class GenericWindowPolicyControllerTest { gwpc.onRunningAppsChanged(uids); assertThat(gwpc.getRunningAppsChangedListenersSizeForTesting()).isEqualTo(0); - verify(mActivityListener).onDisplayEmpty(DISPLAY_ID); + verify(mActivityListener, timeout(TIMEOUT_MILLIS)).onDisplayEmpty(DISPLAY_ID); } @Test @@ -519,7 +522,8 @@ public class GenericWindowPolicyControllerTest { gwpc.onRunningAppsChanged(uids); assertThat(gwpc.getRunningAppsChangedListenersSizeForTesting()).isEqualTo(0); - verify(mRunningAppsChangedListener, never()).onRunningAppsChanged(uids); + verify(mRunningAppsChangedListener, after(TIMEOUT_MILLIS).never()) + .onRunningAppsChanged(uids); } @Test @@ -532,7 +536,8 @@ public class GenericWindowPolicyControllerTest { gwpc.onRunningAppsChanged(uids); assertThat(gwpc.getRunningAppsChangedListenersSizeForTesting()).isEqualTo(0); - verify(mRunningAppsChangedListener, never()).onRunningAppsChanged(uids); + verify(mRunningAppsChangedListener, after(TIMEOUT_MILLIS).never()) + .onRunningAppsChanged(uids); } @Test @@ -582,7 +587,8 @@ public class GenericWindowPolicyControllerTest { assertThat(gwpc.canActivityBeLaunched(activityInfo, intent, WindowConfiguration.WINDOWING_MODE_FULLSCREEN, DISPLAY_ID, /*isNewTask=*/false)) .isTrue(); - verify(mIntentListenerCallback).shouldInterceptIntent(any(Intent.class)); + verify(mIntentListenerCallback, timeout(TIMEOUT_MILLIS)) + .shouldInterceptIntent(any(Intent.class)); } @Test @@ -590,7 +596,7 @@ public class GenericWindowPolicyControllerTest { GenericWindowPolicyController gwpc = createGwpc(); gwpc.onTopActivityChanged(null, 0, 0); - verify(mActivityListener, never()) + verify(mActivityListener, after(TIMEOUT_MILLIS).never()) .onTopActivityChanged(anyInt(), any(ComponentName.class), anyInt()); } @@ -601,7 +607,7 @@ public class GenericWindowPolicyControllerTest { gwpc.setDisplayId(DISPLAY_ID, /* isMirrorDisplay= */ false); gwpc.onTopActivityChanged(BLOCKED_COMPONENT, 0, userId); - verify(mActivityListener) + verify(mActivityListener, timeout(TIMEOUT_MILLIS)) .onTopActivityChanged(eq(DISPLAY_ID), eq(BLOCKED_COMPONENT), eq(userId)); } @@ -618,8 +624,8 @@ public class GenericWindowPolicyControllerTest { assertThat(gwpc.keepActivityOnWindowFlagsChanged(activityInfo, 0, 0)).isTrue(); - verify(mSecureWindowCallback, never()).onSecureWindowShown(DISPLAY_ID, - activityInfo.applicationInfo.uid); + verify(mSecureWindowCallback, after(TIMEOUT_MILLIS).never()) + .onSecureWindowShown(DISPLAY_ID, activityInfo.applicationInfo.uid); verify(mActivityBlockedCallback, never()).onActivityBlocked(DISPLAY_ID, activityInfo); } @@ -636,9 +642,10 @@ public class GenericWindowPolicyControllerTest { assertThat(gwpc.keepActivityOnWindowFlagsChanged(activityInfo, FLAG_SECURE, 0)).isTrue(); - verify(mSecureWindowCallback).onSecureWindowShown(DISPLAY_ID, + verify(mSecureWindowCallback, timeout(TIMEOUT_MILLIS)).onSecureWindowShown(DISPLAY_ID, activityInfo.applicationInfo.uid); - verify(mActivityBlockedCallback, never()).onActivityBlocked(DISPLAY_ID, activityInfo); + verify(mActivityBlockedCallback, after(TIMEOUT_MILLIS).never()) + .onActivityBlocked(DISPLAY_ID, activityInfo); } @Test @@ -655,8 +662,8 @@ public class GenericWindowPolicyControllerTest { assertThat(gwpc.keepActivityOnWindowFlagsChanged(activityInfo, 0, SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS)).isTrue(); - verify(mSecureWindowCallback, never()).onSecureWindowShown(DISPLAY_ID, - activityInfo.applicationInfo.uid); + verify(mSecureWindowCallback, after(TIMEOUT_MILLIS).never()) + .onSecureWindowShown(DISPLAY_ID, activityInfo.applicationInfo.uid); verify(mActivityBlockedCallback, never()).onActivityBlocked(DISPLAY_ID, activityInfo); } @@ -882,7 +889,8 @@ public class GenericWindowPolicyControllerTest { assertThat(gwpc.canActivityBeLaunched(activityInfo, null, windowingMode, fromDisplay, isNewTask)).isTrue(); - verify(mActivityBlockedCallback, never()).onActivityBlocked(fromDisplay, activityInfo); + verify(mActivityBlockedCallback, after(TIMEOUT_MILLIS).never()) + .onActivityBlocked(fromDisplay, activityInfo); verify(mIntentListenerCallback, never()).shouldInterceptIntent(any(Intent.class)); } @@ -897,8 +905,10 @@ public class GenericWindowPolicyControllerTest { assertThat(gwpc.canActivityBeLaunched(activityInfo, null, windowingMode, fromDisplay, isNewTask)).isFalse(); - verify(mActivityBlockedCallback).onActivityBlocked(fromDisplay, activityInfo); - verify(mIntentListenerCallback, never()).shouldInterceptIntent(any(Intent.class)); + verify(mActivityBlockedCallback, timeout(TIMEOUT_MILLIS)) + .onActivityBlocked(fromDisplay, activityInfo); + verify(mIntentListenerCallback, after(TIMEOUT_MILLIS).never()) + .shouldInterceptIntent(any(Intent.class)); } private void assertNoActivityLaunched(GenericWindowPolicyController gwpc, int fromDisplay, @@ -907,7 +917,8 @@ public class GenericWindowPolicyControllerTest { WindowConfiguration.WINDOWING_MODE_FULLSCREEN, DISPLAY_ID, true)) .isFalse(); - verify(mActivityBlockedCallback, never()).onActivityBlocked(fromDisplay, activityInfo); + verify(mActivityBlockedCallback, after(TIMEOUT_MILLIS).never()) + .onActivityBlocked(fromDisplay, activityInfo); verify(mIntentListenerCallback, never()).shouldInterceptIntent(any(Intent.class)); } } diff --git a/services/tests/servicestests/src/com/android/server/inputmethod/InputMethodUtilsTest.java b/services/tests/servicestests/src/com/android/server/inputmethod/InputMethodUtilsTest.java index 4b318de78827..37a1a411c703 100644 --- a/services/tests/servicestests/src/com/android/server/inputmethod/InputMethodUtilsTest.java +++ b/services/tests/servicestests/src/com/android/server/inputmethod/InputMethodUtilsTest.java @@ -287,7 +287,7 @@ public class InputMethodUtilsTest { subtypes); final ArrayList<InputMethodSubtype> result = SubtypeUtils.getImplicitlyApplicableSubtypesLocked( - getResourcesForLocales(LOCALE_EN_US), imi); + new LocaleList(LOCALE_EN_US), imi); assertEquals(1, result.size()); verifyEquality(autoSubtype, result.get(0)); } @@ -311,7 +311,7 @@ public class InputMethodUtilsTest { subtypes); final ArrayList<InputMethodSubtype> result = SubtypeUtils.getImplicitlyApplicableSubtypesLocked( - getResourcesForLocales(LOCALE_EN_US), imi); + new LocaleList(LOCALE_EN_US), imi); assertEquals(2, result.size()); verifyEquality(nonAutoEnUS, result.get(0)); verifyEquality(nonAutoHandwritingEn, result.get(1)); @@ -335,7 +335,7 @@ public class InputMethodUtilsTest { subtypes); final ArrayList<InputMethodSubtype> result = SubtypeUtils.getImplicitlyApplicableSubtypesLocked( - getResourcesForLocales(LOCALE_EN_GB), imi); + new LocaleList(LOCALE_EN_GB), imi); assertEquals(2, result.size()); verifyEquality(nonAutoEnGB, result.get(0)); verifyEquality(nonAutoHandwritingEn, result.get(1)); @@ -360,7 +360,7 @@ public class InputMethodUtilsTest { subtypes); final ArrayList<InputMethodSubtype> result = SubtypeUtils.getImplicitlyApplicableSubtypesLocked( - getResourcesForLocales(LOCALE_FR), imi); + new LocaleList(LOCALE_FR), imi); assertEquals(2, result.size()); verifyEquality(nonAutoFrCA, result.get(0)); verifyEquality(nonAutoHandwritingFr, result.get(1)); @@ -381,7 +381,7 @@ public class InputMethodUtilsTest { subtypes); final ArrayList<InputMethodSubtype> result = SubtypeUtils.getImplicitlyApplicableSubtypesLocked( - getResourcesForLocales(LOCALE_FR_CA), imi); + new LocaleList(LOCALE_FR_CA), imi); assertEquals(2, result.size()); verifyEquality(nonAutoFrCA, result.get(0)); verifyEquality(nonAutoHandwritingFr, result.get(1)); @@ -403,7 +403,7 @@ public class InputMethodUtilsTest { subtypes); final ArrayList<InputMethodSubtype> result = SubtypeUtils.getImplicitlyApplicableSubtypesLocked( - getResourcesForLocales(LOCALE_JA_JP), imi); + new LocaleList(LOCALE_JA_JP), imi); assertEquals(3, result.size()); verifyEquality(nonAutoJa, result.get(0)); verifyEquality(nonAutoEnabledWhenDefaultIsNotAsciiCalableSubtype, result.get(1)); @@ -425,7 +425,7 @@ public class InputMethodUtilsTest { subtypes); final ArrayList<InputMethodSubtype> result = SubtypeUtils.getImplicitlyApplicableSubtypesLocked( - getResourcesForLocales(LOCALE_JA_JP), imi); + new LocaleList(LOCALE_JA_JP), imi); assertEquals(1, result.size()); verifyEquality(nonAutoHi, result.get(0)); } @@ -442,7 +442,7 @@ public class InputMethodUtilsTest { subtypes); final ArrayList<InputMethodSubtype> result = SubtypeUtils.getImplicitlyApplicableSubtypesLocked( - getResourcesForLocales(LOCALE_JA_JP), imi); + new LocaleList(LOCALE_JA_JP), imi); assertEquals(1, result.size()); verifyEquality(nonAutoEnUS, result.get(0)); } @@ -459,7 +459,7 @@ public class InputMethodUtilsTest { subtypes); final ArrayList<InputMethodSubtype> result = SubtypeUtils.getImplicitlyApplicableSubtypesLocked( - getResourcesForLocales(LOCALE_JA_JP), imi); + new LocaleList(LOCALE_JA_JP), imi); assertEquals(1, result.size()); verifyEquality(nonAutoEnUS, result.get(0)); } @@ -481,7 +481,7 @@ public class InputMethodUtilsTest { subtypes); final ArrayList<InputMethodSubtype> result = SubtypeUtils.getImplicitlyApplicableSubtypesLocked( - getResourcesForLocales(Locale.forLanguageTag("sr-Latn-RS")), imi); + new LocaleList(Locale.forLanguageTag("sr-Latn-RS")), imi); assertEquals(2, result.size()); assertThat(nonAutoSrLatn, is(in(result))); assertThat(nonAutoHandwritingSrLatn, is(in(result))); @@ -501,7 +501,7 @@ public class InputMethodUtilsTest { subtypes); final ArrayList<InputMethodSubtype> result = SubtypeUtils.getImplicitlyApplicableSubtypesLocked( - getResourcesForLocales(Locale.forLanguageTag("sr-Cyrl-RS")), imi); + new LocaleList(Locale.forLanguageTag("sr-Cyrl-RS")), imi); assertEquals(2, result.size()); assertThat(nonAutoSrCyrl, is(in(result))); assertThat(nonAutoHandwritingSrCyrl, is(in(result))); @@ -527,7 +527,7 @@ public class InputMethodUtilsTest { subtypes); final ArrayList<InputMethodSubtype> result = SubtypeUtils.getImplicitlyApplicableSubtypesLocked( - getResourcesForLocales( + new LocaleList( Locale.forLanguageTag("sr-Latn-RS-x-android"), Locale.forLanguageTag("ja-JP"), Locale.forLanguageTag("fr-FR"), @@ -554,7 +554,7 @@ public class InputMethodUtilsTest { subtypes); final ArrayList<InputMethodSubtype> result = SubtypeUtils.getImplicitlyApplicableSubtypesLocked( - getResourcesForLocales(LOCALE_FIL_PH), imi); + new LocaleList(LOCALE_FIL_PH), imi); assertEquals(1, result.size()); verifyEquality(nonAutoFil, result.get(0)); } @@ -572,7 +572,7 @@ public class InputMethodUtilsTest { subtypes); final ArrayList<InputMethodSubtype> result = SubtypeUtils.getImplicitlyApplicableSubtypesLocked( - getResourcesForLocales(LOCALE_FI), imi); + new LocaleList(LOCALE_FI), imi); assertEquals(1, result.size()); verifyEquality(nonAutoJa, result.get(0)); } @@ -588,7 +588,7 @@ public class InputMethodUtilsTest { subtypes); final ArrayList<InputMethodSubtype> result = SubtypeUtils.getImplicitlyApplicableSubtypesLocked( - getResourcesForLocales(LOCALE_IN), imi); + new LocaleList(LOCALE_IN), imi); assertEquals(1, result.size()); verifyEquality(nonAutoIn, result.get(0)); } @@ -602,7 +602,7 @@ public class InputMethodUtilsTest { subtypes); final ArrayList<InputMethodSubtype> result = SubtypeUtils.getImplicitlyApplicableSubtypesLocked( - getResourcesForLocales(LOCALE_ID), imi); + new LocaleList(LOCALE_ID), imi); assertEquals(1, result.size()); verifyEquality(nonAutoIn, result.get(0)); } @@ -616,7 +616,7 @@ public class InputMethodUtilsTest { subtypes); final ArrayList<InputMethodSubtype> result = SubtypeUtils.getImplicitlyApplicableSubtypesLocked( - getResourcesForLocales(LOCALE_IN), imi); + new LocaleList(LOCALE_IN), imi); assertEquals(1, result.size()); verifyEquality(nonAutoId, result.get(0)); } @@ -630,7 +630,7 @@ public class InputMethodUtilsTest { subtypes); final ArrayList<InputMethodSubtype> result = SubtypeUtils.getImplicitlyApplicableSubtypesLocked( - getResourcesForLocales(LOCALE_ID), imi); + new LocaleList(LOCALE_ID), imi); assertEquals(1, result.size()); verifyEquality(nonAutoId, result.get(0)); } @@ -652,7 +652,7 @@ public class InputMethodUtilsTest { subtypes); final ArrayList<InputMethodSubtype> result = SubtypeUtils.getImplicitlyApplicableSubtypesLocked( - getResourcesForLocales(LOCALE_FR, LOCALE_EN_US, LOCALE_JA_JP), imi); + new LocaleList(LOCALE_FR, LOCALE_EN_US, LOCALE_JA_JP), imi); assertThat(nonAutoFrCA, is(in(result))); assertThat(nonAutoEnUS, is(in(result))); assertThat(nonAutoJa, is(in(result))); @@ -940,10 +940,6 @@ public class InputMethodUtilsTest { .createConfigurationContext(resourceConfiguration); } - private Resources getResourcesForLocales(Locale... locales) { - return createTargetContextWithLocales(new LocaleList(locales)).getResources(); - } - private String[] getPackageNames(final ArrayList<InputMethodInfo> imis) { final String[] packageNames = new String[imis.size()]; for (int i = 0; i < imis.size(); ++i) { diff --git a/services/tests/vibrator/src/com/android/server/vibrator/VibratorControlServiceTest.java b/services/tests/vibrator/src/com/android/server/vibrator/VibratorControlServiceTest.java new file mode 100644 index 000000000000..49efd1bdd92a --- /dev/null +++ b/services/tests/vibrator/src/com/android/server/vibrator/VibratorControlServiceTest.java @@ -0,0 +1,63 @@ +/* + * Copyright (C) 2021 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.vibrator; + +import static com.google.common.truth.Truth.assertThat; + +import android.os.RemoteException; + +import org.junit.Before; +import org.junit.Test; + +public class VibratorControlServiceTest { + + private VibratorControlService mVibratorControlService; + private final Object mLock = new Object(); + + @Before + public void setUp() throws Exception { + mVibratorControlService = new VibratorControlService(new VibratorControllerHolder(), mLock); + } + + @Test + public void testRegisterVibratorController() throws RemoteException { + FakeVibratorController fakeController = new FakeVibratorController(); + mVibratorControlService.registerVibratorController(fakeController); + + assertThat(fakeController.isLinkedToDeath).isTrue(); + } + + @Test + public void testUnregisterVibratorController_providingTheRegisteredController_performsRequest() + throws RemoteException { + FakeVibratorController fakeController = new FakeVibratorController(); + mVibratorControlService.registerVibratorController(fakeController); + mVibratorControlService.unregisterVibratorController(fakeController); + assertThat(fakeController.isLinkedToDeath).isFalse(); + } + + @Test + public void testUnregisterVibratorController_providingAnInvalidController_ignoresRequest() + throws RemoteException { + FakeVibratorController fakeController1 = new FakeVibratorController(); + FakeVibratorController fakeController2 = new FakeVibratorController(); + mVibratorControlService.registerVibratorController(fakeController1); + + mVibratorControlService.unregisterVibratorController(fakeController2); + assertThat(fakeController1.isLinkedToDeath).isTrue(); + } +} diff --git a/services/tests/vibrator/src/com/android/server/vibrator/VibratorControllerHolderTest.java b/services/tests/vibrator/src/com/android/server/vibrator/VibratorControllerHolderTest.java new file mode 100644 index 000000000000..79abe21a301d --- /dev/null +++ b/services/tests/vibrator/src/com/android/server/vibrator/VibratorControllerHolderTest.java @@ -0,0 +1,72 @@ +/* + * Copyright (C) 2021 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.vibrator; + +import static com.google.common.truth.Truth.assertThat; + +import android.os.RemoteException; + +import org.junit.Before; +import org.junit.Test; + +public class VibratorControllerHolderTest { + + private final FakeVibratorController mFakeVibratorController = new FakeVibratorController(); + private VibratorControllerHolder mVibratorControllerHolder; + + @Before + public void setUp() throws Exception { + mVibratorControllerHolder = new VibratorControllerHolder(); + } + + @Test + public void testSetVibratorController_linksVibratorControllerToDeath() throws RemoteException { + mVibratorControllerHolder.setVibratorController(mFakeVibratorController); + assertThat(mVibratorControllerHolder.getVibratorController()) + .isEqualTo(mFakeVibratorController); + assertThat(mFakeVibratorController.isLinkedToDeath).isTrue(); + } + + @Test + public void testSetVibratorController_setControllerToNull_unlinksVibratorControllerToDeath() + throws RemoteException { + mVibratorControllerHolder.setVibratorController(mFakeVibratorController); + mVibratorControllerHolder.setVibratorController(null); + assertThat(mFakeVibratorController.isLinkedToDeath).isFalse(); + assertThat(mVibratorControllerHolder.getVibratorController()).isNull(); + } + + @Test + public void testBinderDied_withValidController_unlinksVibratorControllerToDeath() + throws RemoteException { + mVibratorControllerHolder.setVibratorController(mFakeVibratorController); + mVibratorControllerHolder.binderDied(mFakeVibratorController); + assertThat(mFakeVibratorController.isLinkedToDeath).isFalse(); + assertThat(mVibratorControllerHolder.getVibratorController()).isNull(); + } + + @Test + public void testBinderDied_withInvalidController_ignoresRequest() + throws RemoteException { + mVibratorControllerHolder.setVibratorController(mFakeVibratorController); + FakeVibratorController imposterVibratorController = new FakeVibratorController(); + mVibratorControllerHolder.binderDied(imposterVibratorController); + assertThat(mFakeVibratorController.isLinkedToDeath).isTrue(); + assertThat(mVibratorControllerHolder.getVibratorController()) + .isEqualTo(mFakeVibratorController); + } +} diff --git a/services/tests/vibrator/src/com/android/server/vibrator/VibratorManagerServiceTest.java b/services/tests/vibrator/src/com/android/server/vibrator/VibratorManagerServiceTest.java index 3fce9e7a83ef..a105649c9b5b 100644 --- a/services/tests/vibrator/src/com/android/server/vibrator/VibratorManagerServiceTest.java +++ b/services/tests/vibrator/src/com/android/server/vibrator/VibratorManagerServiceTest.java @@ -307,9 +307,10 @@ public class VibratorManagerServiceTest { @Override void addService(String name, IBinder service) { - Object serviceInstance = service; - mExternalVibratorService = - (VibratorManagerService.ExternalVibratorService) serviceInstance; + if (service instanceof VibratorManagerService.ExternalVibratorService) { + mExternalVibratorService = + (VibratorManagerService.ExternalVibratorService) service; + } } HapticFeedbackVibrationProvider createHapticFeedbackVibrationProvider( diff --git a/services/tests/vibrator/utils/com/android/server/vibrator/FakeVibratorController.java b/services/tests/vibrator/utils/com/android/server/vibrator/FakeVibratorController.java new file mode 100644 index 000000000000..7e235870cedc --- /dev/null +++ b/services/tests/vibrator/utils/com/android/server/vibrator/FakeVibratorController.java @@ -0,0 +1,58 @@ +/* + * Copyright (C) 2020 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.vibrator; + +import android.annotation.NonNull; +import android.frameworks.vibrator.IVibratorController; +import android.os.IBinder; +import android.os.RemoteException; + +/** + * Provides a fake implementation of {@link android.frameworks.vibrator.IVibratorController} for + * testing. + */ +public final class FakeVibratorController extends IVibratorController.Stub { + + public boolean isLinkedToDeath = false; + + @Override + public void requestVibrationParams(int i, long l, IBinder iBinder) throws RemoteException { + + } + + @Override + public int getInterfaceVersion() throws RemoteException { + return 0; + } + + @Override + public String getInterfaceHash() throws RemoteException { + return null; + } + + @Override + public void linkToDeath(@NonNull DeathRecipient recipient, int flags) { + super.linkToDeath(recipient, flags); + isLinkedToDeath = true; + } + + @Override + public boolean unlinkToDeath(@NonNull DeathRecipient recipient, int flags) { + isLinkedToDeath = false; + return super.unlinkToDeath(recipient, flags); + } +} diff --git a/services/tests/wmtests/src/com/android/server/policy/DeferredKeyActionExecutorTests.java b/services/tests/wmtests/src/com/android/server/policy/DeferredKeyActionExecutorTests.java new file mode 100644 index 000000000000..d2ef1808652f --- /dev/null +++ b/services/tests/wmtests/src/com/android/server/policy/DeferredKeyActionExecutorTests.java @@ -0,0 +1,106 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.policy; + +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +import android.view.KeyEvent; + +import org.junit.Before; +import org.junit.Test; + +/** + * Test class for {@link DeferredKeyActionExecutor}. + * + * <p>Build/Install/Run: atest WmTests:DeferredKeyActionExecutorTests + */ +public final class DeferredKeyActionExecutorTests { + + private DeferredKeyActionExecutor mKeyActionExecutor; + + @Before + public void setUp() { + mKeyActionExecutor = new DeferredKeyActionExecutor(); + } + + @Test + public void queueKeyAction_actionNotExecuted() { + TestAction action = new TestAction(); + + mKeyActionExecutor.queueKeyAction(KeyEvent.KEYCODE_STEM_PRIMARY, /* downTime= */ 1, action); + + assertFalse(action.executed); + } + + @Test + public void setActionsExecutable_afterActionQueued_actionExecuted() { + TestAction action = new TestAction(); + mKeyActionExecutor.queueKeyAction(KeyEvent.KEYCODE_STEM_PRIMARY, /* downTime= */ 1, action); + + mKeyActionExecutor.setActionsExecutable(KeyEvent.KEYCODE_STEM_PRIMARY, /* downTime= */ 1); + + assertTrue(action.executed); + } + + @Test + public void queueKeyAction_alreadyExecutable_actionExecuted() { + TestAction action = new TestAction(); + mKeyActionExecutor.setActionsExecutable(KeyEvent.KEYCODE_STEM_PRIMARY, /* downTime= */ 1); + + mKeyActionExecutor.queueKeyAction(KeyEvent.KEYCODE_STEM_PRIMARY, /* downTime= */ 1, action); + + assertTrue(action.executed); + } + + @Test + public void setActionsExecutable_afterActionQueued_downTimeMismatch_actionNotExecuted() { + TestAction action1 = new TestAction(); + mKeyActionExecutor.queueKeyAction( + KeyEvent.KEYCODE_STEM_PRIMARY, /* downTime= */ 1, action1); + + mKeyActionExecutor.setActionsExecutable(KeyEvent.KEYCODE_STEM_PRIMARY, /* downTime= */ 2); + + assertFalse(action1.executed); + + TestAction action2 = new TestAction(); + mKeyActionExecutor.queueKeyAction( + KeyEvent.KEYCODE_STEM_PRIMARY, /* downTime= */ 2, action2); + + assertFalse(action1.executed); + assertTrue(action2.executed); + } + + @Test + public void queueKeyAction_afterSetExecutable_downTimeMismatch_actionNotExecuted() { + TestAction action = new TestAction(); + mKeyActionExecutor.setActionsExecutable(KeyEvent.KEYCODE_STEM_PRIMARY, /* downTime= */ 1); + + mKeyActionExecutor.queueKeyAction(KeyEvent.KEYCODE_STEM_PRIMARY, /* downTime= */ 2, action); + + assertFalse(action.executed); + } + + static class TestAction implements Runnable { + public boolean executed; + + @Override + public void run() { + executed = true; + } + } +} |