diff options
186 files changed, 4438 insertions, 3035 deletions
diff --git a/core/api/current.txt b/core/api/current.txt index 93fa9a0e8742..ce66a4f073d3 100644 --- a/core/api/current.txt +++ b/core/api/current.txt @@ -8028,7 +8028,7 @@ package android.app.admin { method public CharSequence getDeviceOwnerLockScreenInfo(); method @Nullable public String getDevicePolicyManagementRoleHolderPackage(); method public CharSequence getEndUserSessionMessage(@NonNull android.content.ComponentName); - method @FlaggedApi("android.app.admin.flags.permission_migration_for_zero_trust_api_enabled") @NonNull @RequiresPermission(value=android.Manifest.permission.MANAGE_DEVICE_POLICY_CERTIFICATES, conditional=true) public String getEnrollmentSpecificId(); + method @NonNull @RequiresPermission(value=android.Manifest.permission.MANAGE_DEVICE_POLICY_CERTIFICATES, conditional=true) public String getEnrollmentSpecificId(); method @Nullable @RequiresPermission(value=android.Manifest.permission.MANAGE_DEVICE_POLICY_FACTORY_RESET, conditional=true) public android.app.admin.FactoryResetProtectionPolicy getFactoryResetProtectionPolicy(@Nullable android.content.ComponentName); method @Nullable public String getGlobalPrivateDnsHost(@NonNull android.content.ComponentName); method public int getGlobalPrivateDnsMode(@NonNull android.content.ComponentName); @@ -8067,7 +8067,7 @@ package android.app.admin { method @Deprecated public int getPasswordMinimumSymbols(@Nullable android.content.ComponentName); method @Deprecated public int getPasswordMinimumUpperCase(@Nullable android.content.ComponentName); method @Deprecated public int getPasswordQuality(@Nullable android.content.ComponentName); - method @FlaggedApi("android.app.admin.flags.permission_migration_for_zero_trust_api_enabled") @Nullable @RequiresPermission(value=android.Manifest.permission.MANAGE_DEVICE_POLICY_QUERY_SYSTEM_UPDATES, conditional=true) public android.app.admin.SystemUpdateInfo getPendingSystemUpdate(@Nullable android.content.ComponentName); + method @Nullable @RequiresPermission(value=android.Manifest.permission.MANAGE_DEVICE_POLICY_QUERY_SYSTEM_UPDATES, conditional=true) public android.app.admin.SystemUpdateInfo getPendingSystemUpdate(@Nullable android.content.ComponentName); method @RequiresPermission(value=android.Manifest.permission.MANAGE_DEVICE_POLICY_RUNTIME_PERMISSIONS, conditional=true) public int getPermissionGrantState(@Nullable android.content.ComponentName, @NonNull String, @NonNull String); method public int getPermissionPolicy(android.content.ComponentName); method @Nullable public java.util.List<java.lang.String> getPermittedAccessibilityServices(@NonNull android.content.ComponentName); diff --git a/core/api/system-current.txt b/core/api/system-current.txt index 22d39a4a0fa6..702a47ef54ec 100644 --- a/core/api/system-current.txt +++ b/core/api/system-current.txt @@ -13483,12 +13483,12 @@ package android.service.voice { method public static int getMaxBundleSize(); method public static int getMaxHotwordPhraseId(); method public static int getMaxScore(); - method @FlaggedApi("android.service.voice.flags.allow_speaker_id_egress") public static int getMaxSpeakerId(); + method public static int getMaxSpeakerId(); method @Nullable public android.media.MediaSyncEvent getMediaSyncEvent(); method public int getPersonalizedScore(); method public int getProximity(); method public int getScore(); - method @FlaggedApi("android.service.voice.flags.allow_speaker_id_egress") public int getSpeakerId(); + method public int getSpeakerId(); method public boolean isHotwordDetectionPersonalized(); method public void writeToParcel(@NonNull android.os.Parcel, int); field public static final int AUDIO_CHANNEL_UNSET = -1; // 0xffffffff @@ -13522,7 +13522,7 @@ package android.service.voice { method @NonNull public android.service.voice.HotwordDetectedResult.Builder setMediaSyncEvent(@NonNull android.media.MediaSyncEvent); method @NonNull public android.service.voice.HotwordDetectedResult.Builder setPersonalizedScore(int); method @NonNull public android.service.voice.HotwordDetectedResult.Builder setScore(int); - method @FlaggedApi("android.service.voice.flags.allow_speaker_id_egress") @NonNull public android.service.voice.HotwordDetectedResult.Builder setSpeakerId(int); + method @NonNull public android.service.voice.HotwordDetectedResult.Builder setSpeakerId(int); } public abstract class HotwordDetectionService extends android.app.Service implements android.service.voice.SandboxedDetectionInitializer { @@ -13621,7 +13621,7 @@ package android.service.voice { field public static final int ERROR_CODE_UNKNOWN = 0; // 0x0 } - @FlaggedApi("android.service.voice.flags.allow_various_attention_types") public final class VisualQueryAttentionResult implements android.os.Parcelable { + public final class VisualQueryAttentionResult implements android.os.Parcelable { method public int describeContents(); method @IntRange(from=1, to=100) public int getEngagementLevel(); method public int getInteractionIntention(); @@ -13638,7 +13638,7 @@ package android.service.voice { method @NonNull public android.service.voice.VisualQueryAttentionResult.Builder setInteractionIntention(int); } - @FlaggedApi("android.service.voice.flags.allow_complex_results_egress_from_vqds") public final class VisualQueryDetectedResult implements android.os.Parcelable { + public final class VisualQueryDetectedResult implements android.os.Parcelable { method public int describeContents(); method @Nullable public byte[] getAccessibilityDetectionData(); method public static int getMaxSpeakerId(); @@ -13660,16 +13660,16 @@ package android.service.voice { ctor public VisualQueryDetectionService(); method public final void finishQuery() throws java.lang.IllegalStateException; method public final void gainedAttention(); - method @FlaggedApi("android.service.voice.flags.allow_various_attention_types") public final void gainedAttention(@NonNull android.service.voice.VisualQueryAttentionResult); + method public final void gainedAttention(@NonNull android.service.voice.VisualQueryAttentionResult); method public final void lostAttention(); - method @FlaggedApi("android.service.voice.flags.allow_various_attention_types") public final void lostAttention(int); + method public final void lostAttention(int); method @Nullable public android.os.IBinder onBind(@NonNull android.content.Intent); method public void onStartDetection(); method public void onStopDetection(); method public void onUpdateState(@Nullable android.os.PersistableBundle, @Nullable android.os.SharedMemory, long, @Nullable java.util.function.IntConsumer); method public final void rejectQuery() throws java.lang.IllegalStateException; method public final void streamQuery(@NonNull String) throws java.lang.IllegalStateException; - method @FlaggedApi("android.service.voice.flags.allow_complex_results_egress_from_vqds") public final void streamQuery(@NonNull android.service.voice.VisualQueryDetectedResult); + method public final void streamQuery(@NonNull android.service.voice.VisualQueryDetectedResult); field public static final String SERVICE_INTERFACE = "android.service.voice.VisualQueryDetectionService"; } @@ -13689,10 +13689,10 @@ package android.service.voice { } public class VisualQueryDetector { - method @FlaggedApi("android.service.voice.flags.allow_complex_results_egress_from_vqds") public void clearAccessibilityDetectionEnabledListener(); + method public void clearAccessibilityDetectionEnabledListener(); method public void destroy(); - method @FlaggedApi("android.service.voice.flags.allow_complex_results_egress_from_vqds") public boolean isAccessibilityDetectionEnabled(); - method @FlaggedApi("android.service.voice.flags.allow_complex_results_egress_from_vqds") public void setAccessibilityDetectionEnabledListener(@NonNull java.util.function.Consumer<java.lang.Boolean>); + method public boolean isAccessibilityDetectionEnabled(); + method public void setAccessibilityDetectionEnabledListener(@NonNull java.util.function.Consumer<java.lang.Boolean>); method @RequiresPermission(allOf={android.Manifest.permission.CAMERA, android.Manifest.permission.RECORD_AUDIO}) public boolean startRecognition(); method @RequiresPermission(allOf={android.Manifest.permission.CAMERA, android.Manifest.permission.RECORD_AUDIO}) public boolean stopRecognition(); method public void updateState(@Nullable android.os.PersistableBundle, @Nullable android.os.SharedMemory); @@ -13701,7 +13701,7 @@ package android.service.voice { public static interface VisualQueryDetector.Callback { method public void onFailure(@NonNull android.service.voice.VisualQueryDetectionServiceFailure); method public void onQueryDetected(@NonNull String); - method @FlaggedApi("android.service.voice.flags.allow_complex_results_egress_from_vqds") public default void onQueryDetected(@NonNull android.service.voice.VisualQueryDetectedResult); + method public default void onQueryDetected(@NonNull android.service.voice.VisualQueryDetectedResult); method public void onQueryFinished(); method public void onQueryRejected(); method public void onUnknownFailure(@NonNull String); diff --git a/core/api/test-current.txt b/core/api/test-current.txt index a76aa6743bc5..ca70f03e5859 100644 --- a/core/api/test-current.txt +++ b/core/api/test-current.txt @@ -2769,6 +2769,7 @@ package android.permission { public final class PermissionManager { method @NonNull @RequiresPermission(android.Manifest.permission.GET_APP_OPS_STATS) public java.util.List<android.permission.PermissionGroupUsage> getIndicatorAppOpUsageData(); method @NonNull @RequiresPermission(android.Manifest.permission.GET_APP_OPS_STATS) public java.util.List<android.permission.PermissionGroupUsage> getIndicatorAppOpUsageData(boolean); + method @FlaggedApi("android.permission.flags.should_register_attribution_source") public boolean isRegisteredAttributionSource(@NonNull android.content.AttributionSource); method @NonNull public android.content.AttributionSource registerAttributionSource(@NonNull android.content.AttributionSource); method @RequiresPermission(android.Manifest.permission.REVOKE_POST_NOTIFICATIONS_WITHOUT_KILL) public void revokePostNotificationPermissionWithoutKillForTest(@NonNull String, int); } diff --git a/core/java/android/app/AppOpsManager.java b/core/java/android/app/AppOpsManager.java index ff713d071a05..0ed25eb3125a 100644 --- a/core/java/android/app/AppOpsManager.java +++ b/core/java/android/app/AppOpsManager.java @@ -2685,8 +2685,7 @@ public class AppOpsManager { .setDefaultMode(getSystemAlertWindowDefault()).build(), new AppOpInfo.Builder(OP_ACCESS_NOTIFICATIONS, OPSTR_ACCESS_NOTIFICATIONS, "ACCESS_NOTIFICATIONS") - .setPermission(android.Manifest.permission.ACCESS_NOTIFICATIONS) - .setDefaultMode(AppOpsManager.MODE_ALLOWED).build(), + .setPermission(android.Manifest.permission.ACCESS_NOTIFICATIONS).build(), new AppOpInfo.Builder(OP_CAMERA, OPSTR_CAMERA, "CAMERA") .setPermission(android.Manifest.permission.CAMERA) .setRestriction(UserManager.DISALLOW_CAMERA) diff --git a/core/java/android/app/ResourcesManager.java b/core/java/android/app/ResourcesManager.java index 8b84f062b7b5..b1e7b628baa2 100644 --- a/core/java/android/app/ResourcesManager.java +++ b/core/java/android/app/ResourcesManager.java @@ -736,7 +736,8 @@ public class ResourcesManager { private @Nullable ResourcesImpl findOrCreateResourcesImplForKeyLocked( @NonNull ResourcesKey key, @Nullable ApkAssetsSupplier apkSupplier) { ResourcesImpl impl = findResourcesImplForKeyLocked(key); - if (impl == null) { + // ResourcesImpl also need to be recreated if its shared library count is not up-to-date. + if (impl == null || impl.getSharedLibCount() != mSharedLibAssetsMap.size()) { impl = createResourcesImpl(key, apkSupplier); if (impl != null) { mResourceImpls.put(key, new WeakReference<>(impl)); diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java index 60dffbd0e421..411f7f7bb96c 100644 --- a/core/java/android/app/admin/DevicePolicyManager.java +++ b/core/java/android/app/admin/DevicePolicyManager.java @@ -58,7 +58,6 @@ import static android.app.admin.flags.Flags.FLAG_DEVICE_THEFT_API_ENABLED; import static android.app.admin.flags.Flags.FLAG_ESIM_MANAGEMENT_ENABLED; import static android.app.admin.flags.Flags.FLAG_DEVICE_POLICY_SIZE_TRACKING_ENABLED; import static android.app.admin.flags.Flags.FLAG_HEADLESS_DEVICE_OWNER_SINGLE_USER_ENABLED; -import static android.app.admin.flags.Flags.FLAG_PERMISSION_MIGRATION_FOR_ZERO_TRUST_API_ENABLED; import static android.app.admin.flags.Flags.FLAG_SECURITY_LOG_V2_ENABLED; import static android.app.admin.flags.Flags.onboardingBugreportV2Enabled; import static android.app.admin.flags.Flags.FLAG_IS_MTE_POLICY_ENFORCED; @@ -13462,7 +13461,6 @@ public class DevicePolicyManager { */ @RequiresPermission(value = MANAGE_DEVICE_POLICY_QUERY_SYSTEM_UPDATES, conditional = true) @SuppressLint("RequiresPermission") - @FlaggedApi(FLAG_PERMISSION_MIGRATION_FOR_ZERO_TRUST_API_ENABLED) public @Nullable SystemUpdateInfo getPendingSystemUpdate(@Nullable ComponentName admin) { throwIfParentInstance("getPendingSystemUpdate"); try { @@ -16638,7 +16636,6 @@ public class DevicePolicyManager { */ @RequiresPermission(value = MANAGE_DEVICE_POLICY_CERTIFICATES, conditional = true) @SuppressLint("RequiresPermission") - @FlaggedApi(FLAG_PERMISSION_MIGRATION_FOR_ZERO_TRUST_API_ENABLED) @NonNull public String getEnrollmentSpecificId() { throwIfParentInstance("getEnrollmentSpecificId"); if (mService == null) { diff --git a/core/java/android/companion/CompanionDeviceManager.java b/core/java/android/companion/CompanionDeviceManager.java index 2c26389071ce..5e00b7a798d8 100644 --- a/core/java/android/companion/CompanionDeviceManager.java +++ b/core/java/android/companion/CompanionDeviceManager.java @@ -1086,7 +1086,7 @@ public final class CompanionDeviceManager { } Objects.requireNonNull(deviceAddress, "address cannot be null"); try { - mService.legacyStartObservingDevicePresence(deviceAddress, + mService.registerDevicePresenceListenerService(deviceAddress, mContext.getOpPackageName(), mContext.getUserId()); } catch (RemoteException e) { ExceptionUtils.propagateIfInstanceOf(e.getCause(), DeviceNotAssociatedException.class); @@ -1128,7 +1128,7 @@ public final class CompanionDeviceManager { } Objects.requireNonNull(deviceAddress, "address cannot be null"); try { - mService.legacyStopObservingDevicePresence(deviceAddress, + mService.unregisterDevicePresenceListenerService(deviceAddress, mContext.getPackageName(), mContext.getUserId()); } catch (RemoteException e) { ExceptionUtils.propagateIfInstanceOf(e.getCause(), DeviceNotAssociatedException.class); @@ -1328,7 +1328,7 @@ public final class CompanionDeviceManager { @RequiresPermission(android.Manifest.permission.REQUEST_COMPANION_SELF_MANAGED) public void notifyDeviceAppeared(int associationId) { try { - mService.notifySelfManagedDeviceAppeared(associationId); + mService.notifyDeviceAppeared(associationId); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } @@ -1350,7 +1350,7 @@ public final class CompanionDeviceManager { @RequiresPermission(android.Manifest.permission.REQUEST_COMPANION_SELF_MANAGED) public void notifyDeviceDisappeared(int associationId) { try { - mService.notifySelfManagedDeviceDisappeared(associationId); + mService.notifyDeviceDisappeared(associationId); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } diff --git a/core/java/android/companion/ICompanionDeviceManager.aidl b/core/java/android/companion/ICompanionDeviceManager.aidl index 1b00f90e1fb3..57d59e5e5bf0 100644 --- a/core/java/android/companion/ICompanionDeviceManager.aidl +++ b/core/java/android/companion/ICompanionDeviceManager.aidl @@ -59,16 +59,12 @@ interface ICompanionDeviceManager { int userId); @EnforcePermission("REQUEST_OBSERVE_COMPANION_DEVICE_PRESENCE") - void legacyStartObservingDevicePresence(in String deviceAddress, in String callingPackage, int userId); - - @EnforcePermission("REQUEST_OBSERVE_COMPANION_DEVICE_PRESENCE") - void legacyStopObservingDevicePresence(in String deviceAddress, in String callingPackage, int userId); - - @EnforcePermission("REQUEST_OBSERVE_COMPANION_DEVICE_PRESENCE") - void startObservingDevicePresence(in ObservingDevicePresenceRequest request, in String packageName, int userId); + void registerDevicePresenceListenerService(in String deviceAddress, in String callingPackage, + int userId); @EnforcePermission("REQUEST_OBSERVE_COMPANION_DEVICE_PRESENCE") - void stopObservingDevicePresence(in ObservingDevicePresenceRequest request, in String packageName, int userId); + void unregisterDevicePresenceListenerService(in String deviceAddress, in String callingPackage, + int userId); boolean canPairWithoutPrompt(in String packageName, in String deviceMacAddress, int userId); @@ -97,11 +93,9 @@ interface ICompanionDeviceManager { @EnforcePermission("USE_COMPANION_TRANSPORTS") void removeOnMessageReceivedListener(int messageType, IOnMessageReceivedListener listener); - @EnforcePermission("REQUEST_COMPANION_SELF_MANAGED") - void notifySelfManagedDeviceAppeared(int associationId); + void notifyDeviceAppeared(int associationId); - @EnforcePermission("REQUEST_COMPANION_SELF_MANAGED") - void notifySelfManagedDeviceDisappeared(int associationId); + void notifyDeviceDisappeared(int associationId); PendingIntent buildPermissionTransferUserConsentIntent(String callingPackage, int userId, int associationId); @@ -141,4 +135,10 @@ interface ICompanionDeviceManager { byte[] getBackupPayload(int userId); void applyRestoredPayload(in byte[] payload, int userId); + + @EnforcePermission("REQUEST_OBSERVE_COMPANION_DEVICE_PRESENCE") + void startObservingDevicePresence(in ObservingDevicePresenceRequest request, in String packageName, int userId); + + @EnforcePermission("REQUEST_OBSERVE_COMPANION_DEVICE_PRESENCE") + void stopObservingDevicePresence(in ObservingDevicePresenceRequest request, in String packageName, int userId); } diff --git a/core/java/android/content/res/ResourcesImpl.java b/core/java/android/content/res/ResourcesImpl.java index 8d045aaf4d81..4c22fee332e6 100644 --- a/core/java/android/content/res/ResourcesImpl.java +++ b/core/java/android/content/res/ResourcesImpl.java @@ -146,6 +146,11 @@ public class ResourcesImpl { // Cyclical cache used for recently-accessed XML files. private int mLastCachedXmlBlockIndex = -1; + + // The number of shared libraries registered within this ResourcesImpl, which is designed to + // help to determine whether this ResourcesImpl is outdated on shared library information and + // needs to be replaced. + private int mSharedLibCount; private final int[] mCachedXmlBlockCookies = new int[XML_BLOCK_CACHE_SIZE]; private final String[] mCachedXmlBlockFiles = new String[XML_BLOCK_CACHE_SIZE]; private final XmlBlock[] mCachedXmlBlocks = new XmlBlock[XML_BLOCK_CACHE_SIZE]; @@ -206,6 +211,7 @@ public class ResourcesImpl { for (int i = 0; i < size; i++) { assets.addSharedLibraryPaths(sharedLibMap.valueAt(i).getAllAssetPaths()); } + mSharedLibCount = sharedLibMap.size(); } mMetrics.setToDefaults(); mDisplayAdjustments = displayAdjustments; @@ -1602,4 +1608,8 @@ public class ResourcesImpl { mSize--; } } + + public int getSharedLibCount() { + return mSharedLibCount; + } } diff --git a/core/java/android/hardware/biometrics/BiometricPrompt.java b/core/java/android/hardware/biometrics/BiometricPrompt.java index 7d61c142fa04..d9614d1acd6b 100644 --- a/core/java/android/hardware/biometrics/BiometricPrompt.java +++ b/core/java/android/hardware/biometrics/BiometricPrompt.java @@ -70,6 +70,7 @@ import javax.crypto.Mac; public class BiometricPrompt implements BiometricAuthenticator, BiometricConstants { private static final String TAG = "BiometricPrompt"; + private static final int MAX_LOGO_DESCRIPTION_CHARACTER_NUMBER = 30; /** * Error/help message will show for this amount of time. @@ -222,11 +223,19 @@ public class BiometricPrompt implements BiometricAuthenticator, BiometricConstan * * @param logoDescription The logo description text that will be shown on the prompt. * @return This builder. + * @throws IllegalStateException If logo description is null or exceeds certain character + * limit. */ @FlaggedApi(FLAG_CUSTOM_BIOMETRIC_PROMPT) @RequiresPermission(SET_BIOMETRIC_DIALOG_ADVANCED) @NonNull public BiometricPrompt.Builder setLogoDescription(@NonNull String logoDescription) { + if (logoDescription == null + || logoDescription.length() > MAX_LOGO_DESCRIPTION_CHARACTER_NUMBER) { + throw new IllegalStateException( + "Logo description passed in can not be null or exceed " + + MAX_LOGO_DESCRIPTION_CHARACTER_NUMBER + " character number."); + } mPromptInfo.setLogoDescription(logoDescription); return this; } @@ -814,7 +823,7 @@ public class BiometricPrompt implements BiometricAuthenticator, BiometricConstan /** * Gets the logo description for the prompt, as set by - * {@link Builder#setDescription(CharSequence)}. + * {@link Builder#setLogoDescription(String)}. * Currently for system applications use only. * * @return The logo description of the prompt, or null if the prompt has no logo description diff --git a/core/java/android/hardware/biometrics/PromptContentViewWithMoreOptionsButton.java b/core/java/android/hardware/biometrics/PromptContentViewWithMoreOptionsButton.java index 9ebfa8f4301d..853d86cf94dc 100644 --- a/core/java/android/hardware/biometrics/PromptContentViewWithMoreOptionsButton.java +++ b/core/java/android/hardware/biometrics/PromptContentViewWithMoreOptionsButton.java @@ -40,9 +40,12 @@ import java.util.concurrent.Executor; * or if the user has already selected the appropriate account to use before invoking * BiometricPrompt because it will create additional steps that the user must navigate through. * Clicking the more options button will dismiss the prompt, provide the app an opportunity to ask - * the user for the correct account, &finally allow the app to decide how to proceed once selected. + * the user for the correct account, and finally allow the app to decide how to proceed once + * selected. + * * <p> - * Here's how you'd set a <code>PromptContentViewWithMoreOptionsButton</code> on a Biometric Prompt: + * Here's how you'd set a <code>PromptContentViewWithMoreOptionsButton</code> on a Biometric + * Prompt: * <pre class="prettyprint"> * BiometricPrompt biometricPrompt = new BiometricPrompt.Builder(...) * .setTitle(...) @@ -56,6 +59,7 @@ import java.util.concurrent.Executor; */ @FlaggedApi(FLAG_CUSTOM_BIOMETRIC_PROMPT) public final class PromptContentViewWithMoreOptionsButton implements PromptContentViewParcelable { + private static final int MAX_DESCRIPTION_CHARACTER_NUMBER = 225; private final String mDescription; private DialogInterface.OnClickListener mListener; @@ -139,10 +143,15 @@ public final class PromptContentViewWithMoreOptionsButton implements PromptConte * * @param description The description to display. * @return This builder. + * @throws IllegalArgumentException If description exceeds certain character limit. */ @NonNull @RequiresPermission(SET_BIOMETRIC_DIALOG_ADVANCED) public Builder setDescription(@NonNull String description) { + if (description.length() > MAX_DESCRIPTION_CHARACTER_NUMBER) { + throw new IllegalStateException("The character number of description exceeds " + + MAX_DESCRIPTION_CHARACTER_NUMBER); + } mDescription = description; return this; } @@ -150,14 +159,6 @@ public final class PromptContentViewWithMoreOptionsButton implements PromptConte /** * Required: Sets the executor and click listener for the more options button on the * prompt content. - * This button should be used to provide more options for sign in or other purposes, such - * as when a user needs to select between multiple app-specific accounts or profiles that - * are available for sign in. This is not common and apps should avoid using it if there - * is only one choice available or if the user has already selected the appropriate - * account to use before invoking BiometricPrompt because it will create additional steps - * that the user must navigate through. Clicking the more options button will dismiss the - * prompt, provide the app an opportunity to ask the user for the correct account, &finally - * allow the app to decide how to proceed once selected. * * @param executor Executor that will be used to run the on click callback. * @param listener Listener containing a callback to be run when the button is pressed. @@ -167,12 +168,6 @@ public final class PromptContentViewWithMoreOptionsButton implements PromptConte @RequiresPermission(SET_BIOMETRIC_DIALOG_ADVANCED) public Builder setMoreOptionsButtonListener(@NonNull @CallbackExecutor Executor executor, @NonNull DialogInterface.OnClickListener listener) { - if (executor == null) { - throw new IllegalArgumentException("Executor must not be null"); - } - if (listener == null) { - throw new IllegalArgumentException("Listener must not be null"); - } mExecutor = executor; mListener = listener; return this; @@ -183,15 +178,23 @@ public final class PromptContentViewWithMoreOptionsButton implements PromptConte * Creates a {@link PromptContentViewWithMoreOptionsButton}. * * @return An instance of {@link PromptContentViewWithMoreOptionsButton}. + * @throws IllegalArgumentException If the executor of more options button is null, or the + * listener of more options button is null. */ @NonNull @RequiresPermission(SET_BIOMETRIC_DIALOG_ADVANCED) public PromptContentViewWithMoreOptionsButton build() { - if (mListener == null) { + if (mExecutor == null) { throw new IllegalArgumentException( - "The listener of more options button on prompt content must be set if " + "The executor for the listener of more options button on prompt content " + + "must be set and non-null if " + "PromptContentViewWithMoreOptionsButton is used."); } + if (mListener == null) { + throw new IllegalArgumentException( + "The listener of more options button on prompt content must be set and " + + "non-null if PromptContentViewWithMoreOptionsButton is used."); + } return new PromptContentViewWithMoreOptionsButton(mDescription, mExecutor, mListener); } } diff --git a/core/java/android/hardware/biometrics/PromptInfo.java b/core/java/android/hardware/biometrics/PromptInfo.java index 814321319e2f..18b75c9b8f8f 100644 --- a/core/java/android/hardware/biometrics/PromptInfo.java +++ b/core/java/android/hardware/biometrics/PromptInfo.java @@ -173,14 +173,19 @@ public class PromptInfo implements Parcelable { /** * Returns whether SET_BIOMETRIC_DIALOG_ADVANCED is contained. + * + * Currently, logo res, logo bitmap, logo description, PromptContentViewWithMoreOptions needs + * this permission. */ - public boolean containsSetLogoApiConfigurations() { + public boolean containsAdvancedApiConfigurations() { if (mLogoRes != -1) { return true; } else if (mLogoBitmap != null) { return true; } else if (mLogoDescription != null) { return true; + } else if (mContentView != null && isContentViewMoreOptionsButtonUsed()) { + return true; } return false; } diff --git a/core/java/android/hardware/biometrics/PromptVerticalListContentView.java b/core/java/android/hardware/biometrics/PromptVerticalListContentView.java index 38d32dc73ccb..02b2a50ade3c 100644 --- a/core/java/android/hardware/biometrics/PromptVerticalListContentView.java +++ b/core/java/android/hardware/biometrics/PromptVerticalListContentView.java @@ -29,9 +29,7 @@ import java.util.List; /** - * Contains the information of the template of vertical list content view for Biometric Prompt. Note - * that there are limits on the item count and the number of characters allowed for each item's - * text. + * Contains the information of the template of vertical list content view for Biometric Prompt. * <p> * Here's how you'd set a <code>PromptVerticalListContentView</code> on a Biometric Prompt: * <pre class="prettyprint"> @@ -51,6 +49,8 @@ import java.util.List; public final class PromptVerticalListContentView implements PromptContentViewParcelable { private static final int MAX_ITEM_NUMBER = 20; private static final int MAX_EACH_ITEM_CHARACTER_NUMBER = 640; + private static final int MAX_DESCRIPTION_CHARACTER_NUMBER = 225; + private final List<PromptContentItemParcelable> mContentList; private final String mDescription; @@ -150,51 +150,59 @@ public final class PromptVerticalListContentView implements PromptContentViewPar * * @param description The description to display. * @return This builder. + * @throws IllegalArgumentException If description exceeds certain character limit. */ @NonNull public Builder setDescription(@NonNull String description) { + if (description.length() > MAX_DESCRIPTION_CHARACTER_NUMBER) { + throw new IllegalStateException("The character number of description exceeds " + + MAX_DESCRIPTION_CHARACTER_NUMBER); + } mDescription = description; return this; } /** - * Optional: Adds a list item in the current row. Maximum {@value MAX_ITEM_NUMBER} items in - * total. The maximum length for each item is {@value MAX_EACH_ITEM_CHARACTER_NUMBER} - * characters. + * Optional: Adds a list item in the current row. * * @param listItem The list item view to display * @return This builder. + * @throws IllegalArgumentException If this list item exceeds certain character limits or + * the number of list items exceeds certain limit. */ @NonNull public Builder addListItem(@NonNull PromptContentItem listItem) { - if (doesListItemExceedsCharLimit(listItem)) { - throw new IllegalStateException( - "The character number of list item exceeds " - + MAX_EACH_ITEM_CHARACTER_NUMBER); - } mContentList.add((PromptContentItemParcelable) listItem); + checkItemLimits(listItem); return this; } - /** - * Optional: Adds a list item in the current row. Maximum {@value MAX_ITEM_NUMBER} items in - * total. The maximum length for each item is {@value MAX_EACH_ITEM_CHARACTER_NUMBER} - * characters. + * Optional: Adds a list item in the current row. * * @param listItem The list item view to display - * @param index The position at which to add the item + * @param index The position at which to add the item * @return This builder. + * @throws IllegalArgumentException If this list item exceeds certain character limits or + * the number of list items exceeds certain limit. */ @NonNull public Builder addListItem(@NonNull PromptContentItem listItem, int index) { + mContentList.add(index, (PromptContentItemParcelable) listItem); + checkItemLimits(listItem); + return this; + } + + private void checkItemLimits(@NonNull PromptContentItem listItem) { if (doesListItemExceedsCharLimit(listItem)) { throw new IllegalStateException( "The character number of list item exceeds " + MAX_EACH_ITEM_CHARACTER_NUMBER); } - mContentList.add(index, (PromptContentItemParcelable) listItem); - return this; + if (mContentList.size() > MAX_ITEM_NUMBER) { + throw new IllegalStateException( + "The number of list items exceeds " + MAX_ITEM_NUMBER); + } } private boolean doesListItemExceedsCharLimit(PromptContentItem listItem) { @@ -217,10 +225,6 @@ public final class PromptVerticalListContentView implements PromptContentViewPar */ @NonNull public PromptVerticalListContentView build() { - if (mContentList.size() > MAX_ITEM_NUMBER) { - throw new IllegalStateException( - "The number of list items exceeds " + MAX_ITEM_NUMBER); - } return new PromptVerticalListContentView(mContentList, mDescription); } } diff --git a/core/java/android/hardware/display/ColorDisplayManager.java b/core/java/android/hardware/display/ColorDisplayManager.java index f927b8b52912..0d9db1fa3c91 100644 --- a/core/java/android/hardware/display/ColorDisplayManager.java +++ b/core/java/android/hardware/display/ColorDisplayManager.java @@ -37,6 +37,7 @@ import android.provider.Settings.Secure; import com.android.internal.R; import com.android.internal.logging.MetricsLogger; import com.android.internal.logging.nano.MetricsProto.MetricsEvent; +import com.android.server.display.feature.flags.Flags; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; @@ -533,11 +534,15 @@ public final class ColorDisplayManager { /** * Returns {@code true} if reduce bright colors is supported by the device. + * Will return false if even dimmer is enabled - since this is the successor to RBC and cannot + * be run concurrently. * * @hide */ public static boolean isReduceBrightColorsAvailable(Context context) { - return context.getResources().getBoolean(R.bool.config_reduceBrightColorsAvailable); + return context.getResources().getBoolean(R.bool.config_reduceBrightColorsAvailable) + && !(Flags.evenDimmer() && context.getResources().getBoolean( + com.android.internal.R.bool.config_evenDimmerEnabled)); } /** diff --git a/core/java/android/permission/PermissionManager.java b/core/java/android/permission/PermissionManager.java index 3c7692d03410..3441244d6c58 100644 --- a/core/java/android/permission/PermissionManager.java +++ b/core/java/android/permission/PermissionManager.java @@ -23,6 +23,7 @@ import static android.content.pm.PackageManager.FLAG_PERMISSION_SYSTEM_FIXED; import static android.content.pm.PackageManager.FLAG_PERMISSION_USER_FIXED; import static android.content.pm.PackageManager.FLAG_PERMISSION_USER_SET; import static android.os.Build.VERSION_CODES.S; +import static android.permission.flags.Flags.FLAG_SHOULD_REGISTER_ATTRIBUTION_SOURCE; import static android.permission.flags.Flags.serverSideAttributionRegistration; import android.Manifest; @@ -1652,6 +1653,8 @@ public final class PermissionManager { * * @hide */ + @TestApi + @FlaggedApi(FLAG_SHOULD_REGISTER_ATTRIBUTION_SOURCE) public boolean isRegisteredAttributionSource(@NonNull AttributionSource source) { try { return mPermissionManager.isRegisteredAttributionSource(source.asState()); diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java index ec5421e789f8..c3b646ae34e9 100644 --- a/core/java/android/provider/Settings.java +++ b/core/java/android/provider/Settings.java @@ -11185,6 +11185,32 @@ public final class Settings { public static final String DISPLAY_WHITE_BALANCE_ENABLED = "display_white_balance_enabled"; /** + * Used by DisplayManager to backup/restore the user-selected resolution mode. + * @hide + */ + @Readable + public static final String SCREEN_RESOLUTION_MODE = "screen_resolution_mode"; + + /** + * Resolution Mode Constants for SCREEN_RESOLUTION_MODE setting. + * + * @hide + */ + @IntDef(prefix = { "RESOLUTION_MODE_" }, value = { + RESOLUTION_MODE_UNKNOWN, + RESOLUTION_MODE_HIGH, + RESOLUTION_MODE_FULL + }) + @Retention(RetentionPolicy.SOURCE) + public @interface ResolutionMode {} + /** @hide */ + public static final int RESOLUTION_MODE_UNKNOWN = 0; + /** @hide */ + public static final int RESOLUTION_MODE_HIGH = 1; + /** @hide */ + public static final int RESOLUTION_MODE_FULL = 2; + + /** * Names of the service components that the current user has explicitly allowed to * be a VR mode listener, separated by ':'. * diff --git a/core/java/android/service/notification/StatusBarNotification.java b/core/java/android/service/notification/StatusBarNotification.java index d8210742e331..264b53c6ee40 100644 --- a/core/java/android/service/notification/StatusBarNotification.java +++ b/core/java/android/service/notification/StatusBarNotification.java @@ -174,23 +174,6 @@ public class StatusBarNotification implements Parcelable { return sbnKey; } - /** - * @return Whether the Entry is a group child by the app or system - * @hide - */ - public boolean isAppOrSystemGroupChild() { - return isGroup() && !getNotification().isGroupSummary(); - } - - - /** - * @return Whether the Entry is a group summary by the app or system - * @hide - */ - public boolean isAppOrSystemGroupSummary() { - return isGroup() && getNotification().isGroupSummary(); - } - private String groupKey() { if (overrideGroupKey != null) { return user.getIdentifier() + "|" + pkg + "|" + "g:" + overrideGroupKey; diff --git a/core/java/android/service/voice/HotwordDetectedResult.java b/core/java/android/service/voice/HotwordDetectedResult.java index df4a2bb6afb6..0b7b19962773 100644 --- a/core/java/android/service/voice/HotwordDetectedResult.java +++ b/core/java/android/service/voice/HotwordDetectedResult.java @@ -16,10 +16,10 @@ package android.service.voice; -import android.annotation.FlaggedApi; import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.Nullable; +import android.annotation.SuppressLint; import android.annotation.SystemApi; import android.content.res.Resources; import android.media.AudioRecord; @@ -27,7 +27,6 @@ import android.media.MediaSyncEvent; import android.os.Parcel; import android.os.Parcelable; import android.os.PersistableBundle; -import android.service.voice.flags.Flags; import com.android.internal.R; import com.android.internal.util.DataClass; @@ -142,7 +141,7 @@ public final class HotwordDetectedResult implements Parcelable { } /** Maximum number of active speaker ids. **/ - @FlaggedApi(Flags.FLAG_ALLOW_SPEAKER_ID_EGRESS) + @SuppressLint("UnflaggedApi") // b/325678077 flags not supported in isolated process public static int getMaxSpeakerId() { return 15; } @@ -666,7 +665,7 @@ public final class HotwordDetectedResult implements Parcelable { * <p>Only values between 0 and {@link #getMaxSpeakerId} (inclusive) are accepted. */ @DataClass.Generated.Member - @FlaggedApi(Flags.FLAG_ALLOW_SPEAKER_ID_EGRESS) + @SuppressLint("UnflaggedApi") // b/325678077 flags not supported in isolated process public int getSpeakerId() { return mSpeakerId; } @@ -988,7 +987,7 @@ public final class HotwordDetectedResult implements Parcelable { * <p>Only values between 0 and {@link #getMaxSpeakerId} (inclusive) are accepted. */ @DataClass.Generated.Member - @FlaggedApi(Flags.FLAG_ALLOW_SPEAKER_ID_EGRESS) + @SuppressLint("UnflaggedApi") // b/325678077 flags not supported in isolated process public @NonNull Builder setSpeakerId(int value) { checkNotUsed(); mBuilderFieldsSet |= 0x1; @@ -1232,10 +1231,10 @@ public final class HotwordDetectedResult implements Parcelable { } @DataClass.Generated( - time = 1709773165191L, + time = 1710918729668L, codegenVersion = "1.0.23", sourceFile = "frameworks/base/core/java/android/service/voice/HotwordDetectedResult.java", - inputSignatures = "public static final int CONFIDENCE_LEVEL_NONE\npublic static final int CONFIDENCE_LEVEL_LOW\npublic static final int CONFIDENCE_LEVEL_LOW_MEDIUM\npublic static final int CONFIDENCE_LEVEL_MEDIUM\npublic static final int CONFIDENCE_LEVEL_MEDIUM_HIGH\npublic static final int CONFIDENCE_LEVEL_HIGH\npublic static final int CONFIDENCE_LEVEL_VERY_HIGH\npublic static final int HOTWORD_OFFSET_UNSET\npublic static final int AUDIO_CHANNEL_UNSET\npublic static final int BACKGROUND_AUDIO_POWER_UNSET\nprivate static final int LIMIT_HOTWORD_OFFSET_MAX_VALUE\nprivate static final int LIMIT_AUDIO_CHANNEL_MAX_VALUE\nprivate static final java.lang.String EXTRA_PROXIMITY\npublic static final int PROXIMITY_UNKNOWN\npublic static final int PROXIMITY_NEAR\npublic static final int PROXIMITY_FAR\nprivate final int mSpeakerId\nprivate final @android.service.voice.HotwordDetectedResult.HotwordConfidenceLevelValue int mConfidenceLevel\nprivate @android.annotation.Nullable android.media.MediaSyncEvent mMediaSyncEvent\nprivate int mHotwordOffsetMillis\nprivate int mHotwordDurationMillis\nprivate int mAudioChannel\nprivate boolean mHotwordDetectionPersonalized\nprivate final int mScore\nprivate final int mPersonalizedScore\nprivate final int mHotwordPhraseId\nprivate final @android.annotation.NonNull java.util.List<android.service.voice.HotwordAudioStream> mAudioStreams\nprivate final @android.annotation.NonNull android.os.PersistableBundle mExtras\nprivate static int sMaxBundleSize\nprivate final int mBackgroundAudioPower\nprivate static int defaultSpeakerId()\npublic static @android.annotation.FlaggedApi int getMaxSpeakerId()\nprivate static int defaultConfidenceLevel()\nprivate static int defaultScore()\nprivate static int defaultPersonalizedScore()\npublic static int getMaxScore()\nprivate static int defaultHotwordPhraseId()\npublic static int getMaxHotwordPhraseId()\nprivate static java.util.List<android.service.voice.HotwordAudioStream> defaultAudioStreams()\nprivate static android.os.PersistableBundle defaultExtras()\npublic static int getMaxBundleSize()\npublic @android.annotation.Nullable android.media.MediaSyncEvent getMediaSyncEvent()\nprivate static int defaultBackgroundAudioPower()\npublic static int getMaxBackgroundAudioPower()\npublic static int getParcelableSize(android.os.Parcelable)\npublic static int getUsageSize(android.service.voice.HotwordDetectedResult)\nprivate static int bitCount(long)\nprivate void onConstructed()\npublic @android.annotation.NonNull java.util.List<android.service.voice.HotwordAudioStream> getAudioStreams()\npublic void setProximity(double)\npublic @android.service.voice.HotwordDetectedResult.ProximityValue int getProximity()\nprivate @android.service.voice.HotwordDetectedResult.ProximityValue int convertToProximityLevel(double)\npublic android.service.voice.HotwordDetectedResult.Builder buildUpon()\nclass HotwordDetectedResult extends java.lang.Object implements [android.os.Parcelable]\npublic @android.annotation.NonNull android.service.voice.HotwordDetectedResult.Builder setAudioStreams(java.util.List<android.service.voice.HotwordAudioStream>)\nclass BaseBuilder extends java.lang.Object implements []\n@com.android.internal.util.DataClass(genConstructor=false, genBuilder=true, genEqualsHashCode=true, genHiddenConstDefs=true, genParcelable=true, genToString=true)\npublic @android.annotation.NonNull android.service.voice.HotwordDetectedResult.Builder setAudioStreams(java.util.List<android.service.voice.HotwordAudioStream>)\nclass BaseBuilder extends java.lang.Object implements []") + inputSignatures = "public static final int CONFIDENCE_LEVEL_NONE\npublic static final int CONFIDENCE_LEVEL_LOW\npublic static final int CONFIDENCE_LEVEL_LOW_MEDIUM\npublic static final int CONFIDENCE_LEVEL_MEDIUM\npublic static final int CONFIDENCE_LEVEL_MEDIUM_HIGH\npublic static final int CONFIDENCE_LEVEL_HIGH\npublic static final int CONFIDENCE_LEVEL_VERY_HIGH\npublic static final int HOTWORD_OFFSET_UNSET\npublic static final int AUDIO_CHANNEL_UNSET\npublic static final int BACKGROUND_AUDIO_POWER_UNSET\nprivate static final int LIMIT_HOTWORD_OFFSET_MAX_VALUE\nprivate static final int LIMIT_AUDIO_CHANNEL_MAX_VALUE\nprivate static final java.lang.String EXTRA_PROXIMITY\npublic static final int PROXIMITY_UNKNOWN\npublic static final int PROXIMITY_NEAR\npublic static final int PROXIMITY_FAR\nprivate final int mSpeakerId\nprivate final @android.service.voice.HotwordDetectedResult.HotwordConfidenceLevelValue int mConfidenceLevel\nprivate @android.annotation.Nullable android.media.MediaSyncEvent mMediaSyncEvent\nprivate int mHotwordOffsetMillis\nprivate int mHotwordDurationMillis\nprivate int mAudioChannel\nprivate boolean mHotwordDetectionPersonalized\nprivate final int mScore\nprivate final int mPersonalizedScore\nprivate final int mHotwordPhraseId\nprivate final @android.annotation.NonNull java.util.List<android.service.voice.HotwordAudioStream> mAudioStreams\nprivate final @android.annotation.NonNull android.os.PersistableBundle mExtras\nprivate static int sMaxBundleSize\nprivate final int mBackgroundAudioPower\nprivate static int defaultSpeakerId()\npublic static @android.annotation.SuppressLint int getMaxSpeakerId()\nprivate static int defaultConfidenceLevel()\nprivate static int defaultScore()\nprivate static int defaultPersonalizedScore()\npublic static int getMaxScore()\nprivate static int defaultHotwordPhraseId()\npublic static int getMaxHotwordPhraseId()\nprivate static java.util.List<android.service.voice.HotwordAudioStream> defaultAudioStreams()\nprivate static android.os.PersistableBundle defaultExtras()\npublic static int getMaxBundleSize()\npublic @android.annotation.Nullable android.media.MediaSyncEvent getMediaSyncEvent()\nprivate static int defaultBackgroundAudioPower()\npublic static int getMaxBackgroundAudioPower()\npublic static int getParcelableSize(android.os.Parcelable)\npublic static int getUsageSize(android.service.voice.HotwordDetectedResult)\nprivate static int bitCount(long)\nprivate void onConstructed()\npublic @android.annotation.NonNull java.util.List<android.service.voice.HotwordAudioStream> getAudioStreams()\npublic void setProximity(double)\npublic @android.service.voice.HotwordDetectedResult.ProximityValue int getProximity()\nprivate @android.service.voice.HotwordDetectedResult.ProximityValue int convertToProximityLevel(double)\npublic android.service.voice.HotwordDetectedResult.Builder buildUpon()\nclass HotwordDetectedResult extends java.lang.Object implements [android.os.Parcelable]\npublic @android.annotation.NonNull android.service.voice.HotwordDetectedResult.Builder setAudioStreams(java.util.List<android.service.voice.HotwordAudioStream>)\nclass BaseBuilder extends java.lang.Object implements []\n@com.android.internal.util.DataClass(genConstructor=false, genBuilder=true, genEqualsHashCode=true, genHiddenConstDefs=true, genParcelable=true, genToString=true)\npublic @android.annotation.NonNull android.service.voice.HotwordDetectedResult.Builder setAudioStreams(java.util.List<android.service.voice.HotwordAudioStream>)\nclass BaseBuilder extends java.lang.Object implements []") @Deprecated private void __metadata() {} diff --git a/core/java/android/service/voice/OWNERS b/core/java/android/service/voice/OWNERS index 763c79e20846..5f9f6bde3129 100644 --- a/core/java/android/service/voice/OWNERS +++ b/core/java/android/service/voice/OWNERS @@ -3,5 +3,4 @@ include /core/java/android/app/assist/OWNERS # The owner here should not be assist owner -liangyuchen@google.com adudani@google.com diff --git a/core/java/android/service/voice/VisualQueryAttentionResult.java b/core/java/android/service/voice/VisualQueryAttentionResult.java index 690990b46ed6..aad49d530671 100644 --- a/core/java/android/service/voice/VisualQueryAttentionResult.java +++ b/core/java/android/service/voice/VisualQueryAttentionResult.java @@ -16,15 +16,14 @@ package android.service.voice; -import android.annotation.FlaggedApi; import android.annotation.IntDef; import android.annotation.IntRange; import android.annotation.NonNull; import android.annotation.Nullable; +import android.annotation.SuppressLint; import android.annotation.SystemApi; import android.os.Parcel; import android.os.Parcelable; -import android.service.voice.flags.Flags; import com.android.internal.util.DataClass; @@ -45,13 +44,15 @@ import java.lang.annotation.RetentionPolicy; genToString = true ) @SystemApi -@FlaggedApi(Flags.FLAG_ALLOW_VARIOUS_ATTENTION_TYPES) +@SuppressLint("UnflaggedApi") // b/325678077 flags not supported in isolated process public final class VisualQueryAttentionResult implements Parcelable { /** Intention type to allow the system to listen to audio-visual query interactions. */ + @SuppressLint("UnflaggedApi") // b/325678077 flags not supported in isolated process public static final int INTERACTION_INTENTION_AUDIO_VISUAL = 0; /** Intention type to allow the system to listen to visual accessibility query interactions. */ + @SuppressLint("UnflaggedApi") // b/325678077 flags not supported in isolated process public static final int INTERACTION_INTENTION_VISUAL_ACCESSIBILITY = 1; /** @@ -94,6 +95,16 @@ public final class VisualQueryAttentionResult implements Parcelable { .setEngagementLevel(mEngagementLevel); } + /** + * TODO(b/301491148): Remove suppressLint on generated API when fixed or sdk finalized. + * Codegen does not support flaggedAPI, so needs to review manually on the generated code + * and makes sure the following: + * 1. SuppressLint is added back to the API after each run of codegen + * 2. No unwanted method is modified due to suppressLint annotation + * + * Run $ codegen $ANDROID_BUILD_TOP/frameworks/base/core/java/android/service/voice/VisualQueryAttentionResult.java + * for codegen on new APIs. + */ @@ -112,8 +123,8 @@ public final class VisualQueryAttentionResult implements Parcelable { /** @hide */ @IntDef(prefix = "INTERACTION_INTENTION_", value = { - INTERACTION_INTENTION_AUDIO_VISUAL, - INTERACTION_INTENTION_VISUAL_ACCESSIBILITY + INTERACTION_INTENTION_AUDIO_VISUAL, + INTERACTION_INTENTION_VISUAL_ACCESSIBILITY }) @Retention(RetentionPolicy.SOURCE) @DataClass.Generated.Member @@ -124,15 +135,15 @@ public final class VisualQueryAttentionResult implements Parcelable { public static String interactionIntentionToString(@InteractionIntention int value) { switch (value) { case INTERACTION_INTENTION_AUDIO_VISUAL: - return "INTERACTION_INTENTION_AUDIO_VISUAL"; + return "INTERACTION_INTENTION_AUDIO_VISUAL"; case INTERACTION_INTENTION_VISUAL_ACCESSIBILITY: - return "INTERACTION_INTENTION_VISUAL_ACCESSIBILITY"; + return "INTERACTION_INTENTION_VISUAL_ACCESSIBILITY"; default: return Integer.toHexString(value); } } @DataClass.Generated.Member - /* package-private */ VisualQueryAttentionResult( + /* package-private */ VisualQueryAttentionResult( @InteractionIntention int interactionIntention, @IntRange(from = 1, to = 100) int engagementLevel) { this.mInteractionIntention = interactionIntention; @@ -159,6 +170,7 @@ public final class VisualQueryAttentionResult implements Parcelable { * to after the attention signal is gained. */ @DataClass.Generated.Member + @SuppressLint("UnflaggedApi") public @InteractionIntention int getInteractionIntention() { return mInteractionIntention; } @@ -176,6 +188,7 @@ public final class VisualQueryAttentionResult implements Parcelable { * presentation of the device attention UI. */ @DataClass.Generated.Member + @SuppressLint("UnflaggedApi") public @IntRange(from = 1, to = 100) int getEngagementLevel() { return mEngagementLevel; } @@ -187,7 +200,7 @@ public final class VisualQueryAttentionResult implements Parcelable { // String fieldNameToString() { ... } return "VisualQueryAttentionResult { " + - "interactionIntention = " + interactionIntentionToString(mInteractionIntention) + ", " + + "interactionIntention = " + mInteractionIntention + ", " + "engagementLevel = " + mEngagementLevel + " }"; } @@ -223,6 +236,7 @@ public final class VisualQueryAttentionResult implements Parcelable { @Override @DataClass.Generated.Member + @SuppressLint("UnflaggedApi") public void writeToParcel(@NonNull Parcel dest, int flags) { // You can override field parcelling by defining methods like: // void parcelFieldName(Parcel dest, int flags) { ... } @@ -233,6 +247,7 @@ public final class VisualQueryAttentionResult implements Parcelable { @Override @DataClass.Generated.Member + @SuppressLint("UnflaggedApi") public int describeContents() { return 0; } /** @hide */ @@ -246,7 +261,6 @@ public final class VisualQueryAttentionResult implements Parcelable { int engagementLevel = in.readInt(); this.mInteractionIntention = interactionIntention; - if (!(mInteractionIntention == INTERACTION_INTENTION_AUDIO_VISUAL) && !(mInteractionIntention == INTERACTION_INTENTION_VISUAL_ACCESSIBILITY)) { throw new java.lang.IllegalArgumentException( @@ -254,7 +268,6 @@ public final class VisualQueryAttentionResult implements Parcelable { + "INTERACTION_INTENTION_AUDIO_VISUAL(" + INTERACTION_INTENTION_AUDIO_VISUAL + "), " + "INTERACTION_INTENTION_VISUAL_ACCESSIBILITY(" + INTERACTION_INTENTION_VISUAL_ACCESSIBILITY + ")"); } - this.mEngagementLevel = engagementLevel; com.android.internal.util.AnnotationValidations.validate( IntRange.class, null, mEngagementLevel, @@ -265,6 +278,7 @@ public final class VisualQueryAttentionResult implements Parcelable { } @DataClass.Generated.Member + @SuppressLint("UnflaggedApi") public static final @NonNull Parcelable.Creator<VisualQueryAttentionResult> CREATOR = new Parcelable.Creator<VisualQueryAttentionResult>() { @Override @@ -283,6 +297,7 @@ public final class VisualQueryAttentionResult implements Parcelable { */ @SuppressWarnings("WeakerAccess") @DataClass.Generated.Member + @SuppressLint("UnflaggedApi") public static final class Builder { private @InteractionIntention int mInteractionIntention; @@ -290,6 +305,7 @@ public final class VisualQueryAttentionResult implements Parcelable { private long mBuilderFieldsSet = 0L; + @SuppressLint("UnflaggedApi") public Builder() { } @@ -298,6 +314,7 @@ public final class VisualQueryAttentionResult implements Parcelable { * to after the attention signal is gained. */ @DataClass.Generated.Member + @SuppressLint("UnflaggedApi") public @NonNull Builder setInteractionIntention(@InteractionIntention int value) { checkNotUsed(); mBuilderFieldsSet |= 0x1; @@ -318,6 +335,7 @@ public final class VisualQueryAttentionResult implements Parcelable { * presentation of the device attention UI. */ @DataClass.Generated.Member + @SuppressLint("UnflaggedApi") public @NonNull Builder setEngagementLevel(@IntRange(from = 1, to = 100) int value) { checkNotUsed(); mBuilderFieldsSet |= 0x2; @@ -326,6 +344,7 @@ public final class VisualQueryAttentionResult implements Parcelable { } /** Builds the instance. This builder should not be touched after calling this! */ + @SuppressLint("UnflaggedApi") public @NonNull VisualQueryAttentionResult build() { checkNotUsed(); mBuilderFieldsSet |= 0x4; // Mark builder used @@ -351,10 +370,10 @@ public final class VisualQueryAttentionResult implements Parcelable { } @DataClass.Generated( - time = 1707773691880L, + time = 1710979945907L, codegenVersion = "1.0.23", sourceFile = "frameworks/base/core/java/android/service/voice/VisualQueryAttentionResult.java", - inputSignatures = "public static final int INTERACTION_INTENTION_AUDIO_VISUAL\npublic static final int INTERACTION_INTENTION_VISUAL_ACCESSIBILITY\nprivate final @android.service.voice.VisualQueryAttentionResult.InteractionIntention int mInteractionIntention\nprivate final @android.annotation.IntRange int mEngagementLevel\nprivate static @android.service.voice.VisualQueryAttentionResult.InteractionIntention int defaultInteractionIntention()\nprivate static int defaultEngagementLevel()\npublic android.service.voice.VisualQueryAttentionResult.Builder buildUpon()\nclass VisualQueryAttentionResult extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genConstructor=false, genBuilder=true, genEqualsHashCode=true, genHiddenConstDefs=true, genParcelable=true, genToString=true)") + inputSignatures = "public static final @android.annotation.SuppressLint int INTERACTION_INTENTION_AUDIO_VISUAL\npublic static final @android.annotation.SuppressLint int INTERACTION_INTENTION_VISUAL_ACCESSIBILITY\nprivate final @android.service.voice.VisualQueryAttentionResult.InteractionIntention int mInteractionIntention\nprivate final @android.annotation.IntRange int mEngagementLevel\nprivate static @android.service.voice.VisualQueryAttentionResult.InteractionIntention int defaultInteractionIntention()\nprivate static int defaultEngagementLevel()\npublic android.service.voice.VisualQueryAttentionResult.Builder buildUpon()\nclass VisualQueryAttentionResult extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genConstructor=false, genBuilder=true, genEqualsHashCode=true, genHiddenConstDefs=true, genParcelable=true, genToString=true)") @Deprecated private void __metadata() {} diff --git a/core/java/android/service/voice/VisualQueryDetectedResult.java b/core/java/android/service/voice/VisualQueryDetectedResult.java index 3b617948b41e..74b7da3cbe8a 100644 --- a/core/java/android/service/voice/VisualQueryDetectedResult.java +++ b/core/java/android/service/voice/VisualQueryDetectedResult.java @@ -16,13 +16,12 @@ package android.service.voice; -import android.annotation.FlaggedApi; import android.annotation.NonNull; import android.annotation.Nullable; +import android.annotation.SuppressLint; import android.annotation.SystemApi; import android.os.Parcel; import android.os.Parcelable; -import android.service.voice.flags.Flags; import com.android.internal.util.DataClass; import com.android.internal.util.Preconditions; @@ -43,7 +42,7 @@ import java.util.Objects; genToString = true ) @SystemApi -@FlaggedApi(Flags.FLAG_ALLOW_COMPLEX_RESULTS_EGRESS_FROM_VQDS) +@SuppressLint("UnflaggedApi") // b/325678077 flags not supported in isolated process public final class VisualQueryDetectedResult implements Parcelable { /** @@ -64,6 +63,7 @@ public final class VisualQueryDetectedResult implements Parcelable { return 0; } /** Maximum number of active speaker ids. **/ + @SuppressLint("UnflaggedApi") // b/325678077 flags not supported in isolated process public static int getMaxSpeakerId() { return 15; } @@ -100,6 +100,16 @@ public final class VisualQueryDetectedResult implements Parcelable { .setAccessibilityDetectionData(mAccessibilityDetectionData); } + /** + * TODO(b/301491148): Remove suppressLint on generated API when fixed or sdk finalized. + * Codegen does not support flaggedAPI, so needs to review manually on the generated code + * and makes sure the following: + * 1. SuppressLint is added back to the API after each run of codegen + * 2. No unwanted method is modified due to suppressLint annotation + * + * Run $ codegen $ANDROID_BUILD_TOP/frameworks/base/core/java/android/service/voice/VisualQueryAttentionResult.java + * for codegen on new APIs. + */ // Code below generated by codegen v1.0.23. // @@ -132,6 +142,7 @@ public final class VisualQueryDetectedResult implements Parcelable { * Text query being associated with the detection result. */ @DataClass.Generated.Member + @SuppressLint("UnflaggedApi") // b/325678077 flags not supported in isolated process public @NonNull String getPartialQuery() { return mPartialQuery; } @@ -142,6 +153,7 @@ public final class VisualQueryDetectedResult implements Parcelable { * <p>Only values between 0 and {@link #getMaxSpeakerId} (inclusive) are accepted. */ @DataClass.Generated.Member + @SuppressLint("UnflaggedApi") // b/325678077 flags not supported in isolated process public int getSpeakerId() { return mSpeakerId; } @@ -152,6 +164,7 @@ public final class VisualQueryDetectedResult implements Parcelable { * {@link VisualQueryDetector@setAccessibilityDetectionEnabled(boolean)} */ @DataClass.Generated.Member + @SuppressLint("UnflaggedApi") // b/325678077 flags not supported in isolated process public @Nullable byte[] getAccessibilityDetectionData() { return mAccessibilityDetectionData; } @@ -202,6 +215,7 @@ public final class VisualQueryDetectedResult implements Parcelable { @Override @DataClass.Generated.Member + @SuppressLint("UnflaggedApi") // b/325678077 flags not supported in isolated process public void writeToParcel(@NonNull Parcel dest, int flags) { // You can override field parcelling by defining methods like: // void parcelFieldName(Parcel dest, int flags) { ... } @@ -213,6 +227,7 @@ public final class VisualQueryDetectedResult implements Parcelable { @Override @DataClass.Generated.Member + @SuppressLint("UnflaggedApi") // b/325678077 flags not supported in isolated process public int describeContents() { return 0; } /** @hide */ @@ -236,6 +251,7 @@ public final class VisualQueryDetectedResult implements Parcelable { } @DataClass.Generated.Member + @SuppressLint("UnflaggedApi") // b/325678077 flags not supported in isolated process public static final @NonNull Parcelable.Creator<VisualQueryDetectedResult> CREATOR = new Parcelable.Creator<VisualQueryDetectedResult>() { @Override @@ -254,6 +270,7 @@ public final class VisualQueryDetectedResult implements Parcelable { */ @SuppressWarnings("WeakerAccess") @DataClass.Generated.Member + @SuppressLint("UnflaggedApi") // b/325678077 flags not supported in isolated process public static final class Builder { private @NonNull String mPartialQuery; @@ -262,6 +279,7 @@ public final class VisualQueryDetectedResult implements Parcelable { private long mBuilderFieldsSet = 0L; + @SuppressLint("UnflaggedApi") // b/325678077 flags not supported in isolated process public Builder() { } @@ -269,6 +287,7 @@ public final class VisualQueryDetectedResult implements Parcelable { * Text query being associated with the detection result. */ @DataClass.Generated.Member + @SuppressLint("UnflaggedApi") // b/325678077 flags not supported in isolated process public @NonNull Builder setPartialQuery(@NonNull String value) { checkNotUsed(); mBuilderFieldsSet |= 0x1; @@ -282,6 +301,7 @@ public final class VisualQueryDetectedResult implements Parcelable { * <p>Only values between 0 and {@link #getMaxSpeakerId} (inclusive) are accepted. */ @DataClass.Generated.Member + @SuppressLint("UnflaggedApi") // b/325678077 flags not supported in isolated process public @NonNull Builder setSpeakerId(int value) { checkNotUsed(); mBuilderFieldsSet |= 0x2; @@ -295,6 +315,7 @@ public final class VisualQueryDetectedResult implements Parcelable { * {@link VisualQueryDetector@setAccessibilityDetectionEnabled(boolean)} */ @DataClass.Generated.Member + @SuppressLint("UnflaggedApi") // b/325678077 flags not supported in isolated process public @NonNull Builder setAccessibilityDetectionData(@NonNull byte... value) { checkNotUsed(); mBuilderFieldsSet |= 0x4; @@ -303,6 +324,7 @@ public final class VisualQueryDetectedResult implements Parcelable { } /** Builds the instance. This builder should not be touched after calling this! */ + @SuppressLint("UnflaggedApi") // b/325678077 flags not supported in isolated process public @NonNull VisualQueryDetectedResult build() { checkNotUsed(); mBuilderFieldsSet |= 0x8; // Mark builder used @@ -332,10 +354,10 @@ public final class VisualQueryDetectedResult implements Parcelable { } @DataClass.Generated( - time = 1707429290528L, + time = 1710958903381L, codegenVersion = "1.0.23", sourceFile = "frameworks/base/core/java/android/service/voice/VisualQueryDetectedResult.java", - inputSignatures = "private final @android.annotation.NonNull java.lang.String mPartialQuery\nprivate final int mSpeakerId\nprivate final @android.annotation.Nullable byte[] mAccessibilityDetectionData\nprivate static java.lang.String defaultPartialQuery()\nprivate static int defaultSpeakerId()\npublic static int getMaxSpeakerId()\nprivate static byte[] defaultAccessibilityDetectionData()\nprivate void onConstructed()\npublic android.service.voice.VisualQueryDetectedResult.Builder buildUpon()\nclass VisualQueryDetectedResult extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genConstructor=false, genBuilder=true, genEqualsHashCode=true, genHiddenConstDefs=true, genParcelable=true, genToString=true)") + inputSignatures = "private final @android.annotation.NonNull java.lang.String mPartialQuery\nprivate final int mSpeakerId\nprivate final @android.annotation.Nullable byte[] mAccessibilityDetectionData\nprivate static java.lang.String defaultPartialQuery()\nprivate static int defaultSpeakerId()\npublic static @android.annotation.SuppressLint int getMaxSpeakerId()\nprivate static byte[] defaultAccessibilityDetectionData()\nprivate void onConstructed()\npublic android.service.voice.VisualQueryDetectedResult.Builder buildUpon()\nclass VisualQueryDetectedResult extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genConstructor=false, genBuilder=true, genEqualsHashCode=true, genHiddenConstDefs=true, genParcelable=true, genToString=true)") @Deprecated private void __metadata() {} diff --git a/core/java/android/service/voice/VisualQueryDetectionService.java b/core/java/android/service/voice/VisualQueryDetectionService.java index b9f4c3272207..8c9731d12df3 100644 --- a/core/java/android/service/voice/VisualQueryDetectionService.java +++ b/core/java/android/service/voice/VisualQueryDetectionService.java @@ -17,7 +17,6 @@ package android.service.voice; import android.annotation.DurationMillisLong; -import android.annotation.FlaggedApi; import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.SdkConstant; @@ -36,7 +35,6 @@ import android.os.ParcelFileDescriptor; import android.os.PersistableBundle; import android.os.RemoteException; import android.os.SharedMemory; -import android.service.voice.flags.Flags; import android.speech.IRecognitionServiceManager; import android.util.Log; import android.view.contentcapture.ContentCaptureManager; @@ -272,14 +270,10 @@ public abstract class VisualQueryDetectionService extends Service * */ public final void gainedAttention() { - if (Flags.allowVariousAttentionTypes()) { - gainedAttention(new VisualQueryAttentionResult.Builder().build()); - } else { - try { - mRemoteCallback.onAttentionGained(null); - } catch (RemoteException e) { - throw e.rethrowFromSystemServer(); - } + try { + mRemoteCallback.onAttentionGained(null); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); } } @@ -299,7 +293,7 @@ public abstract class VisualQueryDetectionService extends Service * * @param attentionResult Attention result of type {@link VisualQueryAttentionResult}. */ - @FlaggedApi(Flags.FLAG_ALLOW_VARIOUS_ATTENTION_TYPES) + @SuppressLint("UnflaggedApi") // b/325678077 flags not supported in isolated process public final void gainedAttention(@NonNull VisualQueryAttentionResult attentionResult) { try { mRemoteCallback.onAttentionGained(attentionResult); @@ -312,15 +306,10 @@ public abstract class VisualQueryDetectionService extends Service * Informs the system that all attention has lost to stop streaming. */ public final void lostAttention() { - if (Flags.allowVariousAttentionTypes()) { - lostAttention(VisualQueryAttentionResult.INTERACTION_INTENTION_AUDIO_VISUAL); - lostAttention(VisualQueryAttentionResult.INTERACTION_INTENTION_VISUAL_ACCESSIBILITY); - } else { - try { - mRemoteCallback.onAttentionLost(0); // placeholder - } catch (RemoteException e) { - throw e.rethrowFromSystemServer(); - } + try { + mRemoteCallback.onAttentionLost(0); // placeholder + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); } } @@ -332,7 +321,7 @@ public abstract class VisualQueryDetectionService extends Service * @param interactionIntention Interaction intention, one of * {@link VisualQueryAttentionResult#InteractionIntention}. */ - @FlaggedApi(Flags.FLAG_ALLOW_VARIOUS_ATTENTION_TYPES) + @SuppressLint("UnflaggedApi") // b/325678077 flags not supported in isolated process public final void lostAttention( @VisualQueryAttentionResult.InteractionIntention int interactionIntention) { try { @@ -375,7 +364,7 @@ public abstract class VisualQueryDetectionService extends Service * @param partialResult Partially detected result in the format of * {@link VisualQueryDetectedResult}. */ - @FlaggedApi(Flags.FLAG_ALLOW_COMPLEX_RESULTS_EGRESS_FROM_VQDS) + @SuppressLint("UnflaggedApi") // b/325678077 flags not supported in isolated process public final void streamQuery(@NonNull VisualQueryDetectedResult partialResult) { Objects.requireNonNull(partialResult); try { diff --git a/core/java/android/service/voice/VisualQueryDetector.java b/core/java/android/service/voice/VisualQueryDetector.java index 11858e841a8f..cd1d125708ac 100644 --- a/core/java/android/service/voice/VisualQueryDetector.java +++ b/core/java/android/service/voice/VisualQueryDetector.java @@ -20,7 +20,6 @@ import static android.Manifest.permission.CAMERA; import static android.Manifest.permission.RECORD_AUDIO; import android.annotation.CallbackExecutor; -import android.annotation.FlaggedApi; import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.RequiresPermission; @@ -34,7 +33,6 @@ import android.os.ParcelFileDescriptor; import android.os.PersistableBundle; import android.os.RemoteException; import android.os.SharedMemory; -import android.service.voice.flags.Flags; import android.text.TextUtils; import android.util.Slog; @@ -187,7 +185,7 @@ public class VisualQueryDetector { * @hide */ @SystemApi - @FlaggedApi(Flags.FLAG_ALLOW_COMPLEX_RESULTS_EGRESS_FROM_VQDS) + @SuppressLint("UnflaggedApi") // b/325678077 flags not supported in isolated process public boolean isAccessibilityDetectionEnabled() { Slog.d(TAG, "Fetching accessibility setting"); synchronized (mInitializationDelegate.getLock()) { @@ -214,7 +212,7 @@ public class VisualQueryDetector { * @hide */ @SystemApi - @FlaggedApi(Flags.FLAG_ALLOW_COMPLEX_RESULTS_EGRESS_FROM_VQDS) + @SuppressLint("UnflaggedApi") // b/325678077 flags not supported in isolated process public void setAccessibilityDetectionEnabledListener(@NonNull Consumer<Boolean> listener) { Slog.d(TAG, "Registering Accessibility settings listener."); synchronized (mInitializationDelegate.getLock()) { @@ -247,7 +245,7 @@ public class VisualQueryDetector { * @hide */ @SystemApi - @FlaggedApi(Flags.FLAG_ALLOW_COMPLEX_RESULTS_EGRESS_FROM_VQDS) + @SuppressLint("UnflaggedApi") // b/325678077 flags not supported in isolated process public void clearAccessibilityDetectionEnabledListener() { Slog.d(TAG, "Unregistering Accessibility settings listener."); synchronized (mInitializationDelegate.getLock()) { @@ -327,7 +325,7 @@ public class VisualQueryDetector { * * @param partialResult The partial query in a text form being streamed. */ - @FlaggedApi(Flags.FLAG_ALLOW_COMPLEX_RESULTS_EGRESS_FROM_VQDS) + @SuppressLint("UnflaggedApi") // b/325678077 flags not supported in isolated process default void onQueryDetected(@NonNull VisualQueryDetectedResult partialResult) { throw new UnsupportedOperationException("This emthod must be implemented for use."); } diff --git a/core/java/android/service/voice/flags/flags.aconfig b/core/java/android/service/voice/flags/flags.aconfig index 633304b94a5f..1ae7d8c34442 100644 --- a/core/java/android/service/voice/flags/flags.aconfig +++ b/core/java/android/service/voice/flags/flags.aconfig @@ -22,28 +22,4 @@ flag { namespace: "machine_learning" description: "This flag allows providing foreground app component along with onShow args." bug: "319409708" -} - -flag { - name: "allow_various_attention_types" - is_exported: true - namespace: "visual_query" - description: "This flag allows visual query detection service to set different attention types." - bug: "318617199" -} - -flag { - name: "allow_complex_results_egress_from_vqds" - is_exported: true - namespace: "visual_query" - description: "This flag allows visual query detection service egress detailed results. " - bug: "318617199" -} - -flag { - name: "allow_speaker_id_egress" - is_exported: true - namespace: "machine_learning" - description: "This flag allows hotword detection service and visual query detection service to egress current speaker profile id." - bug: "318617199" }
\ No newline at end of file diff --git a/core/java/android/text/MeasuredParagraph.java b/core/java/android/text/MeasuredParagraph.java index 2b6684ea8f1d..38aeb3757eae 100644 --- a/core/java/android/text/MeasuredParagraph.java +++ b/core/java/android/text/MeasuredParagraph.java @@ -700,6 +700,23 @@ public class MeasuredParagraph { bidiRequest = isRtl ? Bidi.RTL : Bidi.LTR; } mBidi = new Bidi(mCopiedBuffer, 0, null, 0, mCopiedBuffer.length, bidiRequest); + + if (mCopiedBuffer.length > 0 + && mBidi.getParagraphIndex(mCopiedBuffer.length - 1) != 0) { + // Historically, the MeasuredParagraph does not treat the CR letters as paragraph + // breaker but ICU BiDi treats it as paragraph breaker. In the MeasureParagraph, + // the given range always represents a single paragraph, so if the BiDi object has + // multiple paragraph, it should contains a CR letters in the text. Using CR is not + // common in Android and also it should not penalize the easy case, e.g. all LTR, + // check the paragraph count here and replace the CR letters and re-calculate + // BiDi again. + for (int i = 0; i < mTextLength; ++i) { + if (mCopiedBuffer[i] == '\r') { + mCopiedBuffer[i] = OBJECT_REPLACEMENT_CHARACTER; + } + } + mBidi = new Bidi(mCopiedBuffer, 0, null, 0, mCopiedBuffer.length, bidiRequest); + } mLevels.resize(mTextLength); byte[] rawArray = mLevels.getRawArray(); for (int i = 0; i < mTextLength; ++i) { diff --git a/core/java/android/view/PointerIcon.java b/core/java/android/view/PointerIcon.java index 9099f9855eab..147d56253dcf 100644 --- a/core/java/android/view/PointerIcon.java +++ b/core/java/android/view/PointerIcon.java @@ -36,6 +36,7 @@ import android.graphics.drawable.VectorDrawable; import android.os.Build; import android.os.Parcel; import android.os.Parcelable; +import android.os.PointerIconType; import android.util.Log; import android.util.SparseArray; @@ -52,10 +53,10 @@ public final class PointerIcon implements Parcelable { private static final String TAG = "PointerIcon"; /** {@hide} Type constant: Custom icon with a user-supplied bitmap. */ - public static final int TYPE_CUSTOM = -1; + public static final int TYPE_CUSTOM = PointerIconType.CUSTOM; /** Type constant: Null icon. It has no bitmap. */ - public static final int TYPE_NULL = 0; + public static final int TYPE_NULL = PointerIconType.TYPE_NULL; /** * Type constant: no icons are specified. If all views uses this, then the pointer icon falls @@ -63,63 +64,63 @@ public final class PointerIcon implements Parcelable { * to have the default icon. * @hide */ - public static final int TYPE_NOT_SPECIFIED = 1; + public static final int TYPE_NOT_SPECIFIED = PointerIconType.NOT_SPECIFIED; /** Type constant: Arrow icon. (Default mouse pointer) */ - public static final int TYPE_ARROW = 1000; + public static final int TYPE_ARROW = PointerIconType.ARROW; /** {@hide} Type constant: Spot hover icon for touchpads. */ - public static final int TYPE_SPOT_HOVER = 2000; + public static final int TYPE_SPOT_HOVER = PointerIconType.SPOT_HOVER; /** {@hide} Type constant: Spot touch icon for touchpads. */ - public static final int TYPE_SPOT_TOUCH = 2001; + public static final int TYPE_SPOT_TOUCH = PointerIconType.SPOT_TOUCH; /** {@hide} Type constant: Spot anchor icon for touchpads. */ - public static final int TYPE_SPOT_ANCHOR = 2002; + public static final int TYPE_SPOT_ANCHOR = PointerIconType.SPOT_ANCHOR; // Type constants for additional predefined icons for mice. /** Type constant: context-menu. */ - public static final int TYPE_CONTEXT_MENU = 1001; + public static final int TYPE_CONTEXT_MENU = PointerIconType.CONTEXT_MENU; /** Type constant: hand. */ - public static final int TYPE_HAND = 1002; + public static final int TYPE_HAND = PointerIconType.HAND; /** Type constant: help. */ - public static final int TYPE_HELP = 1003; + public static final int TYPE_HELP = PointerIconType.HELP; /** Type constant: wait. */ - public static final int TYPE_WAIT = 1004; + public static final int TYPE_WAIT = PointerIconType.WAIT; /** Type constant: cell. */ - public static final int TYPE_CELL = 1006; + public static final int TYPE_CELL = PointerIconType.CELL; /** Type constant: crosshair. */ - public static final int TYPE_CROSSHAIR = 1007; + public static final int TYPE_CROSSHAIR = PointerIconType.CROSSHAIR; /** Type constant: text. */ - public static final int TYPE_TEXT = 1008; + public static final int TYPE_TEXT = PointerIconType.TEXT; /** Type constant: vertical-text. */ - public static final int TYPE_VERTICAL_TEXT = 1009; + public static final int TYPE_VERTICAL_TEXT = PointerIconType.VERTICAL_TEXT; /** Type constant: alias (indicating an alias of/shortcut to something is * to be created. */ - public static final int TYPE_ALIAS = 1010; + public static final int TYPE_ALIAS = PointerIconType.ALIAS; /** Type constant: copy. */ - public static final int TYPE_COPY = 1011; + public static final int TYPE_COPY = PointerIconType.COPY; /** Type constant: no-drop. */ - public static final int TYPE_NO_DROP = 1012; + public static final int TYPE_NO_DROP = PointerIconType.NO_DROP; /** Type constant: all-scroll. */ - public static final int TYPE_ALL_SCROLL = 1013; + public static final int TYPE_ALL_SCROLL = PointerIconType.ALL_SCROLL; /** Type constant: horizontal double arrow mainly for resizing. */ - public static final int TYPE_HORIZONTAL_DOUBLE_ARROW = 1014; + public static final int TYPE_HORIZONTAL_DOUBLE_ARROW = PointerIconType.HORIZONTAL_DOUBLE_ARROW; /** Type constant: vertical double arrow mainly for resizing. */ - public static final int TYPE_VERTICAL_DOUBLE_ARROW = 1015; + public static final int TYPE_VERTICAL_DOUBLE_ARROW = PointerIconType.VERTICAL_DOUBLE_ARROW; /** Type constant: diagonal double arrow -- top-right to bottom-left. */ public static final int TYPE_TOP_RIGHT_DIAGONAL_DOUBLE_ARROW = 1016; @@ -128,19 +129,19 @@ public final class PointerIcon implements Parcelable { public static final int TYPE_TOP_LEFT_DIAGONAL_DOUBLE_ARROW = 1017; /** Type constant: zoom-in. */ - public static final int TYPE_ZOOM_IN = 1018; + public static final int TYPE_ZOOM_IN = PointerIconType.ZOOM_IN; /** Type constant: zoom-out. */ - public static final int TYPE_ZOOM_OUT = 1019; + public static final int TYPE_ZOOM_OUT = PointerIconType.ZOOM_OUT; /** Type constant: grab. */ - public static final int TYPE_GRAB = 1020; + public static final int TYPE_GRAB = PointerIconType.GRAB; /** Type constant: grabbing. */ - public static final int TYPE_GRABBING = 1021; + public static final int TYPE_GRABBING = PointerIconType.GRABBING; /** Type constant: handwriting. */ - public static final int TYPE_HANDWRITING = 1022; + public static final int TYPE_HANDWRITING = PointerIconType.HANDWRITING; // OEM private types should be defined starting at this range to avoid // conflicts with any system types that may be defined in the future. diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java index 3c61854c89f0..85d3688a9a1e 100644 --- a/core/java/android/view/ViewRootImpl.java +++ b/core/java/android/view/ViewRootImpl.java @@ -104,7 +104,7 @@ import static android.view.WindowManager.PROPERTY_COMPAT_ALLOW_SANDBOXING_VIEW_B import static android.view.WindowManagerGlobal.RELAYOUT_RES_CANCEL_AND_REDRAW; import static android.view.WindowManagerGlobal.RELAYOUT_RES_CONSUME_ALWAYS_SYSTEM_BARS; import static android.view.WindowManagerGlobal.RELAYOUT_RES_SURFACE_CHANGED; -import static android.view.accessibility.Flags.fixMergedContentChangeEvent; +import static android.view.accessibility.Flags.fixMergedContentChangeEventV2; import static android.view.accessibility.Flags.forceInvertColor; import static android.view.accessibility.Flags.reduceWindowContentChangedEventThrottle; import static android.view.flags.Flags.toolkitFrameRateTypingReadOnly; @@ -11796,7 +11796,7 @@ public final class ViewRootImpl implements ViewParent, } if (mSource != null) { - if (fixMergedContentChangeEvent()) { + if (fixMergedContentChangeEventV2()) { View newSource = getCommonPredecessor(mSource, source); if (newSource != null) { newSource = newSource.getSelfOrParentImportantForA11y(); diff --git a/core/java/android/view/accessibility/flags/accessibility_flags.aconfig b/core/java/android/view/accessibility/flags/accessibility_flags.aconfig index 91bd4ea0bc87..eefc72b82c24 100644 --- a/core/java/android/view/accessibility/flags/accessibility_flags.aconfig +++ b/core/java/android/view/accessibility/flags/accessibility_flags.aconfig @@ -56,9 +56,12 @@ flag { flag { namespace: "accessibility" - name: "fix_merged_content_change_event" + name: "fix_merged_content_change_event_v2" description: "Fixes event type and source of content change event merged in ViewRootImpl" bug: "277305460" + metadata { + purpose: PURPOSE_BUGFIX + } } flag { diff --git a/core/java/android/window/BackProgressAnimator.java b/core/java/android/window/BackProgressAnimator.java index 40e28cbbbd05..163e43a009e7 100644 --- a/core/java/android/window/BackProgressAnimator.java +++ b/core/java/android/window/BackProgressAnimator.java @@ -155,6 +155,14 @@ public class BackProgressAnimator { mSpring.animateToFinalPosition(0); } + /** + * Removes the finishCallback passed into {@link #onBackCancelled} + */ + public void removeOnBackCancelledFinishCallback() { + mSpring.removeEndListener(mOnAnimationEndListener); + mBackCancelledFinishRunnable = null; + } + /** Returns true if the back animation is in progress. */ boolean isBackAnimationInProgress() { return mBackAnimationInProgress; diff --git a/core/jni/OWNERS b/core/jni/OWNERS index 2a4f062478bd..593bdf0812aa 100644 --- a/core/jni/OWNERS +++ b/core/jni/OWNERS @@ -1,6 +1,5 @@ # Camera -per-file *Camera*,*camera* = cychen@google.com, epeev@google.com, etalvala@google.com -per-file *Camera*,*camera* = shuzhenwang@google.com, yinchiayeh@google.com, zhijunhe@google.com +per-file *Camera*,*camera* = file:platform/frameworks/av:/camera/OWNERS # Connectivity per-file android_net_* = codewiz@google.com, jchalard@google.com, lorenzo@google.com, reminv@google.com, satk@google.com diff --git a/core/proto/android/providers/settings/secure.proto b/core/proto/android/providers/settings/secure.proto index fcc85b7ec90f..5ae365cc92b4 100644 --- a/core/proto/android/providers/settings/secure.proto +++ b/core/proto/android/providers/settings/secure.proto @@ -249,6 +249,11 @@ message SecureSettingsProto { } optional DateTime date_time = 90; + message Display { + optional SettingProto screen_resolution_mode = 1 [ (android.privacy).dest = DEST_AUTOMATIC ]; + } + optional Display display = 100; + message Doze { option (android.msg_privacy).dest = DEST_EXPLICIT; @@ -727,5 +732,5 @@ message SecureSettingsProto { // Please insert fields in alphabetical order and group them into messages // if possible (to avoid reaching the method limit). - // Next tag = 100; + // Next tag = 101; } diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml index 31f2d8b13794..6431db9d174e 100644 --- a/core/res/AndroidManifest.xml +++ b/core/res/AndroidManifest.xml @@ -6778,7 +6778,7 @@ android:protectionLevel="signature" /> <!-- Allows an application to set the advanced features on BiometricDialog (SystemUI), including - logo, logo description. + logo, logo description, and content view with more options button. <p>Not for use by third-party applications. @FlaggedApi("android.hardware.biometrics.custom_biometric_prompt") --> diff --git a/core/tests/coretests/src/android/app/activity/ActivityThreadTest.java b/core/tests/coretests/src/android/app/activity/ActivityThreadTest.java index be0669c42d44..f87a9e2b3643 100644 --- a/core/tests/coretests/src/android/app/activity/ActivityThreadTest.java +++ b/core/tests/coretests/src/android/app/activity/ActivityThreadTest.java @@ -72,6 +72,7 @@ import android.os.IBinder; import android.platform.test.annotations.Presubmit; import android.platform.test.flag.junit.SetFlagsRule; import android.util.DisplayMetrics; +import android.util.Log; import android.util.MergedConfiguration; import android.view.Display; import android.view.View; @@ -109,6 +110,9 @@ import java.util.function.Consumer; @MediumTest @Presubmit public class ActivityThreadTest { + + private static final String TAG = "ActivityThreadTest"; + private static final int TIMEOUT_SEC = 10; // The first sequence number to try with. Use a large number to avoid conflicts with the first a @@ -968,8 +972,15 @@ public class ActivityThreadTest { @NonNull private static ClientTransaction newRelaunchResumeTransaction(@NonNull Activity activity) { final Configuration currentConfig = activity.getResources().getConfiguration(); - final ActivityWindowInfo activityWindowInfo = getActivityClientRecord(activity) - .getActivityWindowInfo(); + final ActivityClientRecord record = getActivityClientRecord(activity); + final ActivityWindowInfo activityWindowInfo; + if (record == null) { + Log.d(TAG, "The ActivityClientRecord of r=" + activity + " is not created yet. " + + "Likely because this call doesn't wait until activity launch."); + activityWindowInfo = new ActivityWindowInfo(); + } else { + activityWindowInfo = record.getActivityWindowInfo(); + } final ClientTransactionItem callbackItem = ActivityRelaunchItem.obtain( activity.getActivityToken(), null, null, 0, new MergedConfiguration(currentConfig, currentConfig), diff --git a/core/tests/coretests/src/android/app/activity/RegisterComponentCallbacksTest.java b/core/tests/coretests/src/android/app/activity/RegisterComponentCallbacksTest.java index c1b6666a2d17..fd1add9b4468 100644 --- a/core/tests/coretests/src/android/app/activity/RegisterComponentCallbacksTest.java +++ b/core/tests/coretests/src/android/app/activity/RegisterComponentCallbacksTest.java @@ -16,7 +16,7 @@ package android.app.activity; -import static android.content.ComponentCallbacks2.TRIM_MEMORY_RUNNING_LOW; +import static android.content.ComponentCallbacks2.TRIM_MEMORY_BACKGROUND; import static android.content.Context.OVERRIDABLE_COMPONENT_CALLBACKS; import static com.google.common.truth.Truth.assertThat; @@ -67,7 +67,7 @@ public class RegisterComponentCallbacksTest { config.windowConfiguration.setWindowingMode( WindowConfiguration.WINDOWING_MODE_FREEFORM); config.windowConfiguration.setBounds(new Rect(0, 0, 100, 100)); - final int trimMemoryLevel = TRIM_MEMORY_RUNNING_LOW; + final int trimMemoryLevel = TRIM_MEMORY_BACKGROUND; scenario.onActivity(activity -> { // It should be no-op to unregister a ComponentCallbacks without registration. @@ -98,7 +98,7 @@ public class RegisterComponentCallbacksTest { config.windowConfiguration.setWindowingMode( WindowConfiguration.WINDOWING_MODE_FREEFORM); config.windowConfiguration.setBounds(new Rect(0, 0, 100, 100)); - final int trimMemoryLevel = TRIM_MEMORY_RUNNING_LOW; + final int trimMemoryLevel = TRIM_MEMORY_BACKGROUND; scenario.onActivity(activity -> { // It should be no-op to unregister a ComponentCallbacks without registration. diff --git a/core/tests/coretests/src/android/window/WindowMetricsHelperTest.java b/core/tests/coretests/src/android/window/WindowMetricsHelperTest.java index 921866bfe5e9..22e88065ac2c 100644 --- a/core/tests/coretests/src/android/window/WindowMetricsHelperTest.java +++ b/core/tests/coretests/src/android/window/WindowMetricsHelperTest.java @@ -28,6 +28,8 @@ import androidx.test.filters.SmallTest; import androidx.test.rule.ActivityTestRule; import androidx.test.runner.AndroidJUnit4; +import com.android.window.flags.Flags; + import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; @@ -52,6 +54,10 @@ public class WindowMetricsHelperTest { @Test public void testGetLegacySizeMatchesDisplayGetSize() throws Throwable { + if (Flags.insetsDecoupledConfiguration()) { + // TODO (b/151861875): Introduce new test to cover the new behavior. + return; + } mActivityRule.runOnUiThread(() -> { Activity activity = mActivityRule.getActivity(); final WindowMetrics metrics = activity.getWindowManager().getCurrentWindowMetrics(); diff --git a/data/etc/privapp-permissions-platform.xml b/data/etc/privapp-permissions-platform.xml index ea3235bfff6c..fc4277e60bc5 100644 --- a/data/etc/privapp-permissions-platform.xml +++ b/data/etc/privapp-permissions-platform.xml @@ -42,6 +42,10 @@ applications that come with the platform <permission name="android.permission.READ_SYSTEM_GRAMMATICAL_GENDER"/> </privapp-permissions> + <privapp-permissions package="com.android.credentialmanager"> + <permission name="android.permission.SET_BIOMETRIC_DIALOG_ADVANCED" /> + </privapp-permissions> + <privapp-permissions package="com.android.externalstorage"> <permission name="android.permission.MOUNT_UNMOUNT_FILESYSTEMS"/> <permission name="android.permission.WRITE_MEDIA_STORAGE"/> diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java index 9bd8531d33dc..9b9798c6d93b 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java @@ -147,7 +147,7 @@ public class BackAnimationController implements RemoteCallable<BackAnimationCont private final Runnable mAnimationTimeoutRunnable = () -> { ProtoLog.w(WM_SHELL_BACK_PREVIEW, "Animation didn't finish in %d ms. Resetting...", MAX_ANIMATION_DURATION); - onBackAnimationFinished(); + finishBackAnimation(); }; private IBackAnimationFinishedCallback mBackAnimationFinishedCallback; @@ -156,6 +156,8 @@ public class BackAnimationController implements RemoteCallable<BackAnimationCont @Nullable private IOnBackInvokedCallback mActiveCallback; + @Nullable + private RemoteAnimationTarget[] mApps; @VisibleForTesting final RemoteCallback mNavigationObserver = new RemoteCallback( @@ -466,6 +468,14 @@ public class BackAnimationController implements RemoteCallable<BackAnimationCont } private void onGestureStarted(float touchX, float touchY, @BackEvent.SwipeEdge int swipeEdge) { + boolean interruptCancelPostCommitAnimation = mPostCommitAnimationInProgress + && mCurrentTracker.isFinished() && !mCurrentTracker.getTriggerBack() + && mQueuedTracker.isInInitialState(); + if (interruptCancelPostCommitAnimation) { + // If a system animation is currently in the post-commit phase animating an + // onBackCancelled event, let's interrupt it and start animating a new back gesture + resetTouchTracker(); + } TouchTracker touchTracker; if (mCurrentTracker.isInInitialState()) { touchTracker = mCurrentTracker; @@ -480,9 +490,15 @@ public class BackAnimationController implements RemoteCallable<BackAnimationCont touchTracker.setState(TouchTracker.TouchTrackerState.ACTIVE); mBackGestureStarted = true; - if (touchTracker == mCurrentTracker) { + if (interruptCancelPostCommitAnimation) { + // post-commit cancel is currently running. let's interrupt it and dispatch a new + // onBackStarted event. + mPostCommitAnimationInProgress = false; + mShellExecutor.removeCallbacks(mAnimationTimeoutRunnable); + startSystemAnimation(); + } else if (touchTracker == mCurrentTracker) { // Only start the back navigation if no other gesture is being processed. Otherwise, - // the back navigation will be started once the current gesture has finished. + // the back navigation will fall back to legacy back event injection. startBackNavigation(mCurrentTracker); } } @@ -818,6 +834,20 @@ public class BackAnimationController implements RemoteCallable<BackAnimationCont */ @VisibleForTesting void onBackAnimationFinished() { + if (!mPostCommitAnimationInProgress) { + // This can happen when a post-commit cancel animation was interrupted by a new back + // gesture but the timing of interruption was bad such that the back-callback + // implementation finished in between the time of the new gesture having started and + // the time of the back-callback receiving the new onBackStarted event. Due to the + // asynchronous APIs this isn't an unlikely case. To handle this, let's return early. + // The back-callback implementation will call onBackAnimationFinished again when it is + // done with animating the second gesture. + return; + } + finishBackAnimation(); + } + + private void finishBackAnimation() { // Stop timeout runner. mShellExecutor.removeCallbacks(mAnimationTimeoutRunnable); mPostCommitAnimationInProgress = false; @@ -878,6 +908,7 @@ public class BackAnimationController implements RemoteCallable<BackAnimationCont void finishBackNavigation(boolean triggerBack) { ProtoLog.d(WM_SHELL_BACK_PREVIEW, "BackAnimationController: finishBackNavigation()"); mActiveCallback = null; + mApps = null; mShouldStartOnNextMoveEvent = false; mOnBackStartDispatched = false; mPointerPilfered = false; @@ -914,6 +945,42 @@ public class BackAnimationController implements RemoteCallable<BackAnimationCont mTrackingLatency = false; } + private void startSystemAnimation() { + if (mBackNavigationInfo == null) { + ProtoLog.e(WM_SHELL_BACK_PREVIEW, "Lack of navigation info to start animation."); + return; + } + if (mApps == null) { + ProtoLog.w(WM_SHELL_BACK_PREVIEW, "Not starting animation due to mApps being null."); + return; + } + + final BackAnimationRunner runner = + mShellBackAnimationRegistry.getAnimationRunnerAndInit(mBackNavigationInfo); + if (runner == null) { + if (mBackAnimationFinishedCallback != null) { + try { + mBackAnimationFinishedCallback.onAnimationFinished(false); + } catch (RemoteException e) { + Log.w(TAG, "Failed call IBackNaviAnimationController", e); + } + } + return; + } + mActiveCallback = runner.getCallback(); + + ProtoLog.d(WM_SHELL_BACK_PREVIEW, "BackAnimationController: startAnimation()"); + + runner.startAnimation(mApps, /*wallpapers*/ null, /*nonApps*/ null, + () -> mShellExecutor.execute(this::onBackAnimationFinished)); + + if (mApps.length >= 1) { + mCurrentTracker.updateStartLocation(); + BackMotionEvent startEvent = mCurrentTracker.createStartEvent(mApps[0]); + dispatchOnBackStarted(mActiveCallback, startEvent); + } + } + private void createAdapter() { IBackAnimationRunner runner = new IBackAnimationRunner.Stub() { @@ -926,48 +993,9 @@ public class BackAnimationController implements RemoteCallable<BackAnimationCont mShellExecutor.execute( () -> { endLatencyTracking(); - if (mBackNavigationInfo == null) { - ProtoLog.e(WM_SHELL_BACK_PREVIEW, - "Lack of navigation info to start animation."); - return; - } - final BackAnimationRunner runner = - mShellBackAnimationRegistry.getAnimationRunnerAndInit( - mBackNavigationInfo); - if (runner == null) { - if (finishedCallback != null) { - try { - finishedCallback.onAnimationFinished(false); - } catch (RemoteException e) { - Log.w( - TAG, - "Failed call IBackNaviAnimationController", - e); - } - } - return; - } - mActiveCallback = runner.getCallback(); mBackAnimationFinishedCallback = finishedCallback; - - ProtoLog.d( - WM_SHELL_BACK_PREVIEW, - "BackAnimationController: startAnimation()"); - runner.startAnimation( - apps, - wallpapers, - nonApps, - () -> - mShellExecutor.execute( - BackAnimationController.this - ::onBackAnimationFinished)); - - if (apps.length >= 1) { - mCurrentTracker.updateStartLocation(); - BackMotionEvent startEvent = - mCurrentTracker.createStartEvent(apps[0]); - dispatchOnBackStarted(mActiveCallback, startEvent); - } + mApps = apps; + startSystemAnimation(); // Dispatch the first progress after animation start for // smoothing the initial animation, instead of waiting for next diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/back/CrossActivityBackAnimation.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/back/CrossActivityBackAnimation.kt index edf29dd484fc..7561a266c5ec 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/back/CrossActivityBackAnimation.kt +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/back/CrossActivityBackAnimation.kt @@ -283,6 +283,11 @@ class CrossActivityBackAnimation @Inject constructor( private inner class Callback : IOnBackInvokedCallback.Default() { override fun onBackStarted(backMotionEvent: BackMotionEvent) { + // in case we're still animating an onBackCancelled event, let's remove the finish- + // callback from the progress animator to prevent calling finishAnimation() before + // restarting a new animation + progressAnimator.removeOnBackCancelledFinishCallback(); + startBackAnimation(backMotionEvent) progressAnimator.onBackStarted(backMotionEvent) { backEvent: BackEvent -> onGestureProgress(backEvent) diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/back/CrossTaskBackAnimation.java b/libs/WindowManager/Shell/src/com/android/wm/shell/back/CrossTaskBackAnimation.java index 4b3154190910..cfd9fb613414 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/back/CrossTaskBackAnimation.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/back/CrossTaskBackAnimation.java @@ -275,8 +275,6 @@ public class CrossTaskBackAnimation extends ShellBackAnimation { private void onGestureProgress(@NonNull BackEvent backEvent) { if (!mBackInProgress) { - mIsRightEdge = backEvent.getSwipeEdge() == EDGE_RIGHT; - mInitialTouchPos.set(backEvent.getTouchX(), backEvent.getTouchY()); mBackInProgress = true; } float progress = backEvent.getProgress(); @@ -326,6 +324,13 @@ public class CrossTaskBackAnimation extends ShellBackAnimation { private final class Callback extends IOnBackInvokedCallback.Default { @Override public void onBackStarted(BackMotionEvent backEvent) { + // in case we're still animating an onBackCancelled event, let's remove the finish- + // callback from the progress animator to prevent calling finishAnimation() before + // restarting a new animation + mProgressAnimator.removeOnBackCancelledFinishCallback(); + + mIsRightEdge = backEvent.getSwipeEdge() == EDGE_RIGHT; + mInitialTouchPos.set(backEvent.getTouchX(), backEvent.getTouchY()); mProgressAnimator.onBackStarted(backEvent, CrossTaskBackAnimation.this::onGestureProgress); } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/back/CustomizeActivityAnimation.java b/libs/WindowManager/Shell/src/com/android/wm/shell/back/CustomizeActivityAnimation.java index 5254ff466123..fcf500a60166 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/back/CustomizeActivityAnimation.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/back/CustomizeActivityAnimation.java @@ -285,6 +285,11 @@ public class CustomizeActivityAnimation extends ShellBackAnimation { private final class Callback extends IOnBackInvokedCallback.Default { @Override public void onBackStarted(BackMotionEvent backEvent) { + // in case we're still animating an onBackCancelled event, let's remove the finish- + // callback from the progress animator to prevent calling finishAnimation() before + // restarting a new animation + mProgressAnimator.removeOnBackCancelledFinishCallback(); + mProgressAnimator.onBackStarted(backEvent, CustomizeActivityAnimation.this::onGestureProgress); } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransition.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransition.java index 6a1a62ea30a1..d60f5a631044 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransition.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransition.java @@ -27,6 +27,7 @@ import static android.view.WindowManager.TRANSIT_CHANGE; import static android.view.WindowManager.TRANSIT_OPEN; import static android.view.WindowManager.TRANSIT_PIP; import static android.view.WindowManager.TRANSIT_TO_BACK; +import static android.view.WindowManager.TRANSIT_TO_FRONT; import static android.view.WindowManager.transitTypeToString; import static android.window.TransitionInfo.FLAG_IS_DISPLAY; @@ -840,8 +841,11 @@ public class PipTransition extends PipTransitionController { && change.getTaskInfo().getWindowingMode() == WINDOWING_MODE_PINNED && !change.getContainer().equals(mCurrentPipTaskToken)) { // We support TRANSIT_PIP type (from RootWindowContainer) or TRANSIT_OPEN (from apps - // that enter PiP instantly on opening, mostly from CTS/Flicker tests) - if (transitType == TRANSIT_PIP || transitType == TRANSIT_OPEN) { + // that enter PiP instantly on opening, mostly from CTS/Flicker tests). + // TRANSIT_TO_FRONT, though uncommon with triggering PiP, should semantically also + // be allowed to animate if the task in question is pinned already - see b/308054074. + if (transitType == TRANSIT_PIP || transitType == TRANSIT_OPEN + || transitType == TRANSIT_TO_FRONT) { return true; } // This can happen if the request to enter PIP happens when we are collecting for diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/BackAnimationControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/BackAnimationControllerTest.java index 703eb199f260..2919782a758a 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/BackAnimationControllerTest.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/BackAnimationControllerTest.java @@ -409,6 +409,32 @@ public class BackAnimationControllerTest extends ShellTestCase { } @Test + public void gestureNotQueued_WhenPreviousGestureIsPostCommitCancelling() + throws RemoteException { + registerAnimation(BackNavigationInfo.TYPE_RETURN_TO_HOME); + createNavigationInfo(BackNavigationInfo.TYPE_RETURN_TO_HOME, + /* enableAnimation = */ true, + /* isAnimationCallback = */ false); + + doStartEvents(0, 100); + simulateRemoteAnimationStart(); + releaseBackGesture(); + + // Check that back cancellation is dispatched. + verify(mAnimatorCallback).onBackCancelled(); + verify(mBackAnimationRunner).onAnimationStart(anyInt(), any(), any(), any(), any()); + + reset(mAnimatorCallback); + reset(mBackAnimationRunner); + + // Verify that a new start event is dispatched if a new gesture is started during the + // post-commit cancel phase + triggerBackGesture(); + verify(mAnimatorCallback).onBackStarted(any()); + verify(mBackAnimationRunner).onAnimationStart(anyInt(), any(), any(), any(), any()); + } + + @Test public void acceptsGesture_transitionTimeout() throws RemoteException { registerAnimation(BackNavigationInfo.TYPE_RETURN_TO_HOME); createNavigationInfo(BackNavigationInfo.TYPE_RETURN_TO_HOME, diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/BackProgressAnimatorTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/BackProgressAnimatorTest.java index 7e26577e96d4..8932e60048e6 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/BackProgressAnimatorTest.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/BackProgressAnimatorTest.java @@ -134,6 +134,31 @@ public class BackProgressAnimatorTest { assertEquals(0, cancelCallbackCalled.getCount()); } + @Test + public void testCancelFinishCallbackNotInvokedWhenRemoved() throws InterruptedException { + // Give the animator some progress. + final BackMotionEvent backEvent = backMotionEventFrom(100, mTargetProgress); + mMainThreadHandler.post( + () -> mProgressAnimator.onBackProgressed(backEvent)); + mTargetProgressCalled.await(1, TimeUnit.SECONDS); + assertNotNull(mReceivedBackEvent); + + // call onBackCancelled (which animates progress to 0 before invoking the finishCallback) + CountDownLatch finishCallbackCalled = new CountDownLatch(1); + InstrumentationRegistry.getInstrumentation().runOnMainSync( + () -> mProgressAnimator.onBackCancelled(finishCallbackCalled::countDown)); + + // remove onBackCancelled finishCallback (while progress is still animating to 0) + InstrumentationRegistry.getInstrumentation().runOnMainSync( + () -> mProgressAnimator.removeOnBackCancelledFinishCallback()); + + // call reset (which triggers the finishCallback invocation, if one is present) + InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> mProgressAnimator.reset()); + + // verify that finishCallback is not invoked + assertEquals(1, finishCallbackCalled.getCount()); + } + private void onGestureProgress(BackEvent backEvent) { if (mTargetProgress == backEvent.getProgress()) { mReceivedBackEvent = backEvent; diff --git a/libs/hwui/Android.bp b/libs/hwui/Android.bp index cda94440affa..341c3e8cf373 100644 --- a/libs/hwui/Android.bp +++ b/libs/hwui/Android.bp @@ -78,13 +78,13 @@ cc_defaults { include_dirs: [ "external/skia/include/private", "external/skia/src/core", - "external/skia/src/utils", ], target: { android: { include_dirs: [ "external/skia/src/image", + "external/skia/src/utils", "external/skia/src/gpu", "external/skia/src/shaders", ], @@ -529,9 +529,7 @@ cc_defaults { "effects/GainmapRenderer.cpp", "pipeline/skia/BackdropFilterDrawable.cpp", "pipeline/skia/HolePunch.cpp", - "pipeline/skia/SkiaCpuPipeline.cpp", "pipeline/skia/SkiaDisplayList.cpp", - "pipeline/skia/SkiaPipeline.cpp", "pipeline/skia/SkiaRecordingCanvas.cpp", "pipeline/skia/StretchMask.cpp", "pipeline/skia/RenderNodeDrawable.cpp", @@ -570,7 +568,6 @@ cc_defaults { "HWUIProperties.sysprop", "Interpolator.cpp", "JankTracker.cpp", - "LayerUpdateQueue.cpp", "LightingInfo.cpp", "Matrix.cpp", "Mesh.cpp", @@ -607,9 +604,9 @@ cc_defaults { "pipeline/skia/GLFunctorDrawable.cpp", "pipeline/skia/LayerDrawable.cpp", "pipeline/skia/ShaderCache.cpp", - "pipeline/skia/SkiaGpuPipeline.cpp", "pipeline/skia/SkiaMemoryTracer.cpp", "pipeline/skia/SkiaOpenGLPipeline.cpp", + "pipeline/skia/SkiaPipeline.cpp", "pipeline/skia/SkiaProfileRenderer.cpp", "pipeline/skia/SkiaVulkanPipeline.cpp", "pipeline/skia/VkFunctorDrawable.cpp", @@ -633,6 +630,7 @@ cc_defaults { "DeferredLayerUpdater.cpp", "HardwareBitmapUploader.cpp", "Layer.cpp", + "LayerUpdateQueue.cpp", "ProfileDataContainer.cpp", "Readback.cpp", "TreeInfo.cpp", diff --git a/libs/hwui/Properties.h b/libs/hwui/Properties.h index c1510d96461f..ec53070f6cb8 100644 --- a/libs/hwui/Properties.h +++ b/libs/hwui/Properties.h @@ -242,7 +242,7 @@ enum class ProfileType { None, Console, Bars }; enum class OverdrawColorSet { Default = 0, Deuteranomaly }; -enum class RenderPipelineType { SkiaGL, SkiaVulkan, SkiaCpu, NotInitialized = 128 }; +enum class RenderPipelineType { SkiaGL, SkiaVulkan, NotInitialized = 128 }; enum class StretchEffectBehavior { ShaderHWUI, // Stretch shader in HWUI only, matrix scale in SF diff --git a/libs/hwui/pipeline/skia/SkiaCpuPipeline.cpp b/libs/hwui/pipeline/skia/SkiaCpuPipeline.cpp deleted file mode 100644 index 9b8373cea66d..000000000000 --- a/libs/hwui/pipeline/skia/SkiaCpuPipeline.cpp +++ /dev/null @@ -1,122 +0,0 @@ -/* - * Copyright (C) 2024 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. - */ - -#include "pipeline/skia/SkiaCpuPipeline.h" - -#include <system/window.h> - -#include "DeviceInfo.h" -#include "LightingInfo.h" -#include "renderthread/Frame.h" -#include "utils/Color.h" - -using namespace android::uirenderer::renderthread; - -namespace android { -namespace uirenderer { -namespace skiapipeline { - -void SkiaCpuPipeline::renderLayersImpl(const LayerUpdateQueue& layers, bool opaque) { - // Render all layers that need to be updated, in order. - for (size_t i = 0; i < layers.entries().size(); i++) { - renderLayerImpl(layers.entries()[i].renderNode.get(), layers.entries()[i].damage); - } -} - -// If the given node didn't have a layer surface, or had one of the wrong size, this method -// creates a new one and returns true. Otherwise does nothing and returns false. -bool SkiaCpuPipeline::createOrUpdateLayer(RenderNode* node, - const DamageAccumulator& damageAccumulator, - ErrorHandler* errorHandler) { - // compute the size of the surface (i.e. texture) to be allocated for this layer - const int surfaceWidth = ceilf(node->getWidth() / float(LAYER_SIZE)) * LAYER_SIZE; - const int surfaceHeight = ceilf(node->getHeight() / float(LAYER_SIZE)) * LAYER_SIZE; - - SkSurface* layer = node->getLayerSurface(); - if (!layer || layer->width() != surfaceWidth || layer->height() != surfaceHeight) { - SkImageInfo info; - info = SkImageInfo::Make(surfaceWidth, surfaceHeight, getSurfaceColorType(), - kPremul_SkAlphaType, getSurfaceColorSpace()); - SkSurfaceProps props(0, kUnknown_SkPixelGeometry); - node->setLayerSurface(SkSurfaces::Raster(info, &props)); - if (node->getLayerSurface()) { - // update the transform in window of the layer to reset its origin wrt light source - // position - Matrix4 windowTransform; - damageAccumulator.computeCurrentTransform(&windowTransform); - node->getSkiaLayer()->inverseTransformInWindow.loadInverse(windowTransform); - } else { - String8 cachesOutput; - mRenderThread.cacheManager().dumpMemoryUsage(cachesOutput, - &mRenderThread.renderState()); - ALOGE("%s", cachesOutput.c_str()); - if (errorHandler) { - std::ostringstream err; - err << "Unable to create layer for " << node->getName(); - const int maxTextureSize = DeviceInfo::get()->maxTextureSize(); - err << ", size " << info.width() << "x" << info.height() << " max size " - << maxTextureSize << " color type " << (int)info.colorType() << " has context " - << (int)(mRenderThread.getGrContext() != nullptr); - errorHandler->onError(err.str()); - } - } - return true; - } - return false; -} - -MakeCurrentResult SkiaCpuPipeline::makeCurrent() { - return MakeCurrentResult::AlreadyCurrent; -} - -Frame SkiaCpuPipeline::getFrame() { - return Frame(mSurface->width(), mSurface->height(), 0); -} - -IRenderPipeline::DrawResult SkiaCpuPipeline::draw( - const Frame& frame, const SkRect& screenDirty, const SkRect& dirty, - const LightGeometry& lightGeometry, LayerUpdateQueue* layerUpdateQueue, - const Rect& contentDrawBounds, bool opaque, const LightInfo& lightInfo, - const std::vector<sp<RenderNode>>& renderNodes, FrameInfoVisualizer* profiler, - const HardwareBufferRenderParams& bufferParams, std::mutex& profilerLock) { - LightingInfo::updateLighting(lightGeometry, lightInfo); - renderFrame(*layerUpdateQueue, dirty, renderNodes, opaque, contentDrawBounds, mSurface, - SkMatrix::I()); - return {true, IRenderPipeline::DrawResult::kUnknownTime, android::base::unique_fd{}}; -} - -bool SkiaCpuPipeline::setSurface(ANativeWindow* surface, SwapBehavior swapBehavior) { - if (surface) { - ANativeWindowBuffer* buffer; - surface->dequeueBuffer(surface, &buffer, nullptr); - int width, height; - surface->query(surface, NATIVE_WINDOW_WIDTH, &width); - surface->query(surface, NATIVE_WINDOW_HEIGHT, &height); - SkImageInfo imageInfo = - SkImageInfo::Make(width, height, mSurfaceColorType, - SkAlphaType::kPremul_SkAlphaType, mSurfaceColorSpace); - size_t widthBytes = width * imageInfo.bytesPerPixel(); - void* pixels = buffer->reserved[0]; - mSurface = SkSurfaces::WrapPixels(imageInfo, pixels, widthBytes); - } else { - mSurface = sk_sp<SkSurface>(); - } - return true; -} - -} /* namespace skiapipeline */ -} /* namespace uirenderer */ -} /* namespace android */ diff --git a/libs/hwui/pipeline/skia/SkiaCpuPipeline.h b/libs/hwui/pipeline/skia/SkiaCpuPipeline.h deleted file mode 100644 index 5a1014c2c2de..000000000000 --- a/libs/hwui/pipeline/skia/SkiaCpuPipeline.h +++ /dev/null @@ -1,77 +0,0 @@ -/* - * Copyright (C) 2024 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. - */ - -#pragma once - -#include "pipeline/skia/SkiaPipeline.h" - -namespace android { - -namespace uirenderer { -namespace skiapipeline { - -class SkiaCpuPipeline : public SkiaPipeline { -public: - SkiaCpuPipeline(renderthread::RenderThread& thread) : SkiaPipeline(thread) {} - ~SkiaCpuPipeline() {} - - bool pinImages(std::vector<SkImage*>& mutableImages) override { return false; } - bool pinImages(LsaVector<sk_sp<Bitmap>>& images) override { return false; } - void unpinImages() override {} - - // If the given node didn't have a layer surface, or had one of the wrong size, this method - // creates a new one and returns true. Otherwise does nothing and returns false. - bool createOrUpdateLayer(RenderNode* node, const DamageAccumulator& damageAccumulator, - ErrorHandler* errorHandler) override; - void renderLayersImpl(const LayerUpdateQueue& layers, bool opaque) override; - void setHardwareBuffer(AHardwareBuffer* hardwareBuffer) override {} - bool hasHardwareBuffer() override { return false; } - - renderthread::MakeCurrentResult makeCurrent() override; - renderthread::Frame getFrame() override; - renderthread::IRenderPipeline::DrawResult draw( - const renderthread::Frame& frame, const SkRect& screenDirty, const SkRect& dirty, - const LightGeometry& lightGeometry, LayerUpdateQueue* layerUpdateQueue, - const Rect& contentDrawBounds, bool opaque, const LightInfo& lightInfo, - const std::vector<sp<RenderNode>>& renderNodes, FrameInfoVisualizer* profiler, - const renderthread::HardwareBufferRenderParams& bufferParams, - std::mutex& profilerLock) override; - bool swapBuffers(const renderthread::Frame& frame, IRenderPipeline::DrawResult& drawResult, - const SkRect& screenDirty, FrameInfo* currentFrameInfo, - bool* requireSwap) override { - return false; - } - DeferredLayerUpdater* createTextureLayer() override { return nullptr; } - bool setSurface(ANativeWindow* surface, renderthread::SwapBehavior swapBehavior) override; - [[nodiscard]] android::base::unique_fd flush() override { - return android::base::unique_fd(-1); - }; - void onStop() override {} - bool isSurfaceReady() override { return mSurface.get() != nullptr; } - bool isContextReady() override { return true; } - - const SkM44& getPixelSnapMatrix() const override { - static const SkM44 sSnapMatrix = SkM44(); - return sSnapMatrix; - } - -private: - sk_sp<SkSurface> mSurface; -}; - -} /* namespace skiapipeline */ -} /* namespace uirenderer */ -} /* namespace android */ diff --git a/libs/hwui/pipeline/skia/SkiaGpuPipeline.cpp b/libs/hwui/pipeline/skia/SkiaGpuPipeline.cpp deleted file mode 100644 index cd9daf437bdb..000000000000 --- a/libs/hwui/pipeline/skia/SkiaGpuPipeline.cpp +++ /dev/null @@ -1,185 +0,0 @@ -/* - * Copyright (C) 2016 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. - */ - -#include "pipeline/skia/SkiaGpuPipeline.h" - -#include <SkImageAndroid.h> -#include <gui/TraceUtils.h> -#include <include/android/SkSurfaceAndroid.h> -#include <include/gpu/ganesh/SkSurfaceGanesh.h> - -using namespace android::uirenderer::renderthread; - -namespace android { -namespace uirenderer { -namespace skiapipeline { - -SkiaGpuPipeline::SkiaGpuPipeline(RenderThread& thread) : SkiaPipeline(thread) {} - -SkiaGpuPipeline::~SkiaGpuPipeline() { - unpinImages(); -} - -void SkiaGpuPipeline::renderLayersImpl(const LayerUpdateQueue& layers, bool opaque) { - sk_sp<GrDirectContext> cachedContext; - - // Render all layers that need to be updated, in order. - for (size_t i = 0; i < layers.entries().size(); i++) { - RenderNode* layerNode = layers.entries()[i].renderNode.get(); - renderLayerImpl(layerNode, layers.entries()[i].damage); - // cache the current context so that we can defer flushing it until - // either all the layers have been rendered or the context changes - GrDirectContext* currentContext = - GrAsDirectContext(layerNode->getLayerSurface()->getCanvas()->recordingContext()); - if (cachedContext.get() != currentContext) { - if (cachedContext.get()) { - ATRACE_NAME("flush layers (context changed)"); - cachedContext->flushAndSubmit(); - } - cachedContext.reset(SkSafeRef(currentContext)); - } - } - if (cachedContext.get()) { - ATRACE_NAME("flush layers"); - cachedContext->flushAndSubmit(); - } -} - -// If the given node didn't have a layer surface, or had one of the wrong size, this method -// creates a new one and returns true. Otherwise does nothing and returns false. -bool SkiaGpuPipeline::createOrUpdateLayer(RenderNode* node, - const DamageAccumulator& damageAccumulator, - ErrorHandler* errorHandler) { - // compute the size of the surface (i.e. texture) to be allocated for this layer - const int surfaceWidth = ceilf(node->getWidth() / float(LAYER_SIZE)) * LAYER_SIZE; - const int surfaceHeight = ceilf(node->getHeight() / float(LAYER_SIZE)) * LAYER_SIZE; - - SkSurface* layer = node->getLayerSurface(); - if (!layer || layer->width() != surfaceWidth || layer->height() != surfaceHeight) { - SkImageInfo info; - info = SkImageInfo::Make(surfaceWidth, surfaceHeight, getSurfaceColorType(), - kPremul_SkAlphaType, getSurfaceColorSpace()); - SkSurfaceProps props(0, kUnknown_SkPixelGeometry); - SkASSERT(mRenderThread.getGrContext() != nullptr); - node->setLayerSurface(SkSurfaces::RenderTarget(mRenderThread.getGrContext(), - skgpu::Budgeted::kYes, info, 0, - this->getSurfaceOrigin(), &props)); - if (node->getLayerSurface()) { - // update the transform in window of the layer to reset its origin wrt light source - // position - Matrix4 windowTransform; - damageAccumulator.computeCurrentTransform(&windowTransform); - node->getSkiaLayer()->inverseTransformInWindow.loadInverse(windowTransform); - } else { - String8 cachesOutput; - mRenderThread.cacheManager().dumpMemoryUsage(cachesOutput, - &mRenderThread.renderState()); - ALOGE("%s", cachesOutput.c_str()); - if (errorHandler) { - std::ostringstream err; - err << "Unable to create layer for " << node->getName(); - const int maxTextureSize = DeviceInfo::get()->maxTextureSize(); - err << ", size " << info.width() << "x" << info.height() << " max size " - << maxTextureSize << " color type " << (int)info.colorType() << " has context " - << (int)(mRenderThread.getGrContext() != nullptr); - errorHandler->onError(err.str()); - } - } - return true; - } - return false; -} - -bool SkiaGpuPipeline::pinImages(std::vector<SkImage*>& mutableImages) { - if (!mRenderThread.getGrContext()) { - ALOGD("Trying to pin an image with an invalid GrContext"); - return false; - } - for (SkImage* image : mutableImages) { - if (skgpu::ganesh::PinAsTexture(mRenderThread.getGrContext(), image)) { - mPinnedImages.emplace_back(sk_ref_sp(image)); - } else { - return false; - } - } - return true; -} - -void SkiaGpuPipeline::unpinImages() { - for (auto& image : mPinnedImages) { - skgpu::ganesh::UnpinTexture(mRenderThread.getGrContext(), image.get()); - } - mPinnedImages.clear(); -} - -void SkiaGpuPipeline::prepareToDraw(const RenderThread& thread, Bitmap* bitmap) { - GrDirectContext* context = thread.getGrContext(); - if (context && !bitmap->isHardware()) { - ATRACE_FORMAT("Bitmap#prepareToDraw %dx%d", bitmap->width(), bitmap->height()); - auto image = bitmap->makeImage(); - if (image.get()) { - skgpu::ganesh::PinAsTexture(context, image.get()); - skgpu::ganesh::UnpinTexture(context, image.get()); - // A submit is necessary as there may not be a frame coming soon, so without a call - // to submit these texture uploads can just sit in the queue building up until - // we run out of RAM - context->flushAndSubmit(); - } - } -} - -sk_sp<SkSurface> SkiaGpuPipeline::getBufferSkSurface( - const renderthread::HardwareBufferRenderParams& bufferParams) { - auto bufferColorSpace = bufferParams.getColorSpace(); - if (mBufferSurface == nullptr || mBufferColorSpace == nullptr || - !SkColorSpace::Equals(mBufferColorSpace.get(), bufferColorSpace.get())) { - mBufferSurface = SkSurfaces::WrapAndroidHardwareBuffer( - mRenderThread.getGrContext(), mHardwareBuffer, kTopLeft_GrSurfaceOrigin, - bufferColorSpace, nullptr, true); - mBufferColorSpace = bufferColorSpace; - } - return mBufferSurface; -} - -void SkiaGpuPipeline::dumpResourceCacheUsage() const { - int resources; - size_t bytes; - mRenderThread.getGrContext()->getResourceCacheUsage(&resources, &bytes); - size_t maxBytes = mRenderThread.getGrContext()->getResourceCacheLimit(); - - SkString log("Resource Cache Usage:\n"); - log.appendf("%8d items\n", resources); - log.appendf("%8zu bytes (%.2f MB) out of %.2f MB maximum\n", bytes, - bytes * (1.0f / (1024.0f * 1024.0f)), maxBytes * (1.0f / (1024.0f * 1024.0f))); - - ALOGD("%s", log.c_str()); -} - -void SkiaGpuPipeline::setHardwareBuffer(AHardwareBuffer* buffer) { - if (mHardwareBuffer) { - AHardwareBuffer_release(mHardwareBuffer); - mHardwareBuffer = nullptr; - } - - if (buffer) { - AHardwareBuffer_acquire(buffer); - mHardwareBuffer = buffer; - } -} - -} /* namespace skiapipeline */ -} /* namespace uirenderer */ -} /* namespace android */ diff --git a/libs/hwui/pipeline/skia/SkiaOpenGLPipeline.cpp b/libs/hwui/pipeline/skia/SkiaOpenGLPipeline.cpp index e4b1f916b4d6..c8d598702a7c 100644 --- a/libs/hwui/pipeline/skia/SkiaOpenGLPipeline.cpp +++ b/libs/hwui/pipeline/skia/SkiaOpenGLPipeline.cpp @@ -14,25 +14,25 @@ * limitations under the License. */ -#include "pipeline/skia/SkiaOpenGLPipeline.h" +#include "SkiaOpenGLPipeline.h" +#include <include/gpu/ganesh/SkSurfaceGanesh.h> +#include <include/gpu/ganesh/gl/GrGLBackendSurface.h> +#include <include/gpu/gl/GrGLTypes.h> #include <GrBackendSurface.h> #include <SkBlendMode.h> #include <SkImageInfo.h> #include <cutils/properties.h> #include <gui/TraceUtils.h> -#include <include/gpu/ganesh/SkSurfaceGanesh.h> -#include <include/gpu/ganesh/gl/GrGLBackendSurface.h> -#include <include/gpu/gl/GrGLTypes.h> #include <strings.h> #include "DeferredLayerUpdater.h" #include "FrameInfo.h" +#include "LayerDrawable.h" #include "LightingInfo.h" +#include "SkiaPipeline.h" +#include "SkiaProfileRenderer.h" #include "hwui/Bitmap.h" -#include "pipeline/skia/LayerDrawable.h" -#include "pipeline/skia/SkiaGpuPipeline.h" -#include "pipeline/skia/SkiaProfileRenderer.h" #include "private/hwui/DrawGlInfo.h" #include "renderstate/RenderState.h" #include "renderthread/EglManager.h" @@ -47,7 +47,7 @@ namespace uirenderer { namespace skiapipeline { SkiaOpenGLPipeline::SkiaOpenGLPipeline(RenderThread& thread) - : SkiaGpuPipeline(thread), mEglManager(thread.eglManager()) { + : SkiaPipeline(thread), mEglManager(thread.eglManager()) { thread.renderState().registerContextCallback(this); } diff --git a/libs/hwui/platform/android/pipeline/skia/SkiaOpenGLPipeline.h b/libs/hwui/pipeline/skia/SkiaOpenGLPipeline.h index 6e7478288777..ebe8b6e15d44 100644 --- a/libs/hwui/platform/android/pipeline/skia/SkiaOpenGLPipeline.h +++ b/libs/hwui/pipeline/skia/SkiaOpenGLPipeline.h @@ -19,7 +19,7 @@ #include <EGL/egl.h> #include <system/window.h> -#include "pipeline/skia/SkiaGpuPipeline.h" +#include "SkiaPipeline.h" #include "renderstate/RenderState.h" #include "renderthread/HardwareBufferRenderParams.h" @@ -30,7 +30,7 @@ class Bitmap; namespace uirenderer { namespace skiapipeline { -class SkiaOpenGLPipeline : public SkiaGpuPipeline, public IGpuContextCallback { +class SkiaOpenGLPipeline : public SkiaPipeline, public IGpuContextCallback { public: SkiaOpenGLPipeline(renderthread::RenderThread& thread); virtual ~SkiaOpenGLPipeline(); diff --git a/libs/hwui/pipeline/skia/SkiaPipeline.cpp b/libs/hwui/pipeline/skia/SkiaPipeline.cpp index 2cfdd3fb0315..99469d1e3628 100644 --- a/libs/hwui/pipeline/skia/SkiaPipeline.cpp +++ b/libs/hwui/pipeline/skia/SkiaPipeline.cpp @@ -14,8 +14,11 @@ * limitations under the License. */ -#include "pipeline/skia/SkiaPipeline.h" +#include "SkiaPipeline.h" +#include <include/android/SkSurfaceAndroid.h> +#include <include/gpu/ganesh/SkSurfaceGanesh.h> +#include <include/encode/SkPngEncoder.h> #include <SkCanvas.h> #include <SkColor.h> #include <SkColorSpace.h> @@ -37,9 +40,6 @@ #include <SkTypeface.h> #include <android-base/properties.h> #include <gui/TraceUtils.h> -#include <include/android/SkSurfaceAndroid.h> -#include <include/encode/SkPngEncoder.h> -#include <include/gpu/ganesh/SkSurfaceGanesh.h> #include <unistd.h> #include <sstream> @@ -62,13 +62,37 @@ SkiaPipeline::SkiaPipeline(RenderThread& thread) : mRenderThread(thread) { setSurfaceColorProperties(mColorMode); } -SkiaPipeline::~SkiaPipeline() {} +SkiaPipeline::~SkiaPipeline() { + unpinImages(); +} void SkiaPipeline::onDestroyHardwareResources() { unpinImages(); mRenderThread.cacheManager().trimStaleResources(); } +bool SkiaPipeline::pinImages(std::vector<SkImage*>& mutableImages) { + if (!mRenderThread.getGrContext()) { + ALOGD("Trying to pin an image with an invalid GrContext"); + return false; + } + for (SkImage* image : mutableImages) { + if (skgpu::ganesh::PinAsTexture(mRenderThread.getGrContext(), image)) { + mPinnedImages.emplace_back(sk_ref_sp(image)); + } else { + return false; + } + } + return true; +} + +void SkiaPipeline::unpinImages() { + for (auto& image : mPinnedImages) { + skgpu::ganesh::UnpinTexture(mRenderThread.getGrContext(), image.get()); + } + mPinnedImages.clear(); +} + void SkiaPipeline::renderLayers(const LightGeometry& lightGeometry, LayerUpdateQueue* layerUpdateQueue, bool opaque, const LightInfo& lightInfo) { @@ -78,53 +102,136 @@ void SkiaPipeline::renderLayers(const LightGeometry& lightGeometry, layerUpdateQueue->clear(); } -void SkiaPipeline::renderLayerImpl(RenderNode* layerNode, const Rect& layerDamage) { - // only schedule repaint if node still on layer - possible it may have been - // removed during a dropped frame, but layers may still remain scheduled so - // as not to lose info on what portion is damaged - if (CC_UNLIKELY(layerNode->getLayerSurface() == nullptr)) { - return; - } - SkASSERT(layerNode->getLayerSurface()); - SkiaDisplayList* displayList = layerNode->getDisplayList().asSkiaDl(); - if (!displayList || displayList->isEmpty()) { - ALOGE("%p drawLayers(%s) : missing drawable", layerNode, layerNode->getName()); - return; - } +void SkiaPipeline::renderLayersImpl(const LayerUpdateQueue& layers, bool opaque) { + sk_sp<GrDirectContext> cachedContext; - SkCanvas* layerCanvas = layerNode->getLayerSurface()->getCanvas(); + // Render all layers that need to be updated, in order. + for (size_t i = 0; i < layers.entries().size(); i++) { + RenderNode* layerNode = layers.entries()[i].renderNode.get(); + // only schedule repaint if node still on layer - possible it may have been + // removed during a dropped frame, but layers may still remain scheduled so + // as not to lose info on what portion is damaged + if (CC_UNLIKELY(layerNode->getLayerSurface() == nullptr)) { + continue; + } + SkASSERT(layerNode->getLayerSurface()); + SkiaDisplayList* displayList = layerNode->getDisplayList().asSkiaDl(); + if (!displayList || displayList->isEmpty()) { + ALOGE("%p drawLayers(%s) : missing drawable", layerNode, layerNode->getName()); + return; + } - int saveCount = layerCanvas->save(); - SkASSERT(saveCount == 1); + const Rect& layerDamage = layers.entries()[i].damage; - layerCanvas->androidFramework_setDeviceClipRestriction(layerDamage.toSkIRect()); + SkCanvas* layerCanvas = layerNode->getLayerSurface()->getCanvas(); - // TODO: put localized light center calculation and storage to a drawable related code. - // It does not seem right to store something localized in a global state - // fix here and in recordLayers - const Vector3 savedLightCenter(LightingInfo::getLightCenterRaw()); - Vector3 transformedLightCenter(savedLightCenter); - // map current light center into RenderNode's coordinate space - layerNode->getSkiaLayer()->inverseTransformInWindow.mapPoint3d(transformedLightCenter); - LightingInfo::setLightCenterRaw(transformedLightCenter); - - const RenderProperties& properties = layerNode->properties(); - const SkRect bounds = SkRect::MakeWH(properties.getWidth(), properties.getHeight()); - if (properties.getClipToBounds() && layerCanvas->quickReject(bounds)) { - return; - } + int saveCount = layerCanvas->save(); + SkASSERT(saveCount == 1); - ATRACE_FORMAT("drawLayer [%s] %.1f x %.1f", layerNode->getName(), bounds.width(), - bounds.height()); + layerCanvas->androidFramework_setDeviceClipRestriction(layerDamage.toSkIRect()); - layerNode->getSkiaLayer()->hasRenderedSinceRepaint = false; - layerCanvas->clear(SK_ColorTRANSPARENT); + // TODO: put localized light center calculation and storage to a drawable related code. + // It does not seem right to store something localized in a global state + // fix here and in recordLayers + const Vector3 savedLightCenter(LightingInfo::getLightCenterRaw()); + Vector3 transformedLightCenter(savedLightCenter); + // map current light center into RenderNode's coordinate space + layerNode->getSkiaLayer()->inverseTransformInWindow.mapPoint3d(transformedLightCenter); + LightingInfo::setLightCenterRaw(transformedLightCenter); - RenderNodeDrawable root(layerNode, layerCanvas, false); - root.forceDraw(layerCanvas); - layerCanvas->restoreToCount(saveCount); + const RenderProperties& properties = layerNode->properties(); + const SkRect bounds = SkRect::MakeWH(properties.getWidth(), properties.getHeight()); + if (properties.getClipToBounds() && layerCanvas->quickReject(bounds)) { + return; + } - LightingInfo::setLightCenterRaw(savedLightCenter); + ATRACE_FORMAT("drawLayer [%s] %.1f x %.1f", layerNode->getName(), bounds.width(), + bounds.height()); + + layerNode->getSkiaLayer()->hasRenderedSinceRepaint = false; + layerCanvas->clear(SK_ColorTRANSPARENT); + + RenderNodeDrawable root(layerNode, layerCanvas, false); + root.forceDraw(layerCanvas); + layerCanvas->restoreToCount(saveCount); + + LightingInfo::setLightCenterRaw(savedLightCenter); + + // cache the current context so that we can defer flushing it until + // either all the layers have been rendered or the context changes + GrDirectContext* currentContext = + GrAsDirectContext(layerNode->getLayerSurface()->getCanvas()->recordingContext()); + if (cachedContext.get() != currentContext) { + if (cachedContext.get()) { + ATRACE_NAME("flush layers (context changed)"); + cachedContext->flushAndSubmit(); + } + cachedContext.reset(SkSafeRef(currentContext)); + } + } + + if (cachedContext.get()) { + ATRACE_NAME("flush layers"); + cachedContext->flushAndSubmit(); + } +} + +bool SkiaPipeline::createOrUpdateLayer(RenderNode* node, const DamageAccumulator& damageAccumulator, + ErrorHandler* errorHandler) { + // compute the size of the surface (i.e. texture) to be allocated for this layer + const int surfaceWidth = ceilf(node->getWidth() / float(LAYER_SIZE)) * LAYER_SIZE; + const int surfaceHeight = ceilf(node->getHeight() / float(LAYER_SIZE)) * LAYER_SIZE; + + SkSurface* layer = node->getLayerSurface(); + if (!layer || layer->width() != surfaceWidth || layer->height() != surfaceHeight) { + SkImageInfo info; + info = SkImageInfo::Make(surfaceWidth, surfaceHeight, getSurfaceColorType(), + kPremul_SkAlphaType, getSurfaceColorSpace()); + SkSurfaceProps props(0, kUnknown_SkPixelGeometry); + SkASSERT(mRenderThread.getGrContext() != nullptr); + node->setLayerSurface(SkSurfaces::RenderTarget(mRenderThread.getGrContext(), + skgpu::Budgeted::kYes, info, 0, + this->getSurfaceOrigin(), &props)); + if (node->getLayerSurface()) { + // update the transform in window of the layer to reset its origin wrt light source + // position + Matrix4 windowTransform; + damageAccumulator.computeCurrentTransform(&windowTransform); + node->getSkiaLayer()->inverseTransformInWindow.loadInverse(windowTransform); + } else { + String8 cachesOutput; + mRenderThread.cacheManager().dumpMemoryUsage(cachesOutput, + &mRenderThread.renderState()); + ALOGE("%s", cachesOutput.c_str()); + if (errorHandler) { + std::ostringstream err; + err << "Unable to create layer for " << node->getName(); + const int maxTextureSize = DeviceInfo::get()->maxTextureSize(); + err << ", size " << info.width() << "x" << info.height() << " max size " + << maxTextureSize << " color type " << (int)info.colorType() << " has context " + << (int)(mRenderThread.getGrContext() != nullptr); + errorHandler->onError(err.str()); + } + } + return true; + } + return false; +} + +void SkiaPipeline::prepareToDraw(const RenderThread& thread, Bitmap* bitmap) { + GrDirectContext* context = thread.getGrContext(); + if (context && !bitmap->isHardware()) { + ATRACE_FORMAT("Bitmap#prepareToDraw %dx%d", bitmap->width(), bitmap->height()); + auto image = bitmap->makeImage(); + if (image.get()) { + skgpu::ganesh::PinAsTexture(context, image.get()); + skgpu::ganesh::UnpinTexture(context, image.get()); + // A submit is necessary as there may not be a frame coming soon, so without a call + // to submit these texture uploads can just sit in the queue building up until + // we run out of RAM + context->flushAndSubmit(); + } + } } static void savePictureAsync(const sk_sp<SkData>& data, const std::string& filename) { @@ -492,6 +599,45 @@ void SkiaPipeline::renderFrameImpl(const SkRect& clip, } } +void SkiaPipeline::dumpResourceCacheUsage() const { + int resources; + size_t bytes; + mRenderThread.getGrContext()->getResourceCacheUsage(&resources, &bytes); + size_t maxBytes = mRenderThread.getGrContext()->getResourceCacheLimit(); + + SkString log("Resource Cache Usage:\n"); + log.appendf("%8d items\n", resources); + log.appendf("%8zu bytes (%.2f MB) out of %.2f MB maximum\n", bytes, + bytes * (1.0f / (1024.0f * 1024.0f)), maxBytes * (1.0f / (1024.0f * 1024.0f))); + + ALOGD("%s", log.c_str()); +} + +void SkiaPipeline::setHardwareBuffer(AHardwareBuffer* buffer) { + if (mHardwareBuffer) { + AHardwareBuffer_release(mHardwareBuffer); + mHardwareBuffer = nullptr; + } + + if (buffer) { + AHardwareBuffer_acquire(buffer); + mHardwareBuffer = buffer; + } +} + +sk_sp<SkSurface> SkiaPipeline::getBufferSkSurface( + const renderthread::HardwareBufferRenderParams& bufferParams) { + auto bufferColorSpace = bufferParams.getColorSpace(); + if (mBufferSurface == nullptr || mBufferColorSpace == nullptr || + !SkColorSpace::Equals(mBufferColorSpace.get(), bufferColorSpace.get())) { + mBufferSurface = SkSurfaces::WrapAndroidHardwareBuffer( + mRenderThread.getGrContext(), mHardwareBuffer, kTopLeft_GrSurfaceOrigin, + bufferColorSpace, nullptr, true); + mBufferColorSpace = bufferColorSpace; + } + return mBufferSurface; +} + void SkiaPipeline::setSurfaceColorProperties(ColorMode colorMode) { mColorMode = colorMode; switch (colorMode) { diff --git a/libs/hwui/pipeline/skia/SkiaPipeline.h b/libs/hwui/pipeline/skia/SkiaPipeline.h index f9d37b924321..befee8989383 100644 --- a/libs/hwui/pipeline/skia/SkiaPipeline.h +++ b/libs/hwui/pipeline/skia/SkiaPipeline.h @@ -42,9 +42,18 @@ public: void onDestroyHardwareResources() override; + bool pinImages(std::vector<SkImage*>& mutableImages) override; + bool pinImages(LsaVector<sk_sp<Bitmap>>& images) override { return false; } + void unpinImages() override; + void renderLayers(const LightGeometry& lightGeometry, LayerUpdateQueue* layerUpdateQueue, bool opaque, const LightInfo& lightInfo) override; + // If the given node didn't have a layer surface, or had one of the wrong size, this method + // creates a new one and returns true. Otherwise does nothing and returns false. + bool createOrUpdateLayer(RenderNode* node, const DamageAccumulator& damageAccumulator, + ErrorHandler* errorHandler) override; + void setSurfaceColorProperties(ColorMode colorMode) override; SkColorType getSurfaceColorType() const override { return mSurfaceColorType; } sk_sp<SkColorSpace> getSurfaceColorSpace() override { return mSurfaceColorSpace; } @@ -54,8 +63,9 @@ public: const Rect& contentDrawBounds, sk_sp<SkSurface> surface, const SkMatrix& preTransform); - void renderLayerImpl(RenderNode* layerNode, const Rect& layerDamage); - virtual void renderLayersImpl(const LayerUpdateQueue& layers, bool opaque) = 0; + static void prepareToDraw(const renderthread::RenderThread& thread, Bitmap* bitmap); + + void renderLayersImpl(const LayerUpdateQueue& layers, bool opaque); // Sets the recording callback to the provided function and the recording mode // to CallbackAPI @@ -65,11 +75,19 @@ public: mCaptureMode = callback ? CaptureMode::CallbackAPI : CaptureMode::None; } + virtual void setHardwareBuffer(AHardwareBuffer* buffer) override; + bool hasHardwareBuffer() override { return mHardwareBuffer != nullptr; } + void setTargetSdrHdrRatio(float ratio) override; protected: + sk_sp<SkSurface> getBufferSkSurface( + const renderthread::HardwareBufferRenderParams& bufferParams); + void dumpResourceCacheUsage() const; + renderthread::RenderThread& mRenderThread; + AHardwareBuffer* mHardwareBuffer = nullptr; sk_sp<SkSurface> mBufferSurface = nullptr; sk_sp<SkColorSpace> mBufferColorSpace = nullptr; @@ -107,6 +125,8 @@ private: // Set up a multi frame capture. bool setupMultiFrameCapture(); + std::vector<sk_sp<SkImage>> mPinnedImages; + // Block of properties used only for debugging to record a SkPicture and save it in a file. // There are three possible ways of recording drawing commands. enum class CaptureMode { diff --git a/libs/hwui/pipeline/skia/SkiaVulkanPipeline.cpp b/libs/hwui/pipeline/skia/SkiaVulkanPipeline.cpp index d06dba05ee88..fd0a8e06f39c 100644 --- a/libs/hwui/pipeline/skia/SkiaVulkanPipeline.cpp +++ b/libs/hwui/pipeline/skia/SkiaVulkanPipeline.cpp @@ -14,7 +14,7 @@ * limitations under the License. */ -#include "pipeline/skia/SkiaVulkanPipeline.h" +#include "SkiaVulkanPipeline.h" #include <GrDirectContext.h> #include <GrTypes.h> @@ -28,10 +28,10 @@ #include "DeferredLayerUpdater.h" #include "LightingInfo.h" #include "Readback.h" -#include "pipeline/skia/ShaderCache.h" -#include "pipeline/skia/SkiaGpuPipeline.h" -#include "pipeline/skia/SkiaProfileRenderer.h" -#include "pipeline/skia/VkInteropFunctorDrawable.h" +#include "ShaderCache.h" +#include "SkiaPipeline.h" +#include "SkiaProfileRenderer.h" +#include "VkInteropFunctorDrawable.h" #include "renderstate/RenderState.h" #include "renderthread/Frame.h" #include "renderthread/IRenderPipeline.h" @@ -42,8 +42,7 @@ namespace android { namespace uirenderer { namespace skiapipeline { -SkiaVulkanPipeline::SkiaVulkanPipeline(renderthread::RenderThread& thread) - : SkiaGpuPipeline(thread) { +SkiaVulkanPipeline::SkiaVulkanPipeline(renderthread::RenderThread& thread) : SkiaPipeline(thread) { thread.renderState().registerContextCallback(this); } diff --git a/libs/hwui/platform/android/pipeline/skia/SkiaVulkanPipeline.h b/libs/hwui/pipeline/skia/SkiaVulkanPipeline.h index 0d30df48baee..624eaa51a584 100644 --- a/libs/hwui/platform/android/pipeline/skia/SkiaVulkanPipeline.h +++ b/libs/hwui/pipeline/skia/SkiaVulkanPipeline.h @@ -17,7 +17,7 @@ #pragma once #include "SkRefCnt.h" -#include "pipeline/skia/SkiaGpuPipeline.h" +#include "SkiaPipeline.h" #include "renderstate/RenderState.h" #include "renderthread/HardwareBufferRenderParams.h" #include "renderthread/VulkanManager.h" @@ -30,7 +30,7 @@ namespace android { namespace uirenderer { namespace skiapipeline { -class SkiaVulkanPipeline : public SkiaGpuPipeline, public IGpuContextCallback { +class SkiaVulkanPipeline : public SkiaPipeline, public IGpuContextCallback { public: explicit SkiaVulkanPipeline(renderthread::RenderThread& thread); virtual ~SkiaVulkanPipeline(); diff --git a/libs/hwui/platform/android/pipeline/skia/SkiaGpuPipeline.h b/libs/hwui/platform/android/pipeline/skia/SkiaGpuPipeline.h deleted file mode 100644 index 9159eae46065..000000000000 --- a/libs/hwui/platform/android/pipeline/skia/SkiaGpuPipeline.h +++ /dev/null @@ -1,59 +0,0 @@ -/* - * Copyright (C) 2016 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. - */ - -#pragma once - -#include "pipeline/skia/SkiaPipeline.h" - -namespace android { -namespace uirenderer { -namespace skiapipeline { - -class SkiaGpuPipeline : public SkiaPipeline { -public: - SkiaGpuPipeline(renderthread::RenderThread& thread); - virtual ~SkiaGpuPipeline(); - - virtual GrSurfaceOrigin getSurfaceOrigin() = 0; - - // If the given node didn't have a layer surface, or had one of the wrong size, this method - // creates a new one and returns true. Otherwise does nothing and returns false. - bool createOrUpdateLayer(RenderNode* node, const DamageAccumulator& damageAccumulator, - ErrorHandler* errorHandler) override; - - bool pinImages(std::vector<SkImage*>& mutableImages) override; - bool pinImages(LsaVector<sk_sp<Bitmap>>& images) override { return false; } - void unpinImages() override; - void renderLayersImpl(const LayerUpdateQueue& layers, bool opaque) override; - void setHardwareBuffer(AHardwareBuffer* hardwareBuffer) override; - bool hasHardwareBuffer() override { return mHardwareBuffer != nullptr; } - - static void prepareToDraw(const renderthread::RenderThread& thread, Bitmap* bitmap); - -protected: - sk_sp<SkSurface> getBufferSkSurface( - const renderthread::HardwareBufferRenderParams& bufferParams); - void dumpResourceCacheUsage() const; - - AHardwareBuffer* mHardwareBuffer = nullptr; - -private: - std::vector<sk_sp<SkImage>> mPinnedImages; -}; - -} /* namespace skiapipeline */ -} /* namespace uirenderer */ -} /* namespace android */ diff --git a/libs/hwui/platform/host/android/api-level.h b/libs/hwui/platform/host/android/api-level.h deleted file mode 120000 index 4fb4784f9f60..000000000000 --- a/libs/hwui/platform/host/android/api-level.h +++ /dev/null @@ -1 +0,0 @@ -../../../../../../../bionic/libc/include/android/api-level.h
\ No newline at end of file diff --git a/libs/hwui/platform/host/pipeline/skia/SkiaGpuPipeline.h b/libs/hwui/platform/host/pipeline/skia/SkiaGpuPipeline.h deleted file mode 100644 index a71726585081..000000000000 --- a/libs/hwui/platform/host/pipeline/skia/SkiaGpuPipeline.h +++ /dev/null @@ -1,83 +0,0 @@ -/* - * Copyright (C) 2016 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. - */ - -#pragma once - -#include "pipeline/skia/SkiaPipeline.h" -#include "renderthread/Frame.h" - -namespace android { -namespace uirenderer { -namespace skiapipeline { - -class SkiaGpuPipeline : public SkiaPipeline { -public: - SkiaGpuPipeline(renderthread::RenderThread& thread) : SkiaPipeline(thread) {} - ~SkiaGpuPipeline() {} - - bool pinImages(std::vector<SkImage*>& mutableImages) override { return false; } - bool pinImages(LsaVector<sk_sp<Bitmap>>& images) override { return false; } - void unpinImages() override {} - - // If the given node didn't have a layer surface, or had one of the wrong size, this method - // creates a new one and returns true. Otherwise does nothing and returns false. - bool createOrUpdateLayer(RenderNode* node, const DamageAccumulator& damageAccumulator, - ErrorHandler* errorHandler) override { - return false; - } - void renderLayersImpl(const LayerUpdateQueue& layers, bool opaque) override {} - void setHardwareBuffer(AHardwareBuffer* hardwareBuffer) override {} - bool hasHardwareBuffer() override { return false; } - - renderthread::MakeCurrentResult makeCurrent() override { - return renderthread::MakeCurrentResult::Failed; - } - renderthread::Frame getFrame() override { return renderthread::Frame(0, 0, 0); } - renderthread::IRenderPipeline::DrawResult draw( - const renderthread::Frame& frame, const SkRect& screenDirty, const SkRect& dirty, - const LightGeometry& lightGeometry, LayerUpdateQueue* layerUpdateQueue, - const Rect& contentDrawBounds, bool opaque, const LightInfo& lightInfo, - const std::vector<sp<RenderNode>>& renderNodes, FrameInfoVisualizer* profiler, - const renderthread::HardwareBufferRenderParams& bufferParams, - std::mutex& profilerLock) override { - return {false, IRenderPipeline::DrawResult::kUnknownTime, android::base::unique_fd(-1)}; - } - bool swapBuffers(const renderthread::Frame& frame, IRenderPipeline::DrawResult& drawResult, - const SkRect& screenDirty, FrameInfo* currentFrameInfo, - bool* requireSwap) override { - return false; - } - DeferredLayerUpdater* createTextureLayer() override { return nullptr; } - bool setSurface(ANativeWindow* surface, renderthread::SwapBehavior swapBehavior) override { - return false; - } - [[nodiscard]] android::base::unique_fd flush() override { - return android::base::unique_fd(-1); - }; - void onStop() override {} - bool isSurfaceReady() override { return false; } - bool isContextReady() override { return false; } - - const SkM44& getPixelSnapMatrix() const override { - static const SkM44 sSnapMatrix = SkM44(); - return sSnapMatrix; - } - static void prepareToDraw(const renderthread::RenderThread& thread, Bitmap* bitmap) {} -}; - -} /* namespace skiapipeline */ -} /* namespace uirenderer */ -} /* namespace android */ diff --git a/libs/hwui/platform/host/pipeline/skia/SkiaVulkanPipeline.h b/libs/hwui/platform/host/pipeline/skia/SkiaVulkanPipeline.h deleted file mode 100644 index d54caef45bb5..000000000000 --- a/libs/hwui/platform/host/pipeline/skia/SkiaVulkanPipeline.h +++ /dev/null @@ -1,35 +0,0 @@ -/* - * Copyright (C) 2016 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. - */ - -#pragma once - -#include "pipeline/skia/SkiaGpuPipeline.h" - -namespace android { - -namespace uirenderer { -namespace skiapipeline { - -class SkiaVulkanPipeline : public SkiaGpuPipeline { -public: - SkiaVulkanPipeline(renderthread::RenderThread& thread) : SkiaGpuPipeline(thread) {} - - static void invokeFunctor(const renderthread::RenderThread& thread, Functor* functor) {} -}; - -} /* namespace skiapipeline */ -} /* namespace uirenderer */ -} /* namespace android */ diff --git a/libs/hwui/renderthread/CanvasContext.cpp b/libs/hwui/renderthread/CanvasContext.cpp index 984916cb3986..abf64d099935 100644 --- a/libs/hwui/renderthread/CanvasContext.cpp +++ b/libs/hwui/renderthread/CanvasContext.cpp @@ -35,8 +35,8 @@ #include "Properties.h" #include "RenderThread.h" #include "hwui/Canvas.h" -#include "pipeline/skia/SkiaGpuPipeline.h" #include "pipeline/skia/SkiaOpenGLPipeline.h" +#include "pipeline/skia/SkiaPipeline.h" #include "pipeline/skia/SkiaVulkanPipeline.h" #include "thread/CommonPool.h" #include "utils/GLUtils.h" @@ -108,7 +108,7 @@ void CanvasContext::invokeFunctor(const RenderThread& thread, Functor* functor) } void CanvasContext::prepareToDraw(const RenderThread& thread, Bitmap* bitmap) { - skiapipeline::SkiaGpuPipeline::prepareToDraw(thread, bitmap); + skiapipeline::SkiaPipeline::prepareToDraw(thread, bitmap); } CanvasContext::CanvasContext(RenderThread& thread, bool translucent, RenderNode* rootRenderNode, diff --git a/libs/hwui/renderthread/IRenderPipeline.h b/libs/hwui/renderthread/IRenderPipeline.h index ee1d1f8789d9..b8c3a4de2bd4 100644 --- a/libs/hwui/renderthread/IRenderPipeline.h +++ b/libs/hwui/renderthread/IRenderPipeline.h @@ -30,6 +30,8 @@ #include "SwapBehavior.h" #include "hwui/Bitmap.h" +class GrDirectContext; + struct ANativeWindow; namespace android { @@ -92,6 +94,7 @@ public: virtual void setSurfaceColorProperties(ColorMode colorMode) = 0; virtual SkColorType getSurfaceColorType() const = 0; virtual sk_sp<SkColorSpace> getSurfaceColorSpace() = 0; + virtual GrSurfaceOrigin getSurfaceOrigin() = 0; virtual void setPictureCapturedCallback( const std::function<void(sk_sp<SkPicture>&&)>& callback) = 0; diff --git a/libs/hwui/utils/Color.cpp b/libs/hwui/utils/Color.cpp index 913af8ac3474..f6c57927cc85 100644 --- a/libs/hwui/utils/Color.cpp +++ b/libs/hwui/utils/Color.cpp @@ -16,22 +16,18 @@ #include "Color.h" -#include <ui/ColorSpace.h> -#include <utils/Log.h> - -#ifdef __ANDROID__ // Layoutlib does not support hardware buffers or native windows +#include <Properties.h> #include <android/hardware_buffer.h> #include <android/native_window.h> -#endif +#include <ui/ColorSpace.h> +#include <utils/Log.h> #include <algorithm> #include <cmath> -#include <Properties.h> namespace android { namespace uirenderer { -#ifdef __ANDROID__ // Layoutlib does not support hardware buffers or native windows static inline SkImageInfo createImageInfo(int32_t width, int32_t height, int32_t format, sk_sp<SkColorSpace> colorSpace) { SkColorType colorType = kUnknown_SkColorType; @@ -121,7 +117,6 @@ SkColorType BufferFormatToColorType(uint32_t format) { return kUnknown_SkColorType; } } -#endif namespace { static constexpr skcms_TransferFunction k2Dot6 = {2.6f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f}; diff --git a/libs/hwui/utils/Color.h b/libs/hwui/utils/Color.h index 0fd61c7b990b..08f1c9300c30 100644 --- a/libs/hwui/utils/Color.h +++ b/libs/hwui/utils/Color.h @@ -92,7 +92,6 @@ static constexpr float EOCF_sRGB(float srgb) { return srgb <= 0.04045f ? srgb / 12.92f : powf((srgb + 0.055f) / 1.055f, 2.4f); } -#ifdef __ANDROID__ // Layoutlib does not support hardware buffers or native windows SkImageInfo ANativeWindowToImageInfo(const ANativeWindow_Buffer& buffer, sk_sp<SkColorSpace> colorSpace); @@ -101,7 +100,6 @@ SkImageInfo BufferDescriptionToImageInfo(const AHardwareBuffer_Desc& bufferDesc, uint32_t ColorTypeToBufferFormat(SkColorType colorType); SkColorType BufferFormatToColorType(uint32_t bufferFormat); -#endif ANDROID_API sk_sp<SkColorSpace> DataSpaceToColorSpace(android_dataspace dataspace); diff --git a/libs/input/SpriteController.cpp b/libs/input/SpriteController.cpp index 6a32c5a71999..a63453d655e2 100644 --- a/libs/input/SpriteController.cpp +++ b/libs/input/SpriteController.cpp @@ -148,8 +148,9 @@ void SpriteController::doUpdateSprites() { if (update.state.wantSurfaceVisible()) { int32_t desiredWidth = update.state.icon.width(); int32_t desiredHeight = update.state.icon.height(); - if (update.state.surfaceWidth < desiredWidth - || update.state.surfaceHeight < desiredHeight) { + // TODO(b/331260947): investigate using a larger surface width with smaller sprites. + if (update.state.surfaceWidth != desiredWidth || + update.state.surfaceHeight != desiredHeight) { needApplyTransaction = true; update.state.surfaceControl->updateDefaultBufferSize(desiredWidth, desiredHeight); diff --git a/media/OWNERS b/media/OWNERS index 2e9276d73392..1e5a458a5bff 100644 --- a/media/OWNERS +++ b/media/OWNERS @@ -4,6 +4,7 @@ elaurent@google.com essick@google.com etalvala@google.com hunga@google.com +jchowdhary@google.com jmtrivi@google.com jsharkey@android.com lajos@google.com diff --git a/media/java/android/media/AudioDescriptor.java b/media/java/android/media/AudioDescriptor.java index 85a653c9d80c..b5cae2c5ef10 100644 --- a/media/java/android/media/AudioDescriptor.java +++ b/media/java/android/media/AudioDescriptor.java @@ -99,7 +99,7 @@ public class AudioDescriptor implements Parcelable { * When encapsulation is required, only playback with {@link android.media.AudioTrack} API is * supported. But playback with {@link android.media.MediaPlayer} is not. * When an encapsulation type is required, the {@link AudioFormat} encoding selected when - * creating the {@link AudioTrack} must match the encapsulation type, e.g + * creating the {@link AudioTrack} must match the encapsulation type, e.g. * AudioFormat#ENCODING_IEC61937 for AudioProfile.AUDIO_ENCAPSULATION_TYPE_IEC61937. * * @return an integer representing the encapsulation type diff --git a/media/java/android/media/AudioManager.java b/media/java/android/media/AudioManager.java index 1fe3c2ecec29..63b45387f040 100644 --- a/media/java/android/media/AudioManager.java +++ b/media/java/android/media/AudioManager.java @@ -3465,7 +3465,7 @@ public class AudioManager { /* modes for setMode/getMode/setRoute/getRoute */ /** - * Audio harware modes. + * Audio hardware modes. */ /** * Invalid audio mode. diff --git a/nfc/api/current.txt b/nfc/api/current.txt index 80b2be2567a7..6d4cc3a9ca44 100644 --- a/nfc/api/current.txt +++ b/nfc/api/current.txt @@ -194,13 +194,13 @@ package android.nfc { package android.nfc.cardemulation { public final class CardEmulation { - method @Deprecated public boolean categoryAllowsForegroundPreference(String); + method public boolean categoryAllowsForegroundPreference(String); method @Nullable @RequiresPermission(android.Manifest.permission.NFC_PREFERRED_PAYMENT_INFO) public java.util.List<java.lang.String> getAidsForPreferredPaymentService(); method public java.util.List<java.lang.String> getAidsForService(android.content.ComponentName, String); - method @Deprecated @Nullable @RequiresPermission(android.Manifest.permission.NFC_PREFERRED_PAYMENT_INFO) public CharSequence getDescriptionForPreferredPaymentService(); + method @Nullable @RequiresPermission(android.Manifest.permission.NFC_PREFERRED_PAYMENT_INFO) public CharSequence getDescriptionForPreferredPaymentService(); method public static android.nfc.cardemulation.CardEmulation getInstance(android.nfc.NfcAdapter); - method @Deprecated @Nullable @RequiresPermission(android.Manifest.permission.NFC_PREFERRED_PAYMENT_INFO) public String getRouteDestinationForPreferredPaymentService(); - method @Deprecated public int getSelectionModeForCategory(String); + method @Nullable @RequiresPermission(android.Manifest.permission.NFC_PREFERRED_PAYMENT_INFO) public String getRouteDestinationForPreferredPaymentService(); + method public int getSelectionModeForCategory(String); method public boolean isDefaultServiceForAid(android.content.ComponentName, String); method public boolean isDefaultServiceForCategory(android.content.ComponentName, String); method public boolean registerAidsForService(android.content.ComponentName, String, java.util.List<java.lang.String>); diff --git a/nfc/java/android/nfc/cardemulation/CardEmulation.java b/nfc/java/android/nfc/cardemulation/CardEmulation.java index 67697a429a32..de9eada18104 100644 --- a/nfc/java/android/nfc/cardemulation/CardEmulation.java +++ b/nfc/java/android/nfc/cardemulation/CardEmulation.java @@ -36,6 +36,7 @@ import android.nfc.Constants; import android.nfc.Flags; import android.nfc.INfcCardEmulation; import android.nfc.NfcAdapter; +import android.os.Build; import android.os.RemoteException; import android.os.UserHandle; import android.provider.Settings; @@ -271,30 +272,31 @@ public final class CardEmulation { } /** + * <p> * Returns whether the user has allowed AIDs registered in the * specified category to be handled by a service that is preferred * by the foreground application, instead of by a pre-configured default. * * Foreground applications can set such preferences using the * {@link #setPreferredService(Activity, ComponentName)} method. + * <p class="note"> + * Starting with {@link Build.VERSION_CODES#VANILLA_ICE_CREAM}, this method will always + * return true. * * @param category The category, e.g. {@link #CATEGORY_PAYMENT} * @return whether AIDs in the category can be handled by a service * specified by the foreground app. - * - * @deprecated see {@link android.app.role.RoleManager#ROLE_WALLET}. The definition of the - * Preferred Payment service is no longer valid. All routings will be done in a AID - * category agnostic manner. */ @SuppressWarnings("NonUserGetterCalled") - @Deprecated public boolean categoryAllowsForegroundPreference(String category) { Context contextAsUser = mContext.createContextAsUser( UserHandle.of(UserHandle.myUserId()), 0); + RoleManager roleManager = contextAsUser.getSystemService(RoleManager.class); if (roleManager.isRoleAvailable(RoleManager.ROLE_WALLET)) { return true; } + if (CATEGORY_PAYMENT.equals(category)) { boolean preferForeground = false; try { @@ -319,14 +321,14 @@ public final class CardEmulation { * every time what service they would like to use in this category. * <p>{@link #SELECTION_MODE_ASK_IF_CONFLICT} the user will only be asked * to pick a service if there is a conflict. + * + * <p class="note"> + * Starting with {@link Build.VERSION_CODES#VANILLA_ICE_CREAM}, the default service defined + * by the holder of {@link android.app.role.RoleManager#ROLE_WALLET} and is category agnostic. + * * @param category The category, for example {@link #CATEGORY_PAYMENT} * @return the selection mode for the passed in category - * - * @deprecated see {@link android.app.role.RoleManager#ROLE_WALLET}. The definition of the - * Preferred Payment service is no longer valid. All routings will be done in a AID - * category agnostic manner. */ - @Deprecated public int getSelectionModeForCategory(String category) { if (CATEGORY_PAYMENT.equals(category)) { boolean paymentRegistered = false; @@ -919,6 +921,13 @@ public final class CardEmulation { /** * Retrieves the route destination for the preferred payment service. * + * <p class="note"> + * Starting with {@link Build.VERSION_CODES#VANILLA_ICE_CREAM}, the preferred payment service + * no longer exists and is replaced by {@link android.app.role.RoleManager#ROLE_WALLET}. This + * will return the route for one of the services registered by the role holder (if any). If + * there are multiple services registered, it is unspecified which of those will be used to + * determine the route. + * * @return The route destination secure element name of the preferred payment service. * HCE payment: "Host" * OffHost payment: 1. String with prefix SIM or prefix eSE string. @@ -931,15 +940,8 @@ public final class CardEmulation { * (e.g. eSE/eSE1, eSE2, etc.). * 2. "OffHost" if the payment service does not specify secure element * name. - * - * @deprecated see {@link android.app.role.RoleManager#ROLE_WALLET}. The definition of the - * Preferred Payment service is no longer valid. All routings will go to the Wallet Holder app. - * A payment service will be selected automatically based on registered AIDs. In the case of - * multiple services that register for the same payment AID, the selection will be done on - * an alphabetical order based on the component names. */ @RequiresPermission(android.Manifest.permission.NFC_PREFERRED_PAYMENT_INFO) - @Deprecated @Nullable public String getRouteDestinationForPreferredPaymentService() { try { @@ -981,16 +983,16 @@ public final class CardEmulation { /** * Returns a user-visible description of the preferred payment service. * - * @return the preferred payment service description + * <p class="note"> + * Starting with {@link Build.VERSION_CODES#VANILLA_ICE_CREAM}, the preferred payment service + * no longer exists and is replaced by {@link android.app.role.RoleManager#ROLE_WALLET}. This + * will return the description for one of the services registered by the role holder (if any). + * If there are multiple services registered, it is unspecified which of those will be used + * to obtain the service description here. * - * @deprecated see {@link android.app.role.RoleManager#ROLE_WALLET}. The definition of the - * Preferred Payment service is no longer valid. All routings will go to the Wallet Holder app. - * A payment service will be selected automatically based on registered AIDs. In the case of - * multiple services that register for the same payment AID, the selection will be done on - * an alphabetical order based on the component names. + * @return the preferred payment service description */ @RequiresPermission(android.Manifest.permission.NFC_PREFERRED_PAYMENT_INFO) - @Deprecated @Nullable public CharSequence getDescriptionForPreferredPaymentService() { try { diff --git a/packages/CredentialManager/AndroidManifest.xml b/packages/CredentialManager/AndroidManifest.xml index 7a8c25bd12ab..1ac8e19c2bc6 100644 --- a/packages/CredentialManager/AndroidManifest.xml +++ b/packages/CredentialManager/AndroidManifest.xml @@ -21,6 +21,7 @@ <uses-permission android:name="android.permission.LAUNCH_CREDENTIAL_SELECTOR"/> <uses-permission android:name="android.permission.QUERY_ALL_PACKAGES"/> + <uses-permission android:name="android.permission.SET_BIOMETRIC_DIALOG_ADVANCED"/> <uses-permission android:name="android.permission.HIDE_NON_SYSTEM_OVERLAY_WINDOWS"/> <uses-permission android:name="android.permission.ACCESS_INSTANT_APPS" /> <uses-permission android:name="android.permission.USE_BIOMETRIC" /> diff --git a/packages/CredentialManager/src/com/android/credentialmanager/common/BiometricHandler.kt b/packages/CredentialManager/src/com/android/credentialmanager/common/BiometricHandler.kt index db5ab569535f..d21077ee7c5a 100644 --- a/packages/CredentialManager/src/com/android/credentialmanager/common/BiometricHandler.kt +++ b/packages/CredentialManager/src/com/android/credentialmanager/common/BiometricHandler.kt @@ -175,7 +175,8 @@ private fun setupBiometricPrompt( } .setAllowedAuthenticators(finalAuthenticators) .setConfirmationRequired(true) - // TODO(b/326243754) : Add logo back once new permission privileges sorted out + .setLogoBitmap(biometricDisplayInfo.providerIcon) + .setLogoDescription(biometricDisplayInfo.providerName) .setDescription(biometricDisplayInfo.descriptionAboveBiometricButton) .build() diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/dialog/SettingsAlertDialogWithIcon.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/dialog/SettingsAlertDialogWithIcon.kt index 1695e4f33915..030522d73b26 100644 --- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/dialog/SettingsAlertDialogWithIcon.kt +++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/dialog/SettingsAlertDialogWithIcon.kt @@ -39,11 +39,17 @@ fun SettingsAlertDialogWithIcon( confirmButton: AlertDialogButton?, dismissButton: AlertDialogButton?, title: String?, + icon: @Composable (() -> Unit)? = { + Icon( + Icons.Default.WarningAmber, + contentDescription = null + ) + }, text: @Composable (() -> Unit)?, ) { AlertDialog( onDismissRequest = onDismissRequest, - icon = { Icon(Icons.Default.WarningAmber, contentDescription = null) }, + icon = icon, modifier = Modifier.width(getDialogWidth()), confirmButton = { confirmButton?.let { diff --git a/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java b/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java index 87a7f823edfe..38a3a2aad609 100644 --- a/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java +++ b/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java @@ -270,6 +270,7 @@ public class SecureSettings { Settings.Secure.VISUAL_QUERY_ACCESSIBILITY_DETECTION_ENABLED, Settings.Secure.IMMERSIVE_MODE_CONFIRMATIONS, Settings.Secure.AUDIO_DEVICE_INVENTORY, + Settings.Secure.SCREEN_RESOLUTION_MODE, Settings.Secure.ACCESSIBILITY_FLOATING_MENU_TARGETS }; } diff --git a/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java b/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java index edef286b6ac0..252cb8fa8f8b 100644 --- a/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java +++ b/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java @@ -427,5 +427,7 @@ public class SecureSettingsValidators { VALIDATORS.put(Secure.CAMERA_EXTENSIONS_FALLBACK, BOOLEAN_VALIDATOR); VALIDATORS.put(Secure.IMMERSIVE_MODE_CONFIRMATIONS, ANY_STRING_VALIDATOR); VALIDATORS.put(Secure.AUDIO_DEVICE_INVENTORY, ANY_STRING_VALIDATOR); + VALIDATORS.put(Secure.SCREEN_RESOLUTION_MODE, new InclusiveIntegerRangeValidator( + Secure.RESOLUTION_MODE_UNKNOWN, Secure.RESOLUTION_MODE_FULL)); } } diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsHelper.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsHelper.java index 1eb04ac1c181..9e9350b1a17a 100644 --- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsHelper.java +++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsHelper.java @@ -99,6 +99,7 @@ public class SettingsHelper { sBroadcastOnRestore.add(Settings.Secure.ACCESSIBILITY_DISPLAY_MAGNIFICATION_NAVBAR_ENABLED); sBroadcastOnRestore.add(Settings.Secure.ACCESSIBILITY_BUTTON_TARGETS); sBroadcastOnRestore.add(Settings.Secure.ACCESSIBILITY_QS_TARGETS); + sBroadcastOnRestore.add(Settings.Secure.SCREEN_RESOLUTION_MODE); sBroadcastOnRestoreSystemUI = new ArraySet<String>(2); sBroadcastOnRestoreSystemUI.add(Settings.Secure.QS_TILES); sBroadcastOnRestoreSystemUI.add(Settings.Secure.QS_AUTO_ADDED_TILES); diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java index 4603b43b0ab5..46cee6b1cc0a 100644 --- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java +++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java @@ -2112,6 +2112,12 @@ class SettingsProtoDumpUtil { Settings.Secure.DOUBLE_TAP_TO_WAKE, SecureSettingsProto.DOUBLE_TAP_TO_WAKE); + final long displayToken = p.start(SecureSettingsProto.DISPLAY); + dumpSetting(s, p, + Settings.Secure.SCREEN_RESOLUTION_MODE, + SecureSettingsProto.Display.SCREEN_RESOLUTION_MODE); + p.end(displayToken); + final long dozeToken = p.start(SecureSettingsProto.DOZE); dumpSetting(s, p, Settings.Secure.DOZE_ENABLED, diff --git a/packages/SystemUI/Android.bp b/packages/SystemUI/Android.bp index 617df74b866d..f911269dd06a 100644 --- a/packages/SystemUI/Android.bp +++ b/packages/SystemUI/Android.bp @@ -330,6 +330,7 @@ android_library { "androidx.test.uiautomator_uiautomator", "androidx.core_core-animation-testing", "mockito-target-extended-minus-junit4", + "mockito-kotlin2", "androidx.test.ext.junit", "androidx.test.ext.truth", "kotlin-test", diff --git a/packages/SystemUI/aconfig/systemui.aconfig b/packages/SystemUI/aconfig/systemui.aconfig index 91717dc92c4a..c845ab3a7fb1 100644 --- a/packages/SystemUI/aconfig/systemui.aconfig +++ b/packages/SystemUI/aconfig/systemui.aconfig @@ -511,13 +511,6 @@ flag { } flag { - name: "enable_contextual_tips_frequency_cap" - description: "Enables frequency capping for contextual tips, e.g. 1x/day, 2x/week, 3x/lifetime." - namespace: "systemui" - bug: "322891421" -} - -flag { name: "disable_contextual_tips_frequency_check" description: "Disables frequency capping check for contextual tips." namespace: "systemui" @@ -532,13 +525,6 @@ flag { } flag { - name: "disable_contextual_tips_first_30d_check" - description: "Disables condition check which only show tips within 30 days after phone setup." - namespace: "systemui" - bug: "322891421" -} - -flag { name: "enable_contextual_tips" description: "Enables showing contextual tips." namespace: "systemui" diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettings.kt b/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettings.kt index 244861c277c6..a87a8df290b7 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettings.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettings.kt @@ -146,7 +146,7 @@ fun SceneScope.QuickSettings( modifier.fillMaxWidth().layout { measurable, constraints -> val placeable = measurable.measure(constraints) // Use the height of the correct view based on the scene it is being composed in - val height = heightProvider() + val height = heightProvider().coerceAtLeast(0) layout(placeable.width, height) { placeable.placeRelative(0, 0) } } diff --git a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/AnimatableClockView.kt b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/AnimatableClockView.kt index 11c946261816..25c649a7d4fb 100644 --- a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/AnimatableClockView.kt +++ b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/AnimatableClockView.kt @@ -555,7 +555,7 @@ class AnimatableClockView @JvmOverloads constructor( if (distance > 0) { // If distance > 0 then we are moving from the left towards the center. // We need ensure that the glyphs are offset to the initial position. - glyphOffsets -= dir * distance + glyphOffsets[i] -= dir * distance } } invalidate() diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/homecontrols/HomeControlsComponentInteractorKosmos.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/homecontrols/HomeControlsComponentInteractorKosmos.kt index 2c890f4e6c42..7d7841f0da5b 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/homecontrols/HomeControlsComponentInteractorKosmos.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/homecontrols/HomeControlsComponentInteractorKosmos.kt @@ -15,6 +15,7 @@ */ package com.android.systemui.dreams.homecontrols +import android.os.powerManager import android.service.dream.dreamManager import com.android.systemui.common.domain.interactor.packageChangeInteractor import com.android.systemui.controls.dagger.ControlsComponent @@ -37,6 +38,7 @@ val Kosmos.homeControlsComponentInteractor by userRepository = fakeUserRepository, bgScope = applicationCoroutineScope, systemClock = fakeSystemClock, + powerManager = powerManager, dreamManager = dreamManager, packageChangeInteractor = packageChangeInteractor, ) diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/homecontrols/HomeControlsComponentInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/homecontrols/HomeControlsComponentInteractorTest.kt index 298ce7029f0a..feb72989980c 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/homecontrols/HomeControlsComponentInteractorTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/homecontrols/HomeControlsComponentInteractorTest.kt @@ -20,7 +20,9 @@ import android.content.Context import android.content.pm.ApplicationInfo import android.content.pm.ServiceInfo import android.content.pm.UserInfo +import android.os.PowerManager import android.os.UserHandle +import android.os.powerManager import android.service.dream.dreamManager import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest @@ -50,6 +52,8 @@ import kotlinx.coroutines.test.runTest import org.junit.Before import org.junit.Test import org.junit.runner.RunWith +import org.mockito.ArgumentMatchers.anyInt +import org.mockito.Mockito.anyLong import org.mockito.Mockito.never import org.mockito.Mockito.verify @@ -196,7 +200,7 @@ class HomeControlsComponentInteractorTest : SysuiTestCase() { ) fakeSystemClock.advanceTime(MAX_UPDATE_CORRELATION_DELAY.inWholeMilliseconds) // Task fragment becomes empty as a result of the update. - underTest.onTaskFragmentEmpty() + underTest.onDreamEndUnexpectedly() runCurrent() verify(dreamManager, never()).startDream() @@ -240,7 +244,7 @@ class HomeControlsComponentInteractorTest : SysuiTestCase() { ) fakeSystemClock.advanceTime(MAX_UPDATE_CORRELATION_DELAY.inWholeMilliseconds + 100) // Task fragment becomes empty as a result of the update. - underTest.onTaskFragmentEmpty() + underTest.onDreamEndUnexpectedly() runCurrent() verify(dreamManager, never()).startDream() @@ -258,6 +262,25 @@ class HomeControlsComponentInteractorTest : SysuiTestCase() { } } + @Test + fun testDreamUnexpectedlyEnds_triggersUserActivity() = + with(kosmos) { + testScope.runTest { + fakeSystemClock.setUptimeMillis(100000L) + verify(powerManager, never()).userActivity(anyLong(), anyInt(), anyInt()) + + // Dream ends unexpectedly + underTest.onDreamEndUnexpectedly() + + verify(powerManager) + .userActivity( + 100000L, + PowerManager.USER_ACTIVITY_EVENT_OTHER, + PowerManager.USER_ACTIVITY_FLAG_NO_CHANGE_LIGHTS + ) + } + } + private fun runServicesUpdate(hasPanelBoolean: Boolean = true) { val listings = listOf(ControlsServiceInfo(TEST_COMPONENT, "panel", hasPanel = hasPanelBoolean)) diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractorTest.kt index a9541d962639..eec74efd4751 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractorTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractorTest.kt @@ -40,6 +40,7 @@ import com.android.systemui.scene.domain.interactor.sceneInteractor import com.android.systemui.scene.shared.flag.fakeSceneContainerFlags import com.android.systemui.scene.shared.model.Scenes import com.android.systemui.shade.data.repository.FakeShadeRepository +import com.android.systemui.statusbar.notification.stack.domain.interactor.sharedNotificationContainerInteractor import com.android.systemui.testKosmos import com.google.common.truth.Truth.assertThat import kotlinx.coroutines.ExperimentalCoroutinesApi @@ -81,6 +82,10 @@ class KeyguardInteractorTest : SysuiTestCase() { keyguardTransitionInteractor = kosmos.keyguardTransitionInteractor, sceneInteractorProvider = { sceneInteractor }, fromGoneTransitionInteractor = { fromGoneTransitionInteractor }, + sharedNotificationContainerInteractor = { + kosmos.sharedNotificationContainerInteractor + }, + applicationScope = testScope, ) } diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationsPlaceholderViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationsPlaceholderViewModelTest.kt index 2ccc8b44eff8..9e5f7c9ba648 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationsPlaceholderViewModelTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationsPlaceholderViewModelTest.kt @@ -16,15 +16,20 @@ package com.android.systemui.statusbar.notification.stack.ui.viewmodel +import android.platform.test.annotations.DisableFlags import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest +import com.android.systemui.Flags import com.android.systemui.SysuiTestCase import com.android.systemui.common.shared.model.NotificationContainerBounds +import com.android.systemui.coroutines.collectLastValue import com.android.systemui.keyguard.domain.interactor.keyguardInteractor +import com.android.systemui.kosmos.testScope import com.android.systemui.statusbar.notification.stack.domain.interactor.notificationStackAppearanceInteractor import com.android.systemui.statusbar.notification.stack.shared.model.StackBounds import com.android.systemui.testKosmos import com.google.common.truth.Truth.assertThat +import kotlinx.coroutines.test.runTest import org.junit.Test import org.junit.runner.RunWith @@ -33,14 +38,22 @@ import org.junit.runner.RunWith class NotificationsPlaceholderViewModelTest : SysuiTestCase() { private val kosmos = testKosmos() private val underTest = kosmos.notificationsPlaceholderViewModel + @Test - fun onBoundsChanged_setsNotificationContainerBounds() { - underTest.onBoundsChanged(left = 5f, top = 5f, right = 5f, bottom = 5f) - assertThat(kosmos.keyguardInteractor.notificationContainerBounds.value) - .isEqualTo(NotificationContainerBounds(top = 5f, bottom = 5f)) - assertThat(kosmos.notificationStackAppearanceInteractor.stackBounds.value) - .isEqualTo(StackBounds(left = 5f, top = 5f, right = 5f, bottom = 5f)) - } + @DisableFlags(Flags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT) + fun onBoundsChanged_setsNotificationContainerBounds() = + kosmos.testScope.runTest { + underTest.onBoundsChanged(left = 5f, top = 5f, right = 5f, bottom = 5f) + val containerBounds by + collectLastValue(kosmos.keyguardInteractor.notificationContainerBounds) + val stackBounds by + collectLastValue(kosmos.notificationStackAppearanceInteractor.stackBounds) + assertThat(containerBounds) + .isEqualTo(NotificationContainerBounds(top = 5f, bottom = 5f)) + assertThat(stackBounds) + .isEqualTo(StackBounds(left = 5f, top = 5f, right = 5f, bottom = 5f)) + } + @Test fun onContentTopChanged_setsContentTop() { underTest.onContentTopChanged(padding = 5f) diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModelTest.kt index f969ee677763..cc7ebe96ba11 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModelTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModelTest.kt @@ -498,7 +498,7 @@ class SharedNotificationContainerViewModelTest : SysuiTestCase() { showLockscreen() keyguardInteractor.setNotificationContainerBounds( - NotificationContainerBounds(top = 1f, bottom = 2f) + NotificationContainerBounds(top = 1f, bottom = 52f) ) runCurrent() @@ -526,7 +526,7 @@ class SharedNotificationContainerViewModelTest : SysuiTestCase() { showLockscreen() keyguardInteractor.setNotificationContainerBounds( - NotificationContainerBounds(top = 1f, bottom = 2f) + NotificationContainerBounds(top = 1f, bottom = 52f) ) runCurrent() diff --git a/packages/SystemUI/res/layout/dream_overlay_complication_clock_time.xml b/packages/SystemUI/res/layout/dream_overlay_complication_clock_time.xml index 6de10b405aba..6538725e2374 100644 --- a/packages/SystemUI/res/layout/dream_overlay_complication_clock_time.xml +++ b/packages/SystemUI/res/layout/dream_overlay_complication_clock_time.xml @@ -37,4 +37,6 @@ app:ambientShadowOffsetY="@dimen/dream_overlay_clock_ambient_text_shadow_dy" app:ambientShadowAlpha="0.3" app:removeTextDescent="true" - app:textDescentExtraPadding="@dimen/dream_overlay_clock_text_descent_extra_padding" /> + app:textDescentExtraPadding="@dimen/dream_overlay_clock_text_descent_extra_padding" + android:clickable="false" +/> diff --git a/packages/SystemUI/src/com/android/systemui/complication/DreamClockTimeComplication.java b/packages/SystemUI/src/com/android/systemui/complication/DreamClockTimeComplication.java index dc32a59d86a9..c709e3436cd6 100644 --- a/packages/SystemUI/src/com/android/systemui/complication/DreamClockTimeComplication.java +++ b/packages/SystemUI/src/com/android/systemui/complication/DreamClockTimeComplication.java @@ -130,15 +130,9 @@ public class DreamClockTimeComplication implements Complication { } @Override - protected void onViewAttached() { - mView.setOnClickListener(this::onClick); - } + protected void onViewAttached() {} @Override protected void onViewDetached() {} - - private void onClick(View v) { - mUiEventLogger.log(DreamOverlayUiEvent.DREAM_CLOCK_TAPPED); - } } } diff --git a/packages/SystemUI/src/com/android/systemui/complication/DreamOverlayUiEvent.kt b/packages/SystemUI/src/com/android/systemui/complication/DreamOverlayUiEvent.kt index 17cc8299c309..4d41262a8073 100644 --- a/packages/SystemUI/src/com/android/systemui/complication/DreamOverlayUiEvent.kt +++ b/packages/SystemUI/src/com/android/systemui/complication/DreamOverlayUiEvent.kt @@ -23,7 +23,6 @@ import com.android.internal.logging.UiEventLogger.UiEventEnum enum class DreamOverlayUiEvent(private val mId: Int) : UiEventEnum { @UiEvent(doc = "The home controls on the screensaver has been tapped.") DREAM_HOME_CONTROLS_TAPPED(1212), - @UiEvent(doc = "The clock on the screensaver has been tapped") DREAM_CLOCK_TAPPED(1440), @UiEvent(doc = "The weather on the screensaver has been tapped") DREAM_WEATHER_TAPPED(1441); override fun getId(): Int { diff --git a/packages/SystemUI/src/com/android/systemui/dreams/homecontrols/HomeControlsDreamService.kt b/packages/SystemUI/src/com/android/systemui/dreams/homecontrols/HomeControlsDreamService.kt index 376d3129d8c3..ee8e2059e177 100644 --- a/packages/SystemUI/src/com/android/systemui/dreams/homecontrols/HomeControlsDreamService.kt +++ b/packages/SystemUI/src/com/android/systemui/dreams/homecontrols/HomeControlsDreamService.kt @@ -81,7 +81,7 @@ constructor( activity = activity, onCreateCallback = this::onTaskFragmentCreated, onInfoChangedCallback = this::onTaskFragmentInfoChanged, - hide = { finish() } + hide = { endDream() } ) .apply { createTaskFragment() } @@ -91,11 +91,15 @@ constructor( private fun onTaskFragmentInfoChanged(taskFragmentInfo: TaskFragmentInfo) { if (taskFragmentInfo.isEmpty) { logger.d("Finishing dream due to TaskFragment being empty") - finish() - homeControlsComponentInteractor.onTaskFragmentEmpty() + endDream() } } + private fun endDream() { + homeControlsComponentInteractor.onDreamEndUnexpectedly() + wakeUp() + } + private fun onTaskFragmentCreated(taskFragmentInfo: TaskFragmentInfo) { val setting = controlsSettingsRepository.allowActionOnTrivialControlsInLockscreen.value val componentName = homeControlsComponentInteractor.panelComponent.value diff --git a/packages/SystemUI/src/com/android/systemui/dreams/homecontrols/domain/interactor/HomeControlsComponentInteractor.kt b/packages/SystemUI/src/com/android/systemui/dreams/homecontrols/domain/interactor/HomeControlsComponentInteractor.kt index f0067dcb7fe2..74452d1980be 100644 --- a/packages/SystemUI/src/com/android/systemui/dreams/homecontrols/domain/interactor/HomeControlsComponentInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/dreams/homecontrols/domain/interactor/HomeControlsComponentInteractor.kt @@ -19,6 +19,7 @@ package com.android.systemui.dreams.homecontrols.domain.interactor import android.annotation.SuppressLint import android.app.DreamManager import android.content.ComponentName +import android.os.PowerManager import android.os.UserHandle import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow import com.android.systemui.common.domain.interactor.PackageChangeInteractor @@ -66,6 +67,7 @@ constructor( userRepository: UserRepository, private val packageChangeInteractor: PackageChangeInteractor, private val systemClock: SystemClock, + private val powerManager: PowerManager, private val dreamManager: DreamManager, @Background private val bgScope: CoroutineScope ) { @@ -135,7 +137,12 @@ constructor( private val taskFragmentFinished = MutableSharedFlow<Long>(replay = 1, onBufferOverflow = BufferOverflow.DROP_OLDEST) - fun onTaskFragmentEmpty() { + fun onDreamEndUnexpectedly() { + powerManager.userActivity( + systemClock.uptimeMillis(), + PowerManager.USER_ACTIVITY_EVENT_OTHER, + PowerManager.USER_ACTIVITY_FLAG_NO_CHANGE_LIGHTS, + ) taskFragmentFinished.tryEmit(systemClock.currentTimeMillis()) } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt index 283f1601846b..851eafa1a4df 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt @@ -29,6 +29,8 @@ import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCall import com.android.systemui.common.shared.model.NotificationContainerBounds import com.android.systemui.common.ui.domain.interactor.ConfigurationInteractor import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.dagger.qualifiers.Application +import com.android.systemui.keyguard.MigrateClocksToBlueprint import com.android.systemui.keyguard.data.repository.KeyguardRepository import com.android.systemui.keyguard.shared.model.BiometricUnlockModel import com.android.systemui.keyguard.shared.model.BiometricUnlockSource @@ -46,14 +48,17 @@ import com.android.systemui.scene.shared.flag.SceneContainerFlags import com.android.systemui.scene.shared.model.Scenes import com.android.systemui.shade.data.repository.ShadeRepository import com.android.systemui.statusbar.CommandQueue +import com.android.systemui.statusbar.notification.stack.domain.interactor.SharedNotificationContainerInteractor import com.android.systemui.util.kotlin.sample import javax.inject.Inject import javax.inject.Provider +import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.channels.awaitClose import kotlinx.coroutines.delay import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.flow.combine @@ -66,6 +71,7 @@ import kotlinx.coroutines.flow.flowOf import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.merge import kotlinx.coroutines.flow.onStart +import kotlinx.coroutines.flow.stateIn /** * Encapsulates business-logic related to the keyguard but not to a more specific part within it. @@ -84,16 +90,33 @@ constructor( keyguardTransitionInteractor: KeyguardTransitionInteractor, sceneInteractorProvider: Provider<SceneInteractor>, private val fromGoneTransitionInteractor: Provider<FromGoneTransitionInteractor>, + sharedNotificationContainerInteractor: Provider<SharedNotificationContainerInteractor>, + @Application applicationScope: CoroutineScope, ) { // TODO(b/296118689): move to a repository - private val _sharedNotificationContainerBounds = MutableStateFlow(NotificationContainerBounds()) + private val _notificationPlaceholderBounds = MutableStateFlow(NotificationContainerBounds()) /** Bounds of the notification container. */ - val notificationContainerBounds: StateFlow<NotificationContainerBounds> = - _sharedNotificationContainerBounds.asStateFlow() + val notificationContainerBounds: StateFlow<NotificationContainerBounds> by lazy { + combine( + _notificationPlaceholderBounds, + sharedNotificationContainerInteractor.get().configurationBasedDimensions, + ) { bounds, cfg -> + // We offset the placeholder bounds by the configured top margin to account for + // legacy placement behavior within notifications for splitshade. + if (MigrateClocksToBlueprint.isEnabled && cfg.useSplitShade) { + bounds.copy(bottom = bounds.bottom - cfg.keyguardSplitShadeTopMargin) + } else bounds + } + .stateIn( + scope = applicationScope, + started = SharingStarted.WhileSubscribed(), + initialValue = NotificationContainerBounds(), + ) + } fun setNotificationContainerBounds(position: NotificationContainerBounds) { - _sharedNotificationContainerBounds.value = position + _notificationPlaceholderBounds.value = position } /** diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/NotificationStackScrollLayoutSection.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/NotificationStackScrollLayoutSection.kt index 2b601cdc012f..edcf97a81ea4 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/NotificationStackScrollLayoutSection.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/NotificationStackScrollLayoutSection.kt @@ -25,7 +25,6 @@ import androidx.constraintlayout.widget.ConstraintLayout import androidx.constraintlayout.widget.ConstraintSet import androidx.constraintlayout.widget.ConstraintSet.BOTTOM import androidx.constraintlayout.widget.ConstraintSet.TOP -import com.android.systemui.deviceentry.shared.DeviceEntryUdfpsRefactor import com.android.systemui.keyguard.MigrateClocksToBlueprint import com.android.systemui.keyguard.shared.model.KeyguardSection import com.android.systemui.res.R @@ -51,21 +50,18 @@ constructor( * indication area, whichever is higher. */ protected fun addNotificationPlaceholderBarrier(constraintSet: ConstraintSet) { - val lockId = - if (DeviceEntryUdfpsRefactor.isEnabled) { - R.id.device_entry_icon_view - } else { - R.id.lock_icon_view - } - constraintSet.apply { createBarrier( R.id.nssl_placeholder_barrier_bottom, Barrier.TOP, 0, - *intArrayOf(lockId, R.id.ambient_indication_container) + *intArrayOf( + R.id.device_entry_icon_view, + R.id.lock_icon_view, + R.id.ambient_indication_container + ) ) - connect(R.id.nssl_placeholder, BOTTOM, R.id.nssl_placeholder_barrier_bottom, TOP) + connect(placeHolderId, BOTTOM, R.id.nssl_placeholder_barrier_bottom, TOP) } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationEntry.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationEntry.java index 8cdf60b20786..c1dd992b64cd 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationEntry.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationEntry.java @@ -171,6 +171,9 @@ public final class NotificationEntry extends ListEntry { private boolean mIsMarkedForUserTriggeredMovement; private boolean mIsHeadsUpEntry; + private boolean mHasEverBeenGroupSummary; + private boolean mHasEverBeenGroupChild; + public boolean mRemoteEditImeAnimatingAway; public boolean mRemoteEditImeVisible; private boolean mExpandAnimationRunning; @@ -217,6 +220,26 @@ public final class NotificationEntry extends ListEntry { mIsDemoted = true; } + /** called when entry is currently a summary of a group */ + public void markAsGroupSummary() { + mHasEverBeenGroupSummary = true; + } + + /** whether this entry has ever been marked as a summary */ + public boolean hasEverBeenGroupSummary() { + return mHasEverBeenGroupSummary; + } + + /** called when entry is currently a child in a group */ + public void markAsGroupChild() { + mHasEverBeenGroupChild = true; + } + + /** whether this entry has ever been marked as a child */ + public boolean hasEverBeenGroupChild() { + return mHasEverBeenGroupChild; + } + /** * @param sbn the StatusBarNotification from system server * @param ranking also from system server diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/PreparationCoordinator.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/PreparationCoordinator.java index 7a7b18450b48..9b075a650b48 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/PreparationCoordinator.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/PreparationCoordinator.java @@ -49,6 +49,8 @@ import com.android.systemui.statusbar.notification.collection.render.NotifViewBa import com.android.systemui.statusbar.notification.collection.render.NotifViewController; import com.android.systemui.statusbar.notification.row.NotifInflationErrorManager; import com.android.systemui.statusbar.notification.row.NotifInflationErrorManager.NotifInflationErrorListener; +import com.android.systemui.statusbar.notification.row.shared.AsyncGroupHeaderViewInflation; +import com.android.systemui.statusbar.notification.row.shared.AsyncHybridViewInflation; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; @@ -273,10 +275,14 @@ public class PreparationCoordinator implements Coordinator { private void inflateRequiredGroupViews(GroupEntry groupEntry) { NotificationEntry summary = groupEntry.getSummary(); + if (summary != null && AsyncGroupHeaderViewInflation.isEnabled()) { + summary.markAsGroupSummary(); + } List<NotificationEntry> children = groupEntry.getChildren(); inflateRequiredNotifViews(summary); for (int j = 0; j < children.size(); j++) { NotificationEntry child = children.get(j); + if (AsyncHybridViewInflation.isEnabled()) child.markAsGroupChild(); boolean childShouldBeBound = j < mChildBindCutoff; if (childShouldBeBound) { inflateRequiredNotifViews(child); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/inflation/NotifUiAdjustment.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/inflation/NotifUiAdjustment.kt index bab94b50018e..e70fb6b0fdf3 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/inflation/NotifUiAdjustment.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/inflation/NotifUiAdjustment.kt @@ -52,11 +52,8 @@ class NotifUiAdjustment internal constructor( oldAdjustment.needsRedaction != newAdjustment.needsRedaction -> true areDifferent(oldAdjustment.smartActions, newAdjustment.smartActions) -> true newAdjustment.smartReplies != oldAdjustment.smartReplies -> true - // TODO(b/217799515): Here we decide whether to re-inflate the row on every group-status - // change if we want to keep the single-line view, the following line should be: - // !oldAdjustment.isChildInGroup && newAdjustment.isChildInGroup -> true AsyncHybridViewInflation.isEnabled && - oldAdjustment.isChildInGroup != newAdjustment.isChildInGroup -> true + !oldAdjustment.isChildInGroup && newAdjustment.isChildInGroup -> true AsyncGroupHeaderViewInflation.isEnabled && !oldAdjustment.isGroupSummary && newAdjustment.isGroupSummary -> true else -> false diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/inflation/NotifUiAdjustmentProvider.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/inflation/NotifUiAdjustmentProvider.kt index e0e5a3578c31..4c2ef8322731 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/inflation/NotifUiAdjustmentProvider.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/inflation/NotifUiAdjustmentProvider.kt @@ -141,7 +141,7 @@ class NotifUiAdjustmentProvider @Inject constructor( lockscreenUserManager.needsRedaction(entry) || (screenshareNotificationHiding() && sensitiveNotifProtectionController.shouldProtectNotification(entry)), - isChildInGroup = entry.sbn.isAppOrSystemGroupChild, - isGroupSummary = entry.sbn.isAppOrSystemGroupSummary, + isChildInGroup = entry.hasEverBeenGroupChild(), + isGroupSummary = entry.hasEverBeenGroupSummary(), ) } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/AmbientState.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/AmbientState.java index ac44b3e4f1f2..5b9eb21bf5e9 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/AmbientState.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/AmbientState.java @@ -414,7 +414,7 @@ public class AmbientState implements Dumpable { return mLayoutMaxHeight; } - public float getTopPadding() { + public int getTopPadding() { return mTopPadding; } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java index bef26d9958ef..3944c3ae5cdb 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java @@ -201,7 +201,6 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable private float mIntrinsicContentHeight; private int mPaddingBetweenElements; private int mMaxTopPadding; - private int mTopPadding; private boolean mAnimateNextTopPaddingChange; private int mBottomPadding; @VisibleForTesting @@ -282,7 +281,6 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable */ private float mMinTopOverScrollToEscape; private int mIntrinsicPadding; - private float mStackTranslation; private float mTopPaddingOverflow; private boolean mDontReportNextOverScroll; private boolean mDontClampNextScroll; @@ -820,8 +818,8 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable int y = 0; drawDebugInfo(canvas, y, Color.RED, /* label= */ "y = " + y); - y = mTopPadding; - drawDebugInfo(canvas, y, Color.RED, /* label= */ "mTopPadding = " + y); + y = getTopPadding(); + drawDebugInfo(canvas, y, Color.RED, /* label= */ "getTopPadding() = " + y); y = getLayoutHeight(); drawDebugInfo(canvas, y, Color.YELLOW, /* label= */ "getLayoutHeight() = " + y); @@ -1159,7 +1157,6 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable mAmbientState.setLayoutHeight(getLayoutHeight()); mAmbientState.setLayoutMaxHeight(mMaxLayoutHeight); updateAlgorithmLayoutMinHeight(); - mAmbientState.setTopPadding(mTopPadding); } private void updateAlgorithmLayoutMinHeight() { @@ -1252,13 +1249,13 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable } public int getTopPadding() { - return mTopPadding; + return mAmbientState.getTopPadding(); } private void setTopPadding(int topPadding, boolean animate) { - if (mTopPadding != topPadding) { + if (getTopPadding() != topPadding) { + mAmbientState.setTopPadding(topPadding); boolean shouldAnimate = animate || mAnimateNextTopPaddingChange; - mTopPadding = topPadding; updateAlgorithmHeightAndPadding(); updateContentHeight(); if (mAmbientState.isOnKeyguard() @@ -1303,7 +1300,7 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable private void updateStackPosition(boolean listenerNeedsAnimation) { float topOverscrollAmount = mShouldUseSplitNotificationShade ? getCurrentOverScrollAmount(true /* top */) : 0f; - final float endTopPosition = mTopPadding + mExtraTopInsetForFullShadeTransition + final float endTopPosition = getTopPadding() + mExtraTopInsetForFullShadeTransition + mAmbientState.getOverExpansion() + topOverscrollAmount - getCurrentOverScrollAmount(false /* top */); @@ -1316,7 +1313,7 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable // TODO(b/322228881): Clean up scene container vs legacy behavior in NSSL if (SceneContainerFlag.isEnabled()) { // stackY should be driven by scene container, not NSSL - mAmbientState.setStackY(mTopPadding); + mAmbientState.setStackY(getTopPadding()); } else { final float stackY = MathUtils.lerp(0, endTopPosition, fraction); mAmbientState.setStackY(stackY); @@ -1333,7 +1330,7 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable final float oldStackHeight = mAmbientState.getStackHeight(); if (mQsExpansionFraction <= 0 && !shouldSkipHeightUpdate()) { final float endHeight = updateStackEndHeight( - getHeight(), getEmptyBottomMargin(), mTopPadding); + getHeight(), getEmptyBottomMargin(), getTopPadding()); updateStackHeight(endHeight, fraction); } else { // Always updateStackHeight to prevent jumps in the stack height when this fraction @@ -1420,9 +1417,9 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable if (!appearing) { translationY = 0; if (mShouldShowShelfOnly) { - stackHeight = mTopPadding + mShelf.getIntrinsicHeight(); + stackHeight = getTopPadding() + mShelf.getIntrinsicHeight(); } else if (mQsFullScreen) { - int stackStartPosition = mContentHeight - mTopPadding + mIntrinsicPadding; + int stackStartPosition = mContentHeight - getTopPadding() + mIntrinsicPadding; int stackEndPosition = mMaxTopPadding + mShelf.getIntrinsicHeight(); if (stackStartPosition <= stackEndPosition) { stackHeight = stackEndPosition; @@ -1451,7 +1448,7 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable stackHeight = (int) (height - translationY); if (isHeadsUpTransition() && appearFraction >= 0) { int topSpacing = mShouldUseSplitNotificationShade - ? mAmbientState.getStackTopMargin() : mTopPadding; + ? mAmbientState.getStackTopMargin() : getTopPadding(); float startPos = mHeadsUpInset - topSpacing; translationY = MathUtils.lerp(startPos, 0, appearFraction); } @@ -1524,7 +1521,7 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable * Measured relative to the resting position. */ private float getExpandTranslationStart() { - return -mTopPadding + getMinExpansionHeight() - mShelf.getIntrinsicHeight(); + return -getTopPadding() + getMinExpansionHeight() - mShelf.getIntrinsicHeight(); } /** @@ -1593,7 +1590,7 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable } else { appearPosition = mEmptyShadeView.getHeight(); } - return appearPosition + (onKeyguard() ? mTopPadding : mIntrinsicPadding); + return appearPosition + (onKeyguard() ? getTopPadding() : mIntrinsicPadding); } /** @@ -1619,7 +1616,7 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable } else { appearPosition = mEmptyShadeView.getHeight(); } - return appearPosition + (onKeyguard() ? mTopPadding : mIntrinsicPadding); + return appearPosition + (onKeyguard() ? getTopPadding() : mIntrinsicPadding); } private boolean isHeadsUpTransition() { @@ -1653,12 +1650,11 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable } public float getStackTranslation() { - return mStackTranslation; + return mAmbientState.getStackTranslation(); } private void setStackTranslation(float stackTranslation) { - if (stackTranslation != mStackTranslation) { - mStackTranslation = stackTranslation; + if (stackTranslation != getStackTranslation()) { mAmbientState.setStackTranslation(stackTranslation); requestChildrenUpdate(); } @@ -2322,7 +2318,8 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable // The topPadding can be bigger than the regular padding when qs is expanded, in that // state the maxPanelHeight and the contentHeight should be bigger - mContentHeight = (int) (height + Math.max(mIntrinsicPadding, mTopPadding) + mBottomPadding); + mContentHeight = + (int) (height + Math.max(mIntrinsicPadding, getTopPadding()) + mBottomPadding); updateScrollability(); clampScrollPosition(); updateStackPosition(); @@ -2800,7 +2797,7 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable if (mShouldUseSplitNotificationShade) { return mSidePaddings; } - return mTopPadding - mQsScrollBoundaryPosition; + return getTopPadding() - mQsScrollBoundaryPosition; } private int getIntrinsicHeight(View view) { @@ -3859,7 +3856,7 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable // fall through case android.R.id.accessibilityActionScrollUp: final int viewportHeight = - getHeight() - mPaddingBottom - mTopPadding - mPaddingTop + getHeight() - mPaddingBottom - getTopPadding() - mPaddingTop - mShelf.getIntrinsicHeight(); final int targetScrollY = Math.max(0, Math.min(mOwnScrollY + direction * viewportHeight, getScrollRange())); @@ -4077,7 +4074,7 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable if (row.isChildInGroup()) { endPosition += row.getNotificationParent().getTranslationY(); } - int layoutEnd = mMaxLayoutHeight + (int) mStackTranslation; + int layoutEnd = mMaxLayoutHeight + (int) getStackTranslation(); NotificationSection lastSection = getLastVisibleSection(); ExpandableView lastVisibleChild = lastSection == null ? null : lastSection.getLastVisibleChild(); @@ -4560,7 +4557,7 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable } } } - return touchY > mTopPadding + mStackTranslation; + return touchY > getTopPadding() + getStackTranslation(); } /** @@ -4943,7 +4940,7 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable println(pw, "intrinsicContentHeight", mIntrinsicContentHeight); println(pw, "contentHeight", mContentHeight); println(pw, "intrinsicPadding", mIntrinsicPadding); - println(pw, "topPadding", mTopPadding); + println(pw, "topPadding", getTopPadding()); println(pw, "bottomPadding", mBottomPadding); dumpRoundedRectClipping(pw); println(pw, "requestedClipBounds", mRequestedClipBounds); diff --git a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java index c3274477862a..27a708a00cb7 100644 --- a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java +++ b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java @@ -173,9 +173,9 @@ public class VolumeDialogImpl implements VolumeDialog, Dumpable, private static final String TYPE_DISMISS = "dismiss"; /** Volume dialog slider animation. */ private static final String TYPE_UPDATE = "update"; - static final short PROGRESS_HAPTICS_DISABLED = 0; - static final short PROGRESS_HAPTICS_EAGER = 1; - static final short PROGRESS_HAPTICS_ANIMATED = 2; + static final int PROGRESS_HAPTICS_DISABLED = 0; + static final int PROGRESS_HAPTICS_EAGER = 1; + static final int PROGRESS_HAPTICS_ANIMATED = 2; /** * TODO(b/290612381): remove lingering animations or tolerate them @@ -851,10 +851,7 @@ public class VolumeDialogImpl implements VolumeDialog, Dumpable, row.header.setFilters(new InputFilter[] {new InputFilter.LengthFilter(13)}); } row.slider = row.view.findViewById(R.id.volume_row_slider); - if (hapticVolumeSlider()) { - row.createPlugin(mVibratorHelper, mSystemClock); - HapticSliderViewBinder.bind(row.slider, row.mHapticPlugin); - } + addSliderHapticsToRow(row); row.slider.setOnSeekBarChangeListener(new VolumeSeekBarChangeListener(row)); row.number = row.view.findViewById(R.id.volume_number); @@ -915,6 +912,23 @@ public class VolumeDialogImpl implements VolumeDialog, Dumpable, } } + private void addSliderHapticsToRow(VolumeRow row) { + if (hapticVolumeSlider()) { + row.createPlugin(mVibratorHelper, mSystemClock); + HapticSliderViewBinder.bind(row.slider, row.mHapticPlugin); + } + } + + @VisibleForTesting void addSliderHapticsToRows() { + for (VolumeRow row: mRows) { + addSliderHapticsToRow(row); + } + } + + @VisibleForTesting void removeDismissMessages() { + mHandler.removeMessages(H.DISMISS); + } + private void setRingerMode(int newRingerMode) { Events.writeEvent(Events.EVENT_RINGER_TOGGLE, newRingerMode); incrementManualToggleCount(); @@ -2105,7 +2119,7 @@ public class VolumeDialogImpl implements VolumeDialog, Dumpable, } } - @VisibleForTesting short progressHapticsForStream(int stream) { + @VisibleForTesting int progressHapticsForStream(int stream) { for (VolumeRow row: mRows) { if (row.stream == stream) { return row.mProgressHapticsType; @@ -2619,7 +2633,7 @@ public class VolumeDialogImpl implements VolumeDialog, Dumpable, private int animTargetProgress; private int lastAudibleLevel = 1; private SeekableSliderHapticPlugin mHapticPlugin; - private short mProgressHapticsType = PROGRESS_HAPTICS_DISABLED; + private int mProgressHapticsType = PROGRESS_HAPTICS_DISABLED; void setIcon(int iconRes, Resources.Theme theme) { if (icon != null) { @@ -2661,7 +2675,7 @@ public class VolumeDialogImpl implements VolumeDialog, Dumpable, slider.setOnTouchListener(null); } - void deliverOnProgressChangedHaptics(boolean fromUser, int progress, short hapticsType) { + void deliverOnProgressChangedHaptics(boolean fromUser, int progress, int hapticsType) { if (mHapticPlugin == null) return; mHapticPlugin.onProgressChanged(slider, progress, fromUser); diff --git a/packages/SystemUI/tests/src/com/android/systemui/complication/DreamClockTimeComplicationTest.java b/packages/SystemUI/tests/src/com/android/systemui/complication/DreamClockTimeComplicationTest.java index cbbbe5203b24..b9aa4c65be92 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/complication/DreamClockTimeComplicationTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/complication/DreamClockTimeComplicationTest.java @@ -38,7 +38,6 @@ import com.android.systemui.shared.condition.Monitor; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; -import org.mockito.ArgumentCaptor; import org.mockito.Mock; import org.mockito.MockitoAnnotations; @@ -140,18 +139,4 @@ public class DreamClockTimeComplicationTest extends SysuiTestCase { assertThat(viewHolder.getView()).isEqualTo(mView); assertThat(viewHolder.getLayoutParams()).isEqualTo(mLayoutParams); } - - @Test - public void testClick_logUiEvent() { - final DreamClockTimeComplication.DreamClockTimeViewController controller = - new DreamClockTimeComplication.DreamClockTimeViewController(mView, mUiEventLogger); - controller.onViewAttached(); - - final ArgumentCaptor<View.OnClickListener> clickListenerCaptor = - ArgumentCaptor.forClass(View.OnClickListener.class); - verify(mView).setOnClickListener(clickListenerCaptor.capture()); - - clickListenerCaptor.getValue().onClick(mView); - verify(mUiEventLogger).log(DreamOverlayUiEvent.DREAM_CLOCK_TAPPED); - } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/InWindowLauncherUnlockAnimationInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/InWindowLauncherUnlockAnimationInteractorTest.kt index 49f7565517da..c782e9d2d98d 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/InWindowLauncherUnlockAnimationInteractorTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/InWindowLauncherUnlockAnimationInteractorTest.kt @@ -21,6 +21,7 @@ import androidx.test.filters.SmallTest import com.android.systemui.SysUITestModule import com.android.systemui.SysuiTestCase import com.android.systemui.TestMocksModule +import com.android.systemui.biometrics.domain.BiometricsDomainLayerModule import com.android.systemui.coroutines.collectValues import com.android.systemui.dagger.SysUISingleton import com.android.systemui.keyguard.data.repository.FakeKeyguardSurfaceBehindRepository @@ -441,6 +442,7 @@ class InWindowLauncherUnlockAnimationInteractorTest : SysuiTestCase() { modules = [ SysUITestModule::class, + BiometricsDomainLayerModule::class, ] ) interface TestComponent { diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/binder/InWindowLauncherUnlockAnimationManagerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/binder/InWindowLauncherUnlockAnimationManagerTest.kt index e9399cc17158..33e9b363915c 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/binder/InWindowLauncherUnlockAnimationManagerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/binder/InWindowLauncherUnlockAnimationManagerTest.kt @@ -20,6 +20,7 @@ import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysUITestModule import com.android.systemui.SysuiTestCase +import com.android.systemui.biometrics.domain.BiometricsDomainLayerModule import com.android.systemui.dagger.SysUISingleton import com.android.systemui.keyguard.ui.view.InWindowLauncherUnlockAnimationManager import com.android.systemui.shared.system.smartspace.ILauncherUnlockAnimationController @@ -118,6 +119,7 @@ class InWindowLauncherUnlockAnimationManagerTest : SysuiTestCase() { modules = [ SysUITestModule::class, + BiometricsDomainLayerModule::class, ] ) interface TestComponent { diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowControllerImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowControllerImplTest.java index 1ee26db81826..02f2e16b9570 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowControllerImplTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowControllerImplTest.java @@ -197,7 +197,9 @@ public class NotificationShadeWindowControllerImplTest extends SysuiTestCase { shadeRepository, keyguardTransitionInteractor, () -> sceneInteractor, - () -> mKosmos.getFromGoneTransitionInteractor()); + () -> mKosmos.getFromGoneTransitionInteractor(), + () -> mKosmos.getSharedNotificationContainerInteractor(), + mTestScope); CommunalInteractor communalInteractor = mKosmos.getCommunalInteractor(); mFromLockscreenTransitionInteractor = mKosmos.getFromLockscreenTransitionInteractor(); diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/QuickSettingsControllerImplBaseTest.java b/packages/SystemUI/tests/src/com/android/systemui/shade/QuickSettingsControllerImplBaseTest.java index b9451bafec90..ee279d892c1d 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/shade/QuickSettingsControllerImplBaseTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/shade/QuickSettingsControllerImplBaseTest.java @@ -223,7 +223,9 @@ public class QuickSettingsControllerImplBaseTest extends SysuiTestCase { mShadeRepository, keyguardTransitionInteractor, () -> sceneInteractor, - () -> mKosmos.getFromGoneTransitionInteractor()); + () -> mKosmos.getFromGoneTransitionInteractor(), + () -> mKosmos.getSharedNotificationContainerInteractor(), + mTestScope); mFromLockscreenTransitionInteractor = mKosmos.getFromLockscreenTransitionInteractor(); mFromPrimaryBouncerTransitionInteractor = diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/StatusBarStateControllerImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/StatusBarStateControllerImplTest.kt index f9e08fee2120..e54b53225320 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/StatusBarStateControllerImplTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/StatusBarStateControllerImplTest.kt @@ -50,7 +50,6 @@ import com.android.systemui.keyguard.domain.interactor.fromLockscreenTransitionI import com.android.systemui.keyguard.domain.interactor.fromPrimaryBouncerTransitionInteractor import com.android.systemui.keyguard.domain.interactor.keyguardClockInteractor import com.android.systemui.keyguard.domain.interactor.keyguardTransitionInteractor -import com.android.systemui.kosmos.Kosmos import com.android.systemui.kosmos.testScope import com.android.systemui.plugins.statusbar.StatusBarStateController import com.android.systemui.power.data.repository.FakePowerRepository @@ -65,6 +64,7 @@ import com.android.systemui.shade.domain.interactor.ShadeInteractorImpl import com.android.systemui.shade.domain.interactor.ShadeInteractorLegacyImpl import com.android.systemui.statusbar.disableflags.data.repository.FakeDisableFlagsRepository import com.android.systemui.statusbar.notification.stack.domain.interactor.SharedNotificationContainerInteractor +import com.android.systemui.statusbar.notification.stack.domain.interactor.sharedNotificationContainerInteractor import com.android.systemui.statusbar.policy.ResourcesSplitShadeStateController import com.android.systemui.statusbar.policy.data.repository.FakeUserSetupRepository import com.android.systemui.statusbar.policy.domain.interactor.deviceProvisioningInteractor @@ -153,6 +153,8 @@ class StatusBarStateControllerImplTest : SysuiTestCase() { keyguardTransitionInteractor, { kosmos.sceneInteractor }, { kosmos.fromGoneTransitionInteractor }, + { kosmos.sharedNotificationContainerInteractor }, + testScope, ) whenever(deviceEntryUdfpsInteractor.isUdfpsSupported).thenReturn(MutableStateFlow(false)) diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/inflation/NotifUiAdjustmentProviderTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/inflation/NotifUiAdjustmentProviderTest.kt index 34eeba05906a..8e6ceccbc14e 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/inflation/NotifUiAdjustmentProviderTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/inflation/NotifUiAdjustmentProviderTest.kt @@ -49,7 +49,6 @@ import org.junit.Test import org.junit.runner.RunWith import org.mockito.Mockito.clearInvocations import org.mockito.Mockito.inOrder -import org.mockito.Mockito.spy import org.mockito.Mockito.verify import org.mockito.Mockito.verifyNoMoreInteractions import org.mockito.Mockito.`when` as whenever @@ -158,17 +157,14 @@ class NotifUiAdjustmentProviderTest : SysuiTestCase() { @Test @EnableFlags(AsyncHybridViewInflation.FLAG_NAME) - fun changeIsChildInGroup_asyncHybirdFlagEnabled_needReInflation() { + fun becomeChildInGroup_asyncHybirdFlagEnabled_needReInflation() { // Given: an Entry that is not child in group // AsyncHybridViewInflation flag is enabled - val spySbn = spy(entry.sbn) - entry.sbn = spySbn - whenever(spySbn.isAppOrSystemGroupChild).thenReturn(false) val oldAdjustment = adjustmentProvider.calculateAdjustment(entry) assertThat(oldAdjustment.isChildInGroup).isFalse() // When: the Entry becomes a group child - whenever(spySbn.isAppOrSystemGroupChild).thenReturn(true) + entry.markAsGroupChild() val newAdjustment = adjustmentProvider.calculateAdjustment(entry) assertThat(newAdjustment.isChildInGroup).isTrue() assertThat(newAdjustment).isNotEqualTo(oldAdjustment) @@ -179,17 +175,14 @@ class NotifUiAdjustmentProviderTest : SysuiTestCase() { @Test @DisableFlags(AsyncHybridViewInflation.FLAG_NAME) - fun changeIsChildInGroup_asyncHybirdFlagDisabled_noNeedForReInflation() { + fun becomeChildInGroup_asyncHybirdFlagDisabled_noNeedForReInflation() { // Given: an Entry that is not child in group // AsyncHybridViewInflation flag is disabled - val spySbn = spy(entry.sbn) - entry.sbn = spySbn - whenever(spySbn.isAppOrSystemGroupChild).thenReturn(false) val oldAdjustment = adjustmentProvider.calculateAdjustment(entry) assertThat(oldAdjustment.isChildInGroup).isFalse() // When: the Entry becomes a group child - whenever(spySbn.isAppOrSystemGroupChild).thenReturn(true) + entry.markAsGroupChild() val newAdjustment = adjustmentProvider.calculateAdjustment(entry) assertThat(newAdjustment.isChildInGroup).isTrue() assertThat(newAdjustment).isNotEqualTo(oldAdjustment) @@ -202,14 +195,11 @@ class NotifUiAdjustmentProviderTest : SysuiTestCase() { @EnableFlags(AsyncGroupHeaderViewInflation.FLAG_NAME) fun changeIsGroupSummary_needReInflation() { // Given: an Entry that is not a group summary - val spySbn = spy(entry.sbn) - entry.sbn = spySbn - whenever(spySbn.isAppOrSystemGroupSummary).thenReturn(false) val oldAdjustment = adjustmentProvider.calculateAdjustment(entry) assertThat(oldAdjustment.isGroupSummary).isFalse() // When: the Entry becomes a group summary - whenever(spySbn.isAppOrSystemGroupSummary).thenReturn(true) + entry.markAsGroupSummary() val newAdjustment = adjustmentProvider.calculateAdjustment(entry) assertThat(newAdjustment.isGroupSummary).isTrue() assertThat(newAdjustment).isNotEqualTo(oldAdjustment) diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/icon/domain/interactor/NotificationIconsInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/icon/domain/interactor/NotificationIconsInteractorTest.kt index 7faf5628b40a..b410b33b97d9 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/icon/domain/interactor/NotificationIconsInteractorTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/icon/domain/interactor/NotificationIconsInteractorTest.kt @@ -21,6 +21,7 @@ import com.android.systemui.SysUITestComponent import com.android.systemui.SysUITestModule import com.android.systemui.SysuiTestCase import com.android.systemui.TestMocksModule +import com.android.systemui.biometrics.domain.BiometricsDomainLayerModule import com.android.systemui.collectLastValue import com.android.systemui.dagger.SysUISingleton import com.android.systemui.deviceentry.data.repository.FakeDeviceEntryRepository @@ -155,7 +156,7 @@ class AlwaysOnDisplayNotificationIconsInteractorTest : SysuiTestCase() { private val bubbles: Bubbles = mock() - @Component(modules = [SysUITestModule::class]) + @Component(modules = [SysUITestModule::class, BiometricsDomainLayerModule::class]) @SysUISingleton interface TestComponent : SysUITestComponent<AlwaysOnDisplayNotificationIconsInteractor> { diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithmTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithmTest.kt index 995da8192f7b..2c2b1831bcfd 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithmTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithmTest.kt @@ -886,7 +886,7 @@ class StackScrollAlgorithmTest : SysuiTestCase() { fun shadeClosed_hunShouldHaveFullShadow() { // Given: shade is closed, ambientState.stackTranslation == -ambientState.topPadding, // the height of HUN is equal to the height of QQS Panel, - ambientState.stackTranslation = -ambientState.topPadding + ambientState.stackTranslation = (-ambientState.topPadding).toFloat() // Mock the height of shade ambientState.setLayoutMinHeight(1000) val childHunView = @@ -914,7 +914,7 @@ class StackScrollAlgorithmTest : SysuiTestCase() { fun draggingHunToOpenShade_hunShouldHavePartialShadow() { // Given: shade is closed when HUN pops up, // now drags down the HUN to open shade - ambientState.stackTranslation = -ambientState.topPadding + ambientState.stackTranslation = (-ambientState.topPadding).toFloat() // Mock the height of shade ambientState.setLayoutMinHeight(1000) val childHunView = diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewControllerTest.java index d9e9c596ee4a..05fd63e96089 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewControllerTest.java @@ -178,7 +178,9 @@ public class KeyguardStatusBarViewControllerTest extends SysuiTestCase { new FakeShadeRepository(), keyguardTransitionInteractor, () -> mKosmos.getSceneInteractor(), - () -> mKosmos.getFromGoneTransitionInteractor()); + () -> mKosmos.getFromGoneTransitionInteractor(), + () -> mKosmos.getSharedNotificationContainerInteractor(), + mTestScope); mViewModel = new KeyguardStatusBarViewModel( mTestScope.getBackgroundScope(), diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/CollapsedStatusBarViewModelImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/CollapsedStatusBarViewModelImplTest.kt index f63f79ff9835..865b312b6a4b 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/CollapsedStatusBarViewModelImplTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/CollapsedStatusBarViewModelImplTest.kt @@ -23,6 +23,7 @@ import com.android.systemui.CoroutineTestScopeModule import com.android.systemui.SysUITestComponent import com.android.systemui.SysUITestModule import com.android.systemui.SysuiTestCase +import com.android.systemui.biometrics.domain.BiometricsDomainLayerModule import com.android.systemui.collectLastValue import com.android.systemui.collectValues import com.android.systemui.communal.dagger.CommunalModule @@ -60,6 +61,7 @@ class CollapsedStatusBarViewModelImplTest : SysuiTestCase() { [ SysUITestModule::class, CommunalModule::class, + BiometricsDomainLayerModule::class, ] ) interface TestComponent : SysUITestComponent<CollapsedStatusBarViewModelImpl> { diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/ui/viewmodel/KeyguardStatusBarViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/ui/viewmodel/KeyguardStatusBarViewModelTest.kt index e4b9f102c51c..ae3425678abd 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/ui/viewmodel/KeyguardStatusBarViewModelTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/ui/viewmodel/KeyguardStatusBarViewModelTest.kt @@ -35,6 +35,7 @@ import com.android.systemui.shade.data.repository.FakeShadeRepository import com.android.systemui.statusbar.CommandQueue import com.android.systemui.statusbar.data.repository.FakeKeyguardStatusBarRepository import com.android.systemui.statusbar.domain.interactor.KeyguardStatusBarInteractor +import com.android.systemui.statusbar.notification.stack.domain.interactor.sharedNotificationContainerInteractor import com.android.systemui.statusbar.policy.BatteryController import com.android.systemui.testKosmos import com.android.systemui.util.mockito.argumentCaptor @@ -66,6 +67,8 @@ class KeyguardStatusBarViewModelTest : SysuiTestCase() { kosmos.keyguardTransitionInteractor, { kosmos.sceneInteractor }, { kosmos.fromGoneTransitionInteractor }, + { kosmos.sharedNotificationContainerInteractor }, + testScope, ) private val keyguardStatusBarInteractor = KeyguardStatusBarInteractor( diff --git a/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogImplTest.java index 5206db4aa13a..11a53f753b2a 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogImplTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogImplTest.java @@ -95,7 +95,6 @@ import junit.framework.Assert; import org.junit.After; import org.junit.Before; -import org.junit.Ignore; import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; @@ -272,50 +271,53 @@ public class VolumeDialogImplTest extends SysuiTestCase { @Test @DisableFlags(FLAG_HAPTIC_VOLUME_SLIDER) public void testVolumeChange_noSliderHaptics_doesNotDeliverOnProgressChangedHaptics() { - // Initialize the dialog again with haptic sliders disabled - mDialog.init(0, null); final State shellState = createShellState(); VolumeDialogController.StreamState musicStreamState = shellState.states.get(AudioSystem.STREAM_MUSIC); mDialog.show(SHOW_REASON_UNKNOWN); mTestableLooper.processMessages(1); //Only the SHOW message + mDialog.removeDismissMessages(); // Temporarily remove the rescheduled DISMISS // Change the volume two times musicStreamState.level += 10; mDialog.onStateChangedH(shellState); - mAnimatorTestRule.advanceTimeBy(10); musicStreamState.level += 10; mDialog.onStateChangedH(shellState); - // expected: the type of the progress haptics for the stream should be DISABLED - short type = mDialog.progressHapticsForStream(AudioSystem.STREAM_MUSIC); + // expected: the type of the latest progress haptics for the stream should be DISABLED + int type = mDialog.progressHapticsForStream(AudioSystem.STREAM_MUSIC); assertEquals(VolumeDialogImpl.PROGRESS_HAPTICS_DISABLED, type); + + mDialog.dismiss(DISMISS_REASON_UNKNOWN); // Dismiss + mTestableLooper.processAllMessages(); } - @Ignore("Causing breakages so ignoring to resolve, b/329099861") @Test @EnableFlags(FLAG_HAPTIC_VOLUME_SLIDER) public void testVolumeChange_withSliderHaptics_deliversOnProgressChangedHapticsEagerly() { - // Initialize the dialog again to create haptic plugins on the rows with the flag enabled - mDialog.init(0, null); + // create haptic plugins on the rows with the flag enabled + mDialog.addSliderHapticsToRows(); final State shellState = createShellState(); VolumeDialogController.StreamState musicStreamState = shellState.states.get(AudioSystem.STREAM_MUSIC); mDialog.show(SHOW_REASON_UNKNOWN); mTestableLooper.processMessages(1); //Only the SHOW message + mDialog.removeDismissMessages(); // Temporarily remove the rescheduled DISMISS // Change the volume two times musicStreamState.level += 10; mDialog.onStateChangedH(shellState); - mAnimatorTestRule.advanceTimeBy(10); musicStreamState.level += 10; mDialog.onStateChangedH(shellState); - // expected: the type of the progress haptics for the stream should be EAGER - short type = mDialog.progressHapticsForStream(AudioSystem.STREAM_MUSIC); + // expected: the type of the latest progress haptics for the stream should be EAGER + int type = mDialog.progressHapticsForStream(AudioSystem.STREAM_MUSIC); assertEquals(VolumeDialogImpl.PROGRESS_HAPTICS_EAGER, type); + + mDialog.dismiss(DISMISS_REASON_UNKNOWN); // Dismiss + mTestableLooper.processAllMessages(); } @Test diff --git a/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java b/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java index ec27f48f9570..aabd4e9e79be 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java @@ -442,7 +442,9 @@ public class BubblesTest extends SysuiTestCase { shadeRepository, keyguardTransitionInteractor, () -> sceneInteractor, - () -> mKosmos.getFromGoneTransitionInteractor()); + () -> mKosmos.getFromGoneTransitionInteractor(), + () -> mKosmos.getSharedNotificationContainerInteractor(), + mTestScope); mFromLockscreenTransitionInteractor = mKosmos.getFromLockscreenTransitionInteractor(); mFromPrimaryBouncerTransitionInteractor = diff --git a/libs/hwui/platform/host/pipeline/skia/SkiaOpenGLPipeline.h b/packages/SystemUI/tests/utils/src/android/os/PowerManagerKosmos.kt index 4fafbcc4748d..4ddbb457fd6b 100644 --- a/libs/hwui/platform/host/pipeline/skia/SkiaOpenGLPipeline.h +++ b/packages/SystemUI/tests/utils/src/android/os/PowerManagerKosmos.kt @@ -1,5 +1,5 @@ /* - * Copyright (C) 2016 The Android Open Source Project + * Copyright (C) 2024 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. @@ -14,22 +14,9 @@ * limitations under the License. */ -#pragma once +package android.os -#include "pipeline/skia/SkiaGpuPipeline.h" +import com.android.systemui.kosmos.Kosmos +import com.android.systemui.util.mockito.mock -namespace android { - -namespace uirenderer { -namespace skiapipeline { - -class SkiaOpenGLPipeline : public SkiaGpuPipeline { -public: - SkiaOpenGLPipeline(renderthread::RenderThread& thread) : SkiaGpuPipeline(thread) {} - - static void invokeFunctor(const renderthread::RenderThread& thread, Functor* functor) {} -}; - -} /* namespace skiapipeline */ -} /* namespace uirenderer */ -} /* namespace android */ +var Kosmos.powerManager by Kosmos.Fixture { mock<PowerManager>() } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractorFactory.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractorFactory.kt index 00cdc337bc06..e21c76672c1d 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractorFactory.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractorFactory.kt @@ -24,15 +24,20 @@ import com.android.systemui.flags.FakeFeatureFlags import com.android.systemui.keyguard.data.repository.FakeCommandQueue import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository import com.android.systemui.keyguard.shared.model.KeyguardState +import com.android.systemui.kosmos.testScope import com.android.systemui.power.domain.interactor.PowerInteractor import com.android.systemui.power.domain.interactor.PowerInteractorFactory import com.android.systemui.scene.domain.interactor.SceneInteractor import com.android.systemui.scene.shared.flag.FakeSceneContainerFlags import com.android.systemui.scene.shared.flag.SceneContainerFlags import com.android.systemui.shade.data.repository.FakeShadeRepository +import com.android.systemui.statusbar.notification.stack.domain.interactor.SharedNotificationContainerInteractor +import com.android.systemui.statusbar.notification.stack.domain.interactor.SharedNotificationContainerInteractor.ConfigurationBasedDimensions import com.android.systemui.util.mockito.mock import com.android.systemui.util.mockito.whenever +import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.flow.MutableSharedFlow +import kotlinx.coroutines.test.TestScope /** * Simply put, I got tired of adding a constructor argument and then having to tweak dozens of @@ -52,13 +57,33 @@ object KeyguardInteractorFactory { shadeRepository: FakeShadeRepository = FakeShadeRepository(), sceneInteractor: SceneInteractor = mock(), fromGoneTransitionInteractor: FromGoneTransitionInteractor = mock(), + sharedNotificationContainerInteractor: SharedNotificationContainerInteractor? = null, powerInteractor: PowerInteractor = PowerInteractorFactory.create().powerInteractor, + testScope: CoroutineScope = TestScope(), ): WithDependencies { - // Mock this until the class is replaced by kosmos - val keyguardTransitionInteractor: KeyguardTransitionInteractor = mock() + // Mock these until they are replaced by kosmos val currentKeyguardStateFlow = MutableSharedFlow<KeyguardState>() - whenever(keyguardTransitionInteractor.currentKeyguardState) - .thenReturn(currentKeyguardStateFlow) + val keyguardTransitionInteractor = + mock<KeyguardTransitionInteractor>().also { + whenever(it.currentKeyguardState).thenReturn(currentKeyguardStateFlow) + } + val configurationDimensionFlow = MutableSharedFlow<ConfigurationBasedDimensions>() + configurationDimensionFlow.tryEmit( + ConfigurationBasedDimensions( + useSplitShade = false, + useLargeScreenHeader = false, + marginHorizontal = 0, + marginBottom = 0, + marginTop = 0, + marginTopLargeScreen = 0, + keyguardSplitShadeTopMargin = 0, + ) + ) + val sncInteractor = + sharedNotificationContainerInteractor + ?: mock<SharedNotificationContainerInteractor>().also { + whenever(it.configurationBasedDimensions).thenReturn(configurationDimensionFlow) + } return WithDependencies( repository = repository, commandQueue = commandQueue, @@ -79,6 +104,8 @@ object KeyguardInteractorFactory { keyguardTransitionInteractor = keyguardTransitionInteractor, powerInteractor = powerInteractor, fromGoneTransitionInteractor = { fromGoneTransitionInteractor }, + sharedNotificationContainerInteractor = { sncInteractor }, + applicationScope = testScope, ), ) } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractorKosmos.kt index d61bc9f559bb..2a0c01c5c0af 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractorKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractorKosmos.kt @@ -20,11 +20,13 @@ import com.android.systemui.bouncer.data.repository.keyguardBouncerRepository import com.android.systemui.common.ui.domain.interactor.configurationInteractor import com.android.systemui.keyguard.data.repository.keyguardRepository import com.android.systemui.kosmos.Kosmos +import com.android.systemui.kosmos.testScope import com.android.systemui.power.domain.interactor.powerInteractor import com.android.systemui.scene.domain.interactor.sceneInteractor import com.android.systemui.scene.shared.flag.sceneContainerFlags import com.android.systemui.shade.data.repository.shadeRepository import com.android.systemui.statusbar.commandQueue +import com.android.systemui.statusbar.notification.stack.domain.interactor.sharedNotificationContainerInteractor val Kosmos.keyguardInteractor: KeyguardInteractor by Kosmos.Fixture { @@ -39,5 +41,7 @@ val Kosmos.keyguardInteractor: KeyguardInteractor by keyguardTransitionInteractor = keyguardTransitionInteractor, sceneInteractorProvider = { sceneInteractor }, fromGoneTransitionInteractor = { fromGoneTransitionInteractor }, + sharedNotificationContainerInteractor = { sharedNotificationContainerInteractor }, + applicationScope = testScope.backgroundScope, ) } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/kosmos/KosmosJavaAdapter.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/kosmos/KosmosJavaAdapter.kt index 67d08f8d6284..1b23296ec4d3 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/kosmos/KosmosJavaAdapter.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/kosmos/KosmosJavaAdapter.kt @@ -49,6 +49,7 @@ import com.android.systemui.scene.domain.interactor.sceneInteractor import com.android.systemui.scene.sceneContainerConfig import com.android.systemui.scene.shared.flag.fakeSceneContainerFlags import com.android.systemui.scene.shared.model.sceneDataSource +import com.android.systemui.statusbar.notification.stack.domain.interactor.sharedNotificationContainerInteractor import com.android.systemui.statusbar.phone.screenOffAnimationController import com.android.systemui.statusbar.pipeline.mobile.data.repository.fakeMobileConnectionsRepository import com.android.systemui.statusbar.policy.data.repository.fakeDeviceProvisioningRepository @@ -102,6 +103,9 @@ class KosmosJavaAdapter( val globalActionsInteractor by lazy { kosmos.globalActionsInteractor } val sceneDataSource by lazy { kosmos.sceneDataSource } val keyguardClockInteractor by lazy { kosmos.keyguardClockInteractor } + val sharedNotificationContainerInteractor by lazy { + kosmos.sharedNotificationContainerInteractor + } init { kosmos.applicationContext = testCase.context diff --git a/services/accessibility/accessibility.aconfig b/services/accessibility/accessibility.aconfig index 997f3af3533e..04b19ffd4dfc 100644 --- a/services/accessibility/accessibility.aconfig +++ b/services/accessibility/accessibility.aconfig @@ -24,10 +24,13 @@ flag { } flag { - name: "compute_window_changes_on_a11y" + name: "compute_window_changes_on_a11y_v2" namespace: "accessibility" description: "Computes accessibility window changes in accessibility instead of wm package." bug: "322444245" + metadata { + purpose: PURPOSE_BUGFIX + } } flag { diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java index a57138f9de72..21cc8da22088 100644 --- a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java +++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java @@ -164,6 +164,7 @@ import com.android.internal.util.ArrayUtils; import com.android.internal.util.DumpUtils; import com.android.internal.util.IntPair; import com.android.internal.util.Preconditions; +import com.android.modules.expresslog.Counter; import com.android.server.AccessibilityManagerInternal; import com.android.server.LocalServices; import com.android.server.SystemService; @@ -252,6 +253,20 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub public static final int MAGNIFICATION_GESTURE_HANDLER_ID = 0; private static int sIdCounter = MAGNIFICATION_GESTURE_HANDLER_ID + 1; + /** + * The counter metric id tracking how many times users add qs shortcut for a11y features. + * + * <p>Defined in frameworks/proto_logging/stats/express/catalog/accessibility.cfg. + */ + static final String METRIC_ID_QS_SHORTCUT_ADD = "accessibility.value_qs_shortcut_add"; + + /** + * The counter metric id tracking how many times users remove qs shortcut for a11y features. + * + * <p>Defined in frameworks/proto_logging/stats/express/catalog/accessibility.cfg. + */ + static final String METRIC_ID_QS_SHORTCUT_REMOVE = "accessibility.value_qs_shortcut_remove"; + private final Context mContext; @@ -1767,6 +1782,9 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub if (serviceInfo != null && isAccessibilityServiceWarningRequired(serviceInfo)) { // TODO(b/314850435): show full device control warning if needed after // SysUI QS Panel can update live + // The user attempts to add QS shortcut in QS Panel, but we don't actually + // turn on the shortcut due to lack of full device control permission + logMetricForQsShortcutConfiguration(/* enable= */ true, /* numOfFeatures= */ 1); continue; } a11yFeaturesToEnable.add(a11yFeature); @@ -4183,6 +4201,8 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub ); if (shortcutType == UserShortcutType.QUICK_SETTINGS) { + int numOfFeatureChanged = Math.abs(currentTargets.size() - validNewTargets.size()); + logMetricForQsShortcutConfiguration(enable, numOfFeatureChanged); userState.updateA11yQsTargetLocked(validNewTargets); scheduleNotifyClientsOfServicesStateChangeLocked(userState); onUserStateChangedLocked(userState); @@ -6215,7 +6235,6 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub } } - /** * Bypasses the timeout restriction if volume key shortcut assigned. */ @@ -6225,4 +6244,17 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub Settings.Secure.SKIP_ACCESSIBILITY_SHORTCUT_DIALOG_TIMEOUT_RESTRICTION, /* true */ 1); } + + /** + * Log the metric when the user add/remove qs shortcut for accessibility features. Use the + * callingUid to know where the users configure the a11y qs shortcuts. + */ + private void logMetricForQsShortcutConfiguration(boolean enable, int numOfFeatures) { + if (numOfFeatures <= 0) { + // Skip logging metric if no a11y features are configured + return; + } + String metricId = enable ? METRIC_ID_QS_SHORTCUT_ADD : METRIC_ID_QS_SHORTCUT_REMOVE; + Counter.logIncrementWithUid(metricId, Binder.getCallingUid(), numOfFeatures); + } } diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityWindowManager.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityWindowManager.java index d30748478741..6007bfd99e7b 100644 --- a/services/accessibility/java/com/android/server/accessibility/AccessibilityWindowManager.java +++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityWindowManager.java @@ -445,7 +445,7 @@ public class AccessibilityWindowManager { public void onWindowsForAccessibilityChanged(boolean forceSend, int topFocusedDisplayId, IBinder topFocusedWindowToken, @NonNull List<WindowInfo> windows) { synchronized (mLock) { - if (!Flags.computeWindowChangesOnA11y()) { + if (!Flags.computeWindowChangesOnA11yV2()) { // If the flag is enabled, it's already done in #createWindowInfoListLocked. updateWindowsByWindowAttributesLocked(windows); } @@ -491,7 +491,7 @@ public class AccessibilityWindowManager { /** * Called when the windows for accessibility changed. This is called if - * {@link com.android.server.accessibility.Flags.FLAG_COMPUTE_WINDOW_CHANGES_ON_A11Y} is + * {@link com.android.server.accessibility.Flags.FLAG_COMPUTE_WINDOW_CHANGES_ON_A11Y_V2} is * true. * * @param forceSend Send the windows for accessibility even if they haven't @@ -996,7 +996,7 @@ public class AccessibilityWindowManager { final int windowId = findWindowIdLocked(userId, window.token); // With the flag enabled, createWindowInfoListLocked() already removes invalid windows. - if (!Flags.computeWindowChangesOnA11y()) { + if (!Flags.computeWindowChangesOnA11yV2()) { if (windowId < 0) { return null; } diff --git a/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java b/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java index cfeb5f4e2469..5e566aa98215 100644 --- a/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java +++ b/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java @@ -23,6 +23,8 @@ import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK; import static android.content.res.Resources.ID_NULL; import static android.provider.DeviceConfig.NAMESPACE_SYSTEMUI; +import static com.android.server.appwidget.AppWidgetXmlUtil.deserializeWidgetSizesStr; +import static com.android.server.appwidget.AppWidgetXmlUtil.serializeWidgetSizes; import static com.android.server.pm.PackageManagerService.PLATFORM_PACKAGE_NAME; import android.Manifest; @@ -164,7 +166,6 @@ import java.util.Objects; import java.util.Set; import java.util.concurrent.atomic.AtomicLong; import java.util.function.LongSupplier; -import java.util.stream.Collectors; class AppWidgetServiceImpl extends IAppWidgetService.Stub implements WidgetBackupProvider, OnCrossProfileWidgetProvidersChangeListener { @@ -179,7 +180,6 @@ class AppWidgetServiceImpl extends IAppWidgetService.Stub implements WidgetBacku private static final String STATE_FILENAME = "appwidgets.xml"; private static final String KEY_SIZES = "sizes"; - private static final String SIZE_SEPARATOR = ","; private static final int MIN_UPDATE_PERIOD = DEBUG ? 0 : 30 * 60 * 1000; // 30 minutes @@ -2718,9 +2718,7 @@ class AppWidgetServiceImpl extends IAppWidgetService.Stub implements WidgetBacku List<SizeF> sizes = widget.options.getParcelableArrayList( AppWidgetManager.OPTION_APPWIDGET_SIZES, SizeF.class); if (sizes != null) { - String sizeStr = sizes.stream().map(SizeF::toString) - .collect(Collectors.joining(SIZE_SEPARATOR)); - out.attribute(null, KEY_SIZES, sizeStr); + out.attribute(null, KEY_SIZES, serializeWidgetSizes(sizes)); } if (saveRestoreCompleted) { boolean restoreCompleted = widget.options.getBoolean( @@ -2754,15 +2752,9 @@ class AppWidgetServiceImpl extends IAppWidgetService.Stub implements WidgetBacku options.putInt(AppWidgetManager.OPTION_APPWIDGET_MAX_HEIGHT, maxHeight); } String sizesStr = parser.getAttributeValue(null, KEY_SIZES); - if (sizesStr != null) { - try { - ArrayList<SizeF> sizes = Arrays.stream(sizesStr.split(SIZE_SEPARATOR)) - .map(SizeF::parseSizeF) - .collect(Collectors.toCollection(ArrayList::new)); - options.putParcelableArrayList(AppWidgetManager.OPTION_APPWIDGET_SIZES, sizes); - } catch (NumberFormatException e) { - Slog.e(TAG, "Error parsing widget sizes", e); - } + ArrayList<SizeF> sizes = deserializeWidgetSizesStr(sizesStr); + if (sizes != null) { + options.putParcelableArrayList(AppWidgetManager.OPTION_APPWIDGET_SIZES, sizes); } int category = parser.getAttributeIntHex(null, "host_category", AppWidgetProviderInfo.WIDGET_CATEGORY_UNKNOWN); diff --git a/services/appwidget/java/com/android/server/appwidget/AppWidgetXmlUtil.java b/services/appwidget/java/com/android/server/appwidget/AppWidgetXmlUtil.java index 69b738a2704c..d781cd8d58d8 100644 --- a/services/appwidget/java/com/android/server/appwidget/AppWidgetXmlUtil.java +++ b/services/appwidget/java/com/android/server/appwidget/AppWidgetXmlUtil.java @@ -22,12 +22,18 @@ import android.appwidget.AppWidgetProviderInfo; import android.content.ComponentName; import android.os.Build; import android.text.TextUtils; +import android.util.SizeF; +import android.util.Slog; import com.android.modules.utils.TypedXmlPullParser; import com.android.modules.utils.TypedXmlSerializer; import java.io.IOException; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; import java.util.Objects; +import java.util.stream.Collectors; /** * @hide @@ -59,6 +65,7 @@ public class AppWidgetXmlUtil { private static final String ATTR_DESCRIPTION_RES = "description_res"; private static final String ATTR_PROVIDER_INHERITANCE = "provider_inheritance"; private static final String ATTR_OS_FINGERPRINT = "os_fingerprint"; + private static final String SIZE_SEPARATOR = ","; /** * @hide @@ -137,4 +144,25 @@ public class AppWidgetXmlUtil { ATTR_PROVIDER_INHERITANCE, false); return info; } + + @NonNull + static String serializeWidgetSizes(@NonNull List<SizeF> sizes) { + return sizes.stream().map(SizeF::toString) + .collect(Collectors.joining(SIZE_SEPARATOR)); + } + + @Nullable + static ArrayList<SizeF> deserializeWidgetSizesStr(@Nullable String sizesStr) { + if (sizesStr == null || sizesStr.isEmpty()) { + return null; + } + try { + return Arrays.stream(sizesStr.split(SIZE_SEPARATOR)) + .map(SizeF::parseSizeF) + .collect(Collectors.toCollection(ArrayList::new)); + } catch (NumberFormatException e) { + Slog.e(TAG, "Error parsing widget sizes", e); + return null; + } + } } diff --git a/services/companion/java/com/android/server/companion/CompanionApplicationController.java b/services/companion/java/com/android/server/companion/CompanionApplicationController.java new file mode 100644 index 000000000000..0a4148535451 --- /dev/null +++ b/services/companion/java/com/android/server/companion/CompanionApplicationController.java @@ -0,0 +1,567 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.companion; + +import static android.companion.AssociationRequest.DEVICE_PROFILE_AUTOMOTIVE_PROJECTION; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.annotation.SuppressLint; +import android.annotation.UserIdInt; +import android.companion.AssociationInfo; +import android.companion.CompanionDeviceService; +import android.companion.DevicePresenceEvent; +import android.content.ComponentName; +import android.content.Context; +import android.hardware.power.Mode; +import android.os.Handler; +import android.os.ParcelUuid; +import android.os.PowerManagerInternal; +import android.util.Log; +import android.util.Slog; +import android.util.SparseArray; + +import com.android.internal.annotations.GuardedBy; +import com.android.internal.infra.PerUser; +import com.android.server.companion.association.AssociationStore; +import com.android.server.companion.presence.CompanionDevicePresenceMonitor; +import com.android.server.companion.presence.ObservableUuid; +import com.android.server.companion.presence.ObservableUuidStore; +import com.android.server.companion.utils.PackageUtils; + +import java.io.PrintWriter; +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** + * Manages communication with companion applications via + * {@link android.companion.ICompanionDeviceService} interface, including "connecting" (binding) to + * the services, maintaining the connection (the binding), and invoking callback methods such as + * {@link CompanionDeviceService#onDeviceAppeared(AssociationInfo)}, + * {@link CompanionDeviceService#onDeviceDisappeared(AssociationInfo)} and + * {@link CompanionDeviceService#onDevicePresenceEvent(DevicePresenceEvent)} in the + * application process. + * + * <p> + * The following is the list of the APIs provided by {@link CompanionApplicationController} (to be + * utilized by {@link CompanionDeviceManagerService}): + * <ul> + * <li> {@link #bindCompanionApplication(int, String, boolean)} + * <li> {@link #unbindCompanionApplication(int, String)} + * <li> {@link #notifyCompanionDevicePresenceEvent(AssociationInfo, int)} + * <li> {@link #isCompanionApplicationBound(int, String)} + * <li> {@link #isRebindingCompanionApplicationScheduled(int, String)} + * </ul> + * + * @see CompanionDeviceService + * @see android.companion.ICompanionDeviceService + * @see CompanionDeviceServiceConnector + */ +@SuppressLint("LongLogTag") +public class CompanionApplicationController { + static final boolean DEBUG = false; + private static final String TAG = "CDM_CompanionApplicationController"; + + private static final long REBIND_TIMEOUT = 10 * 1000; // 10 sec + + private final @NonNull Context mContext; + private final @NonNull AssociationStore mAssociationStore; + private final @NonNull ObservableUuidStore mObservableUuidStore; + private final @NonNull CompanionDevicePresenceMonitor mDevicePresenceMonitor; + private final @NonNull CompanionServicesRegister mCompanionServicesRegister; + + private final PowerManagerInternal mPowerManagerInternal; + + @GuardedBy("mBoundCompanionApplications") + private final @NonNull AndroidPackageMap<List<CompanionDeviceServiceConnector>> + mBoundCompanionApplications; + @GuardedBy("mScheduledForRebindingCompanionApplications") + private final @NonNull AndroidPackageMap<Boolean> mScheduledForRebindingCompanionApplications; + + CompanionApplicationController(Context context, AssociationStore associationStore, + ObservableUuidStore observableUuidStore, + CompanionDevicePresenceMonitor companionDevicePresenceMonitor, + PowerManagerInternal powerManagerInternal) { + mContext = context; + mAssociationStore = associationStore; + mObservableUuidStore = observableUuidStore; + mDevicePresenceMonitor = companionDevicePresenceMonitor; + mPowerManagerInternal = powerManagerInternal; + mCompanionServicesRegister = new CompanionServicesRegister(); + mBoundCompanionApplications = new AndroidPackageMap<>(); + mScheduledForRebindingCompanionApplications = new AndroidPackageMap<>(); + } + + void onPackagesChanged(@UserIdInt int userId) { + mCompanionServicesRegister.invalidate(userId); + } + + /** + * CDM binds to the companion app. + */ + public void bindCompanionApplication(@UserIdInt int userId, @NonNull String packageName, + boolean isSelfManaged) { + if (DEBUG) { + Log.i(TAG, "bind() u" + userId + "/" + packageName + + " isSelfManaged=" + isSelfManaged); + } + + final List<ComponentName> companionServices = + mCompanionServicesRegister.forPackage(userId, packageName); + if (companionServices.isEmpty()) { + Slog.w(TAG, "Can not bind companion applications u" + userId + "/" + packageName + ": " + + "eligible CompanionDeviceService not found.\n" + + "A CompanionDeviceService should declare an intent-filter for " + + "\"android.companion.CompanionDeviceService\" action and require " + + "\"android.permission.BIND_COMPANION_DEVICE_SERVICE\" permission."); + return; + } + + final List<CompanionDeviceServiceConnector> serviceConnectors = new ArrayList<>(); + synchronized (mBoundCompanionApplications) { + if (mBoundCompanionApplications.containsValueForPackage(userId, packageName)) { + if (DEBUG) Log.e(TAG, "u" + userId + "/" + packageName + " is ALREADY bound."); + return; + } + + for (int i = 0; i < companionServices.size(); i++) { + boolean isPrimary = i == 0; + serviceConnectors.add(CompanionDeviceServiceConnector.newInstance(mContext, userId, + companionServices.get(i), isSelfManaged, isPrimary)); + } + + mBoundCompanionApplications.setValueForPackage(userId, packageName, serviceConnectors); + } + + // Set listeners for both Primary and Secondary connectors. + for (CompanionDeviceServiceConnector serviceConnector : serviceConnectors) { + serviceConnector.setListener(this::onBinderDied); + } + + // Now "bind" all the connectors: the primary one and the rest of them. + for (CompanionDeviceServiceConnector serviceConnector : serviceConnectors) { + serviceConnector.connect(); + } + } + + /** + * CDM unbinds the companion app. + */ + public void unbindCompanionApplication(@UserIdInt int userId, @NonNull String packageName) { + if (DEBUG) Log.i(TAG, "unbind() u" + userId + "/" + packageName); + + final List<CompanionDeviceServiceConnector> serviceConnectors; + + synchronized (mBoundCompanionApplications) { + serviceConnectors = mBoundCompanionApplications.removePackage(userId, packageName); + } + + synchronized (mScheduledForRebindingCompanionApplications) { + mScheduledForRebindingCompanionApplications.removePackage(userId, packageName); + } + + if (serviceConnectors == null) { + if (DEBUG) { + Log.e(TAG, "unbindCompanionApplication(): " + + "u" + userId + "/" + packageName + " is NOT bound"); + Log.d(TAG, "Stacktrace", new Throwable()); + } + return; + } + + for (CompanionDeviceServiceConnector serviceConnector : serviceConnectors) { + serviceConnector.postUnbind(); + } + } + + /** + * @return whether the companion application is bound now. + */ + public boolean isCompanionApplicationBound(@UserIdInt int userId, @NonNull String packageName) { + synchronized (mBoundCompanionApplications) { + return mBoundCompanionApplications.containsValueForPackage(userId, packageName); + } + } + + private void scheduleRebinding(@UserIdInt int userId, @NonNull String packageName, + CompanionDeviceServiceConnector serviceConnector) { + Slog.i(TAG, "scheduleRebinding() " + userId + "/" + packageName); + + if (isRebindingCompanionApplicationScheduled(userId, packageName)) { + if (DEBUG) { + Log.i(TAG, "CompanionApplication rebinding has been scheduled, skipping " + + serviceConnector.getComponentName()); + } + return; + } + + if (serviceConnector.isPrimary()) { + synchronized (mScheduledForRebindingCompanionApplications) { + mScheduledForRebindingCompanionApplications.setValueForPackage( + userId, packageName, true); + } + } + + // Rebinding in 10 seconds. + Handler.getMain().postDelayed(() -> + onRebindingCompanionApplicationTimeout(userId, packageName, serviceConnector), + REBIND_TIMEOUT); + } + + private boolean isRebindingCompanionApplicationScheduled( + @UserIdInt int userId, @NonNull String packageName) { + synchronized (mScheduledForRebindingCompanionApplications) { + return mScheduledForRebindingCompanionApplications.containsValueForPackage( + userId, packageName); + } + } + + private void onRebindingCompanionApplicationTimeout( + @UserIdInt int userId, @NonNull String packageName, + @NonNull CompanionDeviceServiceConnector serviceConnector) { + // Re-mark the application is bound. + if (serviceConnector.isPrimary()) { + synchronized (mBoundCompanionApplications) { + if (!mBoundCompanionApplications.containsValueForPackage(userId, packageName)) { + List<CompanionDeviceServiceConnector> serviceConnectors = + Collections.singletonList(serviceConnector); + mBoundCompanionApplications.setValueForPackage(userId, packageName, + serviceConnectors); + } + } + + synchronized (mScheduledForRebindingCompanionApplications) { + mScheduledForRebindingCompanionApplications.removePackage(userId, packageName); + } + } + + serviceConnector.connect(); + } + + /** + * Notify the app that the device appeared. + * + * @deprecated use {@link #notifyCompanionDevicePresenceEvent(AssociationInfo, int)} instead + */ + @Deprecated + public void notifyCompanionApplicationDeviceAppeared(AssociationInfo association) { + final int userId = association.getUserId(); + final String packageName = association.getPackageName(); + + Slog.i(TAG, "notifyDevice_Appeared() id=" + association.getId() + " u" + userId + + "/" + packageName); + + final CompanionDeviceServiceConnector primaryServiceConnector = + getPrimaryServiceConnector(userId, packageName); + if (primaryServiceConnector == null) { + Slog.e(TAG, "notify_CompanionApplicationDevice_Appeared(): " + + "u" + userId + "/" + packageName + " is NOT bound."); + Slog.e(TAG, "Stacktrace", new Throwable()); + return; + } + + Log.i(TAG, "Calling onDeviceAppeared to userId=[" + userId + "] package=[" + + packageName + "] associationId=[" + association.getId() + "]"); + + primaryServiceConnector.postOnDeviceAppeared(association); + } + + /** + * Notify the app that the device disappeared. + * + * @deprecated use {@link #notifyCompanionDevicePresenceEvent(AssociationInfo, int)} instead + */ + @Deprecated + public void notifyCompanionApplicationDeviceDisappeared(AssociationInfo association) { + final int userId = association.getUserId(); + final String packageName = association.getPackageName(); + + Slog.i(TAG, "notifyDevice_Disappeared() id=" + association.getId() + " u" + userId + + "/" + packageName); + + final CompanionDeviceServiceConnector primaryServiceConnector = + getPrimaryServiceConnector(userId, packageName); + if (primaryServiceConnector == null) { + Slog.e(TAG, "notify_CompanionApplicationDevice_Disappeared(): " + + "u" + userId + "/" + packageName + " is NOT bound."); + Slog.e(TAG, "Stacktrace", new Throwable()); + return; + } + + Log.i(TAG, "Calling onDeviceDisappeared to userId=[" + userId + "] package=[" + + packageName + "] associationId=[" + association.getId() + "]"); + + primaryServiceConnector.postOnDeviceDisappeared(association); + } + + /** + * Notify the app that the device appeared. + */ + public void notifyCompanionDevicePresenceEvent(AssociationInfo association, int event) { + final int userId = association.getUserId(); + final String packageName = association.getPackageName(); + final CompanionDeviceServiceConnector primaryServiceConnector = + getPrimaryServiceConnector(userId, packageName); + final DevicePresenceEvent devicePresenceEvent = + new DevicePresenceEvent(association.getId(), event, null); + + if (primaryServiceConnector == null) { + Slog.e(TAG, "notifyCompanionApplicationDevicePresenceEvent(): " + + "u" + userId + "/" + packageName + + " event=[ " + event + " ] is NOT bound."); + Slog.e(TAG, "Stacktrace", new Throwable()); + return; + } + + Slog.i(TAG, "Calling onDevicePresenceEvent() to userId=[" + userId + "] package=[" + + packageName + "] associationId=[" + association.getId() + + "] event=[" + event + "]"); + + primaryServiceConnector.postOnDevicePresenceEvent(devicePresenceEvent); + } + + /** + * Notify the app that the device disappeared. + */ + public void notifyUuidDevicePresenceEvent(ObservableUuid uuid, int event) { + final int userId = uuid.getUserId(); + final ParcelUuid parcelUuid = uuid.getUuid(); + final String packageName = uuid.getPackageName(); + final CompanionDeviceServiceConnector primaryServiceConnector = + getPrimaryServiceConnector(userId, packageName); + final DevicePresenceEvent devicePresenceEvent = + new DevicePresenceEvent(DevicePresenceEvent.NO_ASSOCIATION, event, parcelUuid); + + if (primaryServiceConnector == null) { + Slog.e(TAG, "notifyApplicationDevicePresenceChanged(): " + + "u" + userId + "/" + packageName + + " event=[ " + event + " ] is NOT bound."); + Slog.e(TAG, "Stacktrace", new Throwable()); + return; + } + + Slog.i(TAG, "Calling onDevicePresenceEvent() to userId=[" + userId + "] package=[" + + packageName + "]" + "event= [" + event + "]"); + + primaryServiceConnector.postOnDevicePresenceEvent(devicePresenceEvent); + } + + void dump(@NonNull PrintWriter out) { + out.append("Companion Device Application Controller: \n"); + + synchronized (mBoundCompanionApplications) { + out.append(" Bound Companion Applications: "); + if (mBoundCompanionApplications.size() == 0) { + out.append("<empty>\n"); + } else { + out.append("\n"); + mBoundCompanionApplications.dump(out); + } + } + + out.append(" Companion Applications Scheduled For Rebinding: "); + if (mScheduledForRebindingCompanionApplications.size() == 0) { + out.append("<empty>\n"); + } else { + out.append("\n"); + mScheduledForRebindingCompanionApplications.dump(out); + } + } + + /** + * Rebinding for Self-Managed secondary services OR Non-Self-Managed services. + */ + private void onBinderDied(@UserIdInt int userId, @NonNull String packageName, + @NonNull CompanionDeviceServiceConnector serviceConnector) { + + boolean isPrimary = serviceConnector.isPrimary(); + Slog.i(TAG, "onBinderDied() u" + userId + "/" + packageName + " isPrimary: " + isPrimary); + + // First, disable hint mode for Auto profile and mark not BOUND for primary service ONLY. + if (isPrimary) { + final List<AssociationInfo> associations = + mAssociationStore.getActiveAssociationsByPackage(userId, packageName); + + for (AssociationInfo association : associations) { + final String deviceProfile = association.getDeviceProfile(); + if (DEVICE_PROFILE_AUTOMOTIVE_PROJECTION.equals(deviceProfile)) { + Slog.i(TAG, "Disable hint mode for device profile: " + deviceProfile); + mPowerManagerInternal.setPowerMode(Mode.AUTOMOTIVE_PROJECTION, false); + break; + } + } + + synchronized (mBoundCompanionApplications) { + mBoundCompanionApplications.removePackage(userId, packageName); + } + } + + // Second: schedule rebinding if needed. + final boolean shouldScheduleRebind = shouldScheduleRebind(userId, packageName, isPrimary); + + if (shouldScheduleRebind) { + scheduleRebinding(userId, packageName, serviceConnector); + } + } + + private @Nullable CompanionDeviceServiceConnector getPrimaryServiceConnector( + @UserIdInt int userId, @NonNull String packageName) { + final List<CompanionDeviceServiceConnector> connectors; + synchronized (mBoundCompanionApplications) { + connectors = mBoundCompanionApplications.getValueForPackage(userId, packageName); + } + return connectors != null ? connectors.get(0) : null; + } + + private boolean shouldScheduleRebind(int userId, String packageName, boolean isPrimary) { + // Make sure do not schedule rebind for the case ServiceConnector still gets callback after + // app is uninstalled. + boolean stillAssociated = false; + // Make sure to clean up the state for all the associations + // that associate with this package. + boolean shouldScheduleRebind = false; + boolean shouldScheduleRebindForUuid = false; + final List<ObservableUuid> uuids = + mObservableUuidStore.getObservableUuidsForPackage(userId, packageName); + + for (AssociationInfo ai : + mAssociationStore.getActiveAssociationsByPackage(userId, packageName)) { + final int associationId = ai.getId(); + stillAssociated = true; + if (ai.isSelfManaged()) { + // Do not rebind if primary one is died for selfManaged application. + if (isPrimary + && mDevicePresenceMonitor.isDevicePresent(associationId)) { + mDevicePresenceMonitor.onSelfManagedDeviceReporterBinderDied(associationId); + shouldScheduleRebind = false; + } + // Do not rebind if both primary and secondary services are died for + // selfManaged application. + shouldScheduleRebind = isCompanionApplicationBound(userId, packageName); + } else if (ai.isNotifyOnDeviceNearby()) { + // Always rebind for non-selfManaged devices. + shouldScheduleRebind = true; + } + } + + for (ObservableUuid uuid : uuids) { + if (mDevicePresenceMonitor.isDeviceUuidPresent(uuid.getUuid())) { + shouldScheduleRebindForUuid = true; + break; + } + } + + return (stillAssociated && shouldScheduleRebind) || shouldScheduleRebindForUuid; + } + + private class CompanionServicesRegister extends PerUser<Map<String, List<ComponentName>>> { + @Override + public synchronized @NonNull Map<String, List<ComponentName>> forUser( + @UserIdInt int userId) { + return super.forUser(userId); + } + + synchronized @NonNull List<ComponentName> forPackage( + @UserIdInt int userId, @NonNull String packageName) { + return forUser(userId).getOrDefault(packageName, Collections.emptyList()); + } + + synchronized void invalidate(@UserIdInt int userId) { + remove(userId); + } + + @Override + protected final @NonNull Map<String, List<ComponentName>> create(@UserIdInt int userId) { + return PackageUtils.getCompanionServicesForUser(mContext, userId); + } + } + + /** + * Associates an Android package (defined by userId + packageName) with a value of type T. + */ + private static class AndroidPackageMap<T> extends SparseArray<Map<String, T>> { + + void setValueForPackage( + @UserIdInt int userId, @NonNull String packageName, @NonNull T value) { + Map<String, T> forUser = get(userId); + if (forUser == null) { + forUser = /* Map<String, T> */ new HashMap(); + put(userId, forUser); + } + + forUser.put(packageName, value); + } + + boolean containsValueForPackage(@UserIdInt int userId, @NonNull String packageName) { + final Map<String, ?> forUser = get(userId); + return forUser != null && forUser.containsKey(packageName); + } + + T getValueForPackage(@UserIdInt int userId, @NonNull String packageName) { + final Map<String, T> forUser = get(userId); + return forUser != null ? forUser.get(packageName) : null; + } + + T removePackage(@UserIdInt int userId, @NonNull String packageName) { + final Map<String, T> forUser = get(userId); + if (forUser == null) return null; + return forUser.remove(packageName); + } + + void dump() { + if (size() == 0) { + Log.d(TAG, "<empty>"); + return; + } + + for (int i = 0; i < size(); i++) { + final int userId = keyAt(i); + final Map<String, T> forUser = get(userId); + if (forUser.isEmpty()) { + Log.d(TAG, "u" + userId + ": <empty>"); + } + + for (Map.Entry<String, T> packageValue : forUser.entrySet()) { + final String packageName = packageValue.getKey(); + final T value = packageValue.getValue(); + Log.d(TAG, "u" + userId + "\\" + packageName + " -> " + value); + } + } + } + + private void dump(@NonNull PrintWriter out) { + for (int i = 0; i < size(); i++) { + final int userId = keyAt(i); + final Map<String, T> forUser = get(userId); + if (forUser.isEmpty()) { + out.append(" u").append(String.valueOf(userId)).append(": <empty>\n"); + } + + for (Map.Entry<String, T> packageValue : forUser.entrySet()) { + final String packageName = packageValue.getKey(); + final T value = packageValue.getValue(); + out.append(" u").append(String.valueOf(userId)).append("\\") + .append(packageName).append(" -> ") + .append(value.toString()).append('\n'); + } + } + } + } +} diff --git a/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java b/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java index edf9fd13d51f..712162b2d3b5 100644 --- a/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java +++ b/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java @@ -20,10 +20,15 @@ package com.android.server.companion; import static android.Manifest.permission.ASSOCIATE_COMPANION_DEVICES; import static android.Manifest.permission.DELIVER_COMPANION_MESSAGES; import static android.Manifest.permission.MANAGE_COMPANION_DEVICES; -import static android.Manifest.permission.REQUEST_COMPANION_SELF_MANAGED; import static android.Manifest.permission.REQUEST_OBSERVE_COMPANION_DEVICE_PRESENCE; import static android.Manifest.permission.USE_COMPANION_TRANSPORTS; +import static android.companion.AssociationRequest.DEVICE_PROFILE_AUTOMOTIVE_PROJECTION; +import static android.companion.DevicePresenceEvent.EVENT_BLE_APPEARED; +import static android.companion.DevicePresenceEvent.EVENT_BLE_DISAPPEARED; import static android.companion.DevicePresenceEvent.EVENT_BT_CONNECTED; +import static android.companion.DevicePresenceEvent.EVENT_BT_DISCONNECTED; +import static android.companion.DevicePresenceEvent.EVENT_SELF_MANAGED_APPEARED; +import static android.companion.DevicePresenceEvent.EVENT_SELF_MANAGED_DISAPPEARED; import static android.content.pm.PackageManager.CERT_INPUT_SHA256; import static android.content.pm.PackageManager.PERMISSION_GRANTED; import static android.os.Process.SYSTEM_UID; @@ -37,10 +42,13 @@ import static com.android.server.companion.utils.PackageUtils.getPackageInfo; import static com.android.server.companion.utils.PackageUtils.isRestrictedSettingsAllowed; import static com.android.server.companion.utils.PermissionsUtils.checkCallerCanManageCompanionDevice; import static com.android.server.companion.utils.PermissionsUtils.enforceCallerCanManageAssociationsForPackage; +import static com.android.server.companion.utils.PermissionsUtils.enforceCallerCanObservingDevicePresenceByUuid; import static com.android.server.companion.utils.PermissionsUtils.enforceCallerIsSystemOr; import static com.android.server.companion.utils.PermissionsUtils.enforceCallerIsSystemOrCanInteractWithUserId; +import static com.android.server.companion.utils.PermissionsUtils.sanitizeWithCallerChecks; import static java.util.Objects.requireNonNull; +import static java.util.concurrent.TimeUnit.DAYS; import static java.util.concurrent.TimeUnit.MINUTES; import android.annotation.EnforcePermission; @@ -56,6 +64,7 @@ import android.app.PendingIntent; import android.bluetooth.BluetoothDevice; import android.companion.AssociationInfo; import android.companion.AssociationRequest; +import android.companion.DeviceNotAssociatedException; import android.companion.IAssociationRequestCallback; import android.companion.ICompanionDeviceManager; import android.companion.IOnAssociationsChangedListener; @@ -70,6 +79,7 @@ import android.content.SharedPreferences; import android.content.pm.PackageInfo; import android.content.pm.PackageManager; import android.content.pm.PackageManagerInternal; +import android.hardware.power.Mode; import android.net.MacAddress; import android.net.NetworkPolicyManager; import android.os.Binder; @@ -81,6 +91,7 @@ import android.os.PowerExemptionManager; import android.os.PowerManagerInternal; import android.os.RemoteException; import android.os.ServiceManager; +import android.os.SystemProperties; import android.os.UserHandle; import android.os.UserManager; import android.util.ArraySet; @@ -107,8 +118,7 @@ import com.android.server.companion.datatransfer.SystemDataTransferRequestStore; import com.android.server.companion.datatransfer.contextsync.CrossDeviceCall; import com.android.server.companion.datatransfer.contextsync.CrossDeviceSyncController; import com.android.server.companion.datatransfer.contextsync.CrossDeviceSyncControllerCallback; -import com.android.server.companion.presence.CompanionAppBinder; -import com.android.server.companion.presence.DevicePresenceProcessor; +import com.android.server.companion.presence.CompanionDevicePresenceMonitor; import com.android.server.companion.presence.ObservableUuid; import com.android.server.companion.presence.ObservableUuidStore; import com.android.server.companion.transport.CompanionTransportManager; @@ -121,7 +131,10 @@ import java.io.PrintWriter; import java.util.Arrays; import java.util.Collection; import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; import java.util.List; +import java.util.Map; import java.util.Set; @SuppressLint("LongLogTag") @@ -133,6 +146,10 @@ public class CompanionDeviceManagerService extends SystemService { private static final String PREF_FILE_NAME = "companion_device_preferences.xml"; private static final String PREF_KEY_AUTO_REVOKE_GRANTS_DONE = "auto_revoke_grants_done"; + private static final String SYS_PROP_DEBUG_REMOVAL_TIME_WINDOW = + "debug.cdm.cdmservice.removal_time_window"; + + private static final long ASSOCIATION_REMOVAL_TIME_WINDOW_DEFAULT = DAYS.toMillis(90); private static final int MAX_CN_LENGTH = 500; private final ActivityTaskManagerInternal mAtmInternal; @@ -148,8 +165,8 @@ public class CompanionDeviceManagerService extends SystemService { private final AssociationRequestsProcessor mAssociationRequestsProcessor; private final SystemDataTransferProcessor mSystemDataTransferProcessor; private final BackupRestoreProcessor mBackupRestoreProcessor; - private final DevicePresenceProcessor mDevicePresenceProcessor; - private final CompanionAppBinder mCompanionAppBinder; + private final CompanionDevicePresenceMonitor mDevicePresenceMonitor; + private final CompanionApplicationController mCompanionAppController; private final CompanionTransportManager mTransportManager; private final DisassociationProcessor mDisassociationProcessor; private final CrossDeviceSyncController mCrossDeviceSyncController; @@ -168,7 +185,7 @@ public class CompanionDeviceManagerService extends SystemService { mPowerManagerInternal = LocalServices.getService(PowerManagerInternal.class); final AssociationDiskStore associationDiskStore = new AssociationDiskStore(); - mAssociationStore = new AssociationStore(context, userManager, associationDiskStore); + mAssociationStore = new AssociationStore(userManager, associationDiskStore); mSystemDataTransferRequestStore = new SystemDataTransferRequestStore(); mObservableUuidStore = new ObservableUuidStore(); @@ -179,17 +196,18 @@ public class CompanionDeviceManagerService extends SystemService { mAssociationStore, associationDiskStore, mSystemDataTransferRequestStore, mAssociationRequestsProcessor); - mCompanionAppBinder = new CompanionAppBinder(context); + mDevicePresenceMonitor = new CompanionDevicePresenceMonitor(userManager, + mAssociationStore, mObservableUuidStore, mDevicePresenceCallback); - mDevicePresenceProcessor = new DevicePresenceProcessor(context, - mCompanionAppBinder, userManager, mAssociationStore, mObservableUuidStore, + mCompanionAppController = new CompanionApplicationController( + context, mAssociationStore, mObservableUuidStore, mDevicePresenceMonitor, mPowerManagerInternal); mTransportManager = new CompanionTransportManager(context, mAssociationStore); mDisassociationProcessor = new DisassociationProcessor(context, activityManager, - mAssociationStore, mPackageManagerInternal, mDevicePresenceProcessor, - mCompanionAppBinder, mSystemDataTransferRequestStore, mTransportManager); + mAssociationStore, mPackageManagerInternal, mDevicePresenceMonitor, + mCompanionAppController, mSystemDataTransferRequestStore, mTransportManager); mSystemDataTransferProcessor = new SystemDataTransferProcessor(this, mPackageManagerInternal, mAssociationStore, @@ -224,7 +242,7 @@ public class CompanionDeviceManagerService extends SystemService { // delays (even in case of the Main Thread). It may be fine overall, but would require // updating the tests (adding a delay there). mPackageMonitor.register(context, FgThread.get().getLooper(), UserHandle.ALL, true); - mDevicePresenceProcessor.init(context); + mDevicePresenceMonitor.init(context); } else if (phase == PHASE_BOOT_COMPLETED) { // Run the Inactive Association Removal job service daily. InactiveAssociationsRemovalService.schedule(getContext()); @@ -253,7 +271,7 @@ public class CompanionDeviceManagerService extends SystemService { // Notify and bind the app after the phone is unlocked. final int userId = user.getUserIdentifier(); final Set<BluetoothDevice> blueToothDevices = - mDevicePresenceProcessor.getPendingConnectedDevices().get(userId); + mDevicePresenceMonitor.getPendingConnectedDevices().get(userId); final List<ObservableUuid> observableUuids = mObservableUuidStore.getObservableUuidsForUser(userId); @@ -269,14 +287,14 @@ public class CompanionDeviceManagerService extends SystemService { mAssociationStore.getActiveAssociationsByAddress( bluetoothDevice.getAddress())) { Slog.i(TAG, "onUserUnlocked, device id( " + ai.getId() + " ) is connected"); - mDevicePresenceProcessor.onBluetoothCompanionDeviceConnected(ai.getId()); + mDevicePresenceMonitor.onBluetoothCompanionDeviceConnected(ai.getId()); } for (ObservableUuid observableUuid : observableUuids) { if (deviceUuids.contains(observableUuid.getUuid())) { Slog.i(TAG, "onUserUnlocked, UUID( " + observableUuid.getUuid() + " ) is connected"); - mDevicePresenceProcessor.onDevicePresenceEventByUuid( + mDevicePresenceMonitor.onDevicePresenceEventByUuid( observableUuid, EVENT_BT_CONNECTED); } } @@ -284,6 +302,181 @@ public class CompanionDeviceManagerService extends SystemService { } } + @NonNull + AssociationInfo getAssociationWithCallerChecks( + @UserIdInt int userId, @NonNull String packageName, @NonNull String macAddress) { + AssociationInfo association = mAssociationStore.getFirstAssociationByAddress( + userId, packageName, macAddress); + association = sanitizeWithCallerChecks(getContext(), association); + if (association != null) { + return association; + } else { + throw new IllegalArgumentException("Association does not exist " + + "or the caller does not have permissions to manage it " + + "(ie. it belongs to a different package or a different user)."); + } + } + + @NonNull + AssociationInfo getAssociationWithCallerChecks(int associationId) { + AssociationInfo association = mAssociationStore.getAssociationById(associationId); + association = sanitizeWithCallerChecks(getContext(), association); + if (association != null) { + return association; + } else { + throw new IllegalArgumentException("Association does not exist " + + "or the caller does not have permissions to manage it " + + "(ie. it belongs to a different package or a different user)."); + } + } + + private void onDeviceAppearedInternal(int associationId) { + if (DEBUG) Log.i(TAG, "onDevice_Appeared_Internal() id=" + associationId); + + final AssociationInfo association = mAssociationStore.getAssociationById(associationId); + if (DEBUG) Log.d(TAG, " association=" + association); + + if (!association.shouldBindWhenPresent()) return; + + bindApplicationIfNeeded(association); + + mCompanionAppController.notifyCompanionApplicationDeviceAppeared(association); + } + + private void onDeviceDisappearedInternal(int associationId) { + if (DEBUG) Log.i(TAG, "onDevice_Disappeared_Internal() id=" + associationId); + + final AssociationInfo association = mAssociationStore.getAssociationById(associationId); + if (DEBUG) Log.d(TAG, " association=" + association); + + final int userId = association.getUserId(); + final String packageName = association.getPackageName(); + + if (!mCompanionAppController.isCompanionApplicationBound(userId, packageName)) { + if (DEBUG) Log.w(TAG, "u" + userId + "\\" + packageName + " is NOT bound"); + return; + } + + if (association.shouldBindWhenPresent()) { + mCompanionAppController.notifyCompanionApplicationDeviceDisappeared(association); + } + } + + private void onDevicePresenceEventInternal(int associationId, int event) { + Slog.i(TAG, "onDevicePresenceEventInternal() id=" + associationId + " event= " + event); + final AssociationInfo association = mAssociationStore.getAssociationById(associationId); + final String packageName = association.getPackageName(); + final int userId = association.getUserId(); + switch (event) { + case EVENT_BLE_APPEARED: + case EVENT_BT_CONNECTED: + case EVENT_SELF_MANAGED_APPEARED: + if (!association.shouldBindWhenPresent()) return; + + bindApplicationIfNeeded(association); + + mCompanionAppController.notifyCompanionDevicePresenceEvent( + association, event); + break; + case EVENT_BLE_DISAPPEARED: + case EVENT_BT_DISCONNECTED: + case EVENT_SELF_MANAGED_DISAPPEARED: + if (!mCompanionAppController.isCompanionApplicationBound(userId, packageName)) { + if (DEBUG) Log.w(TAG, "u" + userId + "\\" + packageName + " is NOT bound"); + return; + } + if (association.shouldBindWhenPresent()) { + mCompanionAppController.notifyCompanionDevicePresenceEvent( + association, event); + } + // Check if there are other devices associated to the app that are present. + if (shouldBindPackage(userId, packageName)) return; + mCompanionAppController.unbindCompanionApplication(userId, packageName); + break; + default: + Slog.e(TAG, "Event: " + event + "is not supported"); + break; + } + } + + private void onDevicePresenceEventByUuidInternal(ObservableUuid uuid, int event) { + Slog.i(TAG, "onDevicePresenceEventByUuidInternal() id=" + uuid.getUuid() + + "for package=" + uuid.getPackageName() + " event=" + event); + final String packageName = uuid.getPackageName(); + final int userId = uuid.getUserId(); + + switch (event) { + case EVENT_BT_CONNECTED: + if (!mCompanionAppController.isCompanionApplicationBound(userId, packageName)) { + mCompanionAppController.bindCompanionApplication( + userId, packageName, /*bindImportant*/ false); + + } else if (DEBUG) { + Log.i(TAG, "u" + userId + "\\" + packageName + " is already bound"); + } + + mCompanionAppController.notifyUuidDevicePresenceEvent(uuid, event); + + break; + case EVENT_BT_DISCONNECTED: + if (!mCompanionAppController.isCompanionApplicationBound(userId, packageName)) { + if (DEBUG) Log.w(TAG, "u" + userId + "\\" + packageName + " is NOT bound"); + return; + } + + mCompanionAppController.notifyUuidDevicePresenceEvent(uuid, event); + // Check if there are other devices associated to the app or the UUID to be + // observed are present. + if (shouldBindPackage(userId, packageName)) return; + + mCompanionAppController.unbindCompanionApplication(userId, packageName); + + break; + default: + Slog.e(TAG, "Event: " + event + "is not supported"); + break; + } + } + + private void bindApplicationIfNeeded(AssociationInfo association) { + final String packageName = association.getPackageName(); + final int userId = association.getUserId(); + // Set bindImportant to true when the association is self-managed to avoid the target + // service being killed. + final boolean bindImportant = association.isSelfManaged(); + if (!mCompanionAppController.isCompanionApplicationBound(userId, packageName)) { + mCompanionAppController.bindCompanionApplication( + userId, packageName, bindImportant); + } else if (DEBUG) { + Log.i(TAG, "u" + userId + "\\" + packageName + " is already bound"); + } + } + + /** + * @return whether the package should be bound (i.e. at least one of the devices associated with + * the package is currently present OR the UUID to be observed by this package is + * currently present). + */ + private boolean shouldBindPackage(@UserIdInt int userId, @NonNull String packageName) { + final List<AssociationInfo> packageAssociations = + mAssociationStore.getActiveAssociationsByPackage(userId, packageName); + final List<ObservableUuid> observableUuids = + mObservableUuidStore.getObservableUuidsForPackage(userId, packageName); + + for (AssociationInfo association : packageAssociations) { + if (!association.shouldBindWhenPresent()) continue; + if (mDevicePresenceMonitor.isDevicePresent(association.getId())) return true; + } + + for (ObservableUuid uuid : observableUuids) { + if (mDevicePresenceMonitor.isDeviceUuidPresent(uuid.getUuid())) { + return true; + } + } + + return false; + } + private void onPackageRemoveOrDataClearedInternal( @UserIdInt int userId, @NonNull String packageName) { if (DEBUG) { @@ -309,7 +502,7 @@ public class CompanionDeviceManagerService extends SystemService { mObservableUuidStore.removeObservableUuid(userId, uuid.getUuid(), packageName); } - mCompanionAppBinder.onPackagesChanged(userId); + mCompanionAppController.onPackagesChanged(userId); } private void onPackageModifiedInternal(@UserIdInt int userId, @NonNull String packageName) { @@ -322,15 +515,34 @@ public class CompanionDeviceManagerService extends SystemService { association.getPackageName()); } - mCompanionAppBinder.onPackagesChanged(userId); + mCompanionAppController.onPackagesChanged(userId); } private void onPackageAddedInternal(@UserIdInt int userId, @NonNull String packageName) { mBackupRestoreProcessor.restorePendingAssociations(userId, packageName); } + // Revoke associations if the selfManaged companion device does not connect for 3 months. void removeInactiveSelfManagedAssociations() { - mDisassociationProcessor.removeIdleSelfManagedAssociations(); + final long currentTime = System.currentTimeMillis(); + long removalWindow = SystemProperties.getLong(SYS_PROP_DEBUG_REMOVAL_TIME_WINDOW, -1); + if (removalWindow <= 0) { + // 0 or negative values indicate that the sysprop was never set or should be ignored. + removalWindow = ASSOCIATION_REMOVAL_TIME_WINDOW_DEFAULT; + } + + for (AssociationInfo association : mAssociationStore.getAssociations()) { + if (!association.isSelfManaged()) continue; + + final boolean isInactive = + currentTime - association.getLastTimeConnectedMs() >= removalWindow; + if (!isInactive) continue; + + final int id = association.getId(); + + Slog.i(TAG, "Removing inactive self-managed association id=" + id); + mDisassociationProcessor.disassociate(id); + } } public class CompanionDeviceManagerImpl extends ICompanionDeviceManager.Stub { @@ -467,15 +679,24 @@ public class CompanionDeviceManagerService extends SystemService { @Deprecated @Override public void legacyDisassociate(String deviceMacAddress, String packageName, int userId) { + Log.i(TAG, "legacyDisassociate() pkg=u" + userId + "/" + packageName + + ", macAddress=" + deviceMacAddress); + requireNonNull(deviceMacAddress); requireNonNull(packageName); - mDisassociationProcessor.disassociate(userId, packageName, deviceMacAddress); + final AssociationInfo association = + getAssociationWithCallerChecks(userId, packageName, deviceMacAddress); + mDisassociationProcessor.disassociate(association.getId()); } @Override public void disassociate(int associationId) { - mDisassociationProcessor.disassociate(associationId); + Slog.i(TAG, "disassociate() associationId=" + associationId); + + final AssociationInfo association = + getAssociationWithCallerChecks(associationId); + mDisassociationProcessor.disassociate(association.getId()); } @Override @@ -537,25 +758,21 @@ public class CompanionDeviceManagerService extends SystemService { } @Override - @Deprecated @EnforcePermission(REQUEST_OBSERVE_COMPANION_DEVICE_PRESENCE) - public void legacyStartObservingDevicePresence(String deviceAddress, String callingPackage, - int userId) throws RemoteException { - legacyStartObservingDevicePresence_enforcePermission(); - - mDevicePresenceProcessor.startObservingDevicePresence(userId, callingPackage, - deviceAddress); + public void registerDevicePresenceListenerService(String deviceAddress, + String callingPackage, int userId) throws RemoteException { + registerDevicePresenceListenerService_enforcePermission(); + // TODO: take the userId into account. + registerDevicePresenceListenerActive(callingPackage, deviceAddress, true); } @Override - @Deprecated @EnforcePermission(REQUEST_OBSERVE_COMPANION_DEVICE_PRESENCE) - public void legacyStopObservingDevicePresence(String deviceAddress, String callingPackage, - int userId) throws RemoteException { - legacyStopObservingDevicePresence_enforcePermission(); - - mDevicePresenceProcessor.stopObservingDevicePresence(userId, callingPackage, - deviceAddress); + public void unregisterDevicePresenceListenerService(String deviceAddress, + String callingPackage, int userId) throws RemoteException { + unregisterDevicePresenceListenerService_enforcePermission(); + // TODO: take the userId into account. + registerDevicePresenceListenerActive(callingPackage, deviceAddress, false); } @Override @@ -563,8 +780,7 @@ public class CompanionDeviceManagerService extends SystemService { public void startObservingDevicePresence(ObservingDevicePresenceRequest request, String packageName, int userId) { startObservingDevicePresence_enforcePermission(); - - mDevicePresenceProcessor.startObservingDevicePresence(request, packageName, userId); + registerDevicePresenceListener(request, packageName, userId, /* active */ true); } @Override @@ -572,8 +788,80 @@ public class CompanionDeviceManagerService extends SystemService { public void stopObservingDevicePresence(ObservingDevicePresenceRequest request, String packageName, int userId) { stopObservingDevicePresence_enforcePermission(); + registerDevicePresenceListener(request, packageName, userId, /* active */ false); + } + + private void registerDevicePresenceListener(ObservingDevicePresenceRequest request, + String packageName, int userId, boolean active) { + enforceUsesCompanionDeviceFeature(getContext(), userId, packageName); + enforceCallerIsSystemOr(userId, packageName); + + final int associationId = request.getAssociationId(); + final AssociationInfo associationInfo = mAssociationStore.getAssociationById( + associationId); + final ParcelUuid uuid = request.getUuid(); + + if (uuid != null) { + enforceCallerCanObservingDevicePresenceByUuid(getContext()); + if (active) { + startObservingDevicePresenceByUuid(uuid, packageName, userId); + } else { + stopObservingDevicePresenceByUuid(uuid, packageName, userId); + } + } else if (associationInfo == null) { + throw new IllegalArgumentException("App " + packageName + + " is not associated with device " + request.getAssociationId() + + " for user " + userId); + } else { + processDevicePresenceListener( + associationInfo, userId, packageName, active); + } + } + + private void startObservingDevicePresenceByUuid(ParcelUuid uuid, String packageName, + int userId) { + final List<ObservableUuid> observableUuids = + mObservableUuidStore.getObservableUuidsForPackage(userId, packageName); + + for (ObservableUuid observableUuid : observableUuids) { + if (observableUuid.getUuid().equals(uuid)) { + Slog.i(TAG, "The uuid: " + uuid + " for package:" + packageName + + "has been already scheduled for observing"); + return; + } + } + + final ObservableUuid observableUuid = new ObservableUuid(userId, uuid, + packageName, System.currentTimeMillis()); + + mObservableUuidStore.writeObservableUuid(userId, observableUuid); + } + + private void stopObservingDevicePresenceByUuid(ParcelUuid uuid, String packageName, + int userId) { + final List<ObservableUuid> uuidsTobeObserved = + mObservableUuidStore.getObservableUuidsForPackage(userId, packageName); + boolean isScheduledObserving = false; + + for (ObservableUuid observableUuid : uuidsTobeObserved) { + if (observableUuid.getUuid().equals(uuid)) { + isScheduledObserving = true; + break; + } + } - mDevicePresenceProcessor.stopObservingDevicePresence(request, packageName, userId); + if (!isScheduledObserving) { + Slog.i(TAG, "The uuid: " + uuid.toString() + " for package:" + packageName + + "has NOT been scheduled for observing yet"); + return; + } + + mObservableUuidStore.removeObservableUuid(userId, uuid, packageName); + mDevicePresenceMonitor.removeCurrentConnectedUuidDevice(uuid); + + if (!shouldBindPackage(userId, packageName)) { + mCompanionAppController.unbindCompanionApplication(userId, packageName); + } } @Override @@ -586,7 +874,8 @@ public class CompanionDeviceManagerService extends SystemService { @Override public boolean isPermissionTransferUserConsented(String packageName, int userId, int associationId) { - return mSystemDataTransferProcessor.isPermissionTransferUserConsented(associationId); + return mSystemDataTransferProcessor.isPermissionTransferUserConsented(packageName, + userId, associationId); } @Override @@ -602,7 +891,8 @@ public class CompanionDeviceManagerService extends SystemService { ParcelFileDescriptor fd) { attachSystemDataTransport_enforcePermission(); - mTransportManager.attachSystemDataTransport(associationId, fd); + getAssociationWithCallerChecks(associationId); + mTransportManager.attachSystemDataTransport(packageName, userId, associationId, fd); } @Override @@ -610,61 +900,161 @@ public class CompanionDeviceManagerService extends SystemService { public void detachSystemDataTransport(String packageName, int userId, int associationId) { detachSystemDataTransport_enforcePermission(); - mTransportManager.detachSystemDataTransport(associationId); - } - - @Override - @EnforcePermission(MANAGE_COMPANION_DEVICES) - public void enableSecureTransport(boolean enabled) { - enableSecureTransport_enforcePermission(); - - mTransportManager.enableSecureTransport(enabled); + getAssociationWithCallerChecks(associationId); + mTransportManager.detachSystemDataTransport(packageName, userId, associationId); } @Override public void enableSystemDataSync(int associationId, int flags) { + getAssociationWithCallerChecks(associationId); mAssociationRequestsProcessor.enableSystemDataSync(associationId, flags); } @Override public void disableSystemDataSync(int associationId, int flags) { + getAssociationWithCallerChecks(associationId); mAssociationRequestsProcessor.disableSystemDataSync(associationId, flags); } @Override public void enablePermissionsSync(int associationId) { + getAssociationWithCallerChecks(associationId); mSystemDataTransferProcessor.enablePermissionsSync(associationId); } @Override public void disablePermissionsSync(int associationId) { + getAssociationWithCallerChecks(associationId); mSystemDataTransferProcessor.disablePermissionsSync(associationId); } @Override public PermissionSyncRequest getPermissionSyncRequest(int associationId) { + // TODO: temporary fix, will remove soon + AssociationInfo association = mAssociationStore.getAssociationById(associationId); + if (association == null) { + return null; + } + getAssociationWithCallerChecks(associationId); return mSystemDataTransferProcessor.getPermissionSyncRequest(associationId); } @Override - @EnforcePermission(REQUEST_COMPANION_SELF_MANAGED) - public void notifySelfManagedDeviceAppeared(int associationId) { - notifySelfManagedDeviceAppeared_enforcePermission(); + @EnforcePermission(MANAGE_COMPANION_DEVICES) + public void enableSecureTransport(boolean enabled) { + enableSecureTransport_enforcePermission(); + mTransportManager.enableSecureTransport(enabled); + } - mDevicePresenceProcessor.notifySelfManagedDevicePresenceEvent(associationId, true); + @Override + public void notifyDeviceAppeared(int associationId) { + if (DEBUG) Log.i(TAG, "notifyDevice_Appeared() id=" + associationId); + + AssociationInfo association = getAssociationWithCallerChecks(associationId); + if (!association.isSelfManaged()) { + throw new IllegalArgumentException("Association with ID " + associationId + + " is not self-managed. notifyDeviceAppeared(int) can only be called for" + + " self-managed associations."); + } + // AssociationInfo class is immutable: create a new AssociationInfo object with updated + // timestamp. + association = (new AssociationInfo.Builder(association)) + .setLastTimeConnected(System.currentTimeMillis()) + .build(); + mAssociationStore.updateAssociation(association); + + mDevicePresenceMonitor.onSelfManagedDeviceConnected(associationId); + + final String deviceProfile = association.getDeviceProfile(); + if (DEVICE_PROFILE_AUTOMOTIVE_PROJECTION.equals(deviceProfile)) { + Slog.i(TAG, "Enable hint mode for device device profile: " + deviceProfile); + mPowerManagerInternal.setPowerMode(Mode.AUTOMOTIVE_PROJECTION, true); + } } @Override - @EnforcePermission(REQUEST_COMPANION_SELF_MANAGED) - public void notifySelfManagedDeviceDisappeared(int associationId) { - notifySelfManagedDeviceDisappeared_enforcePermission(); + public void notifyDeviceDisappeared(int associationId) { + if (DEBUG) Log.i(TAG, "notifyDevice_Disappeared() id=" + associationId); + + final AssociationInfo association = getAssociationWithCallerChecks(associationId); + if (!association.isSelfManaged()) { + throw new IllegalArgumentException("Association with ID " + associationId + + " is not self-managed. notifyDeviceAppeared(int) can only be called for" + + " self-managed associations."); + } + + mDevicePresenceMonitor.onSelfManagedDeviceDisconnected(associationId); - mDevicePresenceProcessor.notifySelfManagedDevicePresenceEvent(associationId, false); + final String deviceProfile = association.getDeviceProfile(); + if (DEVICE_PROFILE_AUTOMOTIVE_PROJECTION.equals(deviceProfile)) { + Slog.i(TAG, "Disable hint mode for device profile: " + deviceProfile); + mPowerManagerInternal.setPowerMode(Mode.AUTOMOTIVE_PROJECTION, false); + } } @Override public boolean isCompanionApplicationBound(String packageName, int userId) { - return mCompanionAppBinder.isCompanionApplicationBound(userId, packageName); + return mCompanionAppController.isCompanionApplicationBound(userId, packageName); + } + + private void registerDevicePresenceListenerActive(String packageName, String deviceAddress, + boolean active) throws RemoteException { + if (DEBUG) { + Log.i(TAG, "registerDevicePresenceListenerActive()" + + " active=" + active + + " deviceAddress=" + deviceAddress); + } + final int userId = getCallingUserId(); + enforceCallerIsSystemOr(userId, packageName); + + AssociationInfo association = mAssociationStore.getFirstAssociationByAddress( + userId, packageName, deviceAddress); + + if (association == null) { + throw new RemoteException(new DeviceNotAssociatedException("App " + packageName + + " is not associated with device " + deviceAddress + + " for user " + userId)); + } + + processDevicePresenceListener(association, userId, packageName, active); + } + + private void processDevicePresenceListener(AssociationInfo association, + int userId, String packageName, boolean active) { + // If already at specified state, then no-op. + if (active == association.isNotifyOnDeviceNearby()) { + if (DEBUG) Log.d(TAG, "Device presence listener is already at desired state."); + return; + } + + // AssociationInfo class is immutable: create a new AssociationInfo object with updated + // flag. + association = (new AssociationInfo.Builder(association)) + .setNotifyOnDeviceNearby(active) + .build(); + // Do not need to call {@link BleCompanionDeviceScanner#restartScan()} since it will + // trigger {@link BleCompanionDeviceScanner#restartScan(int, AssociationInfo)} when + // an application sets/unsets the mNotifyOnDeviceNearby flag. + mAssociationStore.updateAssociation(association); + + int associationId = association.getId(); + // If device is already present, then trigger callback. + if (active && mDevicePresenceMonitor.isDevicePresent(associationId)) { + Slog.i(TAG, "Device is already present. Triggering callback."); + if (mDevicePresenceMonitor.isBlePresent(associationId) + || mDevicePresenceMonitor.isSimulatePresent(associationId)) { + onDeviceAppearedInternal(associationId); + onDevicePresenceEventInternal(associationId, EVENT_BLE_APPEARED); + } else if (mDevicePresenceMonitor.isBtConnected(associationId)) { + onDevicePresenceEventInternal(associationId, EVENT_BT_CONNECTED); + } + } + + // If last listener is unregistered, then unbind application. + if (!active && !shouldBindPackage(userId, packageName)) { + if (DEBUG) Log.d(TAG, "Last listener unregistered. Unbinding application."); + mCompanionAppController.unbindCompanionApplication(userId, packageName); + } } @Override @@ -680,8 +1070,7 @@ public class CompanionDeviceManagerService extends SystemService { } final MacAddress macAddressObj = MacAddress.fromString(macAddress); - mAssociationRequestsProcessor.createAssociation(userId, packageName, macAddressObj, - null, null, null, false, null, null); + createNewAssociation(userId, packageName, macAddressObj, null, null, false); } private void checkCanCallNotificationApi(String callingPackage, int userId) { @@ -710,7 +1099,9 @@ public class CompanionDeviceManagerService extends SystemService { @Override public void setAssociationTag(int associationId, String tag) { - mAssociationRequestsProcessor.setAssociationTag(associationId, tag); + AssociationInfo association = getAssociationWithCallerChecks(associationId); + association = (new AssociationInfo.Builder(association)).setTag(tag).build(); + mAssociationStore.updateAssociation(association); } @Override @@ -733,7 +1124,7 @@ public class CompanionDeviceManagerService extends SystemService { @NonNull ParcelFileDescriptor out, @NonNull ParcelFileDescriptor err, @NonNull String[] args) { return new CompanionDeviceShellCommand(CompanionDeviceManagerService.this, - mAssociationStore, mDevicePresenceProcessor, mTransportManager, + mAssociationStore, mDevicePresenceMonitor, mTransportManager, mSystemDataTransferProcessor, mAssociationRequestsProcessor, mBackupRestoreProcessor, mDisassociationProcessor) .exec(this, in.getFileDescriptor(), out.getFileDescriptor(), @@ -748,13 +1139,21 @@ public class CompanionDeviceManagerService extends SystemService { } mAssociationStore.dump(out); - mDevicePresenceProcessor.dump(out); - mCompanionAppBinder.dump(out); + mDevicePresenceMonitor.dump(out); + mCompanionAppController.dump(out); mTransportManager.dump(out); mSystemDataTransferRequestStore.dump(out); } } + void createNewAssociation(@UserIdInt int userId, @NonNull String packageName, + @Nullable MacAddress macAddress, @Nullable CharSequence displayName, + @Nullable String deviceProfile, boolean isSelfManaged) { + mAssociationRequestsProcessor.createAssociation(userId, packageName, macAddress, + displayName, deviceProfile, /* associatedDevice */ null, isSelfManaged, + /* callback */ null, /* resultReceiver */ null); + } + /** * Update special access for the association's package */ @@ -770,6 +1169,8 @@ public class CompanionDeviceManagerService extends SystemService { return; } + Slog.i(TAG, "Updating special access for package=[" + packageInfo.packageName + "]..."); + if (containsEither(packageInfo.requestedPermissions, android.Manifest.permission.RUN_IN_BACKGROUND, android.Manifest.permission.REQUEST_COMPANION_RUN_IN_BACKGROUND)) { @@ -879,6 +1280,29 @@ public class CompanionDeviceManagerService extends SystemService { } }; + private final CompanionDevicePresenceMonitor.Callback mDevicePresenceCallback = + new CompanionDevicePresenceMonitor.Callback() { + @Override + public void onDeviceAppeared(int associationId) { + onDeviceAppearedInternal(associationId); + } + + @Override + public void onDeviceDisappeared(int associationId) { + onDeviceDisappearedInternal(associationId); + } + + @Override + public void onDevicePresenceEvent(int associationId, int event) { + onDevicePresenceEventInternal(associationId, event); + } + + @Override + public void onDevicePresenceEventByUuid(ObservableUuid uuid, int event) { + onDevicePresenceEventByUuidInternal(uuid, event); + } + }; + private final PackageMonitor mPackageMonitor = new PackageMonitor() { @Override public void onPackageRemoved(String packageName, int uid) { @@ -891,7 +1315,7 @@ public class CompanionDeviceManagerService extends SystemService { } @Override - public void onPackageModified(@NonNull String packageName) { + public void onPackageModified(String packageName) { onPackageModifiedInternal(getChangingUserId(), packageName); } @@ -901,15 +1325,25 @@ public class CompanionDeviceManagerService extends SystemService { } }; + private static Map<String, Set<Integer>> deepUnmodifiableCopy(Map<String, Set<Integer>> orig) { + final Map<String, Set<Integer>> copy = new HashMap<>(); + + for (Map.Entry<String, Set<Integer>> entry : orig.entrySet()) { + final Set<Integer> valueCopy = new HashSet<>(entry.getValue()); + copy.put(entry.getKey(), Collections.unmodifiableSet(valueCopy)); + } + + return Collections.unmodifiableMap(copy); + } + private static <T> boolean containsEither(T[] array, T a, T b) { return ArrayUtils.contains(array, a) || ArrayUtils.contains(array, b); } private class LocalService implements CompanionDeviceManagerServiceInternal { - @Override public void removeInactiveSelfManagedAssociations() { - mDisassociationProcessor.removeIdleSelfManagedAssociations(); + CompanionDeviceManagerService.this.removeInactiveSelfManagedAssociations(); } @Override diff --git a/services/companion/java/com/android/server/companion/CompanionDeviceManagerServiceInternal.java b/services/companion/java/com/android/server/companion/CompanionDeviceManagerServiceInternal.java index 9d1250d361c4..cdf832f8c788 100644 --- a/services/companion/java/com/android/server/companion/CompanionDeviceManagerServiceInternal.java +++ b/services/companion/java/com/android/server/companion/CompanionDeviceManagerServiceInternal.java @@ -27,9 +27,8 @@ import java.util.Collection; * Companion Device Manager Local System Service Interface. */ public interface CompanionDeviceManagerServiceInternal { - /** - * Remove idle self-managed associations. + * @see CompanionDeviceManagerService#removeInactiveSelfManagedAssociations */ void removeInactiveSelfManagedAssociations(); diff --git a/services/companion/java/com/android/server/companion/presence/CompanionServiceConnector.java b/services/companion/java/com/android/server/companion/CompanionDeviceServiceConnector.java index c01c3195e04d..5abdb42b34fc 100644 --- a/services/companion/java/com/android/server/companion/presence/CompanionServiceConnector.java +++ b/services/companion/java/com/android/server/companion/CompanionDeviceServiceConnector.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.android.server.companion.presence; +package com.android.server.companion; import static android.content.Context.BIND_ALMOST_PERCEPTIBLE; import static android.content.Context.BIND_TREAT_LIKE_VISIBLE_FOREGROUND_SERVICE; @@ -33,42 +33,36 @@ import android.content.Context; import android.content.Intent; import android.os.Handler; import android.os.IBinder; -import android.util.Slog; +import android.util.Log; import com.android.internal.infra.ServiceConnector; import com.android.server.ServiceThread; -import com.android.server.companion.CompanionDeviceManagerService; /** * Manages a connection (binding) to an instance of {@link CompanionDeviceService} running in the * application process. */ @SuppressLint("LongLogTag") -public class CompanionServiceConnector extends ServiceConnector.Impl<ICompanionDeviceService> { - - /** Listener for changes to the state of the {@link CompanionServiceConnector} */ - public interface Listener { - /** - * Called when service binding is died. - */ - void onBindingDied(@UserIdInt int userId, @NonNull String packageName, - @NonNull CompanionServiceConnector serviceConnector); - } - +class CompanionDeviceServiceConnector extends ServiceConnector.Impl<ICompanionDeviceService> { private static final String TAG = "CDM_CompanionServiceConnector"; + private static final boolean DEBUG = false; /* Unbinding before executing the callbacks can cause problems. Wait 5-seconds before unbind. */ private static final long UNBIND_POST_DELAY_MS = 5_000; - @UserIdInt - private final int mUserId; - @NonNull - private final ComponentName mComponentName; - private final boolean mIsPrimary; + + /** Listener for changes to the state of the {@link CompanionDeviceServiceConnector} */ + interface Listener { + void onBindingDied(@UserIdInt int userId, @NonNull String packageName, + @NonNull CompanionDeviceServiceConnector serviceConnector); + } + + private final @UserIdInt int mUserId; + private final @NonNull ComponentName mComponentName; // IMPORTANT: this can (and will!) be null (at the moment, CompanionApplicationController only // installs a listener to the primary ServiceConnector), hence we should always null-check the // reference before calling on it. - @Nullable - private Listener mListener; + private @Nullable Listener mListener; + private boolean mIsPrimary; /** * Create a CompanionDeviceServiceConnector instance. @@ -85,16 +79,16 @@ public class CompanionServiceConnector extends ServiceConnector.Impl<ICompanionD * IMPORTANCE_FOREGROUND_SERVICE = 125. In order to kill the one time permission session, the * service importance level should be higher than 125. */ - static CompanionServiceConnector newInstance(@NonNull Context context, + static CompanionDeviceServiceConnector newInstance(@NonNull Context context, @UserIdInt int userId, @NonNull ComponentName componentName, boolean isSelfManaged, boolean isPrimary) { final int bindingFlags = isSelfManaged ? BIND_TREAT_LIKE_VISIBLE_FOREGROUND_SERVICE : BIND_ALMOST_PERCEPTIBLE; - return new CompanionServiceConnector( + return new CompanionDeviceServiceConnector( context, userId, componentName, bindingFlags, isPrimary); } - private CompanionServiceConnector(@NonNull Context context, @UserIdInt int userId, + private CompanionDeviceServiceConnector(@NonNull Context context, @UserIdInt int userId, @NonNull ComponentName componentName, int bindingFlags, boolean isPrimary) { super(context, buildIntent(componentName), bindingFlags, userId, null); mUserId = userId; @@ -139,7 +133,6 @@ public class CompanionServiceConnector extends ServiceConnector.Impl<ICompanionD return mIsPrimary; } - @NonNull ComponentName getComponentName() { return mComponentName; } @@ -147,15 +140,17 @@ public class CompanionServiceConnector extends ServiceConnector.Impl<ICompanionD @Override protected void onServiceConnectionStatusChanged( @NonNull ICompanionDeviceService service, boolean isConnected) { - Slog.d(TAG, "onServiceConnectionStatusChanged() " + mComponentName.toShortString() - + " connected=" + isConnected); + if (DEBUG) { + Log.d(TAG, "onServiceConnection_StatusChanged() " + mComponentName.toShortString() + + " connected=" + isConnected); + } } @Override public void binderDied() { super.binderDied(); - Slog.d(TAG, "binderDied() " + mComponentName.toShortString()); + if (DEBUG) Log.d(TAG, "binderDied() " + mComponentName.toShortString()); // Handle primary process being killed if (mListener != null) { @@ -177,8 +172,7 @@ public class CompanionServiceConnector extends ServiceConnector.Impl<ICompanionD * within system_server and thus tends to get heavily congested) */ @Override - @NonNull - protected Handler getJobHandler() { + protected @NonNull Handler getJobHandler() { return getServiceThread().getThreadHandler(); } @@ -188,14 +182,12 @@ public class CompanionServiceConnector extends ServiceConnector.Impl<ICompanionD return -1; } - @NonNull - private static Intent buildIntent(@NonNull ComponentName componentName) { + private static @NonNull Intent buildIntent(@NonNull ComponentName componentName) { return new Intent(CompanionDeviceService.SERVICE_INTERFACE) .setComponent(componentName); } - @NonNull - private static ServiceThread getServiceThread() { + private static @NonNull ServiceThread getServiceThread() { if (sServiceThread == null) { synchronized (CompanionDeviceManagerService.class) { if (sServiceThread == null) { @@ -214,6 +206,5 @@ public class CompanionServiceConnector extends ServiceConnector.Impl<ICompanionD * <p> * Do NOT reference directly, use {@link #getServiceThread()} method instead. */ - @Nullable - private static volatile ServiceThread sServiceThread; + private static volatile @Nullable ServiceThread sServiceThread; } diff --git a/services/companion/java/com/android/server/companion/CompanionDeviceShellCommand.java b/services/companion/java/com/android/server/companion/CompanionDeviceShellCommand.java index a78938400a1e..a7a73cb6bddb 100644 --- a/services/companion/java/com/android/server/companion/CompanionDeviceShellCommand.java +++ b/services/companion/java/com/android/server/companion/CompanionDeviceShellCommand.java @@ -18,6 +18,8 @@ package com.android.server.companion; import static android.companion.CompanionDeviceManager.MESSAGE_REQUEST_CONTEXT_SYNC; +import static com.android.server.companion.utils.PermissionsUtils.sanitizeWithCallerChecks; + import android.companion.AssociationInfo; import android.companion.ContextSyncMessage; import android.companion.Flags; @@ -36,7 +38,7 @@ import com.android.server.companion.association.DisassociationProcessor; import com.android.server.companion.datatransfer.SystemDataTransferProcessor; import com.android.server.companion.datatransfer.contextsync.BitmapUtils; import com.android.server.companion.datatransfer.contextsync.CrossDeviceSyncController; -import com.android.server.companion.presence.DevicePresenceProcessor; +import com.android.server.companion.presence.CompanionDevicePresenceMonitor; import com.android.server.companion.presence.ObservableUuid; import com.android.server.companion.transport.CompanionTransportManager; @@ -49,7 +51,7 @@ class CompanionDeviceShellCommand extends ShellCommand { private final CompanionDeviceManagerService mService; private final DisassociationProcessor mDisassociationProcessor; private final AssociationStore mAssociationStore; - private final DevicePresenceProcessor mDevicePresenceProcessor; + private final CompanionDevicePresenceMonitor mDevicePresenceMonitor; private final CompanionTransportManager mTransportManager; private final SystemDataTransferProcessor mSystemDataTransferProcessor; @@ -58,7 +60,7 @@ class CompanionDeviceShellCommand extends ShellCommand { CompanionDeviceShellCommand(CompanionDeviceManagerService service, AssociationStore associationStore, - DevicePresenceProcessor devicePresenceProcessor, + CompanionDevicePresenceMonitor devicePresenceMonitor, CompanionTransportManager transportManager, SystemDataTransferProcessor systemDataTransferProcessor, AssociationRequestsProcessor associationRequestsProcessor, @@ -66,7 +68,7 @@ class CompanionDeviceShellCommand extends ShellCommand { DisassociationProcessor disassociationProcessor) { mService = service; mAssociationStore = associationStore; - mDevicePresenceProcessor = devicePresenceProcessor; + mDevicePresenceMonitor = devicePresenceMonitor; mTransportManager = transportManager; mSystemDataTransferProcessor = systemDataTransferProcessor; mAssociationRequestsProcessor = associationRequestsProcessor; @@ -83,7 +85,7 @@ class CompanionDeviceShellCommand extends ShellCommand { if ("simulate-device-event".equals(cmd) && Flags.devicePresence()) { associationId = getNextIntArgRequired(); int event = getNextIntArgRequired(); - mDevicePresenceProcessor.simulateDeviceEvent(associationId, event); + mDevicePresenceMonitor.simulateDeviceEvent(associationId, event); return 0; } @@ -95,7 +97,7 @@ class CompanionDeviceShellCommand extends ShellCommand { ObservableUuid observableUuid = new ObservableUuid( userId, ParcelUuid.fromString(uuid), packageName, System.currentTimeMillis()); - mDevicePresenceProcessor.simulateDeviceEventByUuid(observableUuid, event); + mDevicePresenceMonitor.simulateDeviceEventByUuid(observableUuid, event); return 0; } @@ -122,9 +124,8 @@ class CompanionDeviceShellCommand extends ShellCommand { String address = getNextArgRequired(); String deviceProfile = getNextArg(); final MacAddress macAddress = MacAddress.fromString(address); - mAssociationRequestsProcessor.createAssociation(userId, packageName, macAddress, - deviceProfile, deviceProfile, /* associatedDevice */ null, false, - /* callback */ null, /* resultReceiver */ null); + mService.createNewAssociation(userId, packageName, macAddress, + /* displayName= */ deviceProfile, deviceProfile, false); } break; @@ -133,13 +134,8 @@ class CompanionDeviceShellCommand extends ShellCommand { final String packageName = getNextArgRequired(); final String address = getNextArgRequired(); final AssociationInfo association = - mAssociationStore.getFirstAssociationByAddress(userId, packageName, - address); - if (association == null) { - out.println("Association doesn't exist."); - } else { - mDisassociationProcessor.disassociate(association.getId()); - } + mService.getAssociationWithCallerChecks(userId, packageName, address); + mDisassociationProcessor.disassociate(association.getId()); } break; @@ -148,7 +144,9 @@ class CompanionDeviceShellCommand extends ShellCommand { final List<AssociationInfo> userAssociations = mAssociationStore.getAssociationsByUser(userId); for (AssociationInfo association : userAssociations) { - mDisassociationProcessor.disassociate(association.getId()); + if (sanitizeWithCallerChecks(mService.getContext(), association) != null) { + mDisassociationProcessor.disassociate(association.getId()); + } } } break; @@ -159,12 +157,12 @@ class CompanionDeviceShellCommand extends ShellCommand { case "simulate-device-appeared": associationId = getNextIntArgRequired(); - mDevicePresenceProcessor.simulateDeviceEvent(associationId, /* event */ 0); + mDevicePresenceMonitor.simulateDeviceEvent(associationId, /* event */ 0); break; case "simulate-device-disappeared": associationId = getNextIntArgRequired(); - mDevicePresenceProcessor.simulateDeviceEvent(associationId, /* event */ 1); + mDevicePresenceMonitor.simulateDeviceEvent(associationId, /* event */ 1); break; case "get-backup-payload": { @@ -412,9 +410,10 @@ class CompanionDeviceShellCommand extends ShellCommand { pw.println(" Remove an existing Association."); pw.println(" disassociate-all USER_ID"); pw.println(" Remove all Associations for a user."); - pw.println(" refresh-cache"); + pw.println(" clear-association-memory-cache"); pw.println(" Clear the in-memory association cache and reload all association "); - pw.println(" information from disk. USE FOR DEBUGGING AND/OR TESTING PURPOSES ONLY."); + pw.println(" information from persistent storage. USE FOR DEBUGGING PURPOSES ONLY."); + pw.println(" USE FOR DEBUGGING AND/OR TESTING PURPOSES ONLY."); pw.println(" simulate-device-appeared ASSOCIATION_ID"); pw.println(" Make CDM act as if the given companion device has appeared."); diff --git a/services/companion/java/com/android/server/companion/association/AssociationRequestsProcessor.java b/services/companion/java/com/android/server/companion/association/AssociationRequestsProcessor.java index a18776e67200..a02d9f912bcd 100644 --- a/services/companion/java/com/android/server/companion/association/AssociationRequestsProcessor.java +++ b/services/companion/java/com/android/server/companion/association/AssociationRequestsProcessor.java @@ -145,8 +145,7 @@ public class AssociationRequestsProcessor { /** * Handle incoming {@link AssociationRequest}s, sent via - * {@link android.companion.ICompanionDeviceManager#associate(AssociationRequest, - * IAssociationRequestCallback, String, int)} + * {@link android.companion.ICompanionDeviceManager#associate(AssociationRequest, IAssociationRequestCallback, String, int)} */ public void processNewAssociationRequest(@NonNull AssociationRequest request, @NonNull String packageName, @UserIdInt int userId, @@ -213,8 +212,7 @@ public class AssociationRequestsProcessor { // 2b.4. Send the PendingIntent back to the app. try { callback.onAssociationPending(pendingIntent); - } catch (RemoteException ignore) { - } + } catch (RemoteException ignore) { } } /** @@ -254,8 +252,7 @@ public class AssociationRequestsProcessor { // forward it back to the application via the callback. try { callback.onFailure(e.getMessage()); - } catch (RemoteException ignore) { - } + } catch (RemoteException ignore) { } return; } @@ -325,8 +322,7 @@ public class AssociationRequestsProcessor { * Enable system data sync. */ public void enableSystemDataSync(int associationId, int flags) { - AssociationInfo association = mAssociationStore.getAssociationWithCallerChecks( - associationId); + AssociationInfo association = mAssociationStore.getAssociationById(associationId); AssociationInfo updated = (new AssociationInfo.Builder(association)) .setSystemDataSyncFlags(association.getSystemDataSyncFlags() | flags).build(); mAssociationStore.updateAssociation(updated); @@ -336,23 +332,12 @@ public class AssociationRequestsProcessor { * Disable system data sync. */ public void disableSystemDataSync(int associationId, int flags) { - AssociationInfo association = mAssociationStore.getAssociationWithCallerChecks( - associationId); + AssociationInfo association = mAssociationStore.getAssociationById(associationId); AssociationInfo updated = (new AssociationInfo.Builder(association)) .setSystemDataSyncFlags(association.getSystemDataSyncFlags() & (~flags)).build(); mAssociationStore.updateAssociation(updated); } - /** - * Set association tag. - */ - public void setAssociationTag(int associationId, String tag) { - AssociationInfo association = mAssociationStore.getAssociationWithCallerChecks( - associationId); - association = (new AssociationInfo.Builder(association)).setTag(tag).build(); - mAssociationStore.updateAssociation(association); - } - private void sendCallbackAndFinish(@Nullable AssociationInfo association, @Nullable IAssociationRequestCallback callback, @Nullable ResultReceiver resultReceiver) { @@ -411,14 +396,14 @@ public class AssociationRequestsProcessor { // If the application already has a pending association request, that PendingIntent // will be cancelled except application wants to cancel the request by the system. return Binder.withCleanCallingIdentity(() -> - PendingIntent.getActivityAsUser( - mContext, /*requestCode */ packageUid, intent, - FLAG_ONE_SHOT | FLAG_CANCEL_CURRENT | FLAG_IMMUTABLE, - ActivityOptions.makeBasic() - .setPendingIntentCreatorBackgroundActivityStartMode( - ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOWED) - .toBundle(), - UserHandle.CURRENT) + PendingIntent.getActivityAsUser( + mContext, /*requestCode */ packageUid, intent, + FLAG_ONE_SHOT | FLAG_CANCEL_CURRENT | FLAG_IMMUTABLE, + ActivityOptions.makeBasic() + .setPendingIntentCreatorBackgroundActivityStartMode( + ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOWED) + .toBundle(), + UserHandle.CURRENT) ); } diff --git a/services/companion/java/com/android/server/companion/association/AssociationStore.java b/services/companion/java/com/android/server/companion/association/AssociationStore.java index ae2b70852a35..edebb55233d0 100644 --- a/services/companion/java/com/android/server/companion/association/AssociationStore.java +++ b/services/companion/java/com/android/server/companion/association/AssociationStore.java @@ -18,7 +18,6 @@ package com.android.server.companion.association; import static com.android.server.companion.utils.MetricUtils.logCreateAssociation; import static com.android.server.companion.utils.MetricUtils.logRemoveAssociation; -import static com.android.server.companion.utils.PermissionsUtils.checkCallerCanManageAssociationsForPackage; import android.annotation.IntDef; import android.annotation.NonNull; @@ -27,7 +26,6 @@ import android.annotation.SuppressLint; import android.annotation.UserIdInt; import android.companion.AssociationInfo; import android.companion.IOnAssociationsChangedListener; -import android.content.Context; import android.content.pm.UserInfo; import android.net.MacAddress; import android.os.Binder; @@ -59,22 +57,21 @@ import java.util.concurrent.Executors; @SuppressLint("LongLogTag") public class AssociationStore { - @IntDef(prefix = {"CHANGE_TYPE_"}, value = { + @IntDef(prefix = { "CHANGE_TYPE_" }, value = { CHANGE_TYPE_ADDED, CHANGE_TYPE_REMOVED, CHANGE_TYPE_UPDATED_ADDRESS_CHANGED, CHANGE_TYPE_UPDATED_ADDRESS_UNCHANGED, }) @Retention(RetentionPolicy.SOURCE) - public @interface ChangeType { - } + public @interface ChangeType {} public static final int CHANGE_TYPE_ADDED = 0; public static final int CHANGE_TYPE_REMOVED = 1; public static final int CHANGE_TYPE_UPDATED_ADDRESS_CHANGED = 2; public static final int CHANGE_TYPE_UPDATED_ADDRESS_UNCHANGED = 3; - /** Listener for any changes to associations. */ + /** Listener for any changes to associations. */ public interface OnChangeListener { /** * Called when there are association changes. @@ -103,30 +100,25 @@ public class AssociationStore { /** * Called when an association is added. */ - default void onAssociationAdded(AssociationInfo association) { - } + default void onAssociationAdded(AssociationInfo association) {} /** * Called when an association is removed. */ - default void onAssociationRemoved(AssociationInfo association) { - } + default void onAssociationRemoved(AssociationInfo association) {} /** * Called when an association is updated. */ - default void onAssociationUpdated(AssociationInfo association, boolean addressChanged) { - } + default void onAssociationUpdated(AssociationInfo association, boolean addressChanged) {} } private static final String TAG = "CDM_AssociationStore"; - private final Context mContext; - private final UserManager mUserManager; - private final AssociationDiskStore mDiskStore; + private final Object mLock = new Object(); + private final ExecutorService mExecutor; - private final Object mLock = new Object(); @GuardedBy("mLock") private boolean mPersisted = false; @GuardedBy("mLock") @@ -140,9 +132,10 @@ public class AssociationStore { private final RemoteCallbackList<IOnAssociationsChangedListener> mRemoteListeners = new RemoteCallbackList<>(); - public AssociationStore(Context context, UserManager userManager, - AssociationDiskStore diskStore) { - mContext = context; + private final UserManager mUserManager; + private final AssociationDiskStore mDiskStore; + + public AssociationStore(UserManager userManager, AssociationDiskStore diskStore) { mUserManager = userManager; mDiskStore = diskStore; mExecutor = Executors.newSingleThreadExecutor(); @@ -209,7 +202,7 @@ public class AssociationStore { synchronized (mLock) { if (mIdToAssociationMap.containsKey(id)) { - Slog.e(TAG, "Association id=[" + id + "] already exists."); + Slog.e(TAG, "Association with id=[" + id + "] already exists."); return; } @@ -456,26 +449,6 @@ public class AssociationStore { } /** - * Get association by id with caller checks. - */ - @NonNull - public AssociationInfo getAssociationWithCallerChecks(int associationId) { - AssociationInfo association = getAssociationById(associationId); - if (association == null) { - throw new IllegalArgumentException( - "getAssociationWithCallerChecks() Association id=[" + associationId - + "] doesn't exist."); - } - if (checkCallerCanManageAssociationsForPackage(mContext, association.getUserId(), - association.getPackageName())) { - return association; - } - - throw new IllegalArgumentException( - "The caller can't interact with the association id=[" + associationId + "]."); - } - - /** * Register a local listener for association changes. */ public void registerLocalListener(@NonNull OnChangeListener listener) { diff --git a/services/companion/java/com/android/server/companion/association/DisassociationProcessor.java b/services/companion/java/com/android/server/companion/association/DisassociationProcessor.java index acf683d387a3..ec8977918c56 100644 --- a/services/companion/java/com/android/server/companion/association/DisassociationProcessor.java +++ b/services/companion/java/com/android/server/companion/association/DisassociationProcessor.java @@ -22,8 +22,6 @@ import static android.companion.AssociationRequest.DEVICE_PROFILE_AUTOMOTIVE_PRO import static com.android.internal.util.CollectionUtils.any; import static com.android.server.companion.utils.RolesUtils.removeRoleHolderForAssociation; -import static java.util.concurrent.TimeUnit.DAYS; - import android.annotation.NonNull; import android.annotation.SuppressLint; import android.annotation.UserIdInt; @@ -32,27 +30,21 @@ import android.companion.AssociationInfo; import android.content.Context; import android.content.pm.PackageManagerInternal; import android.os.Binder; -import android.os.SystemProperties; import android.os.UserHandle; import android.util.Slog; +import com.android.server.companion.CompanionApplicationController; import com.android.server.companion.datatransfer.SystemDataTransferRequestStore; -import com.android.server.companion.presence.CompanionAppBinder; -import com.android.server.companion.presence.DevicePresenceProcessor; +import com.android.server.companion.presence.CompanionDevicePresenceMonitor; import com.android.server.companion.transport.CompanionTransportManager; /** - * This class responsible for disassociation. + * A class response for Association removal. */ @SuppressLint("LongLogTag") public class DisassociationProcessor { private static final String TAG = "CDM_DisassociationProcessor"; - - private static final String SYS_PROP_DEBUG_REMOVAL_TIME_WINDOW = - "debug.cdm.cdmservice.removal_time_window"; - private static final long ASSOCIATION_REMOVAL_TIME_WINDOW_DEFAULT = DAYS.toMillis(90); - @NonNull private final Context mContext; @NonNull @@ -60,11 +52,11 @@ public class DisassociationProcessor { @NonNull private final PackageManagerInternal mPackageManagerInternal; @NonNull - private final DevicePresenceProcessor mDevicePresenceMonitor; + private final CompanionDevicePresenceMonitor mDevicePresenceMonitor; @NonNull private final SystemDataTransferRequestStore mSystemDataTransferRequestStore; @NonNull - private final CompanionAppBinder mCompanionAppController; + private final CompanionApplicationController mCompanionAppController; @NonNull private final CompanionTransportManager mTransportManager; private final OnPackageVisibilityChangeListener mOnPackageVisibilityChangeListener; @@ -74,8 +66,8 @@ public class DisassociationProcessor { @NonNull ActivityManager activityManager, @NonNull AssociationStore associationStore, @NonNull PackageManagerInternal packageManager, - @NonNull DevicePresenceProcessor devicePresenceMonitor, - @NonNull CompanionAppBinder applicationController, + @NonNull CompanionDevicePresenceMonitor devicePresenceMonitor, + @NonNull CompanionApplicationController applicationController, @NonNull SystemDataTransferRequestStore systemDataTransferRequestStore, @NonNull CompanionTransportManager companionTransportManager) { mContext = context; @@ -97,7 +89,11 @@ public class DisassociationProcessor { public void disassociate(int id) { Slog.i(TAG, "Disassociating id=[" + id + "]..."); - final AssociationInfo association = mAssociationStore.getAssociationWithCallerChecks(id); + final AssociationInfo association = mAssociationStore.getAssociationById(id); + if (association == null) { + Slog.e(TAG, "Can't disassociate id=[" + id + "]. It doesn't exist."); + return; + } final int userId = association.getUserId(); final String packageName = association.getPackageName(); @@ -122,12 +118,12 @@ public class DisassociationProcessor { return; } - // Detach transport if exists - mTransportManager.detachSystemDataTransport(id); - // Association cleanup. - mSystemDataTransferRequestStore.removeRequestsByAssociationId(userId, id); mAssociationStore.removeAssociation(association.getId()); + mSystemDataTransferRequestStore.removeRequestsByAssociationId(userId, id); + + // Detach transport if exists + mTransportManager.detachSystemDataTransport(packageName, userId, id); // If role is not in use by other associations, revoke the role. // Do not need to remove the system role since it was pre-granted by the system. @@ -147,28 +143,10 @@ public class DisassociationProcessor { it -> it.isNotifyOnDeviceNearby() && mDevicePresenceMonitor.isDevicePresent(it.getId())); if (!shouldStayBound) { - mCompanionAppController.unbindCompanionApp(userId, packageName); + mCompanionAppController.unbindCompanionApplication(userId, packageName); } } - /** - * @deprecated Use {@link #disassociate(int)} instead. - */ - @Deprecated - public void disassociate(int userId, String packageName, String macAddress) { - AssociationInfo association = mAssociationStore.getFirstAssociationByAddress(userId, - packageName, macAddress); - - if (association == null) { - throw new IllegalArgumentException( - "Association for mac address=[" + macAddress + "] doesn't exist"); - } - - mAssociationStore.getAssociationWithCallerChecks(association.getId()); - - disassociate(association.getId()); - } - @SuppressLint("MissingPermission") private int getPackageProcessImportance(@UserIdInt int userId, @NonNull String packageName) { return Binder.withCleanCallingIdentity(() -> { @@ -185,7 +163,7 @@ public class DisassociationProcessor { () -> mActivityManager.addOnUidImportanceListener( mOnPackageVisibilityChangeListener, ActivityManager.RunningAppProcessInfo.IMPORTANCE_VISIBLE)); - } catch (IllegalArgumentException e) { + } catch (IllegalArgumentException e) { Slog.e(TAG, "Failed to start listening to uid importance changes."); } } @@ -201,34 +179,6 @@ public class DisassociationProcessor { } /** - * Remove idle self-managed associations. - */ - public void removeIdleSelfManagedAssociations() { - Slog.i(TAG, "Removing idle self-managed associations."); - - final long currentTime = System.currentTimeMillis(); - long removalWindow = SystemProperties.getLong(SYS_PROP_DEBUG_REMOVAL_TIME_WINDOW, -1); - if (removalWindow <= 0) { - // 0 or negative values indicate that the sysprop was never set or should be ignored. - removalWindow = ASSOCIATION_REMOVAL_TIME_WINDOW_DEFAULT; - } - - for (AssociationInfo association : mAssociationStore.getAssociations()) { - if (!association.isSelfManaged()) continue; - - final boolean isInactive = - currentTime - association.getLastTimeConnectedMs() >= removalWindow; - if (!isInactive) continue; - - final int id = association.getId(); - - Slog.i(TAG, "Removing inactive self-managed association=[" + association.toShortString() - + "]."); - disassociate(id); - } - } - - /** * An OnUidImportanceListener class which watches the importance of the packages. * In this class, we ONLY interested in the importance of the running process is greater than * {@link ActivityManager.RunningAppProcessInfo#IMPORTANCE_VISIBLE}. diff --git a/services/companion/java/com/android/server/companion/association/InactiveAssociationsRemovalService.java b/services/companion/java/com/android/server/companion/association/InactiveAssociationsRemovalService.java index b509e71626ea..f28731548dcc 100644 --- a/services/companion/java/com/android/server/companion/association/InactiveAssociationsRemovalService.java +++ b/services/companion/java/com/android/server/companion/association/InactiveAssociationsRemovalService.java @@ -30,7 +30,7 @@ import com.android.server.LocalServices; import com.android.server.companion.CompanionDeviceManagerServiceInternal; /** - * A Job Service responsible for clean up self-managed associations if it's idle for 90 days. + * A Job Service responsible for clean up idle self-managed associations. * * The job will be executed only if the device is charging and in idle mode due to the application * will be killed if association/role are revoked. See {@link DisassociationProcessor} @@ -45,10 +45,10 @@ public class InactiveAssociationsRemovalService extends JobService { @Override public boolean onStartJob(final JobParameters params) { Slog.i(TAG, "Execute the Association Removal job"); - + // Special policy for selfManaged that need to revoke associations if the device + // does not connect for 90 days. LocalServices.getService(CompanionDeviceManagerServiceInternal.class) .removeInactiveSelfManagedAssociations(); - jobFinished(params, false); return true; } diff --git a/services/companion/java/com/android/server/companion/datatransfer/SystemDataTransferProcessor.java b/services/companion/java/com/android/server/companion/datatransfer/SystemDataTransferProcessor.java index 9069689ee5eb..c5ca0bf7e9c5 100644 --- a/services/companion/java/com/android/server/companion/datatransfer/SystemDataTransferProcessor.java +++ b/services/companion/java/com/android/server/companion/datatransfer/SystemDataTransferProcessor.java @@ -31,6 +31,7 @@ import android.annotation.UserIdInt; import android.app.ActivityOptions; import android.app.PendingIntent; import android.companion.AssociationInfo; +import android.companion.DeviceNotAssociatedException; import android.companion.IOnMessageReceivedListener; import android.companion.ISystemDataTransferCallback; import android.companion.datatransfer.PermissionSyncRequest; @@ -55,6 +56,7 @@ import com.android.server.companion.CompanionDeviceManagerService; import com.android.server.companion.association.AssociationStore; import com.android.server.companion.transport.CompanionTransportManager; import com.android.server.companion.utils.PackageUtils; +import com.android.server.companion.utils.PermissionsUtils; import java.util.List; import java.util.concurrent.ExecutorService; @@ -118,10 +120,28 @@ public class SystemDataTransferProcessor { } /** + * Resolve the requested association, throwing if the caller doesn't have + * adequate permissions. + */ + @NonNull + private AssociationInfo resolveAssociation(String packageName, int userId, + int associationId) { + AssociationInfo association = mAssociationStore.getAssociationById(associationId); + association = PermissionsUtils.sanitizeWithCallerChecks(mContext, association); + if (association == null) { + throw new DeviceNotAssociatedException("Association " + + associationId + " is not associated with the app " + packageName + + " for user " + userId); + } + return association; + } + + /** * Return whether the user has consented to the permission transfer for the association. */ - public boolean isPermissionTransferUserConsented(int associationId) { - mAssociationStore.getAssociationWithCallerChecks(associationId); + public boolean isPermissionTransferUserConsented(String packageName, @UserIdInt int userId, + int associationId) { + resolveAssociation(packageName, userId, associationId); PermissionSyncRequest request = getPermissionSyncRequest(associationId); if (request == null) { @@ -147,8 +167,7 @@ public class SystemDataTransferProcessor { return null; } - final AssociationInfo association = mAssociationStore.getAssociationWithCallerChecks( - associationId); + final AssociationInfo association = resolveAssociation(packageName, userId, associationId); Slog.i(LOG_TAG, "Creating permission sync intent for userId [" + userId + "] associationId [" + associationId + "]"); @@ -188,7 +207,7 @@ public class SystemDataTransferProcessor { Slog.i(LOG_TAG, "Start system data transfer for package [" + packageName + "] userId [" + userId + "] associationId [" + associationId + "]"); - mAssociationStore.getAssociationWithCallerChecks(associationId); + final AssociationInfo association = resolveAssociation(packageName, userId, associationId); // Check if the request has been consented by the user. PermissionSyncRequest request = getPermissionSyncRequest(associationId); @@ -220,20 +239,24 @@ public class SystemDataTransferProcessor { * Enable perm sync for the association */ public void enablePermissionsSync(int associationId) { - int userId = mAssociationStore.getAssociationWithCallerChecks(associationId).getUserId(); - PermissionSyncRequest request = new PermissionSyncRequest(associationId); - request.setUserConsented(true); - mSystemDataTransferRequestStore.writeRequest(userId, request); + Binder.withCleanCallingIdentity(() -> { + int userId = mAssociationStore.getAssociationById(associationId).getUserId(); + PermissionSyncRequest request = new PermissionSyncRequest(associationId); + request.setUserConsented(true); + mSystemDataTransferRequestStore.writeRequest(userId, request); + }); } /** * Disable perm sync for the association */ public void disablePermissionsSync(int associationId) { - int userId = mAssociationStore.getAssociationWithCallerChecks(associationId).getUserId(); - PermissionSyncRequest request = new PermissionSyncRequest(associationId); - request.setUserConsented(false); - mSystemDataTransferRequestStore.writeRequest(userId, request); + Binder.withCleanCallingIdentity(() -> { + int userId = mAssociationStore.getAssociationById(associationId).getUserId(); + PermissionSyncRequest request = new PermissionSyncRequest(associationId); + request.setUserConsented(false); + mSystemDataTransferRequestStore.writeRequest(userId, request); + }); } /** @@ -241,17 +264,18 @@ public class SystemDataTransferProcessor { */ @Nullable public PermissionSyncRequest getPermissionSyncRequest(int associationId) { - int userId = mAssociationStore.getAssociationWithCallerChecks(associationId) - .getUserId(); - List<SystemDataTransferRequest> requests = - mSystemDataTransferRequestStore.readRequestsByAssociationId(userId, - associationId); - for (SystemDataTransferRequest request : requests) { - if (request instanceof PermissionSyncRequest) { - return (PermissionSyncRequest) request; + return Binder.withCleanCallingIdentity(() -> { + int userId = mAssociationStore.getAssociationById(associationId).getUserId(); + List<SystemDataTransferRequest> requests = + mSystemDataTransferRequestStore.readRequestsByAssociationId(userId, + associationId); + for (SystemDataTransferRequest request : requests) { + if (request instanceof PermissionSyncRequest) { + return (PermissionSyncRequest) request; + } } - } - return null; + return null; + }); } /** diff --git a/services/companion/java/com/android/server/companion/presence/BleCompanionDeviceScanner.java b/services/companion/java/com/android/server/companion/presence/BleCompanionDeviceScanner.java index 9c37881499bd..c89ce11c169d 100644 --- a/services/companion/java/com/android/server/companion/presence/BleCompanionDeviceScanner.java +++ b/services/companion/java/com/android/server/companion/presence/BleCompanionDeviceScanner.java @@ -33,7 +33,7 @@ import static android.bluetooth.le.ScanSettings.CALLBACK_TYPE_FIRST_MATCH; import static android.bluetooth.le.ScanSettings.CALLBACK_TYPE_MATCH_LOST; import static android.bluetooth.le.ScanSettings.SCAN_MODE_LOW_POWER; -import static com.android.server.companion.presence.DevicePresenceProcessor.DEBUG; +import static com.android.server.companion.presence.CompanionDevicePresenceMonitor.DEBUG; import static com.android.server.companion.utils.Utils.btDeviceToString; import static java.util.Objects.requireNonNull; diff --git a/services/companion/java/com/android/server/companion/presence/BluetoothCompanionDeviceConnectionListener.java b/services/companion/java/com/android/server/companion/presence/BluetoothCompanionDeviceConnectionListener.java index 2d345c48a8eb..cb363a7c9d7f 100644 --- a/services/companion/java/com/android/server/companion/presence/BluetoothCompanionDeviceConnectionListener.java +++ b/services/companion/java/com/android/server/companion/presence/BluetoothCompanionDeviceConnectionListener.java @@ -19,7 +19,7 @@ package com.android.server.companion.presence; import static android.companion.DevicePresenceEvent.EVENT_BT_CONNECTED; import static android.companion.DevicePresenceEvent.EVENT_BT_DISCONNECTED; -import static com.android.server.companion.presence.DevicePresenceProcessor.DEBUG; +import static com.android.server.companion.presence.CompanionDevicePresenceMonitor.DEBUG; import static com.android.server.companion.utils.Utils.btDeviceToString; import android.annotation.NonNull; diff --git a/services/companion/java/com/android/server/companion/presence/CompanionAppBinder.java b/services/companion/java/com/android/server/companion/presence/CompanionAppBinder.java deleted file mode 100644 index b6348ea9594d..000000000000 --- a/services/companion/java/com/android/server/companion/presence/CompanionAppBinder.java +++ /dev/null @@ -1,321 +0,0 @@ -/* - * Copyright (C) 2022 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.server.companion.presence; - -import android.annotation.NonNull; -import android.annotation.Nullable; -import android.annotation.SuppressLint; -import android.annotation.UserIdInt; -import android.companion.AssociationInfo; -import android.companion.CompanionDeviceService; -import android.companion.DevicePresenceEvent; -import android.content.ComponentName; -import android.content.Context; -import android.os.Handler; -import android.util.Pair; -import android.util.Slog; - -import com.android.internal.annotations.GuardedBy; -import com.android.internal.infra.PerUser; -import com.android.server.companion.CompanionDeviceManagerService; -import com.android.server.companion.utils.PackageUtils; - -import java.io.PrintWriter; -import java.util.ArrayList; -import java.util.Collections; -import java.util.HashMap; -import java.util.HashSet; -import java.util.List; -import java.util.Map; -import java.util.Set; - -/** - * Manages communication with companion applications via - * {@link android.companion.ICompanionDeviceService} interface, including "connecting" (binding) to - * the services, maintaining the connection (the binding), and invoking callback methods such as - * {@link CompanionDeviceService#onDeviceAppeared(AssociationInfo)}, - * {@link CompanionDeviceService#onDeviceDisappeared(AssociationInfo)} and - * {@link CompanionDeviceService#onDevicePresenceEvent(DevicePresenceEvent)} in the - * application process. - * - * <p> - * The following is the list of the APIs provided by {@link CompanionAppBinder} (to be - * utilized by {@link CompanionDeviceManagerService}): - * <ul> - * <li> {@link #bindCompanionApp(int, String, boolean, CompanionServiceConnector.Listener)} - * <li> {@link #unbindCompanionApp(int, String)} - * <li> {@link #isCompanionApplicationBound(int, String)} - * <li> {@link #isRebindingCompanionApplicationScheduled(int, String)} - * </ul> - * - * @see CompanionDeviceService - * @see android.companion.ICompanionDeviceService - * @see CompanionServiceConnector - */ -@SuppressLint("LongLogTag") -public class CompanionAppBinder { - private static final String TAG = "CDM_CompanionAppBinder"; - - private static final long REBIND_TIMEOUT = 10 * 1000; // 10 sec - - @NonNull - private final Context mContext; - @NonNull - private final CompanionServicesRegister mCompanionServicesRegister; - - @NonNull - @GuardedBy("mBoundCompanionApplications") - private final Map<Pair<Integer, String>, List<CompanionServiceConnector>> - mBoundCompanionApplications; - @NonNull - @GuardedBy("mScheduledForRebindingCompanionApplications") - private final Set<Pair<Integer, String>> mScheduledForRebindingCompanionApplications; - - public CompanionAppBinder(@NonNull Context context) { - mContext = context; - mCompanionServicesRegister = new CompanionServicesRegister(); - mBoundCompanionApplications = new HashMap<>(); - mScheduledForRebindingCompanionApplications = new HashSet<>(); - } - - /** - * On package changed. - */ - public void onPackagesChanged(@UserIdInt int userId) { - mCompanionServicesRegister.invalidate(userId); - } - - /** - * CDM binds to the companion app. - */ - public void bindCompanionApp(@UserIdInt int userId, @NonNull String packageName, - boolean isSelfManaged, CompanionServiceConnector.Listener listener) { - Slog.i(TAG, "Binding user=[" + userId + "], package=[" + packageName + "], isSelfManaged=[" - + isSelfManaged + "]..."); - - final List<ComponentName> companionServices = - mCompanionServicesRegister.forPackage(userId, packageName); - if (companionServices.isEmpty()) { - Slog.e(TAG, "Can not bind companion applications u" + userId + "/" + packageName + ": " - + "eligible CompanionDeviceService not found.\n" - + "A CompanionDeviceService should declare an intent-filter for " - + "\"android.companion.CompanionDeviceService\" action and require " - + "\"android.permission.BIND_COMPANION_DEVICE_SERVICE\" permission."); - return; - } - - final List<CompanionServiceConnector> serviceConnectors = new ArrayList<>(); - synchronized (mBoundCompanionApplications) { - if (mBoundCompanionApplications.containsKey(new Pair<>(userId, packageName))) { - Slog.w(TAG, "The package is ALREADY bound."); - return; - } - - for (int i = 0; i < companionServices.size(); i++) { - boolean isPrimary = i == 0; - serviceConnectors.add(CompanionServiceConnector.newInstance(mContext, userId, - companionServices.get(i), isSelfManaged, isPrimary)); - } - - mBoundCompanionApplications.put(new Pair<>(userId, packageName), serviceConnectors); - } - - // Set listeners for both Primary and Secondary connectors. - for (CompanionServiceConnector serviceConnector : serviceConnectors) { - serviceConnector.setListener(listener); - } - - // Now "bind" all the connectors: the primary one and the rest of them. - for (CompanionServiceConnector serviceConnector : serviceConnectors) { - serviceConnector.connect(); - } - } - - /** - * CDM unbinds the companion app. - */ - public void unbindCompanionApp(@UserIdInt int userId, @NonNull String packageName) { - Slog.i(TAG, "Unbinding user=[" + userId + "], package=[" + packageName + "]..."); - - final List<CompanionServiceConnector> serviceConnectors; - - synchronized (mBoundCompanionApplications) { - serviceConnectors = mBoundCompanionApplications.remove(new Pair<>(userId, packageName)); - } - - synchronized (mScheduledForRebindingCompanionApplications) { - mScheduledForRebindingCompanionApplications.remove(new Pair<>(userId, packageName)); - } - - if (serviceConnectors == null) { - Slog.e(TAG, "The package is not bound."); - return; - } - - for (CompanionServiceConnector serviceConnector : serviceConnectors) { - serviceConnector.postUnbind(); - } - } - - /** - * @return whether the companion application is bound now. - */ - public boolean isCompanionApplicationBound(@UserIdInt int userId, @NonNull String packageName) { - synchronized (mBoundCompanionApplications) { - return mBoundCompanionApplications.containsKey(new Pair<>(userId, packageName)); - } - } - - /** - * Remove bound apps for package. - */ - public void removePackage(int userId, String packageName) { - synchronized (mBoundCompanionApplications) { - mBoundCompanionApplications.remove(new Pair<>(userId, packageName)); - } - - synchronized (mScheduledForRebindingCompanionApplications) { - mScheduledForRebindingCompanionApplications.remove(new Pair<>(userId, packageName)); - } - } - - /** - * Schedule rebinding for the package. - */ - public void scheduleRebinding(@UserIdInt int userId, @NonNull String packageName, - CompanionServiceConnector serviceConnector) { - Slog.i(TAG, "scheduleRebinding() " + userId + "/" + packageName); - - if (isRebindingCompanionApplicationScheduled(userId, packageName)) { - Slog.i(TAG, "CompanionApplication rebinding has been scheduled, skipping " - + serviceConnector.getComponentName()); - return; - } - - if (serviceConnector.isPrimary()) { - synchronized (mScheduledForRebindingCompanionApplications) { - mScheduledForRebindingCompanionApplications.add(new Pair<>(userId, packageName)); - } - } - - // Rebinding in 10 seconds. - Handler.getMain().postDelayed(() -> - onRebindingCompanionApplicationTimeout(userId, packageName, - serviceConnector), - REBIND_TIMEOUT); - } - - private boolean isRebindingCompanionApplicationScheduled( - @UserIdInt int userId, @NonNull String packageName) { - synchronized (mScheduledForRebindingCompanionApplications) { - return mScheduledForRebindingCompanionApplications.contains( - new Pair<>(userId, packageName)); - } - } - - private void onRebindingCompanionApplicationTimeout( - @UserIdInt int userId, @NonNull String packageName, - @NonNull CompanionServiceConnector serviceConnector) { - // Re-mark the application is bound. - if (serviceConnector.isPrimary()) { - synchronized (mBoundCompanionApplications) { - if (!mBoundCompanionApplications.containsKey(new Pair<>(userId, packageName))) { - List<CompanionServiceConnector> serviceConnectors = - Collections.singletonList(serviceConnector); - mBoundCompanionApplications.put(new Pair<>(userId, packageName), - serviceConnectors); - } - } - - synchronized (mScheduledForRebindingCompanionApplications) { - mScheduledForRebindingCompanionApplications.remove(new Pair<>(userId, packageName)); - } - } - - serviceConnector.connect(); - } - - /** - * Dump bound apps. - */ - public void dump(@NonNull PrintWriter out) { - out.append("Companion Device Application Controller: \n"); - - synchronized (mBoundCompanionApplications) { - out.append(" Bound Companion Applications: "); - if (mBoundCompanionApplications.isEmpty()) { - out.append("<empty>\n"); - } else { - out.append("\n"); - for (Map.Entry<Pair<Integer, String>, List<CompanionServiceConnector>> entry : - mBoundCompanionApplications.entrySet()) { - out.append("<u").append(String.valueOf(entry.getKey().first)).append(", ") - .append(entry.getKey().second).append(">"); - for (CompanionServiceConnector serviceConnector : entry.getValue()) { - out.append(", isPrimary=").append( - String.valueOf(serviceConnector.isPrimary())); - } - } - } - } - - out.append(" Companion Applications Scheduled For Rebinding: "); - synchronized (mScheduledForRebindingCompanionApplications) { - if (mScheduledForRebindingCompanionApplications.isEmpty()) { - out.append("<empty>\n"); - } else { - out.append("\n"); - for (Pair<Integer, String> app : mScheduledForRebindingCompanionApplications) { - out.append("<u").append(String.valueOf(app.first)).append(", ") - .append(app.second).append(">"); - } - } - } - } - - @Nullable - CompanionServiceConnector getPrimaryServiceConnector( - @UserIdInt int userId, @NonNull String packageName) { - final List<CompanionServiceConnector> connectors; - synchronized (mBoundCompanionApplications) { - connectors = mBoundCompanionApplications.get(new Pair<>(userId, packageName)); - } - return connectors != null ? connectors.get(0) : null; - } - - private class CompanionServicesRegister extends PerUser<Map<String, List<ComponentName>>> { - @Override - public synchronized @NonNull Map<String, List<ComponentName>> forUser( - @UserIdInt int userId) { - return super.forUser(userId); - } - - synchronized @NonNull List<ComponentName> forPackage( - @UserIdInt int userId, @NonNull String packageName) { - return forUser(userId).getOrDefault(packageName, Collections.emptyList()); - } - - synchronized void invalidate(@UserIdInt int userId) { - remove(userId); - } - - @Override - protected final @NonNull Map<String, List<ComponentName>> create(@UserIdInt int userId) { - return PackageUtils.getCompanionServicesForUser(mContext, userId); - } - } -} diff --git a/services/companion/java/com/android/server/companion/presence/CompanionDevicePresenceMonitor.java b/services/companion/java/com/android/server/companion/presence/CompanionDevicePresenceMonitor.java new file mode 100644 index 000000000000..7a1a83f53315 --- /dev/null +++ b/services/companion/java/com/android/server/companion/presence/CompanionDevicePresenceMonitor.java @@ -0,0 +1,620 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.companion.presence; + +import static android.companion.DevicePresenceEvent.EVENT_BLE_APPEARED; +import static android.companion.DevicePresenceEvent.EVENT_BLE_DISAPPEARED; +import static android.companion.DevicePresenceEvent.EVENT_BT_CONNECTED; +import static android.companion.DevicePresenceEvent.EVENT_BT_DISCONNECTED; +import static android.companion.DevicePresenceEvent.EVENT_SELF_MANAGED_APPEARED; +import static android.companion.DevicePresenceEvent.EVENT_SELF_MANAGED_DISAPPEARED; +import static android.os.Process.ROOT_UID; +import static android.os.Process.SHELL_UID; + +import android.annotation.NonNull; +import android.annotation.SuppressLint; +import android.annotation.TestApi; +import android.bluetooth.BluetoothAdapter; +import android.bluetooth.BluetoothDevice; +import android.companion.AssociationInfo; +import android.content.Context; +import android.os.Binder; +import android.os.Handler; +import android.os.Looper; +import android.os.Message; +import android.os.ParcelUuid; +import android.os.UserManager; +import android.util.Log; +import android.util.Slog; +import android.util.SparseArray; +import android.util.SparseBooleanArray; + +import com.android.internal.annotations.GuardedBy; +import com.android.server.companion.association.AssociationStore; + +import java.io.PrintWriter; +import java.util.HashSet; +import java.util.Set; + +/** + * Class responsible for monitoring companion devices' "presence" status (i.e. + * connected/disconnected for Bluetooth devices; nearby or not for BLE devices). + * + * <p> + * Should only be used by + * {@link com.android.server.companion.CompanionDeviceManagerService CompanionDeviceManagerService} + * to which it provides the following API: + * <ul> + * <li> {@link #onSelfManagedDeviceConnected(int)} + * <li> {@link #onSelfManagedDeviceDisconnected(int)} + * <li> {@link #isDevicePresent(int)} + * <li> {@link Callback#onDeviceAppeared(int) Callback.onDeviceAppeared(int)} + * <li> {@link Callback#onDeviceDisappeared(int) Callback.onDeviceDisappeared(int)} + * <li> {@link Callback#onDevicePresenceEvent(int, int)}} + * </ul> + */ +@SuppressLint("LongLogTag") +public class CompanionDevicePresenceMonitor implements AssociationStore.OnChangeListener, + BluetoothCompanionDeviceConnectionListener.Callback, BleCompanionDeviceScanner.Callback { + static final boolean DEBUG = false; + private static final String TAG = "CDM_CompanionDevicePresenceMonitor"; + + /** Callback for notifying about changes to status of companion devices. */ + public interface Callback { + /** Invoked when companion device is found nearby or connects. */ + void onDeviceAppeared(int associationId); + + /** Invoked when a companion device no longer seen nearby or disconnects. */ + void onDeviceDisappeared(int associationId); + + /** Invoked when device has corresponding event changes. */ + void onDevicePresenceEvent(int associationId, int event); + + /** Invoked when device has corresponding event changes base on the UUID */ + void onDevicePresenceEventByUuid(ObservableUuid uuid, int event); + } + + private final @NonNull AssociationStore mAssociationStore; + private final @NonNull ObservableUuidStore mObservableUuidStore; + private final @NonNull Callback mCallback; + private final @NonNull BluetoothCompanionDeviceConnectionListener mBtConnectionListener; + private final @NonNull BleCompanionDeviceScanner mBleScanner; + + // NOTE: Same association may appear in more than one of the following sets at the same time. + // (E.g. self-managed devices that have MAC addresses, could be reported as present by their + // companion applications, while at the same be connected via BT, or detected nearby by BLE + // scanner) + private final @NonNull Set<Integer> mConnectedBtDevices = new HashSet<>(); + private final @NonNull Set<Integer> mNearbyBleDevices = new HashSet<>(); + private final @NonNull Set<Integer> mReportedSelfManagedDevices = new HashSet<>(); + private final @NonNull Set<ParcelUuid> mConnectedUuidDevices = new HashSet<>(); + @GuardedBy("mBtDisconnectedDevices") + private final @NonNull Set<Integer> mBtDisconnectedDevices = new HashSet<>(); + + // A map to track device presence within 10 seconds of Bluetooth disconnection. + // The key is the association ID, and the boolean value indicates if the device + // was detected again within that time frame. + @GuardedBy("mBtDisconnectedDevices") + private final @NonNull SparseBooleanArray mBtDisconnectedDevicesBlePresence = + new SparseBooleanArray(); + + // Tracking "simulated" presence. Used for debugging and testing only. + private final @NonNull Set<Integer> mSimulated = new HashSet<>(); + private final SimulatedDevicePresenceSchedulerHelper mSchedulerHelper = + new SimulatedDevicePresenceSchedulerHelper(); + + private final BleDeviceDisappearedScheduler mBleDeviceDisappearedScheduler = + new BleDeviceDisappearedScheduler(); + + public CompanionDevicePresenceMonitor(UserManager userManager, + @NonNull AssociationStore associationStore, + @NonNull ObservableUuidStore observableUuidStore, @NonNull Callback callback) { + mAssociationStore = associationStore; + mObservableUuidStore = observableUuidStore; + mCallback = callback; + mBtConnectionListener = new BluetoothCompanionDeviceConnectionListener(userManager, + associationStore, mObservableUuidStore, + /* BluetoothCompanionDeviceConnectionListener.Callback */ this); + mBleScanner = new BleCompanionDeviceScanner(associationStore, + /* BleCompanionDeviceScanner.Callback */ this); + } + + /** Initialize {@link CompanionDevicePresenceMonitor} */ + public void init(Context context) { + if (DEBUG) Log.i(TAG, "init()"); + + final BluetoothAdapter btAdapter = BluetoothAdapter.getDefaultAdapter(); + if (btAdapter != null) { + mBtConnectionListener.init(btAdapter); + mBleScanner.init(context, btAdapter); + } else { + Log.w(TAG, "BluetoothAdapter is NOT available."); + } + + mAssociationStore.registerLocalListener(this); + } + + /** + * @return current connected UUID devices. + */ + public Set<ParcelUuid> getCurrentConnectedUuidDevices() { + return mConnectedUuidDevices; + } + + /** + * Remove current connected UUID device. + */ + public void removeCurrentConnectedUuidDevice(ParcelUuid uuid) { + mConnectedUuidDevices.remove(uuid); + } + + /** + * @return whether the associated companion devices is present. I.e. device is nearby (for BLE); + * or devices is connected (for Bluetooth); or reported (by the application) to be + * nearby (for "self-managed" associations). + */ + public boolean isDevicePresent(int associationId) { + return mReportedSelfManagedDevices.contains(associationId) + || mConnectedBtDevices.contains(associationId) + || mNearbyBleDevices.contains(associationId) + || mSimulated.contains(associationId); + } + + /** + * @return whether the current uuid to be observed is present. + */ + public boolean isDeviceUuidPresent(ParcelUuid uuid) { + return mConnectedUuidDevices.contains(uuid); + } + + /** + * @return whether the current device is BT connected and had already reported to the app. + */ + + public boolean isBtConnected(int associationId) { + return mConnectedBtDevices.contains(associationId); + } + + /** + * @return whether the current device in BLE range and had already reported to the app. + */ + public boolean isBlePresent(int associationId) { + return mNearbyBleDevices.contains(associationId); + } + + /** + * @return whether the current device had been already reported by the simulator. + */ + public boolean isSimulatePresent(int associationId) { + return mSimulated.contains(associationId); + } + + /** + * Marks a "self-managed" device as connected. + * + * <p> + * Must ONLY be invoked by the + * {@link com.android.server.companion.CompanionDeviceManagerService CompanionDeviceManagerService} + * when an application invokes + * {@link android.companion.CompanionDeviceManager#notifyDeviceAppeared(int) notifyDeviceAppeared()} + */ + public void onSelfManagedDeviceConnected(int associationId) { + onDevicePresenceEvent(mReportedSelfManagedDevices, + associationId, EVENT_SELF_MANAGED_APPEARED); + } + + /** + * Marks a "self-managed" device as disconnected. + * + * <p> + * Must ONLY be invoked by the + * {@link com.android.server.companion.CompanionDeviceManagerService CompanionDeviceManagerService} + * when an application invokes + * {@link android.companion.CompanionDeviceManager#notifyDeviceDisappeared(int) notifyDeviceDisappeared()} + */ + public void onSelfManagedDeviceDisconnected(int associationId) { + onDevicePresenceEvent(mReportedSelfManagedDevices, + associationId, EVENT_SELF_MANAGED_DISAPPEARED); + } + + /** + * Marks a "self-managed" device as disconnected when binderDied. + */ + public void onSelfManagedDeviceReporterBinderDied(int associationId) { + onDevicePresenceEvent(mReportedSelfManagedDevices, + associationId, EVENT_SELF_MANAGED_DISAPPEARED); + } + + @Override + public void onBluetoothCompanionDeviceConnected(int associationId) { + synchronized (mBtDisconnectedDevices) { + // A device is considered reconnected within 10 seconds if a pending BLE lost report is + // followed by a detected Bluetooth connection. + boolean isReconnected = mBtDisconnectedDevices.contains(associationId); + if (isReconnected) { + Slog.i(TAG, "Device ( " + associationId + " ) is reconnected within 10s."); + mBleDeviceDisappearedScheduler.unScheduleDeviceDisappeared(associationId); + } + + Slog.i(TAG, "onBluetoothCompanionDeviceConnected: " + + "associationId( " + associationId + " )"); + onDevicePresenceEvent(mConnectedBtDevices, associationId, EVENT_BT_CONNECTED); + + // Stop the BLE scan if all devices report BT connected status and BLE was present. + if (canStopBleScan()) { + mBleScanner.stopScanIfNeeded(); + } + + } + } + + @Override + public void onBluetoothCompanionDeviceDisconnected(int associationId) { + Slog.i(TAG, "onBluetoothCompanionDeviceDisconnected " + + "associationId( " + associationId + " )"); + // Start BLE scanning when the device is disconnected. + mBleScanner.startScan(); + + onDevicePresenceEvent(mConnectedBtDevices, associationId, EVENT_BT_DISCONNECTED); + // If current device is BLE present but BT is disconnected , means it will be + // potentially out of range later. Schedule BLE disappeared callback. + if (isBlePresent(associationId)) { + synchronized (mBtDisconnectedDevices) { + mBtDisconnectedDevices.add(associationId); + } + mBleDeviceDisappearedScheduler.scheduleBleDeviceDisappeared(associationId); + } + } + + @Override + public void onDevicePresenceEventByUuid(ObservableUuid uuid, int event) { + final ParcelUuid parcelUuid = uuid.getUuid(); + + switch(event) { + case EVENT_BT_CONNECTED: + boolean added = mConnectedUuidDevices.add(parcelUuid); + + if (!added) { + Slog.w(TAG, "Uuid= " + parcelUuid + "is ALREADY reported as " + + "present by this event=" + event); + } + + break; + case EVENT_BT_DISCONNECTED: + final boolean removed = mConnectedUuidDevices.remove(parcelUuid); + + if (!removed) { + Slog.w(TAG, "UUID= " + parcelUuid + " was NOT reported " + + "as present by this event= " + event); + + return; + } + + break; + } + + mCallback.onDevicePresenceEventByUuid(uuid, event); + } + + + @Override + public void onBleCompanionDeviceFound(int associationId) { + onDevicePresenceEvent(mNearbyBleDevices, associationId, EVENT_BLE_APPEARED); + synchronized (mBtDisconnectedDevices) { + final boolean isCurrentPresent = mBtDisconnectedDevicesBlePresence.get(associationId); + if (mBtDisconnectedDevices.contains(associationId) && isCurrentPresent) { + mBleDeviceDisappearedScheduler.unScheduleDeviceDisappeared(associationId); + } + } + } + + @Override + public void onBleCompanionDeviceLost(int associationId) { + onDevicePresenceEvent(mNearbyBleDevices, associationId, EVENT_BLE_DISAPPEARED); + } + + /** FOR DEBUGGING AND/OR TESTING PURPOSES ONLY. */ + @TestApi + public void simulateDeviceEvent(int associationId, int event) { + // IMPORTANT: this API should only be invoked via the + // 'companiondevice simulate-device-appeared' Shell command, so the only uid-s allowed to + // make this call are SHELL and ROOT. + // No other caller (including SYSTEM!) should be allowed. + enforceCallerShellOrRoot(); + // Make sure the association exists. + enforceAssociationExists(associationId); + + switch (event) { + case EVENT_BLE_APPEARED: + simulateDeviceAppeared(associationId, event); + break; + case EVENT_BT_CONNECTED: + onBluetoothCompanionDeviceConnected(associationId); + break; + case EVENT_BLE_DISAPPEARED: + simulateDeviceDisappeared(associationId, event); + break; + case EVENT_BT_DISCONNECTED: + onBluetoothCompanionDeviceDisconnected(associationId); + break; + default: + throw new IllegalArgumentException("Event: " + event + "is not supported"); + } + } + + /** FOR DEBUGGING AND/OR TESTING PURPOSES ONLY. */ + @TestApi + public void simulateDeviceEventByUuid(ObservableUuid uuid, int event) { + // IMPORTANT: this API should only be invoked via the + // 'companiondevice simulate-device-uuid-events' Shell command, so the only uid-s allowed to + // make this call are SHELL and ROOT. + // No other caller (including SYSTEM!) should be allowed. + enforceCallerShellOrRoot(); + onDevicePresenceEventByUuid(uuid, event); + } + + private void simulateDeviceAppeared(int associationId, int state) { + onDevicePresenceEvent(mSimulated, associationId, state); + mSchedulerHelper.scheduleOnDeviceGoneCallForSimulatedDevicePresence(associationId); + } + + private void simulateDeviceDisappeared(int associationId, int state) { + mSchedulerHelper.unscheduleOnDeviceGoneCallForSimulatedDevicePresence(associationId); + onDevicePresenceEvent(mSimulated, associationId, state); + } + + private void enforceAssociationExists(int associationId) { + if (mAssociationStore.getAssociationById(associationId) == null) { + throw new IllegalArgumentException( + "Association with id " + associationId + " does not exist."); + } + } + + private void onDevicePresenceEvent(@NonNull Set<Integer> presentDevicesForSource, + int associationId, int event) { + Slog.i(TAG, "onDevicePresenceEvent() id=" + associationId + ", event=" + event); + + switch (event) { + case EVENT_BLE_APPEARED: + synchronized (mBtDisconnectedDevices) { + // If a BLE device is detected within 10 seconds after BT is disconnected, + // flag it as BLE is present. + if (mBtDisconnectedDevices.contains(associationId)) { + Slog.i(TAG, "Device ( " + associationId + " ) is present," + + " do not need to send the callback with event ( " + + EVENT_BLE_APPEARED + " )."); + mBtDisconnectedDevicesBlePresence.append(associationId, true); + } + } + case EVENT_BT_CONNECTED: + case EVENT_SELF_MANAGED_APPEARED: + final boolean added = presentDevicesForSource.add(associationId); + + if (!added) { + Slog.w(TAG, "Association with id " + + associationId + " is ALREADY reported as " + + "present by this source, event=" + event); + } + + mCallback.onDeviceAppeared(associationId); + + break; + case EVENT_BLE_DISAPPEARED: + case EVENT_BT_DISCONNECTED: + case EVENT_SELF_MANAGED_DISAPPEARED: + final boolean removed = presentDevicesForSource.remove(associationId); + + if (!removed) { + Slog.w(TAG, "Association with id " + associationId + " was NOT reported " + + "as present by this source, event= " + event); + + return; + } + + mCallback.onDeviceDisappeared(associationId); + + break; + default: + Slog.e(TAG, "Event: " + event + " is not supported"); + return; + } + + mCallback.onDevicePresenceEvent(associationId, event); + } + + /** + * Implements + * {@link AssociationStore.OnChangeListener#onAssociationRemoved(AssociationInfo)} + */ + @Override + public void onAssociationRemoved(@NonNull AssociationInfo association) { + final int id = association.getId(); + if (DEBUG) { + Log.i(TAG, "onAssociationRemoved() id=" + id); + Log.d(TAG, " > association=" + association); + } + + mConnectedBtDevices.remove(id); + mNearbyBleDevices.remove(id); + mReportedSelfManagedDevices.remove(id); + mSimulated.remove(id); + mBtDisconnectedDevices.remove(id); + mBtDisconnectedDevicesBlePresence.delete(id); + + // Do NOT call mCallback.onDeviceDisappeared()! + // CompanionDeviceManagerService will know that the association is removed, and will do + // what's needed. + } + + /** + * Return a set of devices that pending to report connectivity + */ + public SparseArray<Set<BluetoothDevice>> getPendingConnectedDevices() { + synchronized (mBtConnectionListener.mPendingConnectedDevices) { + return mBtConnectionListener.mPendingConnectedDevices; + } + } + + private static void enforceCallerShellOrRoot() { + final int callingUid = Binder.getCallingUid(); + if (callingUid == SHELL_UID || callingUid == ROOT_UID) return; + + throw new SecurityException("Caller is neither Shell nor Root"); + } + + /** + * The BLE scan can be only stopped if all the devices have been reported + * BT connected and BLE presence and are not pending to report BLE lost. + */ + private boolean canStopBleScan() { + for (AssociationInfo ai : mAssociationStore.getActiveAssociations()) { + int id = ai.getId(); + synchronized (mBtDisconnectedDevices) { + if (ai.isNotifyOnDeviceNearby() && !(isBtConnected(id) + && isBlePresent(id) && mBtDisconnectedDevices.isEmpty())) { + Slog.i(TAG, "The BLE scan cannot be stopped, " + + "device( " + id + " ) is not yet connected " + + "OR the BLE is not current present Or is pending to report BLE lost"); + return false; + } + } + } + return true; + } + + /** + * Dumps system information about devices that are marked as "present". + */ + public void dump(@NonNull PrintWriter out) { + out.append("Companion Device Present: "); + if (mConnectedBtDevices.isEmpty() + && mNearbyBleDevices.isEmpty() + && mReportedSelfManagedDevices.isEmpty()) { + out.append("<empty>\n"); + return; + } else { + out.append("\n"); + } + + out.append(" Connected Bluetooth Devices: "); + if (mConnectedBtDevices.isEmpty()) { + out.append("<empty>\n"); + } else { + out.append("\n"); + for (int associationId : mConnectedBtDevices) { + AssociationInfo a = mAssociationStore.getAssociationById(associationId); + out.append(" ").append(a.toShortString()).append('\n'); + } + } + + out.append(" Nearby BLE Devices: "); + if (mNearbyBleDevices.isEmpty()) { + out.append("<empty>\n"); + } else { + out.append("\n"); + for (int associationId : mNearbyBleDevices) { + AssociationInfo a = mAssociationStore.getAssociationById(associationId); + out.append(" ").append(a.toShortString()).append('\n'); + } + } + + out.append(" Self-Reported Devices: "); + if (mReportedSelfManagedDevices.isEmpty()) { + out.append("<empty>\n"); + } else { + out.append("\n"); + for (int associationId : mReportedSelfManagedDevices) { + AssociationInfo a = mAssociationStore.getAssociationById(associationId); + out.append(" ").append(a.toShortString()).append('\n'); + } + } + } + + private class SimulatedDevicePresenceSchedulerHelper extends Handler { + SimulatedDevicePresenceSchedulerHelper() { + super(Looper.getMainLooper()); + } + + void scheduleOnDeviceGoneCallForSimulatedDevicePresence(int associationId) { + // First, unschedule if it was scheduled previously. + if (hasMessages(/* what */ associationId)) { + removeMessages(/* what */ associationId); + } + + sendEmptyMessageDelayed(/* what */ associationId, 60 * 1000 /* 60 seconds */); + } + + void unscheduleOnDeviceGoneCallForSimulatedDevicePresence(int associationId) { + removeMessages(/* what */ associationId); + } + + @Override + public void handleMessage(@NonNull Message msg) { + final int associationId = msg.what; + if (mSimulated.contains(associationId)) { + onDevicePresenceEvent(mSimulated, associationId, EVENT_BLE_DISAPPEARED); + } + } + } + + private class BleDeviceDisappearedScheduler extends Handler { + BleDeviceDisappearedScheduler() { + super(Looper.getMainLooper()); + } + + void scheduleBleDeviceDisappeared(int associationId) { + if (hasMessages(associationId)) { + removeMessages(associationId); + } + Slog.i(TAG, "scheduleBleDeviceDisappeared for Device: ( " + associationId + " )."); + sendEmptyMessageDelayed(associationId, 10 * 1000 /* 10 seconds */); + } + + void unScheduleDeviceDisappeared(int associationId) { + if (hasMessages(associationId)) { + Slog.i(TAG, "unScheduleDeviceDisappeared for Device( " + associationId + " )"); + synchronized (mBtDisconnectedDevices) { + mBtDisconnectedDevices.remove(associationId); + mBtDisconnectedDevicesBlePresence.delete(associationId); + } + + removeMessages(associationId); + } + } + + @Override + public void handleMessage(@NonNull Message msg) { + final int associationId = msg.what; + synchronized (mBtDisconnectedDevices) { + final boolean isCurrentPresent = mBtDisconnectedDevicesBlePresence.get( + associationId); + // If a device hasn't reported after 10 seconds and is not currently present, + // assume BLE is lost and trigger the onDeviceEvent callback with the + // EVENT_BLE_DISAPPEARED event. + if (mBtDisconnectedDevices.contains(associationId) + && !isCurrentPresent) { + Slog.i(TAG, "Device ( " + associationId + " ) is likely BLE out of range, " + + "sending callback with event ( " + EVENT_BLE_DISAPPEARED + " )"); + onDevicePresenceEvent(mNearbyBleDevices, associationId, EVENT_BLE_DISAPPEARED); + } + + mBtDisconnectedDevices.remove(associationId); + mBtDisconnectedDevicesBlePresence.delete(associationId); + } + } + } +} diff --git a/services/companion/java/com/android/server/companion/presence/DevicePresenceProcessor.java b/services/companion/java/com/android/server/companion/presence/DevicePresenceProcessor.java deleted file mode 100644 index 642460e64216..000000000000 --- a/services/companion/java/com/android/server/companion/presence/DevicePresenceProcessor.java +++ /dev/null @@ -1,1042 +0,0 @@ -/* - * Copyright (C) 2022 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.server.companion.presence; - -import static android.companion.AssociationRequest.DEVICE_PROFILE_AUTOMOTIVE_PROJECTION; -import static android.companion.DevicePresenceEvent.EVENT_BLE_APPEARED; -import static android.companion.DevicePresenceEvent.EVENT_BLE_DISAPPEARED; -import static android.companion.DevicePresenceEvent.EVENT_BT_CONNECTED; -import static android.companion.DevicePresenceEvent.EVENT_BT_DISCONNECTED; -import static android.companion.DevicePresenceEvent.EVENT_SELF_MANAGED_APPEARED; -import static android.companion.DevicePresenceEvent.EVENT_SELF_MANAGED_DISAPPEARED; -import static android.companion.DevicePresenceEvent.NO_ASSOCIATION; -import static android.os.Process.ROOT_UID; -import static android.os.Process.SHELL_UID; - -import static com.android.server.companion.utils.PermissionsUtils.enforceCallerCanManageAssociationsForPackage; -import static com.android.server.companion.utils.PermissionsUtils.enforceCallerCanObserveDevicePresenceByUuid; - -import android.annotation.NonNull; -import android.annotation.SuppressLint; -import android.annotation.TestApi; -import android.annotation.UserIdInt; -import android.bluetooth.BluetoothAdapter; -import android.bluetooth.BluetoothDevice; -import android.companion.AssociationInfo; -import android.companion.DeviceNotAssociatedException; -import android.companion.DevicePresenceEvent; -import android.companion.ObservingDevicePresenceRequest; -import android.content.Context; -import android.hardware.power.Mode; -import android.os.Binder; -import android.os.Handler; -import android.os.Looper; -import android.os.Message; -import android.os.ParcelUuid; -import android.os.PowerManagerInternal; -import android.os.RemoteException; -import android.os.UserManager; -import android.util.Log; -import android.util.Slog; -import android.util.SparseArray; -import android.util.SparseBooleanArray; - -import com.android.internal.annotations.GuardedBy; -import com.android.server.companion.association.AssociationStore; - -import java.io.PrintWriter; -import java.util.HashSet; -import java.util.List; -import java.util.Set; - -/** - * Class responsible for monitoring companion devices' "presence" status (i.e. - * connected/disconnected for Bluetooth devices; nearby or not for BLE devices). - * - * <p> - * Should only be used by - * {@link com.android.server.companion.CompanionDeviceManagerService CompanionDeviceManagerService} - * to which it provides the following API: - * <ul> - * <li> {@link #onSelfManagedDeviceConnected(int)} - * <li> {@link #onSelfManagedDeviceDisconnected(int)} - * <li> {@link #isDevicePresent(int)} - * </ul> - */ -@SuppressLint("LongLogTag") -public class DevicePresenceProcessor implements AssociationStore.OnChangeListener, - BluetoothCompanionDeviceConnectionListener.Callback, BleCompanionDeviceScanner.Callback { - static final boolean DEBUG = false; - private static final String TAG = "CDM_DevicePresenceProcessor"; - - @NonNull - private final Context mContext; - @NonNull - private final CompanionAppBinder mCompanionAppBinder; - @NonNull - private final AssociationStore mAssociationStore; - @NonNull - private final ObservableUuidStore mObservableUuidStore; - @NonNull - private final BluetoothCompanionDeviceConnectionListener mBtConnectionListener; - @NonNull - private final BleCompanionDeviceScanner mBleScanner; - @NonNull - private final PowerManagerInternal mPowerManagerInternal; - - // NOTE: Same association may appear in more than one of the following sets at the same time. - // (E.g. self-managed devices that have MAC addresses, could be reported as present by their - // companion applications, while at the same be connected via BT, or detected nearby by BLE - // scanner) - @NonNull - private final Set<Integer> mConnectedBtDevices = new HashSet<>(); - @NonNull - private final Set<Integer> mNearbyBleDevices = new HashSet<>(); - @NonNull - private final Set<Integer> mReportedSelfManagedDevices = new HashSet<>(); - @NonNull - private final Set<ParcelUuid> mConnectedUuidDevices = new HashSet<>(); - @NonNull - @GuardedBy("mBtDisconnectedDevices") - private final Set<Integer> mBtDisconnectedDevices = new HashSet<>(); - - // A map to track device presence within 10 seconds of Bluetooth disconnection. - // The key is the association ID, and the boolean value indicates if the device - // was detected again within that time frame. - @GuardedBy("mBtDisconnectedDevices") - private final @NonNull SparseBooleanArray mBtDisconnectedDevicesBlePresence = - new SparseBooleanArray(); - - // Tracking "simulated" presence. Used for debugging and testing only. - private final @NonNull Set<Integer> mSimulated = new HashSet<>(); - private final SimulatedDevicePresenceSchedulerHelper mSchedulerHelper = - new SimulatedDevicePresenceSchedulerHelper(); - - private final BleDeviceDisappearedScheduler mBleDeviceDisappearedScheduler = - new BleDeviceDisappearedScheduler(); - - public DevicePresenceProcessor(@NonNull Context context, - @NonNull CompanionAppBinder companionAppBinder, - UserManager userManager, - @NonNull AssociationStore associationStore, - @NonNull ObservableUuidStore observableUuidStore, - @NonNull PowerManagerInternal powerManagerInternal) { - mContext = context; - mCompanionAppBinder = companionAppBinder; - mAssociationStore = associationStore; - mObservableUuidStore = observableUuidStore; - mBtConnectionListener = new BluetoothCompanionDeviceConnectionListener(userManager, - associationStore, mObservableUuidStore, - /* BluetoothCompanionDeviceConnectionListener.Callback */ this); - mBleScanner = new BleCompanionDeviceScanner(associationStore, - /* BleCompanionDeviceScanner.Callback */ this); - mPowerManagerInternal = powerManagerInternal; - } - - /** Initialize {@link DevicePresenceProcessor} */ - public void init(Context context) { - if (DEBUG) Slog.i(TAG, "init()"); - - final BluetoothAdapter btAdapter = BluetoothAdapter.getDefaultAdapter(); - if (btAdapter != null) { - mBtConnectionListener.init(btAdapter); - mBleScanner.init(context, btAdapter); - } else { - Slog.w(TAG, "BluetoothAdapter is NOT available."); - } - - mAssociationStore.registerLocalListener(this); - } - - /** - * Process device presence start request. - */ - public void startObservingDevicePresence(ObservingDevicePresenceRequest request, - String packageName, int userId) { - Slog.i(TAG, - "Start observing request=[" + request + "] for userId=[" + userId + "], package=[" - + packageName + "]..."); - final ParcelUuid requestUuid = request.getUuid(); - - if (requestUuid != null) { - enforceCallerCanObserveDevicePresenceByUuid(mContext); - - // If it's already being observed, then no-op. - if (mObservableUuidStore.isUuidBeingObserved(requestUuid, userId, packageName)) { - Slog.i(TAG, "UUID=[" + requestUuid + "], package=[" + packageName + "], userId=[" - + userId + "] is already being observed."); - return; - } - - final ObservableUuid observableUuid = new ObservableUuid(userId, requestUuid, - packageName, System.currentTimeMillis()); - mObservableUuidStore.writeObservableUuid(userId, observableUuid); - } else { - final int associationId = request.getAssociationId(); - AssociationInfo association = mAssociationStore.getAssociationWithCallerChecks( - associationId); - - // If it's already being observed, then no-op. - if (association.isNotifyOnDeviceNearby()) { - Slog.i(TAG, "Associated device id=[" + association.getId() - + "] is already being observed. No-op."); - return; - } - - association = (new AssociationInfo.Builder(association)).setNotifyOnDeviceNearby(true) - .build(); - mAssociationStore.updateAssociation(association); - - // Send callback immediately if the device is present. - if (isDevicePresent(associationId)) { - Slog.i(TAG, "Device is already present. Triggering callback."); - if (isBlePresent(associationId)) { - onDevicePresenceEvent(mNearbyBleDevices, associationId, EVENT_BLE_APPEARED); - } else if (isBtConnected(associationId)) { - onDevicePresenceEvent(mConnectedBtDevices, associationId, EVENT_BT_CONNECTED); - } else if (isSimulatePresent(associationId)) { - onDevicePresenceEvent(mSimulated, associationId, EVENT_BLE_APPEARED); - } - } - } - - Slog.i(TAG, "Registered device presence listener."); - } - - /** - * Process device presence stop request. - */ - public void stopObservingDevicePresence(ObservingDevicePresenceRequest request, - String packageName, int userId) { - Slog.i(TAG, - "Stop observing request=[" + request + "] for userId=[" + userId + "], package=[" - + packageName + "]..."); - - final ParcelUuid requestUuid = request.getUuid(); - - if (requestUuid != null) { - enforceCallerCanObserveDevicePresenceByUuid(mContext); - - if (!mObservableUuidStore.isUuidBeingObserved(requestUuid, userId, packageName)) { - Slog.i(TAG, "UUID=[" + requestUuid + "], package=[" + packageName + "], userId=[" - + userId + "] is already not being observed."); - return; - } - - mObservableUuidStore.removeObservableUuid(userId, requestUuid, packageName); - removeCurrentConnectedUuidDevice(requestUuid); - } else { - final int associationId = request.getAssociationId(); - AssociationInfo association = mAssociationStore.getAssociationWithCallerChecks( - associationId); - - // If it's already being observed, then no-op. - if (!association.isNotifyOnDeviceNearby()) { - Slog.i(TAG, "Associated device id=[" + association.getId() - + "] is already not being observed. No-op."); - return; - } - - association = (new AssociationInfo.Builder(association)).setNotifyOnDeviceNearby(false) - .build(); - mAssociationStore.updateAssociation(association); - } - - Slog.i(TAG, "Unregistered device presence listener."); - - // If last listener is unregistered, then unbind application. - if (!shouldBindPackage(userId, packageName)) { - mCompanionAppBinder.unbindCompanionApp(userId, packageName); - } - } - - /** - * For legacy device presence below Android V. - * - * @deprecated Use {@link #startObservingDevicePresence(ObservingDevicePresenceRequest, String, - * int)} - */ - @Deprecated - public void startObservingDevicePresence(int userId, String packageName, String deviceAddress) - throws RemoteException { - Slog.i(TAG, - "Start observing device=[" + deviceAddress + "] for userId=[" + userId - + "], package=[" - + packageName + "]..."); - - enforceCallerCanManageAssociationsForPackage(mContext, userId, packageName, null); - - AssociationInfo association = mAssociationStore.getFirstAssociationByAddress(userId, - packageName, deviceAddress); - - if (association == null) { - throw new RemoteException(new DeviceNotAssociatedException("App " + packageName - + " is not associated with device " + deviceAddress - + " for user " + userId)); - } - - startObservingDevicePresence( - new ObservingDevicePresenceRequest.Builder().setAssociationId(association.getId()) - .build(), packageName, userId); - } - - /** - * For legacy device presence below Android V. - * - * @deprecated Use {@link #stopObservingDevicePresence(ObservingDevicePresenceRequest, String, - * int)} - */ - @Deprecated - public void stopObservingDevicePresence(int userId, String packageName, String deviceAddress) - throws RemoteException { - Slog.i(TAG, - "Stop observing device=[" + deviceAddress + "] for userId=[" + userId - + "], package=[" - + packageName + "]..."); - - enforceCallerCanManageAssociationsForPackage(mContext, userId, packageName, null); - - AssociationInfo association = mAssociationStore.getFirstAssociationByAddress(userId, - packageName, deviceAddress); - - if (association == null) { - throw new RemoteException(new DeviceNotAssociatedException("App " + packageName - + " is not associated with device " + deviceAddress - + " for user " + userId)); - } - - stopObservingDevicePresence( - new ObservingDevicePresenceRequest.Builder().setAssociationId(association.getId()) - .build(), packageName, userId); - } - - /** - * @return whether the package should be bound (i.e. at least one of the devices associated with - * the package is currently present OR the UUID to be observed by this package is - * currently present). - */ - private boolean shouldBindPackage(@UserIdInt int userId, @NonNull String packageName) { - final List<AssociationInfo> packageAssociations = - mAssociationStore.getActiveAssociationsByPackage(userId, packageName); - final List<ObservableUuid> observableUuids = - mObservableUuidStore.getObservableUuidsForPackage(userId, packageName); - - for (AssociationInfo association : packageAssociations) { - if (!association.shouldBindWhenPresent()) continue; - if (isDevicePresent(association.getId())) return true; - } - - for (ObservableUuid uuid : observableUuids) { - if (isDeviceUuidPresent(uuid.getUuid())) { - return true; - } - } - - return false; - } - - /** - * Bind the system to the app if it's not bound. - * - * Set bindImportant to true when the association is self-managed to avoid the target service - * being killed. - */ - private void bindApplicationIfNeeded(int userId, String packageName, boolean bindImportant) { - if (!mCompanionAppBinder.isCompanionApplicationBound(userId, packageName)) { - mCompanionAppBinder.bindCompanionApp( - userId, packageName, bindImportant, this::onBinderDied); - } else { - Slog.i(TAG, - "UserId=[" + userId + "], packageName=[" + packageName + "] is already bound."); - } - } - - /** - * @return current connected UUID devices. - */ - public Set<ParcelUuid> getCurrentConnectedUuidDevices() { - return mConnectedUuidDevices; - } - - /** - * Remove current connected UUID device. - */ - public void removeCurrentConnectedUuidDevice(ParcelUuid uuid) { - mConnectedUuidDevices.remove(uuid); - } - - /** - * @return whether the associated companion devices is present. I.e. device is nearby (for BLE); - * or devices is connected (for Bluetooth); or reported (by the application) to be - * nearby (for "self-managed" associations). - */ - public boolean isDevicePresent(int associationId) { - return mReportedSelfManagedDevices.contains(associationId) - || mConnectedBtDevices.contains(associationId) - || mNearbyBleDevices.contains(associationId) - || mSimulated.contains(associationId); - } - - /** - * @return whether the current uuid to be observed is present. - */ - public boolean isDeviceUuidPresent(ParcelUuid uuid) { - return mConnectedUuidDevices.contains(uuid); - } - - /** - * @return whether the current device is BT connected and had already reported to the app. - */ - - public boolean isBtConnected(int associationId) { - return mConnectedBtDevices.contains(associationId); - } - - /** - * @return whether the current device in BLE range and had already reported to the app. - */ - public boolean isBlePresent(int associationId) { - return mNearbyBleDevices.contains(associationId); - } - - /** - * @return whether the current device had been already reported by the simulator. - */ - public boolean isSimulatePresent(int associationId) { - return mSimulated.contains(associationId); - } - - /** - * Marks a "self-managed" device as connected. - * - * <p> - * Must ONLY be invoked by the - * {@link com.android.server.companion.CompanionDeviceManagerService - * CompanionDeviceManagerService} - * when an application invokes - * {@link android.companion.CompanionDeviceManager#notifyDeviceAppeared(int) - * notifyDeviceAppeared()} - */ - public void onSelfManagedDeviceConnected(int associationId) { - onDevicePresenceEvent(mReportedSelfManagedDevices, - associationId, EVENT_SELF_MANAGED_APPEARED); - } - - /** - * Marks a "self-managed" device as disconnected. - * - * <p> - * Must ONLY be invoked by the - * {@link com.android.server.companion.CompanionDeviceManagerService - * CompanionDeviceManagerService} - * when an application invokes - * {@link android.companion.CompanionDeviceManager#notifyDeviceDisappeared(int) - * notifyDeviceDisappeared()} - */ - public void onSelfManagedDeviceDisconnected(int associationId) { - onDevicePresenceEvent(mReportedSelfManagedDevices, - associationId, EVENT_SELF_MANAGED_DISAPPEARED); - } - - /** - * Marks a "self-managed" device as disconnected when binderDied. - */ - public void onSelfManagedDeviceReporterBinderDied(int associationId) { - onDevicePresenceEvent(mReportedSelfManagedDevices, - associationId, EVENT_SELF_MANAGED_DISAPPEARED); - } - - @Override - public void onBluetoothCompanionDeviceConnected(int associationId) { - synchronized (mBtDisconnectedDevices) { - // A device is considered reconnected within 10 seconds if a pending BLE lost report is - // followed by a detected Bluetooth connection. - boolean isReconnected = mBtDisconnectedDevices.contains(associationId); - if (isReconnected) { - Slog.i(TAG, "Device ( " + associationId + " ) is reconnected within 10s."); - mBleDeviceDisappearedScheduler.unScheduleDeviceDisappeared(associationId); - } - - Slog.i(TAG, "onBluetoothCompanionDeviceConnected: " - + "associationId( " + associationId + " )"); - onDevicePresenceEvent(mConnectedBtDevices, associationId, EVENT_BT_CONNECTED); - - // Stop the BLE scan if all devices report BT connected status and BLE was present. - if (canStopBleScan()) { - mBleScanner.stopScanIfNeeded(); - } - - } - } - - @Override - public void onBluetoothCompanionDeviceDisconnected(int associationId) { - Slog.i(TAG, "onBluetoothCompanionDeviceDisconnected " - + "associationId( " + associationId + " )"); - // Start BLE scanning when the device is disconnected. - mBleScanner.startScan(); - - onDevicePresenceEvent(mConnectedBtDevices, associationId, EVENT_BT_DISCONNECTED); - // If current device is BLE present but BT is disconnected , means it will be - // potentially out of range later. Schedule BLE disappeared callback. - if (isBlePresent(associationId)) { - synchronized (mBtDisconnectedDevices) { - mBtDisconnectedDevices.add(associationId); - } - mBleDeviceDisappearedScheduler.scheduleBleDeviceDisappeared(associationId); - } - } - - - @Override - public void onBleCompanionDeviceFound(int associationId) { - onDevicePresenceEvent(mNearbyBleDevices, associationId, EVENT_BLE_APPEARED); - synchronized (mBtDisconnectedDevices) { - final boolean isCurrentPresent = mBtDisconnectedDevicesBlePresence.get(associationId); - if (mBtDisconnectedDevices.contains(associationId) && isCurrentPresent) { - mBleDeviceDisappearedScheduler.unScheduleDeviceDisappeared(associationId); - } - } - } - - @Override - public void onBleCompanionDeviceLost(int associationId) { - onDevicePresenceEvent(mNearbyBleDevices, associationId, EVENT_BLE_DISAPPEARED); - } - - /** FOR DEBUGGING AND/OR TESTING PURPOSES ONLY. */ - @TestApi - public void simulateDeviceEvent(int associationId, int event) { - // IMPORTANT: this API should only be invoked via the - // 'companiondevice simulate-device-appeared' Shell command, so the only uid-s allowed to - // make this call are SHELL and ROOT. - // No other caller (including SYSTEM!) should be allowed. - enforceCallerShellOrRoot(); - // Make sure the association exists. - enforceAssociationExists(associationId); - - switch (event) { - case EVENT_BLE_APPEARED: - simulateDeviceAppeared(associationId, event); - break; - case EVENT_BT_CONNECTED: - onBluetoothCompanionDeviceConnected(associationId); - break; - case EVENT_BLE_DISAPPEARED: - simulateDeviceDisappeared(associationId, event); - break; - case EVENT_BT_DISCONNECTED: - onBluetoothCompanionDeviceDisconnected(associationId); - break; - default: - throw new IllegalArgumentException("Event: " + event + "is not supported"); - } - } - - /** FOR DEBUGGING AND/OR TESTING PURPOSES ONLY. */ - @TestApi - public void simulateDeviceEventByUuid(ObservableUuid uuid, int event) { - // IMPORTANT: this API should only be invoked via the - // 'companiondevice simulate-device-uuid-events' Shell command, so the only uid-s allowed to - // make this call are SHELL and ROOT. - // No other caller (including SYSTEM!) should be allowed. - enforceCallerShellOrRoot(); - onDevicePresenceEventByUuid(uuid, event); - } - - private void simulateDeviceAppeared(int associationId, int state) { - onDevicePresenceEvent(mSimulated, associationId, state); - mSchedulerHelper.scheduleOnDeviceGoneCallForSimulatedDevicePresence(associationId); - } - - private void simulateDeviceDisappeared(int associationId, int state) { - mSchedulerHelper.unscheduleOnDeviceGoneCallForSimulatedDevicePresence(associationId); - onDevicePresenceEvent(mSimulated, associationId, state); - } - - private void enforceAssociationExists(int associationId) { - if (mAssociationStore.getAssociationById(associationId) == null) { - throw new IllegalArgumentException( - "Association with id " + associationId + " does not exist."); - } - } - - private void onDevicePresenceEvent(@NonNull Set<Integer> presentDevicesForSource, - int associationId, int eventType) { - Slog.i(TAG, - "onDevicePresenceEvent() id=[" + associationId + "], event=[" + eventType + "]..."); - - AssociationInfo association = mAssociationStore.getAssociationById(associationId); - if (association == null) { - Slog.e(TAG, "Association doesn't exist."); - return; - } - - final int userId = association.getUserId(); - final String packageName = association.getPackageName(); - final DevicePresenceEvent event = new DevicePresenceEvent(associationId, eventType, null); - - if (eventType == EVENT_BLE_APPEARED) { - synchronized (mBtDisconnectedDevices) { - // If a BLE device is detected within 10 seconds after BT is disconnected, - // flag it as BLE is present. - if (mBtDisconnectedDevices.contains(associationId)) { - Slog.i(TAG, "Device ( " + associationId + " ) is present," - + " do not need to send the callback with event ( " - + EVENT_BLE_APPEARED + " )."); - mBtDisconnectedDevicesBlePresence.append(associationId, true); - } - } - } - - switch (eventType) { - case EVENT_BLE_APPEARED: - case EVENT_BT_CONNECTED: - case EVENT_SELF_MANAGED_APPEARED: - final boolean added = presentDevicesForSource.add(associationId); - if (!added) { - Slog.w(TAG, "The association is already present."); - } - - if (association.shouldBindWhenPresent()) { - bindApplicationIfNeeded(userId, packageName, association.isSelfManaged()); - } else { - return; - } - - if (association.isSelfManaged() || added) { - notifyDevicePresenceEvent(userId, packageName, event); - // Also send the legacy callback. - legacyNotifyDevicePresenceEvent(association, true); - } - break; - case EVENT_BLE_DISAPPEARED: - case EVENT_BT_DISCONNECTED: - case EVENT_SELF_MANAGED_DISAPPEARED: - final boolean removed = presentDevicesForSource.remove(associationId); - if (!removed) { - Slog.w(TAG, "The association is already NOT present."); - } - - if (!mCompanionAppBinder.isCompanionApplicationBound(userId, packageName)) { - Slog.e(TAG, "Package is not bound"); - return; - } - - if (association.isSelfManaged() || removed) { - notifyDevicePresenceEvent(userId, packageName, event); - // Also send the legacy callback. - legacyNotifyDevicePresenceEvent(association, false); - } - - // Check if there are other devices associated to the app that are present. - if (!shouldBindPackage(userId, packageName)) { - mCompanionAppBinder.unbindCompanionApp(userId, packageName); - } - break; - default: - Slog.e(TAG, "Event: " + eventType + " is not supported."); - break; - } - } - - @Override - public void onDevicePresenceEventByUuid(ObservableUuid uuid, int eventType) { - Slog.i(TAG, "onDevicePresenceEventByUuid ObservableUuid=[" + uuid + "], event=[" + eventType - + "]..."); - - final ParcelUuid parcelUuid = uuid.getUuid(); - final String packageName = uuid.getPackageName(); - final int userId = uuid.getUserId(); - final DevicePresenceEvent event = new DevicePresenceEvent(NO_ASSOCIATION, eventType, - parcelUuid); - - switch (eventType) { - case EVENT_BT_CONNECTED: - boolean added = mConnectedUuidDevices.add(parcelUuid); - if (!added) { - Slog.w(TAG, "This device is already connected."); - } - - bindApplicationIfNeeded(userId, packageName, false); - - notifyDevicePresenceEvent(userId, packageName, event); - break; - case EVENT_BT_DISCONNECTED: - final boolean removed = mConnectedUuidDevices.remove(parcelUuid); - if (!removed) { - Slog.w(TAG, "This device is already disconnected."); - return; - } - - if (!mCompanionAppBinder.isCompanionApplicationBound(userId, packageName)) { - Slog.e(TAG, "Package is not bound."); - return; - } - - notifyDevicePresenceEvent(userId, packageName, event); - - if (!shouldBindPackage(userId, packageName)) { - mCompanionAppBinder.unbindCompanionApp(userId, packageName); - } - break; - default: - Slog.e(TAG, "Event: " + eventType + " is not supported"); - break; - } - } - - /** - * Notify device presence event to the app. - * - * @deprecated Use {@link #notifyDevicePresenceEvent(int, String, DevicePresenceEvent)} instead. - */ - @Deprecated - private void legacyNotifyDevicePresenceEvent(AssociationInfo association, - boolean isAppeared) { - Slog.i(TAG, "legacyNotifyDevicePresenceEvent() association=[" + association.toShortString() - + "], isAppeared=[" + isAppeared + "]"); - - final int userId = association.getUserId(); - final String packageName = association.getPackageName(); - - final CompanionServiceConnector primaryServiceConnector = - mCompanionAppBinder.getPrimaryServiceConnector(userId, packageName); - if (primaryServiceConnector == null) { - Slog.e(TAG, "Package is not bound."); - return; - } - - if (isAppeared) { - primaryServiceConnector.postOnDeviceAppeared(association); - } else { - primaryServiceConnector.postOnDeviceDisappeared(association); - } - } - - /** - * Notify the device presence event to the app. - */ - private void notifyDevicePresenceEvent(int userId, String packageName, - DevicePresenceEvent event) { - Slog.i(TAG, - "notifyCompanionDevicePresenceEvent userId=[" + userId + "], packageName=[" - + packageName + "], event=[" + event + "]..."); - - final CompanionServiceConnector primaryServiceConnector = - mCompanionAppBinder.getPrimaryServiceConnector(userId, packageName); - - if (primaryServiceConnector == null) { - Slog.e(TAG, "Package is NOT bound."); - return; - } - - primaryServiceConnector.postOnDevicePresenceEvent(event); - } - - /** - * Notify the self-managed device presence event to the app. - */ - public void notifySelfManagedDevicePresenceEvent(int associationId, boolean isAppeared) { - Slog.i(TAG, "notifySelfManagedDeviceAppeared() id=" + associationId); - - AssociationInfo association = mAssociationStore.getAssociationWithCallerChecks( - associationId); - if (!association.isSelfManaged()) { - throw new IllegalArgumentException("Association id=[" + associationId - + "] is not self-managed."); - } - // AssociationInfo class is immutable: create a new AssociationInfo object with updated - // timestamp. - association = (new AssociationInfo.Builder(association)) - .setLastTimeConnected(System.currentTimeMillis()) - .build(); - mAssociationStore.updateAssociation(association); - - if (isAppeared) { - onSelfManagedDeviceConnected(associationId); - } else { - onSelfManagedDeviceDisconnected(associationId); - } - - final String deviceProfile = association.getDeviceProfile(); - if (DEVICE_PROFILE_AUTOMOTIVE_PROJECTION.equals(deviceProfile)) { - Slog.i(TAG, "Enable hint mode for device device profile: " + deviceProfile); - mPowerManagerInternal.setPowerMode(Mode.AUTOMOTIVE_PROJECTION, isAppeared); - } - } - - private void onBinderDied(@UserIdInt int userId, @NonNull String packageName, - @NonNull CompanionServiceConnector serviceConnector) { - - boolean isPrimary = serviceConnector.isPrimary(); - Slog.i(TAG, "onBinderDied() u" + userId + "/" + packageName + " isPrimary: " + isPrimary); - - // First, disable hint mode for Auto profile and mark not BOUND for primary service ONLY. - if (isPrimary) { - final List<AssociationInfo> associations = - mAssociationStore.getActiveAssociationsByPackage(userId, packageName); - - for (AssociationInfo association : associations) { - final String deviceProfile = association.getDeviceProfile(); - if (DEVICE_PROFILE_AUTOMOTIVE_PROJECTION.equals(deviceProfile)) { - Slog.i(TAG, "Disable hint mode for device profile: " + deviceProfile); - mPowerManagerInternal.setPowerMode(Mode.AUTOMOTIVE_PROJECTION, false); - break; - } - } - - mCompanionAppBinder.removePackage(userId, packageName); - } - - // Second: schedule rebinding if needed. - final boolean shouldScheduleRebind = shouldScheduleRebind(userId, packageName, isPrimary); - - if (shouldScheduleRebind) { - mCompanionAppBinder.scheduleRebinding(userId, packageName, serviceConnector); - } - } - - /** - * Check if the system should rebind the self-managed secondary services - * OR non-self-managed services. - */ - private boolean shouldScheduleRebind(int userId, String packageName, boolean isPrimary) { - // Make sure do not schedule rebind for the case ServiceConnector still gets callback after - // app is uninstalled. - boolean stillAssociated = false; - // Make sure to clean up the state for all the associations - // that associate with this package. - boolean shouldScheduleRebind = false; - boolean shouldScheduleRebindForUuid = false; - final List<ObservableUuid> uuids = - mObservableUuidStore.getObservableUuidsForPackage(userId, packageName); - - for (AssociationInfo ai : - mAssociationStore.getActiveAssociationsByPackage(userId, packageName)) { - final int associationId = ai.getId(); - stillAssociated = true; - if (ai.isSelfManaged()) { - // Do not rebind if primary one is died for selfManaged application. - if (isPrimary && isDevicePresent(associationId)) { - onSelfManagedDeviceReporterBinderDied(associationId); - shouldScheduleRebind = false; - } - // Do not rebind if both primary and secondary services are died for - // selfManaged application. - shouldScheduleRebind = mCompanionAppBinder.isCompanionApplicationBound(userId, - packageName); - } else if (ai.isNotifyOnDeviceNearby()) { - // Always rebind for non-selfManaged devices. - shouldScheduleRebind = true; - } - } - - for (ObservableUuid uuid : uuids) { - if (isDeviceUuidPresent(uuid.getUuid())) { - shouldScheduleRebindForUuid = true; - break; - } - } - - return (stillAssociated && shouldScheduleRebind) || shouldScheduleRebindForUuid; - } - - /** - * Implements - * {@link AssociationStore.OnChangeListener#onAssociationRemoved(AssociationInfo)} - */ - @Override - public void onAssociationRemoved(@NonNull AssociationInfo association) { - final int id = association.getId(); - if (DEBUG) { - Log.i(TAG, "onAssociationRemoved() id=" + id); - Log.d(TAG, " > association=" + association); - } - - mConnectedBtDevices.remove(id); - mNearbyBleDevices.remove(id); - mReportedSelfManagedDevices.remove(id); - mSimulated.remove(id); - synchronized (mBtDisconnectedDevices) { - mBtDisconnectedDevices.remove(id); - mBtDisconnectedDevicesBlePresence.delete(id); - } - - // Do NOT call mCallback.onDeviceDisappeared()! - // CompanionDeviceManagerService will know that the association is removed, and will do - // what's needed. - } - - /** - * Return a set of devices that pending to report connectivity - */ - public SparseArray<Set<BluetoothDevice>> getPendingConnectedDevices() { - synchronized (mBtConnectionListener.mPendingConnectedDevices) { - return mBtConnectionListener.mPendingConnectedDevices; - } - } - - private static void enforceCallerShellOrRoot() { - final int callingUid = Binder.getCallingUid(); - if (callingUid == SHELL_UID || callingUid == ROOT_UID) return; - - throw new SecurityException("Caller is neither Shell nor Root"); - } - - /** - * The BLE scan can be only stopped if all the devices have been reported - * BT connected and BLE presence and are not pending to report BLE lost. - */ - private boolean canStopBleScan() { - for (AssociationInfo ai : mAssociationStore.getActiveAssociations()) { - int id = ai.getId(); - synchronized (mBtDisconnectedDevices) { - if (ai.isNotifyOnDeviceNearby() && !(isBtConnected(id) - && isBlePresent(id) && mBtDisconnectedDevices.isEmpty())) { - Slog.i(TAG, "The BLE scan cannot be stopped, " - + "device( " + id + " ) is not yet connected " - + "OR the BLE is not current present Or is pending to report BLE lost"); - return false; - } - } - } - return true; - } - - /** - * Dumps system information about devices that are marked as "present". - */ - public void dump(@NonNull PrintWriter out) { - out.append("Companion Device Present: "); - if (mConnectedBtDevices.isEmpty() - && mNearbyBleDevices.isEmpty() - && mReportedSelfManagedDevices.isEmpty()) { - out.append("<empty>\n"); - return; - } else { - out.append("\n"); - } - - out.append(" Connected Bluetooth Devices: "); - if (mConnectedBtDevices.isEmpty()) { - out.append("<empty>\n"); - } else { - out.append("\n"); - for (int associationId : mConnectedBtDevices) { - AssociationInfo a = mAssociationStore.getAssociationById(associationId); - out.append(" ").append(a.toShortString()).append('\n'); - } - } - - out.append(" Nearby BLE Devices: "); - if (mNearbyBleDevices.isEmpty()) { - out.append("<empty>\n"); - } else { - out.append("\n"); - for (int associationId : mNearbyBleDevices) { - AssociationInfo a = mAssociationStore.getAssociationById(associationId); - out.append(" ").append(a.toShortString()).append('\n'); - } - } - - out.append(" Self-Reported Devices: "); - if (mReportedSelfManagedDevices.isEmpty()) { - out.append("<empty>\n"); - } else { - out.append("\n"); - for (int associationId : mReportedSelfManagedDevices) { - AssociationInfo a = mAssociationStore.getAssociationById(associationId); - out.append(" ").append(a.toShortString()).append('\n'); - } - } - } - - private class SimulatedDevicePresenceSchedulerHelper extends Handler { - SimulatedDevicePresenceSchedulerHelper() { - super(Looper.getMainLooper()); - } - - void scheduleOnDeviceGoneCallForSimulatedDevicePresence(int associationId) { - // First, unschedule if it was scheduled previously. - if (hasMessages(/* what */ associationId)) { - removeMessages(/* what */ associationId); - } - - sendEmptyMessageDelayed(/* what */ associationId, 60 * 1000 /* 60 seconds */); - } - - void unscheduleOnDeviceGoneCallForSimulatedDevicePresence(int associationId) { - removeMessages(/* what */ associationId); - } - - @Override - public void handleMessage(@NonNull Message msg) { - final int associationId = msg.what; - if (mSimulated.contains(associationId)) { - onDevicePresenceEvent(mSimulated, associationId, EVENT_BLE_DISAPPEARED); - } - } - } - - private class BleDeviceDisappearedScheduler extends Handler { - BleDeviceDisappearedScheduler() { - super(Looper.getMainLooper()); - } - - void scheduleBleDeviceDisappeared(int associationId) { - if (hasMessages(associationId)) { - removeMessages(associationId); - } - Slog.i(TAG, "scheduleBleDeviceDisappeared for Device: ( " + associationId + " )."); - sendEmptyMessageDelayed(associationId, 10 * 1000 /* 10 seconds */); - } - - void unScheduleDeviceDisappeared(int associationId) { - if (hasMessages(associationId)) { - Slog.i(TAG, "unScheduleDeviceDisappeared for Device( " + associationId + " )"); - synchronized (mBtDisconnectedDevices) { - mBtDisconnectedDevices.remove(associationId); - mBtDisconnectedDevicesBlePresence.delete(associationId); - } - - removeMessages(associationId); - } - } - - @Override - public void handleMessage(@NonNull Message msg) { - final int associationId = msg.what; - synchronized (mBtDisconnectedDevices) { - final boolean isCurrentPresent = mBtDisconnectedDevicesBlePresence.get( - associationId); - // If a device hasn't reported after 10 seconds and is not currently present, - // assume BLE is lost and trigger the onDeviceEvent callback with the - // EVENT_BLE_DISAPPEARED event. - if (mBtDisconnectedDevices.contains(associationId) - && !isCurrentPresent) { - Slog.i(TAG, "Device ( " + associationId + " ) is likely BLE out of range, " - + "sending callback with event ( " + EVENT_BLE_DISAPPEARED + " )"); - onDevicePresenceEvent(mNearbyBleDevices, associationId, EVENT_BLE_DISAPPEARED); - } - - mBtDisconnectedDevices.remove(associationId); - mBtDisconnectedDevicesBlePresence.delete(associationId); - } - } - } -} diff --git a/services/companion/java/com/android/server/companion/presence/ObservableUuidStore.java b/services/companion/java/com/android/server/companion/presence/ObservableUuidStore.java index fa0f6bd92acb..db15da2922cf 100644 --- a/services/companion/java/com/android/server/companion/presence/ObservableUuidStore.java +++ b/services/companion/java/com/android/server/companion/presence/ObservableUuidStore.java @@ -300,18 +300,4 @@ public class ObservableUuidStore { return readObservableUuidsFromCache(userId); } } - - /** - * Check if a UUID is being observed by the package. - */ - public boolean isUuidBeingObserved(ParcelUuid uuid, int userId, String packageName) { - final List<ObservableUuid> uuidsBeingObserved = getObservableUuidsForPackage(userId, - packageName); - for (ObservableUuid observableUuid : uuidsBeingObserved) { - if (observableUuid.getUuid().equals(uuid)) { - return true; - } - } - return false; - } } diff --git a/services/companion/java/com/android/server/companion/transport/CompanionTransportManager.java b/services/companion/java/com/android/server/companion/transport/CompanionTransportManager.java index 697ef87b5a12..793fb7ff74b1 100644 --- a/services/companion/java/com/android/server/companion/transport/CompanionTransportManager.java +++ b/services/companion/java/com/android/server/companion/transport/CompanionTransportManager.java @@ -46,6 +46,7 @@ import java.util.concurrent.Future; @SuppressLint("LongLogTag") public class CompanionTransportManager { private static final String TAG = "CDM_CompanionTransportManager"; + private static final boolean DEBUG = false; private boolean mSecureTransportEnabled = true; @@ -136,17 +137,11 @@ public class CompanionTransportManager { } } - /** - * Attach transport. - */ - public void attachSystemDataTransport(int associationId, ParcelFileDescriptor fd) { - Slog.i(TAG, "Attaching transport for association id=[" + associationId + "]..."); - - mAssociationStore.getAssociationWithCallerChecks(associationId); - + public void attachSystemDataTransport(String packageName, int userId, int associationId, + ParcelFileDescriptor fd) { synchronized (mTransports) { if (mTransports.contains(associationId)) { - detachSystemDataTransport(associationId); + detachSystemDataTransport(packageName, userId, associationId); } // TODO: Implement new API to pass a PSK @@ -154,18 +149,9 @@ public class CompanionTransportManager { notifyOnTransportsChanged(); } - - Slog.i(TAG, "Transport attached."); } - /** - * Detach transport. - */ - public void detachSystemDataTransport(int associationId) { - Slog.i(TAG, "Detaching transport for association id=[" + associationId + "]..."); - - mAssociationStore.getAssociationWithCallerChecks(associationId); - + public void detachSystemDataTransport(String packageName, int userId, int associationId) { synchronized (mTransports) { final Transport transport = mTransports.removeReturnOld(associationId); if (transport == null) { @@ -175,8 +161,6 @@ public class CompanionTransportManager { transport.stop(); notifyOnTransportsChanged(); } - - Slog.i(TAG, "Transport detached."); } private void notifyOnTransportsChanged() { @@ -323,7 +307,8 @@ public class CompanionTransportManager { int associationId = transport.mAssociationId; AssociationInfo association = mAssociationStore.getAssociationById(associationId); if (association != null) { - detachSystemDataTransport( + detachSystemDataTransport(association.getPackageName(), + association.getUserId(), association.getId()); } } diff --git a/services/companion/java/com/android/server/companion/utils/PermissionsUtils.java b/services/companion/java/com/android/server/companion/utils/PermissionsUtils.java index d7e766eed209..2cf1f462a7d1 100644 --- a/services/companion/java/com/android/server/companion/utils/PermissionsUtils.java +++ b/services/companion/java/com/android/server/companion/utils/PermissionsUtils.java @@ -39,6 +39,7 @@ import android.Manifest; import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.UserIdInt; +import android.companion.AssociationInfo; import android.companion.AssociationRequest; import android.companion.CompanionDeviceManager; import android.content.Context; @@ -207,7 +208,7 @@ public final class PermissionsUtils { /** * Require the caller to hold necessary permission to observe device presence by UUID. */ - public static void enforceCallerCanObserveDevicePresenceByUuid(@NonNull Context context) { + public static void enforceCallerCanObservingDevicePresenceByUuid(@NonNull Context context) { if (context.checkCallingPermission(REQUEST_OBSERVE_DEVICE_UUID_PRESENCE) != PERMISSION_GRANTED) { throw new SecurityException("Caller (uid=" + getCallingUid() + ") does not have " @@ -234,6 +235,23 @@ public final class PermissionsUtils { return checkCallerCanManageCompanionDevice(context); } + /** + * Check if CDM can trust the context to process the association. + */ + @Nullable + public static AssociationInfo sanitizeWithCallerChecks(@NonNull Context context, + @Nullable AssociationInfo association) { + if (association == null) return null; + + final int userId = association.getUserId(); + final String packageName = association.getPackageName(); + if (!checkCallerCanManageAssociationsForPackage(context, userId, packageName)) { + return null; + } + + return association; + } + private static boolean checkPackage(@UserIdInt int uid, @NonNull String packageName) { try { return getAppOpsService().checkPackage(uid, packageName) == MODE_ALLOWED; diff --git a/services/core/java/com/android/server/SensitiveContentProtectionManagerService.java b/services/core/java/com/android/server/SensitiveContentProtectionManagerService.java index 589d8b373802..e3f16ae07202 100644 --- a/services/core/java/com/android/server/SensitiveContentProtectionManagerService.java +++ b/services/core/java/com/android/server/SensitiveContentProtectionManagerService.java @@ -43,10 +43,12 @@ import android.view.ISensitiveContentProtectionManager; import com.android.internal.annotations.GuardedBy; import com.android.internal.annotations.VisibleForTesting; +import com.android.internal.util.FrameworkStatsLog; import com.android.server.wm.SensitiveContentPackages.PackageInfo; import com.android.server.wm.WindowManagerInternal; import java.util.Objects; +import java.util.Random; import java.util.Set; /** @@ -61,8 +63,15 @@ public final class SensitiveContentProtectionManagerService extends SystemServic @VisibleForTesting @Nullable NotificationListener mNotificationListener; - @Nullable private MediaProjectionManager mProjectionManager; - @Nullable private WindowManagerInternal mWindowManager; + @Nullable + private MediaProjectionManager mProjectionManager; + @Nullable + private MediaProjectionSession mMediaProjectionSession; + + private PackageManagerInternal mPackageManagerInternal; + + @Nullable + private WindowManagerInternal mWindowManager; // screen recorder packages exempted from screen share protection. private ArraySet<String> mExemptedPackages = null; @@ -74,6 +83,16 @@ public final class SensitiveContentProtectionManagerService extends SystemServic @GuardedBy("mSensitiveContentProtectionLock") private boolean mProjectionActive = false; + private static class MediaProjectionSession { + final int mUid; + final long mSessionId; + + MediaProjectionSession(int uid, long sessionId) { + mUid = uid; + mSessionId = sessionId; + } + } + private final MediaProjectionManager.Callback mProjectionCallback = new MediaProjectionManager.Callback() { @Override @@ -82,7 +101,7 @@ public final class SensitiveContentProtectionManagerService extends SystemServic Trace.traceBegin(Trace.TRACE_TAG_SYSTEM_SERVER, "SensitiveContentProtectionManagerService.onProjectionStart"); try { - onProjectionStart(info.getPackageName()); + onProjectionStart(info); } finally { Trace.traceEnd(Trace.TRACE_TAG_SYSTEM_SERVER); } @@ -119,12 +138,13 @@ public final class SensitiveContentProtectionManagerService extends SystemServic if (DEBUG) Log.d(TAG, "onBootPhase - PHASE_BOOT_COMPLETED"); + mPackageManagerInternal = LocalServices.getService(PackageManagerInternal.class); init(getContext().getSystemService(MediaProjectionManager.class), LocalServices.getService(WindowManagerInternal.class), getExemptedPackages()); if (sensitiveContentAppProtection()) { publishBinderService(Context.SENSITIVE_CONTENT_PROTECTION_SERVICE, - new SensitiveContentProtectionManagerServiceBinder()); + new SensitiveContentProtectionManagerServiceBinder(mPackageManagerInternal)); } } @@ -186,11 +206,12 @@ public final class SensitiveContentProtectionManagerService extends SystemServic return SystemConfig.getInstance().getBugreportWhitelistedPackages(); } - private void onProjectionStart(String packageName) { + private void onProjectionStart(MediaProjectionInfo projectionInfo) { // exempt on device screen recorder as well. - if ((mExemptedPackages != null && mExemptedPackages.contains(packageName)) - || canRecordSensitiveContent(packageName)) { - Log.w(TAG, packageName + " is exempted from screen share protection."); + if ((mExemptedPackages != null && mExemptedPackages.contains( + projectionInfo.getPackageName())) + || canRecordSensitiveContent(projectionInfo.getPackageName())) { + Log.w(TAG, projectionInfo.getPackageName() + " is exempted."); return; } // TODO(b/324447419): move GlobalSettings lookup to background thread @@ -204,6 +225,10 @@ public final class SensitiveContentProtectionManagerService extends SystemServic synchronized (mSensitiveContentProtectionLock) { mProjectionActive = true; + int uid = mPackageManagerInternal.getPackageUid(projectionInfo.getPackageName(), 0, + projectionInfo.getUserHandle().getIdentifier()); + // TODO review sessionId, whether to use a sequence generator or random is good? + mMediaProjectionSession = new MediaProjectionSession(uid, new Random().nextLong()); if (sensitiveNotificationAppProtection()) { updateAppsThatShouldBlockScreenCapture(); } @@ -217,6 +242,7 @@ public final class SensitiveContentProtectionManagerService extends SystemServic private void onProjectionEnd() { synchronized (mSensitiveContentProtectionLock) { mProjectionActive = false; + mMediaProjectionSession = null; // notify windowmanager to clear any sensitive notifications observed during projection // session @@ -351,9 +377,9 @@ public final class SensitiveContentProtectionManagerService extends SystemServic * Block projection for a package window when the window is showing sensitive content on * the screen, the projection is unblocked when window no more shows sensitive content. * - * @param windowToken window where the content is shown. - * @param packageName package name. - * @param uid uid of the package. + * @param windowToken window where the content is shown. + * @param packageName package name. + * @param uid uid of the package. * @param isShowingSensitiveContent whether the window is showing sensitive content. */ @VisibleForTesting @@ -385,8 +411,22 @@ public final class SensitiveContentProtectionManagerService extends SystemServic packageInfos.add(packageInfo); if (isShowingSensitiveContent) { mWindowManager.addBlockScreenCaptureForApps(packageInfos); + FrameworkStatsLog.write( + FrameworkStatsLog.SENSITIVE_CONTENT_APP_PROTECTION, + mMediaProjectionSession.mSessionId, + uid, + mMediaProjectionSession.mUid, + FrameworkStatsLog.SENSITIVE_CONTENT_APP_PROTECTION__STATE__BLOCKED + ); } else { mWindowManager.removeBlockScreenCaptureForApps(packageInfos); + FrameworkStatsLog.write( + FrameworkStatsLog.SENSITIVE_CONTENT_APP_PROTECTION, + mMediaProjectionSession.mSessionId, + uid, + mMediaProjectionSession.mUid, + FrameworkStatsLog.SENSITIVE_CONTENT_APP_PROTECTION__STATE__UNBLOCKED + ); } } } @@ -395,8 +435,9 @@ public final class SensitiveContentProtectionManagerService extends SystemServic extends ISensitiveContentProtectionManager.Stub { private final PackageManagerInternal mPackageManagerInternal; - SensitiveContentProtectionManagerServiceBinder() { - mPackageManagerInternal = LocalServices.getService(PackageManagerInternal.class); + SensitiveContentProtectionManagerServiceBinder( + PackageManagerInternal packageManagerInternal) { + mPackageManagerInternal = packageManagerInternal; } public void setSensitiveContentProtection(IBinder windowToken, String packageName, diff --git a/services/core/java/com/android/server/am/BroadcastSkipPolicy.java b/services/core/java/com/android/server/am/BroadcastSkipPolicy.java index b8ef03f36c23..f4a931f89551 100644 --- a/services/core/java/com/android/server/am/BroadcastSkipPolicy.java +++ b/services/core/java/com/android/server/am/BroadcastSkipPolicy.java @@ -289,27 +289,22 @@ public class BroadcastSkipPolicy { if (info.activityInfo.applicationInfo.uid != Process.SYSTEM_UID && r.requiredPermissions != null && r.requiredPermissions.length > 0) { - final AttributionSource attributionSource; + final AttributionSource[] attributionSources; if (usePermissionManagerForBroadcastDeliveryCheck()) { - attributionSource = - new AttributionSource.Builder(info.activityInfo.applicationInfo.uid) - .setPackageName(info.activityInfo.packageName) - .build(); + attributionSources = createAttributionSourcesForResolveInfo(info); } else { - attributionSource = null; + attributionSources = null; } for (int i = 0; i < r.requiredPermissions.length; i++) { String requiredPermission = r.requiredPermissions[i]; try { if (usePermissionManagerForBroadcastDeliveryCheck()) { - final PermissionManager permissionManager = getPermissionManager(); - if (permissionManager != null) { - perm = permissionManager.checkPermissionForDataDelivery( - requiredPermission, attributionSource, null /* message */); - } else { - // Assume permission denial if PermissionManager is not yet available. - perm = PackageManager.PERMISSION_DENIED; - } + perm = hasPermissionForDataDelivery( + requiredPermission, + "Broadcast delivered to " + info.activityInfo.name, + attributionSources) + ? PackageManager.PERMISSION_GRANTED + : PackageManager.PERMISSION_DENIED; } else { perm = AppGlobals.getPackageManager() .checkPermission( @@ -457,10 +452,35 @@ public class BroadcastSkipPolicy { // Check that the receiver has the required permission(s) to receive this broadcast. if (r.requiredPermissions != null && r.requiredPermissions.length > 0) { + final AttributionSource attributionSource; + if (usePermissionManagerForBroadcastDeliveryCheck()) { + attributionSource = + new AttributionSource.Builder(filter.receiverList.uid) + .setPid(filter.receiverList.pid) + .setPackageName(filter.packageName) + .setAttributionTag(filter.featureId) + .build(); + } else { + attributionSource = null; + } for (int i = 0; i < r.requiredPermissions.length; i++) { String requiredPermission = r.requiredPermissions[i]; - int perm = checkComponentPermission(requiredPermission, - filter.receiverList.pid, filter.receiverList.uid, -1, true); + final int perm; + if (usePermissionManagerForBroadcastDeliveryCheck()) { + perm = hasPermissionForDataDelivery( + requiredPermission, + "Broadcast delivered to registered receiver " + filter.receiverId, + attributionSource) + ? PackageManager.PERMISSION_GRANTED + : PackageManager.PERMISSION_DENIED; + } else { + perm = checkComponentPermission( + requiredPermission, + filter.receiverList.pid, + filter.receiverList.uid, + -1 /* owningUid */, + true /* exported */); + } if (perm != PackageManager.PERMISSION_GRANTED) { return "Permission Denial: receiving " + r.intent.toString() @@ -471,21 +491,23 @@ public class BroadcastSkipPolicy { + " due to sender " + r.callerPackage + " (uid " + r.callingUid + ")"; } - int appOp = AppOpsManager.permissionToOpCode(requiredPermission); - if (appOp != AppOpsManager.OP_NONE && appOp != r.appOp - && mService.getAppOpsManager().noteOpNoThrow(appOp, - filter.receiverList.uid, filter.packageName, filter.featureId, - "Broadcast delivered to registered receiver " + filter.receiverId) - != AppOpsManager.MODE_ALLOWED) { - return "Appop Denial: receiving " - + r.intent.toString() - + " to " + filter.receiverList.app - + " (pid=" + filter.receiverList.pid - + ", uid=" + filter.receiverList.uid + ")" - + " requires appop " + AppOpsManager.permissionToOp( - requiredPermission) - + " due to sender " + r.callerPackage - + " (uid " + r.callingUid + ")"; + if (!usePermissionManagerForBroadcastDeliveryCheck()) { + int appOp = AppOpsManager.permissionToOpCode(requiredPermission); + if (appOp != AppOpsManager.OP_NONE && appOp != r.appOp + && mService.getAppOpsManager().noteOpNoThrow(appOp, + filter.receiverList.uid, filter.packageName, filter.featureId, + "Broadcast delivered to registered receiver " + filter.receiverId) + != AppOpsManager.MODE_ALLOWED) { + return "Appop Denial: receiving " + + r.intent.toString() + + " to " + filter.receiverList.app + + " (pid=" + filter.receiverList.pid + + ", uid=" + filter.receiverList.uid + ")" + + " requires appop " + AppOpsManager.permissionToOp( + requiredPermission) + + " due to sender " + r.callerPackage + + " (uid " + r.callingUid + ")"; + } } } } @@ -724,10 +746,54 @@ public class BroadcastSkipPolicy { return false; } + @Nullable private PermissionManager getPermissionManager() { if (mPermissionManager == null) { mPermissionManager = mService.mContext.getSystemService(PermissionManager.class); } return mPermissionManager; } + + private boolean hasPermissionForDataDelivery( + @NonNull String permission, + @NonNull String message, + @NonNull AttributionSource... attributionSources) { + final PermissionManager permissionManager = getPermissionManager(); + if (permissionManager == null) { + return false; + } + + for (AttributionSource attributionSource : attributionSources) { + final int permissionCheckResult = + permissionManager.checkPermissionForDataDelivery( + permission, attributionSource, message); + if (permissionCheckResult != PackageManager.PERMISSION_GRANTED) { + return false; + } + } + + return true; + } + + private AttributionSource[] createAttributionSourcesForResolveInfo(ResolveInfo info) { + final String[] attributionTags = info.activityInfo.attributionTags; + if (ArrayUtils.isEmpty(attributionTags)) { + return new AttributionSource[] { + new AttributionSource.Builder(info.activityInfo.applicationInfo.uid) + .setPackageName(info.activityInfo.packageName) + .build() + }; + } + + final AttributionSource[] attributionSources = + new AttributionSource[attributionTags.length]; + for (int i = 0; i < attributionTags.length; i++) { + attributionSources[i] = + new AttributionSource.Builder(info.activityInfo.applicationInfo.uid) + .setPackageName(info.activityInfo.packageName) + .setAttributionTag(attributionTags[i]) + .build(); + } + return attributionSources; + } } diff --git a/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java b/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java index 48d3c09290ce..390dca30ac96 100644 --- a/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java +++ b/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java @@ -125,13 +125,13 @@ public class SettingsToPropertiesMapper { "app_widgets", "arc_next", "avic", + "biometrics", + "biometrics_framework", + "biometrics_integration", "bluetooth", "brownout_mitigation_audio", "brownout_mitigation_modem", "build", - "biometrics", - "biometrics_framework", - "biometrics_integration", "camera_hal", "camera_platform", "car_framework", @@ -160,8 +160,8 @@ public class SettingsToPropertiesMapper { "media_audio", "media_drm", "media_reliability", - "media_tv", "media_solutions", + "media_tv", "nfc", "pdf_viewer", "perfetto", @@ -183,18 +183,20 @@ public class SettingsToPropertiesMapper { "rust", "safety_center", "sensors", + "spoon", + "statsd", "system_performance", "system_sw_touch", "system_sw_usb", - "statsd", "test_suites", "text", "threadnetwork", + "treble", "tv_system_ui", "usb", "vibrator", - "virtualization", "virtual_devices", + "virtualization", "wallet_integration", "wear_calling_messaging", "wear_connectivity", diff --git a/services/core/java/com/android/server/biometrics/AuthService.java b/services/core/java/com/android/server/biometrics/AuthService.java index b7e11a7fe261..14f3120b60b6 100644 --- a/services/core/java/com/android/server/biometrics/AuthService.java +++ b/services/core/java/com/android/server/biometrics/AuthService.java @@ -312,8 +312,8 @@ public class AuthService extends SystemService { if (promptInfo.containsPrivateApiConfigurations()) { checkInternalPermission(); } - if (promptInfo.containsSetLogoApiConfigurations()) { - checkManageBiometricPermission(); + if (promptInfo.containsAdvancedApiConfigurations()) { + checkBiometricAdvancedPermission(); } final long identity = Binder.clearCallingIdentity(); @@ -1029,7 +1029,7 @@ public class AuthService extends SystemService { "Must have USE_BIOMETRIC_INTERNAL permission"); } - private void checkManageBiometricPermission() { + private void checkBiometricAdvancedPermission() { getContext().enforceCallingOrSelfPermission(SET_BIOMETRIC_DIALOG_ADVANCED, "Must have SET_BIOMETRIC_DIALOG_ADVANCED permission"); } diff --git a/services/core/java/com/android/server/display/DisplayDevice.java b/services/core/java/com/android/server/display/DisplayDevice.java index 0ebb2a30685c..09094ffb4f0d 100644 --- a/services/core/java/com/android/server/display/DisplayDevice.java +++ b/services/core/java/com/android/server/display/DisplayDevice.java @@ -26,6 +26,7 @@ import android.graphics.Rect; import android.hardware.display.DisplayManagerInternal.DisplayOffloadSession; import android.hardware.display.DisplayViewport; import android.os.IBinder; +import android.util.ArraySet; import android.util.Slog; import android.view.Display; import android.view.DisplayAddress; @@ -35,6 +36,7 @@ import android.view.SurfaceControl; import com.android.server.display.mode.DisplayModeDirector; import java.io.PrintWriter; +import java.util.Arrays; /** * Represents a display device such as the built-in display, an external monitor, a WiFi display, @@ -429,6 +431,21 @@ abstract class DisplayDevice { return mCurrentOrientation == ROTATION_90 || mCurrentOrientation == ROTATION_270; } + /** + * @return set of supported resolutions as an ascending sorted array. + */ + Point[] getSupportedResolutionsLocked() { + ArraySet<Point> resolutions = new ArraySet<>(2); + Display.Mode[] supportedModes = getDisplayDeviceInfoLocked().supportedModes; + for (Display.Mode mode : supportedModes) { + resolutions.add(new Point(mode.getPhysicalWidth(), mode.getPhysicalHeight())); + } + Point[] sortedArray = new Point[resolutions.size()]; + resolutions.toArray(sortedArray); + Arrays.sort(sortedArray, (p1, p2) -> p1.x * p1.y - p2.x * p2.y); + return sortedArray; + } + private DisplayDeviceConfig loadDisplayDeviceConfig() { return DisplayDeviceConfig.create(mContext, /* useConfigXml= */ false, mDisplayAdapter.getFeatureFlags()); diff --git a/services/core/java/com/android/server/display/DisplayDeviceConfig.java b/services/core/java/com/android/server/display/DisplayDeviceConfig.java index da26209ad495..641b6a2f170b 100644 --- a/services/core/java/com/android/server/display/DisplayDeviceConfig.java +++ b/services/core/java/com/android/server/display/DisplayDeviceConfig.java @@ -54,6 +54,7 @@ import com.android.server.display.config.DisplayBrightnessMappingConfig; import com.android.server.display.config.DisplayBrightnessPoint; import com.android.server.display.config.DisplayConfiguration; import com.android.server.display.config.DisplayQuirks; +import com.android.server.display.config.EvenDimmerBrightnessData; import com.android.server.display.config.HbmTiming; import com.android.server.display.config.HdrBrightnessData; import com.android.server.display.config.HighBrightnessMode; @@ -61,7 +62,6 @@ import com.android.server.display.config.IdleScreenRefreshRateTimeout; import com.android.server.display.config.IdleScreenRefreshRateTimeoutLuxThresholdPoint; import com.android.server.display.config.IdleScreenRefreshRateTimeoutLuxThresholds; import com.android.server.display.config.IntegerArray; -import com.android.server.display.config.LowBrightnessData; import com.android.server.display.config.LuxThrottling; import com.android.server.display.config.NitsMap; import com.android.server.display.config.NonNegativeFloatToFloatPoint; @@ -556,7 +556,7 @@ import javax.xml.datatype.DatatypeConfigurationException; * <majorVersion>2</majorVersion> * <minorVersion>0</minorVersion> * </usiVersion> - * <lowBrightness enabled="true"> + * <evenDimmer enabled="true"> * <transitionPoint>0.1</transitionPoint> * * <nits>0.2</nits> @@ -573,7 +573,7 @@ import javax.xml.datatype.DatatypeConfigurationException; * <brightness>0.1</brightness> * <brightness>0.5</brightness> * <brightness>1.0</brightness> - * </lowBrightness> + * </evenDimmer> * <screenBrightnessCapForWearBedtimeMode>0.1</screenBrightnessCapForWearBedtimeMode> * <idleScreenRefreshRateTimeout> * <luxThresholds> @@ -894,9 +894,9 @@ public class DisplayDeviceConfig { @Nullable private HdrBrightnessData mHdrBrightnessData; - // Null if low brightness mode is disabled - in config or by flag. + // Null if even dimmer is disabled - in config or by flag. @Nullable - public LowBrightnessData mLowBrightnessData; + public EvenDimmerBrightnessData mEvenDimmerBrightnessData; /** * Maximum screen brightness setting when screen brightness capped in Wear Bedtime mode. @@ -1064,8 +1064,8 @@ public class DisplayDeviceConfig { * @return The brightness mapping nits array. */ public float[] getNits() { - if (mLowBrightnessData != null) { - return mLowBrightnessData.mNits; + if (mEvenDimmerBrightnessData != null) { + return mEvenDimmerBrightnessData.mNits; } return mNits; } @@ -1077,8 +1077,8 @@ public class DisplayDeviceConfig { */ @VisibleForTesting public float[] getBacklight() { - if (mLowBrightnessData != null) { - return mLowBrightnessData.mBacklight; + if (mEvenDimmerBrightnessData != null) { + return mEvenDimmerBrightnessData.mBacklight; } return mBacklight; } @@ -1091,8 +1091,8 @@ public class DisplayDeviceConfig { * @return backlight value on the HAL scale of 0-1 */ public float getBacklightFromBrightness(float brightness) { - if (mLowBrightnessData != null) { - return mLowBrightnessData.mBrightnessToBacklight.interpolate(brightness); + if (mEvenDimmerBrightnessData != null) { + return mEvenDimmerBrightnessData.mBrightnessToBacklight.interpolate(brightness); } return mBrightnessToBacklightSpline.interpolate(brightness); } @@ -1104,30 +1104,19 @@ public class DisplayDeviceConfig { * @return brightness value from 0-1 framework scale */ public float getBrightnessFromBacklight(float backlight) { - if (mLowBrightnessData != null) { - return mLowBrightnessData.mBacklightToBrightness.interpolate(backlight); + if (mEvenDimmerBrightnessData != null) { + return mEvenDimmerBrightnessData.mBacklightToBrightness.interpolate(backlight); } return mBacklightToBrightnessSpline.interpolate(backlight); } /** * - * @return low brightness mode transition point - */ - public float getLowBrightnessTransitionPoint() { - if (mLowBrightnessData == null) { - return PowerManager.BRIGHTNESS_MIN; - } - return mLowBrightnessData.mTransitionPoint; - } - - /** - * * @return HAL backlight mapping to framework brightness */ private Spline getBacklightToBrightnessSpline() { - if (mLowBrightnessData != null) { - return mLowBrightnessData.mBacklightToBrightness; + if (mEvenDimmerBrightnessData != null) { + return mEvenDimmerBrightnessData.mBacklightToBrightness; } return mBacklightToBrightnessSpline; } @@ -1139,12 +1128,12 @@ public class DisplayDeviceConfig { * exits. */ public float getNitsFromBacklight(float backlight) { - if (mLowBrightnessData != null) { - if (mLowBrightnessData.mBacklightToNits == null) { + if (mEvenDimmerBrightnessData != null) { + if (mEvenDimmerBrightnessData.mBacklightToNits == null) { return INVALID_NITS; } backlight = Math.max(backlight, mBacklightMinimum); - return mLowBrightnessData.mBacklightToNits.interpolate(backlight); + return mEvenDimmerBrightnessData.mBacklightToNits.interpolate(backlight); } if (mBacklightToNitsSpline == null) { @@ -1160,15 +1149,15 @@ public class DisplayDeviceConfig { * @return corresponding HAL backlight value */ public float getBacklightFromNits(float nits) { - if (mLowBrightnessData != null) { - return mLowBrightnessData.mNitsToBacklight.interpolate(nits); + if (mEvenDimmerBrightnessData != null) { + return mEvenDimmerBrightnessData.mNitsToBacklight.interpolate(nits); } return mNitsToBacklightSpline.interpolate(nits); } private Spline getNitsToBacklightSpline() { - if (mLowBrightnessData != null) { - return mLowBrightnessData.mNitsToBacklight; + if (mEvenDimmerBrightnessData != null) { + return mEvenDimmerBrightnessData.mNitsToBacklight; } return mNitsToBacklightSpline; } @@ -1179,10 +1168,21 @@ public class DisplayDeviceConfig { * @return minimum allowed nits, given the lux. */ public float getMinNitsFromLux(float lux) { - if (mLowBrightnessData == null) { + if (mEvenDimmerBrightnessData == null) { return INVALID_NITS; } - return mLowBrightnessData.mMinLuxToNits.interpolate(lux); + return mEvenDimmerBrightnessData.mMinLuxToNits.interpolate(lux); + } + + /** + * + * @return even dimmer mode transition point + */ + public float getEvenDimmerTransitionPoint() { + if (mEvenDimmerBrightnessData == null) { + return PowerManager.BRIGHTNESS_MIN; + } + return mEvenDimmerBrightnessData.mTransitionPoint; } /** @@ -1239,8 +1239,8 @@ public class DisplayDeviceConfig { * @return brightness array */ public float[] getBrightness() { - if (mLowBrightnessData != null) { - return mLowBrightnessData.mBrightness; + if (mEvenDimmerBrightnessData != null) { + return mEvenDimmerBrightnessData.mBrightness; } return mBrightness; } @@ -1928,11 +1928,11 @@ public class DisplayDeviceConfig { /** * - * @return true if low brightness mode is enabled + * @return true if even dimmer mode is enabled */ @VisibleForTesting - public boolean getLbmEnabled() { - return mLowBrightnessData != null; + public boolean isEvenDimmerAvailable() { + return mEvenDimmerBrightnessData != null; } /** @@ -2075,8 +2075,8 @@ public class DisplayDeviceConfig { + "mHdrBrightnessData= " + mHdrBrightnessData + "\n" + "mBrightnessCapForWearBedtimeMode= " + mBrightnessCapForWearBedtimeMode + "\n" - + "mLowBrightnessData:" + (mLowBrightnessData != null - ? mLowBrightnessData.toString() : "null") + + "mEvenDimmerBrightnessData:" + (mEvenDimmerBrightnessData != null + ? mEvenDimmerBrightnessData.toString() : "null") + "}"; } @@ -2128,7 +2128,7 @@ public class DisplayDeviceConfig { loadBrightnessDefaultFromDdcXml(config); loadBrightnessConstraintsFromConfigXml(); if (mFlags.isEvenDimmerEnabled()) { - mLowBrightnessData = LowBrightnessData.loadConfig(config); + mEvenDimmerBrightnessData = EvenDimmerBrightnessData.loadConfig(config); } loadBrightnessMap(config); loadThermalThrottlingConfig(config); @@ -2923,7 +2923,7 @@ public class DisplayDeviceConfig { private void createBacklightConversionSplines() { - // Create original brightness splines - not using low brightness mode arrays - this is + // Create original brightness splines - not using even dimmer mode arrays - this is // so that we can continue to log the original brightness splines. mBrightness = new float[mBacklight.length]; diff --git a/services/core/java/com/android/server/display/DisplayManagerService.java b/services/core/java/com/android/server/display/DisplayManagerService.java index 84eebe838954..ba21a327d837 100644 --- a/services/core/java/com/android/server/display/DisplayManagerService.java +++ b/services/core/java/com/android/server/display/DisplayManagerService.java @@ -45,6 +45,9 @@ import static android.hardware.display.HdrConversionMode.HDR_CONVERSION_UNSUPPOR import static android.os.IServiceManager.DUMP_FLAG_PRIORITY_CRITICAL; import static android.os.Process.FIRST_APPLICATION_UID; import static android.os.Process.ROOT_UID; +import static android.provider.Settings.Secure.RESOLUTION_MODE_FULL; +import static android.provider.Settings.Secure.RESOLUTION_MODE_HIGH; +import static android.provider.Settings.Secure.RESOLUTION_MODE_UNKNOWN; import android.Manifest; import android.annotation.EnforcePermission; @@ -531,6 +534,18 @@ public final class DisplayManagerService extends SystemService { } }; + private final BroadcastReceiver mResolutionRestoreReceiver = new BroadcastReceiver() { + @Override + public void onReceive(Context context, Intent intent) { + if (Intent.ACTION_SETTING_RESTORED.equals(intent.getAction())) { + if (Settings.Secure.SCREEN_RESOLUTION_MODE.equals( + intent.getExtra(Intent.EXTRA_SETTING_NAME))) { + restoreResolutionFromBackup(); + } + } + } + }; + private final BrightnessSynchronizer mBrightnessSynchronizer; private final DeviceConfigParameterProvider mConfigParameterProvider; @@ -560,6 +575,9 @@ public final class DisplayManagerService extends SystemService { @EnabledSince(targetSdkVersion = android.os.Build.VERSION_CODES.S) static final long DISPLAY_MODE_RETURNS_PHYSICAL_REFRESH_RATE = 170503758L; + private final Uri mScreenResolutionModeUri = Settings.Secure.getUriFor( + Settings.Secure.SCREEN_RESOLUTION_MODE); + public DisplayManagerService(Context context) { this(context, new Injector()); } @@ -782,6 +800,11 @@ public final class DisplayManagerService extends SystemService { mContext.registerReceiver(mIdleModeReceiver, filter); + if (mFlags.isResolutionBackupRestoreEnabled()) { + final IntentFilter restoreFilter = new IntentFilter(Intent.ACTION_SETTING_RESTORED); + mContext.registerReceiver(mResolutionRestoreReceiver, restoreFilter); + } + mSmallAreaDetectionController = (mFlags.isSmallAreaDetectionEnabled()) ? SmallAreaDetectionController.create(mContext) : null; } @@ -1031,6 +1054,44 @@ public final class DisplayManagerService extends SystemService { 1, UserHandle.USER_CURRENT) != 0); } + private void restoreResolutionFromBackup() { + int savedMode = Settings.Secure.getIntForUser(mContext.getContentResolver(), + Settings.Secure.SCREEN_RESOLUTION_MODE, + RESOLUTION_MODE_UNKNOWN, UserHandle.USER_CURRENT); + if (savedMode == RESOLUTION_MODE_UNKNOWN) { + // Nothing to restore. + return; + } + + synchronized (mSyncRoot) { + LogicalDisplay display = + mLogicalDisplayMapper.getDisplayLocked(Display.DEFAULT_DISPLAY); + DisplayDevice device = display == null ? null : display.getPrimaryDisplayDeviceLocked(); + if (device == null) { + Slog.w(TAG, "No default display device present to restore resolution mode"); + return; + } + + Point[] supportedRes = device.getSupportedResolutionsLocked(); + if (supportedRes.length != 2) { + if (DEBUG) { + Slog.d(TAG, "Skipping resolution restore - " + supportedRes.length); + } + return; + } + + // We follow the same logic as Settings but in reverse. If the display supports 2 + // resolutions, we treat the small (index=0) one as HIGH and the larger (index=1) + // one as FULL and restore the correct resolution accordingly. + int index = savedMode == RESOLUTION_MODE_HIGH ? 0 : 1; + Point res = supportedRes[index]; + Display.Mode newMode = new Display.Mode(res.x, res.y, /*refreshRate=*/ 0); + Slog.i(TAG, "Restoring resolution from backup: (" + savedMode + ") " + + res.x + "x" + res.y); + setUserPreferredDisplayModeInternal(Display.DEFAULT_DISPLAY, newMode); + } + } + private void updateUserDisabledHdrTypesFromSettingsLocked() { mAreUserDisabledHdrTypesAllowed = (Settings.Global.getInt( mContext.getContentResolver(), @@ -2348,6 +2409,28 @@ public final class DisplayManagerService extends SystemService { if (displayDevice == null) { return; } + + // We do not yet support backup and restore for our PersistentDataStore, however, we want to + // preserve the user's choice for HIGH/FULL resolution setting, so we when we are given a + // a new resolution for the default display (normally stored in PDS), we will also save it + // to a setting that is backed up. + // TODO(b/330943343) - Consider a full fidelity DisplayBackupHelper for this instead. + if (mFlags.isResolutionBackupRestoreEnabled() && displayId == Display.DEFAULT_DISPLAY) { + // Checks to see which of the two resolutions is selected + // TODO(b/330906790) Uses the same logic as Settings, but should be made to support + // more than two resolutions using explicit mode enums long-term. + Point[] resolutions = displayDevice.getSupportedResolutionsLocked(); + if (resolutions.length == 2) { + Point newMode = new Point(mode.getPhysicalWidth(), mode.getPhysicalHeight()); + int resolutionMode = newMode.equals(resolutions[0]) ? RESOLUTION_MODE_HIGH + : newMode.equals(resolutions[1]) ? RESOLUTION_MODE_FULL + : RESOLUTION_MODE_UNKNOWN; + Settings.Secure.putIntForUser(mContext.getContentResolver(), + Settings.Secure.SCREEN_RESOLUTION_MODE, resolutionMode, + UserHandle.USER_CURRENT); + } + } + displayDevice.setUserPreferredDisplayModeLocked(mode); } diff --git a/services/core/java/com/android/server/display/LocalDisplayAdapter.java b/services/core/java/com/android/server/display/LocalDisplayAdapter.java index 86fab17e6ae8..a577e225076f 100644 --- a/services/core/java/com/android/server/display/LocalDisplayAdapter.java +++ b/services/core/java/com/android/server/display/LocalDisplayAdapter.java @@ -53,6 +53,7 @@ import com.android.internal.annotations.VisibleForTesting; import com.android.internal.display.BrightnessSynchronizer; import com.android.internal.util.function.pooled.PooledLambda; import com.android.server.LocalServices; +import com.android.server.display.color.ColorDisplayService; import com.android.server.display.feature.DisplayManagerFlags; import com.android.server.display.mode.DisplayModeDirector; import com.android.server.display.notifications.DisplayNotificationManager; @@ -100,6 +101,7 @@ final class LocalDisplayAdapter extends DisplayAdapter { private Context mOverlayContext; private int mEvenDimmerStrength = -1; + private ColorDisplayService.ColorDisplayServiceInternal mCdsi; // Called with SyncRoot lock held. LocalDisplayAdapter(DisplayManagerService.SyncRoot syncRoot, Context context, @@ -1000,9 +1002,15 @@ final class LocalDisplayAdapter extends DisplayAdapter { || strength <= 1) { mEvenDimmerStrength = strength; } + boolean enabled = mEvenDimmerStrength > 0.0f; - // TODO: use `enabled` and `mRbcStrength` to set color matrices here - // TODO: boolean enabled = mEvenDimmerStrength > 0.0f; + if (mCdsi == null) { + mCdsi = LocalServices.getService( + ColorDisplayService.ColorDisplayServiceInternal.class); + } + if (mCdsi != null) { + mCdsi.applyEvenDimmerColorChanges(enabled, strength); + } } }; } diff --git a/services/core/java/com/android/server/display/brightness/clamper/BrightnessLowLuxModifier.java b/services/core/java/com/android/server/display/brightness/clamper/BrightnessLowLuxModifier.java index 7f88c3029820..29b457fca8fc 100644 --- a/services/core/java/com/android/server/display/brightness/clamper/BrightnessLowLuxModifier.java +++ b/services/core/java/com/android/server/display/brightness/clamper/BrightnessLowLuxModifier.java @@ -106,7 +106,7 @@ public class BrightnessLowLuxModifier extends BrightnessModifier { ? BrightnessReason.MODIFIER_MIN_USER_SET_LOWER_BOUND : BrightnessReason.MODIFIER_MIN_LUX; } else { - minBrightnessAllowed = mDisplayDeviceConfig.getLowBrightnessTransitionPoint(); + minBrightnessAllowed = mDisplayDeviceConfig.getEvenDimmerTransitionPoint(); reason = 0; } diff --git a/services/core/java/com/android/server/display/color/ColorDisplayService.java b/services/core/java/com/android/server/display/color/ColorDisplayService.java index a313bcf1f7af..0bb93a96d9df 100644 --- a/services/core/java/com/android/server/display/color/ColorDisplayService.java +++ b/services/core/java/com/android/server/display/color/ColorDisplayService.java @@ -1632,6 +1632,15 @@ public final class ColorDisplayService extends SystemService { return mAppSaturationController .addColorTransformController(packageName, userId, controller); } + + /** + * Applies tint changes for even dimmer feature. + */ + public void applyEvenDimmerColorChanges(boolean enabled, int strength) { + mReduceBrightColorsTintController.setActivated(enabled); + mReduceBrightColorsTintController.setMatrix(strength); + mHandler.sendEmptyMessage(MSG_APPLY_REDUCE_BRIGHT_COLORS); + } } /** diff --git a/services/core/java/com/android/server/display/config/LowBrightnessData.java b/services/core/java/com/android/server/display/config/EvenDimmerBrightnessData.java index 1a4e807fece6..555636538893 100644 --- a/services/core/java/com/android/server/display/config/LowBrightnessData.java +++ b/services/core/java/com/android/server/display/config/EvenDimmerBrightnessData.java @@ -26,13 +26,13 @@ import java.util.Arrays; import java.util.List; /** - * Brightness config for low brightness mode + * Brightness config for even dimmer */ -public class LowBrightnessData { - private static final String TAG = "LowBrightnessData"; +public class EvenDimmerBrightnessData { + private static final String TAG = "EvenDimmerBrightnessData"; /** - * Brightness value at which lower brightness methods are used. + * Brightness value at which even dimmer methods are used. */ public final float mTransitionPoint; @@ -69,7 +69,7 @@ public class LowBrightnessData { public final Spline mMinLuxToNits; @VisibleForTesting - public LowBrightnessData(float transitionPoint, float[] nits, + public EvenDimmerBrightnessData(float transitionPoint, float[] nits, float[] backlight, float[] brightness, Spline backlightToNits, Spline nitsToBacklight, Spline brightnessToBacklight, Spline backlightToBrightness, Spline minLuxToNits) { @@ -86,7 +86,7 @@ public class LowBrightnessData { @Override public String toString() { - return "LowBrightnessData {" + return "EvenDimmerBrightnessData {" + "mTransitionPoint: " + mTransitionPoint + ", mNits: " + Arrays.toString(mNits) + ", mBacklight: " + Arrays.toString(mBacklight) @@ -100,11 +100,11 @@ public class LowBrightnessData { } /** - * Loads LowBrightnessData from DisplayConfiguration + * Loads EvenDimmerBrightnessData from DisplayConfiguration */ @Nullable - public static LowBrightnessData loadConfig(DisplayConfiguration config) { - final LowBrightnessMode lbm = config.getLowBrightness(); + public static EvenDimmerBrightnessData loadConfig(DisplayConfiguration config) { + final EvenDimmerMode lbm = config.getEvenDimmer(); if (lbm == null) { return null; } @@ -122,7 +122,7 @@ public class LowBrightnessData { if (nitsList.isEmpty() || backlightList.size() != brightnessList.size() || backlightList.size() != nitsList.size()) { - Slog.e(TAG, "Invalid low brightness array lengths"); + Slog.e(TAG, "Invalid even dimmer array lengths"); return null; } @@ -164,7 +164,7 @@ public class LowBrightnessData { ++i; } - return new LowBrightnessData(transitionPoints, nits, backlight, brightness, + return new EvenDimmerBrightnessData(transitionPoints, nits, backlight, brightness, Spline.createSpline(backlight, nits), Spline.createSpline(nits, backlight), Spline.createSpline(brightness, backlight), diff --git a/services/core/java/com/android/server/display/feature/DisplayManagerFlags.java b/services/core/java/com/android/server/display/feature/DisplayManagerFlags.java index e1a166ec95f5..81f824e69cf3 100644 --- a/services/core/java/com/android/server/display/feature/DisplayManagerFlags.java +++ b/services/core/java/com/android/server/display/feature/DisplayManagerFlags.java @@ -96,6 +96,10 @@ public class DisplayManagerFlags { Flags.FLAG_ENABLE_RESTRICT_DISPLAY_MODES, Flags::enableRestrictDisplayModes); + private final FlagState mResolutionBackupRestore = new FlagState( + Flags.FLAG_RESOLUTION_BACKUP_RESTORE, + Flags::resolutionBackupRestore); + private final FlagState mVsyncLowPowerVote = new FlagState( Flags.FLAG_ENABLE_VSYNC_LOW_POWER_VOTE, Flags::enableVsyncLowPowerVote); @@ -246,6 +250,10 @@ public class DisplayManagerFlags { return mRestrictDisplayModes.isEnabled(); } + public boolean isResolutionBackupRestoreEnabled() { + return mResolutionBackupRestore.isEnabled(); + } + public boolean isVsyncLowPowerVoteEnabled() { return mVsyncLowPowerVote.isEnabled(); } @@ -309,6 +317,7 @@ public class DisplayManagerFlags { pw.println(" " + mHdrClamperFlagState); pw.println(" " + mNbmControllerFlagState); pw.println(" " + mPowerThrottlingClamperFlagState); + pw.println(" " + mEvenDimmerFlagState); pw.println(" " + mSmallAreaDetectionFlagState); pw.println(" " + mBrightnessIntRangeUserPerceptionFlagState); pw.println(" " + mRestrictDisplayModes); @@ -321,6 +330,7 @@ public class DisplayManagerFlags { pw.println(" " + mSensorBasedBrightnessThrottling); pw.println(" " + mIdleScreenRefreshRateTimeout); pw.println(" " + mRefactorDisplayPowerController); + pw.println(" " + mResolutionBackupRestore); } private static class FlagState { diff --git a/services/core/java/com/android/server/input/InputManagerInternal.java b/services/core/java/com/android/server/input/InputManagerInternal.java index 7e190ddbb24f..4e9cf51fda56 100644 --- a/services/core/java/com/android/server/input/InputManagerInternal.java +++ b/services/core/java/com/android/server/input/InputManagerInternal.java @@ -141,6 +141,11 @@ public abstract class InputManagerInternal { */ public abstract void unregisterLidSwitchCallback(@NonNull LidSwitchCallback callbacks); + /** + * Notify the input manager that an IME connection is becoming active or is no longer active. + */ + public abstract void notifyInputMethodConnectionActive(boolean connectionIsActive); + /** Callback interface for notifications relating to the lid switch. */ public interface LidSwitchCallback { /** diff --git a/services/core/java/com/android/server/input/InputManagerService.java b/services/core/java/com/android/server/input/InputManagerService.java index 468b90259fc7..77119d5ac384 100644 --- a/services/core/java/com/android/server/input/InputManagerService.java +++ b/services/core/java/com/android/server/input/InputManagerService.java @@ -2846,13 +2846,6 @@ public class InputManagerService extends IInputManager.Stub lockedModifierState); } - // Native callback. - @SuppressWarnings("unused") - boolean isInputMethodConnectionActive() { - return mInputMethodManagerInternal != null - && mInputMethodManagerInternal.isAnyInputConnectionActive(); - } - /** * Callback interface implemented by the Window Manager. */ @@ -3301,6 +3294,11 @@ public class InputManagerService extends IInputManager.Stub } @Override + public void notifyInputMethodConnectionActive(boolean connectionIsActive) { + mNative.setInputMethodConnectionIsActive(connectionIsActive); + } + + @Override public InputChannel createInputChannel(String inputChannelName) { return InputManagerService.this.createInputChannel(inputChannelName); } diff --git a/services/core/java/com/android/server/input/NativeInputManagerService.java b/services/core/java/com/android/server/input/NativeInputManagerService.java index 972a9e34f496..32d5044d9e41 100644 --- a/services/core/java/com/android/server/input/NativeInputManagerService.java +++ b/services/core/java/com/android/server/input/NativeInputManagerService.java @@ -273,6 +273,8 @@ interface NativeInputManagerService { */ void setAccessibilityStickyKeysEnabled(boolean enabled); + void setInputMethodConnectionIsActive(boolean isActive); + /** The native implementation of InputManagerService methods. */ class NativeImpl implements NativeInputManagerService { /** Pointer to native input manager service object, used by native code. */ @@ -549,5 +551,8 @@ interface NativeInputManagerService { @Override public native void setAccessibilityStickyKeysEnabled(boolean enabled); + + @Override + public native void setInputMethodConnectionIsActive(boolean isActive); } } diff --git a/services/core/java/com/android/server/inputmethod/InputMethodManagerInternal.java b/services/core/java/com/android/server/inputmethod/InputMethodManagerInternal.java index dda50cab2cdd..1d048cb687cb 100644 --- a/services/core/java/com/android/server/inputmethod/InputMethodManagerInternal.java +++ b/services/core/java/com/android/server/inputmethod/InputMethodManagerInternal.java @@ -235,12 +235,6 @@ public abstract class InputMethodManagerInternal { IBinder targetWindowToken); /** - * Returns true if any InputConnection is currently active. - * {@hide} - */ - public abstract boolean isAnyInputConnectionActive(); - - /** * Fake implementation of {@link InputMethodManagerInternal}. All the methods do nothing. */ private static final InputMethodManagerInternal NOP = @@ -331,11 +325,6 @@ public abstract class InputMethodManagerInternal { public void onSwitchKeyboardLayoutShortcut(int direction, int displayId, IBinder targetWindowToken) { } - - @Override - public boolean isAnyInputConnectionActive() { - return false; - } }; /** diff --git a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java index b595f0e71d16..c6a48ec9018e 100644 --- a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java +++ b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java @@ -2177,6 +2177,8 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub prepareClientSwitchLocked(cs); } + final boolean connectionWasActive = mCurInputConnection != null; + // Bump up the sequence for this client and attach it. advanceSequenceNumberLocked(); mCurClient = cs; @@ -2195,6 +2197,12 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub } mCurEditorInfo = editorInfo; + // Notify input manager if the connection state changes. + final boolean connectionIsActive = mCurInputConnection != null; + if (connectionIsActive != connectionWasActive) { + mInputManagerInternal.notifyInputMethodConnectionActive(connectionIsActive); + } + // If configured, we want to avoid starting up the IME if it is not supposed to be showing if (shouldPreventImeStartupLocked(selectedMethodId, startInputFlags, unverifiedTargetSdkVersion)) { @@ -5693,14 +5701,6 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub switchKeyboardLayoutLocked(direction); } } - - /** - * Returns true if any InputConnection is currently active. - */ - @Override - public boolean isAnyInputConnectionActive() { - return mCurInputConnection != null; - } } @BinderThread diff --git a/services/core/java/com/android/server/media/SystemMediaRoute2Provider.java b/services/core/java/com/android/server/media/SystemMediaRoute2Provider.java index 319947ab1016..67bc61c3b9f6 100644 --- a/services/core/java/com/android/server/media/SystemMediaRoute2Provider.java +++ b/services/core/java/com/android/server/media/SystemMediaRoute2Provider.java @@ -336,6 +336,14 @@ class SystemMediaRoute2Provider extends MediaRoute2Provider { } } } + + if (Flags.enableBuiltInSpeakerRouteSuitabilityStatuses()) { + RoutingSessionInfo oldSessionInfo = mSessionInfos.get(0); + builder.setTransferReason(oldSessionInfo.getTransferReason()) + .setTransferInitiator(oldSessionInfo.getTransferInitiatorUserHandle(), + oldSessionInfo.getTransferInitiatorPackageName()); + } + return builder.setProviderId(mUniqueId).build(); } } diff --git a/services/core/java/com/android/server/wm/AccessibilityController.java b/services/core/java/com/android/server/wm/AccessibilityController.java index 3f041cb48ee2..1da9f25afbc7 100644 --- a/services/core/java/com/android/server/wm/AccessibilityController.java +++ b/services/core/java/com/android/server/wm/AccessibilityController.java @@ -1723,7 +1723,7 @@ final class AccessibilityController { mA11yWindowsPopulator.populateVisibleWindowsOnScreenLocked( mDisplayId, visibleWindows); - if (!com.android.server.accessibility.Flags.computeWindowChangesOnA11y()) { + if (!com.android.server.accessibility.Flags.computeWindowChangesOnA11yV2()) { windows = buildWindowInfoListLocked(visibleWindows, screenSize); } @@ -1732,7 +1732,7 @@ final class AccessibilityController { topFocusedWindowToken = topFocusedWindowState.mClient.asBinder(); } - if (com.android.server.accessibility.Flags.computeWindowChangesOnA11y()) { + if (com.android.server.accessibility.Flags.computeWindowChangesOnA11yV2()) { mCallback.onAccessibilityWindowsChanged(forceSend, topFocusedDisplayId, topFocusedWindowToken, screenSize, visibleWindows); } else { @@ -1747,7 +1747,7 @@ final class AccessibilityController { mInitialized = true; } - // Here are old code paths, called when computeWindowChangesOnA11y flag is disabled. + // Here are old code paths, called when computeWindowChangesOnA11yV2 flag is disabled. // LINT.IfChange /** diff --git a/services/core/java/com/android/server/wm/AccessibilityWindowsPopulator.java b/services/core/java/com/android/server/wm/AccessibilityWindowsPopulator.java index ac3251c9bb12..f6afc52fd8d8 100644 --- a/services/core/java/com/android/server/wm/AccessibilityWindowsPopulator.java +++ b/services/core/java/com/android/server/wm/AccessibilityWindowsPopulator.java @@ -722,7 +722,7 @@ public final class AccessibilityWindowsPopulator extends WindowInfosListener { } // Compute system bar insets frame if needed. - if (com.android.server.accessibility.Flags.computeWindowChangesOnA11y() + if (com.android.server.accessibility.Flags.computeWindowChangesOnA11yV2() && windowState != null && instance.isUntouchableNavigationBar()) { final InsetsSourceProvider provider = windowState.getControllableInsetProvider(); diff --git a/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java b/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java index 4ec2f576efa9..07f52574395c 100644 --- a/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java +++ b/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java @@ -2266,7 +2266,7 @@ public class ActivityTaskSupervisor implements RecentTasks.Callbacks { * activity releases the top state and reports back, message about acquiring top state will be * sent to the new top resumed activity. */ - void updateTopResumedActivityIfNeeded(String reason) { + ActivityRecord updateTopResumedActivityIfNeeded(String reason) { final ActivityRecord prevTopActivity = mTopResumedActivity; final Task topRootTask = mRootWindowContainer.getTopDisplayFocusedRootTask(); if (topRootTask == null || topRootTask.getTopResumedActivity() == prevTopActivity) { @@ -2279,7 +2279,7 @@ public class ActivityTaskSupervisor implements RecentTasks.Callbacks { // according to the current top focused activity. mService.updateTopApp(null /* topResumedActivity */); } - return; + return mTopResumedActivity; } // Ask previous activity to release the top state. @@ -2306,6 +2306,8 @@ public class ActivityTaskSupervisor implements RecentTasks.Callbacks { scheduleTopResumedActivityStateIfNeeded(); mService.updateTopApp(mTopResumedActivity); + + return mTopResumedActivity; } /** Schedule current top resumed activity state loss */ diff --git a/services/core/java/com/android/server/wm/BackgroundActivityStartController.java b/services/core/java/com/android/server/wm/BackgroundActivityStartController.java index 2788342cb097..933633836e56 100644 --- a/services/core/java/com/android/server/wm/BackgroundActivityStartController.java +++ b/services/core/java/com/android/server/wm/BackgroundActivityStartController.java @@ -853,8 +853,7 @@ public class BackgroundActivityStartController { } // don't abort if the callingUid has START_ACTIVITIES_FROM_BACKGROUND permission - if (ActivityTaskManagerService.checkPermission(START_ACTIVITIES_FROM_BACKGROUND, - callingPid, callingUid) == PERMISSION_GRANTED) { + if (hasBalPermission(callingUid, callingPid)) { return new BalVerdict(BAL_ALLOW_PERMISSION, /*background*/ true, "START_ACTIVITIES_FROM_BACKGROUND permission granted"); @@ -919,9 +918,7 @@ public class BackgroundActivityStartController { BalVerdict checkBackgroundActivityStartAllowedBySender(BalState state) { if (state.isPendingIntentBalAllowedByPermission() - && ActivityManager.checkComponentPermission( - android.Manifest.permission.START_ACTIVITIES_FROM_BACKGROUND, - state.mRealCallingUid, NO_PROCESS_UID, true) == PackageManager.PERMISSION_GRANTED) { + && hasBalPermission(state.mRealCallingUid, state.mRealCallingPid)) { return new BalVerdict(BAL_ALLOW_PERMISSION, /*background*/ false, "realCallingUid has BAL permission."); @@ -980,6 +977,11 @@ public class BackgroundActivityStartController { return BalVerdict.BLOCK; } + @VisibleForTesting boolean hasBalPermission(int uid, int pid) { + return ActivityTaskManagerService.checkPermission(START_ACTIVITIES_FROM_BACKGROUND, + pid, uid) == PERMISSION_GRANTED; + } + /** * Check if the app allows BAL. * <p> diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java index 9b2ca3953b0d..b89c12bca62f 100644 --- a/services/core/java/com/android/server/wm/Task.java +++ b/services/core/java/com/android/server/wm/Task.java @@ -1074,9 +1074,11 @@ class Task extends TaskFragment { // as the one in the task because either one of them could be the alias activity. if (Objects.equals(realActivity, r.mActivityComponent) && this.intent != null) { intent.setComponent(this.intent.getComponent()); - // Make sure the package name the same to prevent one of the intent is set while the - // other one is not. - intent.setPackage(this.intent.getPackage()); + if (intent.getSelector() == null) { + // Make sure the package name the same to prevent one of the intent is set while the + // other one is not. + intent.setPackage(this.intent.getPackage()); + } } return intent.filterEquals(this.intent); } diff --git a/services/core/java/com/android/server/wm/TaskFragment.java b/services/core/java/com/android/server/wm/TaskFragment.java index dc0e0341ee8b..bd1503f15666 100644 --- a/services/core/java/com/android/server/wm/TaskFragment.java +++ b/services/core/java/com/android/server/wm/TaskFragment.java @@ -579,7 +579,13 @@ class TaskFragment extends WindowContainer<WindowContainer> { final ActivityRecord prevR = mResumedActivity; mResumedActivity = r; - mTaskSupervisor.updateTopResumedActivityIfNeeded(reason); + final ActivityRecord topResumed = mTaskSupervisor.updateTopResumedActivityIfNeeded(reason); + if (mResumedActivity != null && topResumed != null && topResumed.isEmbedded() + && topResumed.getTaskFragment().getAdjacentTaskFragment() == this) { + // Explicitly updates the last resumed Activity if the resumed activity is + // adjacent to the top-resumed embedded activity. + mAtmService.setLastResumedActivityUncheckLocked(mResumedActivity, reason); + } if (r == null && prevR.mDisplayContent != null && prevR.mDisplayContent.getFocusedRootTask() == null) { // Only need to notify DWPC when no activity will resume. diff --git a/services/core/java/com/android/server/wm/WallpaperWindowToken.java b/services/core/java/com/android/server/wm/WallpaperWindowToken.java index dc500a2748cf..55eeaf22cca8 100644 --- a/services/core/java/com/android/server/wm/WallpaperWindowToken.java +++ b/services/core/java/com/android/server/wm/WallpaperWindowToken.java @@ -192,7 +192,18 @@ class WallpaperWindowToken extends WindowToken { void setVisibility(boolean visible) { if (mVisibleRequested != visible) { // Before setting mVisibleRequested so we can track changes. - mTransitionController.collect(this); + final WindowState wpTarget = mDisplayContent.mWallpaperController.getWallpaperTarget(); + final boolean isTargetNotCollectedActivity = wpTarget != null + && wpTarget.mActivityRecord != null + && !mTransitionController.isCollecting(wpTarget.mActivityRecord); + // Skip collecting requesting-invisible wallpaper if the wallpaper target is an activity + // and it is not collected. Because the visibility change may be called after the + // transition of activity is finished, e.g. WallpaperController#hideWallpapers from + // hiding surface of the target. Then if there is a next transition, the wallpaper + // change may be collected into the unrelated transition and cause a weird animation. + if (!isTargetNotCollectedActivity || visible) { + mTransitionController.collect(this); + } setVisibleRequested(visible); } diff --git a/services/core/java/com/android/server/wm/WindowManagerInternal.java b/services/core/java/com/android/server/wm/WindowManagerInternal.java index daf8129f1683..7e2ffd486c7e 100644 --- a/services/core/java/com/android/server/wm/WindowManagerInternal.java +++ b/services/core/java/com/android/server/wm/WindowManagerInternal.java @@ -160,7 +160,7 @@ public abstract class WindowManagerInternal { /** * Called when the windows for accessibility changed. This is called if - * {@link com.android.server.accessibility.Flags.FLAG_COMPUTE_WINDOW_CHANGES_ON_A11Y} is + * {@link com.android.server.accessibility.Flags.FLAG_COMPUTE_WINDOW_CHANGES_ON_A11Y_V2} is * false. * * @param forceSend Send the windows for accessibility even if they haven't changed. @@ -173,7 +173,7 @@ public abstract class WindowManagerInternal { /** * Called when the windows for accessibility changed. This is called if - * {@link com.android.server.accessibility.Flags.FLAG_COMPUTE_WINDOW_CHANGES_ON_A11Y} is + * {@link com.android.server.accessibility.Flags.FLAG_COMPUTE_WINDOW_CHANGES_ON_A11Y_V2} is * true. * TODO(b/322444245): Remove screenSize parameter by getting it from * DisplayManager#getDisplay(int).getRealSize() on the a11y side. diff --git a/services/core/jni/com_android_server_input_InputManagerService.cpp b/services/core/jni/com_android_server_input_InputManagerService.cpp index 70224db061c7..88c47f3cc5f8 100644 --- a/services/core/jni/com_android_server_input_InputManagerService.cpp +++ b/services/core/jni/com_android_server_input_InputManagerService.cpp @@ -117,7 +117,6 @@ static struct { jmethodID notifySensorAccuracy; jmethodID notifyStickyModifierStateChanged; jmethodID notifyStylusGestureStarted; - jmethodID isInputMethodConnectionActive; jmethodID notifyVibratorState; jmethodID filterInputEvent; jmethodID interceptKeyBeforeQueueing; @@ -311,6 +310,7 @@ public: void setStylusButtonMotionEventsEnabled(bool enabled); FloatPoint getMouseCursorPosition(int32_t displayId); void setStylusPointerIconEnabled(bool enabled); + void setInputMethodConnectionIsActive(bool isActive); /* --- InputReaderPolicyInterface implementation --- */ @@ -453,6 +453,9 @@ private: // True if a pointer icon should be shown for stylus pointers. bool stylusPointerIconEnabled{false}; + + // True if there is an active input method connection. + bool isInputMethodConnectionActive{false}; } mLocked GUARDED_BY(mLock); std::atomic<bool> mInteractive; @@ -1507,11 +1510,8 @@ void NativeInputManager::notifyStylusGestureStarted(int32_t deviceId, nsecs_t ev } bool NativeInputManager::isInputMethodConnectionActive() { - JNIEnv* env = jniEnv(); - const jboolean result = - env->CallBooleanMethod(mServiceObj, gServiceClassInfo.isInputMethodConnectionActive); - checkAndClearExceptionFromCallback(env, "isInputMethodConnectionActive"); - return result; + std::scoped_lock _l(mLock); + return mLocked.isInputMethodConnectionActive; } std::optional<DisplayViewport> NativeInputManager::getPointerViewportForAssociatedDisplay( @@ -1862,6 +1862,15 @@ void NativeInputManager::setStylusPointerIconEnabled(bool enabled) { InputReaderConfiguration::Change::DISPLAY_INFO); } +void NativeInputManager::setInputMethodConnectionIsActive(bool isActive) { + { // acquire lock + std::scoped_lock _l(mLock); + mLocked.isInputMethodConnectionActive = isActive; + } // release lock + + mInputManager->getDispatcher().setInputMethodConnectionIsActive(isActive); +} + // ---------------------------------------------------------------------------- static NativeInputManager* getNativeInputManager(JNIEnv* env, jobject clazz) { @@ -2854,6 +2863,12 @@ static void nativeSetAccessibilityStickyKeysEnabled(JNIEnv* env, jobject nativeI } } +static void nativeSetInputMethodConnectionIsActive(JNIEnv* env, jobject nativeImplObj, + jboolean isActive) { + NativeInputManager* im = getNativeInputManager(env, nativeImplObj); + im->setInputMethodConnectionIsActive(isActive); +} + // ---------------------------------------------------------------------------- static const JNINativeMethod gInputManagerMethods[] = { @@ -2964,6 +2979,7 @@ static const JNINativeMethod gInputManagerMethods[] = { (void*)nativeSetAccessibilitySlowKeysThreshold}, {"setAccessibilityStickyKeysEnabled", "(Z)V", (void*)nativeSetAccessibilityStickyKeysEnabled}, + {"setInputMethodConnectionIsActive", "(Z)V", (void*)nativeSetInputMethodConnectionIsActive}, }; #define FIND_CLASS(var, className) \ @@ -3028,9 +3044,6 @@ int register_android_server_InputManager(JNIEnv* env) { GET_METHOD_ID(gServiceClassInfo.notifyStylusGestureStarted, clazz, "notifyStylusGestureStarted", "(IJ)V"); - GET_METHOD_ID(gServiceClassInfo.isInputMethodConnectionActive, clazz, - "isInputMethodConnectionActive", "()Z"); - GET_METHOD_ID(gServiceClassInfo.notifyVibratorState, clazz, "notifyVibratorState", "(IZ)V"); GET_METHOD_ID(gServiceClassInfo.notifyNoFocusedWindowAnr, clazz, "notifyNoFocusedWindowAnr", diff --git a/services/core/xsd/display-device-config/display-device-config.xsd b/services/core/xsd/display-device-config/display-device-config.xsd index 912ff4ae2022..755a7f884a95 100644 --- a/services/core/xsd/display-device-config/display-device-config.xsd +++ b/services/core/xsd/display-device-config/display-device-config.xsd @@ -162,7 +162,7 @@ <xs:element type="usiVersion" name="usiVersion"> <xs:annotation name="final"/> </xs:element> - <xs:element type="lowBrightnessMode" name="lowBrightness"> + <xs:element type="evenDimmerMode" name="evenDimmer"> <xs:attribute name="enabled" type="xs:boolean" use="optional"/> <xs:annotation name="final"/> </xs:element> @@ -221,7 +221,7 @@ </xs:restriction> </xs:simpleType> - <xs:complexType name="lowBrightnessMode"> + <xs:complexType name="evenDimmerMode"> <xs:sequence> <xs:element name="transitionPoint" type="nonNegativeDecimal" minOccurs="1" maxOccurs="1"> diff --git a/services/core/xsd/display-device-config/schema/current.txt b/services/core/xsd/display-device-config/schema/current.txt index 3c708900c64e..4fa77d963733 100644 --- a/services/core/xsd/display-device-config/schema/current.txt +++ b/services/core/xsd/display-device-config/schema/current.txt @@ -109,11 +109,11 @@ package com.android.server.display.config { method @Nullable public final com.android.server.display.config.DensityMapping getDensityMapping(); method @NonNull public final com.android.server.display.config.Thresholds getDisplayBrightnessChangeThresholds(); method public final com.android.server.display.config.Thresholds getDisplayBrightnessChangeThresholdsIdle(); + method public final com.android.server.display.config.EvenDimmerMode getEvenDimmer(); method @Nullable public final com.android.server.display.config.HdrBrightnessConfig getHdrBrightnessConfig(); method public com.android.server.display.config.HighBrightnessMode getHighBrightnessMode(); method public final com.android.server.display.config.IdleScreenRefreshRateTimeout getIdleScreenRefreshRateTimeout(); method public final com.android.server.display.config.SensorDetails getLightSensor(); - method public final com.android.server.display.config.LowBrightnessMode getLowBrightness(); method public com.android.server.display.config.LuxThrottling getLuxThrottling(); method @Nullable public final String getName(); method public com.android.server.display.config.PowerThrottlingConfig getPowerThrottlingConfig(); @@ -146,11 +146,11 @@ package com.android.server.display.config { method public final void setDensityMapping(@Nullable com.android.server.display.config.DensityMapping); method public final void setDisplayBrightnessChangeThresholds(@NonNull com.android.server.display.config.Thresholds); method public final void setDisplayBrightnessChangeThresholdsIdle(com.android.server.display.config.Thresholds); + method public final void setEvenDimmer(com.android.server.display.config.EvenDimmerMode); method public final void setHdrBrightnessConfig(@Nullable com.android.server.display.config.HdrBrightnessConfig); method public void setHighBrightnessMode(com.android.server.display.config.HighBrightnessMode); method public final void setIdleScreenRefreshRateTimeout(com.android.server.display.config.IdleScreenRefreshRateTimeout); method public final void setLightSensor(com.android.server.display.config.SensorDetails); - method public final void setLowBrightness(com.android.server.display.config.LowBrightnessMode); method public void setLuxThrottling(com.android.server.display.config.LuxThrottling); method public final void setName(@Nullable String); method public void setPowerThrottlingConfig(com.android.server.display.config.PowerThrottlingConfig); @@ -182,6 +182,19 @@ package com.android.server.display.config { method public java.util.List<java.lang.String> getQuirk(); } + public class EvenDimmerMode { + ctor public EvenDimmerMode(); + method public java.util.List<java.lang.Float> getBacklight(); + method public java.util.List<java.lang.Float> getBrightness(); + method public boolean getEnabled(); + method public com.android.server.display.config.NitsMap getLuxToMinimumNitsMap(); + method public java.util.List<java.lang.Float> getNits(); + method public java.math.BigDecimal getTransitionPoint(); + method public void setEnabled(boolean); + method public void setLuxToMinimumNitsMap(com.android.server.display.config.NitsMap); + method public void setTransitionPoint(java.math.BigDecimal); + } + public class HbmTiming { ctor public HbmTiming(); method @NonNull public final java.math.BigInteger getTimeMaxSecs_all(); @@ -250,19 +263,6 @@ package com.android.server.display.config { method public java.util.List<java.math.BigInteger> getItem(); } - public class LowBrightnessMode { - ctor public LowBrightnessMode(); - method public java.util.List<java.lang.Float> getBacklight(); - method public java.util.List<java.lang.Float> getBrightness(); - method public boolean getEnabled(); - method public com.android.server.display.config.NitsMap getLuxToMinimumNitsMap(); - method public java.util.List<java.lang.Float> getNits(); - method public java.math.BigDecimal getTransitionPoint(); - method public void setEnabled(boolean); - method public void setLuxToMinimumNitsMap(com.android.server.display.config.NitsMap); - method public void setTransitionPoint(java.math.BigDecimal); - } - public class LuxThrottling { ctor public LuxThrottling(); method @NonNull public final java.util.List<com.android.server.display.config.BrightnessLimitMap> getBrightnessLimitMap(); diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyEngine.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyEngine.java index 6aeb4fd53905..28fe5dfc9bc1 100644 --- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyEngine.java +++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyEngine.java @@ -1671,19 +1671,29 @@ final class DevicePolicyEngine { public void dump(IndentingPrintWriter pw) { synchronized (mLock) { pw.println("Local Policies: "); + pw.increaseIndent(); for (int i = 0; i < mLocalPolicies.size(); i++) { - for (PolicyKey policy : mLocalPolicies.get(mLocalPolicies.keyAt(i)).keySet()) { - PolicyState<?> policyState = mLocalPolicies.get( - mLocalPolicies.keyAt(i)).get(policy); - pw.println(policyState); + int userId = mLocalPolicies.keyAt(i); + pw.printf("User %d:\n", userId); + pw.increaseIndent(); + for (PolicyKey policy : mLocalPolicies.get(userId).keySet()) { + PolicyState<?> policyState = mLocalPolicies.get(userId).get(policy); + policyState.dump(pw); + pw.println(); } + pw.decreaseIndent(); } + pw.decreaseIndent(); pw.println(); + pw.println("Global Policies: "); + pw.increaseIndent(); for (PolicyKey policy : mGlobalPolicies.keySet()) { PolicyState<?> policyState = mGlobalPolicies.get(policy); - pw.println(policyState); + policyState.dump(pw); + pw.println(); } + pw.decreaseIndent(); } } diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/EnforcingAdmin.java b/services/devicepolicy/java/com/android/server/devicepolicy/EnforcingAdmin.java index 49ffb0d01019..d234dee3c8f7 100644 --- a/services/devicepolicy/java/com/android/server/devicepolicy/EnforcingAdmin.java +++ b/services/devicepolicy/java/com/android/server/devicepolicy/EnforcingAdmin.java @@ -325,8 +325,22 @@ final class EnforcingAdmin { @Override public String toString() { - return "EnforcingAdmin { mPackageName= " + mPackageName + ", mComponentName= " - + mComponentName + ", mAuthorities= " + mAuthorities + ", mUserId= " - + mUserId + ", mIsRoleAuthority= " + mIsRoleAuthority + " }\n"; + StringBuilder sb = new StringBuilder(); + sb.append("EnforcingAdmin { mPackageName= "); + sb.append(mPackageName); + if (mComponentName != null) { + sb.append(", mComponentName= "); + sb.append(mComponentName); + } + if (mAuthorities != null) { + sb.append(", mAuthorities= "); + sb.append(mAuthorities); + } + sb.append(", mUserId= "); + sb.append(mUserId); + sb.append(", mIsRoleAuthority= "); + sb.append(mIsRoleAuthority); + sb.append(" }"); + return sb.toString(); } } diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/PolicyState.java b/services/devicepolicy/java/com/android/server/devicepolicy/PolicyState.java index c2e370eeec44..c544ebc008b9 100644 --- a/services/devicepolicy/java/com/android/server/devicepolicy/PolicyState.java +++ b/services/devicepolicy/java/com/android/server/devicepolicy/PolicyState.java @@ -19,6 +19,7 @@ package com.android.server.devicepolicy; import android.annotation.NonNull; import android.annotation.Nullable; import android.app.admin.PolicyValue; +import android.util.IndentingPrintWriter; import com.android.internal.util.XmlUtils; import com.android.modules.utils.TypedXmlPullParser; @@ -195,6 +196,33 @@ final class PolicyState<V> { + ",\nmCurrentResolvedPolicy= \n\t" + mCurrentResolvedPolicy + " }"; } + public void dump(IndentingPrintWriter pw) { + pw.println(mPolicyDefinition.getPolicyKey()); + pw.increaseIndent(); + + pw.println("Per-admin Policy"); + pw.increaseIndent(); + if (mPoliciesSetByAdmins.size() == 0) { + pw.println("null"); + } else { + for (EnforcingAdmin admin : mPoliciesSetByAdmins.keySet()) { + pw.println(admin); + pw.increaseIndent(); + pw.println(mPoliciesSetByAdmins.get(admin)); + pw.decreaseIndent(); + } + } + pw.decreaseIndent(); + + pw.printf("Resolved Policy (%s):\n", + mPolicyDefinition.getResolutionMechanism().getClass().getSimpleName()); + pw.increaseIndent(); + pw.println(mCurrentResolvedPolicy); + pw.decreaseIndent(); + + pw.decreaseIndent(); + } + void saveToXml(TypedXmlSerializer serializer) throws IOException { serializer.startTag(/* namespace= */ null, TAG_POLICY_DEFINITION_ENTRY); mPolicyDefinition.saveToXml(serializer); diff --git a/services/tests/VpnTests/java/com/android/server/connectivity/VpnTest.java b/services/tests/VpnTests/java/com/android/server/connectivity/VpnTest.java index 9115f952b724..08155c7b3f98 100644 --- a/services/tests/VpnTests/java/com/android/server/connectivity/VpnTest.java +++ b/services/tests/VpnTests/java/com/android/server/connectivity/VpnTest.java @@ -3158,13 +3158,6 @@ public class VpnTest extends VpnTestBase { assertEquals(profile, ikev2VpnProfile.toVpnProfile()); } - private void assertTransportInfoMatches(NetworkCapabilities nc, int type) { - assertNotNull(nc); - VpnTransportInfo ti = (VpnTransportInfo) nc.getTransportInfo(); - assertNotNull(ti); - assertEquals(type, ti.getType()); - } - // Make it public and un-final so as to spy it public class TestDeps extends Vpn.Dependencies { TestDeps() {} diff --git a/services/tests/displayservicetests/AndroidManifest.xml b/services/tests/displayservicetests/AndroidManifest.xml index e71ea263983a..74260cdd497c 100644 --- a/services/tests/displayservicetests/AndroidManifest.xml +++ b/services/tests/displayservicetests/AndroidManifest.xml @@ -29,6 +29,7 @@ <uses-permission android:name="android.permission.READ_DEVICE_CONFIG" /> <uses-permission android:name="android.permission.WRITE_SECURE_SETTINGS" /> <uses-permission android:name="android.permission.MANAGE_USB" /> + <uses-permission android:name="android.permission.MODIFY_USER_PREFERRED_DISPLAY_MODE" /> <!-- Permissions needed for DisplayTransformManagerTest --> <uses-permission android:name="android.permission.CHANGE_CONFIGURATION" /> diff --git a/services/tests/displayservicetests/src/com/android/server/display/DisplayDeviceConfigTest.java b/services/tests/displayservicetests/src/com/android/server/display/DisplayDeviceConfigTest.java index 5a022c0f5d27..494a6677e633 100644 --- a/services/tests/displayservicetests/src/com/android/server/display/DisplayDeviceConfigTest.java +++ b/services/tests/displayservicetests/src/com/android/server/display/DisplayDeviceConfigTest.java @@ -913,7 +913,7 @@ public final class DisplayDeviceConfigTest { setupDisplayDeviceConfigFromDisplayConfigFile(getContent(getValidLuxThrottling(), getValidProxSensor(), /* includeIdleMode= */ false, /* enableEvenDimmer */ true)); - assertTrue(mDisplayDeviceConfig.getLbmEnabled()); + assertTrue(mDisplayDeviceConfig.isEvenDimmerAvailable()); assertEquals(0.0001f, mDisplayDeviceConfig.getBacklightFromBrightness(0.1f), ZERO_DELTA); assertEquals(0.2f, mDisplayDeviceConfig.getNitsFromBacklight(0.0f), ZERO_DELTA); } @@ -1637,7 +1637,7 @@ public final class DisplayDeviceConfigTest { } private String evenDimmerConfig(boolean enabled) { - return (enabled ? "<lowBrightness enabled=\"true\">" : "<lowBrightness enabled=\"false\">") + return (enabled ? "<evenDimmer enabled=\"true\">" : "<evenDimmer enabled=\"false\">") + " <transitionPoint>0.1</transitionPoint>\n" + " <nits>0.2</nits>\n" + " <nits>2.0</nits>\n" @@ -1662,7 +1662,7 @@ public final class DisplayDeviceConfigTest { + " <value>100</value> <nits>1.0</nits>\n" + " </point>\n" + " </luxToMinimumNitsMap>\n" - + "</lowBrightness>"; + + "</evenDimmer>"; } private void mockDeviceConfigs() { diff --git a/services/tests/displayservicetests/src/com/android/server/display/DisplayManagerServiceTest.java b/services/tests/displayservicetests/src/com/android/server/display/DisplayManagerServiceTest.java index 869cec8d733d..b7fa7ead4c81 100644 --- a/services/tests/displayservicetests/src/com/android/server/display/DisplayManagerServiceTest.java +++ b/services/tests/displayservicetests/src/com/android/server/display/DisplayManagerServiceTest.java @@ -45,6 +45,7 @@ import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.ArgumentMatchers.anyLong; +import static org.mockito.ArgumentMatchers.argThat; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.ArgumentMatchers.nullable; import static org.mockito.Mockito.any; @@ -56,6 +57,7 @@ import static org.mockito.Mockito.inOrder; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; @@ -68,8 +70,11 @@ import android.companion.virtual.IVirtualDeviceManager; import android.companion.virtual.VirtualDeviceManager; import android.companion.virtual.flags.Flags; import android.compat.testing.PlatformCompatChangeRule; +import android.content.BroadcastReceiver; import android.content.Context; import android.content.ContextWrapper; +import android.content.Intent; +import android.content.IntentFilter; import android.content.pm.PackageManager; import android.content.pm.PackageManagerInternal; import android.content.pm.UserInfo; @@ -107,6 +112,7 @@ import android.os.SystemProperties; import android.os.UserManager; import android.platform.test.flag.junit.SetFlagsRule; import android.provider.Settings; +import android.provider.Settings.SettingNotFoundException; import android.test.mock.MockContentResolver; import android.util.SparseArray; import android.view.ContentRecordingSession; @@ -153,6 +159,7 @@ import org.junit.Test; import org.junit.rules.TestRule; import org.junit.runner.RunWith; import org.mockito.ArgumentCaptor; +import org.mockito.ArgumentMatcher; import org.mockito.Captor; import org.mockito.InOrder; import org.mockito.Mock; @@ -327,6 +334,7 @@ public class DisplayManagerServiceTest { } private final DisplayManagerService.Injector mBasicInjector = new BasicInjector(); + private final FakeSettingsProvider mFakeSettingsProvider = new FakeSettingsProvider(); @Mock DisplayNotificationManager mMockedDisplayNotificationManager; @Mock IMediaProjectionManager mMockProjectionService; @@ -384,6 +392,7 @@ public class DisplayManagerServiceTest { ApplicationProvider.getApplicationContext().createDisplayContext(display))); final MockContentResolver resolver = mSettingsProviderRule.mockContentResolver(mContext); when(mContext.getContentResolver()).thenReturn(resolver); + resolver.addProvider(Settings.AUTHORITY, mFakeSettingsProvider); mResources = Mockito.spy(mContext.getResources()); mPowerHandler = new Handler(Looper.getMainLooper()); manageDisplaysPermission(/* granted= */ false); @@ -2893,6 +2902,110 @@ public class DisplayManagerServiceTest { FLOAT_TOLERANCE); } + @Test + public void testResolutionChangeGetsBackedUp_FeatureFlagFalse() throws Exception { + when(mMockFlags.isResolutionBackupRestoreEnabled()).thenReturn(false); + DisplayManagerService displayManager = + new DisplayManagerService(mContext, mBasicInjector); + + Display.Mode[] modes = new Display.Mode[2]; + modes[0] = new Display.Mode(/*id=*/ 101, /*width=*/ 100, /*height=*/ 200, /*rr=*/ 60); + modes[1] = new Display.Mode(/*id=*/ 101, /*width=*/ 200, /*height=*/ 400, /*rr=*/ 60); + FakeDisplayDevice displayDevice = createFakeDisplayDevice(displayManager, modes); + waitForIdleHandler(displayManager.getDisplayHandler()); + + displayManager.onBootPhase(SystemService.PHASE_BOOT_COMPLETED); + + DisplayManagerService.BinderService bs = displayManager.new BinderService(); + + bs.setUserPreferredDisplayMode(Display.DEFAULT_DISPLAY, new Display.Mode(100, 200, 0)); + try { + Settings.Secure.getInt(mContext.getContentResolver(), + Settings.Secure.SCREEN_RESOLUTION_MODE); + fail("SettingNotFoundException should have been thrown."); + } catch (SettingNotFoundException expected) { + } + } + + @Test + public void testResolutionChangeGetsBackedUp() throws Exception { + when(mMockFlags.isResolutionBackupRestoreEnabled()).thenReturn(true); + DisplayManagerService displayManager = + new DisplayManagerService(mContext, mBasicInjector); + + Display.Mode[] modes = new Display.Mode[2]; + modes[0] = new Display.Mode(/*id=*/ 101, /*width=*/ 100, /*height=*/ 200, /*rr=*/ 60); + modes[1] = new Display.Mode(/*id=*/ 101, /*width=*/ 200, /*height=*/ 400, /*rr=*/ 60); + FakeDisplayDevice displayDevice = createFakeDisplayDevice(displayManager, modes); + waitForIdleHandler(displayManager.getDisplayHandler()); + + displayManager.onBootPhase(SystemService.PHASE_BOOT_COMPLETED); + + DisplayManagerService.BinderService bs = displayManager.new BinderService(); + + bs.setUserPreferredDisplayMode(Display.DEFAULT_DISPLAY, new Display.Mode(100, 200, 0)); + assertEquals(Settings.Secure.RESOLUTION_MODE_HIGH, + Settings.Secure.getInt(mContext.getContentResolver(), + Settings.Secure.SCREEN_RESOLUTION_MODE)); + + bs.setUserPreferredDisplayMode(Display.DEFAULT_DISPLAY, new Display.Mode(200, 400, 0)); + assertEquals(Settings.Secure.RESOLUTION_MODE_FULL, + Settings.Secure.getInt(mContext.getContentResolver(), + Settings.Secure.SCREEN_RESOLUTION_MODE)); + } + + @Test + public void testResolutionGetsRestored() throws Exception { + when(mMockFlags.isResolutionBackupRestoreEnabled()).thenReturn(true); + DisplayManagerService displayManager = + new DisplayManagerService(mContext, mBasicInjector); + + displayManager.systemReady(false /* safeMode */); + ArgumentCaptor<BroadcastReceiver> receiverCaptor = + ArgumentCaptor.forClass(BroadcastReceiver.class); + ArgumentMatcher<IntentFilter> matchesFilter = + (filter) -> Intent.ACTION_SETTING_RESTORED.equals(filter.getAction(0)); + verify(mContext).registerReceiver(receiverCaptor.capture(), argThat(matchesFilter)); + BroadcastReceiver receiver = receiverCaptor.getValue(); + + Display.Mode emptyMode = new Display.Mode.Builder().build(); + Display.Mode[] modes = new Display.Mode[2]; + modes[0] = new Display.Mode(/*id=*/ 101, /*width=*/ 100, /*height=*/ 200, /*rr=*/ 60); + modes[1] = new Display.Mode(/*id=*/ 102, /*width=*/ 200, /*height=*/ 400, /*rr=*/ 60); + FakeDisplayDevice displayDevice = createFakeDisplayDevice(displayManager, modes); + waitForIdleHandler(displayManager.getDisplayHandler()); + + displayManager.onBootPhase(SystemService.PHASE_BOOT_COMPLETED); + DisplayManagerService.BinderService bs = displayManager.new BinderService(); + + // Get the current display mode, ensure it is null + Display.Mode prevMode = bs.getUserPreferredDisplayMode(Display.DEFAULT_DISPLAY); + assertEquals(emptyMode, prevMode); + + // Set a new mode (FULL) via restore + Settings.Secure.putInt(mContext.getContentResolver(), + Settings.Secure.SCREEN_RESOLUTION_MODE, Settings.Secure.RESOLUTION_MODE_FULL); + Intent restoreIntent = new Intent(Intent.ACTION_SETTING_RESTORED); + restoreIntent.putExtra(Intent.EXTRA_SETTING_NAME, Settings.Secure.SCREEN_RESOLUTION_MODE); + receiver.onReceive(mContext, restoreIntent); + + Display.Mode newMode = bs.getUserPreferredDisplayMode(Display.DEFAULT_DISPLAY); + assertEquals(modes[1], newMode); + } + + @Test + public void testResolutionGetsRestored_FeatureFlagFalse() throws Exception { + when(mMockFlags.isResolutionBackupRestoreEnabled()).thenReturn(false); + DisplayManagerService displayManager = + new DisplayManagerService(mContext, mBasicInjector); + + displayManager.systemReady(false /* safeMode */); + ArgumentMatcher<IntentFilter> matchesFilter = + (filter) -> Intent.ACTION_SETTING_RESTORED.equals(filter.getAction(0)); + verify(mContext, times(0)).registerReceiver(any(BroadcastReceiver.class), + argThat(matchesFilter)); + } + private void initDisplayPowerController(DisplayManagerInternal localService) { localService.initPowerManagement(new DisplayManagerInternal.DisplayPowerCallbacks() { @Override @@ -3116,6 +3229,29 @@ public class DisplayManagerServiceTest { } private FakeDisplayDevice createFakeDisplayDevice(DisplayManagerService displayManager, + Display.Mode[] modes) { + FakeDisplayDevice displayDevice = new FakeDisplayDevice(); + DisplayDeviceInfo displayDeviceInfo = new DisplayDeviceInfo(); + displayDeviceInfo.supportedModes = modes; + displayDeviceInfo.modeId = 101; + displayDeviceInfo.type = Display.TYPE_INTERNAL; + displayDeviceInfo.renderFrameRate = displayDeviceInfo.supportedModes[0].getRefreshRate(); + displayDeviceInfo.width = displayDeviceInfo.supportedModes[0].getPhysicalWidth(); + displayDeviceInfo.height = displayDeviceInfo.supportedModes[0].getPhysicalHeight(); + final Rect zeroRect = new Rect(); + displayDeviceInfo.displayCutout = new DisplayCutout( + Insets.of(0, 10, 0, 0), + zeroRect, new Rect(0, 0, 10, 10), zeroRect, zeroRect); + displayDeviceInfo.flags = DisplayDeviceInfo.FLAG_ALLOWED_TO_BE_DEFAULT_DISPLAY; + displayDeviceInfo.address = new TestUtils.TestDisplayAddress(); + displayDevice.setDisplayDeviceInfo(displayDeviceInfo); + displayManager.getDisplayDeviceRepository() + .onDisplayDeviceEvent(displayDevice, DisplayAdapter.DISPLAY_DEVICE_EVENT_ADDED); + return displayDevice; + } + + private FakeDisplayDevice createFakeDisplayDevice(DisplayManagerService displayManager, + float[] refreshRates) { return createFakeDisplayDevice(displayManager, refreshRates, Display.TYPE_UNKNOWN); } @@ -3307,6 +3443,7 @@ public class DisplayManagerServiceTest { private class FakeDisplayDevice extends DisplayDevice { private DisplayDeviceInfo mDisplayDeviceInfo; + private Display.Mode mPreferredMode = new Display.Mode.Builder().build(); FakeDisplayDevice() { super(mMockDisplayAdapter, /* displayToken= */ null, /* uniqueId= */ "", mContext); @@ -3325,5 +3462,23 @@ public class DisplayManagerServiceTest { public DisplayDeviceInfo getDisplayDeviceInfoLocked() { return mDisplayDeviceInfo; } + + @Override + public void setUserPreferredDisplayModeLocked(Display.Mode preferredMode) { + for (Display.Mode mode : mDisplayDeviceInfo.supportedModes) { + if (mode.matchesIfValid( + preferredMode.getPhysicalWidth(), + preferredMode.getPhysicalHeight(), + preferredMode.getRefreshRate())) { + mPreferredMode = mode; + break; + } + } + } + + @Override + public Display.Mode getUserPreferredDisplayModeLocked() { + return mPreferredMode; + } } } diff --git a/services/tests/displayservicetests/src/com/android/server/display/brightness/clamper/BrightnessLowLuxModifierTest.kt b/services/tests/displayservicetests/src/com/android/server/display/brightness/clamper/BrightnessLowLuxModifierTest.kt index 749c400f819e..21e066dd0922 100644 --- a/services/tests/displayservicetests/src/com/android/server/display/brightness/clamper/BrightnessLowLuxModifierTest.kt +++ b/services/tests/displayservicetests/src/com/android/server/display/brightness/clamper/BrightnessLowLuxModifierTest.kt @@ -32,7 +32,7 @@ import org.junit.Before import org.junit.Test import org.mockito.kotlin.mock -private const val userId = UserHandle.USER_CURRENT +private const val USER_ID = UserHandle.USER_CURRENT class BrightnessLowLuxModifierTest { @@ -85,7 +85,7 @@ class BrightnessLowLuxModifierTest { .thenReturn(1.0f) - whenever(mockDisplayDeviceConfig.lowBrightnessTransitionPoint).thenReturn(TRANSITION_POINT) + whenever(mockDisplayDeviceConfig.evenDimmerTransitionPoint).thenReturn(TRANSITION_POINT) testHandler.flush() } @@ -94,7 +94,7 @@ class BrightnessLowLuxModifierTest { fun testSettingOffDisablesModifier() { // test transition point ensures brightness doesn't drop when setting is off. Settings.Secure.putIntForUser(context.contentResolver, - Settings.Secure.EVEN_DIMMER_ACTIVATED, 0, userId) + Settings.Secure.EVEN_DIMMER_ACTIVATED, 0, USER_ID) modifier.setAutoBrightnessState(AUTO_BRIGHTNESS_ENABLED) modifier.recalculateLowerBound() testHandler.flush() @@ -112,9 +112,9 @@ class BrightnessLowLuxModifierTest { fun testLuxRestrictsBrightnessRange() { // test that high lux prevents low brightness range. Settings.Secure.putIntForUser(context.contentResolver, - Settings.Secure.EVEN_DIMMER_ACTIVATED, 1, userId) + Settings.Secure.EVEN_DIMMER_ACTIVATED, 1, USER_ID) Settings.Secure.putFloatForUser(context.contentResolver, - Settings.Secure.EVEN_DIMMER_MIN_NITS, 0.1f, userId) + Settings.Secure.EVEN_DIMMER_MIN_NITS, 0.1f, USER_ID) modifier.setAutoBrightnessState(AUTO_BRIGHTNESS_ENABLED) modifier.onAmbientLuxChange(400.0f) testHandler.flush() @@ -130,9 +130,9 @@ class BrightnessLowLuxModifierTest { fun testUserRestrictsBrightnessRange() { // test that user minimum nits setting prevents low brightness range. Settings.Secure.putIntForUser(context.contentResolver, - Settings.Secure.EVEN_DIMMER_ACTIVATED, 1, userId) + Settings.Secure.EVEN_DIMMER_ACTIVATED, 1, USER_ID) Settings.Secure.putFloatForUser(context.contentResolver, - Settings.Secure.EVEN_DIMMER_MIN_NITS, 10.0f, userId) + Settings.Secure.EVEN_DIMMER_MIN_NITS, 10.0f, USER_ID) modifier.setAutoBrightnessState(AUTO_BRIGHTNESS_ENABLED) modifier.recalculateLowerBound() testHandler.flush() @@ -149,9 +149,9 @@ class BrightnessLowLuxModifierTest { fun testOnToOff() { // test that high lux prevents low brightness range. Settings.Secure.putIntForUser(context.contentResolver, - Settings.Secure.EVEN_DIMMER_ACTIVATED, 1, userId) // on + Settings.Secure.EVEN_DIMMER_ACTIVATED, 1, USER_ID) // on Settings.Secure.putFloatForUser(context.contentResolver, - Settings.Secure.EVEN_DIMMER_MIN_NITS, 1.0f, userId) + Settings.Secure.EVEN_DIMMER_MIN_NITS, 1.0f, USER_ID) modifier.setAutoBrightnessState(AUTO_BRIGHTNESS_ENABLED) modifier.onAmbientLuxChange(400.0f) testHandler.flush() @@ -162,7 +162,7 @@ class BrightnessLowLuxModifierTest { assertThat(modifier.brightnessLowerBound).isEqualTo(LOW_LUX_BRIGHTNESS) Settings.Secure.putIntForUser(context.contentResolver, - Settings.Secure.EVEN_DIMMER_ACTIVATED, 0, userId) // off + Settings.Secure.EVEN_DIMMER_ACTIVATED, 0, USER_ID) // off modifier.recalculateLowerBound() testHandler.flush() @@ -177,9 +177,9 @@ class BrightnessLowLuxModifierTest { fun testOffToOn() { // test that high lux prevents low brightness range. Settings.Secure.putIntForUser(context.contentResolver, - Settings.Secure.EVEN_DIMMER_ACTIVATED, 0, userId) // off + Settings.Secure.EVEN_DIMMER_ACTIVATED, 0, USER_ID) // off Settings.Secure.putFloatForUser(context.contentResolver, - Settings.Secure.EVEN_DIMMER_MIN_NITS, 1.0f, userId) + Settings.Secure.EVEN_DIMMER_MIN_NITS, 1.0f, USER_ID) modifier.setAutoBrightnessState(AUTO_BRIGHTNESS_ENABLED) modifier.onAmbientLuxChange(400.0f) testHandler.flush() @@ -191,7 +191,7 @@ class BrightnessLowLuxModifierTest { Settings.Secure.putIntForUser(context.contentResolver, - Settings.Secure.EVEN_DIMMER_ACTIVATED, 1, userId) // on + Settings.Secure.EVEN_DIMMER_ACTIVATED, 1, USER_ID) // on modifier.recalculateLowerBound() testHandler.flush() @@ -206,9 +206,9 @@ class BrightnessLowLuxModifierTest { fun testDisabledWhenAutobrightnessIsOff() { // test that high lux prevents low brightness range. Settings.Secure.putIntForUser(context.contentResolver, - Settings.Secure.EVEN_DIMMER_ACTIVATED, 1, userId) // on + Settings.Secure.EVEN_DIMMER_ACTIVATED, 1, USER_ID) // on Settings.Secure.putFloatForUser(context.contentResolver, - Settings.Secure.EVEN_DIMMER_MIN_NITS, 1.0f, userId) + Settings.Secure.EVEN_DIMMER_MIN_NITS, 1.0f, USER_ID) modifier.setAutoBrightnessState(AUTO_BRIGHTNESS_ENABLED) modifier.onAmbientLuxChange(400.0f) diff --git a/services/tests/displayservicetests/src/com/android/server/display/color/ColorDisplayServiceTest.java b/services/tests/displayservicetests/src/com/android/server/display/color/ColorDisplayServiceTest.java index ec27f9d220dc..f391e409a717 100644 --- a/services/tests/displayservicetests/src/com/android/server/display/color/ColorDisplayServiceTest.java +++ b/services/tests/displayservicetests/src/com/android/server/display/color/ColorDisplayServiceTest.java @@ -38,6 +38,9 @@ import android.hardware.display.DisplayManagerInternal; import android.hardware.display.Time; import android.os.Handler; import android.os.UserHandle; +import android.platform.test.annotations.RequiresFlagsEnabled; +import android.platform.test.flag.junit.CheckFlagsRule; +import android.platform.test.flag.junit.DeviceFlagsValueProvider; import android.provider.Settings; import android.provider.Settings.Secure; import android.provider.Settings.System; @@ -51,6 +54,7 @@ import com.android.internal.R; import com.android.internal.util.test.FakeSettingsProvider; import com.android.internal.util.test.LocalServiceKeeperRule; import com.android.server.SystemService; +import com.android.server.display.feature.flags.Flags; import com.android.server.twilight.TwilightListener; import com.android.server.twilight.TwilightManager; import com.android.server.twilight.TwilightState; @@ -94,6 +98,9 @@ public class ColorDisplayServiceTest { @Rule public LocalServiceKeeperRule mLocalServiceKeeperRule = new LocalServiceKeeperRule(); + @Rule + public final CheckFlagsRule mCheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule(); + @Before public void setUp() { mContext = Mockito.spy(new ContextWrapper( @@ -1003,6 +1010,20 @@ public class ColorDisplayServiceTest { } @Test + @RequiresFlagsEnabled(Flags.FLAG_EVEN_DIMMER) + public void ensureRbcDisabledWhenEvenDimmerEnabled() { + // If rbc & even dimmer are enabled + doReturn(true).when(mResourcesSpy).getBoolean( + R.bool.config_reduceBrightColorsAvailable); + doReturn(true).when(mResourcesSpy).getBoolean( + com.android.internal.R.bool.config_evenDimmerEnabled); + startService(); + + // ensure rbc isn't enabled, since even dimmer is the successor. + assertThat(ColorDisplayManager.isReduceBrightColorsAvailable(mContext)).isFalse(); + } + + @Test public void displayWhiteBalance_enabled() { setDisplayWhiteBalanceEnabled(true); setNightDisplayActivated(false /* activated */, -30 /* lastActivatedTimeOffset */); diff --git a/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityWindowManagerTest.java b/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityWindowManagerTest.java index 8717a0500e57..403930d96a12 100644 --- a/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityWindowManagerTest.java +++ b/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityWindowManagerTest.java @@ -86,11 +86,12 @@ import java.util.List; // LINT.IfChange /** - * Tests for the AccessibilityWindowManager with Flags.FLAG_COMPUTE_WINDOW_CHANGES_ON_A11Y enabled. + * Tests for the AccessibilityWindowManager with Flags.FLAG_COMPUTE_WINDOW_CHANGES_ON_A11Y_V2 + * enabled. * TODO(b/322444245): Merge with AccessibilityWindowManagerWithAccessibilityWindowTest * after completing the flag migration. */ -@RequiresFlagsDisabled(Flags.FLAG_COMPUTE_WINDOW_CHANGES_ON_A11Y) +@RequiresFlagsDisabled(Flags.FLAG_COMPUTE_WINDOW_CHANGES_ON_A11Y_V2) public class AccessibilityWindowManagerTest { private static final String PACKAGE_NAME = "com.android.server.accessibility"; private static final boolean FORCE_SEND = true; diff --git a/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityWindowManagerWithAccessibilityWindowTest.java b/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityWindowManagerWithAccessibilityWindowTest.java index f44879fa54d9..9083a1e28e2c 100644 --- a/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityWindowManagerWithAccessibilityWindowTest.java +++ b/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityWindowManagerWithAccessibilityWindowTest.java @@ -89,11 +89,11 @@ import java.util.Arrays; import java.util.List; /** - * Tests for the AccessibilityWindowManager with Flags.FLAG_COMPUTE_WINDOW_CHANGES_ON_A11Y + * Tests for the AccessibilityWindowManager with Flags.FLAG_COMPUTE_WINDOW_CHANGES_ON_A11Y_V2 * TODO(b/322444245): Merge with AccessibilityWindowManagerTest * after completing the flag migration. */ -@RequiresFlagsEnabled(Flags.FLAG_COMPUTE_WINDOW_CHANGES_ON_A11Y) +@RequiresFlagsEnabled(Flags.FLAG_COMPUTE_WINDOW_CHANGES_ON_A11Y_V2) public class AccessibilityWindowManagerWithAccessibilityWindowTest { private static final String PACKAGE_NAME = "com.android.server.accessibility"; private static final boolean FORCE_SEND = true; diff --git a/services/tests/servicestests/src/com/android/server/appwidget/AppWidgetXmlUtilTest.kt b/services/tests/servicestests/src/com/android/server/appwidget/AppWidgetXmlUtilTest.kt new file mode 100644 index 000000000000..e2b9944f2cc7 --- /dev/null +++ b/services/tests/servicestests/src/com/android/server/appwidget/AppWidgetXmlUtilTest.kt @@ -0,0 +1,86 @@ +/* + * Copyright (C) 2024 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.appwidget + +import android.util.SizeF +import androidx.test.ext.junit.runners.AndroidJUnit4 +import androidx.test.filters.SmallTest +import com.android.server.appwidget.AppWidgetXmlUtil.deserializeWidgetSizesStr +import com.android.server.appwidget.AppWidgetXmlUtil.serializeWidgetSizes +import com.google.common.truth.Truth.assertThat +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith + +@SmallTest +@RunWith(AndroidJUnit4::class) +class AppWidgetXmlUtilTest { + + private val sizes = ArrayList<SizeF>() + private val sizeStr = "1.0x2.1,-9.91x6291.134,0.0x0.0" + + @Before + fun setup() { + sizes.add(SizeF(1.0f, 2.1f)) + sizes.add(SizeF(-9.91f, 6291.134f)) + sizes.add(SizeF(0f, 0f)) + } + + @Test + fun serializeWidgetSizes() { + val serializedSizeStr = serializeWidgetSizes(sizes) + + assertThat(serializedSizeStr).isEqualTo(sizeStr) + } + + @Test + fun deserializeWidgetSizesStr() { + val deserializedSizes = deserializeWidgetSizesStr(sizeStr) + + assertThat(deserializedSizes).isEqualTo(sizes) + } + + @Test + fun deserializeInvalidWidgetSizesStr1() { + assertThat(deserializeWidgetSizesStr("abc,def")).isEqualTo(null) + } + + @Test + fun deserializeInvalidWidgetSizesStr2() { + assertThat(deserializeWidgetSizesStr("+30x9,90")).isEqualTo(null) + } + + @Test + fun deserializeNullWidgetSizesStr1() { + assertThat(deserializeWidgetSizesStr(null)).isEqualTo(null) + } + + @Test + fun deserializeEmptyWidgetSizesStr1() { + assertThat(deserializeWidgetSizesStr("")).isEqualTo(null) + } + + @Test + fun deserializeEmptyWidgetSizesStr2() { + assertThat(deserializeWidgetSizesStr(",")).isEqualTo(ArrayList<SizeF>()) + } + + @Test + fun deserializeEmptyWidgetSizesStr3() { + assertThat(deserializeWidgetSizesStr(",,,")).isEqualTo(ArrayList<SizeF>()) + } +} diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java index beafeec20bb5..e42acba4da7a 100644 --- a/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java @@ -3110,6 +3110,10 @@ public class ActivityRecordTests extends WindowTestsBase { @Test public void testCloseToSquareFixedOrientation() { + if (Flags.insetsDecoupledConfiguration()) { + // No test needed as decor insets no longer affects orientation. + return; + } // create a square display final DisplayContent squareDisplay = new TestDisplayContent.Builder(mAtm, 2000, 2000) .setSystemDecorations(true).build(); diff --git a/services/tests/wmtests/src/com/android/server/wm/BackgroundActivityStartControllerExemptionTests.java b/services/tests/wmtests/src/com/android/server/wm/BackgroundActivityStartControllerExemptionTests.java new file mode 100644 index 000000000000..4afc8ac6c599 --- /dev/null +++ b/services/tests/wmtests/src/com/android/server/wm/BackgroundActivityStartControllerExemptionTests.java @@ -0,0 +1,567 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.wm; + +import static com.android.server.wm.ActivityTaskManagerService.APP_SWITCH_ALLOW; +import static com.android.server.wm.BackgroundActivityStartController.BAL_ALLOW_ALLOWLISTED_COMPONENT; +import static com.android.server.wm.BackgroundActivityStartController.BAL_ALLOW_FOREGROUND; +import static com.android.server.wm.BackgroundActivityStartController.BAL_ALLOW_PERMISSION; +import static com.android.server.wm.BackgroundActivityStartController.BAL_ALLOW_SAW_PERMISSION; +import static com.android.server.wm.BackgroundActivityStartController.BAL_ALLOW_VISIBLE_WINDOW; + +import static com.google.common.truth.Truth.assertThat; +import static com.google.common.truth.Truth.assertWithMessage; + +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.when; + +import android.app.ActivityOptions; +import android.app.AppOpsManager; +import android.app.BackgroundStartPrivileges; +import android.content.ComponentName; +import android.content.Context; +import android.content.Intent; +import android.content.pm.PackageManagerInternal; +import android.os.UserHandle; +import android.platform.test.annotations.Presubmit; +import android.provider.DeviceConfig; +import android.util.Pair; + +import androidx.test.filters.SmallTest; + +import com.android.compatibility.common.util.DeviceConfigStateHelper; +import com.android.modules.utils.testing.ExtendedMockitoRule; +import com.android.server.am.PendingIntentRecord; +import com.android.server.wm.BackgroundActivityStartController.BalVerdict; + +import org.junit.After; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; +import org.mockito.Mock; +import org.mockito.Mockito; +import org.mockito.quality.Strictness; + +import java.lang.reflect.Field; +import java.util.ArrayList; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +/** + * Tests for the {@link BackgroundActivityStartController} class. + * + * Build/Install/Run: + * atest WmTests:BackgroundActivityStartControllerExemptionTests + */ +@SmallTest +@Presubmit +@RunWith(JUnit4.class) +public class BackgroundActivityStartControllerExemptionTests { + + private static final int REGULAR_UID_1 = 10100; + private static final int REGULAR_UID_2 = 10200; + private static final int NO_UID = -1; + private static final int REGULAR_PID_1 = 11100; + private static final int REGULAR_PID_1_1 = 11101; + private static final int REGULAR_PID_2 = 11200; + private static final int NO_PID = -1; + private static final String REGULAR_PACKAGE_1 = "package.app1"; + private static final String REGULAR_PACKAGE_2 = "package.app2"; + + private static final Intent TEST_INTENT = new Intent() + .setComponent(new ComponentName("package.app3", "someClass")); + + @Rule + public final ExtendedMockitoRule extendedMockitoRule = + new ExtendedMockitoRule.Builder(this).setStrictness(Strictness.LENIENT).build(); + + TestableBackgroundActivityStartController mController; + @Mock + ActivityMetricsLogger mActivityMetricsLogger; + @Mock + WindowProcessController mCallerApp; + DeviceConfigStateHelper mDeviceConfig = new DeviceConfigStateHelper( + DeviceConfig.NAMESPACE_WINDOW_MANAGER); + @Mock + ActivityRecord mResultRecord; + + @Mock + ActivityTaskManagerService mService; + @Mock + Context /* mService. */ mContext; + @Mock + PackageManagerInternal /* mService. */ mPackageManagerInternal; + @Mock + RootWindowContainer /* mService. */ mRootWindowContainer; + @Mock + AppOpsManager mAppOpsManager; + MirrorActiveUids mActiveUids = new MirrorActiveUids(); + WindowProcessControllerMap mProcessMap = new WindowProcessControllerMap(); + + @Mock + ActivityTaskSupervisor mSupervisor; + @Mock + RecentTasks /* mSupervisor. */ mRecentTasks; + + @Mock + PendingIntentRecord mPendingIntentRecord; // just so we can pass a non-null instance + + record BalAllowedLog(String packageName, int code) { + } + + List<String> mShownToasts = new ArrayList<>(); + List<BalAllowedLog> mBalAllowedLogs = new ArrayList<>(); + + ActivityOptions mCheckedOptions = ActivityOptions.makeBasic() + .setPendingIntentCreatorBackgroundActivityStartMode( + ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOWED) + .setPendingIntentBackgroundActivityStartMode( + ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOWED); + + class TestableBackgroundActivityStartController extends BackgroundActivityStartController { + private Set<Pair<Integer, Integer>> mBalPermissionUidPidPairs = new HashSet<>(); + + TestableBackgroundActivityStartController(ActivityTaskManagerService service, + ActivityTaskSupervisor supervisor) { + super(service, supervisor); + } + + @Override + protected void writeBalAllowedLog(String activityName, int code, + BalState state) { + mBalAllowedLogs.add(new BalAllowedLog(activityName, code)); + } + + @Override + boolean hasBalPermission(int uid, int pid) { + return mBalPermissionUidPidPairs.contains(Pair.create(uid, pid)); + } + + void addBalPermission(int uid, int pid) { + mBalPermissionUidPidPairs.add(Pair.create(uid, pid)); + } + + } + + @Before + public void setUp() throws Exception { + // wire objects + mService.mTaskSupervisor = mSupervisor; + mService.mContext = mContext; + setViaReflection(mService, "mActiveUids", mActiveUids); + when(mService.getPackageManagerInternalLocked()).thenReturn( + mPackageManagerInternal); + mService.mRootWindowContainer = mRootWindowContainer; + when(mService.getAppOpsManager()).thenReturn(mAppOpsManager); + setViaReflection(mService, "mProcessMap", mProcessMap); + + //Mockito.when(mSupervisor.getBackgroundActivityLaunchController()).thenReturn(mController); + setViaReflection(mSupervisor, "mRecentTasks", mRecentTasks); + + mController = new TestableBackgroundActivityStartController(mService, mSupervisor); + + // nicer toString + when(mPendingIntentRecord.toString()).thenReturn("PendingIntentRecord"); + + // safe defaults + when(mAppOpsManager.checkOpNoThrow( + eq(AppOpsManager.OP_SYSTEM_EXEMPT_FROM_ACTIVITY_BG_START_RESTRICTION), + anyInt(), anyString())).thenReturn(AppOpsManager.MODE_DEFAULT); + when(mCallerApp.areBackgroundActivityStartsAllowed(anyInt())).thenReturn( + BalVerdict.BLOCK); + } + + private void setViaReflection(Object o, String property, Object value) { + try { + Field field = o.getClass().getDeclaredField(property); + field.setAccessible(true); + field.set(o, value); + } catch (IllegalAccessException | NoSuchFieldException e) { + throw new IllegalArgumentException("Cannot set " + property + " of " + o.getClass(), e); + } + } + + @After + public void tearDown() throws Exception { + } + + @Test + public void testNoExemption() { + int callingUid = REGULAR_UID_1; + int callingPid = REGULAR_PID_1; + final String callingPackage = REGULAR_PACKAGE_1; + int realCallingUid = REGULAR_UID_2; + int realCallingPid = REGULAR_PID_2; + + // prepare call + PendingIntentRecord originatingPendingIntent = mPendingIntentRecord; + BackgroundStartPrivileges forcedBalByPiSender = BackgroundStartPrivileges.NONE; + Intent intent = TEST_INTENT; + ActivityOptions checkedOptions = mCheckedOptions; + BackgroundActivityStartController.BalState balState = mController.new BalState(callingUid, + callingPid, callingPackage, realCallingUid, realCallingPid, mCallerApp, + originatingPendingIntent, forcedBalByPiSender, mResultRecord, intent, + checkedOptions); + + assertWithMessage(balState.toString()).that(balState.isPendingIntent()).isTrue(); + + // call + BalVerdict callerVerdict = mController.checkBackgroundActivityStartAllowedByCaller( + balState); + BalVerdict realCallerVerdict = mController.checkBackgroundActivityStartAllowedBySender( + balState); + + balState.setResultForCaller(callerVerdict); + balState.setResultForRealCaller(realCallerVerdict); + + // assertions + assertWithMessage(balState.toString()).that(callerVerdict.getCode()).isEqualTo( + BackgroundActivityStartController.BAL_BLOCK); + assertWithMessage(balState.toString()).that(realCallerVerdict.getCode()).isEqualTo( + BackgroundActivityStartController.BAL_BLOCK); + } + + @Test + public void testCaller_appHasVisibleWindow() { + int callingUid = REGULAR_UID_1; + int callingPid = REGULAR_PID_1; + final String callingPackage = REGULAR_PACKAGE_1; + int realCallingUid = REGULAR_UID_2; + int realCallingPid = REGULAR_PID_2; + + // setup state + when(mService.hasActiveVisibleWindow(eq(callingUid))).thenReturn(true); + when(mService.getBalAppSwitchesState()).thenReturn(APP_SWITCH_ALLOW); + + // prepare call + PendingIntentRecord originatingPendingIntent = mPendingIntentRecord; + BackgroundStartPrivileges forcedBalByPiSender = BackgroundStartPrivileges.NONE; + Intent intent = TEST_INTENT; + ActivityOptions checkedOptions = mCheckedOptions; + BackgroundActivityStartController.BalState balState = mController.new BalState(callingUid, + callingPid, callingPackage, realCallingUid, realCallingPid, mCallerApp, + originatingPendingIntent, forcedBalByPiSender, mResultRecord, intent, + checkedOptions); + + // call + BalVerdict callerVerdict = mController.checkBackgroundActivityStartAllowedByCaller( + balState); + balState.setResultForCaller(callerVerdict); + + // assertions + assertWithMessage(balState.toString()).that(callerVerdict.getCode()).isEqualTo( + BAL_ALLOW_VISIBLE_WINDOW); + } + + @Test + public void testRealCaller_appHasVisibleWindow() { + int callingUid = REGULAR_UID_1; + int callingPid = REGULAR_PID_1; + final String callingPackage = REGULAR_PACKAGE_1; + int realCallingUid = REGULAR_UID_2; + int realCallingPid = REGULAR_PID_2; + + // setup state + when(mService.hasActiveVisibleWindow(eq(realCallingUid))).thenReturn(true); + when(mService.getBalAppSwitchesState()).thenReturn(APP_SWITCH_ALLOW); + + // prepare call + PendingIntentRecord originatingPendingIntent = mPendingIntentRecord; + BackgroundStartPrivileges forcedBalByPiSender = BackgroundStartPrivileges.NONE; + Intent intent = TEST_INTENT; + ActivityOptions checkedOptions = mCheckedOptions; + BackgroundActivityStartController.BalState balState = mController.new BalState(callingUid, + callingPid, callingPackage, realCallingUid, realCallingPid, mCallerApp, + originatingPendingIntent, forcedBalByPiSender, mResultRecord, intent, + checkedOptions); + + // call + BalVerdict realCallerVerdict = mController.checkBackgroundActivityStartAllowedBySender( + balState); + balState.setResultForRealCaller(realCallerVerdict); + + // assertions + assertWithMessage(balState.toString()).that(realCallerVerdict.getCode()).isEqualTo( + BAL_ALLOW_VISIBLE_WINDOW); + } + + @Test + public void testCaller_appAllowedByBLPC() { + // This covers the cases + // - The app has an activity in the back stack of the foreground task. + // - The app has an activity in the back stack of an existing task on the Recents screen. + // - The app has an activity that started very recently. + // - The app called finish() on an activity very recently. + // - The app has a service that is bound by a different, visible app. + + int callingUid = REGULAR_UID_1; + int callingPid = REGULAR_PID_1; + final String callingPackage = REGULAR_PACKAGE_1; + int realCallingUid = REGULAR_UID_2; + int realCallingPid = REGULAR_PID_2; + + // setup state + when(mCallerApp.areBackgroundActivityStartsAllowed(anyInt())).thenReturn( + new BalVerdict(BAL_ALLOW_FOREGROUND, false, "allowed")); + when(mService.getBalAppSwitchesState()).thenReturn(APP_SWITCH_ALLOW); + + // prepare call + PendingIntentRecord originatingPendingIntent = mPendingIntentRecord; + BackgroundStartPrivileges forcedBalByPiSender = BackgroundStartPrivileges.NONE; + Intent intent = TEST_INTENT; + ActivityOptions checkedOptions = mCheckedOptions; + BackgroundActivityStartController.BalState balState = mController.new BalState(callingUid, + callingPid, callingPackage, realCallingUid, realCallingPid, mCallerApp, + originatingPendingIntent, forcedBalByPiSender, mResultRecord, intent, + checkedOptions); + + // call + BalVerdict callerVerdict = mController.checkBackgroundActivityStartAllowedByCaller( + balState); + balState.setResultForCaller(callerVerdict); + + // assertions + assertWithMessage(balState.toString()).that(callerVerdict.getCode()).isEqualTo( + BAL_ALLOW_FOREGROUND); + } + + @Test + public void testRealCaller_appAllowedByBLPC() { + int callingUid = REGULAR_UID_1; + int callingPid = REGULAR_PID_1; + final String callingPackage = REGULAR_PACKAGE_1; + int realCallingUid = REGULAR_UID_2; + int realCallingPid = REGULAR_PID_2; + + // setup state + when( + mService.getProcessController(eq(realCallingPid), eq(realCallingUid))).thenReturn( + mCallerApp); + when(mService.getBalAppSwitchesState()).thenReturn(APP_SWITCH_ALLOW); + when(mCallerApp.areBackgroundActivityStartsAllowed(anyInt())).thenReturn( + new BalVerdict(BAL_ALLOW_FOREGROUND, false, "allowed")); + + // prepare call + PendingIntentRecord originatingPendingIntent = mPendingIntentRecord; + BackgroundStartPrivileges forcedBalByPiSender = BackgroundStartPrivileges.NONE; + Intent intent = TEST_INTENT; + ActivityOptions checkedOptions = mCheckedOptions; + BackgroundActivityStartController.BalState balState = mController.new BalState(callingUid, + callingPid, callingPackage, realCallingUid, realCallingPid, null, + originatingPendingIntent, forcedBalByPiSender, mResultRecord, intent, + checkedOptions); + + // call + BalVerdict realCallerVerdict = mController.checkBackgroundActivityStartAllowedBySender( + balState); + balState.setResultForRealCaller(realCallerVerdict); + + // assertions + assertWithMessage(balState.toString()).that(realCallerVerdict.getCode()).isEqualTo( + BAL_ALLOW_FOREGROUND); + } + + // TODO? The app has one of the following services that is bound by the system. These + // services might need to launch a UI. + + @Test + public void testRealCaller_appAllowedByBLPCforOtherProcess() { + // The app has a service that is bound by a different, visible app. The app bound to the + // service must remain visible for the app in the background to start activities + // successfully. + int callingUid = REGULAR_UID_1; + int callingPid = REGULAR_PID_1; + final String callingPackage = REGULAR_PACKAGE_1; + int realCallingUid = REGULAR_UID_2; + int realCallingPid = REGULAR_PID_2; + + // setup state + WindowProcessControllerMap mProcessMap = new WindowProcessControllerMap(); + WindowProcessController otherProcess = Mockito.mock(WindowProcessController.class); + mProcessMap.put(callingPid, mCallerApp); + mProcessMap.put(REGULAR_PID_1_1, otherProcess); + setViaReflection(mService, "mProcessMap", mProcessMap); + when( + mService.getProcessController(eq(realCallingPid), eq(realCallingUid))).thenReturn( + mCallerApp); + when(mService.getBalAppSwitchesState()).thenReturn(APP_SWITCH_ALLOW); + when(mCallerApp.areBackgroundActivityStartsAllowed(anyInt())).thenReturn( + BalVerdict.BLOCK); + when(otherProcess.areBackgroundActivityStartsAllowed(anyInt())).thenReturn( + new BalVerdict(BAL_ALLOW_FOREGROUND, false, "allowed")); + + // prepare call + PendingIntentRecord originatingPendingIntent = mPendingIntentRecord; + BackgroundStartPrivileges forcedBalByPiSender = BackgroundStartPrivileges.NONE; + Intent intent = TEST_INTENT; + ActivityOptions checkedOptions = mCheckedOptions; + BackgroundActivityStartController.BalState balState = mController.new BalState(callingUid, + callingPid, callingPackage, realCallingUid, realCallingPid, null, + originatingPendingIntent, forcedBalByPiSender, mResultRecord, intent, + checkedOptions); + + // call + BalVerdict realCallerVerdict = mController.checkBackgroundActivityStartAllowedBySender( + balState); + balState.setResultForRealCaller(realCallerVerdict); + + // assertions + assertWithMessage(balState.toString()).that(realCallerVerdict.getCode()).isEqualTo( + BAL_ALLOW_FOREGROUND); + } + + @Test + public void testRealCaller_isCompanionApp() { + // The app has a service that is bound by a different, visible app. The app bound to the + // service must remain visible for the app in the background to start activities + // successfully. + int callingUid = REGULAR_UID_1; + int callingPid = REGULAR_PID_1; + final String callingPackage = REGULAR_PACKAGE_1; + int realCallingUid = REGULAR_UID_2; + int realCallingPid = REGULAR_PID_2; + + // setup state + final int realCallingUserId = UserHandle.getUserId(realCallingUid); + when(mService.isAssociatedCompanionApp(eq(realCallingUserId), + eq(realCallingUid))).thenReturn(true); + + // prepare call + PendingIntentRecord originatingPendingIntent = mPendingIntentRecord; + BackgroundStartPrivileges forcedBalByPiSender = BackgroundStartPrivileges.NONE; + Intent intent = TEST_INTENT; + ActivityOptions checkedOptions = mCheckedOptions; + BackgroundActivityStartController.BalState balState = mController.new BalState(callingUid, + callingPid, callingPackage, realCallingUid, realCallingPid, null, + originatingPendingIntent, forcedBalByPiSender, mResultRecord, intent, + checkedOptions); + + // call + BalVerdict realCallerVerdict = mController.checkBackgroundActivityStartAllowedBySender( + balState); + balState.setResultForRealCaller(realCallerVerdict); + + // assertions + assertWithMessage(balState.toString()).that(realCallerVerdict.getCode()).isEqualTo( + BAL_ALLOW_ALLOWLISTED_COMPONENT); + } + + @Test + public void testCaller_balPermission() { + int callingUid = REGULAR_UID_1; + int callingPid = REGULAR_PID_1; + final String callingPackage = REGULAR_PACKAGE_1; + int realCallingUid = REGULAR_UID_2; + int realCallingPid = REGULAR_PID_2; + + // setup state + mController.addBalPermission(callingUid, callingPid); + mController.addBalPermission(callingUid, NO_PID); + + // prepare call + PendingIntentRecord originatingPendingIntent = mPendingIntentRecord; + BackgroundStartPrivileges forcedBalByPiSender = BackgroundStartPrivileges.NONE; + Intent intent = TEST_INTENT; + ActivityOptions checkedOptions = mCheckedOptions; + BackgroundActivityStartController.BalState balState = mController.new BalState(callingUid, + callingPid, callingPackage, realCallingUid, realCallingPid, null, + originatingPendingIntent, forcedBalByPiSender, mResultRecord, intent, + checkedOptions); + + // call + BalVerdict callerVerdict = mController.checkBackgroundActivityStartAllowedByCaller( + balState); + balState.setResultForCaller(callerVerdict); + + // assertions + assertWithMessage(balState.toString()).that(callerVerdict.getCode()).isEqualTo( + BAL_ALLOW_PERMISSION); + } + + @Test + public void testRealCaller_balPermission() { + // BAL allowed by permission. Requires explicit opt-in in options (hidden/not documented!). + int callingUid = REGULAR_UID_1; + int callingPid = REGULAR_PID_1; + final String callingPackage = REGULAR_PACKAGE_1; + int realCallingUid = REGULAR_UID_2; + int realCallingPid = REGULAR_PID_2; + + // setup state + mController.addBalPermission(realCallingUid, realCallingPid); + mController.addBalPermission(realCallingUid, NO_PID); + + // prepare call + PendingIntentRecord originatingPendingIntent = mPendingIntentRecord; + BackgroundStartPrivileges forcedBalByPiSender = BackgroundStartPrivileges.NONE; + Intent intent = TEST_INTENT; + ActivityOptions checkedOptions = mCheckedOptions; + checkedOptions.setPendingIntentBackgroundActivityLaunchAllowedByPermission(true); + BackgroundActivityStartController.BalState balState = mController.new BalState(callingUid, + callingPid, callingPackage, realCallingUid, realCallingPid, null, + originatingPendingIntent, forcedBalByPiSender, mResultRecord, intent, + checkedOptions); + + assertThat(balState.isPendingIntentBalAllowedByPermission()).isTrue(); + + // call + BalVerdict realCallerVerdict = mController.checkBackgroundActivityStartAllowedBySender( + balState); + balState.setResultForRealCaller(realCallerVerdict); + + // assertions + assertWithMessage(balState.toString()).that(realCallerVerdict.getCode()).isEqualTo( + BAL_ALLOW_PERMISSION); + } + + @Test + public void testCaller_sawPermission() { + int callingUid = REGULAR_UID_1; + int callingPid = REGULAR_PID_1; + final String callingPackage = REGULAR_PACKAGE_1; + int realCallingUid = REGULAR_UID_2; + int realCallingPid = REGULAR_PID_2; + + // setup state + when(mService.hasSystemAlertWindowPermission(eq(callingUid), eq(callingPid), + eq(callingPackage))).thenReturn(true); + + // prepare call + PendingIntentRecord originatingPendingIntent = mPendingIntentRecord; + BackgroundStartPrivileges forcedBalByPiSender = BackgroundStartPrivileges.NONE; + Intent intent = TEST_INTENT; + ActivityOptions checkedOptions = mCheckedOptions; + BackgroundActivityStartController.BalState balState = mController.new BalState(callingUid, + callingPid, callingPackage, realCallingUid, realCallingPid, null, + originatingPendingIntent, forcedBalByPiSender, mResultRecord, intent, + checkedOptions); + + // call + BalVerdict callerVerdict = mController.checkBackgroundActivityStartAllowedByCaller( + balState); + balState.setResultForCaller(callerVerdict); + + // assertions + assertWithMessage(balState.toString()).that(callerVerdict.getCode()).isEqualTo( + BAL_ALLOW_SAW_PERMISSION); + } +} diff --git a/services/tests/wmtests/src/com/android/server/wm/BackgroundActivityStartControllerTests.java b/services/tests/wmtests/src/com/android/server/wm/BackgroundActivityStartControllerTests.java index ef131ac337b1..5aa4ba3e1d42 100644 --- a/services/tests/wmtests/src/com/android/server/wm/BackgroundActivityStartControllerTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/BackgroundActivityStartControllerTests.java @@ -62,7 +62,7 @@ import java.util.Map; import java.util.Optional; /** - * Tests for the {@link ActivityStarter} class. + * Tests for the {@link BackgroundActivityStartController} class. * * Build/Install/Run: * atest WmTests:BackgroundActivityStartControllerTests diff --git a/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyTests.java b/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyTests.java index 5965fae74dcc..c77a4d650bb5 100644 --- a/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyTests.java @@ -70,6 +70,8 @@ import android.view.WindowManager; import androidx.test.filters.SmallTest; +import com.android.window.flags.Flags; + import org.junit.Assume; import org.junit.Test; import org.junit.runner.RunWith; @@ -411,6 +413,10 @@ public class DisplayPolicyTests extends WindowTestsBase { @Test public void testUpdateDisplayConfigurationByDecor() { + if (Flags.insetsDecoupledConfiguration()) { + // No configuration update when flag enables. + return; + } doReturn(NO_CUTOUT).when(mDisplayContent).calculateDisplayCutoutForRotation(anyInt()); final WindowState navbar = createNavBarWithProvidedInsets(mDisplayContent); final DisplayPolicy displayPolicy = mDisplayContent.getDisplayPolicy(); diff --git a/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java b/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java index b96f39d7a4e1..85172e08423e 100644 --- a/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java @@ -123,6 +123,7 @@ import com.android.internal.policy.SystemBarUtils; import com.android.internal.statusbar.LetterboxDetails; import com.android.server.statusbar.StatusBarManagerInternal; import com.android.server.wm.DeviceStateController.DeviceState; +import com.android.window.flags.Flags; import libcore.junit.util.compat.CoreCompatChangeRule.DisableCompatChanges; import libcore.junit.util.compat.CoreCompatChangeRule.EnableCompatChanges; @@ -703,6 +704,11 @@ public class SizeCompatTests extends WindowTestsBase { @Test public void testFixedAspectRatioBoundsWithDecorInSquareDisplay() { + if (Flags.insetsDecoupledConfiguration()) { + // TODO (b/151861875): Re-enable it. This is disabled temporarily because the config + // bounds no longer contains display cutout. + return; + } final int notchHeight = 100; setUpApp(new TestDisplayContent.Builder(mAtm, 600, 800).setNotch(notchHeight).build()); @@ -944,6 +950,11 @@ public class SizeCompatTests extends WindowTestsBase { @Test public void testMoveToDifferentOrientationDisplay() { + if (Flags.insetsDecoupledConfiguration()) { + // TODO (b/151861875): Re-enable it. This is disabled temporarily because the config + // bounds no longer contains display cutout. + return; + } setUpDisplaySizeWithApp(1000, 2500); prepareUnresizable(mActivity, -1.f /* maxAspect */, SCREEN_ORIENTATION_PORTRAIT); assertFitted(); @@ -991,6 +1002,11 @@ public class SizeCompatTests extends WindowTestsBase { @Test public void testFixedOrientationRotateCutoutDisplay() { + if (Flags.insetsDecoupledConfiguration()) { + // TODO (b/151861875): Re-enable it. This is disabled temporarily because the config + // bounds no longer contains display cutout. + return; + } // Create a display with a notch/cutout final int notchHeight = 60; final int width = 1000; @@ -1587,6 +1603,11 @@ public class SizeCompatTests extends WindowTestsBase { @EnableCompatChanges({ActivityInfo.OVERRIDE_MIN_ASPECT_RATIO, ActivityInfo.OVERRIDE_MIN_ASPECT_RATIO_MEDIUM}) public void testOverrideMinAspectRatioLowerThanManifest() { + if (Flags.insetsDecoupledConfiguration()) { + // TODO (b/151861875): Re-enable it. This is disabled temporarily because the config + // bounds no longer contains display cutout. + return; + } final DisplayContent display = new TestDisplayContent.Builder(mAtm, 1400, 1800) .setNotch(200).setSystemDecorations(true).build(); mTask = new TaskBuilder(mSupervisor).setDisplay(display).build(); @@ -1934,6 +1955,11 @@ public class SizeCompatTests extends WindowTestsBase { @Test public void testLaunchWithFixedRotationTransform() { + if (Flags.insetsDecoupledConfiguration()) { + // TODO (b/151861875): Re-enable it. This is disabled temporarily because the config + // bounds no longer contains display cutout. + return; + } final int dw = 1000; final int dh = 2500; final int notchHeight = 200; @@ -3757,6 +3783,11 @@ public class SizeCompatTests extends WindowTestsBase { @Test public void testLetterboxDetailsForStatusBar_letterboxNotOverlappingStatusBar() { + if (Flags.insetsDecoupledConfiguration()) { + // TODO (b/151861875): Re-enable it. This is disabled temporarily because the config + // bounds no longer contains display cutout. + return; + } // Align to center so that we don't overlap with the status bar mAtm.mWindowManager.mLetterboxConfiguration.setLetterboxVerticalPositionMultiplier(0.5f); final DisplayContent display = new TestDisplayContent.Builder(mAtm, 1000, 2800) @@ -4076,6 +4107,11 @@ public class SizeCompatTests extends WindowTestsBase { @Test public void testPortraitCloseToSquareDisplayWithTaskbar_notLetterboxed() { + if (Flags.insetsDecoupledConfiguration()) { + // TODO (b/151861875): Re-enable it. This is disabled temporarily because the config + // bounds no longer contains display cutout. + return; + } // Set up portrait close to square display setUpDisplaySizeWithApp(2200, 2280); final DisplayContent display = mActivity.mDisplayContent; @@ -4102,6 +4138,11 @@ public class SizeCompatTests extends WindowTestsBase { @Test public void testApplyAspectRatio_activityAlignWithParentAppVertical() { + if (Flags.insetsDecoupledConfiguration()) { + // TODO (b/151861875): Re-enable it. This is disabled temporarily because the config + // bounds no longer contains display cutout. + return; + } // The display's app bounds will be (0, 100, 1000, 2350) final DisplayContent display = new TestDisplayContent.Builder(mAtm, 1000, 2500) .setCanRotate(false) @@ -4116,6 +4157,11 @@ public class SizeCompatTests extends WindowTestsBase { } @Test public void testApplyAspectRatio_activityCannotAlignWithParentAppVertical() { + if (Flags.insetsDecoupledConfiguration()) { + // TODO (b/151861875): Re-enable it. This is disabled temporarily because the config + // bounds no longer contains display cutout. + return; + } // The display's app bounds will be (0, 100, 1000, 2150) final DisplayContent display = new TestDisplayContent.Builder(mAtm, 1000, 2300) .setCanRotate(false) @@ -4131,6 +4177,11 @@ public class SizeCompatTests extends WindowTestsBase { @Test public void testApplyAspectRatio_activityAlignWithParentAppHorizontal() { + if (Flags.insetsDecoupledConfiguration()) { + // TODO (b/151861875): Re-enable it. This is disabled temporarily because the config + // bounds no longer contains display cutout. + return; + } // The display's app bounds will be (100, 0, 2350, 1000) final DisplayContent display = new TestDisplayContent.Builder(mAtm, 2500, 1000) .setCanRotate(false) @@ -4145,6 +4196,11 @@ public class SizeCompatTests extends WindowTestsBase { } @Test public void testApplyAspectRatio_activityCannotAlignWithParentAppHorizontal() { + if (Flags.insetsDecoupledConfiguration()) { + // TODO (b/151861875): Re-enable it. This is disabled temporarily because the config + // bounds no longer contains display cutout. + return; + } // The display's app bounds will be (100, 0, 2150, 1000) final DisplayContent display = new TestDisplayContent.Builder(mAtm, 2300, 1000) .setCanRotate(false) @@ -4393,6 +4449,11 @@ public class SizeCompatTests extends WindowTestsBase { @Test public void testUpdateResolvedBoundsPosition_alignToTop() { + if (Flags.insetsDecoupledConfiguration()) { + // TODO (b/151861875): Re-enable it. This is disabled temporarily because the config + // bounds no longer contains display cutout. + return; + } final int notchHeight = 100; final DisplayContent display = new TestDisplayContent.Builder(mAtm, 1000, 2800) .setNotch(notchHeight) diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskFragmentTest.java b/services/tests/wmtests/src/com/android/server/wm/TaskFragmentTest.java index 6b1bf26bfdff..3c5b12c68e1c 100644 --- a/services/tests/wmtests/src/com/android/server/wm/TaskFragmentTest.java +++ b/services/tests/wmtests/src/com/android/server/wm/TaskFragmentTest.java @@ -898,6 +898,32 @@ public class TaskFragmentTest extends WindowTestsBase { } } + @Test + public void testSetResumedActivity() { + // Setup two activities in ActivityEmbedding split. + final Task task = createTask(mDisplayContent); + final TaskFragment taskFragmentLeft = new TaskFragmentBuilder(mAtm) + .setParentTask(task) + .createActivityCount(1) + .build(); + final TaskFragment taskFragmentRight = new TaskFragmentBuilder(mAtm) + .setParentTask(task) + .createActivityCount(1) + .build(); + taskFragmentRight.setAdjacentTaskFragment(taskFragmentLeft); + taskFragmentLeft.setAdjacentTaskFragment(taskFragmentRight); + final ActivityRecord appLeftTop = taskFragmentLeft.getTopMostActivity(); + final ActivityRecord appRightTop = taskFragmentRight.getTopMostActivity(); + + // Ensure the focused app is updated when the right activity resumed. + taskFragmentRight.setResumedActivity(appRightTop, "test"); + assertEquals(appRightTop, task.getDisplayContent().mFocusedApp); + + // Ensure the focused app is updated when the left activity resumed. + taskFragmentLeft.setResumedActivity(appLeftTop, "test"); + assertEquals(appLeftTop, task.getDisplayContent().mFocusedApp); + } + private WindowState createAppWindow(ActivityRecord app, String name) { final WindowState win = createWindow(null, TYPE_BASE_APPLICATION, app, name, 0 /* ownerId */, false /* ownerCanAddInternalSystemWindow */, new TestIWindow()); |